/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1992 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 source code is provided to you solely for       */
/*    the purpose of assisting you in your development of OS/2 device        */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Developer Connection Device Driver       */
/*    Source Kit for OS/2. This Copyright statement may not be removed.      */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = 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, rc;      /* ABM0496 */

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

   rc=0;		     /* ABM0496 */
   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();
		     rc=ResetAdapter(i);     /* ABM0496 */
		     break;
		  }
	       }
	    }

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

	    else {
	       AdapterResetFailed(i);
	    }
	    if (rc) {			   /* ABM0496 */
	       AdapterResetFailed(i);	   /* ABM0496 */
	    }
	 }
      }
   }

   _asm{popa}

   STI();
}


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

USHORT ResetAdapter(USHORT ai)		   /* ABM0496 */

{

   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  = 0xff;	   /* ABM0496 */

   /*----------------------------------------------------------------------*/
   /* 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);
   return(ResetToggle(ACBTbl[ai].baseIO));    /* ABM0496 */

}

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

   /* ABM0496 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

   /*----------------------------------------------------------------*/
   /* Do clear assignments command before doing real assignments     */
   /*----------------------------------------------------------------*/

   if (ACBTbl[ai].resetcount == 0xff) {
      npDCB = ACBTbl[ai].np1stDCB;
      ACBTbl[ai].resetcount++;

      assigncmd = SCB_ASSIGN + (0x00800000);

      /*---------------------------------------------*/
      /* Issue the clear assignments command.	     */
      /* 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;
   }

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

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

      if (npDCB->UnitInfo.UnitSCSITargetID > 7) {

	 assigncmd &= 0xffff7fff;
	 assigncmd |= 0x08000000;
      }

   /* ABM0496 ---------------------------------------------------------*/

      /*---------------------------------------------*/
      /* 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; 	 /* ABM0496 */

   /*-----------------------------------------------------------------*/
   /* 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++) {
      done = FALSE;			     /* ABM0496 */
      npDCB->Timeout = DFLT_TO;
      npDCB->state = IDLE;
      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*/
	    pIORB = pNextIORB; /* ABM0496 */
	 }
      }
      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 &= ~(RESETINPROGRESS+RESETCOMPLETE); /* ABM0496 */
   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++;
   }

}
