/*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 = SCSISUB2.C
 *
 * DESCRIPTIVE NAME = IBM2SCSI.ADD - Adapter Driver for IBM SCSI adapters.
 *                    General routines, file 2.
 *
 *
 * 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"


/*************************************************************************/
/*                                                                       */
/* InitMoveMode - Initialize move mode for the adapter.                  */
/*                                                                       */
/*    This routine sets up the pipes for the adapter and attempts to     */
/*    initialize move mode.  If it passes then set mode pages will be    */
/*    sent to certain devices to set there QErr bit to a state we want.  */
/*                                                                       */
/*       Entry:  InitMoveMode(ai)                                        */
/*                                                                       */
/*               ai - adapter index.                                     */
/*                                                                       */
/*       Exit:   0 if no error, non-zero if unable to initialize move    */
/*               mode.                                                   */
/*                                                                       */
/*************************************************************************/

USHORT InitMoveMode(USHORT ai)

{

   CFGPIPES   cfgEl;
   PIPEDATA   PipeData;
   NPBYTE     npPipeMem;
   ULONG      pInPipe, pOutPipe, ppPipeMem, ppcfgEl;
   NPCTRLAREA npAdpCtrl, npSysCtrl;
   USHORT     delta, i;
   NPDCB      npDCB;
   USHORT     DelivLvl;

   INFMSG1("Configuring move mode for adapter index %w\n\r",ai);

   /*-----------------------------------------------------------------*/
   /* Attach to the delivery ADD if not already done.  If the attach  */
   /* works call the delivery ADD to register ourselves and get the   */
   /* call table.  Also mark the ADD status such that we have         */
   /* attached to the delivery driver.  If the attach fails then      */
   /* corvette adapters will operate in locate mode.                  */
   /*-----------------------------------------------------------------*/

   if (!(ADDStatus & DELIV_ATTACHED)) {
      if (!DevHelp_AttachDD(&DelivName, (NPBYTE)&DDTbl)) {
         if (!DDTbl.DeliveryADD((PBYTE)&(SCSIADDHeader.DevName),
                                1, (PUSHORT)&DelivLvl,
                                (PUSHORT)&DeliveryHandle,
                                (PDELIVERYTABLE)&DelivTbl)){
            ADDStatus |= DELIV_ATTACHED;
         }
         else {
            ERRMSG("Unable to get a handle to the delivery ADD.\n\r");
            return(ERROR);
         }
      }
      else {
         ERRMSG("Unable to attach to the delivery ADD.\n\r");
         return(ERROR);
      }
   }

   /*--------------------------------------------------------------*/
   /* Call the delivery ADD to allocate a unit number for the      */
   /* adapter if one not already allocated.                        */
   /*--------------------------------------------------------------*/

   if (!(ACBTbl[ai].status & UNITNUMALLOC)) {
      if (DelivTbl.AllocateUnit(DeliveryHandle,(PBYTE)CorvetteDesc,
                                (PHOSTRTNTBL)&HostRtnTbl,
                                1, (ULONG) ai, (PUSHORT)&ACBTbl[ai].unit)) {
         ERRMSG1("Error allocating unit number for adatper ai=%w\n\r",ai);
         return(ERROR);
      }
      ACBTbl[ai].status |= UNITNUMALLOC;
   }

   /*-----------------------------------------------------------------*/
   /* If the pipes have not already been registered with the delivery */
   /* service then allocate the space from the buffer reserved        */
   /* previously.  If the pipes were already registered then just     */
   /* initialize them again with the delivery service.                */
   /*-----------------------------------------------------------------*/

   // align pipe areas on 4 byte boundary (sufficient space was allocated)

   npPipeMem = (NPBYTE)ACBTbl[ai].np1stDCB + (ACBTbl[ai].cDev * sizeof(DCB));
   if (delta = ((USHORT)npPipeMem & 0x000f)) {     /* non 4-byte aligned  */
      npPipeMem += 4 - delta;
   }

   INFMSG1("   Start of Pipe Memory %w\n\r",npPipeMem);

   npAdpCtrl = (NPCTRLAREA)npPipeMem;
   npSysCtrl = npAdpCtrl + 1;

   if (!(ACBTbl[ai].status & PIPEREG)) {

      /*---------------------------------------------------------------*/
      /* Initialize the pipe data structure.  The pipes are built in   */
      /* the memory immediately following the DCBs for this adapter.   */
      /* Once initialized call the delivery ADD to register the pipes  */
      /* and get a pipe handle.  If the register passed then it is OK  */
      /* to continue.  If the register failed then don't enable move   */
      /* mode.                                                         */
      /*---------------------------------------------------------------*/

      PipeData.pOutPipe = (PBYTE)npPipeMem + 2*sizeof(CTRLAREA);
      PipeData.pInPipe  = (PBYTE)npPipeMem + 2*sizeof(CTRLAREA) + PipeSize;

      PipeData.pOutSDS  = (PUSHORT)&npSysCtrl->sds;
      PipeData.pOutSSE  = (PUSHORT)&npSysCtrl->sse;
      PipeData.pOutSES  = (PUSHORT)&npAdpCtrl->ses;
      PipeData.pOutSSF  = (PUSHORT)&npAdpCtrl->ssf;
      PipeData.pAdpSCA  = (PVOID)&npAdpCtrl->signal;

      PipeData.cSizeOutPipe = PipeSize;
      PipeData.AdpCfgOpt    = CO_PP_SYSMEM + CO_UT_PHYSADP + CO_SIG_MT2NMT +
                              CO_SIG_FULL2NFULL + CO_SIG_NFULL2FULL;

      PipeData.pInSDS   = (PUSHORT)&npAdpCtrl->sds;
      PipeData.pInSSE   = (PUSHORT)&npAdpCtrl->sse;
      PipeData.pInSES   = (PUSHORT)&npSysCtrl->ses;
      PipeData.pInSSF   = (PUSHORT)&npSysCtrl->ssf;
      PipeData.pSysSCA  = (PVOID)&npSysCtrl->signal;

      PipeData.cSizeInPipe  = PipeSize;
      PipeData.SysCfgOpt    = CO_PP_SYSMEM + CO_UT_SYSTEM  + CO_SIG_MT2NMT +
                              CO_SIG_FULL2NFULL + CO_SIG_NFULL2FULL;

      PipeData.InitSysSig   = 0;
      PipeData.InitAdpSig   = 0;

      ACBTbl[ai].ph = -1;
      if (DelivTbl.RegisterPipe(ACBTbl[ai].unit,(PPIPEDATA)&PipeData,
                                (PULONG)&ACBTbl[ai].ph)) {
         ERRMSG1("Error registering pipes for adatper ai=%w\n\r",ai);
         return(ERROR);
      }
      ACBTbl[ai].status |= PIPEREG;

   }
   else {
      DelivTbl.InitPipe(ACBTbl[ai].ph, 0L, 0L);       /* re-init the pipe  */
   }

/*
** We now have a pipe handle for use with the delivery service.  Now
** send a management request to the adapter to initialize move mode.
** This is always done even if move mode was previously configured on the
** adapter.  Why, because if previously configured, then the pipes have been
** destroyed for some reason.
*/

   cfgEl.length       = sizeof(CFGPIPES);
   cfgEl.type         = 0;
   cfgEl.rsvd1        = 0;
   cfgEl.ci           = FN_MGMNT;
   cfgEl.rsvd2        = 0;
   cfgEl.AdpUID1      = (UCHAR)ACBTbl[ai].unit;    /* tell him he's this unit */
   cfgEl.srcid        = SYSTEM_UNIT;
   cfgEl.corrid       = (ULONG)&cfgEl;
   cfgEl.id           = 0;
   cfgEl.function     = MGMT_CFGPIPES;
   cfgEl.AdpUID2      = (UCHAR)ACBTbl[ai].unit;
   cfgEl.PeerUID      = SYSTEM_UNIT;
   cfgEl.CfgStatus    = 0xffff;
   cfgEl.ppPeerSigAdr = ppData + (USHORT)&npSysCtrl->signal;
   cfgEl.ppAdpSigAdr  = ppData + (USHORT)&npAdpCtrl->signal;
   cfgEl.AdpBaseIO    = ACBTbl[ai].baseIO;
   cfgEl.PeerBaseIO   = 0;                   /* we are the system  */
   cfgEl.TimerFreq    = 0;
   cfgEl.TimeUnit     = 0;
   cfgEl.SysMID       = 0;
   cfgEl.AdpCfgOpt    = CO_PP_SYSMEM + CO_UT_PHYSADP + CO_SIG_MT2NMT +
                        CO_SIG_FULL2NFULL + CO_SIG_NFULL2FULL;
   cfgEl.PeerCfgOpt   = CO_PP_SYSMEM + CO_UT_SYSTEM  + CO_SIG_MT2NMT +
                        CO_SIG_FULL2NFULL + CO_SIG_NFULL2FULL;
   cfgEl.OutPipeSize  = PipeSize;
   cfgEl.InPipeSize   = PipeSize;
   cfgEl.ppInPipe     = ppData + (USHORT)npAdpCtrl + 2*sizeof(CTRLAREA) + PipeSize;
   cfgEl.ppInSDS      = ppData + (USHORT)&npAdpCtrl->sds;
   cfgEl.ppInSSE      = ppData + (USHORT)&npAdpCtrl->sse;
   cfgEl.ppInSES      = ppData + (USHORT)&npSysCtrl->ses;
   cfgEl.ppInSSF      = ppData + (USHORT)&npSysCtrl->ssf;
   cfgEl.ppOutPipe    = ppData + (USHORT)npAdpCtrl + 2*sizeof(CTRLAREA);
   cfgEl.ppOutSDS     = ppData + (USHORT)&npSysCtrl->sds;
   cfgEl.ppOutSSE     = ppData + (USHORT)&npSysCtrl->sse;
   cfgEl.ppOutSES     = ppData + (USHORT)&npAdpCtrl->ses;
   cfgEl.ppOutSSF     = ppData + (USHORT)&npAdpCtrl->ssf;

   ppcfgEl = VirtToPhys((PBYTE)&cfgEl);

   INFMSG1("   Move mode config el at %p\n\r",(PBYTE)&cfgEl);

   CLI();
   if (_StartIO(ACBTbl[ai].baseIO, ppcfgEl, CMD_MGMTREQ + SYSTEM_UNIT)) {
      ERRMSG("InitMoveMode: Error configuring movemode.\n\r");
      return(ERROR);
   }

   ACBTbl[ai].status |= OPINPROGRESS;               /* operation in progess  */
   STI();                                           /* enable interrupts     */
   while (ACBTbl[ai].status & OPINPROGRESS) { }     /* wait until done       */

   /*---------------------------------------------------------*/
   /* Now look and see if the request passed.  If it did then */
   /* set the EOI on read bit in the BCR.                     */
   /*---------------------------------------------------------*/

   if ((ACBTbl[ai].bsr & (BSR_BUSY+BSR_EXCP))) {
      ERRMSG1("Error configuring move mode bsr=%w\n\r",ACBTbl[ai].bsr);
      return(ERROR);
   }

   /*-----------------------------------------------------*/
   /* Set clear on read for no EOI required in move mode. */
   /*-----------------------------------------------------*/

   WriteReg(ACBTbl[ai].baseIO+REG_BCR,
            (ReadReg(ACBTbl[ai].baseIO+REG_BCR) | BCR_CLRONRD));

   ACBTbl[ai].status |= MOVEMODE;

   INFMSG("   Move mode configured\n\r\n\r");

   /*-----------------------------------------------------------------*/
   /* Move mode has been successfully configured for the adapter.     */
   /* Now we need to do allocate entity IDs for each attached device. */
   /* This is skipped if entity IDs have already been allocated.      */
   /*-----------------------------------------------------------------*/

   npDCB = ACBTbl[ai].np1stDCB;
   for (i=0; i<ACBTbl[ai].cDev; i++) {
      npDCB->npCallTbl = IORBMMCallTbl;
      AllocEntityID(npDCB);
      npDCB++;
   }

   return(0);

}


/************************************************************************/
/*                                                                      */
/* AllocEntityID                                                        */
/*                                                                      */
/*    Allocate an entity ID for the specified device.                   */
/*                                                                      */
/*    Entry:  AllocEntityID(pIORB, npDCB)                               */
/*                                                                      */
/*            pIORB - ptr to the IORB                                   */
/*            npDCB - ptr to the DCB                                    */
/*                                                                      */
/*    Exit:   none.                                                     */
/*                                                                      */
/************************************************************************/

VOID AllocEntityID(NPDCB npDCB)

{

   USHORT     eid;         /* allocated entity ID  */
   ME_ASGNEID me_asgneid;
   IORBH      allocIORB;
   PIORB      pIORB;

   pIORB = (PIORB)&allocIORB;    /* use our IORB for a place holder  */
   InitIORBWorkSpace(pIORB);

   /*-------------------------------------------------------------*/
   /* Allocate an entity ID for the device if not already done.   */
   /* It will have been done previously if we are resetting the   */
   /* adapter.                                                    */
   /*-------------------------------------------------------------*/

   if (npDCB->EntityID) {        /* Entity ID already allocated?  */
      eid = npDCB->EntityID;
   }
   else {
      DelivTbl.AllocateEntity(ACBTbl[npDCB->ai].unit, (PUSHORT)&eid);
   }

   /*-------------------------------------------------------------*/
   /* Build the assign entity ID control element and enqueue it.  */
   /*-------------------------------------------------------------*/

   me_asgneid.length   = sizeof(ME_ASGNEID);
   me_asgneid.type     = 0;
   me_asgneid.rsvd1    = 0;
   me_asgneid.ci       = FN_MGMNT;
   me_asgneid.dstid    = ACBTbl[npDCB->ai].unit << 8;
   me_asgneid.srcid    = SYSTEM_UNIT;
   me_asgneid.corrid   = (ULONG)pIORB;
   me_asgneid.id       = 0;
   me_asgneid.function = MGMT_ASSIGNENTITY;
   me_asgneid.eid      = (UCHAR)eid;
   me_asgneid.rsvd2    = 0;
   me_asgneid.tid_lun  = (npDCB->UnitInfo.UnitSCSITargetID << 4) +
                         npDCB->UnitInfo.UnitSCSILUN;
   me_asgneid.bus      = npDCB->bus << 4;
   me_asgneid.option   = (npDCB->status & UNITCMDQ) ? 0 : OPTION_NOQMSG;
   me_asgneid.option  |= (npDCB->status & UNITWIDE) ? 0 : OPTION_NOWIDE;

   pIORB->RequestControl = 0;
   pIORB->pNxtIORB       = NULL;

   IORBWRKSP2(pIORB, npfnCallOut)  = AllocEntityIDStage;
   IORBWRKSP2(pIORB, Status)       = IORBCALLOUTERR + IORBCALLOUT;

   StartCtrlEl((PREQH)&me_asgneid, npDCB);

   while (!(IORBWRKSP2(pIORB, Status) & IORBINTERRUPT)) { }   /* wait for int */

   npDCB->EntityID = (UCHAR)eid;

}


/************************************************************************/
/*                                                                      */
/* AllocEntityIDStage                                                   */
/*                                                                      */
/*    Stage routine for AllocEntityID.  This routine returns an in-     */
/*    complete status so that the interrupt routine will not finish     */
/*    the IORB marking it done, and calling back the post routine.      */
/*                                                                      */
/*    Entry:  AllocEntityID(npDCB, pIORB)                               */
/*                                                                      */
/*            npDCB - ptr to the DCB                                    */
/*            pIORB - ptr to the IORB                                   */
/*                                                                      */
/*    Exit:   none.                                                     */
/*                                                                      */
/************************************************************************/

USHORT AllocEntityIDStage(PIORB pIORB, NPDCB npDCB)

{
   return(INCOMPLETE);  /* don't let any one else finish this IORB  */
}



/************************************************************************/
/*                                                                      */
/* InitAutoReqSense                                                     */
/*                                                                      */
/*    Initialize the control element header to enable to auto request   */
/*    sense feature to transfer the sense data to the user buffer       */
/*    directly.                                                         */
/*                                                                      */
/*    Entry:  InitAutoReqSense                                          */
/*                                                                      */
/*    Exit:                                                             */
/*                                                                      */
/************************************************************************/

VOID InitAutoReqSense(PIORB pIORB, PSTDHEAD pStdHead, NPDCB npDCB)

{

   PSCSI_STATUS_BLOCK  pStatusBlock;

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

   pStdHead->ReqH.optctrl         |= OP_ARSENSE;

   if (pStatusBlock->ReqSenseLen >= sizeof(SCSI_REQSENSE_DATA)) {

      pStdHead->arsense.ppSenseData = VirtToPhys((PBYTE)(pStatusBlock->SenseData));
      pStdHead->arsense.cSenseBytes = pStatusBlock->ReqSenseLen;
      IORBWRKSP(pIORB,Status)      |= IORBUSERSENSBUF;
   }
   else {
      pStdHead->arsense.ppSenseData = ppData + (USHORT)&(npDCB->SenseData);
      pStdHead->arsense.cSenseBytes = sizeof(SCSI_REQSENSE_DATA);
   }

}


/************************************************************************/
/*                                                                      */
/* StartCtrlEl                                                          */
/*                                                                      */
/*    Start the control element on the specified device.  The control   */
/*    element is enqueued, by calling the delivery service, on the      */
/*    pipe for the adapter which controls the device.  The IORB is also */
/*    added to the active work queue.                                   */
/*                                                                      */
/*    Entry:  StartCtrlEl                                               */
/*                                                                      */
/*    Exit:                                                             */
/*                                                                      */
/************************************************************************/

VOID StartCtrlEl(PREQH pReqH, NPDCB npDCB)

{

   PIORB pCurIORB, pIORB;
   ULONG toval = DFLT_TO;      /* timeout value  */

   /*------------------------------------------------------------------*/
   /* Put the IORB at the end of the active work queue for the device. */
   /*------------------------------------------------------------------*/

   SAVEIF();  /* turn off ints so nobody messes with the queue  */

   pIORB    = (PIORB)(pReqH->corrid);
   IORBWRKSP2(pIORB, npDCB) = npDCB;
   IORBWRKSP2(pIORB, Status) |= IORBSTARTING;

   pCurIORB = npDCB->pActiveIORB;
   pIORB->RequestControl &= ~IORB_CHAIN;
   pIORB->pNxtIORB = NULL;
   while(pCurIORB && (pCurIORB->RequestControl & IORB_CHAIN))
      pCurIORB = pCurIORB->pNxtIORB;

   if (pCurIORB) {
      pCurIORB->pNxtIORB = pIORB;
      pCurIORB->RequestControl |= IORB_CHAIN;
   }
   else {
      npDCB->pActiveIORB = pIORB;
   }

   /*---------------------------------------------------------------*/
   /* Set the timeout values in the control element if appropriate. */
   /*---------------------------------------------------------------*/

   if ((pReqH->ci & FN_FUNCTION) != FN_MGMNT) {
      if (pIORB->Timeout == 0) {
       pReqH->timeout = DFLT_TO;
      }
      else {

         if (pIORB->Timeout < 0x10000L) {
            pReqH->timeout = pIORB->Timeout;
         }
         else {
            pReqH->timeout = pIORB->Timeout / 60;
            pReqH->optctrl |= TO_MINUTES;
         }
         toval = pIORB->Timeout + 30;
      }
   }

   if (IORBWRKSP2(pIORB,Status) & IORBRESTARTQ) {  /* set restart q bit */
      pReqH->optctrl |= OP_RQ;
   }

   /*-------------------------------------------------------------------*/
   /* Call the delivery service enqueue routine to enqueue the element. */
   /* If the call fails then the pipe is full and the element is added  */
   /* to the waiting queue for the device.  The adapter will signal us  */
   /* when the pipe goes to the not full state.  At that point we will  */
   /* send any waiting elements to the adapter for any device that had  */
   /* a pipe full condition returned.                                   */
   /*-------------------------------------------------------------------*/

   TRACE(TR_STARTED, pIORB);            /* insert a trace record  */

   if (DelivTbl.Enqueue(ACBTbl[npDCB->ai].ph, pReqH, 1)) {
      INFMSG1("Pipe full for adapter %w\n\r",npDCB->ai);
   }
   else {
      LED_ON(npDCB);
      STARTTIMEOUT(npDCB->ai, toval);
   }

   RESTOREIF();
}

/************************************************************************/
/*                                                                      */
/* AbortDevice                                                          */
/*                                                                      */
/*    This routine is called to abort I/O on the specified device.  It  */
/*    will end all waiting and active I/O.  The caller must specify if  */
/*    an immediate abort should be sent for locate mode devices.        */
/*                                                                      */
/*    Entry:  AbortDevice(npDCB, pIORB, flag)                           */
/*                                                                      */
/*            npDCB - ptr to the DCB of device to abort                 */
/*            pIORB - ptr to the IORB (may be NULL for locate mode)     */
/*            ec    - error code to use (if <> 0)                       */
/*            flag  - 0 for no immediate abort, 1 - for immediate abort */
/*                                                                      */
/*    Exit:   0 if no error, <>0 otherwise.                             */
/*                                                                      */
/*    Note:   Caller is responsible for setting the IORB up properly.   */
/*                                                                      */
/************************************************************************/


USHORT AbortDevice (NPDCB npDCB, PIORB pIORB, USHORT ec, USHORT flag)

{

   CE_ABORT ce_abort;
   PIORB    pTempIORB;
   USHORT   rc = 0;

   npDCB->status |= UNITDEVCTRL;         /* device control active  */

   /*--------------------------------------------------------*/
   /* End all I/O that may be in the work queues.            */
   /*--------------------------------------------------------*/

   if (pTempIORB = npDCB->pWorkQ) {   /* assignment intentional  */
      npDCB->pWorkQ = NULL;
      CompleteIORBChain(npDCB, pTempIORB, NULL, ec);
   }

   /*---------------------------------------------------------------*/
   /* If the device is a locate mode device and the flag indicates  */
   /* that an immediate abort command should be sent then send one. */
   /* The interrupt handler will end the active IORBs.  If the flag */
   /* is not set then we assume that the active I/O chain has       */
   /* completed, probably with an error, and this chain is ended    */
   /* the input errorcode.                                          */
   /* If I/O is started to do the abort then the pointer to the     */
   /* abort IORB is saved in the special device control IORB save   */
   /* area in the DCB.  The int handler knows this.                 */
   /*---------------------------------------------------------------*/

   if (!(ACBTbl[npDCB->ai].status & MOVEMODE)) {
      SAVEIF();
      if (flag) {
         if (!_StartIO(ACBTbl[npDCB->ai].baseIO, SCB_ABORT,
                       CMD_IMMEDIATE+npDCB->ldn)) {
            npDCB->state = WAITONABORT;
            npDCB->pDevCtrlIORB = pIORB;
            STARTTIMEOUT(npDCB->ai, DFLT_TO);
         }
         else
            rc = ERROR;
      }
      else {
         npDCB->status &= ~UNITDEVCTRL;         /* no int coming, reset bit  */
         pTempIORB = npDCB->pActiveIORB;                            /*V@90971*/
         npDCB->pActiveIORB = NULL;                                 /*V@90971*/
         CompleteIORBChain(npDCB, pTempIORB, NULL, ec);             /*V@90971*/
      }
      RESTOREIF();
   }

   /*------------------------------------------------------------------*/
   /* The adapter is operating in move mode.  Enqueue an abort control */
   /* element.  Suppress the reply from the abort element, however     */
   /* allow the replies for the aborted elements.  The IORBs assoc-    */
   /* iated with each aborted element will be ended when received.     */
   /* When all are done a reply for the abort will come back.  The     */
   /* call out will be made to the abort done routine which will fin-  */
   /* the IORB that this abort was done under.                         */
   /*------------------------------------------------------------------*/

   else {

      IORBWRKSP2(pIORB,npfnCallOut) = MMDevCtrlDone;
      IORBWRKSP2(pIORB,Status)     |= IORBCALLOUT+IORBCALLOUTERR;
      IORBWRKSP2(pIORB,npDCB)       = npDCB;

      ce_abort.ReqH.length   = sizeof(CE_ABORT);
      ce_abort.ReqH.type     = 0;
      ce_abort.ReqH.rsvd1    = 0;
      ce_abort.ReqH.ci       = FN_ABORTSCSI + EXPEDITE;
      ce_abort.ReqH.dstid    = (ACBTbl[npDCB->ai].unit << 8) + npDCB->EntityID;
      ce_abort.ReqH.srcid    = SYSTEM_UNIT;
      ce_abort.ReqH.corrid   = (ULONG)pIORB;
      ce_abort.ReqH.optctrl  = 0;
      StartCtrlEl((PREQH)&ce_abort, npDCB);
      STARTTIMEOUT(npDCB->ai, DFLT_TO+20);
   }

   return(rc);

}

/************************************************************************/
/*                                                                      */
/* ResetDevice                                                          */
/*                                                                      */
/*    Reset the specified device.  This routine will send either an     */
/*    immediate command for locate mode devices or a reset control      */
/*    element for move mode devices to the specified device.            */
/*                                                                      */
/*    Entry:  ResetDevice(npDCB, pIORB)                                 */
/*                                                                      */
/*            npDCB - ptr to the DCB of device to reset                 */
/*            pIORB - ptr to the IORB                                   */
/*                                                                      */
/*    Exit:   NONE                                                      */
/*                                                                      */
/************************************************************************/

VOID ResetDevice (NPDCB npDCB, PIORB pIORB)

{

   CE_ABORT ce_abort;
   PIORB    pTempIORB;
   USHORT   TargetID, i;
   NPDCB    npDCBList;

   SAVEIF();                             /* no ints while we are doing this */

   /*--------------------------------------------------------*/
   /* End all I/O that may be in the work queues.  This must */
   /* be done for all LUNs on the affected target ID.  For   */
   /* each device mark it as device control operation active.*/
   /* Once this is done the reset only needs to be sent to   */
   /* the specified device.  This will reset all LUNs on the */
   /* same Target ID.                                        */
   /*--------------------------------------------------------*/

   TargetID = npDCB->UnitInfo.UnitSCSITargetID;
   npDCBList = ACBTbl[npDCB->ai].np1stDCB;
   for (i=0; i<ACBTbl[npDCB->ai].cDev; i++) {
      if (npDCBList->UnitInfo.UnitSCSITargetID == TargetID) {
         npDCB->status |= UNITDEVCTRL;
         if (pTempIORB = npDCB->pWorkQ) {     /* assignment intentional  */
            npDCB->pWorkQ = NULL;
            CompleteIORBChain(npDCB, pTempIORB, NULL, IOERR_DEVICE_RESET);
         }
      }
      npDCBList++;
   }

   /*---------------------------------------------------------------*/
   /* If the device is a locate mode device then send an immediate  */
   /* command to the adapter to reset the device.  The IORB for the */
   /* the reset is not added to the active chain.  The abort IORB   */
   /* ptr is saved in the special dev ctrl IORB save area.          */
   /*---------------------------------------------------------------*/

   if (!(ACBTbl[npDCB->ai].status & MOVEMODE)) {
      if (!_StartIO(ACBTbl[npDCB->ai].baseIO, SCB_RESET,
                    CMD_IMMEDIATE+npDCB->ldn)) {
         npDCB->state = WAITONRESET;
         npDCB->pDevCtrlIORB = pIORB;
         STARTTIMEOUT(npDCB->ai, DFLT_TO);
      }
   }

   /*------------------------------------------------------------------*/
   /* The adapter is operating in move mode.  Enqueue an abort element */
   /* with the proper bits set to send a reset message to the device.  */
   /* The reply element will signal the completion of the reset.       */
   /*------------------------------------------------------------------*/

   else {
      IORBWRKSP2(pIORB,npfnCallOut) = MMDevCtrlDone;
      IORBWRKSP2(pIORB,Status)     |= IORBCALLOUT+IORBCALLOUTERR;
      IORBWRKSP2(pIORB,npDCB)       = npDCB;

      ce_abort.ReqH.length   = sizeof(CE_ABORT);
      ce_abort.ReqH.type     = 0;
      ce_abort.ReqH.rsvd1    = 0;
      ce_abort.ReqH.ci       = FN_ABORTSCSI;
      ce_abort.ReqH.dstid    = (ACBTbl[npDCB->ai].unit << 8) + npDCB->EntityID;
      ce_abort.ReqH.srcid    = SYSTEM_UNIT;
      ce_abort.ReqH.corrid   = (ULONG)pIORB;

      ce_abort.ReqH.optctrl  = OP_B;

      StartCtrlEl((PREQH)&ce_abort, npDCB);
   }
   RESTOREIF();                                  /* ints are OK now  */
}

/************************************************************************/
/*                                                                      */
/* MMDevCtrlDone                                                        */
/*                                                                      */
/*    This routine is called when the interrupt for a device control    */
/*    element is received in move mode.                                 */
/*                                                                      */
/*    Entry:  MMDevCtrlDone(npDCB, pIORB)                               */
/*                                                                      */
/*            npDCB - ptr to the DCB of device to reset                 */
/*            pIORB - ptr to the IORB                                   */
/*                                                                      */
/*    Exit:   NONE                                                      */
/*                                                                      */
/************************************************************************/

USHORT MMDevCtrlDone(PIORB pIORB, NPDCB npDCB)

{
   npDCB->status &= ~UNITDEVCTRL;
   return(COMPLETE);
}


/************************************************************************/
/*                                                                      */
/* HoldDevice                                                           */
/*                                                                      */
/*    This routine will take all work for the specified device and move */
/*    it to the devices work queue.  It will then go through all the    */
/*    I/O and free any SCBs that may have been allocated.  Later the    */
/*    entire work queue can be sent to the IORB entry point.            */
/*                                                                      */
/*    Entry:  HoldDevice(npDCB, ai)                                     */
/*                                                                      */
/*            npDCB - ptr to the device control block                   */
/*            ai    - adapter index                                     */
/*                                                                      */
/*    Exit:  0L                                                         */
/*                                                                      */
/************************************************************************/

USHORT HoldDevice(NPDCB npDCB, USHORT ai)
{

   PIORB  pIORB, pNextIORB, pWorkQ, pActiveQ;
   BOOL   done;

   /*----------------------------------------------------*/
   /* First check to see if there is active work.  If so */
   /* then we will move it all to the front of the wait  */
   /* queue.  If not then we don't do anything.  We can  */
   /* do this because in locate mode there will never be */
   /* waiting work if there is no active work, and for   */
   /* move mode there are no resources to free.          */
   /*----------------------------------------------------*/

   if (pIORB = npDCB->pActiveIORB) {   /* assignment intentional  */

      pWorkQ   = npDCB->pWorkQ;
      pActiveQ = npDCB->pActiveIORB;

      npDCB->pWorkQ = pActiveQ;    /* put active work at front of queue */

      pIORB = pActiveQ;
      done = FALSE;

      /*-------------------------------------------*/
      /* Now go through the active IORBs and turn  */
      /* off the LED (decrement count) for all the */
      /* I/O.  It makes sure the LID does not stay */
      /* on.                                       */
      /*-------------------------------------------*/

      while (!done) {
         done = !(pIORB->RequestControl & IORB_CHAIN);
         LED_OFF(npDCB);
         if (!done) pIORB = pIORB->pNxtIORB;
      }

      if (pWorkQ) {
         pIORB->pNxtIORB = pWorkQ;
         pIORB->RequestControl |= IORB_CHAIN;
      }

      /*----------------------------------------------*/
      /* Now go through all the I/O that is now on    */
      /* the wait queue and free all SCBs that may be */
      /* attached (previously allocated) to the IORBs.*/
      /*----------------------------------------------*/

      pIORB = npDCB->pWorkQ;
      if (!(ACBTbl[ai].status & MOVEMODE)) {
         done = FALSE;
         while (!done) {
            done = !(pIORB->RequestControl & IORB_CHAIN);
            if (IORBWRKSP(pIORB,cSCBs)) {
               FreeSCB(IORBWRKSP(pIORB,cSCBs),IORBWRKSP(pIORB,npSCBH),npDCB);
            }
            if (!done) pIORB = pIORB->pNxtIORB;
         }
      }

      npDCB->pActiveIORB = NULL;
   }
}

/************************************************************************/
/*                                                                      */
/* StartUnit                                                            */
/*                                                                      */
/*    This routine will send a start unit command to the device.        */
/*                                                                      */
/*    Entry:  StartUnit(pIORB, npDCB)                                   */
/*                                                                      */
/*    Exit:  0L                                                         */
/*                                                                      */
/************************************************************************/

USHORT StartUnit(PIORB pIORB, NPDCB npDCB)
{

   CE_EXEC_CDB ce_execcdb;
   CE_ABORT    ce_abort;

   SAVEIF();

   /*------------------------------------------------------------*/
   /* For locate mode we must build an passthru CDB to start the */
   /* unit.  The CDB built is a start/stop unit CDB.             */
   /*------------------------------------------------------------*/

   if (!(ACBTbl[npDCB->ai].status & MOVEMODE)) {

      /*-----------------------------------------*/
      /* First put the device in the Hold state. */
      /*-----------------------------------------*/

      HoldDevice(npDCB, npDCB->ai);

      /*-------------------------------------------------------*/
      /* Now the device is in the hold state.  We will use the */
      /* reserved SCB in the DCB to build the start unit SCB.  */
      /*-------------------------------------------------------*/

      npDCB->status |= UNITSCBBUSY;       /* mark DCB SCB as busy  */
      BuildSCB(&(npDCB->ResSCBH.scb), SCB_SENDOTHER, npDCB, 6L, 0L, 0L, 0, 0);
      npDCB->ResSCBH.scb.Enable &= ~SCBEfPT;
      npDCB->ResSCBH.scb.Cmd |= SCBCfND + SCBCfNS;
      npDCB->ResSCBH.scb.EXT.CDB.SCSIcdb[0] = 0x1b;
      npDCB->ResSCBH.scb.EXT.CDB.SCSIcdb[1] = (npDCB->UnitInfo.UnitSCSILUN<<5);
      npDCB->ResSCBH.scb.EXT.CDB.SCSIcdb[2] = 0;
      npDCB->ResSCBH.scb.EXT.CDB.SCSIcdb[3] = 0;
      npDCB->ResSCBH.scb.EXT.CDB.SCSIcdb[4] = 1;
      npDCB->ResSCBH.scb.EXT.CDB.SCSIcdb[5] = 0;

      npDCB->state = WAITONSTARTUNIT;

      StartLocateModeSCB(npDCB->ai, npDCB->ResSCBH.scbctrl.ppSCB,
                         CMD_LONGSCB+(USHORT)npDCB->ldn);
   }

   /*-------------------------------------------------------*/
   /* For move mode format a start unit send other control  */
   /* element and enqueue it.  Set the un-freeze bit to let */
   /* other elements go out.                                */
   /*-------------------------------------------------------*/

   else {

      /*---------------------------------------------------------*/
      /* We know the device is not active, and there may be cmds */
      /* on the adapter, or even lost on the device when it was  */
      /* made not ready, they must be aborted.  To do this send  */
      /* an abort element, but supress reply on the aborted ele- */
      /* ments.  This allows any that may be in the inbound pipe */
      /* to be processed normally.                               */
      /*---------------------------------------------------------*/

      ce_abort.ReqH.length   = sizeof(CE_ABORT);
      ce_abort.ReqH.type     = 0;
      ce_abort.ReqH.rsvd1    = 0;
      ce_abort.ReqH.ci       = FN_ABORTSCSI + EXPEDITE;
      ce_abort.ReqH.dstid    = (ACBTbl[npDCB->ai].unit << 8) + npDCB->EntityID;
      ce_abort.ReqH.srcid    = SYSTEM_UNIT;
      ce_abort.ReqH.corrid   = (ULONG)pIORB;
      ce_abort.ReqH.optctrl  = OP_S + OP_E;
      StartCtrlEl((PREQH)&ce_abort, npDCB);
      LED_OFF(npDCB);                        /* StartCtrlEl turns it on  */

      IORBWRKSP2(pIORB,Status) |= IORBCALLOUT;
      IORBWRKSP2(pIORB,npfnCallOut) = MMStartUnit_AbortDone;

   }

   RESTOREIF();


}

/************************************************************************/
/*                                                                      */
/* MMStartUnit_AbortDone                                                */
/*                                                                      */
/*    This routine is called when the abort for a start unit has been   */
/*    completed.  It shows that there is no I/O on the adapter for the  */
/*    unit.  All elements in the inbound pipe will also have been de-   */
/*    queued and placed on the waiting queue.                           */
/*                                                                      */
/*    Entry:  MMStartUnit_AbortDone(pIORB, npDCB)                       */
/*                                                                      */
/*            pIORB - ptr to the IORB                                   */
/*            npDCB - ptr to the device control block                   */
/*                                                                      */
/*    Exit:  return code from worker routine                            */
/*                                                                      */
/************************************************************************/

USHORT MMStartUnit_AbortDone(PIORB pIORB, NPDCB npDCB)
{
   PIORB       pWorkQ;
   CE_EXEC_CDB ce_execcdb;

   /*-----------------------------------------------------------*/
   /* Before sending the start I/O, we must take any I/O that   */
   /* still on the active queue and put it on the wait queue.   */
   /* Any I/O in this state was probably lost on the device and */
   /* has already been aborted on the adapter when we sent the  */
   /* abort in the StartUnit routine above.  This is done by    */
   /* calling the HoldDevice routine.                           */
   /*-----------------------------------------------------------*/

   HoldDevice(npDCB, npDCB->ai);

   /*-----------------------------------------------------------*/
   /* Format an execute CDB control element that has the start  */
   /* unit command in it.  Then send it.  The queue is still in */
   /* the frozen state so make this an expedited element.       */
   /*-----------------------------------------------------------*/

   INITCTRLEL(ce_execcdb,0,0,npDCB,pIORB);
   INITARSENSE(ce_execcdb,pIORB,npDCB);

   ce_execcdb.ReqH.ci       = FN_SENDSCSI + EXPEDITE;
   ce_execcdb.cBytes        = 0;
   ce_execcdb.ReqH.optctrl |= (6 << 8);  /* cdb length  */
   ce_execcdb.cdb[0] = 0x1b;
   ce_execcdb.cdb[1] = (npDCB->UnitInfo.UnitSCSILUN << 5);
   ce_execcdb.cdb[2] = 0;
   ce_execcdb.cdb[3] = 0;
   ce_execcdb.cdb[4] = 1;
   ce_execcdb.cdb[5] = 0;
   ce_execcdb.cBytes = 0L;

   ce_execcdb.ReqH.length = sizeof(CE_EXEC_CDB) - (17*sizeof(SCATGATENTRY));

   IORBWRKSP2(pIORB,Status) |= IORBCALLOUT;
   IORBWRKSP2(pIORB,npfnCallOut) = MMStartUnit_StartDone;

   StartCtrlEl((PREQH)&ce_execcdb, npDCB);
   STARTTIMEOUT(npDCB->ai, DFLT_TO+20);


}


/************************************************************************/
/*                                                                      */
/* MMStartUnit_StartDone                                                */
/*                                                                      */
/*    This routine is called to restart I/O after the unit has been     */
/*    restarted.                                                        */
/*                                                                      */
/*    Entry:  MMStartUnit_StartDone(pIORB, npDCB)                       */
/*                                                                      */
/*            pIORB - ptr to the IORB                                   */
/*            npDCB - ptr to the device control block                   */
/*                                                                      */
/*    Exit:  return code from worker routine                            */
/*                                                                      */
/************************************************************************/

USHORT MMStartUnit_StartDone(PIORB pIORB, NPDCB npDCB)
{
   USHORT      rc;
   PIORB       pWorkQ;
   CE_REACTQUE ce_reactque;           /* scsi queue reactivate element */

   /*----------------------------------------------------------*/
   /* Send an unfreeze device queue element to free the queue. */
   /*----------------------------------------------------------*/

   ce_reactque.length = sizeof(CE_REACTQUE);
   ce_reactque.type   = 0;
   ce_reactque.rsvd1  = 0;
   ce_reactque.ci     = FN_REACTIVATEQ + SUPPRESS_REPLY + EXPEDITE;
   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);

   /*----------------------------------------------------------*/
   /* Take any waiting work, previously active, off the work Q */
   /*----------------------------------------------------------*/

   pWorkQ = npDCB->pWorkQ;
   npDCB->pWorkQ = NULL;
   npDCB->status &= ~UNITRESTART;

   /*-----------------------------------------------------------*/
   /* Now restart the I/O.  This is done in two steps.  First   */
   /* send the IORB input to this routine back to the IORBEntry */
   /* routine.  Then send the work Q back through the IORBEntry */
   /* routine.  Then return a INCOMPLETE so the caller won't    */
   /* end this IORB.                                            */
   /*-----------------------------------------------------------*/

   IORBEntry(pIORB);

   if (pWorkQ) {
      IORBEntry(pWorkQ);
   }

   return(INCOMPLETE);

}

/************************************************************************/
/*                                                                      */
/* SignalAdapter                                                        */
/*                                                                      */
/*    This is a host routine that is used to signal the adapter when    */
/*    it has been determined by the delivery service that a signal must */
/*    be sent.  This is according to the signalling options that were   */
/*    set when the pipe was created.                                    */
/*                                                                      */
/*    Entry:  SignalAdapter(Unit, key)                                  */
/*                                                                      */
/*            Unit     - Unit number                                    */
/*            key      - key value when unit allocated (low word is     */
/*                       the adapter index)                             */
/*                                                                      */
/*    Exit:  0L                                                         */
/*                                                                      */
/************************************************************************/

ULONG FAR _loadds SignalAdapter (USHORT Unit, ULONG key)

{

   USHORT ai;

   ai = (USHORT)key;           /* key is the adapter index  */
   WriteAttn(ACBTbl[ai].baseIO, CMD_SIGNAL);

   return(0L);

}

/************************************************************************/
/*                                                                      */
/* PipeFull                                                             */
/*                                                                      */
/*    This is a host routine that is called by the enqueue service      */
/*    when it has determined that the pipe is full.  It allows us to    */
/*    queue the IORB before the adapter is signaled that the pipe is    */
/*    full.  It eliminates a timing window that has created problems.   */
/*                                                                      */
/*    Entry:  PipeFull(pEl, key)                                        */
/*                                                                      */
/*            pEl - ptr to the element that could not be enqueued       */
/*            key      - key value when unit allocated (low word is     */
/*                       the adapter index)                             */
/*                                                                      */
/*    Exit:  0L                                                         */
/*                                                                      */
/************************************************************************/

ULONG FAR _loadds PipeFull (ULONG key, PREQH pEl)

{

   NPDCB npDCB;
   PIORB pIORB;

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

   /*---------------------------------------------------------------*/
   /* The IORB is already on the active chain.  Remove it and put   */
   /* it on the work queue.                                         */
   /*---------------------------------------------------------------*/

   RemoveIORBFromActiveChain(pIORB, npDCB);
   QueueIORB(pIORB, npDCB, BACK);                          /* queue it */

   return(0L);

}

