/*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 = SCSINIT.C
 *
 * DESCRIPTIVE NAME = IBM2SCSI.ADD - Adapter Driver for IBM SCSI adapters.
 *		      Interrupt handler.
 *
 *
 * VERSION = V2.0
 *
 * DATE
 *
 * DESCRIPTION	Handle interrupts from the adapters and devices.  Handles
 *		both locate mode and move mode adapters and devices.
 *
 *
*/

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

#define INCL_NOBASEAPI
#define INCL_NOPMAPI
#include "os2.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"


/*************************************************************************/
/*									 */
/* SCSIInterruptHdlr							 */
/*									 */
/*    This is the interrupt handler.  It determine who and why the int	 */
/*    ocurred.	It then routes it.					 */
/*									 */
/*	 Entry:  called far from the kernel at interrupt time.		 */
/*									 */
/*	 Exit:	 carry clear if we own the interrupt, set otherwise	 */
/*									 */
/*************************************************************************/

VOID SCSIInterruptHdlr()

{
   NPDCB   npDCB;
   NPBYTE  npRB;
   USHORT  ai, IntStatus;
   USHORT  rc;

   IntCount++;

   IntStatus = 0;

   /*---------------------------------------------------------------*/
   /* Determine if one of our adapters is interrupting.  If so then */
   /* ai will be set to a value other than -1.	-1 indicates that   */
   /* it is not our interrupt.					    */
   /*---------------------------------------------------------------*/

   if ((ai = GetIntrAdapter()) != 0xffff) {

      IntStatus |= OURINT;			   /* yes, our interrupt  */

      /*-----------------------------------------------------------------*/
      /* See if the adapter is operating in move mode.	If so then turn  */
      /* interupts off while we determine what has happened.  Do the	 */
      /* EOI to the system.  Check the BSR to see if there was an error  */
      /* on the adapter.  If so clear the exception condition and reset  */
      /* the adapter.  Otherwise continue with normal processing by	 */
      /* calling the delivery service to determine what signal we recvd. */
      /* No adapter EOI is reqd because of clear on read is set.	 */
      /*-----------------------------------------------------------------*/

      if (ACBTbl[ai].status & MOVEMODE) {
	 CLI(); 			    /* no ints for a bit  */

	 if (ACBTbl[ai].bsr & BSR_EXCP) {
	    LOGSYSTEMERROR();		  /* exception condition on adapter  */
	    CLEAREXCP(ai);
	    ResetAdapter(ai);
	 }
	 else {
	    if (DelivTbl.SignalHandler(ACBTbl[ai].ph)) {
	       ERRMSG1("Adapter interrupting with no signal bits set ai = %w\n\r",ai);
	    }
	 }
	 CLI(); 		     //%%%%
	 DevHelp_EOI(0x000e);	     //%%%% moved from above
//	 STI();       /* turn ints back on from above	*/
      }

      /*----------------------------------------------------------------*/
      /* Otherwise the adapter is in locate mode.  Determine the reason */
      /* for the interrupt and process it.				*/
      /*----------------------------------------------------------------*/

      else {

	 /*-------------------------------------------------------------*/
	 /* If no special situations are active on the adapter then the */
	 /* interrupt is a normal device I/O completion.  Get the DCB	*/
	 /* for the device (this call performs the EOI to the adapter)	*/
	 /* and call the process routine.  Also EOI the system. 	*/
	 /*-------------------------------------------------------------*/

	 if (!(ACBTbl[ai].status & (OPINPROGRESS+RESETINPROGRESS))) {
	    npDCB = GetIntrDevice(ai);
	    DevHelp_EOI(0x000e);

	    if (npDCB) {
	       ProcessLocateModeDevice(npDCB);
	    }
	    else {
	       ERRMSG1("Adapter int with no op in progress (%w).\n\r",ai);
	    }
	 }

	 /*--------------------------------------------------------------*/
	 /* Otherwise there is some special operation in progress.  Read */
	 /* the ISR, EOI the adapter and system and process the int.	 */
	 /*--------------------------------------------------------------*/

	 else {

	    ACBTbl[ai].isr = ReadReg(ACBTbl[ai].baseIO+REG_ISR);
	    WriteAttn(ACBTbl[ai].baseIO, ADAPTERDEVICE + ADAPTER_EOI);
	    DevHelp_EOI(0x000e);

	    /*------------------------------------------------------------*/
	    /* OPINPROGRESS is for initialization operations.  Just clear */
	    /* the bit.  Someone is spinning on it.			  */
	    /*------------------------------------------------------------*/

	    if (ACBTbl[ai].status & OPINPROGRESS) {
	       ACBTbl[ai].status &= ~OPINPROGRESS;
	    }

	    /*------------------------------------------------------------*/
	    /* Otherwise the int may be for a reset. For a reset call the */
	    /* reset complete routine, which will end the reset or it may */
	    /* start another request in the sequence of operations that   */
	    /* are required to do a reset.  DO NOT RESET THE BIT HERE!!   */
	    /* The reset complete routine will do that. 		  */
	    /*------------------------------------------------------------*/

	    else if (ACBTbl[ai].status & RESETINPROGRESS) {
	       AdapterResetComplete(ai);
	    }

	    else {
	       ERRMSG1("Unknown reason for special int on adapter %w\r\n",ai);
	    }
	 }
      }
   }

   IntCount--;

   if (IntStatus & OURINT) {
      CLC();
   }
   else {
      STC();
   }

   _asm { leave }
   _asm { retf	}

}


/*************************************************************************/
/*									 */
/* ProcessLocateModeDevice						 */
/*									 */
/*    Process an interrupt from a locate mode device.			 */
/*									 */
/*	 Entry:  called far from interrupt handler.			 */
/*									 */
/*	 Exit:	none.							 */
/*									 */
/*									 */
/*************************************************************************/


VOID ProcessLocateModeDevice (NPDCB npDCB)

{

   USHORT cc, status, ec, cmd, i, TargetID;
   BOOL   done;
   PIORB  pCurIORB, pIORB;
   NPDCB  npDCBList;
   UCHAR  sds;				      /* scsi device status  */

   PSCSI_STATUS_BLOCK  pStatusBlock;

   /*-----------------------------------------------------------------*/
   /* Set local variables which define the state of this I/O.	      */
   /*-----------------------------------------------------------------*/

   cc	    = (npDCB->isr & ISR_IDMASK) >> 4; /* interrupt completion code  */
   done     = FALSE;
   status   = STARTWORK;		      /* assume we want to start work */
   pCurIORB = npDCB->pActiveIORB;

   /*------------------*/
   /* Turn the LED off */
   /*------------------*/

   SAVEIF();
   LED_OFF(npDCB);
   RESTOREIF();

   /*------------------------------------------------------------------*/
   /* Process the interrupt depending on what the current state of the */
   /* device is.						       */
   /*------------------------------------------------------------------*/

   switch (npDCB->state) {

      /*----------------------------------------------------------------*/
      /* WAITONINT: This is the state the device is in after an I/O is	*/
      /* started.  It will see if the I/O completed OK.  If not it will */
      /* start the error processing.					*/
      /*----------------------------------------------------------------*/

      case WAITONINT:

	 /*------------------------------------------------------------*/
	 /* If the completion code indicates sucess then end the IORB  */
	 /* chain.  The complete IORB chain routine may return an IORB */
	 /* pointer.  This IORB will be an IORB that has (a) remaining */
	 /* stage(s).  It was not completely setup because it probably */
	 /* was unable to allocate all needed resources (SCBs).        */
	 /*------------------------------------------------------------*/

	 if (cc == CC_SUCCESS || cc == CC_IMMSUCCESS || cc == CC_RETRY) {

	    if (pIORB = CompleteIORBChain(npDCB, pCurIORB, NULL, 0)) {
	       status |= SAMEIORB;
	       npDCB->pActiveIORB = pIORB;
	    }
	 }

	 /*-------------------------------------------------------------*/
	 /* Otherwise see if there was an error.  Look at the TSB to	*/
	 /* see what happened.	Note that the TSB is only guarenteed to */
	 /* be returned when the completion code is CC_ERROR.		*/
	 /*-------------------------------------------------------------*/

	 else if (cc == CC_ERROR) {

	    INFMSG1("Command completed with failure, cc = %w\n\r",cc);
	    pIORB = FindFailingIORB(npDCB);
	    INFMSG1("   IORB = %p\n\r",pCurIORB);

	    if (pIORB) {
	       CompleteIORBChain(npDCB, npDCB->pActiveIORB, pIORB, 0);
	       pCurIORB = pIORB;

	       sds = npDCB->tsb.SCSIStatus & SCSISTATUSMASK;

	       /*--------------------------------------------*/
	       /* If check condition then go get sense data. */
	       /*--------------------------------------------*/

	       if (sds == CHKCOND) {
		  INFMSG("Checkcondition returned\n\r");
		  status &= ~STARTWORK; 	   /* don't start more work */
		  if (GetSenseData(npDCB, pCurIORB)) {
		     npDCB->state = WAITONSENSE;   /* next state	    */
		  }
	       }

	       /*---------------------------------------------------------*/
	       /* Otherwise the error was not a check condition, so we	  */
	       /* look at the TSB to see what happend.	The first thing   */
	       /* we look at is if the device had a global timeout.  If   */
	       /* so then reset the device.  Next we look to see if there */
	       /* was a bus reset.  If there was and retries are not	  */
	       /* disabled then do a retry, otherwise return a reset	  */
	       /* error.  For all other cases look further at the TSB and */
	       /* map an appropriate IORB error code.  Then end the	  */
	       /* request.						  */
	       /*---------------------------------------------------------*/

	       else {

		  ERRMSG("Non check condition returned\n\r");

		  switch (ProcessTSB(&npDCB->tsb, pCurIORB)) {

		     case RETRY:
			status |= SAMEIORB;
			break;

		     case IGNORE:
			if (pCurIORB->ErrorCode) {
			   pCurIORB->Status |= IORB_RECOV_ERROR;
			}		     /* fall through intentional   */
		     case FAIL: 	     /* both cases end the current */
			status |= ENDCURRENT;
			break;

		     case RESETADAPTER:
			status &= ~STARTWORK;		 /* don't start work */
			ResetAdapter(npDCB->ai);
			ERRMSG1("reset adapter for ai = %w\n\r",npDCB->ai);
			break;

		     case PURGE:
			ec = pCurIORB->ErrorCode;
			AbortDevice(npDCB, NULL, ec, 0);
			break;

		  }
	       }
	    }

	    /*--------------------------------------------------------*/
	    /* The failing IORB was not found.	End the current IORB. */
	    /*--------------------------------------------------------*/

	    else {
	       pCurIORB->ErrorCode = IOERR_ADAPTER_DIAGFAIL;
	       status |= ENDCURRENT;
	    }
	 }

	 /*-------------------------------------------------------------*/
	 /* Some other error ocurred.  Look futher at the completion	*/
	 /* code and decide what to do. 				*/
	 /*-------------------------------------------------------------*/

	 else {
	    if (cc == CC_PARMERR) {	   /*     parm in SCB		  */
	       status |= ENDCURRENT;
	       pCurIORB->ErrorCode = IOERR_CMD_SYNTAX;
	    }
	    else {			   /* some hardware adapter error */
	       status &= ~STARTWORK;
	       pCurIORB->ErrorCode = IOERR_ADAPTER_DIAGFAIL;
	       ResetAdapter(npDCB->ai);
	    }
	 }

	 break;

      /*----------------------------------------*/
      /* This case handles returned sense data. */
      /*----------------------------------------*/

      case WAITONSENSE:

	 /*---------------------------------------------------------*/
	 /* First see if the sense data was returned with no error. */
	 /*---------------------------------------------------------*/

	 if (cc ==  CC_SUCCESS || cc == CC_IMMSUCCESS) {

	    /*-------------------------*/
	    /* Process the sense data. */
	    /*-------------------------*/

	    switch (ProcessSenseData(pCurIORB, npDCB)) {

	       case ACTION_DONE:
		  status |= ENDCURRENT;
		  break;

	       case ACTION_ABORT:
		  ec = pCurIORB->ErrorCode;
		  AbortDevice(npDCB, NULL, ec, 0);
		  break;

	       case ACTION_RETRY:

		  status &= ~STARTWORK;
		  cmd = CMD_NORMSCB + npDCB->ldn;
		  if (!StartLocateModeSCB(npDCB->ai, npDCB->tsb.ppLastSCB,
					  cmd)) {
		     npDCB->state = WAITONINT;
		     STI();
		  }
		  break;

	       case ACTION_RECOVERED:

		  if (npDCB->pFailSCB->Enable & SCBEfCC) {
		     cmd = CMD_NORMSCB + npDCB->ldn;
		     status &= ~STARTWORK;
		     if (!StartLocateModeSCB(npDCB->ai,
					     npDCB->pFailSCB->ppNxtSCB, cmd)) {
			npDCB->state = WAITONINT;
			STI();
		     }
		  }
		  else {
		     if (pCurIORB->ErrorCode) {
			pCurIORB->Status |= IORB_RECOV_ERROR;
		     }
		     FinishIORB(pCurIORB, npDCB);
		  }

		  break;

	       case ACTION_STARTUNIT:
		  status &= ~STARTWORK;
		  StartUnit(pCurIORB, npDCB);
		  break;

	    }

	 }
	 else {
	    pCurIORB->ErrorCode = IOERR_ADAPTER_DIAGFAIL;
	    status |= ENDCURRENT;
	 }

	 break;

      /*-----------------------------------------------------------------*/
      /* This case handles the state when waiting for an interrupt on an */
      /* abort	command.						 */
      /*-----------------------------------------------------------------*/

      case WAITONABORT:

	 pIORB = npDCB->pDevCtrlIORB;

	 /*-------------------------------------------------------------*/
	 /* The abort is done.	Now just end all I/O that was active.	*/
	 /* Make sure that there is I/O because it may have completed	*/
	 /* before the immediate abort was issued.  Small chance of this*/
	 /*-------------------------------------------------------------*/

	 if (pCurIORB) {
	    CompleteIORBChain(npDCB, pCurIORB, NULL, IOERR_CMD_ABORTED);
	 }

	 FinishIORB(pIORB, npDCB);
	 npDCB->status &= ~UNITDEVCTRL;

	 break;


      /*--------------------------------------------------------------------*/
      /* This case handles the interrupt from a reset.	When it is recieved */
      /* All outstanding IORBs that were active are ended with a device     */
      /* reset error.  This is done for all LUNs with the same TargetID of  */
      /* the device that the reset was done for.			    */
      /*--------------------------------------------------------------------*/

      case WAITONRESET:

	 pIORB = npDCB->pDevCtrlIORB;
	 npDCB->pDevCtrlIORB = NULL;
	 TargetID = npDCB->UnitInfo.UnitSCSITargetID;
	 npDCBList = ACBTbl[npDCB->ai].np1stDCB;

	 for (i=0; i<ACBTbl[npDCB->ai].cDev; i++) {
	    if (npDCBList->UnitInfo.UnitSCSITargetID == TargetID) {
	       CompleteIORBChain(npDCBList, npDCBList->pActiveIORB,
				 NULL, IOERR_DEVICE_RESET);
	       npDCB->status &= ~UNITDEVCTRL;
	    }
	    npDCBList++;
	 }

	 FinishIORB(pIORB, npDCB);
	 break;


      /*-------------------------------------------------------------------*/
      /* This case is for interrupts when we are setting the global	   */
      /* timeout on the adapter for the specified device.  Since the IORB  */
      /* was never actually started, and it was in the process of	   */
      /* starting, we know it is ready.  So just set status to start the   */
      /* same IORB.  This will call the StartIORB routine, which will know */
      /* that the timeout has been set. 				   */
      /*-------------------------------------------------------------------*/

      case WAITONSETTIMEOUT:

	 status |= SAMEIORB;
	 break;


      /*-------------------------------------------------------------------*/
      /* This case is for restarting the unit.	TWe check to see if the    */
      /* start unit SCB passed.  If so then restart the I/O, if not then   */
      /* fail all the I/O.						   */
      /*-------------------------------------------------------------------*/

      case WAITONSTARTUNIT:

	 INFMSG1("Int received for startup on device %w\n\r",npDCB);
	 DBSTOP();
	 npDCB->state  = IDLE;		  /* device is now IDLE     */
	 npDCB->status &= ~UNITSCBBUSY;   /* reserved SCB now free  */
	 pIORB = npDCB->pWorkQ; 	  /* get the work queue     */
	 npDCB->pWorkQ = NULL;		  /* clear the work queue   */
	 status &= ~STARTWORK;		  /* don't start work later */

	 if (cc == CC_SUCCESS || cc == CC_IMMSUCCESS || cc == CC_RETRY) {
	    INFMSG("Completed OK, restarting I/O\n\r");
	    IORBEntry(pIORB);
	 }
	 else {
	    ERRMSG1("ERROR doing startup, cc = %w\n\r",cc);
	    CompleteIORBChain(npDCB, pIORB, NULL, IOERR_UNIT_NOT_READY);
	 }
	 break;

   }

   /*-------------------------------------------------------------------------*/
   /* Now we look at what has happend above.  We will either restart the same */
   /* IORB, or get new work to start.  In all cases if more work is started   */
   /* then the device state is set to WAITONINT.			      */
   /*-------------------------------------------------------------------------*/

   CLI();			      /* make sure ints disabled here  */

   pIORB = NULL;

   if (status & STARTWORK) {

      if (status & ENDCURRENT) {

	 if (pCurIORB->RequestControl & IORB_CHAIN) {
	    pIORB = pCurIORB->pNxtIORB;
	 }
	 FinishIORB(pCurIORB, npDCB);
      }
      else if (status & SAMEIORB) {
	 pIORB = npDCB->pActiveIORB;
      }
      else {
	 pIORB = GetNextWork(npDCB);
      }

      if (pIORB) {
	 StartIORB(pIORB, npDCB);
      }
      else {
	 npDCB->pActiveIORB = NULL;	   /* nothing active	*/
	 npDCB->state = IDLE;		   /* we are now IDLE	*/
      }
   }
}


/*************************************************************************/
/*									 */
/* DequeueSignal							 */
/*									 */
/*    This routine is called by the delivery service on an dequeue	 */
/*    signal.  It is used to signal that there are elements in the	 */
/*    inbound pipe to be dequeued and processed.			 */
/*									 */
/*	 Entry:  called far from the delivery service.			 */
/*									 */
/*		 DequeueSignal(key)					 */
/*									 */
/*		 key - is the key value (adapter index) 		 */
/*									 */
/*	 Exit:	0 always.						 */
/*									 */
/*	 Note:	Should be entered with interrupts disabled.		 */
/*									 */
/*************************************************************************/

ULONG _loadds FAR DequeueSignal(ULONG key)

{
   USHORT ai, rc;
   BOOL    done;

   EXTREPLY reply;		 /* element to be dequeued  */

   ai = key;
   done = FALSE;

   /*-----------------------------------------------------------*/
   /* We know here that there are elements in the pipe.  If we	*/
   /* are already processing a signal then we ignore this sig-	*/
   /* nal.  We will pick up the elements on the previous signal */
   /* because we dequeue until there are no elements left in	*/
   /* the pipe. 						*/
   /*-----------------------------------------------------------*/

   if (!(ACBTbl[ai].status & PROCESS_SIGNAL)) {
      ACBTbl[ai].status |= PROCESS_SIGNAL;

      /*--------------------------------------------------------*/
      /* We now call the dequeue service.  The dequeue service	*/
      /* should be called with ints off.  The routine which is	*/
      /* called by the dequeue service will turns ints on while */
      /* it runs.  This loop will run until the dequeue service */
      /* returns a pipe empty error.				*/
      /*--------------------------------------------------------*/

      while (!done) {
	 rc = DelivTbl.Dequeue(ACBTbl[ai].ph, (PEXTREPLY)&reply);
	 if (rc == DADD_ERR_PIPEEMPTY) {
	    done = TRUE;
	 }
	 else if (rc) {
	    ERRMSG("Unexpected return code from dequeue\n\r");
	    done = TRUE;
	 }
      }
      ACBTbl[ai].status &= ~PROCESS_SIGNAL;
   }
}

/*************************************************************************/
/*									 */
/* EnqueueSignal							 */
/*									 */
/*    This routine is called by the delivery service on an enqueue	 */
/*    signal.  It is used to signal that a pipe has gone from full to	 */
/*    not full. 							 */
/*									 */
/*	 Entry:  called far from the delivery service.			 */
/*									 */
/*		 EnqueueSignal(key)					 */
/*									 */
/*		 key - is the key value (adapter index) 		 */
/*									 */
/*	 Exit:	0 always.						 */
/*									 */
/*									 */
/*************************************************************************/

ULONG FAR _loadds EnqueueSignal(ULONG key)

{

   USHORT ai, i;
   NPDCB  npDCB;
   PIORB  pIORB;

   ai = key;

   /*-------------------------------------------------------------------*/
   /* For this adapter search through all DCBs and send any IORB chains */
   /* that are in the waiting queue to the IORB entry point.		*/
   /*-------------------------------------------------------------------*/

   npDCB = ACBTbl[ai].np1stDCB;

   for (i=0; i<ACBTbl[ai].cDev; i++) {
      if (pIORB = npDCB->pWorkQ) {	     /* assignment intentional	*/
	 npDCB->pWorkQ = NULL;
	 IORBEntry(pIORB);
      }
      npDCB++;
   }
   return(0);
}

/*************************************************************************/
/*									 */
/* ProcessMoveModeElement						 */
/*									 */
/*    Process an element from the adapter.  This is the router routine	 */
/*    which is called by the delivery dequeue service.			 */
/*									 */
/*	 Entry: ProcessMoveModeSignal(pReply, key)			 */
/*									 */
/*		pReply - far ptr to the element just dequeued		 */
/*		key    - key value registered for this unit.		 */
/*									 */
/*	 Exit:	returns 0L for all calls.				 */
/*									 */
/*************************************************************************/

ULONG FAR _loadds ProcessMoveModeElement(PEXTREPLY pReply, ULONG key)

{

   NPDCB  npDCB;
   PIORB  pIORB, pCurIORB, pLastIORB, pTempIORB;
   USHORT eltype, ec;

   UCHAR  sds;				      /* scsi device status   */

   STI();				      /* ints on while in here	*/

   /*----------------------------------------------------*/
   /* Determine which device this reply element is for.  */
   /*----------------------------------------------------*/

   pIORB = (PIORB)pReply->corrid;
   npDCB = IORBWRKSP2(pIORB,npDCB);

   LED_OFF(npDCB);

   eltype = pReply->ci & EID;

   /*------------------------------------------------------------------*/
   /* If the element is a normal reply element, call the pass routine. */
   /* The default is to call ProcessGoodElement if no address set.     */
   /*------------------------------------------------------------------*/

   if (eltype == EID_REP) {
      if (IORBWRKSP2(pIORB,npfnPass)) {
	 (*IORBWRKSP2(pIORB,npfnPass))(pIORB);
      }
      else {
	 ProcessGoodElement(pIORB);
      }
   }

   /*------------------------------------------------------------------*/
   /* Otherwise call the fail routine. The default is to call	       */
   /* ProcessErrorElement if no address is set. 		       */
   /*------------------------------------------------------------------*/

   else if (eltype == EID_ERR) {
      if (IORBWRKSP2(pIORB,npfnFail)) {
	 (*IORBWRKSP2(pIORB,npfnFail))(pIORB, pReply);
      }
      else {
	 ProcessErrorElement(pIORB, pReply);
      }
   }

   else {
      ERRMSG1("Unrecognized element type returned, %p\n\r",pReply);
   }

   CLI();	       /* ints back off to dequeue routine.   */

   return(0L);

}

/*************************************************************************/
/*									 */
/* ProcessGoodElmement							 */
/*									 */
/*    This routine is called when the element being processed completed  */
/*    without error.  It is the normal routine used.  It's address is    */
/*    placed in the npPass slot of the add workspace area of the IORB.	 */
/*    This is the default routine to be called. 			 */
/*									 */
/*	 Entry: ProcessGoodElement(pIORB)				 */
/*									 */
/*		pIORB - ptr to the IORB that had a good completion	 */
/*									 */
/*	 Exit:	none							 */
/*									 */
/*************************************************************************/

VOID ProcessGoodElement(PIORB pIORB)

{
   NPDCB  npDCB;

   npDCB = IORBWRKSP2(pIORB,npDCB);

   /*-------------------------------------------------------------*/
   /* We know that the element completed w/o error.  Increment	  */
   /* the count of elements field in the IORB (if found) and call */
   /* the finish routine.  If done then we will end it, otherwise */
   /* the next stage will be started.				  */
   /*-------------------------------------------------------------*/

   if (!RemoveIORBFromActiveChain(pIORB, npDCB)) {
      IORBWRKSP2(pIORB,cEl)++;
      if (pIORB->ErrorCode && !(npDCB->status & UNITDEVCTRL)) {
	 pIORB->Status |= IORB_RECOV_ERROR;
      }
      FinishIORB(pIORB, npDCB);
   }
}


/*************************************************************************/
/*									 */
/* ProcessErrorElmement 						 */
/*									 */
/*    This routine is called when the element being processed completed  */
/*    with an error.  It is the normal routine used.  It's address is    */
/*    placed in the npFail slot of the add workspace area of the IORB.	 */
/*    This is the default routine to be called on a failure.		 */
/*									 */
/*	 Entry: ProcessErrorElement(pIORB)				 */
/*									 */
/*		pIORB - ptr to the IORB that had a good completion	 */
/*									 */
/*	 Exit:	none							 */
/*									 */
/*************************************************************************/

VOID ProcessErrorElement(PIORB pIORB, PEXTREPLY pReply)

{

   NPDCB  npDCB;
   USHORT status, ec;
   PIORB  pTempIORB;

   UCHAR  sds;				   /* scsi device status	    */

   CE_REACTQUE ce_reactque;		   /* scsi queue reactivate element */

   npDCB = IORBWRKSP2(pIORB,npDCB);
   status = 0;

   INFMSG1("MOVE MODE ERROR ELEMENT RECIEVED %p\n\r",pReply);

   /*---------------------------------------------*/
   /* Get the device status.  Also set the react- */
   /* ivate bit if needed.  Remove the IORB from  */
   /* the active chain. 			  */
   /*---------------------------------------------*/

   sds = pReply->tsb.SCSIStatus & SCSISTATUSMASK;

   if (pReply->tsb.Retries & CMDQ_SUSPEND) {
      status |= SUSPENDQUE;
      IORBWRKSP2(pIORB,Status) |= IORBRESTARTQ;
   }

   RemoveIORBFromActiveChain(pIORB, npDCB);

   /*----------------------------------------------------------------*/
   /* See if the error was a check condition.  If so then make sure  */
   /* that the auto reqsense data was returned.  If not then end the */
   /* IORB with a general failure.  If so then look at the sense data*/
   /* to determine what the failure was and if it should be retried. */
   /*----------------------------------------------------------------*/

   if (sds == CHKCOND) {

      INFMSG("move mode check condition\n\r");

      if (pReply->tsb.Retries & SENSEDATA) {

	 switch (ProcessSenseData(pIORB, npDCB)) {

	    case ACTION_DONE:
	       FinishIORB(pIORB, npDCB);
	       break;

	    case ACTION_ABORT:
	       ec = pIORB->ErrorCode;
	       if (npDCB->pActiveIORB) {
		  AbortDevice(npDCB, pIORB, ec, 0);
	       }
	       else {
		  FinishIORB(pIORB, npDCB);
	       }
	       break;

	    case ACTION_RETRY:
	       status |= RESTARTSENT;
	       (*(npDCB->npCallTbl+pIORB->CommandCode)->npFunc)(pIORB, npDCB);
	       break;

	    case ACTION_RECOVERED:
	       IORBWRKSP2(pIORB,cEl)++;
	       if (pIORB->ErrorCode) {
		  pIORB->Status |= IORB_RECOV_ERROR;
	       }
	       FinishIORB(pIORB, npDCB);
	       break;

	    case ACTION_STARTUNIT:

	       /*-------------------------------------------------------*/
	       /* The unit needs to be started.  If we have not already */
	       /* started the sequence then show we have started it and */
	       /* use this IORB to do it.  If already started then put	*/
	       /* this IORB on the wait queue.	In either case make	*/
	       /* sure that the restart queue element is not sent.	*/
	       /*-------------------------------------------------------*/

	       IORBWRKSP2(pIORB,Status) &= ~IORBRESTARTQ;
	       status |= RESTARTSENT;

	       if (!(npDCB->status & UNITRESTART)) {
		  npDCB->status |= UNITRESTART;
		  StartUnit(pIORB, npDCB);
	       }
	       else {
		  QueueIORB(pIORB, npDCB, BACK);
	       }
	       break;
	 }
      }
      else {
	 ERRMSG("Sense Data was not returned\n\r");
	 pIORB->ErrorCode = IOERR_ADAPTER_DIAGFAIL;
	 FinishIORB(pIORB, npDCB);
      }
   }

   /*----------------------------------------------------------------*/
   /* Otherwise the error was not a check condition.  Look at the    */
   /* TSB to see what to do next.				     */
   /*----------------------------------------------------------------*/

   else {
      switch (ProcessTSB(&pReply->tsb, pIORB)) {
	 case RETRY:
	    status |= RESTARTSENT;
	    (*(npDCB->npCallTbl+pIORB->CommandCode)->npFunc)(pIORB, npDCB);
	    break;

	 case IGNORE:
	    if (pIORB->ErrorCode) {
	       pIORB->Status |= IORB_RECOV_ERROR;
	    }					 /* fall through intentional  */
	 case FAIL:				 /* both cases just fail      */

	    FinishIORB(pIORB, npDCB);
	    break;

	 case RESETADAPTER:
	    ERRMSG1("Resetting adapter %w from TSB error\n\r",npDCB->ai);
	    ResetAdapter(npDCB->ai);
	    break;

	 case PURGE:
	    ERRMSG("Purge on move mode device\n\r");
	    ec = pIORB->ErrorCode;
	    if (npDCB->pActiveIORB) {
	       AbortDevice(npDCB, pIORB, ec, 0);
	    }
	    else {
	       FinishIORB(pIORB, npDCB);
	    }
	    break;
      }
   }

   /*---------------------------------------------------------------------*/
   /* If the queue was suspended and was not restarted above, by a retry, */
   /* enqueue an element to restart the queue.	We call the delivery	  */
   /* service here directly because there is special code in the start	  */
   /* routine that we don't want to execute.                              */
   /*---------------------------------------------------------------------*/

   if (status & SUSPENDQUE && !(status & RESTARTSENT)) {

      ce_reactque.length = sizeof(CE_REACTQUE);
      ce_reactque.type	 = 0;
      ce_reactque.rsvd1  = 0;
      ce_reactque.ci	 = FN_REACTIVATEQ + SUPPRESS_REPLY;
      ce_reactque.dstid  = (ACBTbl[npDCB->ai].unit << 8) + npDCB->EntityID;
      ce_reactque.srcid  = SYSTEM_UNIT;
      ce_reactque.corrid = 0;

      DelivTbl.Enqueue(ACBTbl[npDCB->ai].ph, (PREQH)&ce_reactque, 1);
   }

}

/*************************************************************************/
/*									 */
/* GetSenseData 							 */
/*									 */
/*    Start a request to get the sense data from the device.		 */
/*									 */
/*	 Entry:  GetSenseData(npDCB, pIORB)				 */
/*									 */
/*		 npDCB - DCB for the device				 */
/*		 pIORB - IORB of the request				 */
/*									 */
/*	 Exit:	 TRUE if no error, FALSE otherwise.			 */
/*									 */
/*************************************************************************/

BOOL GetSenseData(NPDCB npDCB, PIORB pIORB)

{

   ULONG ppSenseBuf, cSenseData;
   PSCSI_STATUS_BLOCK  pStatusBlock;

   pStatusBlock = MAKEP(SELECTOROF(pIORB), (USHORT)pIORB->pStatusBlock);

   if (pIORB->RequestControl & IORB_REQ_STATUSBLOCK &&
       pStatusBlock->ReqSenseLen >= sizeof(SCSI_REQSENSE_DATA)) {

      ppSenseBuf = VirtToPhys((PBYTE)(pStatusBlock->SenseData));
      cSenseData = pStatusBlock->ReqSenseLen;
      IORBWRKSP(pIORB,Status) |= IORBUSERSENSBUF;
   }
   else {
      ppSenseBuf = ppData + (USHORT)&(npDCB->SenseData);
      cSenseData = sizeof(SCSI_REQSENSE_DATA);
   }

   npDCB->SenseSCB.ppXferBuf  = ppSenseBuf;
   npDCB->SenseSCB.XferBufLen = cSenseData;

   if (npDCB->status & UNITNOSYNC) {
      npDCB->SenseSCB.Cmd |= SCBCfNS;
   }

   if (StartLocateModeSCB(npDCB->ai, ppData+(USHORT)&(npDCB->SenseSCB),
			  CMD_NORMSCB+(USHORT)npDCB->ldn)) {
      return(FALSE);
   }
   else {
      STARTTIMEOUT(npDCB->ai, DfltTimeout);
      return(TRUE);
   }
}




/*************************************************************************/
/*									 */
/* FindFailingIORB							 */
/*									 */
/*    This routine searches the active IORB chain for the IORB that was  */
/*    the cause of the failure. 					 */
/*									 */
/*	 Entry:  FindFailingIORB(npDCB) 				 */
/*									 */
/*		 npIntDCB - DCB ptr.					 */
/*									 */
/*	 Exit:	 pIORB if found, NULL otherwise 			 */
/*									 */
/*************************************************************************/


PIORB FindFailingIORB(NPDCB npDCB)

{

   PIORB   pCurIORB;
   PSCBHDR pSCBHDR;
   PSCB    pSCB;
   ULONG   ppSCB;
   PTSB    pTSB;
   USHORT  i;
   NPSCBH  npSCBH;

   pCurIORB = npDCB->pActiveIORB;

   /*----------------------------------------------------------------------*/
   /* If the IORB was a passthru SCB then it was not grouped with other    */
   /* IORBs, therefore the failing IORB is the active IORB.  Since it was  */
   /* a passthru SCB the TSB was not written to the DCB.  Since we need to */
   /* look at it, copy it to the DCB.					   */
   /*----------------------------------------------------------------------*/

   if (IORBWRKSP(pCurIORB,Status) & IORBPASSTHRUSCB) {

      pSCBHDR = (PSCBHDR)((PIORB_ADAPTER_PASSTHRU)pCurIORB)->pControllerCmd;
      ppSCB   = ((PIORB_ADAPTER_PASSTHRU)pCurIORB)->ppSCB;

      while (1) {
	 pTSB = pSCBHDR->pTSB;
	 if (pTSB->ppLastSCB == ppSCB) {
	    npDCB->tsb = *pTSB;
	    npDCB->pFailSCB = (PSCB)((PBYTE)pSCBHDR + sizeof(SCBHDR));
	    return(pCurIORB);
	 }
	 else {
	    pSCB = (PSCB)((PBYTE)pSCBHDR + sizeof(SCBHDR));
	    if (pSCB->Enable & SCBEfCC) {
	       pSCBHDR = pSCBHDR->pNextSCBHdr;
	       ppSCB = pSCB->ppNxtSCB;
	    }
	    else {
	       ERRMSG1("Passthru IORB failing SCB not found %p\n\r",pCurIORB);
	       return(NULL);
	    }
	 }
      }
   }
   else {

      npSCBH = IORBWRKSP(pCurIORB,npSCBH);

      while (1) {
	 if (npDCB->tsb.ppLastSCB == npSCBH->scbctrl.ppSCB) {
	    npDCB->pFailSCB = (PSCB)&npSCBH->scb;
	    return(npSCBH->scbctrl.pIORB);
	 }
	 else {
	    if (npSCBH->scb.Enable & SCBEfCC) {
	       npSCBH = npSCBH->scbctrl.npNextSCBH;
	    }
	    else {
	       ERRMSG1("End SCB chain w/o failing IORB, DCB = %w\n\r", npDCB);
	       return(NULL);
	    }
	 }
      }
   }

   ERRMSG1("Unable to find failing IORB, DCB = %w\n\r",npDCB);
   return(NULL);    /* just in case  */

}



/*************************************************************************/
/*									 */
/* GetIntrAdapter							 */
/*									 */
/*    This routine determines the adapter that is interrupting. 	 */
/*									 */
/*	 Entry:  none							 */
/*									 */
/*	 Exit:	 -1 if not our interrupt, adapter index otherwise	 */
/*									 */
/*************************************************************************/

USHORT GetIntrAdapter()

{
   USHORT i;
   UCHAR  bsr;

   for (i=0; i<cAdapters; i++) {
      if ((bsr = ReadReg(ACBTbl[i].baseIO + REG_BSR)) & BSR_INTREQ) {
	 ACBTbl[i].bsr = bsr;
	 return(i);
      }
   }

   return(-1);

}


/*************************************************************************/
/*									 */
/* GetIntrDevice							 */
/*									 */
/*    This routine determines which device on the adapter is interrupting*/
/*    and returns the DCB ptr for it.  If the adapter itself has caused  */
/*    the interrupt then a NULL value is returned.  This routine only	 */
/*    works for adapters that are running in locate mode.  The EOI is	 */
/*    also done for the device. 					 */
/*									 */
/*	 Entry:  ai - adapter index					 */
/*									 */
/*	 Exit:	 npDCB if device is causing int, NULL if adapter is.	 */
/*									 */
/*************************************************************************/

NPDCB GetIntrDevice(USHORT ai)

{

   USHORT i, ldn;
   NPDCB  npDCB;

   UCHAR isr;

   isr	 = ReadReg(ACBTbl[ai].baseIO+REG_ISR);
   ldn	 = isr & ISR_DEVMASK;

   WriteAttn(ACBTbl[ai].baseIO, (USHORT)(ldn | ADAPTER_EOI));  /* Adapter EOI */

   if (ldn == ADAPTERDEVICE) {
      ACBTbl[ai].isr = isr;
      return(NULL);
   }
   else {
      npDCB = ACBTbl[ai].np1stDCB;

      for (i=0; i<ACBTbl[ai].cDev; i++) {
	 if (npDCB->ldn == ldn) {
	    npDCB->isr = isr;
	    ACBTbl[npDCB->ai].isr = isr;	/* may not be needed  */
	    return(npDCB);
	 }
	 npDCB++;
      }
   }

   ERRMSG1("No interrupting device found on adapter %w.\n\r",ai);
   return(NULL);

}


/*************************************************************************/
/*									 */
/* ProcessSenseData							 */
/*									 */
/*    Process sense data from a device.  Look at the sense data and tell */
/*    the caller what to do with the IORB.				 */
/*									 */
/*	 Entry:  ProcessSenseData(pIORB, npDCB, ecv, sk, asc, ascq)	 */
/*									 */
/*		 pIORB - failing IORB					 */
/*		 npDCB - DCB ptr					 */
/*									 */
/*	 Exit:	 USHORT:						 */
/*									 */
/*		   RECOVERED - The error was a recovered error by scsi	 */
/*			       subsystem.  Caller must handle.		 */
/*		   RETRY     - Caller should retry the IORB.		 */
/*		   DONE      - finish the IORB.  Error code has been set */
/*		   ABORT     - Abort all commands on the device.	 */
/*									 */
/*************************************************************************/

USHORT ProcessSenseData (PIORB pIORB, NPDCB npDCB)

{

   UCHAR  sk, ecv, asc, ascq;		      /* sense data values   */
   USHORT i;
   PSCSI_STATUS_BLOCK  pStatusBlock;

   /*---------------------------------------------------------------------*/
   /* Go get the sense data from either the user buffer or the DCB	  */
   /* reserved area.  The data is in the user area if he requested the	  */
   /* status block and he provided a buffer big enough to handle the	  */
   /* minimum sense data we need to look at.  If he wants it and did not  */
   /* give a big enough buffer then we will copy what he wants. 	  */
   /*---------------------------------------------------------------------*/

   /* assume we have a local copy, will overwrite if user buffer has data. */

   ecv	= npDCB->SenseData.ErrCode_Valid & SCSI_ERRCODE_MASK;
   sk	= npDCB->SenseData.SenseKey & SCSI_SENSEKEY_MASK;
   asc	= npDCB->SenseData.AddSenseCode;
   ascq = npDCB->SenseData.AddSenseCodeQual;

   if (pIORB->RequestControl & IORB_REQ_STATUSBLOCK) {
      pIORB->Status |= IORB_STATUSBLOCK_AVAIL;
      pStatusBlock =
	 MAKEP(SELECTOROF(pIORB), (USHORT)pIORB->pStatusBlock);
      pStatusBlock->AdapterErrorCode = 0;
      pStatusBlock->TargetStatus     = SCSI_STAT_CHECKCOND;
      pStatusBlock->ResidualLength   = 0;

      if (IORBWRKSP(pIORB,Status) & IORBUSERSENSBUF) {
	 ecv  = pStatusBlock->SenseData->ErrCode_Valid & SCSI_ERRCODE_MASK;
	 sk   = pStatusBlock->SenseData->SenseKey & SCSI_SENSEKEY_MASK;
	 asc  = pStatusBlock->SenseData->AddSenseCode;
	 ascq = pStatusBlock->SenseData->AddSenseCodeQual;
      }
      else {  /* we need to copy what he wanted (less than minimum)  */

	 for (i=0; i<pStatusBlock->ReqSenseLen; i++) {
	    *(((PBYTE)pStatusBlock->SenseData)+i) =
	       *(((NPBYTE)&(npDCB->SenseData))+i);
	 }
      }

      if (pStatusBlock->ReqSenseLen) {
	 pStatusBlock->Flags = STATUS_SENSEDATA_VALID;
      }
   }

   INFMSG1("Sense Data returned:\n\r   ECV = %w\n\r",(USHORT)ecv);
   INFMSG1("   SK  = %w\n\r",(USHORT)sk);
   INFMSG1("   ASC = %w\n\r",(USHORT)asc);
   INFMSG1("   ASQ = %w\n\r",(USHORT)ascq);
   DBSTOP();

   pIORB->Status |= IORB_ERROR;


   /*------------------------------------------------------*/
   /* First make sure the sense data is not vendor unique  */
   /*------------------------------------------------------*/

   if (ecv != SCSI_UNIQUE_SENSEDATA) {

      /*--------------------------------------------------------*/
      /* Next make sure that we have an error (sense key <> 0)	*/
      /*--------------------------------------------------------*/

      if (sk) {

	 /*----------------------------------------------------*/
	 /* Map the sense data to an IORB defined error code.  */
	 /*----------------------------------------------------*/

	 if (sk == SCSI_SK_NOTRDY) {
	    pIORB->ErrorCode = IOERR_UNIT_NOT_READY;
	 }
	 else {
	    if (asc < MaxAddSenseData) {
	       pIORB->ErrorCode = AddSenseDataMap[asc];
	    }
	    else {
	       pIORB->ErrorCode = IOERR_ADAPTER_REFER_TO_STATUS;
	    }
	 }

	 /*-------------------------------------------------------------*/
	 /* If the error was a recovered error then return that status	*/
	 /* and let the caller figure out where to restart the command. */
	 /*-------------------------------------------------------------*/


	 if (sk == SCSI_SK_RECERR) {
	    return(ACTION_RECOVERED);
	 }

	 /*---------------------------------------------------------------*/
	 /* Otherwise the command actually failed.  Determine if a retry  */
	 /* is allowed or not.	If so then return ACTION_RETRY, if not	  */
	 /* then determine if the caller should end just this command or  */
	 /* abort all commands. 					  */
	 /*---------------------------------------------------------------*/

	 else {
	    if (pIORB->RequestControl & IORB_DISABLE_RETRY ||
		npDCB->UnitInfo.UnitFlags & UF_REMOVABLE   ||
		!IORBWRKSP(pIORB,cRetries)) {

	       if (npDCB->UnitInfo.UnitFlags & UF_REMOVABLE) {
		  return(ACTION_ABORT);
	       }
	       else {
		  return(ACTION_DONE);
	       }
	    }
	    else {

	       /*-------------------------------------------*/
	       /* A retry is allowed.  See if the unit has  */
	       /* stopped and needs to be init'd, otherwise */
	       /* just do a retry.			    */
	       /*-------------------------------------------*/

	       if (asc == 0x04 && ascq == 0x02) {
		  pIORB->ErrorCode = 0;       /* no error for now  */
		  pIORB->Status &= ~IORB_ERROR;
		  return(ACTION_STARTUNIT);
	       }
	       else {
		  IORBWRKSP(pIORB,cRetries)--;
		  return(ACTION_RETRY);
	       }
	    }
	 }
      }
      else {
	 return(ACTION_DONE);
      }
   }
   else {
      pIORB->ErrorCode = IOERR_ADAPTER_REFER_TO_STATUS;
      return(ACTION_DONE);
   }
}


/*************************************************************************/
/*									 */
/* RemoveIORBFromActiveChain						 */
/*									 */
/*    This routine removes the specified IORB from the devices active	 */
/*    IORB chain.							 */
/*									 */
/*	 Entry:  RemoveIORBFromActiveChain(pIORB, npDCB)		 */
/*									 */
/*		 pIORB - ptr to IORB to be removed			 */
/*		 npDCB - ptr to DCB					 */
/*									 */
/*	 Exit:	 0 If found, ERROR otherwise.				 */
/*									 */
/*************************************************************************/

USHORT RemoveIORBFromActiveChain(PIORB pIORB, NPDCB npDCB)

{

   PIORB pCurIORB, pLastIORB;

   SAVEIF();

   pCurIORB = pLastIORB = npDCB->pActiveIORB;

   while (pCurIORB != pIORB) {
      pLastIORB = pCurIORB;

      if (!pCurIORB->Length) {
	 _asm int 3
      }

      if (pCurIORB->RequestControl & IORB_CHAIN)
	 pCurIORB  = pCurIORB->pNxtIORB;
      else
	 break;
   }

   if (pCurIORB == pIORB) {
      if (pCurIORB == pLastIORB) {
	 npDCB->pActiveIORB = pCurIORB->pNxtIORB;
      }
      else {
	 pLastIORB->pNxtIORB = pCurIORB->pNxtIORB;
	 if (!(pIORB->RequestControl & IORB_CHAIN)) {
	    pLastIORB->RequestControl &= ~IORB_CHAIN;
	 }
      }
      pIORB->RequestControl &= ~IORB_CHAIN;
      pIORB->pNxtIORB	     = NULL;
      RESTOREIF();
      return(0);
   }
   else {
      INFMSG1("IORB %p not found for device ",pIORB);
      INFMSG1("%w\n\r",npDCB);
      DBSTOP();
      RESTOREIF();
      return(ERROR);
   }
}


/*************************************************************************/
/*									 */
/* ProcessTSB								 */
/*									 */
/*    This routine looks at the TSB that was returned.	It determines	 */
/*    what should be done (ie retry, reset, fail).			 */
/*									 */
/*	 Entry:  ProcessTSB(pTSB, npDCB)				 */
/*									 */
/*		 pTSB  - ptr to the TSB 				 */
/*		 pIORB - ptr to IORB					 */
/*									 */
/*	 Exit:	 IGNORE       - ignore the error and end request	 */
/*		 RESET	      - reset the device			 */
/*		 RESETADAPTER - reset the adapter			 */
/*		 RETRY	      - retry the request			 */
/*		 FAIL	      - fail the request			 */
/*		 PURGE	      - fail all active and waiting requests	 */
/*									 */
/* NOTE: The far ptr to the TSB is needed because the TSB is returned	 */
/*	 in an element when operating in move mode, not written 	 */
/*	 directly to the DCB as in locate mode. 			 */
/*									 */
/*************************************************************************/

USHORT ProcessTSB(PTSB pTSB, PIORB pIORB)

{

   USHORT ec;	   /* error code for the IORB				    */
   USHORT ce, de;  /* command error and device error from TSB		    */
   USHORT status;  /* internal status bits 00000001 - attempt retry	    */
		   /*			   00000010 - reset adapter	    */
		   /*			   00000100 - abort device (t/o)    */
		   /*			   00001000 - check 4 disable retry */

   #define PTSB_ATTEMPTRETRY	  0x0001
   #define PTSB_RESETADAPTER	  0x0002
   #define PTSB_ABORTDEVICE	  0x0004
   #define PTSB_CHKDISABLERETRY   0x0008

   ec	  = 0;
   status = 0;

   if (!(pTSB->Status & TSBSfNOERR)) {	      /* there was an error

      /*-------------------------------------------------*/
      /* First see if this error is a negotiation error. */
      /* If so then just retry the command, w/o checking */
      /* to see if the retry is OK.  If the negotiation  */
      /* error caused a reset to the device then a reset */
      /* error will be returned later.	We recover then. */
      /*-------------------------------------------------*/

      if (pTSB->Retries & RC_NEGOTIATE_ERROR) {
	 return(RETRY);
      }

      /*-------------------------------------------------*/
      /* Otherwise get the command and device error and  */
      /* determine what happened.  Set an internal error */
      /* code and status.  These will be used later to	 */
      /* determine what action to take. 		 */
      /*-------------------------------------------------*/

      ce = pTSB->CmdError;	     /* get command and device error codes  */
      de = pTSB->DevError;

      if (ce) {
	 if (ce == 0x0b) {		      /* max RBA exceeded (fence)   */
	    ec = IOERR_RBA_LIMIT;
	 }
	 else if (ce == 0x20) { 	      /* adapter hardware error     */
	    ec = IOERR_ADAPTER_DIAGFAIL;
	    status = PTSB_RESETADAPTER;       /* reset adapter		    */
	 }
	 else if (ce == 0x21) { 	      /* global timeout 	    */
	    ec = IOERR_ADAPTER_TIMEOUT;
	    status = PTSB_ATTEMPTRETRY+PTSB_CHKDISABLERETRY;
	 }
	 else if (ce == 0x24) { 	      /* cmd aborted by us or caller */
	    ec = IOERR_CMD_ABORTED;
	 }
	 else { 			      /* attempt retry on all others */
	    status = PTSB_ATTEMPTRETRY;
	    ec = IOERR_ADAPTER_DIAGFAIL;
	 }
      }
      else if (de) {
	 if (de == 0x01) {		/* SCSI bus reset  */
	    ec = IOERR_DEVICE_RESET;
	    status = PTSB_ATTEMPTRETRY+PTSB_CHKDISABLERETRY;
	 }
	 else if (de == 0x02 || de == 0x11 || de == 0x13) {   /* bus error  */
	    ec = IOERR_DEVICE_DEVICEBUSCHECK;
	    status = PTSB_ATTEMPTRETRY;
	 }
	 else if (de == 0x10) { 	/* selection timeout (power off)  */
	    ec = IOERR_UNIT_PWR_OFF;
	    status = PTSB_ATTEMPTRETRY;
	 }
	 else if (de == 0x20) { 	/* short length record	 */
	    ec = IOERR_DEVICE_UNDERRUN;
	 }
      }
      else {				/* otherwise just do a retry  */
	 status = PTSB_ATTEMPTRETRY;
      }

      /*-------------------------------------------------------------*/
      /* Now look at our internal status to see what to return.  If  */
      /* a retry is to be attempted then see if we should check to   */
      /* see if retries are disabled.  If disabled then all waiting  */
      /* IORBs are purged, otherwise the current is retried.	     */
      /* The remaining return codes are self explanatory.	     */
      /*-------------------------------------------------------------*/

      pIORB->ErrorCode = ec;

      if (status & PTSB_ATTEMPTRETRY) { 	    /* do a retry ?  */
	 if (IORBWRKSP(pIORB,cRetries)--) {	    /* more left ?   */
	    if (status & PTSB_CHKDISABLERETRY &&
		pIORB->RequestControl & IORB_DISABLE_RETRY) {
	       return(PURGE);
	    }
	    else {
	       return(RETRY);
	    }
	 }
	 else {
	    return(FAIL);
	 }
      }
      else if (status & PTSB_RESETADAPTER) {
	 return(RESETADAPTER);
      }
      else {
	 return(FAIL);
      }
   }

   /*--------------------------------------------*/
   /*  No error found so just ignore the error.  */
   /*--------------------------------------------*/

   else {
      return(IGNORE);
   }
}

