/*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 = SCSISUBS.C
 *
 * DESCRIPTIVE NAME = IBM2SCSI.ADD - Adapter Driver for IBM SCSI adapters.
 *                    General routines.
 *
 *
 * VERSION = V2.0
 *
 * DATE
 *
 * DESCRIPTION  This file contains general utility routines for the ADD.
 *
*/

/*----------------------*/
/* 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"


/************************************************************************/
/*                                                                      */
/* FindDeviceEntry                                                      */
/*                                                                      */
/*    Find the specified unit handle entry and return a pointer to the  */
/*    device table entry.                                               */
/*                                                                      */
/*    Entry:  FindDeviceEntry(uh)                                       */
/*                                                                      */
/*            uh - unit handle                                          */
/*                                                                      */
/*    Exit:  NULL if entry not found                                    */
/*                                                                      */
/************************************************************************/

NPDCB FindDeviceEntry(USHORT uh)

{

   /*-------------------------------------------------------------*/
   /* Get the device table entry from the unit handle table.  The */
   /* unit handle uh is used as an index into the table.          */
   /*-------------------------------------------------------------*/

   if (uh < cDevices) {
      return(*(UnitHandleTbl + uh));
   }
   else {
      return(NULL);
   }
}


/************************************************************************/
/*                                                                      */
/* BuildSCB                                                             */
/*                                                                      */
/*    Build the specified canned SCB.                                   */
/*                                                                      */
/*    Entry:  BuildSCB(npSCB, type, npDCB, lba, ppBuf, BufLen, BlkCnt,  */
/*                     BlkSize)                                         */
/*                                                                      */
/*            npSCB   - ptr to the SCB to be built                      */
/*            type    - type of SCB to build                            */
/*            npDCB   - ptr to the device control block                 */
/*            lba     - logical block address                           */
/*            ppBuf   - phys ptr to the system buffer address (SG list) */
/*            BufLen  - Buffer length                                   */
/*            BlkCnt  - number of blocks to xfer                        */
/*            BlkSize - block size                                      */
/*                                                                      */
/*    Exit:   good always.                                              */
/*                                                                      */
/************************************************************************/

VOID BuildSCB(NPSCB npSCB, USHORT type, NPDCB npDCB, ULONG lba, ULONG ppBuf,
              ULONG BufLen, USHORT BlkCnt, USHORT BlkSize)
{

   /*-------------------------------------------------------------*/
   /* Build the SCB using the switch statement.  The SCB built is */
   /* considered a default SCB.  It will, if applicable, have the */
   /* scatter/gather list enabled.  The caller should turn this   */
   /* off if required.  All other fields will be zero'd to insure */
   /* the SCB is fresh.  The chain field will also be disabled as */
   /* a default.                                                  */
   /*-------------------------------------------------------------*/

   npSCB->LBA        = lba;
   npSCB->ppXferBuf  = ppBuf;
   npSCB->XferBufLen = BufLen;
   npSCB->ppTSB      = ppData + (USHORT)&npDCB->tsb;
   npSCB->ppNxtSCB   = 0L;
   npSCB->EXT.BLK.BlockCnt   = BlkCnt;
   npSCB->EXT.BLK.BlockSize  = BlkSize;

   switch (type) {

      case SCB_READ:
         npSCB->Cmd    = SCBREAD;
         npSCB->Enable = SCBEWREAD;
         break;

      case SCB_WRITE:

         npSCB->Cmd    = SCBWRITE;
         npSCB->Enable = SCBEWWRITE;
         break;

      case SCB_READV:
         npSCB->Cmd    = SCBREADV;
         npSCB->Enable = SCBEWREADV;
         break;

      case SCB_WRITEV:
         npSCB->Cmd    = SCBWRITEV;
         npSCB->Enable = SCBEWWRITEV;
         break;

      case SCB_CMDSENSE:
         npSCB->Cmd    = SCBCMDSENSE;
         npSCB->Enable = SCBEWCMDSENSE;
         break;

      case SCB_DEVICECAP:
         npSCB->Cmd    = SCBDEVICECAP;
         npSCB->Enable = SCBEWDEVICECAP;
         break;

      case SCB_DEVICEINQ:
         npSCB->Cmd    = SCBDEVICEINQ;
         npSCB->Enable = SCBEWDEVICEINQ;
         break;

      case SCB_SENDOTHER:
         npSCB->Cmd    = SCBSENDOTHER;
         npSCB->Enable = SCBEWSENDOTHER;
         break;

      default:
         ERRMSG1("Attempt to build invalid SCB type %w\n\r",type);

   }
}


/************************************************************************/
/*                                                                      */
/* AllocSCB                                                             */
/*                                                                      */
/*    Get an SCB for the caller.  An SCB is retrieved from the SCB pool */
/*    free list.  If there are no free SCBs then the reserved SCB in    */
/*    the DCB is allocated.  It is allocated only if the request is in  */
/*    the process of being started.  This eliminates a possible dead    */
/*    lock situation where no SCBs are available.                       */
/*                                                                      */
/*    Entry:  AllocSCB()                                                */
/*                                                                      */
/*            npDCB - Ptr to the DCB                                    */
/*            pIORB - ptr to the IORB                                   */
/*                                                                      */
/*    Exit:   NULL if not found, pointer to an SCBCTRL if found.        */
/*                                                                      */
/************************************************************************/

NPSCBH AllocSCB(NPDCB npDCB, PIORB pIORB)

{

   NPSCBH npSCBH;

   SAVEIF();      /* nobody but us messes with the free list for a bit  */

   /*---------------------------------------------------------------*/
   /* Try to get an SCB from the free list.  If no more SCBs on the */
   /* free list then try to allocate the DCB reserved SCB.  The SCB */
   /* from the DCB is only allocated if the IORB is in the process  */
   /* of starting.                                                  */
   /*---------------------------------------------------------------*/

   if (npSCBH = npSCBFreeList) {       /* asignment intentional !!  */
      npSCBFreeList              = npSCBFreeList->scbctrl.npNextSCBH;
      npSCBH->scbctrl.npNextSCBH = NULL;
      npSCBH->scbctrl.status     = 0;
   }
   else {
      if (!(npDCB->status & UNITSCBBUSY) &&
          (IORBWRKSP(pIORB,Status) & IORBSTARTING ||
          npDCB->state == IDLE)) {
         npSCBH             = &(npDCB->ResSCBH);
         npDCB->status     |= UNITSCBBUSY;
         npSCBH->scbctrl.status = DCBSCB;
      }
      else {
         npSCBH = NULL;
      }
   }

   /*--------------------------------------*/
   /* If we found one, then initialize it. */
   /*--------------------------------------*/

   if (npSCBH) {
      npSCBH->scb.LBA                 = 0L;
      npSCBH->scb.ppXferBuf           = 0L;
      npSCBH->scb.XferBufLen          = 0L;
      npSCBH->scb.ppNxtSCB            = 0L;
      npSCBH->scb.EXT.BLK.BlockCnt    = 0;
      npSCBH->scb.EXT.BLK.BlockSize   = 0;
      npSCBH->scbctrl.pIORB           = pIORB;
   }

   RESTOREIF();

   return(npSCBH);

}


/************************************************************************/
/*                                                                      */
/* FreeSCB                                                              */
/*                                                                      */
/*    Put the SCB(s) back on the free list.  The ptr passed is a ptr    */
/*    to a SCB control structure.  The number of SCBs in the list       */
/*    is also given.  The DCB reserved SCB can also be in the list.     */
/*                                                                      */
/*    Entry:  FreeSCB(cSCBs, SCBList, npDCB)                            */
/*                                                                      */
/*            cSCBs   - number of SCBs to free in a linked list.        */
/*                    - 0 indicates all.                                */
/*            SCBList - ptr to first SCBControl header                  */
/*            npDCB   - ptr to device control block                     */
/*                                                                      */
/*    Exit:   returns a ptr to the remainder of the pool list if only   */
/*            the first entry is returned.  NULL if only 1 item in the  */
/*            list or the whole list is freed.                          */
/*                                                                      */
/*    Notes:  Only 64K entries can be returned with cSCBs = 0 on input  */
/*            This should more than handle any case.                    */
/*                                                                      */
/************************************************************************/

NPSCBH FreeSCB(USHORT cSCBs, NPSCBH npSCBListH, NPDCB npDCB)

{

   NPSCBH npSCBH, nextSCBH;

   SAVEIF();                   /* save the interrupt flag and disable  */

   npSCBH = npSCBListH;

   do {
      if (!(npSCBH->scbctrl.status & DCBSCB)) {

         nextSCBH                   = npSCBH->scbctrl.npNextSCBH;
         npSCBH->scbctrl.npNextSCBH = npSCBFreeList;
         npSCBFreeList              = npSCBH;
         npSCBH                     = nextSCBH;
      }
      else {
         npDCB->status                    &= ~UNITSCBBUSY;
         npSCBH                            = npSCBH->scbctrl.npNextSCBH;
         npDCB->ResSCBH.scbctrl.npNextSCBH = NULL;
      }
   } while (npSCBH && --cSCBs);

   RESTOREIF();                /* restore the interrupt flag  */

   return(npSCBH);

}


/************************************************************************/
/*                                                                      */
/* QueueIORB                                                            */
/*                                                                      */
/*    This routine will place the IORB on the waiting queue for the     */
/*    device.  The location option indicates if the IORB is to be       */
/*    queued at the end or the front.  The end of the queue is found by */
/*    walking the chain until the IORB_CHAIN field is not set.  This    */
/*    will indicate the end of the chain.                               */
/*                                                                      */
/*    Entry:  QueueIORB(pIORB, npDCB, loc)                              */
/*                                                                      */
/*            pIORB - ptr to IORB to queue                              */
/*            npDCB - ptr to device control block                       */
/*            loc   - front or back of the queue                        */
/*                                                                      */
/*    Exit:   normal always                                             */
/*                                                                      */
/*    Note:   This routine can be called at interrupt time.             */
/*                                                                      */
/************************************************************************/

VOID QueueIORB(PIORB pIORB, NPDCB npDCB, USHORT loc)

{

   PIORB pCurIORB;

   SAVEIF();                           /* nobody else in here           */
   TRACE(TR_QUEUED, pIORB);            /* insert a queued trace record  */

   /*--------------------------------------------------------------*/
   /* If the location is the back of the queue then find the end   */
   /* of the queue and link this IORB to last one.  If there is no */
   /* queue yet then this IORB becomes the queue.                  */
   /*--------------------------------------------------------------*/

   if (loc == BACK) {
      if (!npDCB->pWorkQ) {                            /* no work queued  */
         npDCB->pWorkQ = pIORB;
      }
      else {                                           /* work is queued  */
         pCurIORB = npDCB->pWorkQ;
         while (pCurIORB->RequestControl & IORB_CHAIN) {
            pCurIORB = pCurIORB->pNxtIORB;
         }
         pCurIORB->pNxtIORB = pIORB;
         pCurIORB->RequestControl |= IORB_CHAIN;
      }
   }

   /*--------------------------------------------------------------*/
   /* Otherwise the location is the front.  Make this IORB the 1st */
   /* IORB in the chain.                                           */
   /*--------------------------------------------------------------*/

   else {
      if (npDCB->pWorkQ) {                        /* work already queued */
         pIORB->RequestControl |= IORB_CHAIN;
         pIORB->pNxtIORB = npDCB->pWorkQ;
         npDCB->pWorkQ = pIORB;
      }
      else {
         npDCB->pWorkQ = pIORB;
         pIORB->RequestControl &= ~IORB_CHAIN;
      }
   }

   RESTOREIF();
}

/************************************************************************/
/*                                                                      */
/* DeQueueIORB                                                          */
/*                                                                      */
/*    This routine dequeues the first element in the IORB work queue.   */
/*    It will return NULL if the work queue is empty.                   */
/*                                                                      */
/*    Entry:  DeQueueIORB(npDCB)                                        */
/*                                                                      */
/*            npDCB - ptr to device control block                       */
/*                                                                      */
/*    Exit:   PIORB = IORB if present, NULL otherwise                   */
/*                                                                      */
/*    Note:   This routine can be called at interrupt time.             */
/*                                                                      */
/************************************************************************/

PIORB DeQueueIORB(NPDCB npDCB)

{

   PIORB pIORB;

   /*---------------------------------------------------------------*/
   /* Turn ints off and peel the first IORB from the queue.  If no  */
   /* IORBs in the queue then return NULL.                          */
   /*---------------------------------------------------------------*/

   SAVEIF();

   if (pIORB = npDCB->pWorkQ) {                 /* assignment intentional !! */
      npDCB->pWorkQ          = pIORB->pNxtIORB;
      pIORB->pNxtIORB        = NULL;
      pIORB->RequestControl &= ~IORB_CHAIN;
      TRACE(TR_DEQUEUED, pIORB);
   }

   RESTOREIF();
   return(pIORB);
}


/*************************************************************************/
/*                                                                       */
/* StartIORB                                                             */
/*                                                                       */
/*    This routine starts the passed IORB chain.  The ABIOS RB in the    */
/*    device table entry is set-up to point to the first SCB in hanging  */
/*    off the IORB.  If an SCB chain is being sent somebody else must    */
/*    set up that chain properly.  This routine assumes the device is    */
/*    is not busy.                                                       */
/*                                                                       */
/*    Since the device is not busy, when called at task time we know     */
/*    that there can be nothing queued.  Since nothing is queued, if the */
/*    request completes immediately we don't have to go look for more    */
/*    work to start.  For this reason the caller is responsible for      */
/*    starting more work if there is work queued.                        */
/*                                                                       */
/*                                                                       */
/*       Entry:  StartIORB(pIORB, npDCB, start, complt, error)           */
/*                                                                       */
/*               pIORB  - ptr to the IORB group to start                 */
/*               npDCB  - ptr to the DCB of the device                   */
/*                                                                       */
/*       Exit:   Returns:                                                */
/*                  COMPLETE - Request was started and it completed.     */
/*                  ERROR    - Request completed with error.             */
/*                                                                       */
/*       Note:   This routine can be called at interrupt time.           */
/*                                                                       */
/*                                                                       */
/*************************************************************************/

VOID StartIORB(PIORB pIORB, NPDCB npDCB)

{

   USHORT cmd;
   ULONG  ppSCB, to_cmd;

   /*------------------------------------------------------------*/
   /* Assume the request is going to start OK.  Set the IORB to  */
   /* show that the IORB is starting.  Set the active IORB in    */
   /* the DCB, and set the device state.                         */
   /*------------------------------------------------------------*/

   IORBWRKSP(pIORB,Status) |= IORBSTARTING;
   npDCB->pActiveIORB = pIORB;
   npDCB->state = WAITONINT;

   /*-------------------------------------------------------------------*/
   /* Before sending the SCB out, see if any adjustments in the adapter */
   /* global command timeout is required.  We try to use the adapter    */
   /* value, but it is only good upto 128 minutes.  Some I/Os take upto */
   /* 4 hours (tape erase and retension).  This section of code will    */
   /* adjust the adapter global timeout upto the maximum value as req-  */
   /* uired by the command.  If the timeout specified for the IORB is   */
   /* greater than the max adapter value then the adapter timeout is    */
   /* disabled and the software timeout is used.  Once disabled it will */
   /* never be reset.  In general the timeout value is only adjusted    */
   /* upwards.  It is never lowered.  If the t/o value is adjusted with */
   /* an I/O then we exit here w/o starting the actual I/O.  The int    */
   /* handler will call us back later to start it.                      */
   /*-------------------------------------------------------------------*/

   if (!(npDCB->status & UNITTODISABLED)) {        /* only if not disabled */
      if (pIORB->Timeout > npDCB->Timeout) {
         if (pIORB->Timeout > MAX_TIMEOUT) {
            npDCB->status |= UNITTODISABLED;
            to_cmd = SCB_FEATURE;
         }
         else {
            to_cmd = SCB_FEATURE + (pIORB->Timeout << 16);   /* REN 91694 */
         }

         if (!StartLocateModeSCB(npDCB->ai, to_cmd,
                            CMD_IMMEDIATE+(USHORT)npDCB->ldn)) {
            npDCB->Timeout = pIORB->Timeout;
            npDCB->state = WAITONSETTIMEOUT;
         }

         STI();
         return ;
      }
   }
   else {                                      /* no adapter op required  */
      if (pIORB->Timeout > npDCB->Timeout) {
         npDCB->Timeout = pIORB->Timeout;
      }
   }

   /*----------------------------------------------------------------*/
   /* Now for any adapter that has only 1 device attached turn the   */
   /* No Disconnect bit in the SCB enable word on.  This will in-    */
   /* crease performance.
   /*----------------------------------------------------------------*/

   if (ACBTbl[npDCB->ai].cDev == 1) {
      IORBWRKSP(pIORB,npSCBH)->scb.Cmd |= SCBCfND;
   }

   /*----------------------------------------------------------------*/
   /* Determine the command to send to the adapter.  Send the SCB to */
   /* the adapter.  If there is an error then the adapter is reset   */
   /* by the start routine.  The start routine leaves INTs off.      */
   /*----------------------------------------------------------------*/

   cmd = ((IORBWRKSP(pIORB,Status) & IORBSCBLONG) ? CMD_LONGSCB : CMD_NORMSCB);
   if (IORBWRKSP(pIORB,Status) & IORBPASSTHRUSCB) {
      ppSCB = ((PIORB_ADAPTER_PASSTHRU)pIORB)->ppSCB;
   }
   else {
      ppSCB = IORBWRKSP(pIORB,npSCBH)->scbctrl.ppSCB;
   }

   TRACE(TR_STARTED, pIORB);

   if (!StartLocateModeSCB(npDCB->ai, ppSCB, cmd+(USHORT)npDCB->ldn)) {
      STARTTIMEOUT(npDCB->ai, npDCB->Timeout+30);
      LED_ON(npDCB);
   }

   STI();   /* StartLocateModeSCB leaves ints off  */
   return;

}


/*************************************************************************/
/*                                                                       */
/* FinishIORB                                                            */
/*                                                                       */
/*    This routine actually does the finish up processing on the IORB.   */
/*    The caller must free any attached SCBs before calling.             */
/*                                                                       */
/*       1.  If the ADD work space status indicates that a call out is   */
/*           needed then it is done.  The return code is ignored here    */
/*           because it is assumed, by the caller, at this point that    */
/*           the request can do no more work.                            */
/*                                                                       */
/*       2.  The done bit in the IORB is set and the call back is done.  */
/*                                                                       */
/*       Entry:  FinishIORB(pIORB, npDCB)                                */
/*                                                                       */
/*               pIORB  - ptr to the IORB                                */
/*               npDCB  - ptr to the DCB of the device                   */
/*                                                                       */
/*       Exit:   returns                                                 */
/*                                                                       */
/*       Note:   This routine can be called at interrupt time.           */
/*                                                                       */
/*               The IORB is considered invalid after the callback is    */
/*               done.                                                   */
/*                                                                       */
/*************************************************************************/

USHORT FinishIORB(PIORB pIORB, NPDCB npDCB)

{

   USHORT rc;

   rc = COMPLETE;  /* assume that we will be done (in case no callout done) */

   /*----------------------------------------------------------------------*/
   /* First free any SCBs that were allocated for non move mode devices.   */
   /*----------------------------------------------------------------------*/

   if (npDCB) {
      if (!(ACBTbl[npDCB->ai].status & MOVEMODE)) {
         if (IORBWRKSP(pIORB,cSCBs)) {
            FreeSCB(IORBWRKSP(pIORB,cSCBs),IORBWRKSP(pIORB,npSCBH),npDCB);
         }
      }
   }

   /*----------------------------------------------------------------------*/
   /* Set the error indicator if there was an error.  The set our int flag */
   /* so that we know it is OK to use the reserved SCB for this device if  */
   /* another stage is required.                                           */
   /*----------------------------------------------------------------------*/

   pIORB->Status |= (pIORB->ErrorCode) ? IORB_ERROR : 0;
   IORBWRKSP(pIORB,Status) |= IORBINTERRUPT;

   /*----------------------------------------------------------------------*/
   /* Now do our local callout if needed.  The call out is done only if    */
   /* there was no error or if the call out on error bit is set, or there  */
   /* is a recovered error.                                                */
   /*----------------------------------------------------------------------*/

   if ( (IORBWRKSP(pIORB,Status) & IORBCALLOUT)              &&
         ( !(pIORB->ErrorCode)                            ||
           (pIORB->Status & IORB_RECOV_ERROR)             ||
           (IORBWRKSP(pIORB,Status) & IORBCALLOUTERR))) {

      rc = (*IORBWRKSP(pIORB,npfnCallOut))(pIORB, npDCB);
   }

   /*----------------------------------------------------------------------*/
   /* If the callout returned COMPLETE (or rc is default value) and the    */
   /* IORB has the async callback bit set then make the call back.         */
   /*----------------------------------------------------------------------*/

   if (rc == COMPLETE) {
      pIORB->Status |= IORB_DONE;

      TRACE(TR_FINISHED, pIORB);
      if (pIORB->RequestControl & IORB_ASYNC_POST) {
         *(pIORB->NotifyAddress)(pIORB);
      }
   }
   return(rc);
}


/*************************************************************************/
/*                                                                       */
/* ValidIORBCommand                                                      */
/*                                                                       */
/*    Determine if the IORB is valid.  This call validates the IORB cmd  */
/*    itself, the state of the device, and the state of the adapter.     */
/*                                                                       */
/*       Entry:  ValidIORBCommand(pIORB)                                 */
/*                                                                       */
/*               pIORB - pointer to the IORB.                            */
/*                                                                       */
/*       Exit:  Returns the DCB ptr if valid, or NULL if not required.   */
/*              If on return the IORB error code is not 0 then there     */
/*              was an error.                                            */
/*                                                                       */
/*************************************************************************/

NPDCB ValidIORBCommand(PIORB pIORB)

{

   USHORT cc;                 /* command code  */
   NPDCB npDCB;

   cc = pIORB->CommandCode;
   npDCB = NULL;

   if (cc <= MAX_IOCC_COMMAND &&
       pIORB->CommandModifier <= IORBCallTbl[cc].maxcm) {

      if (IORBCallTbl[cc].control & NEEDDCB) {
         if ((npDCB = FindDeviceEntry(pIORB->UnitHandle)) != NULL) {

            if (npDCB->UnitInfo.UnitFlags & UF_DEFECTIVE) {
               pIORB->ErrorCode = IOERR_DEVICE_DIAGFAIL;
               return(NULL);
            }

            if (ACBTbl[npDCB->ai].status & ADAPTERDEFECTIVE) {
               pIORB->ErrorCode = IOERR_ADAPTER_DIAGFAIL;
               return(NULL);
            }

            if (ACBTbl[npDCB->ai].status & RESETINPROGRESS ||
                npDCB->status & UNITDEVCTRL) {
               pIORB->ErrorCode = IOERR_DEVICE_BUSY;
               return(NULL);
            }
         }
         else {
            ERRMSG1("IORB unable to find specified DCB for unit %w\n\r",
                   pIORB->UnitHandle);
            pIORB->ErrorCode = IOERR_CMD_SYNTAX;
            return(NULL);
         }
      }

      if (IORBCallTbl[cc].control & NEEDALLOC) {
         if (!(npDCB->status & UNITALLOCATED)) {
            pIORB->ErrorCode = IOERR_UNIT_NOT_ALLOCATED;
            return(NULL);
         }
      }

   }

   else {
      pIORB->ErrorCode = IOERR_CMD_NOT_SUPPORTED;
      return(NULL);
   }

   return(npDCB);

}

/*************************************************************************/
/*                                                                       */
/* CountSGBlocks                                                         */
/*                                                                       */
/*    Determine the number of blocks in the SG list.                     */
/*                                                                       */
/*       Entry:  CountSGBlocks(pSGList, cSGEntries, BlkSize)             */
/*                                                                       */
/*               pSGList - ptr to the SG list                            */
/*               cSGEntries - number of entries                          */
/*               BlkSize - block size                                    */
/*                                                                       */
/*       Exit: USHORT - number of blocks                                 */
/*                                                                       */
/*       Notes: This routine can be called at interrupt time.            */
/*                                                                       */
/*************************************************************************/

USHORT CountSGBlocks(PSCATGATENTRY pSGList, USHORT cSGEntries, USHORT BlkSize)

{

   USHORT i;
   ULONG  ByteCount;

   ByteCount = 0;

   for (i=0; i<cSGEntries; i++) {
      ByteCount += (pSGList+i)->XferBufLen;
   }

   return((USHORT)(ByteCount/BlkSize));

}


/*************************************************************************/
/*                                                                       */
/* InitIORBWorkSpace                                                     */
/*                                                                       */
/*    This routine initializes the IORB ADD work space.                  */
/*                                                                       */
/*       Entry:  InitIORBWorkSpace(pIORB)                                */
/*                                                                       */
/*               pIORB  - ptr to the IORB.                               */
/*                                                                       */
/*       Exit:  good always.                                             */
/*                                                                       */
/*************************************************************************/


VOID InitIORBWorkSpace(PIORB pIORB)

{

   USHORT i;

   for (i=0; i< ADD_WORKSPACE_SIZE; i++) {
      pIORB->ADDWorkSpace[i]=0;
   }
   IORBWRKSP(pIORB,Status) |= IORBLAST;
   IORBWRKSP(pIORB,cRetries) = RetryCount;

}


/*************************************************************************/
/*                                                                       */
/* GetNextWork                                                           */
/*                                                                       */
/*    This routine builds the next unit of work for the device.  It      */
/*    removes IORBs from the waiting queue and goups them together to    */
/*    form a SCB chain for the device.  If an IORB is marked as one that */
/*    cannot be grouped with others then it is returned as a separate    */
/*    piece of work.  See below.                                         */
/*                                                                       */
/*       Entry:  GetNextWork(npDCB)                                      */
/*                                                                       */
/*               npDCB  - ptr to the DCB.                                */
/*                                                                       */
/*       Exit:  pIORB  if work ready.                                    */
/*              NULL   if no work waiting.                               */
/*                                                                       */
/*************************************************************************/

PIORB GetNextWork(NPDCB npDCB)

{

   PIORB pCurIORB,                 /* current IORB we are working on         */
         pHeadIORB,                /* head of entire group/chain being built.*/
         pLastIORB;                /* last IORB in current group/chain       */

   NPSCBH npSCBH;                  /* scb ptr                                */

   USHORT i, j, rc;

   pHeadIORB = pLastIORB = NULL;

   /*-------------------------------------------------*/
   /* Build an IORB/SCB chain of upto size GroupSize. */
   /*-------------------------------------------------*/

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

      if (!(pCurIORB = DeQueueIORB(npDCB))) {
         break;
      }

      if (!pHeadIORB) pHeadIORB = pLastIORB = pCurIORB;

      /*---------------------------------------------------------------*/
      /* First mark the IORB as starting.  Next check the status of    */
      /* the IORB to see if it was setup completely (all resources     */
      /* allocated).  If so then continue.  If not then make the reqd  */
      /* callout (which should be a setup routine).  On retrurn if     */
      /* the return code is NORESOURCE then this IORB is not ready to  */
      /* be started, so requeue it (at the front) and break out of the */
      /* loop now.  If the return code is INCOMPLETE, then the IORB    */
      /* has had some resources allocated to it is OK to start it.  A  */
      /* subsequent interrupt will try again to allocated more re-     */
      /* sources.  For this case it is OK to put the IORB on the chain */
      /* but it must be the last one, so set the loop var to the term- */
      /* inating value.                                                */
      /*---------------------------------------------------------------*/

      IORBWRKSP(pCurIORB,Status) |= IORBSTARTING;   /* set started bit  */

      if (IORBWRKSP(pCurIORB,Status) & IORBINCOMPLT) {
         if (IORBWRKSP(pCurIORB,Status) & IORBCALLOUT) {
            rc = (*IORBWRKSP(pCurIORB,npfnCallOut))(pCurIORB,npDCB);
            if (rc == NORESOURCE) {
               INFMSG1("GetNextWork - callout returned no resource (%p)\n\r",
                       pCurIORB);
               QueueIORB(pCurIORB, npDCB, FRONT);
               break;                               /* break out now  */
            }
            else if (rc == INCOMPLETE) {
               i = GroupSize;
            }
         }
         else {
            ERRMSG1("Incomplete IORB found w/o callout address (%p)\n\r",
                    pCurIORB);
         }
      }

      /*-------------------------------------------------------------------*/
      /* Now see it the IORB can be grouped with others.  If it is OK then */
      /* continue.  If not then see if a chain has already been started.   */
      /* If so then requeue this IORB to the front of the queue and break. */
      /* If not then just break.  (out of the loop)                        */
      /*-------------------------------------------------------------------*/

      if (IORBWRKSP(pCurIORB,Status) & IORBNOGROUP) {
         if (pCurIORB != pHeadIORB) {
            QueueIORB(pCurIORB, npDCB, FRONT);
         }
         break;
      }

      /*-------------------------------------------------------------------*/
      /* If we get here we know that the IORB is ready to be chained into  */
      /* the group.  A chain has been started if the current IORB is != to */
      /* the last IORB.  To put the IORB on the chain, the 1st SCB in the  */
      /* current IORB is linked to the last SCB on the current chain.  If  */
      /* no chain has been started then initialize the chain.              */
      /*-------------------------------------------------------------------*/

      if (pCurIORB != pLastIORB) {

         IORBWRKSP(pLastIORB,Status) &= ~IORBLAST;
         pLastIORB->pNxtIORB = pCurIORB;
         pLastIORB->RequestControl |= IORB_CHAIN;

         npSCBH = IORBWRKSP(pLastIORB,npSCBH);
         for (j=0; j<IORBWRKSP(pLastIORB,cSCBs)-1; j++) {
            npSCBH = npSCBH->scbctrl.npNextSCBH;
         }

         npSCBH->scbctrl.npNextSCBH = IORBWRKSP(pCurIORB,npSCBH);

         npSCBH->scb.ppNxtSCB =
            IORBWRKSP(pCurIORB,npSCBH)->scbctrl.ppSCB;

         npSCBH->scb.Enable |= SCBEfCC;

      }
      pLastIORB = pCurIORB;

      if (IORBWRKSP(pCurIORB,Status) & IORBINCOMPLT) {
         break;
      }
   }

   return(pHeadIORB);     /* return the head pointer  */
}



/*************************************************************************/
/*                                                                       */
/* Trace                                                                 */
/*                                                                       */
/*    This routine inserts the info in the trace buffer.                 */
/*                                                                       */
/*       Entry:  Trace(event, pIORB)                                     */
/*                                                                       */
/*               event - event to put in buffer.                         */
/*                                                                       */
/*************************************************************************/

VOID Trace(USHORT event, PIORB pIORB)

{

   USHORT temp;             /* DO NOT REMOVE !!   */


   SAVEIF();

   TraceIndex = (TraceIndex == MAXTRACE-1) ? 0 : ++TraceIndex;

   (TraceBuf+TraceIndex)->event  = event;
   (TraceBuf+TraceIndex)->unit   = pIORB->UnitHandle;
   (TraceBuf+TraceIndex)->pIORB  = pIORB;
   (TraceBuf+TraceIndex)->cm     = (UCHAR)pIORB->CommandModifier;
   (TraceBuf+TraceIndex)->cc     = (UCHAR)pIORB->CommandCode;
   (TraceBuf+TraceIndex)->status = pIORB->Status;
   (TraceBuf+TraceIndex)->ec     = pIORB->ErrorCode;
   (TraceBuf+TraceIndex)->npSCBH = IORBWRKSP(pIORB,npSCBH);

   RESTOREIF();

}

/*************************************************************************/
/*                                                                       */
/* VirtToPhys                                                            */
/*                                                                       */
/*    This routine converts a virtual address to a physical address.  It */
/*    will work at interrupt time.                                       */
/*                                                                       */
/*       Entry:  VirtToPhys(VirtAddr)                                    */
/*                                                                       */
/*               VirtAddr - virtual address to be converted              */
/*                                                                       */
/*       Exit:  returns ULONG physical address.                          */
/*                                                                       */
/*************************************************************************/

ULONG VirtToPhys (PBYTE VirtAddr)

{

   USHORT rc;
   SCATGATENTRY ScatGatEntry;
   ULONG VirtLinAddr, ScatLinAddr, PageListCount;

   DevHelp_VirtToLin(SELECTOROF(VirtAddr), (ULONG)(OFFSETOF(VirtAddr)),
                     (PLIN)&VirtLinAddr);

   DevHelp_VirtToLin((USHORT)(((ULONG)((PVOID)&ScatGatEntry)) >> 16),
                     (ULONG)((USHORT)((PVOID)&ScatGatEntry)),
                     (PLIN)&ScatLinAddr);

   DevHelp_LinToPageList(VirtLinAddr, 1, ScatLinAddr, (PULONG)&PageListCount);

   return(ScatGatEntry.ppXferBuf);

}


/*************************************************************************/
/*                                                                       */
/* CompleteIORBChain                                                     */
/*                                                                       */
/*    This routine loops through an IORB chain completing the IORBs as   */
/*    needed.  It will stop at the IORB which caused an error if         */
/*    requested.                                                         */
/*                                                                       */
/*       Entry:  CompleteIORBChain(npDCB, pHeadIORB, pLastIORB)          */
/*                                                                       */
/*               npDCB     - DCB for device                              */
/*               pHeadIORB - IORB at the head of the chain               */
/*               pLastIORB - last IORB.  This IORB will NOT be processed.*/
/*                           use NULL for entire chain.                  */
/*               ec        - error code (0 to ignore)                    */
/*                                                                       */
/*       Exit:   pIORB - ptr to an IORB                                  */
/*                                                                       */
/*               if pLastIORB == NULL then                               */
/*                  pIORB = NULL if all IORBs where completed            */
/*                        = IORB that needs to be started (may not have  */
/*                          been setup completely)                       */
/*                                                                       */
/*               if pLastIORB <> NULL                                    */
/*                  pIORB = the error IORB if found                      */
/*                        = NULL if not found (error condition)          */
/*                                                                       */
/*************************************************************************/

PIORB CompleteIORBChain(NPDCB npDCB, PIORB pHeadIORB, PIORB pLastIORB,
                        USHORT ec)

{
   PIORB pCurIORB, pNextIORB;
   BOOL  done;
   USHORT rc;

   done = FALSE;
   pCurIORB = pHeadIORB;

   /*------------------------------------------------------------------*/
   /* Follow the chain of IORBs finishing them until pLastIORB is      */
   /* reached.  An IORB is finished if it is not incomplete or an      */
   /* error code was specified.  If an error code is passed in then    */
   /* the IORB error code is set.  In all cases the IORB pointed to by */
   /* pLastIORB is not finished and is passed back.  A value of NULL   */
   /* for pLastIORB will complete an entire chain.  The recovered      */
   /* error bit is set if the IORB already has an error code and no    */
   /* error code was passed in.                                        */
   /*------------------------------------------------------------------*/

   while (!done && pCurIORB) {

      pNextIORB = pCurIORB->pNxtIORB;

      if (!(IORBWRKSP(pCurIORB,Status) & IORBINCOMPLT) || ec) {

         if (pCurIORB != pLastIORB) {

            if (ec) {
               pCurIORB->ErrorCode = ec;
            }
            else if (pCurIORB->ErrorCode) {
               pCurIORB->Status |= IORB_RECOV_ERROR;
            }

            rc = FinishIORB(pCurIORB, npDCB);                         /*@V95775*/
            if (rc != COMPLETE) {                                     /*@V95775*/
               break;                     /* get out now and go finish */
            }                                                         /*@V95775*/
            else {
               pCurIORB = pNextIORB;
            }
         }
      }
      else {
         IORBWRKSP(pCurIORB,Status) |= IORBINTERRUPT;
         (*IORBWRKSP(pCurIORB,npfnCallOut))(pCurIORB, npDCB);

         #ifdef DEBUG
            if (!(IORBWRKSP(pCurIORB,Status) & IORBLAST)) {
               ERRMSG1("Incomplete IORB not last %p\n\r",pCurIORB);
            }
         #endif
      }

      if (pCurIORB == pLastIORB) {
         done = TRUE;
      }
   }

   return(pCurIORB);

}

/*************************************************************************/
/*                                                                       */
/* StartLocateModeSCB                                                    */
/*                                                                       */
/*    This routine will start the requested SCB.  It calls the assembler */
/*    routine to start it.  Upon return if there is an error then a      */
/*    reset will be done.  This keeps the reset on start code in one     */
/*    place.  Other parts of the code may call the assembler routine     */
/*    directly and do somethin different on an error if needed.  This is */
/*    mainly during intialization where we are not setup to do a reset.  */
/*                                                                       */
/*    If the actual start of the SCB fails, bsr never goes non busy,     */
/*    then we do a reset.                                                */
/*                                                                       */
/*       Entry:  StartLocateModeSCB(IOAddr, ppSCB, ldn, flag)            */
/*                                                                       */
/*               ai     - adapter index                                  */
/*               ppSCB  - phys ptr to the SCB (chain)                    */
/*               cmd    - command to the attention register of the card. */
/*                                                                       */
/*       Exit:   <> 0 if some error, 0 if none, ints disabled if IORB    */
/*               was started.                                            */
/*                                                                       */
/*************************************************************************/

USHORT StartLocateModeSCB(USHORT ai, ULONG ppSCB, USHORT cmd)

{

   USHORT ec = 0;

   if (_StartIO(ACBTbl[ai].baseIO, ppSCB, cmd)) {
      ResetAdapter(ai);
      ec = ERROR;
   }

   return(ec);

}
