/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = SCSIINIT.C
 *
 * DESCRIPTIVE NAME = IBM2SCSI.ADD - Adapter Driver for IBM SCSI adapters.
 *                    Initialization routines.
 *
 *
 * VERSION = V2.0
 *
 * DATE
 *
 * DESCRIPTION  Handle initialization of the adapters and devices.  This
 *              code is discarded so it should be linked last.
 *
*/

/*----------------------*/
/* System include files */
/*----------------------*/

#define INCL_NOBASEAPI
#define INCL_NOPMAPI
#include "os2.h"
#include "infoseg.h"
#include "dskinit.h"

/*----------------------*/
/* ADD include files    */
/*----------------------*/

#include "strat2.h"
#include "dhcalls.h"
#include "iorb.h"
#include "scb.h"
#include "reqpkt.h"
#include "abios.h"
#include "scsi.h"

/*----------------------*/
/* Local include files  */
/*----------------------*/

#include "mmscb.h"
#include "delivery.h"
#include "scsiadd.h"
#include "scsiextn.h"
#include "scsipro.h"

/*-----------------------*/
/* some local prototypes */
/*-----------------------*/

VOID NEAR putmsg(UCHAR *);
VOID NEAR sprntf ( PSZ buf, PSZ fmt, ... );
VOID NEAR Code_End() {};          /* for end of code  */

/*************************************************************************/
/*                                                                       */
/* SCSIInit - Intialize the SCSI subsystem                               */
/*                                                                       */
/*    This routine initializes SCSI ABIOS and gets device information    */
/*    necessary to build the device tables neede to operate the device.  */
/*                                                                       */
/*       Entry:  None                                                    */
/*                                                                       */
/*       Exit:   0, if all OK, 1 if fail loading, 2 if silent load fail. */
/*                                                                       */
/*       Note:   All commands except INIT are returned with invalid      */
/*               command error.                                          */
/*                                                                       */
/*************************************************************************/


USHORT SCSIInit()

{

   USHORT rc, i, j, k, UnitCount;
   USHORT rflag, retcode;
   BOOL   done;

   retcode = 0;


   /*--------------------------------------------*/
   /* For verbose mode print the signon message  */
   /*--------------------------------------------*/

   if (verbose) {
      putmsg("IBM SCSI Adapter OS/2 2.1 Driver - (941019)");
   }

   INFMSG("Entered SCSI initialization\n\r");
   free = (NPBYTE)MemPool;

   /*----------------------------------------------------------------*/
   /* Determine the phys address of our data segment.  Then calc the */
   /* phys addresses of the init time SCB and TSB.  Then initialize  */
   /* them as far as we can.                                         */
   /*----------------------------------------------------------------*/

   DevHelp_VirtToPhys((PVOID)&SCSIADDHeader, (PULONG)&ppData);

   ppinitSCB = VirtToPhys((PBYTE)&initSCB);
   ppinitTSB = VirtToPhys((PBYTE)&initTSB);

   initSCB.ppTSB             = ppinitTSB;
   initSCB.ppNxtSCB          = 0L;
   initSCB.EXT.BLK.BlockCnt  = 0;
   initSCB.EXT.BLK.BlockSize = 0;


   /*-------------------------------*/
   /* Init the initialization IORB. */
   /*-------------------------------*/

   pInitIORB = (PIORB)&initIORB;
   IORBWRKSP(pInitIORB,cRetries) = 1;

   if (InitSCSISubsystem()) return(ERROR+FATAL);

   if (!cDevices) {               /* if no devices found do a quiet fail  */
      return(ERROR+NONFATAL);
   }

   /*----------------------------------------------------------------*/
   /* If we get here then there are no errors and there are devices. */
   /*----------------------------------------------------------------*/

   BuildUnitHandleTable();
   BuildSCBPool();
   InitTimerPool();
   if (ADDStatus & ADDTRACE) {
      InitTraceBuffer();
      TraceIORBs = TRUE;
   }
   DevHelp_RegisterDeviceClass(ADDNAME, (PFN)IORBEntry, 0, 1, &ADDHandle);
}


/*************************************************************************/
/*                                                                       */
/* InitSCSISubsystem - Initialize the SCSI Subsystem.                    */
/*                                                                       */
/*    This routine begins the initialization process.                    */
/*                                                                       */
/*       Entry: none                                                     */
/*                                                                       */
/*       Exit:   0 if no error, non-0 otherwise.                         */
/*                                                                       */
/*************************************************************************/

USHORT InitSCSISubsystem()

{
   USHORT       i, j, rc, bmask, ai;
   PSCSIDATATBL pSCSITbl;
   USHORT       cAdp;
   PSCSIADPINFO pAdpInfo;
   SCB          SCB;
   ULONG        ppSCB;
   PDIR         pDIR;
   PBYTE        pByte;
   USHORT       LID;
   USHORT       QSize;
   NPDCB        npDCB;
   UCHAR        slotinfo;

   pSCSITbl = NULL;

   /*---------------------------------------------------------*/
   /* Get addressability to the SCSI data area in the EBDA.   */
   /*---------------------------------------------------------*/

   if (GetSCSIDataPtr(&pSCSITbl)) {
      ERRMSG("InitSCSISubsystem: Error finding SCSI data table in EBDA.\n\r");
      return(ERROR);
   }

   /*----------------------------------------------*/
   /* If we find the SCSI data area then continue  */
   /*----------------------------------------------*/

   if (pSCSITbl) {

      /*--------------*/
      /* Hook the IRQ */
      /*--------------*/

      if (DevHelp_SetIRQ((NPFN)SCSIInterruptHdlr , 0x0e, 1)) {
         ERRMSG("InitSCSISubsystem: Unable to set IRQ.\n\r");
         return(ERROR);
      }

      ADDStatus |= IRQSET;

      /*----------------------------------------*/
      /* Get control info from the data table.  */
      /*----------------------------------------*/

      cAdp     = pSCSITbl->cAdapters;
      pAdpInfo = (PSCSIADPINFO)((PBYTE)pSCSITbl + sizeof(SCSIDATATBL));

      ledmask_on  = pSCSITbl->ledmask & LEDMASK;
      ledmask_off = ~ledmask_on;

      INFMSG1("Number of adapters in system ==> %w\n\r",cAdp);

      /*--------------------------------------------------------*/
      /* Now handle the case of selectable boot.  EBDA:ec will  */
      /* point to a byte which is the selectable boot switch    */
      /* drive.  If !0 and in the range of the SCSI drives then */
      /* we must swap those drives later.                       */
      /*--------------------------------------------------------*/

      pByte = MAKEP(sEBDA,0x00ec);
      SelectableBootDrv = *pByte;
      FirstSCSIDrive    = pSCSITbl->FirstDrive;

      if (SelectableBootDrv && FirstSCSIDrive == 0x80 &&
          0x80 < SelectableBootDrv &&
          SelectableBootDrv <= (0x80 + pSCSITbl->cDrives)) {
         ADDStatus |= SWITCHDRIVES;
      }

      /*-----------------------------------------------------------------*/
      /* Now loop through the adapter instance records in the data area. */
      /* bmask is used to ignore adapters that the user may want to      */
      /* control himself.                                                */
      /*-----------------------------------------------------------------*/

      bmask = 0x0001;             /* bit mask for skipping adapters  */

      for (i=0; i<cAdp; i++) {

         if (!(bmask&IgnoreMask) && (((PSCSIADPINFO)pAdpInfo)->status & USABLE)) {

            ai = cAdapters++;

            /*-----------------------------------------------------------*/
            /* Assign the card I/O address according to the type of SCSI */
            /* table found in the EBDA.  Also get slot info for SCSI 2.  */
            /*-----------------------------------------------------------*/

            if (ADDStatus & SCSI2TABLE) {                                       /*@D2623*/
               ACBTbl[ai].baseIO = ((PSCSIADPINFO2)pAdpInfo)->cardio;           /*@D2623*/
               slotinfo = ((PSCSIADPINFO2)pAdpInfo)->slot;                      /*@D2623*/
               if (slotinfo & SLOT_PSEUDO || !(slotinfo & SLOT_NUMBER)) {       /*@D2623*/
                  ACBTbl[ai].slot = 0;                                          /*@D2623*/
               }                                                                /*@D2623*/
               else {                                                           /*@D2623*/
                  ACBTbl[ai].slot = slotinfo & SLOT_NUMBER;                     /*@D2623*/
               }                                                                /*@D2623*/
            }                                                                   /*@D2623*/
            else {                                                              /*@D2623*/
               ACBTbl[ai].baseIO = IOBASEADDR +                                 /*@D2623*/
                  (USHORT)((PSCSIADPINFO)pAdpInfo)->cardio;                     /*@D2623*/
               ACBTbl[ai].slot   = -1;                                          /*@D2623*/
            }                                                                   /*@D2623*/

            if (!InitAdapter(pAdpInfo, i, ai)) {

               /*----------------------------------------------------------*/
               /* Scan the DIRs for devices attached to this adapter.      */
               /* This will build the DCBs for all the devices attached to */
               /* this adapter.  ScanDIRs will also do any required mode   */
               /* sets for each device.                                    */
               /*----------------------------------------------------------*/

               ACBTbl[ai].cDev = 0;

               pDIR=(PDIR)MAKEP(sEBDA, (OFFSETOF(pSCSITbl)+pSCSITbl->DriveTbl));
               if (ScanDIRs(pDIR, pSCSITbl->cDrives, i, ai))
                  return(ERROR);

               pDIR=(PDIR)MAKEP(sEBDA, (OFFSETOF(pSCSITbl)+pSCSITbl->OtherTbl));
               if (ScanDIRs(pDIR, pSCSITbl->cOther, i, ai)) return(ERROR);

               /*------------------------------------------------------------*/
               /* if mode is to be enabled then copy the needed information  */
               /* from the logical device assignment table to each DCB for   */
               /* the adapter.  This info will be used later to initialize   */
               /* move mode.  After that reserve space in the free area for  */
               /* the pipes.  See memory map in scsidata.c.  If there are no */
               /* devices attached then clear the move mode enable bit.      */
               /*------------------------------------------------------------*/

               if (ACBTbl[ai].status & ENABLEMOVEMODE) {

                  if (ACBTbl[ai].cDev) {
                     QSize =
                        (ACBTbl[ai].cCmds - ACBTbl[ai].cDev) / ACBTbl[ai].cDev;
                     npDCB = ACBTbl[ai].np1stDCB;

                     for (j=0; j<ACBTbl[ai].cDev; j++,npDCB++) {
                        npDCB->UnitInfo.QueuingCount = QSize;
                     }

                     free += 2*PipeSize + 2*sizeof(CTRLAREA) + 4;
                     ACBTbl[ai].cBus = 2;                         /* 2 busses */
                  }
                  else {
                     ACBTbl[ai].status &= ~ENABLEMOVEMODE;
                  }
               }
            }
         }
         bmask << 1;

         if (ADDStatus & SCSI2TABLE) {                                          /*@D2623*/
            (PBYTE)pAdpInfo += sizeof(SCSIADPINFO2);         /* REN 9164 */
                                                             /*@D2623*/
         }                                                                      /*@D2623*/
         else {                                                                 /*@D2623*/
            (PBYTE)pAdpInfo += sizeof(SCSIADPINFO);          /* REN 9164 */
                                                             /*@D2623*/
         }                                                                      /*@D2623*/
      }
   }

   return(NOERROR);    /* no error found (may be no SCSI present)  */

}

/************************************************************************/
/*                                                                      */
/* InitAdapter                                                          */
/*                                                                      */
/*    Initialize the specified adapter.                                 */
/*                                                                      */
/*    Entry:  InitAdapter()                                             */
/*                                                                      */
/*    Exit:                                                             */
/*                                                                      */
/************************************************************************/

USHORT InitAdapter(PSCSIADPINFO pAdpInfo, USHORT RelAdpNum, USHORT ai)

{

   USHORT LID;
   UCHAR ic;

   /*-----------------------------------------------------------------*/
   /* Get the LID for this adpater.  Forget about the return code, we */
   /* never use the LID.                                              */
   /*-----------------------------------------------------------------*/
   DevHelp_GetLIDEntry(DEVID_SCSIADAPTER, RelAdpNum, 0, (PUSHORT)&LID);

   ACBTbl[ai].np1stDCB  = (NPDCB)free;
   ACBTbl[ai].cBus      = 1;             /* assume only 1 bus  */

   /*-------------------*/
   /* Get the POS info  */
   /*-------------------*/

   INFMSG ("Adapter Allocated\n\r");
   INFMSG1("  Base I/O ===> %w\n\r",ACBTbl[ai].baseIO);

   if (GetPOSData(ai, pAdpInfo)) {
      if (verbose) {
         sprntf((PSZ)msgbuf,"\nAdapter %d -- *** ERROR *** Is not functioning.",ai);
         putmsg(msgbuf);
      }
      return(ERROR);
   }

   INFMSG1("  Adapter ID => %w\n\r",ACBTbl[ai].id);
   INFMSG1("  uCode lvl ==> %w\n\r",ACBTbl[ai].uCodeLevel);

   if (verbose) {
      if (ACBTbl[ai].slot == -1) {                                                  /*@D2623*/
         sprntf((PSZ)msgbuf,"\nAdapter %d -- POS ID = %4x, Microcode Level = %d",   /*@D2623*/
                ai, ACBTbl[ai].id, ACBTbl[ai].uCodeLevel);                          /*@D2623*/
      }                                                                             /*@D2623*/
      else if (ACBTbl[ai].slot == 0) {                                              /*@D2623*/
         sprntf((PSZ)msgbuf,"\nAdapter %d -- POS ID = %4x, Microcode Level = %d,"\
                " Planar SCSI", ai, ACBTbl[ai].id, ACBTbl[ai].uCodeLevel);
      }                                                                             /*@D2623*/
      else {                                                                        /*@D2623*/
         sprntf((PSZ)msgbuf,"\nAdapter %d -- POS ID = %4x, Microcode Level = %d,"\
                " Slot = %d", ai, ACBTbl[ai].id, ACBTbl[ai].uCodeLevel,             /*@D2623*/
                ACBTbl[ai].slot);                                                   /*@D2623*/
      }                                                                             /*@D2623*/
      putmsg(msgbuf);
   }

   /*-------------------------------------*/
   /* see if tribble and patch if needed. */
   /*-------------------------------------*/

   if (ACBTbl[ai].id == AID_TRIBBLE && (ADDStatus & TRIBBLEPATCH)) {
      if (PatchTribble(ai)) {
         if (verbose) {
            putmsg("   *** ERROR *** patching adapter microcode.");
         }
         return(ERROR);
      }
   }

   /*---------------------------------------------------------------------*/
   /* If the adapter is a corvette then get the device assignment table.  */
   /* We will use this later in assigning entity IDs for devices attached */
   /* to this adapter.  Move mode will be setup later.                  e */
   /*---------------------------------------------------------------------*/

   if (ACBTbl[ai].id == AID_CORVETTE && ACBTbl[ai].status & ENABLEMOVEMODE) {
      initSCB.Cmd               = 0x1c2a;       /* get device assgn tbl  */
      initSCB.Enable            = SCBEWDEFAULT+SCBEfBB+SCBEfRD;
      initSCB.LBA               = 0L;
      initSCB.ppXferBuf         = ppData + (USHORT)&ldatable;
      initSCB.XferBufLen        = 32;

      if (_StartIO(ACBTbl[ai].baseIO, ppinitSCB, ADAPTERDEVICE+CMD_NORMSCB)) {
         ERRMSG("InitAdapter: Error starting SCB.\n\r");
         return(ERROR);
      }

      ACBTbl[ai].status |= OPINPROGRESS;
      STI();                                  /* StartIO disables interrupts */
      while (ACBTbl[ai].status & OPINPROGRESS) { }
      ic = (ACBTbl[ai].isr & ISR_IDMASK) >> 4;

      /*---------------------------------------*/
      /* If error then don't enable move mode  */
      /*---------------------------------------*/

      if (ic != CC_SUCCESS && ic != CC_RETRY) {
         ERRMSG1("Error getting LDA table for adatper index %w\n\r",ai);
         ACBTbl[ai].status &= ~ENABLEMOVEMODE;
      }
   }

   /*-------------------------------------------------*/
   /* Now set the DMA pacing to 100% for the adapter. */
   /* Ignore error returned.                          */
   /*-------------------------------------------------*/

   if (_StartIO(ACBTbl[ai].baseIO, SCB_SETDMAPAC,ADAPTERDEVICE+CMD_IMMEDIATE)) {
      ERRMSG("InitAdapter: Error starting SET dma pacing factorSCB.\n\r");
      return(ERROR);
   }
   ACBTbl[ai].status |= OPINPROGRESS;
   STI();            /* StartIO disables interrupts */
   while (ACBTbl[ai].status & OPINPROGRESS) { }

   return(0);

}

/*************************************************************************/
/*                                                                       */
/* ScanDIRs - scan the device information records                        */
/*                                                                       */
/*    Scan the device information records for devices that are attached  */
/*    to the specified adapter.  If device found then initialize it.     */
/*    bdrv is the boot drive relative device number.  It is only valid   */
/*    if no -1.  It is used while searching throught the hardfile area.  */
/*                                                                       */
/*    In general when this routine completes DCBs have built for each    */
/*    device attached to the specified adapter.  Note that only cDev     */
/*    entries are checked.  This is because the EBDA separates the       */
/*    hardfile and other devices into two tables.                        */
/*                                                                       */
/*       Entry: pDIR - pointer to the first DIR in the table             */
/*              cDev - count of devices in this table                    */
/*              cnum - card number of the specified adapter.             */
/*              ai   - adapter index.                                    */
/*                                                                       */
/*       Exit:   0 if no error detected, <> 0 otherwise.                 */
/*                                                                       */
/*************************************************************************/

USHORT ScanDIRs(PDIR pDIR, USHORT cDev, USHORT cnum, USHORT ai)

{

   USHORT i, rc;
   NPDCB  npDCB;

   /*----------------------------------------------------------------*/
   /* Loop through the DIR table looking for devices that match the  */
   /* card number.  If a match is found then call InitDevice to      */
   /* build a DCB for the device and do any required initialization. */
   /* If there are no errors during intialization then the device is */
   /* marked allocated in the EBDA so that no one else can use it.   */
   /*----------------------------------------------------------------*/

   for (i=0; i<cDev; i++) {

      if ((pDIR->dir[0] & CARDNUM) == cnum) {        /* match found  */

         npDCB = (NPDCB)free;

         ACBTbl[ai].cDev++;
         npDCB->UnitInfo.AdapterIndex = ai;   /* our index. */
         npDCB->UnitInfo.UnitFlags = (pDIR->dir[2] & DEFECT) ? UF_DEFECTIVE : 0;
         npDCB->UnitInfo.UnitHandle = UnitHandle++;
         npDCB->UnitInfo.UnitSCSILUN      = (pDIR->dir[1] & LUN) >> 4 ;
         if (ADDStatus & SCSI2TABLE) {                                          /*@D2623*/
            npDCB->UnitInfo.UnitSCSITargetID = pDIR->dir[1] & PUN_2;            /*@D2623*/
         }                                                                      /*@D2623*/
         else {                                                                 /*@D2623*/
            npDCB->UnitInfo.UnitSCSITargetID = pDIR->dir[1] & PUN;              /*@D2623*/
         }                                                                      /*@D2623*/

         npDCB->status = 0;
         npDCB->state  = IDLE;
         npDCB->ai     = ai;

         npDCB->pActiveIORB = NULL;
         npDCB->pWorkQ      = NULL;
         npDCB->npCallTbl   = IORBCallTbl;

         npDCB->ldn         = (pDIR->dir[0] & DEVNUM) >> 4;

         rc = InitDevice(npDCB, ai, pDIR);

         if (!rc) {
            cDevices++;
            pDIR->dir[2] |= ALLOC;
            free += sizeof(DCB);
         }
         else {
            if (rc == (ERROR+FATAL)) {
               return(ERROR);
            }
            ACBTbl[ai].cDev--;          /* this device is skipped.  */
            UnitHandle--;               /* decrement the unithandle */
         }
      }

      if (ADDStatus & SCSI2TABLE) {     /* if new EBDA      REN6284 */
         pDIR++;                        /* step by 5        REN6284 */
      }                                 /*                  REN6284 */
      else {                            /*                  REN6284 */
         (UCHAR *)pDIR +=4;             /* step by 4        REN6284 */
      }                                 /*                  REN6284 */

   }

   return(0);

}

/************************************************************************/
/*                                                                      */
/* InitDevice                                                           */
/*                                                                      */
/*    This routine will initialize the specified device.                */
/*                                                                      */
/*    Entry: InitDevice(npDCB, ai)                                      */
/*                                                                      */
/*    Exit: Returns ERROR if error detected.  FATAL + ERROR if load     */
/*          should fail.                                                */
/*                                                                      */
/************************************************************************/

USHORT InitDevice(NPDCB npDCB, USHORT ai, PDIR pDIR)

{

   USHORT rc;

   /*-----------------------------------*/
   /* Set the bus number if available.  */
   /*-----------------------------------*/

   if (ACBTbl[ai].status & ENABLEMOVEMODE) {
      npDCB->bus = (ldatable[npDCB->ldn] & LDA_BUS) >> 8;
      npDCB->EntityID = 0;
   }
   else {
      npDCB->bus = 0;
   }

   npDCB->ResSCBH.scbctrl.npNextSCBH = NULL;
   npDCB->ResSCBH.scbctrl.ppSCB      = ppData + (USHORT)&(npDCB->ResSCBH.scb);
   npDCB->ResSCBH.scbctrl.pIORB      = pInitIORB;

   /*--------------------------------------------------------------------*/
   /* Go get the inquiry data.  From that get the device type and rem-   */
   /* ovable media indicator.  Also determine if the device supports     */
   /* command tag queueing and a wide scsi bus.                          */
   /*--------------------------------------------------------------------*/

   rc = GetINQData(ai, npDCB);

   if (rc == NOERROR) {

      npDCB->UnitInfo.UnitType      = (USHORT)inqdata.DevType;
      npDCB->UnitInfo.QueuingCount  = QueueSize;
      npDCB->UnitInfo.UnitFlags    |=
         (inqdata.RMB_TypeMod & SCSI_REMOVABLE_MEDIA) ? UF_REMOVABLE : 0;

      npDCB->status |= (inqdata.Flags & SCSI_INQ_CMDQUEUE) ? UNITCMDQ : 0;
      npDCB->status |= (inqdata.Flags & (SCSI_INQ_WBUS16 + SCSI_INQ_WBUS32))
                       ? UNITWIDE : 0;

      /*-------------------------------------------------------------*/
      /* Build the get sense data SCB for all devices.  This is done */
      /* even for move mode devices in case move mode can't be       */
      /* initialized.                                                */
      /*-------------------------------------------------------------*/

      BuildSCB(&npDCB->SenseSCB, SCB_CMDSENSE, npDCB, 0L,
               (ppData + (USHORT)&(npDCB->SenseData)),
               (ULONG)sizeof(SCSI_REQSENSE_DATA), 0, 0);

      npDCB->Timeout = DFLT_TO;

      if (verbose) {
         sprntf((PSZ)msgbuf,"   Target: %d  LUN: %d  %28s",
            npDCB->UnitInfo.UnitSCSITargetID,
            npDCB->UnitInfo.UnitSCSILUN,
            (UCHAR FAR *)inqdata.VendorID);
         putmsg(msgbuf);

      }

      return(NOERROR);

   }
   else if (rc & ERROR) {
      if (rc & FATAL) {
         return(ERROR+FATAL);
      }
      else {
         return(ERROR);
      }
   }

   npDCB->ResSCBH.scbctrl.pIORB = NULL;
   return(NOERROR);

}


/*************************************************************************/
/*                                                                       */
/* GetINQData - get device inquiry data                                  */
/*                                                                       */
/*    Issue a request to the device to get the inquiry data.             */
/*                                                                       */
/*       Entry:  ai - adapter index                                      */
/*               npDCB - ptr to the DCB                                  */
/*                                                                       */
/*       Exit:   NOERROR     - no error found.                           */
/*               ERROR       - error accessing device, OK to continue.   */
/*               ERROR+FATAL - fatal error, exit init with failure       */
/*                                                                       */
/*************************************************************************/

USHORT GetINQData(USHORT ai, NPDCB npDCB)

{

   BYTE   ic;

   /*------------------*/
   /*  Setup the SCB.  */
   /*------------------*/

   BuildSCB(&npDCB->ResSCBH.scb, SCB_DEVICEINQ, npDCB, 0L,
            ppData + (USHORT)&inqdata, sizeof(SCSI_INQDATA), 0, 0);

   IORBWRKSP(pInitIORB,cRetries) = 1;          /* allow one retry  */
   initIORB.Status = 0;
   IORBWRKSP(pInitIORB,npSCBH)   = &npDCB->ResSCBH;

   /*--------------------------------------------------------------------*/
   /* Start the SCB to get the inquiry data.  If it starts with no error */
   /* then set the device state and the active IORB to the init time     */
   /* IORB.  Then wait for it to complete.                               */
   /*--------------------------------------------------------------------*/

   if (_StartIO(ACBTbl[ai].baseIO, npDCB->ResSCBH.scbctrl.ppSCB,
                npDCB->ldn+CMD_NORMSCB)) {
      ERRMSG("GetINQData: Error starting SCB.\n\r");
      return(ERROR+FATAL);
   }

   npDCB->state = WAITONINT;
   npDCB->pActiveIORB = pInitIORB;
   STI();            /* StartIO disables interrupts  */
   while (!(initIORB.Status & IORB_DONE)) ;    /* wait until done  */

   /*-------------------------------------------------------------------*/
   /* See if there was an error getting the inquiry data.  If the error */
   /* is a SCSI bus timeout then we assume the device is powered off.   */
   /* If the error is an adapter hardware error then return an error.   */
   /* We are not able to reset the adapter during initialization.  For  */
   /* any other error we mark the device as defective.                  */
   /*-------------------------------------------------------------------*/

   ic = (npDCB->isr & ISR_IDMASK) >> 4;

   if (ic == CC_SUCCESS || ic == CC_RETRY) {
      return(NOERROR);
   }
   else if (ic == CC_ERROR) {
      ERRMSG1("GetINQData: Error returned. DCB = %w\n\r",npDCB);
      if (npDCB->tsb.DevError == 0x10) {                /* selection timeout */
         npDCB->status |= UNITPOFF;              /* Unit power is off  */
      }
      else {                                        /* otherwise defective  */
         npDCB->UnitInfo.UnitFlags |= UF_DEFECTIVE;
      }
      return(ERROR);
   }
   else {
      ERRMSG("GetINQData: some other error returned\n\r");
      return(ERROR);
   }
}


/*************************************************************************/
/*                                                                       */
/* GetPOSData - get POS data                                             */
/*                                                                       */
/*    Issue a request to the adapter to return the POS and adapter       */
/*    information.                                                       */
/*                                                                       */
/*       Entry:  None                                                    */
/*                                                                       */
/*       Exit:   ptr to SCSI Data area if found, 0 if not found.         */
/*                                                                       */
/*************************************************************************/

USHORT GetPOSData(USHORT ai, PSCSIADPINFO pAdpInfo)

{

   POSINFO      POSinfo;
   ULONG        ppPOSinfo;
   BYTE         ic;
   USHORT       retry;

   retry = 0;

   /*-----------------------------------------------------------------*/
   /* Calc phys address of needed buffers.  Setup the SCB to get the  */
   /* POS and adapter information and submit the request.             */
   /*-----------------------------------------------------------------*/

   DevHelp_VirtToPhys((PVOID)&POSinfo, (PULONG)&ppPOSinfo);

   initSCB.Cmd        = SCBQUERYPOSINFO;
   initSCB.LBA        = 0L;
   initSCB.ppXferBuf  = ppPOSinfo;
   initSCB.Enable     = SCBEWDEFAULT + SCBEfRD + SCBEfBB;
   initSCB.XferBufLen = 18L;

again:

   if (_StartIO(ACBTbl[ai].baseIO, ppinitSCB, ADAPTERDEVICE+CMD_NORMSCB)) {
      ERRMSG("GetPOSData: Error starting SCB\n\r");
      return(ERROR);
   }

   ACBTbl[ai].status |= OPINPROGRESS;

   STI();            /* StartIO disables interrupts  */

   while (ACBTbl[ai].status & OPINPROGRESS) { }

   /*---------------------------------------------------*/
   /* Save needed data here and do some error checking. */
   /*---------------------------------------------------*/

   ic = (ACBTbl[ai].isr & ISR_IDMASK) >> 4;

   if (ic == CC_SUCCESS || ic == CC_RETRY) {
      if (!retry && POSinfo.id == AID_CORVETTE ) {
         initSCB.Enable     = SCBEWDEFAULT + SCBEfRD + SCBEfSS;
         initSCB.XferBufLen = sizeof(POSINFO);
         retry++;
         goto again;
      }

      /*----------------------------------------*/
      /* save data common to all adapter types. */
      /*----------------------------------------*/

      ACBTbl[ai].TargetID   = (POSinfo.pr3 & 0xe0) >> 5;
      ACBTbl[ai].AFlags     = (POSinfo.slot_rev & 0x1000) ? 0 : AF_16M;
      ACBTbl[ai].id         = POSinfo.id;
      ACBTbl[ai].uCodeLevel = POSinfo.slot_rev & 0x0fff;

      /*---------------------*/
      /* get bus information */
      /*---------------------*/

      if (ACBTbl[ai].id == AID_CORVETTE) {
         ACBTbl[ai].iBus = (POSinfo.pr4b & 0x01) ? BUS_FAST : BUS_WIDE+BUS_FAST;
         ACBTbl[ai].eBus  = (POSinfo.pr4b & 0x02) ? 0 : BUS_WIDE;
         ACBTbl[ai].eBus |= (POSinfo.pr4b & 0x04) ? BUS_FAST : 0;
      }
      else {
         ACBTbl[ai].iBus = 0;
         ACBTbl[ai].eBus = 0;
      }

      /*-----------------------------------------------------------------*/
      /* set different values if the adapter is a corvette and move mode */
      /* is to be enabled (POS register 4 bit 2 set)                     */
      /*-----------------------------------------------------------------*/

      if (ACBTbl[ai].id == AID_CORVETTE && (POSinfo.pr4 & 0x04)) {
         ACBTbl[ai].status |= ENABLEMOVEMODE;
         ACBTbl[ai].cCmds = POSinfo.cCmds;
      }
      else {
         ACBTbl[ai].cCmds = 0;
      }

      return(0);
   }
   else {
      ERRMSG("GetPOSData error.\n\r");
      return(ERROR);
   }
}


/*************************************************************************/
/*                                                                       */
/* GetSCSIDataPtr - get ptr to scsi data area.                           */
/*                                                                       */
/*    This outline gets the address to the scsi data area in the EBDA.   */
/*    It searches for adapter ID 8EFEh.                                  */
/*                                                                       */
/*       Entry:  None                                                    */
/*                                                                       */
/*       Exit:   ptr to SCSI Data area if found, 0 if not found.         */
/*                                                                       */
/*************************************************************************/

USHORT GetSCSIDataPtr(PSCSIDATATBL FAR *ppSCSITbl)

{

   PEBDAHDR pCurDataArea;           /* ptr to a dynamic data area  */

   /*--------------------------------------------------------*/
   /* start at the 1st data area and search until either the */
   /* end, or the SCSI data area is found.                   */
   /*--------------------------------------------------------*/

   if (!GetEBDAPtr()) {
      pCurDataArea = MAKEP(sEBDA, 0x180);

      while (1) {
         if (pCurDataArea->NextHdr == 0) {
            ERRMSG("GetSCSIDataPtr: SCSI data area not found\n\r");
            return(0);
         }
         else if (pCurDataArea->AdpID == 0x8efe) {
            INFMSG1("GetSCISDataPtr: SCSI data area ptr found (%p)\n\r",pCurDataArea);
            *ppSCSITbl = (PSCSIDATATBL)pCurDataArea;
            return(0);
         }
         else if (pCurDataArea->AdpID == 0x8efc) {
            INFMSG1("GetSCISDataPtr: SCSI2 data area ptr found (%p)\n\r",pCurDataArea);
            *ppSCSITbl = (PSCSIDATATBL)pCurDataArea;
            ADDStatus |= SCSI2TABLE;                                            /*@D2623*/
            return(0);
         }
         else {
            pCurDataArea = MAKEP(sEBDA,pCurDataArea->NextHdr);
         }
      }
   }
   else {
      ERRMSG("GetSCSIDataPtr: Error getting ptr to EBDA\n\r");
   }
   return(ERROR);
}


/*************************************************************************/
/*                                                                       */
/* GetEBDAPtr - get addressability to the EBDA                           */
/*                                                                       */
/*    This routine establishes addressability to the EBDA.               */
/*                                                                       */
/*       Entry:  None                                                    */
/*                                                                       */
/*       Exit:   0 if no error and sEBDA initialized.  <>0 if error.     */
/*                                                                       */
/*************************************************************************/


USHORT GetEBDAPtr()

{

   PUSHORT pEBDASeg;           /* ptr in BIOS data area to EBDA segment  */
   USHORT  EBDASeg;            /* actual EBDA segment                    */
   ULONG   ppEBDA;             /* phys address of EBDA                   */

   pEBDASeg = MAKEP(0x40,0x0e);
   EBDASeg  = (USHORT)*pEBDASeg;
   ppEBDA   = (ULONG) EBDASeg * 16L;

   if (DevHelp_AllocGDTSelector((PSEL)&sEBDA, 1)) {
      ERRMSG("GetEBDAPtr: Unable to allocate GDT selector\n\r");
      return(ERROR);
   }

   if (DevHelp_PhysToGDTSelector(ppEBDA, 0xffff, sEBDA)) {
      ERRMSG("GetEBDAPtr: Phys to GDT selector failed.\n\r");
      return(ERROR);
   }

   return(0);

}


/************************************************************************/
/*                                                                      */
/* BuildSCBPool                                                         */
/*                                                                      */
/*    Initialize the rest of memory into the Free SCB pool.             */
/*                                                                      */
/*    Entry:  BuildSCBPool()                                            */
/*                                                                      */
/*    Exit:   SCB pool is built.                                        */
/*                                                                      */
/************************************************************************/

VOID BuildSCBPool()

{
   NPSCBH curSCBH, nextSCBH;
   USHORT i;
   LONG   SpaceLeft;

   /*-----------------------------------------------------------------*/
   /* Loop through the rest of (available ??)  memory initializing    */
   /* the pointers to create the SCB free list.  When done            */
   /* npSCBNextFree will point to the start of the list and           */
   /* npSCBLastFree will point to the end of the list.                */
   /*-----------------------------------------------------------------*/

   SpaceLeft = &EndData - free;

   curSCBH = (NPSCBH)free;
   npSCBFreeList = curSCBH;
   nextSCBH = curSCBH + 1;

   for (i=0; (i<(cDevices*QueueSize)) && (sizeof(SCBH)<SpaceLeft); i++) {
      curSCBH->scbctrl.npNextSCBH = nextSCBH;
      curSCBH->scbctrl.ppSCB      = ppData + ((USHORT)&(curSCBH->scb));
      curSCBH                     = nextSCBH;
      nextSCBH                    = curSCBH + 1;
      SpaceLeft                  -= sizeof(SCBH);
      free                       += sizeof(SCBH);
   }

   curSCBH->scbctrl.ppSCB = ppData + ((USHORT)&(curSCBH->scb));

   free += sizeof(SCBH);

   INFMSG1("%w entries built in SCB free pool\n\r",i);

}

/************************************************************************/
/*                                                                      */
/* InitTimerPool                                                        */
/*                                                                      */
/*    This call initializes the timer element/event pool.  There is one */
/*    ULONG entry per device and adapter.  These are used by the        */
/*    timeout processing logic.  See the timer handler for more info.   */
/*                                                                      */
/*    Entry:  InitTimerPool()                                           */
/*                                                                      */
/*    Exit:   TRUE always.                                              */
/*                                                                      */
/************************************************************************/

VOID InitTimerPool()

{

   USHORT i;
   struct InfoSegGDT far *pInfoSegGDT;
   ULONG  far *pInfoSegSel;

   /*-----------------------------------------------------------------*/
   /* If timeouts are enabled then reserve 1 long per adapter (set to */
   /* 0).  Then start a timer that fires every second.                */
   /*-----------------------------------------------------------------*/

   Timers = (ULONG *)free;
   free += cAdapters * sizeof(ULONG);

   for (i=0; i<cAdapters; i++) {
      *(Timers+i) = 0;
   }

   if (!(ADDStatus & NOTIMEOUTS)) {

      DevHelp_GetDOSVar(DHGETDOSV_SYSINFOSEG, 0, (PPVOID)&pInfoSegSel);
      pInfoSegGDT = MAKEP(*pInfoSegSel, 0);
      DevHelp_TickCount((NPFN)TimerHandler, 10000 / pInfoSegGDT->SIS_ClkIntrvl);
   }
}

/************************************************************************/
/*                                                                      */
/* BuildUnitHandleTable                                                 */
/*                                                                      */
/*    Build the unithandle table for quick access to device table       */
/*    entries.  Unithandles are assigned in ascending order with no     */
/*    skipped numbers.  Update free pointer.                            */
/*                                                                      */
/*    Entry:  BuildUnitHandleTalbe();                                   */
/*                                                                      */
/*    Exit:   normal always.                                            */
/*                                                                      */
/************************************************************************/

VOID BuildUnitHandleTable()

{

   USHORT i, j;
   NPBYTE CurEntry;
   NPDCB  CurDCB;


/*
** The unithandle table is stored starting at the memory pointed to by free.
** It is organized as a vector of offsets to the DCB that corresponds to the
** unithandle.
**
** The table is built by looping through the device tables for each adapter.
** The unit handles are assigned in order starting at 0.
*/

   (NPBYTE)UnitHandleTbl = free;
   CurEntry = free;

   for (j=0; j<cAdapters; j++) {
      CurDCB = ACBTbl[j].np1stDCB;
      for (i=0; i<ACBTbl[j].cDev; i++) {
         (NPDCB)*CurEntry = CurDCB;
         CurEntry        += sizeof(NPDCB);
         (NPBYTE)CurDCB  += sizeof(DCB);
      }
   }

   free += cDevices * sizeof(NPDCB);

}


VOID InitTraceBuffer()

{

   (NPBYTE)TraceBuf = free;
   free += MAXTRACE * sizeof(TRACEINFO);

}


/************************************************************************/
/*                                                                      */
/* PatchTribble                                                         */
/*                                                                      */
/*    Apply a patch to the tribble adapter.                             */
/*                                                                      */
/*    Entry:  PatchTribble                                              */
/*                                                                      */
/*    Exit:                                                             */
/*                                                                      */
/************************************************************************/


USHORT PatchTribble(USHORT ai)

{
   USHORT rc = 0;
   USHORT AdapterPosID;
   UCHAR  ic;
   ULONG  ppPatchData, ppPatchVData;

   if (ACBTbl[ai].uCodeLevel < 14) {

      DevHelp_Save_Message( (NPBYTE) &AdapterWngMsg );

   }

   else {

      /*----------------------------------------------------------------*/
      /* Determine if the microcode level is one we have a patch for.   */
      /* If so then calc the starting phys addresses of the patch data. */
      /*----------------------------------------------------------------*/


      switch (ACBTbl[ai].uCodeLevel) {

         case 15:
            ppPatchData  = ppData + (USHORT)&Patch15Data;
            ppPatchVData = ppData + (USHORT)&Patch15vData;
            break;

         case 16:
            ppPatchData  = ppData + (USHORT)&Patch16Data;
            ppPatchVData = ppData + (USHORT)&Patch16vData;
            break;

         case 17:
         case 18:
            ppPatchData  = ppData + (USHORT)&Patch17Data;
            ppPatchVData = ppData + (USHORT)&Patch17vData;
            break;

         case 19:
            ppPatchData  = ppData + (USHORT)&Patch19Data;
            ppPatchVData = ppData + (USHORT)&Patch19vData;
            break;

         default:
            return(0);
      }

      /*------------------------------------------------------------------*/
      /* Now apply the first part of the patch.  The first part is to be  */
      /* applied at RAM address 9be1.  It starts with byte 1 of the patch */
      /* data (0 based).  By starting at byte 1 and writing the patch as  */
      /* two writes, the adapter will not get lost in case it receives an */
      /* interrupt during the patch process.  The original op code at     */
      /* 9be0 is a return.  This will be patched in a separate 1 byte     */
      /* operation.
      /*------------------------------------------------------------------*/

      initSCB.ppXferBuf  = ppPatchData + 1;
      initSCB.Cmd        = 0x1c1d;
      initSCB.Enable     = 0x4000;
      initSCB.LBA        = 0x00009be1;
      initSCB.XferBufLen = 0x00000396;

      if (_StartIO(ACBTbl[ai].baseIO, ppinitSCB, ADAPTERDEVICE+CMD_NORMSCB)) {
         ERRMSG("Error applying uCode patch, stage 1.\n\r");
         return(ERROR);
      }

      ACBTbl[ai].status |= OPINPROGRESS;

      STI();            /* allow ints */

      while (ACBTbl[ai].status & OPINPROGRESS) { }

      ic = (ACBTbl[ai].isr & ISR_IDMASK) >> 4;

      if (ic != CC_SUCCESS && ic != CC_RETRY) {
         ERRMSG("SCB1 ended in error applying uCode patch.\n\r");
         return(ERROR);
      }

      /*--------------------------------------------*/
      /* Now do the single byte patch at addr 9be0. */
      /*--------------------------------------------*/

      initSCB.ppXferBuf  = ppPatchData;
      initSCB.Cmd        = 0x1c1d;
      initSCB.Enable     = 0x4000;
      initSCB.LBA        = 0x00009be0;
      initSCB.XferBufLen = 0x00000001;

      if (_StartIO(ACBTbl[ai].baseIO, ppinitSCB, ADAPTERDEVICE+CMD_NORMSCB)) {
         ERRMSG("Error applying uCode patch, stage 2.\n\r");
         return(ERROR);
      }

      ACBTbl[ai].status |= OPINPROGRESS;

      STI();            /* allow ints  */

      while (ACBTbl[ai].status & OPINPROGRESS) { }

      ic = (ACBTbl[ai].isr & ISR_IDMASK) >> 4;

      if (ic != CC_SUCCESS && ic != CC_RETRY) {
         ERRMSG("SCB1 ended in error applying uCode patch.\n\r");
         return(ERROR);
      }

      /*---------------------------------------------------*/
      /* Now patch the uCode checksum data so a reset will */
      /* not cause problems.                               */
      /*---------------------------------------------------*/

      initSCB.ppXferBuf  = ppPatchVData;
      initSCB.Cmd        = 0x1c1d;
      initSCB.Enable     = 0x4000;
      initSCB.LBA        = 0x00009fdf;
      initSCB.XferBufLen = 0x0000000e;

      if (_StartIO(ACBTbl[ai].baseIO, ppinitSCB, ADAPTERDEVICE+CMD_NORMSCB)) {
         ERRMSG("Error applying uCode patch, stage 3.\n\r");
         return(ERROR);
      }

      ACBTbl[ai].status |= OPINPROGRESS;

      STI();            /* allow ints  */

      while (ACBTbl[ai].status & OPINPROGRESS) { }

      ic = (ACBTbl[ai].isr & ISR_IDMASK) >> 4;

      if (ic != CC_SUCCESS && ic != CC_RETRY) {
         ERRMSG("SCB2 ended in error applying uCode patch.\n\r");
         return(ERROR);
      }
   }

   return(0);
}


/*--------------------------------------------------------------------------
;
;** GetInitParms - Get init parms from BASEDEV command line
;
;   VOID GetInitParms (PRPINITIN pRP);
;
;   ENTRY:    pRP              - Pointer to init request packet
;
;   RETURN:   VOID
;
;   EFFECTS:  Turns on Queueing flags in global DDFlags.
;
;   Parameters supported:
;
;   Parm                Description                              Defalult
;   ------------------------------------------------------------------------
;   /[!]DM[:unit_list]  Dasd manager support                     none
;   /[!]SM[:unit_list]  SCSI manager support                     none
;   /[!]SN[:unit_list]  Synchronous negotiation                  on
;   /A:#                Adapter indicator                        none
;   /TR                 Turns on tracing                         off
;   /LED                Turns on model 95 display panel disk led off
;   /GS:#               Chain size for locate mode               4
;   /PS:#               Pipe size in K bytes (1 - 9)             1
;   /PA                 patch tribble cards                      off
;   /TO                 disable software timeouts                on
;   /I                  ignore this adapter                      off
;   /V                  verbose                                  off
;   /C#                 comport for debug messages (1 or 2)      1
;   /FG:#               FILE GEOMETRY in GIG byte 1 < 2; 2 < 4   FALSE
;
;   unit_list = u[,unit_list]             unit list
;   u         = t | (t,l) | (t,l,b)       one of these three
;   t         = #                         target ID
;   l         = #                         LUN
;   b         = #                         Bus (corvette only)
;
;---------------------------------------------------------------------------*/

#define ISDIGIT(_c) (( (_c) >= '0' && (_c) <= '9') ? 1 : 0)
extern USHORT comport;

VOID GetInitParms (PRPINITIN pRP)

{
   PSZ    pc;
   CHAR   c;
   USHORT i, state, j, aa;
   USHORT num;
   USHORT f;
   UCHAR  target, lun, bus;
   BOOL   not, done, local, chkbus;
   NPDCB  npDCB;

   chkbus = not = done = local = FALSE;
   state = target = lun = bus = 0;
   aa = -1;

   pc = pRP->InitArgs;
   OFFSETOF(pc) = ((PDDD_PARM_LIST)pc)->cmd_line_args;

   while (!done) {

      c = *pc;

      if (c == 0) {
         done = TRUE;
         c = ' ';
      }

      if (c >= 'a' && c <= 'z') {     /* convert to upper case  */
         c = c - ('a' - 'A');
      }

      switch (state) {
         case 0:
            if (c == '/') state = 1;
            not = FALSE;
            chkbus = FALSE;
            break;

         case 1:
            if      (c == 'A') state = 2;
            else if (c == 'T') state = 4;
            else if (c == 'L') state = 5;
            else if (c == 'D') state = 8;
            else if (c == 'S') state = 9;
            else if (c == 'G') state = 19;
            else if (c == 'P') state = 22;
            else if (c == 'C') state = 25;
            else if (c == 'F') state = 26;
            else if (c == 'V') {
               verbose = TRUE;
               state = 0;
            }
            else if (c == 'I') {
               if (aa != -1) {
                  IgnoreMask |= (1 << aa);
                  state = 0;
               }
            }
            else if (c == '!') {
               not = TRUE;
               state = 7;
            }
            else state = 30;

            break;

         case 2:                            /* /A:  */
            if (c == ':') state = 3;
            else          state = 30;
            break;

         case 3:                            /* /A:#  */
            if (ISDIGIT(c)) {
               aa = c - '0';
               f = 0;
               not = FALSE;
               state = 0;
            }
            else state = 30;
            break;

         case 4:                            /* /TR or /TO  */
            if (c == 'R') {
               ADDStatus |= ADDTRACE;
               state = 0;
            }
            else if (c == 'O') {
               ADDStatus |= NOTIMEOUTS;
               state = 0;
            }
            else state = 30;
            break;

         case 5:                            /* /LE  */
            if (c == 'E') state = 6;
            else          state = 30;
            break;

         case 6:                            /* /LED  */
            if (c == 'D') {
               ADDStatus |= LED;
               state = 0;
            }
            else state = 30;
            break;

         case 7:                                     /* /[!]D | /[!]S  */
            if      (c == 'D') state = not ? 8 : 0;
            else if (c == 'S') state = not ? 9 : 0;
            else               state = 30;
            break;

         case 8:                                     /* /!DM  */
            if (c == 'M') {
               f = UF_NODASD_SUPT;
               state = 10;
            }
            else state = 30;
            break;
                                                     /* /!SM  */
         case 9:
            if (c == 'M') {
               f = UF_NOSCSI_SUPT;
               state = 10;
            }
            else if (c == 'N') {
               f = UNITNOSYNC;
               local = TRUE;
               state = 10;
            }
            else state = 30;
            break;

         case 10:                                    /* /!xN | M  ' ' | :  */
            if (c == ' ') {
               if (aa != -1) {
                  for (j=0; j<cDevices; j++) {
                     npDCB = *(UnitHandleTbl + j);
                     if (npDCB->UnitInfo.AdapterIndex == aa) {
                        if (!local) {
                           npDCB->UnitInfo.UnitFlags |= f;
                        }
                        else {
                           npDCB->status |= f;
                        }
                     }
                  }
                  local = FALSE;
                  state = 0;
               }
               else {
                  state = 30;
               }
            }
            else if (c == ':') {
               state = 11;
            }
            else state = 30;
            break;

         case 11:                        /* /!xM: # | (  */
            if (ISDIGIT(c)) {
               target = c - '0';
               state = 12;
            }
            else if (c == '(') {
               state = 13;
            }
            else state = 30;
            break;

         case 12:                            /* u found, last one ?  */
            if (aa != -1) {
               for (j=0; j<cDevices; j++) {
                  npDCB = *(UnitHandleTbl + j);
                  if (npDCB->UnitInfo.AdapterIndex == aa         &&
                      npDCB->UnitInfo.UnitSCSITargetID == target &&
                      npDCB->UnitInfo.UnitSCSILUN == lun         &&
                      ((chkbus && npDCB->bus == bus) || !chkbus)) {
                     if (!local) {
                        npDCB->UnitInfo.UnitFlags |= f;
                     }
                     else {
                        npDCB->status |= f;
                     }
                  }
               }
               if      (c == ' ') {local = FALSE; state = 0;}
               else if (c == ',') {
                  target = 0;
                  lun    = 0;
                  bus    = 0;
                  state  = 11;
               }
               else                state = 30;
            }
            else {
               state = 30;
            }

            break;

         case 13:                   /* target ID in ()  */
            if (ISDIGIT(c)) {
               target = c - '0';
               state  = 14;
            }
            else state = 30;
            break;

         case 14:                       /* 1st , in ()  */
            if (c == ',') state = 15;
            else          state = 30;
            break;

         case 15:                       /* lun in ()  */
            if (ISDIGIT(c)) {
               lun = c - '0';
               state = 16;
            }
            else state = 30;
            break;

         case 16:                           /* end () for target lun or ,  */
            if (c == ')')      state = 12;
            else if (c == ',') state = 17;
            else               state = 30;
            break;

         case 17:                           /* bus in ()  */
            if (ISDIGIT(c)) {
               bus = c - '0';
               chkbus = TRUE;
               state = 18;
            }
            else state = 30;
            break;

         case 18:                            /* end of () with t,l,b  */
            if (c == ')')      state = 12;
            else               state = 30;
            break;

         case 19:                            /* /GS  */
            if (c == 'S') state = 20;
            else          state = 30;
            break;

         case 20:                            /* /GS:  */
            if (c == ':') state = 21;
            else          state = 30;
            break;

         case 21:                            /* /GS:#  */
            if (ISDIGIT(c) && c != '0') {
               GroupSize = c - '0';
               QueueSize = 2 * GroupSize;
               state = 0;
            }
            else
               state = 30;
            break;


         case 22:                            /* /PA or /PS  */
            if (c == 'A') {
               ADDStatus |= TRIBBLEPATCH;
               state = 0;
            }
            else if (c == 'S') {
               state = 23;
            }
            else {
               state = 30;
            }
            break;

         case 23:                           /*  /PS:   */
            if (c == ':')  state = 24;
            else           state = 30;
            break;

         case 24:                           /* /PS:#   */
            if (ISDIGIT(c) && c != '0') {
               PipeSize = (c - '0') * 1024;
               state = 0;
            }
            else {
               state = 30;
            }
            break;

         case 25:                            /* /C#  */
            if (ISDIGIT(c)) {
               if ((c - '0') == 2)
                  comport = 0x02f8;
            }
            state = 0;
            break;

         case 26:                            /* /FG */

            if (c == 'G')  state = 27;
            else           state = 30;
            break;

         case 27:                            /* /FG: */

            if (c == ':')  state = 28;
            else           state = 30;
            break;

         case 28:                           /* /FG:#  */

            if (c == '1') {
               old_part = 1;
               state = 0;
            }
            else if (c == '2') {
               old_part = 2;
               state = 0;
            }
            else {
               state = 30;
            }
            break;

         case 30:                /* only prints first time  */
            if (cDevices == 0) {
               DevHelp_Save_Message( (NPBYTE) &InvalidParmMsg);
               done = TRUE;
            }
            break;
      }
      pc++;
   }
}


VOID NEAR putmsg(UCHAR *msg)

{
   InitMsg.MsgStrings[0] = msg;
   DevHelp_Save_Message( (NPBYTE) &InitMsg );

}
