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

               /*--------------------------------------------*/ /*205624*/
               /* At this point we must reset pActiveIORB to */ /*205624*/
               /* the failing IORB since if we don't, when   */ /*205624*/
               /* final status is posted for the failing IORB*/ /*205624*/
               /* we will repost the chain from pActiveIORB, */ /*205624*/
               /* but this time pActiveIORB may be on the    */ /*205624*/
               /* free list or have been resued and now in a */ /*205624*/
               /* new active chain. Either way erroneous     */ /*205624*/
               /* posting will occur and we may doubly post  */ /*205624*/
               /* some requests. Further more hotfix         */ /*205624*/
               /* correction probably won't get a chance to  */ /*205624*/
               /* run.                                       */ /*205624*/
               /*--------------------------------------------*/ /*205624*/

               npDCB->pActiveIORB = pIORB;                      /*205624*/

               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;
         }
    else {                                                          /*@V131958*/
       pIORB = GetNextWork(npDCB);                                  /*@V131958*/
    }                                                               /*@V131958*/
         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);
      }

      /*-------------------------------------------------*/ /* ABM0995 */
      /* Check for SCSI Busy Status returned from the    */ /* ABM0995 */
      /* device.  If so, then set error code and return  */ /* ABM0995 */
      /* command failed indication.                      */ /* ABM0995 */
      /*-------------------------------------------------*/ /* ABM0995 */
                                                            /* ABM0995 */
      if ((pTSB->SCSIStatus & SCSISTATUSMASK) == 0x08) {    /* ABM0995 */
         pIORB->ErrorCode = IOERR_DEVICE_BUSY;              /* ABM0995 */
         return(FAIL);                                      /* ABM0995 */
      }                                                     /* ABM0995 */
      /*-------------------------------------------------*/
      /* 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;                 /* ABM0995 */
//          status = PTSB_ATTEMPTRETRY;              /* ABM0995 */
            pIORB->ErrorCode = IOERR_UNIT_PWR_OFF;   /* ABM0995 */
            return(FAIL);                            /* ABM0995 */
         }
         else if (de == 0x20) {         /* short length record   */
            ec = IOERR_DEVICE_UNDERRUN;
         }
      }
      else if ((pTSB->SCSIStatus & SCSISTATUSMASK) == SCSI_STAT_CONDMET) { //@179288
         return(IGNORE);                                                   //@179288
      }                                                                    //@179288
      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);
   }
}

