/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1992 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 source code is provided to you solely for       */
/*    the purpose of assisting you in your development of OS/2 device        */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Developer Connection 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;					     /* ABM 0695 */

	 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 = 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);

/* Update scsi bus number in DCB structure if EBDA has SCSI2 table info. ABM 0695 */
/* Is used later if adapter is reset and devices need reassignment. ABM 0695 */

	 if (ADDStatus & SCSI2TABLE) {			     /* ABM 0695 */	/*@D2623*/
	    npDCB->bus = (pDIR->dir[4] & BUSNUM);	     /* ABM 0695 */
	 }						     /* ABM 0695 */	/*@D2623*/

	 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 );

}
