/* **********************************
 * *                                *  
 * *          DSP Library           *
 * *                                *  
 * **********************************  
 * file: libs/dsp/dsp_open_for_io.c
 *
 * COPYRIGHT (c) 1992, 1994, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
 *                         2004, 2005 BY
 *         SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.  
 *     
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED. 
 */
#define DSP_SCSI_INCLUDED 1

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#ifdef IRIX
#include <sys/dsreq.h>
#endif
#ifdef HPUX
#include <sys/scsi.h>
#endif
#ifdef LINUX
#include <sys/param.h>
#include <sys/ioctl.h>
#include <scsi/sg.h>
#endif

#include "sigtypes.h"
#include "setup.h"
#include "rda.h"
#include "user_lib.h"
#include "dsp.h"
#include "dsp_lib.h"

#include "dsp_def.h"

static MESSAGE DspOpenScsiIo(Dsp *pDsp_a );
static MESSAGE scsi_to_status( UINT1 scsistatus_a ) ;
static MESSAGE DspOpenNativeIo(Dsp *pDsp_a );

/* ======================================================================
 * This subroutine must be called before any I/O to the signal processor
 * can be performed.  If there is an error, it will return NULL, and
 * set the message value in istatus_a.
 *
 * Exit parameters:
 *   Pointer to DSP structure if successful, or NULL (0) if error
 */
Dsp *DspOpenForIo
( MESSAGE *istatus_a )		/* MESSAGE return */
{
  Dsp *pdsp ;

  /* First open for information.
   */
  pdsp = DspOpen( istatus_a ) ;
  if( NULL == pdsp ) return( NULL ) ;

  *istatus_a = DspAuthorizeIo( pdsp );

  if (SS_NORMAL == *istatus_a ) return pdsp;

  /* Close on error */
  DspClose( pdsp ) ; return( NULL ) ;
}
/* ============================================================
 * Raise authorization from info to I/O mode.  Called with an
 * already opened Dsp.  Returns error if it cannot authorize I/O,
 * but the library remains open.
 */
MESSAGE DspAuthorizeIo
( Dsp *pDsp_a )
{
  MESSAGE iStatus;
  /* Initialize the SOPRM state
   */
  memset( &soprm_c, 0, sizeof(soprm_c) ) ;

  iunfold_c       = PRF_FIXED ;
  lAngleSynced_c  = FALSE ;
  lgparm_valid_c  = FALSE ;
  lSoprmValid_c   = FALSE ;
  iTTYCount_c     = 0 ;
  lpw_setting_valid_c  = FALSE ;
  lpol_setting_valid_c = FALSE ;
  
  switch (pDsp_a->interface)
    {
      /* =============== DSP's on a SCSI Bus ===============
       */
    case DSPIFACE_SCSI:
      iStatus = DspOpenScsiIo( pDsp_a );
      break ;
      
      /* =============== Native DSP on Host Machine ===============
       */
    case DSPIFACE_NATIVE:
      iStatus = DspOpenNativeIo( pDsp_a );
      break ;
      
      /* =============== Socket DSP on Remote Machine ===============
       */
    case DSPIFACE_SOCKET:
      {
	iStatus = WriteSocketData1( pDsp_a->idsplu, "OPEN|", 5, FALSE );
	if (iStatus==SS_NORMAL)
	  {
	    /* Read back the acknowledge */
	    iStatus = ReadSocketAck( pDsp_a->idsplu, FALSE, DSP_TIMEOUT_MS );
	    if (iStatus==SS_NORMAL) pDsp_a->iopen |= DSP_OPEN_FOR_IO ;
	  }
      }
      break ;
      
      /* =============== Other Interface DSP's ===============
       */
    default:
      iStatus = DSP_CASE_IFACE ;
    }
  
  
  return iStatus ;
  
}
/* ==========================================================
 * Open port for SCSI operation.  All returns that are not SS_NORMAL
 * are failures to open.
 */
static MESSAGE DspOpenScsiIo
(Dsp *pDsp_a )
{
  MESSAGE iStatus;
  UINT1 gkdata[36], err ; SINT4 fildes ;

  iStatus = SS_NORMAL ;
  
  fildes = open( dspdrv_setup_c.sdevice, O_RDWR, 0 ) ;
  if( fildes < 0 ) {
    if( errno == ENOENT )
      iStatus = iMessageMod_va( DSP_DEV_ENOENT, 1, dspdrv_setup_c.sdevice ) ;
    else
      iStatus = errno_to_message_va( errno, 1, dspdrv_setup_c.sdevice ) ;
    return( iStatus ) ;
  }
  
  if( lockf( fildes, F_TLOCK, 0 ) ) {
    iStatus = iMessageMod_va( USER_DEVALLOC , 1, dspdrv_setup_c.sdevice ) ;
    close( fildes ) ; return( iStatus ) ;
  }
  pDsp_a->iopen |= DSP_OPEN_FOR_IO ;
  pDsp_a->idsplu = fildes ;      
  
  /* Send an inquiry command to the SCSI target as the first
   * step in making a connection with it.
   */
  memset( gkdata, 0, sizeof(gkdata) ) ;
  
#ifdef HPUX /* ------------------------- HP Code ------------------------- */
  {
    struct sctl_io gkdesc ; SINT4 ists ;
    
    memset( &gkdesc, 0, sizeof(gkdesc) ) ;
    gkdesc.data_length    = sizeof(gkdata) ;
    gkdesc.data           = (void *)&gkdata ;
    gkdesc.max_msecs      = DSP_TIMEOUT_MS ;
    gkdesc.flags          = SCTL_READ ;
    gkdesc.cdb_length     = 6 ;
    
    gkdesc.cdb[0]  = GK_SCSI_INQUIRY ;
    gkdesc.cdb[4]  = sizeof(gkdata) ;
    
    ists = ioctl( pDsp_a->idsplu, SIOC_IO, &gkdesc ) ;
    if( ists < 0 ) {
      iStatus = errno_to_message_va( errno, 1, "dsp_open_for_io()" ) ;
      return iStatus;
    }
    iStatus = hpux_scsi_status(&gkdesc) ;
    if( iStatus != SS_NORMAL ) return iStatus;
  }
#endif /* #ifdef HPUX */
  
#ifdef IRIX /* ------------------------- SGI Code ------------------------- */
  {
    char cdb[6] = { GK_SCSI_INQUIRY, 0, 0, 0, sizeof(gkdata), 0 } ;
    struct dsreq gkdesc ; SINT4 ists ;
    
    memset( &gkdesc, 0, sizeof(gkdesc) ) ;
    gkdesc.ds_flags       = DSRQ_READ | DSRQ_DISC ;
    gkdesc.ds_time        = DSP_TIMEOUT_MS ;
    gkdesc.ds_cmdbuf      = (caddr_t)&cdb ;
    gkdesc.ds_cmdlen      = sizeof(cdb) ;
    gkdesc.ds_databuf     = (caddr_t)&gkdata ;
    gkdesc.ds_datalen     = sizeof(gkdata) ;
    
    ists = ioctl( pDsp_a->idsplu, DS_ENTER, &gkdesc ) ;
    if( ists < 0 ) {
      iStatus = errno_to_message_va( errno, 1, "dsp_open_for_io()" ) ;
      return iStatus;
    }
    iStatus = irix_scsi_status(&gkdesc) ;
    if( iStatus != SS_NORMAL ) return iStatus;
  }
#endif /* #ifdef IRIX */
  
#ifdef LINUX /* ----------------------- LINUX Code ----------------------- */
  {
    UINT1 cdb[6] = { GK_SCSI_INQUIRY, 0, 0, 0, sizeof(gkdata), 0 } ;
    int itimeout = (HZ * DSP_TIMEOUT_MS)/1000 ; SINT4 ists ;
    
    ists = ioctl( pDsp_a->idsplu, SG_SET_TIMEOUT, &itimeout ) ;
    if( ists < 0 ) {
      iStatus = errno_to_message_va( errno, 1, "dsp_open_for_io()" ) ;
      
    } else {
      iStatus = linux_scsi_command
	( pDsp_a->idsplu, &gkdata, sizeof(gkdata), TRUE, cdb ) ;
    }
    if( iStatus != SS_NORMAL ) return iStatus;
  }
#endif /* #ifdef LINUX */
  
  /* Check for type mismatch
   */
  err = 0 ;
  switch( pDsp_a->itype)
    {
    case DSP_TYPE_RVP6:
      if( strncmp( (char *)&gkdata[16], "RVP6    ", 8 ) ) err++ ;
      break ;
    case DSP_TYPE_RVP6_2:
      if( strncmp( (char *)&gkdata[16], "RVP6-2  ", 8 ) ) err++ ;
      break ;
    case DSP_TYPE_RVP7:
      if( strncmp( (char *)&gkdata[16], "RVP7    ", 8 ) ) err++ ;
      break ;
    case DSP_TYPE_RVP8:
      if( strncmp( (char *)&gkdata[16], "RVP8    ", 8 ) ) err++ ;
      break ;
    }
  if( err )
    {
      /* Encode the vendor and product ID into the signal so
       * that we get some clue of what went wrong.  Be careful
       * not to propagate possible junk characters into the
       * message.
       */
      char sIden[80], sVend[9], sProd[17], *sPtr ;
      
      sPtr = nullify( (char *)&gkdata[ 8],  8 ) ;
      strncpy_filter( sVend, sPtr, sizeof(sVend) ) ;
      
      sPtr = nullify( (char *)&gkdata[16], 16 ) ;
      strncpy_filter( sProd, sPtr, sizeof(sProd) ) ;

      sprintf( sIden, "Vend:'%s' Prod:'%s'", sVend, sProd ) ;
      
      iStatus = iMessageMod_va( DSP_TYPE_MISMATCH, 1, sIden ) ;
      
      return iStatus;
    }
  return iStatus;
}

/* ======================================================================
 * Generic SCSI Basic Read/Write Support Routines.
 *
 * Some platforms require support merely to perform direct Read/Write
 * operations with the DSP.
 */
#ifdef LINUX /* ----------------------- LINUX Code ----------------------- */
MESSAGE linux_scsi_command
( int fd_a, void *pbuf_a, SINT4 buflen_a, UINT2 lread_a, UINT1 cdb_a[6] )
{
  int istat ; MESSAGE istatus = SS_NORMAL ;
  SINT4 ilen, ireply_len ;

  /* Make up the command that is to be written to the DSP.  LINUX
   * requires that the user data be appended onto the same buffer that
   * has the SG structure and the SCSI command buf contents (yuck).
   */
  {
    struct {
      struct sg_header hdr ;
      char cdb[6] ;
      char writebuf[SCSI_MAX_IOLEN] ;
    } scsicmd ;

    memset( &scsicmd.hdr, 0, sizeof(scsicmd.hdr) ) ;
    memcpy( scsicmd.cdb, cdb_a, 6 ) ;

    if( lread_a ) {
      scsicmd.hdr.pack_len  = sizeof(struct sg_header) + 6 ;
      scsicmd.hdr.reply_len = sizeof(struct sg_header) + buflen_a ;
    } else {
      scsicmd.hdr.pack_len  = sizeof(struct sg_header) + 6 + buflen_a ;
      scsicmd.hdr.reply_len = sizeof(struct sg_header) ;
      memcpy( scsicmd.writebuf, pbuf_a, buflen_a ) ;
    }
    ireply_len = scsicmd.hdr.reply_len ;

    ilen = scsicmd.hdr.pack_len ; errno = 0 ;
    istat = write( fd_a, &scsicmd, ilen ) ;
    if( (istat < 0) || (istat != ilen) || scsicmd.hdr.result ) {
      if( errno ) istatus = errno_to_message( errno ) ;
      else        istatus = errno_to_message( scsicmd.hdr.result ) ;
    }
  }

  /* If the command was written properly, then do a read to complete
   * the operation and get status.  Move the data into the user buffer
   * if this was a SCSI read operation.
   */
  if( istatus == SS_NORMAL ) {
    struct {
      struct sg_header hdr ;
      char readbuf[SCSI_MAX_IOLEN] ;
    } scsiget ;

    errno = 0 ; memset( &scsiget.hdr, 0, sizeof(scsiget.hdr) ) ;
    istat = read( fd_a, &scsiget, ireply_len ) ;
    if( (istat < 0) || (istat != ireply_len) || scsiget.hdr.result ) {
      if( errno ) {
	istatus = errno_to_message( errno ) ;

      } else {
	/* Convert the EBUSY error, which seems to result from a
	 * device timeout, into a real timeout message.
	 */
	if( scsiget.hdr.result == EBUSY )
	  istatus = USER_DEVTIMEOUT ;
	else
	  istatus = errno_to_message( scsiget.hdr.result ) ;
      }
    } else {
      istatus = scsi_to_status( scsiget.hdr.sense_buffer[0] ) ;
      if( lread_a ) memcpy( pbuf_a, scsiget.readbuf, buflen_a ) ;
    }
  }
  return( istatus ) ;
}
#endif /* #ifdef LINUX */

/* ======================================================================
 * Given an I/O control structure from a call to a Generic SCSI driver,
 * return the message for it.
 */
static MESSAGE scsi_to_status( UINT1 scsistatus_a ) {
  switch( scsistatus_a & 0x1F ) {
  case 0x00: return( SS_NORMAL      ) ;
  case 0x01: return( DSP_SCSI_00001 ) ;
  case 0x02: return( DSP_SCSI_00010 ) ;
  case 0x04: return( DSP_SCSI_00100 ) ;
  case 0x08: return( DSP_SCSI_01000 ) ;
  case 0x0A: return( DSP_SCSI_01010 ) ;
  case 0x0C: return( DSP_SCSI_01100 ) ;
  case 0x11: return( DSP_SCSI_10001 ) ;
  case 0x14: return( DSP_SCSI_10100 ) ;
  default:   return( DSP_SCSI_XXXXX ) ;
  }
}

#ifdef HPUX /* ------------------------- HP Code ------------------------- */
MESSAGE hpux_scsi_status (struct sctl_io *sctl ) {
  MESSAGE istatus ;
  switch(sctl->cdb_status) {
  case 0x00:
    istatus = SS_NORMAL ; break ;
  default:
    istatus = DSP_SCSI_XXXXX ; break ;

  case SCTL_INCOMPLETE:
  case SCTL_SELECT_TIMEOUT:
    istatus = USER_DEVTIMEOUT ; break ;

  case S_CHECK_CONDITION:
    istatus = scsi_to_status( sctl->sense_status ) ;
  }
  return( istatus ) ;
}
#endif /* #ifdef HPUX */

#ifdef IRIX /* ------------------------- SGI Code ------------------------- */
MESSAGE irix_scsi_status (struct dsreq *ds )
{
  MESSAGE istatus ;
  switch(RET(ds))
    {
    case 0x00:
      istatus = SS_NORMAL ; break ;
    case DSRT_TIMEOUT:
    case DSRT_HOST:
      istatus = USER_DEVTIMEOUT ; break ;
    default:
      istatus = scsi_to_status( STATUS(ds) ) ;
    }
  return( istatus ) ;
}
#endif /* #ifdef IRIX */

/* ==========================================================
 * Open port for Native I/O operation.  All returns that are not SS_NORMAL
 * are failures to open.
 */
static MESSAGE DspOpenNativeIo
(Dsp *pDsp_a )
{
  MESSAGE iStatus;
  SINT4 fildes ; char sDevice[] = RDA_DEV_TOPPATH "/" RDA_DEV_RVPUSER_W ;
  
  fildes = open( sDevice, O_RDWR, 0 ) ;
  if( fildes < 0 )
    {
      if( errno == ENOENT )
	iStatus = iMessageMod_va( DSP_DEV_ENOENT, 1, sDevice ) ;
      else
	iStatus = errno_to_message_va( errno, 1, sDevice ) ;
      return iStatus;
    }
  if( lockf( fildes, F_TLOCK, 0 ) ) 
    {
      iStatus = iMessageMod_va( USER_DEVALLOC , 1, sDevice ) ;
      close( fildes ) ; 
      return iStatus;
    }
  if( ioctl( fildes, RVPIOC_SETIMEOUT, DSP_TIMEOUT_MS ) ) 
    {
      iStatus = errno_to_message_va( errno, 1, "DspOpenForIo" ) ;
      lockf( fildes, F_ULOCK, 0 ) ; close( fildes ) ;
      return iStatus ;
    }
  pDsp_a->iopen |= DSP_OPEN_FOR_IO ;
  pDsp_a->idsplu = fildes ;
  return SS_NORMAL;
}

/* ======================================================================
 * Routine to check that the range mask spacing of the signal
 * processor matches the IRIS setup values.
 */
MESSAGE DspCheckMaskSpacing
( Dsp *dsp_a )
{
  MESSAGE istatus = SS_NORMAL ;

  /* Only do the hardware check if our processor supports it.
   * Otherwise just return as if everything is okay (the old
   * behavior).
   */
  if( (dsp_a->itype == DSP_TYPE_RVP8) ||
      ( ( dsp_a->itype == DSP_TYPE_RVP7               ) &&
	( SS_NORMAL    == dspr_pram_internal( dsp_a ) ) &&
	( dsp_cmp_version( &gparm_c, 23, 5 ) >= 0   ) )) {

    /* Read back the four range mask spacings, and compare with the
     * IRIS setup values.
     */
    UINT2 iSpacings[4] ;
    istatus = dspr_rback( dsp_a, RBACK_MASKRES, iSpacings, 4 ) ;

    if( istatus == SS_NORMAL ) {
      SINT4 iPw, iwidcnt = MAX( 1, MIN( 4, dspdrv_setup_c.iwidcnt ) ) ;

      for( iPw=0 ; iPw < iwidcnt ; iPw++ ) {
	SINT4 iIRISres = dspdrv_setup_c.iRangeMaskSpacing[iPw];
	SINT4 iDSPres  = (SINT4)(UINT4)iSpacings[iPw] ;

	if( 1 < abs( iIRISres - iDSPres ) ) {
	  char sText[80] ;
	  sprintf( sText, "PW:%d, IRIS:%.2f, DSP:%.2f",
		   iPw, 0.01*(FLT4)iIRISres, 0.01*(FLT4)iDSPres ) ;
	  istatus = iMessageMod_va( DSP_RANGE_MASK_ERR, 1, sText ) ;
	  break ;
	}
      }
    }
  }
  return( istatus ) ;
}
/* ======================================================================
 * Routine to check that the maximum PRFs and pulsewidth control signals
 * are set the same in IRIS and in the processor. 
 */
MESSAGE DspCheckPrfLimits
( Dsp *pDsp_a )
{
  MESSAGE iStatus = SS_NORMAL ;

  /* Only check on RVP7 and later.
   * Otherwise just return as if everything is okay (the old behavior).
   */
  if ( (pDsp_a->itype >= DSP_TYPE_RVP7) &&
       (SS_NORMAL    == dspr_pram_internal( pDsp_a ) ) )
    {
      UINT2 *pMinPrts =  &(gparm_c.ipwmin_0);
      SINT4 iPw, iwidcnt = MAX( 1, MIN( 4, dspdrv_setup_c.iwidcnt ) ) ;

      for( iPw=0 ; iPw < iwidcnt ; iPw++ ) 
	{
	  SINT4 iRvpPrf  = (SINT4)(UINT4)idsp_prt( pDsp_a, pMinPrts[iPw] );
	  SINT4 iIrisPrf = dspdrv_setup_c.imaxprf[iPw];
	  SINT2 iRvpBits = 0x000f & ( gparm_c.ipw_bits >> 4*iPw );
	  SINT2 iIrisBits= dspdrv_setup_c.ipwbits[iPw];
	  if ( abs(iIrisPrf-iRvpPrf ) > 2 )
	    {
	      char sText[80] ;
	      sprintf( sText, "PW:%d, IRIS:%d Hz, RVP:%d Hz", iPw, iIrisPrf, iRvpPrf );
	      iStatus = iMessageMod_va( DSP_MAX_PRF_ERR, 1, sText ) ;
	      break;
	    }
	  if ( iIrisBits != iRvpBits )
	    {
	      char sText[80] ;
	      sprintf( sText, "PW:%d, IRIS:%d, RVP:%d", iPw, iIrisBits, iRvpBits );
	      iStatus = iMessageMod_va( DSP_PW_CONT_ERR, 1, sText ) ;
	      break;
	    }
	}
    }
  return( iStatus ) ;
}
