/*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 = SCSITIMR.C
 *
 * DESCRIPTIVE NAME = IBM2SCSI.ADD - Adapter Driver for IBM SCSI adapters.
 *                    Timer handler.
 *
 *
 * VERSION = V2.0
 *
 * DATE
 *
 * DESCRIPTION  Handle timer tick interrupts.  Also handle resetting the
 *              adaptper on an adapter timeout.
 *
*/

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

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


/*************************************************************************/
/*                                                                       */
/* TimerHandler                                                          */
/*                                                                       */
/*    This routine is called on each timer tick.  It determines if a     */
/*    timeout has ocurred.                                               */
/*                                                                       */
/*       Entry:  TimerHandler()                                          */
/*                                                                       */
/*               far call from kernel in interrupt mode.                 */
/*                                                                       */
/*       Exit:   normal always.                                          */
/*                                                                       */
/*************************************************************************/


VOID FAR TimerHandler ()

{

   NPDCB  npDCB;
   USHORT i, ai, j;

   CLI();                    /* no interrupts allowed                       */
   _asm{pusha}               /* DD timer handlers required to save all regs */

   /*------------------------------------------------------------------*/
   /* The timer handler loops through the timer array decrementing all */
   /* non-zero values.  If a value is decremented to 0 then the time-  */
   /* out for that adapter has fired.  If that adapter has an active   */
   /* device on it then the adapter has most likely gone south.  The   */
   /* suspect adapter is reset.                                        */
   /*------------------------------------------------------------------*/

   for (i=0; i<cAdapters; i++) {
      if (*(Timers+i)) {
         if (!--*(Timers+i)) {

            /*-----------------------------------------------------*/
            /* If an adapter reset is not already in progress then */
            /* see if the adapter is active.  If so then reset it. */
            /*-----------------------------------------------------*/

            if (!(ACBTbl[i].status & RESETINPROGRESS)) {

               npDCB = ACBTbl[i].np1stDCB;
               for (j=0; j<ACBTbl[i].cDev; j++, npDCB++) {
                  if (npDCB->pActiveIORB) {
                     INFMSG1("Timeout fired for Adapter %w \n\r",i);
                     DBSTOP();
                     ResetAdapter(i);
                     break;
                  }
               }
            }

            /*--------------------------------------------------*/
            /* Otherwise we have timed out waiting on the reset */
            /* to complete.  The adapter is completely hosed.   */
            /*--------------------------------------------------*/

            else {
               AdapterResetFailed(i);
            }
         }
      }
   }

   _asm{popa}

   STI();
}


/************************************************************************/
/*                                                                      */
/* ResetAdapter                                                         */
/*                                                                      */
/*    Reset the specified adapter.                                      */
/*                                                                      */
/*    Entry:  ResetAdapter(ai)                                          */
/*                                                                      */
/*            ai - adapter index                                        */
/*                                                                      */
/*    Exit:                                                             */
/*                                                                      */
/************************************************************************/

VOID ResetAdapter(USHORT ai)

{

   USHORT i;
   NPDCB  npDCB;
   PIORB  pIORB, pNextIORB, pWorkQ, pActiveQ;
   BOOL   done;

   /*------------------------------------------------------------*/
   /* Indicate that the adapter is resetting, turn off move mode */
   /* and set the reset count to 0.                              */
   /*------------------------------------------------------------*/

   ACBTbl[ai].status     |= RESETINPROGRESS;
   ACBTbl[ai].status     &= ~MOVEMODE;
   ACBTbl[ai].resetcount  = 0;

   /*----------------------------------------------------------------------*/
   /* Now take all active work for all devices on this adapter and move it */
   /* to that device's work queue (waiting I/O).  Later on when the reset  */
   /* completes the work is restarted by sending the entire IORB chain to  */
   /* the IORB entry point.                                                */
   /*----------------------------------------------------------------------*/

   npDCB = ACBTbl[ai].np1stDCB;

   for (i=0; i<ACBTbl[ai].cDev; i++) {
      HoldDevice(npDCB, ai);
      npDCB++;
   }

   /*-------------------------------------------------------------*/
   /* Now the adapter is ready for the reset.  Go toggle the      */
   /* reset bit in the Basic control register to cause the reset. */
   /* Then wait for the interrupt.                                */
   /*-------------------------------------------------------------*/

   STARTTIMEOUT(ai,RESETTIMEOUT);
   ResetToggle(ACBTbl[ai].baseIO);

}

/************************************************************************/
/*                                                                      */
/* AdapterResetComplete                                                 */
/*                                                                      */
/*    An interrupt has been received during an adapter reset. Determine */
/*    what the next step is.  If this is the first int then mark the    */
/*    adapter as having the reset done.  Then continue by checking if   */
/*    the adapter is a move mode adapter.  If so then initialize move   */
/*    mode and restart the I/O (there are no other stages needed to     */
/*    complete the reset).  If the adapter is in locate mode then start */
/*    to assign the LDNs.  Each LDN assignment will take an interrupt.  */
/*    We keep track of where we are by using a count field in the       */
/*    adapter control block.  When the LDNs are all assigned the reset  */
/*    is completed and the I/O is restarted.                            */
/*                                                                      */
/*    Entry:  AdapterResetComplete(ai)                                  */
/*                                                                      */
/*            ai - adapter index                                        */
/*                                                                      */
/*    Exit:                                                             */
/*                                                                      */
/************************************************************************/

VOID AdapterResetComplete(USHORT ai)

{

   ULONG  assigncmd;
   USHORT i;
   PIORB  pIORB;
   NPDCB  npDCB;

   /*---------------------------------------------------------------*/
   /* If the adapter status indicates that the reset is not         */
   /* complete then this is the first interrupt and the BCR reset   */
   /* has been completed.  See if it completed OK.  If not then     */
   /* call the fail routine.  If so then mark it done.  If the      */
   /* adapter status indicates that the reset is already complete   */
   /* then this interrupt is a stage interrupt for locate mode.     */
   /* In this case we will continue the reset sequence below.       */
   /*---------------------------------------------------------------*/

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

      if (ACBTbl[ai].isr != 0x0f) {  /* fail?  */
         AdapterResetFailed(ai);
         return ;
      }
      else {
         ACBTbl[ai].status |= RESETCOMPLETE;
      }
   }

   /*----------------------------------------------------------------*/
   /* Now if the adapter is in move mode go try to initialize it.    */
   /* If move mode failed to start, then we will fall through and    */
   /* use locate mode.                                               */
   /*----------------------------------------------------------------*/

   if (ACBTbl[ai].status & ENABLEMOVEMODE) {
      if (InitMoveMode(ai)) {                     /* fail ?  */
         ACBTbl[ai].status &= ~ENABLEMOVEMODE;
      }
      else {
         ACBTbl[ai].status &= ~(RESETINPROGRESS+RESETCOMPLETE);
         RestartIO(ai);
         return;
      }
   }

   /*---------------------------------------------------------------*/
   /* If we get here we are in locate mode.  We need to assign an   */
   /* LDN for the next device.  The field resetcount in the ACB is  */
   /* the control variable that tells us where we are in the reset  */
   /* sequence.  We must assign each devices LDN on this adapter    */
   /* before the reset is done.  When all are assigned we will do   */
   /* the else protion below.                                       */
   /*---------------------------------------------------------------*/

   if (ACBTbl[ai].resetcount < ACBTbl[ai].cDev) {
      npDCB = ACBTbl[ai].np1stDCB;
      npDCB += ACBTbl[ai].resetcount++;

      assigncmd = SCB_ASSIGN + (npDCB->ldn << 16) +
                  (npDCB->UnitInfo.UnitSCSITargetID << 20) +
                  (npDCB->UnitInfo.UnitSCSILUN << 24);

      /*---------------------------------------------*/
      /* Assign the LDN on the adapter.  The int     */
      /* handler will call us back later.            */
      /*---------------------------------------------*/

      if (_StartIO(ACBTbl[ai].baseIO, assigncmd,ADAPTERDEVICE+CMD_IMMEDIATE)) {
         ERRMSG1("Error assigning LDN after reset, AI = %w.\n\r",ai);
         ACBTbl[ai].status &= ~(RESETINPROGRESS+RESETCOMPLETE);
         ACBTbl[ai].status |= ADAPTERDEFECTIVE;

         npDCB = ACBTbl[ai].np1stDCB;

         for (i=0; i<ACBTbl[ai].cDev; i++, npDCB++) {
            if (npDCB->pWorkQ) {
               pIORB = npDCB->pWorkQ;
               npDCB->pWorkQ = NULL;
               CompleteIORBChain(npDCB, pIORB, NULL, IOERR_ADAPTER_DIAGFAIL);
            }
         }
         return;
      }
      return;
   }

   /*--------------------------------------------------------------*/
   /* All the LDNs have been assigned.  Mark the adapter as having */
   /* completed the reset sequence and restart all the I/O.        */
   /*--------------------------------------------------------------*/

   else {

      ACBTbl[ai].status &= ~(RESETINPROGRESS+RESETCOMPLETE);

      RestartIO(ai);
      return;

   }
}

/************************************************************************/
/*                                                                      */
/* RestartIO                                                            */
/*                                                                      */
/*    This routine will take all the I/O that is attached to the work   */
/*    queue for each device that this adapter owns and send it as a     */
/*    chain to the IORBEntry routine.  This will restart all the I/O    */
/*    that was halted during a reset.  Some fields in the DCB are also  */
/*    reset to starting values.                                         */
/*                                                                      */
/*    Entry:  RestartIO(ai)                                             */
/*                                                                      */
/*            ai - adapter index                                        */
/*                                                                      */
/*    Exit: none                                                        */
/*                                                                      */
/************************************************************************/

VOID RestartIO(USHORT ai)

{

   PIORB  pIORB, pNextIORB;
   USHORT i;
   NPDCB  npDCB;
   BOOL   done = FALSE;

   /*-----------------------------------------------------------------*/
   /* For each device on the adapter init some fields in the DCB.     */
   /* Then send the IORBs that are on the wait queue as a chain to    */
   /* the IORB entry point.                                           */
   /*-----------------------------------------------------------------*/

   npDCB = ACBTbl[ai].np1stDCB;
   for (i=0; i<ACBTbl[ai].cDev; i++) {
      npDCB->Timeout = DFLT_TO;
      npDCB->status &= ~UNITTODISABLED;
      npDCB->status &= ~UNITDEVCTRL;
      if (pIORB = npDCB->pWorkQ) {          /* assigment intentional  */
         npDCB->pWorkQ = NULL;

         /*--------------------------------------------------------*/ /*@81320*/
         /* Go through each IORB and end any that either have      */ /*@81320*/
         /* retries disabled, or that retries have been exhausted. */ /*@81320*/
         /* All others send to the IORB entry point.               */ /*@81320*/
         /*--------------------------------------------------------*/ /*@81320*/

         while (!done) {                                              /*@81320*/

            /*-----------------------------------------------------*/ /*@81320*/
            /* Get the next IORB and unlink this one, or set the   */ /*@81320*/
            /* done variable to end the loop.                      */ /*@81320*/
            /*-----------------------------------------------------*/ /*@81320*/

            if (pIORB->RequestControl & IORB_CHAIN) {                 /*@81320*/
               pNextIORB = pIORB->pNxtIORB;                           /*@81320*/
               pIORB->RequestControl &= ~IORB_CHAIN;                  /*@81320*/
               pIORB->pNxtIORB = NULL;                                /*@81320*/
            }                                                         /*@81320*/
            else {                                                    /*@81320*/
               done = TRUE;                                           /*@81320*/
            }                                                         /*@81320*/

            if (pIORB->RequestControl & IORB_DISABLE_RETRY ||         /*@81320*/
               !IORBWRKSP(pIORB,cRetries)) {                          /*@81320*/
               pIORB->ErrorCode = IOERR_ADAPTER_DEVICE_TIMEOUT;       /*@81320*/
               FinishIORB(pIORB, npDCB);                              /*@81320*/
            }                                                         /*@81320*/
            else {                                                    /*@81320*/
               IORBWRKSP(pIORB, cRetries)--;                          /*@81320*/
               IORBEntry(pIORB);                                      /*@81320*/
            }                                                         /*@81320*/
         }
      }
      npDCB++;
   }
}


/************************************************************************/
/*                                                                      */
/* AdapterResetFailed                                                   */
/*                                                                      */
/*    The adapter reset for the specified adapter failed.  Mark the     */
/*    adapter as defective so that we will not accept any more requests */
/*    for devices attached to it.  Also go through and fail all out-    */
/*    standing requests for devices on the adapter.                     */
/*                                                                      */
/*    Entry:  AdapterResetFail(ai)                                      */
/*                                                                      */
/*            ai - adapter index                                        */
/*                                                                      */
/*    Exit:                                                             */
/*                                                                      */
/************************************************************************/

VOID AdapterResetFailed(USHORT ai)

{

   USHORT i;

   ACBTbl[ai].status |= ADAPTERDEFECTIVE;                 /* adapter is      */

   ERRMSG1("Adapter reset failed for adapter %w\n\r",ai);


   /*----------------------------------------------------------------*/
   /* Now take all I/O that had been active and end it with an error.*/
   /* The adapter will no longer accept any I/O.                     */
   /*----------------------------------------------------------------*/

   npDCB = ACBTbl[ai].np1stDCB;
   for (i=0; i<ACBTbl[ai].cDev; i++) {
      if (npDCB->pWorkQ) {
         CompleteIORBChain(npDCB, npDCB->pWorkQ, NULL,
                           IOERR_ADAPTER_DIAGFAIL);
      }
      npDCB++;
   }

}
