/*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.                                */
/*                                                                           */
/*****************************************************************************/
/*static char *SCCSID = "%w% %e%";*/
/**************************************************************************
 *
 * SOURCE FILE NAME = FLP1ISM.C
 *
 * DESCRIPTIVE NAME = Inner State Machine - Module 1
 *
 *
 *
 * VERSION = V2.1
 *
 * DATE = 94/03/14
 *
 * DESCRIPTION :
 *
 * Purpose:  Low-level diskette controller state processing
 *
 *
 *
 *
 *
*/
#define INCL_NOBASEAPI
#define INCL_NOPMAPI
#include "os2.h"
#include "dos.h"

#include "dskinit.h"

#include "iorb.h"
#include "addcalls.h"
#include "dhcalls.h"

#define INCL_INITRP_ONLY
#include "reqpkt.h"

#include "flp1cons.h"
#include "flp1regs.h"
#include "flp1misc.h"
#include "flp1type.h"
#include "flp1extn.h"
#include "flp1pro.h"
#include "flp1mif.h"


/*------------------------------------------------------------------------*/
/*                                                                        */
/* Inner State Machine (ISM) - Basic State Handling / Routine             */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*-------------------------------------------*/
/*                                           */
/* Outer State Machine Interface             */
/*                                           */
/* This routine sets variables required by   */
/* the inner state machine and sets up       */
/* the outer state machine to go into a      */
/* WAITSTATE until the inner state machine   */
/* finishes the requested operation.         */
/*                                           */
/*-------------------------------------------*/

VOID FAR StartOSMRequest( NPACB npACB, USHORT ReqFlags )
{

  /*-----------------------------------------------*/
  /* Transfer any pending operations for this Unit */
  /* (i.e. RECAL, etc. to the ISM Request Flags    */
  /*-----------------------------------------------*/
  npACB->ReqFlags        = npACB->npUCB->ReqFlags | ReqFlags;
  npACB->ISMRecal        = 0;

  /*---------------------------------*/
  /* Setup to call StartOSM when the */
  /* ISM request completes.          */
  /*---------------------------------*/
  npACB->ISMDoneRtn    = StartOSM;
  npACB->OState        = ACBO_ISM_COMPLETE;
  npACB->Flags        |= ACBF_OSM_WAITSTATE;

  npACB->CurStatus     = REQS_NO_ERROR;

  npACB->State         = ACBS_NEXT_REQUEST;
  StartISM( npACB );
}

/*---------------------------------------------------*/
/*                                                   */
/* Inner State Machine Router                        */
/*                                                   */
/* The inner state machine has four basic states:    */
/*                                                   */
/* 1.) ACBS_NEXT_REQUEST                             */
/*                                                   */
/*     This state scans the ReqFlags mask and calls  */
/*     state handling routines as needed.            */
/*                                                   */
/* 2.) ACBS_INTERRUPT                                */
/*                                                   */
/*     The ISM is waiting on an interrupt or an      */
/*     interrupt timeout. State processing is        */
/*     suspended until the interrupt or timeout      */
/*     occurs. This state calls the IRQRoutine       */
/*     set by the state handling routine to          */
/*     complete the state waiting on the interrupt.  */
/*                                                   */
/*                                                   */
/* 3.) ACBS_TIMER                                    */
/*                                                   */
/*     The ISM is waiting on a timed delay to        */
/*     complete. State processing is suspended       */
/*     until the delay completes. The TmrRoutine     */
/*     set by the state handling routine requesting  */
/*     the delay is called.                          */
/*                                                   */
/*                                                   */
/* 3.) ACBS_COMPLETE                                 */
/*                                                   */
/*     State processing should be exited. The        */
/*     notify routine (*ISMDoneRtn)() set by         */
/*     the OSM or other ISM user is called.          */
/*                                                   */
/* The ISM router will exit any time a state         */
/* sets ACBF_WAITSTATE. When the ISM is called       */
/* again via StartISM, state processing will resume  */
/* with the last state set in the ACB. It is the     */
/* responsibility of the state handling routines     */
/* to set the appropriate state. In general the      */
/* state handling routines can assume that the       */
/* current state is ACBS_NEXT_REQUEST on entry.      */
/*                                                   */
/*                                                   */
/*---------------------------------------------------*/


VOID FAR StartISM( NPACB npACB )
{
  /*-----------------------------------------*/
  /* The UseCount prevents recursive ISM     */
  /* requests. If we are in the ISM and an   */
  /* attempt is made to call StartISM, then  */
  /* the UseCount is incremented and control */
  /* is returned to the recursive caller.    */
  /*                                         */
  /* This is a 'light-duty' type of event    */
  /* handling. In a more complex scenario,   */
  /* a queue of requests would need to be    */
  /* to be kept.                             */
  /*-----------------------------------------*/

  DISABLE
  npACB->UseCount++;

  if ( npACB->UseCount == 1 )
  {
    do
    {
      ENABLE
      do
      {
        npACB->Flags &= ~ACBF_WAITSTATE;

        switch ( npACB->State )
        {
          case ACBS_INTERRUPT:
            InterruptState( npACB );
            break;

          case ACBS_TIMER:
            TimedState( npACB );
            break;

          /*-------------------------------------------*/
          /* RequestFlags is a bit mask representing   */
          /* routines to be called. During each        */
          /* iteration of this loop the mask is        */
          /* rescanned from bits 0->15. This allows    */
          /* a state handling routine to create        */
          /* additional states 'on-the-fly'            */
          /*                                           */
          /* When there are no bits on in the mask     */
          /* NextReqIndex() will set ACBS_COMPLETE.    */
          /* ACBS_COMPLETE may also be set if a        */
          /* state handling routine wants to terminate */
          /* state processing.                         */
          /*-------------------------------------------*/

          case ACBS_NEXT_REQUEST:
            npACB->TimerFlags = 0;

            if ( (npACB->ReqIndex = NextReqIndex(npACB)) != -1 )
            {
              (*ReqRoutine[npACB->ReqIndex])( npACB );
            }
            break;

          case ACBS_COMPLETE:
            /*-----------------------------------------*/
            /* A delay timer may be running if we      */
            /* started a drive motor but did not do    */
            /* a MOTOR_WAIT or aborted a SEEK or RECAL */
            /*-----------------------------------------*/
            DISABLE
            if ( npACB->DelayTimerHandle )
            {
              f_ADD_CancelTimer( npACB->DelayTimerHandle );
              npACB->DelayTimerHandle = 0;
            }
            ENABLE
            npACB->UseCount  = 1;
            npACB->Flags    |= ACBF_WAITSTATE;
            break;
        }
      }
      while ( !(npACB->Flags & ACBF_WAITSTATE) );

      DISABLE
    }
    while ( --npACB->UseCount );
  }
  ENABLE

  if ( npACB->State == ACBS_COMPLETE )
  {
    (*npACB->ISMDoneRtn)( npACB );
  }

}

/*-------------------------------*/
/*                               */
/* Return index of Next State    */
/* from ReqFlags bit mask.       */
/*                               */
/* If there are no bits on then  */
/* set ACBS_COMPLETE and return  */
/* -1.                           */
/*-------------------------------*/

USHORT NEAR NextReqIndex( NPACB npACB )
{
  USHORT        ReqFlags;
  USHORT        i;

  ReqFlags = npACB->ReqFlags;

  /**
   ** Scan the current list of things to do
   **/
  for (i=0; ReqFlags && !(ReqFlags & 0x0001); i++, ReqFlags >>= 1)
  {
    ;
  }

  if ( !ReqFlags )
  {
    npACB->State  = ACBS_COMPLETE;
    i = -1;
  }

  return( i );
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Interrupt/Timer State Handling                                         */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*---------------------------------------*/
/*                                       */
/* Handle Interrupt State                */
/*                                       */
/* The ISM was expecting an interrupt    */
/* and the interrupt has occurred or     */
/* an IRQ timeout has occurred.          */
/*                                       */
/* If the interrupt occurred, then       */
/* read the results from the controller. */
/* The expected number of results bytes  */
/* was set by the routine that initiated */
/* the operation.                        */
/*                                       */
/* Then call the IRQ routine set by the  */
/* routine that started the operation.   */
/*                                       */
/*                                       */
/*---------------------------------------*/

VOID NEAR InterruptState( NPACB npACB )
{
  NPUCB         npUCB;

  VOID (NEAR *IRQRoutine)( NPACB );

  if ( !(npACB->TimerFlags & ACBT_INTERRUPT) )
  {
    if ( npACB->IRQRoutine )
    {
      IRQRoutine        = npACB->IRQRoutine;
      npACB->IRQRoutine = 0;

      (*IRQRoutine)( npACB );
    }
  }

  /*------------------------------------------*/
  /* An interrupt timeout occurred.           */
  /*                                          */
  /* Given the floppy controller's propensity */
  /* to lock-up, set a pending reset in all   */
  /* the UCBs on this controller.             */
  /*                                          */
  /* An IRQ timeout forces the ISM to exit.   */
  /*                                          */
  /*------------------------------------------*/
  else
  {
    npUCB = npACB->npFirstUCB;
    while( npUCB )
    {
      npUCB->ReqFlags |= UCBR_RESET;

      npUCB = npUCB->npNextUCB;
    }

    if ( !npACB->CurStatus )
    {
      npACB->CurStatus = REQS_CTRL_TIMEOUT;
    }

    npACB->npUCB->Flags |= UCBF_MEDIA_UNCERTAIN;

    npACB->State  =  ACBS_COMPLETE;
    npACB->Flags &= ~ACBF_WAITSTATE;
  }
}

/*-------------------------------*/
/*                               */
/* Handle timed delay            */
/*                               */
/* The requester asked for a     */
/* timed delay. The delay has    */
/* expired. The TmrRoutine set   */
/* by the requester is called    */
/* and then state handling is    */
/* restarted.                    */
/*                               */
/*-------------------------------*/

VOID NEAR TimedState( NPACB npACB )
{
  VOID (NEAR *TmrRoutine)( NPACB );

  if ( npACB->TmrRoutine )
  {
    TmrRoutine        = npACB->TmrRoutine;
    npACB->TmrRoutine = 0;

    (*TmrRoutine)( npACB );
  }
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Controller Reset State                                                 */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*-------------------------------*/
/*                               */
/* Start Controller Reset        */
/*                               */
/*                               */
/*-------------------------------*/

VOID NEAR StartReset( NPACB npACB )
{
  NPUCB         npUCB;

  /*-----------------------------------*/
  /* Set pending SELECT/RECAL states   */
  /* for each UCB.                     */
  /*-----------------------------------*/

  if ( npACB->npUCB )
  {
    npACB->ReqFlags |= (ACBR_FORCE_SELECT | ACBR_RECAL);
  }

  npUCB = npACB->npFirstUCB;

  while ( npUCB )
  {
    npUCB->ReqFlags |= (UCBR_FORCE_SELECT | UCBR_RECAL);
    npUCB->ReqFlags &= ~UCBR_RESET;

    npUCB = npUCB->npNextUCB;
  }

  npACB->SeekFlags     &= ~ACBK_ISEEK_ENABLED;
  npACB->CurCyl         = -1;
  npACB->CurResultsLen  =  0;

  npACB->IRQRoutine = EndReset;

  SendReset( npACB );
}

/*-----------------------------------------------*/
/*                                               */
/* Complete RESET Handling                       */
/*                                               */
/* We got an interrupt from the controller. Now  */
/* issue Sense Interrupt Status commands         */
/* to complete the reset processing.             */
/*                                               */
/* The requirement to do this is part of the     */
/* bizzare Nec 765 architecture.                 */
/*                                               */
/*-----------------------------------------------*/

VOID NEAR EndReset( NPACB npACB )
{
  USHORT        Port;
  USHORT        Value;

  USHORT        i;

  USHORT        rc = MAX_DRIVES-1;

  npACB->CurCmdLen     = FC_LEN_SENSE_INT_STATUS;
  npACB->CurResultsLen = FR_LEN_SENSE_INT_STATUS;
  npACB->CurCmd[0]     = FC_SENSE_INT_STATUS;

  Port  = npACB->IOPorts[FCR_DOR];

  for ( i=1; i < MAX_DRIVES; i++ )
  {
    DISABLE
    npACB->IORegs[FCR_DOR] &= ~FCR_DOR_DRIVE_SEL;
    npACB->IORegs[FCR_DOR] |= i;

    Value = npACB->IORegs[FCR_DOR];
    outp( Port, Value );

    ENABLE

    if( !SendCommand( npACB, 0 ) )
    {
      if ( !ReadResults( npACB ) )
      {
        rc--;
      }
    }
  }

  npACB->ReqFlags &= ~ACBR_RESET;
  npACB->State     = ACBS_NEXT_REQUEST;

  if ( !rc ) npACB->CurStatus = REQS_NO_ERROR;

}

/*---------------------------------------------------*/
/*                                                   */
/* Start Reset                                       */
/*                                                   */
/* This routine issues a controller reset and sets   */
/* an interrupt timeout timer.                       */
/*                                                   */
/*---------------------------------------------------*/

VOID NEAR SendReset( NPACB npACB )
{

  USHORT        Port;
  USHORT        Value;
  USHORT        i;
  USHORT        tempval;                                   /*V88951*/
  USHORT        tempPort;                                  /*V88951*/

  Port  = npACB->IOPorts[FCR_DOR];

  npACB->CurResultsLen = 0;
  npACB->CurStatus     = REQS_RESET_FAIL;

  DISABLE

  /*---------------------------------------------------------------*/
  /* Some NCR laptops use a power management scheme that puts the  */
  /*  controller chip to sleep, and the reset command is ignored.  */
  /* We poll the MSR once to wake up the controller so it is ready */
  /*  for the reset command.                                       */
  /*---------------------------------------------------------------*/

  tempPort = npACB->IOPorts[FCR_MSR];                      /*V88951*/
  inp( tempPort, tempval);                                 /*V88951*/
  for (i=0;i<5 ;i++, IODelay());                           /*V88951*/

  /*------------------------------------*/
  /* Assert RESET (active low) for 10us */
  /*------------------------------------*/

  Value = npACB->IORegs[FCR_DOR];
  Value &= ~FCR_DOR_RESET;

  outp( Port, Value );
  for ( i=0; i < RESET_DELAY_TIME; i++ )
  {
    IODelay();
  }
  Value |= FCR_DOR_RESET;
  outp( Port, Value );

  /*------------------------------------*/
  /* Interrupt expected after RESET     */
  /*------------------------------------*/

  if ( f_ADD_StartTimerMS((PULONG) &npACB->IRQTimerHandle,
                          (ULONG)  npACB->IRQTimer,
                          (PFN)    IRQTimeOutHandler,
                          (PVOID)  npACB,
                          (ULONG)  0                           ) )
  {
    _asm { int 3 }
  }
  npACB->Flags |= (ACBF_INTERRUPT | ACBF_WAITSTATE);
  npACB->State  = ACBS_INTERRUPT;

  ENABLE
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Select/Specify States                                                  */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*----------------------------------------------*/
/*                                              */
/* Select the Drive                             */
/*                                              */
/* This state selects the drive that subsequent */
/* states will operate on. This state also      */
/* sets the proper data rate. Note that some    */
/* diskette controller functions (such as       */
/* Step Rates are driven and dependent on the   */
/* data rate set.                               */
/*                                              */
/*----------------------------------------------*/

VOID NEAR StartSelect( NPACB npACB )
{
  USHORT        Port;
  USHORT        Value;
  NPUCB         npUCB;


  /*---------------------------------------------*/
  /* The FORCE_SELECT state is used to guarantee */
  /* that a drive will be reselected regardless  */
  /* if whether or not the drive was previously  */
  /* selected.                                   */
  /*---------------------------------------------*/
  if ( (npACB->ReqFlags & ACBR_FORCE_SELECT)
         || (npACB->CurUnitId != npACB->UnitId) )
  {

    /*-----------------------------------------------*/
    /* We must have a UCB at this point representing */
    /* the drive we are about to operate on!         */
    /*-----------------------------------------------*/
    if ( !(npUCB = npACB->npUCB) )
    {
      _asm int 3
    }

    /*-------------------------------------------*/
    /* Some drives shut off their motor when     */
    /* deselected even though the motor enables  */
    /* are left on.  Therefore we must indicate  */
    /* that the unit we are about to select must */
    /* wait for its motor to spin-up.            */
    /*-------------------------------------------*/
    if ( npACB->CurUnitId != npACB->UnitId )                         /*@V85053*/
    {                                                                /*@V85053*/
      npUCB->Flags &= ~UCBF_MOTOR_OK;                                /*@V85053*/
    }                                                                /*@V85053*/

    /*-------------------------------------------------*/
    /* Set up ACB variables. Set the current MediaInfo */
    /* pointer based on the current Media Index in     */
    /* the selected UCB.                               */
    /*-------------------------------------------------*/

    npACB->CurUnitId = npUCB->UnitId;
    npACB->npMIF     = &npUCB->npMediaInfo[npUCB->MediaInfoIndex];
    npACB->CurCyl    = npUCB->CurCyl;

    /*-----------------------------------------*/
    /* Set drive select on controller DOR reg  */
    /*-----------------------------------------*/

    Port  = npACB->IOPorts[FCR_DOR];

    DISABLE

    npACB->IORegs[FCR_DOR] &= ~FCR_DOR_DRIVE_SEL;
    npACB->IORegs[FCR_DOR] |= npACB->CurUnitId;

    Value = npACB->IORegs[FCR_DOR];
    outp( Port, Value );

    Port = npACB->IOPorts[FCR_CCR];
    Value = npACB->npMIF->DataRate;

    outp( Port, Value );

    ((NPBYTE) &npACB->IORegs[FCR_CCR])[1] = Value;

    ENABLE

    /*-----------------------------------------------*/
    /* Dont know why the controller can't remember   */
    /* the specify bytes and select the appropriate  */
    /* step rate when the unit is selected.          */
    /*-----------------------------------------------*/
    npACB->ReqFlags |= ACBR_SPECIFY;

    /*--------------------------------------*/
    /* Turn off pending FORCE_SELECT in the */
    /* selected UCB                         */
    /*--------------------------------------*/
    npUCB->ReqFlags &= ~UCBR_FORCE_SELECT;
  }

  npACB->ReqFlags &= ~(ACBR_SELECT | ACBR_FORCE_SELECT);
}

/*------------------------------------------*/
/*                                          */
/* Specify State                            */
/*                                          */
/* Send a Specify command to the controller */
/* to set the Step Rate and other diskette  */
/* timings.                                 */
/*                                          */
/* This command has no results phase.       */
/*                                          */
/*------------------------------------------*/

VOID NEAR StartSpecify( NPACB npACB )
{
  npACB->CurCmdLen     = FC_LEN_SPECIFY;
  npACB->CurResultsLen = FR_LEN_SPECIFY;

  npACB->CurCmd[0] = FC_SPECIFY;
  npACB->CurCmd[1] = npACB->npMIF->Specify[0];
  npACB->CurCmd[2] = npACB->npMIF->Specify[1];

  SendCommand( npACB, 0 );

  npACB->ReqFlags &= ~ACBR_SPECIFY;
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* Motor On / Motor Wait States                                                   */
/*                                                                        */
/*------------------------------------------------------------------------*/


/*-------------------------------*/
/*                               */
/* Start Drive Motor             */
/*                               */
/*                               */
/*-------------------------------*/

VOID NEAR StartMotorOn( NPACB npACB )
{
  NPUCB         npUCB;

  USHORT        Port;
  USHORT        Value;

  npUCB = npACB->npUCB;

  DISABLE

  /*--------------------------------------------*/
  /* Setting MotorOffCount to -1 prevents       */
  /* the MotorIdleCheck() routine from changing */
  /* the motor state, i.e. turning the motor    */
  /* off.                                       */
  /*                                            */
  /* The previous motor state is kept so that   */
  /* the ChangeLine handling can determine      */
  /* whether to simulate a changeline event on  */
  /* drives that do not have a changeline.      */
  /*--------------------------------------------*/
  npUCB->MotorOffCount  = -1;

  /*-------------------------------------*/
  /* If the Motor was off, then start it */
  /*-------------------------------------*/
  if ( !(npUCB->Flags & UCBF_MOTOR_ON) )
  {
    npUCB->Flags         &= ~UCBF_MOTOR_ON_PREV;
    npUCB->Flags         |=  UCBF_MOTOR_ON;

    Port  = npACB->IOPorts[FCR_DOR];

    npACB->IORegs[FCR_DOR] |= (FCR_DOR_MOTOR_0 << npACB->CurUnitId);

    Value = npACB->IORegs[FCR_DOR];
    outp( Port, Value );
    ENABLE

  }
  else
  {
    npUCB->Flags |= UCBF_MOTOR_ON_PREV;
  }

  ENABLE

  /*--------------------------------------------------*/
  /* UCBF_MOTOR_OK indicates that the motor had       */
  /* previously reached operating speed.              */
  /*                                                  */
  /* If this bit is not set, then when we enter the   */
  /* MOTOR_WAIT state, the ISM will begin a WAITSTATE */
  /* until the Motor spin-up time elapses. During     */
  /* this interval we allow SEEKs and RECALs to       */
  /* occur which are not dependent on motor speed     */
  /*--------------------------------------------------*/

  if ( !(npUCB->Flags & UCBF_MOTOR_OK) )
  {
    DISABLE

    if ( npACB->DelayTimerHandle )                                   /*@V85053*/
    {                                                                /*@V85053*/
      f_ADD_CancelTimer( npACB->DelayTimerHandle );                  /*@V85053*/
      npACB->DelayTimerHandle = 0;                                   /*@V85053*/
    }                                                                /*@V85053*/
                                                                     /*@V85053*/
    ENABLE                                                           /*@V85053*/

    npACB->Flags &= ~ACBF_MOTOR_POST;

    if ( f_ADD_StartTimerMS((PULONG) &npACB->DelayTimerHandle,
                            (ULONG)  npUCB->MotorOnTimer,
                            (PFN)    MotorOnAsync,
                            (PVOID)  npACB,
                            (PVOID)  npACB->npUCB                  ) )
    {
      _asm { int 3 }
    }
  }
  else
  {
    npACB->ReqFlags &= ~ACBR_MOTOR_WAIT;
  }


  npACB->State     = ACBS_NEXT_REQUEST;
  npACB->ReqFlags &= ~ACBR_MOTOR_ON;

}

/*----------------------------------------------------*/
/*                                                    */
/* Wait for Motor to spin-up                          */
/*                                                    */
/* If we reach this state and the motor On interval   */
/* has not completed, then we enter a WAITSTATE.      */
/* Before doing so we set a bit indicating that       */
/* MotorOnAsync() should restart the ISM when the     */
/* motor on interval expires.                         */
/*                                                    */
/*----------------------------------------------------*/

VOID NEAR MotorWait( NPACB npACB )
{
  DISABLE

  npACB->State     = ACBS_NEXT_REQUEST;

  if ( !(npACB->npUCB->Flags & UCBF_MOTOR_OK) )
  {
    npACB->Flags |= ACBF_MOTOR_POST;
    npACB->Flags |= ACBF_WAITSTATE;
  }
  else
  {
    npACB->ReqFlags &= ~(ACBR_MOTOR_WAIT | ACBR_MOTOR_SPINUP);       /*@V85053*/
  }
  ENABLE
}

/*------------------------------------------------*/
/*                                                */
/* Motor On interval expired                      */
/*                                                */
/*------------------------------------------------*/

VOID FAR MotorOnAsync( ULONG TimerHandle, PACB pACB, PUCB pUCB  )
{
  NPACB         npACB;
  NPUCB         npUCB;

  npACB = (NPACB)OFFSETOF(pACB);
  npUCB = (NPUCB)OFFSETOF(pUCB);

  /*-----------------------------------------*/
  /* Set the UCB flags to indicate that      */
  /* the Motor speed is now OK.              */
  /*                                         */
  /* If the ISM has been suspended waiting   */
  /* for the Motor to spin-up, restart state */
  /* processing now.                         */
  /*-----------------------------------------*/

  DISABLE

  npUCB->Flags |= UCBF_MOTOR_OK;

  if ( TimerHandle != npACB->DelayTimerHandle )
  {
    _asm { int 3 }
  }

  f_ADD_CancelTimer( TimerHandle );
  npACB->DelayTimerHandle = 0;

  if ( npACB->Flags & ACBF_MOTOR_POST )
  {
    npACB->Flags &= ~ACBF_MOTOR_POST;
    ENABLE

    npACB->State = ACBS_NEXT_REQUEST;

    StartISM( npACB );
  }

  ENABLE;
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Media Removal/Change detection (Changeline States)                     */
/*                                                                        */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*-------------------------------------------------*/
/*                                                 */
/* Read the drive Changeline                       */
/*                                                 */
/* If the changeline is set, issue a series of     */
/* states to reset it. The current ISM request     */
/* will be returned with a Media Changed           */
/* indication after the changeline is reset.       */
/*                                                 */
/*-------------------------------------------------*/

VOID NEAR CheckMediaChange( NPACB npACB )
{

  USHORT        Port;
  USHORT        Value = 0;

  npACB->ReqFlags &= ~ACBR_CHECK_MEDIA;

  /*---------------------------------------------*/
  /* If the drive has a changeline, then we read */
  /* the changeline bit for the drive            */
  /*---------------------------------------------*/

  if ( !(npACB->npUCB->Flags & UCBF_NO_CHANGELINE) )
  {
    DISABLE
    Port = npACB->IOPorts[FCR_DIR];
    inp( Port, Value );
    npACB->IORegs[FCR_DIR] = Value;
    ENABLE

    Value &= FCR_DIR_DSK_CHG;
    Value ^= (npACB->npUCB->Flags & UCBF_CHANGELINE_INVERT) ? FCR_DIR_DSK_CHG : 0;


    /*---------------------------------------------------*/
    /* The changeline bit indicates that the media has   */
    /* been changed.                                     */
    /*                                                   */
    /*     ....Here's where the trouble begins.....      */
    /*                                                   */
    /*---------------------------------------------------*/

    if ( Value )
    {
      npACB->State         = ACBS_NEXT_REQUEST;
      npACB->CurStatus     = REQS_MEDIA_CHANGED;
      npACB->npUCB->Flags |= UCBF_MEDIA_UNCERTAIN;

      /*--------------------------------------------------*/
      /* In order to reset the changline we first must    */
      /* reset the controller. This is to insure that     */
      /* the brain-dead controller has not locked-up to   */
      /* the point that we cannot issue seek commands to  */
      /* the drive to reset the changeline.               */
      /*                                                  */
      /* When the Reset/Recal states are completed        */
      /* the RESET_CHANGE state routine is called.        */
      /*                                                  */
      /*--------------------------------------------------*/

      npACB->npUCB->Flags &= ~UCBF_MOTOR_OK;                         /*@V85053*/
      npACB->ReqFlags     |= (ACBR_RESET        | ACBR_MOTOR_ON |    /*@V85053*/
                              ACBR_MOTOR_SPINUP | ACBR_RESET_CHANGE);/*@V85053*/
    }
  }

  /*----------------------------------------------------*/
  /* If the drive does not have changeline support we   */
  /* check the previous motor setting. If the motor was */
  /* previously off we indicate the media has changed   */
  /*----------------------------------------------------*/

  else if ( !(npACB->npUCB->Flags & UCBF_MOTOR_ON_PREV) )
  {
    npACB->CurStatus     = REQS_MEDIA_CHANGED;
    npACB->npUCB->Flags |= UCBF_MEDIA_UNCERTAIN;
    npACB->State         = ACBS_COMPLETE;
  }

  /*----------------------------------------------------*/
  /* If the media was changed, we reset the UNFORMATTED */
  /* media indicator.                                   */
  /*----------------------------------------------------*/

  if ( npACB->CurStatus == REQS_MEDIA_CHANGED )
  {
    npACB->npUCB->Flags &= ~UCBF_MEDIA_UNFORMATTED;
  }
}

/*-------------------------------------------------------*/
/*                                                       */
/* Reset Media Change                                    */
/*                                                       */
/* In order to reset the changeline we must move the     */
/* drive at least 1 cylinder. Since we previous Recal'd  */
/* the drive it must be at cylinder 0. We now Seek to    */
/* cylinder 1.                                           */
/*                                                       */
/*-------------------------------------------------------*/

VOID NEAR ResetMediaChange( NPACB npACB )
{
  npACB->IRQRoutine = EndResetMediaChange;

  npACB->CurResultsLen = FR_LEN_SEEK;
  npACB->CurCmdLen     = FC_LEN_SEEK;

  npACB->CurCmd[0] = FC_SEEK;
  npACB->CurCmd[1] = npACB->CurUnitId; /* | (npACB->ReqHead << 2) */
  npACB->CurCmd[2] = (npACB->npMIF->Flags & MIF_DOUBLE_STEP) ? 2 : 1;

  npACB->npUCB->CurCyl = npACB->CurCyl = -1;

  SendCommand( npACB, SCF_IRQTIMER );

}
/*---------------------------------------------------*/
/*                                                   */
/* Complete Changeline reset                         */
/*                                                   */
/* We just completed a seek to cylinder 1 to reset   */
/* the changeline.                                   */
/*                                                   */
/* If the changeline is still set, this indicates    */
/* that there is no media in the drive.              */
/*                                                   */
/*---------------------------------------------------*/

VOID NEAR EndResetMediaChange( NPACB npACB )
{
  USHORT        Port;
  USHORT        Value;

  /*-------------------------------------------*/
  /* Status should indicate no Equipment Check */
  /*-------------------------------------------*/

  if ( !npACB->TimerFlags
         && !(npACB->CurResults[0] & ST0_EQUIPMENT_CHK) )
  {
    npACB->CurCyl = (npACB->npMIF->Flags & MIF_DOUBLE_STEP) ?
                        npACB->CurResults[1] >> 1 : npACB->CurResults[1];

    npACB->npUCB->CurCyl = npACB->CurCyl;

    /*----------------------------------------------*/
    /* Read the changeline again. If the changeline */
    /* did not reset then the drive was NOT READY.  */
    /*----------------------------------------------*/
    DISABLE
    Port = npACB->IOPorts[FCR_DIR];
    inp( Port, Value );
    npACB->IORegs[FCR_DIR] = Value;
    ENABLE

    Value &= FCR_DIR_DSK_CHG;
    Value ^= (npACB->npUCB->Flags & UCBF_CHANGELINE_INVERT) ? FCR_DIR_DSK_CHG : 0;

    if ( Value )
    {
      npACB->CurStatus  = REQS_NOT_READY;
    }
    else
    {
      npACB->CurStatus  = REQS_MEDIA_CHANGED;
    }
  }


  npACB->ReqFlags &= ~ACBR_RESET_CHANGE;
  npACB->State     = ACBS_COMPLETE;
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Seek / Recalibrate States                                              */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------------*/
/*                                          */
/* Start Recalibrate Operation              */
/*                                          */
/*------------------------------------------*/

VOID NEAR StartRecal( NPACB npACB )
{
  npACB->IRQRoutine = EndRecal;

  npACB->npUCB->CurCyl = npACB->CurCyl = -1;

  npACB->CurCmdLen     = FC_LEN_RECALIBRATE;
  npACB->CurResultsLen = FR_LEN_RECALIBRATE;

  npACB->CurCmd[0]     = FC_RECALIBRATE;
  npACB->CurCmd[1]     = npACB->CurUnitId;

  npACB->CurStatus     = REQS_RECAL_FAIL;

  SendCommand( npACB, SCF_IRQTIMER );
}

/*-------------------------------------------------*/
/*                                                 */
/* End Recalibrate Operation                       */
/*                                                 */
/* For the recalibrate to succeed the drive must   */
/* set its TRK 0 line indicating the head is now   */
/* at cylinder 0. We set the CurCyl field of the   */
/* ACB/UCB accordingly and clear any pending       */
/* RECAL request flags in the UCB/ACB.             */
/*                                                 */
/*-------------------------------------------------*/

VOID NEAR EndRecal( NPACB npACB )
{
  npACB->ReqFlags &= ~ACBR_RECAL;

  /*-------------------------------------------*/
  /* Status should indicate no Equipment Check */
  /* and PCN == 0                              */
  /*-------------------------------------------*/

  if ( !npACB->TimerFlags
         && npACB->CurResults[1] == 0 )
  {
    if ( !(npACB->CurResults[0] & ST0_EQUIPMENT_CHK) )
    {
      npACB->npUCB->CurCyl    = npACB->CurCyl = 0;
      npACB->State            = ACBS_NEXT_REQUEST;
      npACB->CurStatus        = REQS_NO_ERROR;
      npACB->npUCB->ReqFlags &= ~UCBR_RECAL;
    }
    /*--------------------------------------------------------*/
    /* Some Dell's 325D/333D seem to have a brain-dead floppy */
    /* controller that issues less than 79 step pulses during */
    /* a RECAL.                                               */
    /*                                                        */
    /* We retry the RECAL in this case to get the head back   */
    /* to Track 0. We should not have to do this!             */
    /*--------------------------------------------------------*/
    else
    {
      npACB->ISMRecal++;

      if ( npACB->ISMRecal < MAX_ISM_RECAL )
      {
        npACB->ReqFlags |= ACBR_RECAL;
        npACB->State = ACBS_NEXT_REQUEST;
      }
      else
      {
        npACB->State = ACBS_COMPLETE;
      }
    }
  }
  else
  {
    npACB->State = ACBS_COMPLETE;
  }


}

/*------------------------------------------*/
/*                                          */
/* Start Seek Operation                     */
/*                                          */
/*------------------------------------------*/

VOID NEAR StartSeek( NPACB npACB )
{
  /*-------------------------------------------------*/
  /* Note: ImpliedSeekCheck will set ReqCyl==CurCyl  */
  /*       when an implied seek is possible.         */
  /*                                                 */
  /*       In addition this routine will turn off    */
  /*       the implied seek function for media       */
  /*       requiring double stepping.                */
  /*-------------------------------------------------*/

  ImpliedSeekCheck( npACB );

  if ( npACB->ReqCyl != npACB->CurCyl )
  {
    npACB->IRQRoutine = EndSeek;

    npACB->npUCB->CurCyl = npACB->CurCyl = -1;

    npACB->CurResultsLen = FR_LEN_SEEK;
    npACB->CurCmdLen     = FC_LEN_SEEK;

    npACB->CurCmd[0]     = FC_SEEK;
    npACB->CurCmd[1]     = npACB->CurUnitId; /* | (npACB->ReqHead << 2) */
    npACB->CurCmd[2]     = npACB->ReqCyl;

    /*-------------------------------------------*/
    /* 40-Track media in 80-Track drive requires */
    /* requires 2 steps per cylinder.            */
    /*-------------------------------------------*/
    if ( npACB->npMIF->Flags & MIF_DOUBLE_STEP )
    {
      npACB->CurCmd[2] <<= 1;
    }

    npACB->CurStatus     = REQS_SEEK_FAIL;

    SendCommand( npACB, SCF_IRQTIMER );
  }
  else
  {
    npACB->ReqFlags   &= ~(ACBR_SEEK | ACBR_SETTLE);
  }
}

/*-------------------------------------------------*/
/*                                                 */
/* End Seek Operation                              */
/*                                                 */
/* This routine checks that we arrived at the      */
/* cylinder address we requested.                  */
/*-------------------------------------------------*/

VOID NEAR EndSeek( NPACB npACB )
{
  UCHAR         ExpCyl;

  npACB->ReqFlags     &= ~ACBR_SEEK;

  ExpCyl = (npACB->npMIF->Flags & MIF_DOUBLE_STEP) ? npACB->ReqCyl << 1 :
                                                     npACB->ReqCyl;

  /*-------------------------------------------*/
  /* Status should indicate no Equipment Check */
  /* and PCN == ReqCyl                         */
  /*-------------------------------------------*/
  if ( !npACB->TimerFlags
         && !(npACB->CurResults[0] & ST0_EQUIPMENT_CHK)
           && ( npACB->CurResults[1] == ExpCyl ) )
  {
    npACB->npUCB->CurCyl = npACB->CurCyl = npACB->ReqCyl;
    npACB->State         = ACBS_NEXT_REQUEST;
    npACB->CurStatus     = REQS_NO_ERROR;

  }
  else
  {
    npACB->State = ACBS_COMPLETE;
  }

}

/*-------------------------------------------------*/
/* Implied Seek Check                              */
/*                                                 */
/* Determine whether Implied Seeks are applicable  */
/* to the current request.                         */
/*                                                 */
/*-------------------------------------------------*/
VOID NEAR ImpliedSeekCheck( NPACB npACB )
{
  /*--------------------------------------*/
  /* If the controller is not capable of  */
  /* Implied Seeks then exit              */
  /*--------------------------------------*/
  if ( !(npACB->SeekFlags & ACBK_ISEEK_CAPABLE) )
  {
    goto ImpliedSeekExit;
  }

  /*---------------------------------------*/
  /* If Implied Seeks are enabled but      */
  /* the media requires double steping,    */
  /* disable the implied seek function     */
  /*---------------------------------------*/
  if ( npACB->npMIF->Flags & MIF_DOUBLE_STEP )
  {
    SetSeekState( npACB, 0 );
    goto ImpliedSeekExit;
  }

  /*----------------------------------*/
  /* Enable the Implied Seek function */
  /* if it was previously disabled    */
  /*----------------------------------*/
  if ( !(npACB->SeekFlags & ACBK_ISEEK_ENABLED) )
  {
    SetSeekState( npACB, 1 );
  }

  /*-------------------------------------*/
  /* If the command is an Implied Seek   */
  /* command, set the current cylinder   */
  /* to the requested cylinder.          */
  /* This will cause the caller to skip  */
  /* the SEEK and SETTLE states.         */
  /*-------------------------------------*/

  if ( npACB->ReqFlags & ACBR_COMMAND )
  {
    switch ( npACB->ReqCmd[0] )
    {
      case FC_READ_DATA:
      case FC_WRITE_DATA:
      case FC_READ_ID:
      case FC_READ_DELETED_DATA:
      case FC_WRITE_DELETED_DATA:
      case FC_READ_TRACK:
        npACB->npUCB->CurCyl = npACB->CurCyl = npACB->ReqCyl;
    }
  }

  ImpliedSeekExit: ;
}

/*-------------------------------------------------*/
/* Set Seek State                                  */
/*                                                 */
/* Enables / Disables Implied Seek function.       */
/*                                                 */
/*-------------------------------------------------*/

VOID NEAR SetSeekState( NPACB npACB, USHORT SeekState )
{
  npACB->CurCmdLen     = FC_LEN_CONFIGURE;

  npACB->CurCmd[0]     = FC_CONFIGURE;
  npACB->CurCmd[1]     = 0;
  npACB->CurCmd[2]     = FIFO_THRESHOLD_DEFAULT;
  npACB->CurCmd[3]     = 0;

  if ( SeekState )
  {
    npACB->CurCmd[2] |= ENABLE_IMPLIED_SEEKS;
    npACB->SeekFlags |= ACBK_ISEEK_ENABLED;
  }
  else
  {
    npACB->SeekFlags &= ~ACBK_ISEEK_ENABLED;
  }

  SendCommand( npACB, 0 );

  /*-----------------------------------------------------*/
  /* National series controllers (8477, 87311,...) use   */
  /* a separate register to control head settle timing   */
  /* after seeks. We use the delays in the Specify Cmd   */
  /* and disable this option.                            */
  /*                                                     */
  /* Note: These are the default (software reset) values */
  /*       for the MODE parameters except for the Head   */
  /*       Settle (Byte 3 Bits 3-0) which is set to 8    */
  /*       by default.                                   */
  /*-----------------------------------------------------*/

  if ( SeekState && (npACB->SeekFlags & ACBK_NATIONAL_8477) )
  {
    npACB->CurCmdLen   = FC_LEN_MODE;
    npACB->CurCmd[0]   = FC_MODE;
    npACB->CurCmd[1]   = 0x02;
    npACB->CurCmd[2]   = 0x00;
    npACB->CurCmd[3]   = 0xC0;
    npACB->CurCmd[4]   = 0x00;

    SendCommand( npACB, 0 );
  }

}

/*------------------------------------------------------------------------*/
/*                                                                        */
/*  Settle Timed Delay                                                    */
/*                                                                        */
/*------------------------------------------------------------------------*/


/*---------------------------------------------------*/
/*                                                   */
/* Start Settle timed delay                          */
/*                                                   */
/* This state suspends state handling to allow       */
/* the drive head to stop moving after a seek        */
/* operation.                                        */
/*                                                   */
/*---------------------------------------------------*/

VOID NEAR StartSettle( NPACB npACB )
{
  npACB->TmrRoutine = EndSettle;

  if ( f_ADD_StartTimerMS((PULONG) &npACB->DelayTimerHandle,
                          (ULONG)  npACB->SettleTime,
                          (PFN)    DelayTimerExpired,
                          (PVOID)  npACB,
                          (PVOID)  npACB->CurUnitId                ) )
  {
    _asm { int 3 }
  }

  npACB->State  = ACBS_TIMER;
  npACB->Flags |= ACBF_WAITSTATE;
}

/*-----------------------------------------*/
/*                                         */
/* Complete settle delay                   */
/*                                         */
/*-----------------------------------------*/


VOID NEAR EndSettle( NPACB npACB )
{
  npACB->TmrRoutine = 0;

  npACB->State      =  ACBS_NEXT_REQUEST;
  npACB->ReqFlags  &= ~ACBR_SETTLE;
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/*  Command Issue State                                                   */
/*                                                                        */
/*------------------------------------------------------------------------*/


/*----------------------------------------------------*/
/*                                                    */
/* Start OSM Command                                  */
/*                                                    */
/* The purpose of all the previous states were to     */
/* get the controller/drive to the point where        */
/* we can do some "productive?" work.                 */
/*                                                    */
/* We now set up and issue to command passed by the   */
/* Outer State machine (or other requestor) in        */
/* ReqCmd[]. If the command requires a data transfer  */
/* we set-up the DMA controller before issuing        */
/* the command.                                       */
/*                                                    */
/*----------------------------------------------------*/

VOID NEAR StartCommand( NPACB npACB )
{

  npACB->IRQRoutine = EndCommand;

  if ( npACB->ReqDataLen )
  {
    SetupDMA( npACB );
  }

  npACB->CurCmdLen     = npACB->ReqCmdLen;
  npACB->CurResultsLen = npACB->ReqResultsLen;

  memcpy( npACB->CurCmd, npACB->ReqCmd, MAX_CMD_LEN );

  npACB->CurStatus     = REQS_COMMAND_FAIL;

  SendCommand( npACB, SCF_IRQTIMER );
}


/*-------------------------------------------------*/
/*                                                 */
/* Complete OSM Command                            */
/*                                                 */
/* The Outer State Machine command has completed.  */
/* To assist the OSM we check the first result     */
/* byte to determine whether the command completed */
/* O.K.                                            */
/*                                                 */
/* The OSM may perform additional checks on the    */
/* results collected in CurResults[].              */
/*                                                 */
/*-------------------------------------------------*/

VOID NEAR EndCommand( NPACB npACB )
{
  npACB->ReqFlags   &= ~ACBR_COMMAND;
  npACB->State = ACBS_NEXT_REQUEST;

  if ( !npACB->TimerFlags
         && !(npACB->CurResults[0] & ST0_INT_CODE) )
  {
    npACB->CurStatus = REQS_NO_ERROR;
  }
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* Read/Write Commands to Diskette Controller                             */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*--------------------------------------------------*/
/*                                                  */
/* Read Result Bytes from controller                */
/*                                                  */
/* This routine obtains the requested number        */
/* of result bytes (CurResultsLen) from the         */
/* diskette controller. The results bytes           */
/* read from the controller are placed in           */
/* (CurResults[]).                                  */
/*                                                  */
/*--------------------------------------------------*/

USHORT FAR f_ReadResults( NPACB npACB )
{
  return( ReadResults( npACB ) );
}

USHORT NEAR ReadResults( NPACB npACB )
{
  NPUCB         npUCB;

  USHORT        rc = 0;
  USHORT        i;
  USHORT        Value;
  USHORT        DATAPort;

  DATAPort = npACB->IOPorts[FCR_DATA];

  /*----------------------------------------*/
  /* Wait for the contoller to signal its   */
  /* ready to transfer a results byte.      */
  /*                                        */
  /* Continue to poll the controller until  */
  /* the expected number of bytes have been */
  /* transferred.                           */
  /*----------------------------------------*/

  for (i=0; i < npACB->CurResultsLen; i++ )
  {
    if ( CheckMSRRead( npACB ) )
    {
      break;
    }

    inp( DATAPort, Value );
    IODelay();
    npACB->CurResults[i] = Value;
  }
  /*---------------------------------------------*/
  /* If we did not transfer the expected number  */
  /* of results bytes, we timed-out waiting for  */
  /* the controller.                             */
  /*                                             */
  /* Set a pending reset in each UCB to clear    */
  /* any potential controller hang.              */
  /*                                             */
  /* Return the reuqest to the OSM.              */
  /*                                             */
  /*---------------------------------------------*/

  if ( i != npACB->CurResultsLen )
  {
    npUCB = npACB->npFirstUCB;
    while( npUCB )
    {
      npUCB->ReqFlags |= UCBR_RESET;

      npUCB = npUCB->npNextUCB;
    }

    if ( !npACB->CurStatus )
    {
      npACB->CurStatus = REQS_CTRL_TIMEOUT;
    }

    npACB->TimerFlags   |= ACBT_READ_RESULTS;
    npACB->State         = ACBS_COMPLETE;
    npACB->Flags        &= ~ACBF_WAITSTATE;
    rc = 1;
  }

  return( rc );

}


/*-----------------------------------------------------*/
/*                                                     */
/* Send Command Bytes to the controller                */
/*                                                     */
/* This routine sends the requested number             */
/* of command bytes (CurCmdLen) to the controller.     */
/*                                                     */
/* If the caller indicated the command will interrupt  */
/* then setup an interrupt timeout timer and           */
/* set the ISM to go into a WAITSTATE.                 */
/*                                                     */
/*-----------------------------------------------------*/

USHORT FAR f_SendCommand( NPACB npACB, USHORT Flags )
{
  return( SendCommand( npACB, Flags ) );
}

USHORT NEAR SendCommand( NPACB npACB, USHORT Flags )
{
  NPUCB         npUCB;

  USHORT        i = 0;
  USHORT        DATAPort;
  USHORT        CurCmdLen;                                           /*@V85816*/
  USHORT        Value;
  USHORT        rc = 1;


  memset( npACB->CurResults, -1, MAX_RESULTS_LEN );

  /*-------------------------------------------*/
  /* Check if the diskette controller is ready */
  /* to accept a command.                      */
  /*-------------------------------------------*/
  if ( !CheckMSRSend( npACB ) )
  {
    /*--------------------------------------------*/
    /* If we are expecting an INTERRUPT start the */
    /* interrupt timer running before programming */
    /* the controller.                            */
    /*--------------------------------------------*/
    if ( Flags & SCF_IRQTIMER )
    {
      if ( f_ADD_StartTimerMS((PULONG) &npACB->IRQTimerHandle,
                              (ULONG)  npACB->IRQTimer,
                              (PFN)    IRQTimeOutHandler,
                              (PVOID)  npACB,
                              (ULONG)  0                           ) )
      {
        _asm { int 3 }
      }

      npACB->Flags |= (ACBF_INTERRUPT | ACBF_WAITSTATE);
      npACB->State  = ACBS_INTERRUPT;
    }

    /*-----------------------------------*/
    /* Send to command to the controller */
    /*-----------------------------------*/
    DATAPort = npACB->IOPorts[FCR_DATA];

    CurCmdLen = npACB->CurCmdLen;                                    /*@V85816*/

    for (i = 0; i < CurCmdLen; i++ )                                 /*@V85816*/
    {
      if ( CheckMSRSend( npACB ) )
      {
        break;
      }
      Value = npACB->CurCmd[i];
      outp( DATAPort, Value );
      IODelay();
    }

    /*-----------------------------------------*/
    /* If we did not send the entire command,  */
    /* cancel the interrupt timer (if any) and */
    /* abort the current ISM request           */
    /*-----------------------------------------*/

    if ( i == CurCmdLen )                                            /*@V85816*/
    {
      rc = 0;
    }
    else
    {
      DISABLE
      if ( npACB->IRQTimerHandle )
      {
        f_ADD_CancelTimer( npACB->IRQTimerHandle );                  /*@V92714*/
        npACB->IRQTimerHandle = 0;
      }
      ENABLE

    }
  }

  /*---------------------------------------------*/
  /* If we did not transfer the expected number  */
  /* of command bytes, we timed-out waiting for  */
  /* the controller.                             */
  /*                                             */
  /* Set a pending reset in each UCB to clear    */
  /* any potential controller hang.              */
  /*                                             */
  /* Return the reuqest to the OSM.              */
  /*                                             */
  /*---------------------------------------------*/
  if ( rc )
  {
    npUCB = npACB->npFirstUCB;
    while( npUCB )
    {
      npUCB->ReqFlags |= UCBR_RESET;

      npUCB = npUCB->npNextUCB;
    }

    if ( !npACB->CurStatus )
    {
      npACB->CurStatus = REQS_CTRL_TIMEOUT;
    }

    npACB->State         = ACBS_COMPLETE;
    npACB->Flags        &= ~ACBF_WAITSTATE;

    npACB->TimerFlags   |= ACBT_SEND_COMMAND;
  }

  return( rc );
}


/*-----------------------------------------------------------*/
/*                                                           */
/* Check if controller is ready to accept a command byte     */
/*                                                           */
/*-----------------------------------------------------------*/

USHORT FAR CheckMSRSend( NPACB npACB )
{
  ULONG         MSRLoopCount;
  USHORT        MSRPort;
  USHORT        Value;
  USHORT        rc = 1;

  MSRPort = npACB->IOPorts[FCR_MSR];

  MSRLoopCount = MSRWaitCount;

  while ( MSRLoopCount-- )
  {
    inp( MSRPort, Value );
    IODelay();
    if ( !Calibrate && (Value & FCR_MSR_RQM) && !(Value & FCR_MSR_DIO) )
    {
      rc = 0;
      break;
    }
  }
  return( rc );
}


/*-----------------------------------------------------------*/
/*                                                           */
/* Check if controller is ready to send a result byte        */
/*                                                           */
/*-----------------------------------------------------------*/

USHORT NEAR CheckMSRRead( NPACB npACB )
{
  ULONG         MSRLoopCount;
  USHORT        MSRPort;
  USHORT        Value;
  USHORT        rc = 1;

  MSRPort = npACB->IOPorts[FCR_MSR];

  MSRLoopCount = MSRWaitCount;

  while ( MSRLoopCount-- )
  {
    inp( MSRPort, Value );
    IODelay();
    if ( (Value & FCR_MSR_RQM) && (Value & FCR_MSR_DIO) )
    {
      rc = 0;
      break;
    }
  }
  return( rc );
}

/*-------------------------------------------------------*/
/*                                                       */
/* Setup DMA controller                                  */
/*                                                       */
/* This routine checks the data transfer direction       */
/* flags and selects the appropriate DMA command.        */
/*                                                       */
/* The DMA is programmed by a worker routine in          */
/* ADDCALLS.LIB.                                         */
/*                                                       */
/*-------------------------------------------------------*/

USHORT NEAR SetupDMA( NPACB npACB )
{
  USHORT        DMACmd;

  if ( npACB->OpFlags & OPF_DATA_TO_HOST )
  {
    DMACmd = DMA_READ;
  }
  else if ( npACB->OpFlags & OPF_DATA_TO_DSKT )
  {
    DMACmd = DMA_WRITE;
// Start of @V88464.
    if ( (npACB->ReqCmd[0] == FC_FORMAT_TRACK) && uCMachine )
    {
      if ( npACB->SeekFlags & ACBK_ISEEK_CAPABLE )
      {
        npACB->CurCmdLen     = FC_LEN_CONFIGURE;
        npACB->CurCmd[0]     = FC_CONFIGURE;
        npACB->CurCmd[1]     = 0;
  // When not using the ABIOS (using IBM1FLPY.ADD on MCA machines,
  // floppy format track fails with DMA timeout (underrun), because
  // CHRN information for a few sectors is still in the FIFO.
  // MCA machine must have DMA hardware timer circuitry.
        npACB->CurCmd[2]    = FIFO_DISABLED;
        npACB->CurCmd[3]    = 0;
        npACB->SeekFlags &= ~ACBK_ISEEK_ENABLED;
        SendCommand( npACB, 0 );
      }
    }
// End of @V88464.
  }
  else
  {
    DMACmd = DMA_VERIFY;
  }
  if ( f_ADD_DMASetup( DMACmd,
                       npACB->DMAChannel,
                       npACB->ReqDataLen,
                       npACB->ReqDataBuf  ) )
  {
    _asm int 3
  }
}

/*------------------------------------*/
/*                                    */
/* Spin for 1us                       */
/*                                    */
/*------------------------------------*/

VOID FAR IODelay()
{
  ULONG   IODelayCtr = IODelayCount;

  while( IODelayCtr--)
    ;
}


/*---------------------------------------------*/
/* DsktIRQ0, DsktIRQ1                          */
/* ------------------                          */
/*                                             */
/* Controller 0,1 interrupt entry points       */
/*                                             */
/*                                             */
/*---------------------------------------------*/

USHORT FAR _loadds DsktIRQ0()
{
  /*-------------------------------------------------*/
  /* The function return of DsktInterrupt is used    */
  /* to set the CY flag for the kernel to indicate   */
  /* whether or not we serviced the interrupt.       */
  /*-------------------------------------------------*/

  return( DsktInterrupt( 0 ) >> 1);
}

USHORT FAR _loadds DsktIRQ1()
{
  return( DsktInterrupt( 1 ) >> 1);
}

/*-------------------------------------------*/
/* Common interrupt service routine          */
/*                                           */
/* The interrupt entry points above selected */
/* the appropriate ACB for the controller we */
/* got the interrupt on.                     */
/*                                           */
/*-------------------------------------------*/

USHORT NEAR DsktInterrupt( USHORT ACBIndex )
{
  NPACB         npACB;

  USHORT        Claimed = 0;
  USHORT        IRQLevel;
  USHORT        HWRIndex;

  IRQLevel  = ACBPtrs[ACBIndex].IRQLevel;
  HWRIndex  = ACBPtrs[ACBIndex].HWRIndex;

  npACB = HWResource[HWRIndex].npOwnerACB;

  if ( npACB )
  {
    /*---------------------------------------*/
    /* If we were expecting an interrupt     */
    /*                                       */
    /* Cancel the interrupt timeout timer,   */
    /* and call the ISM to continue state    */
    /* processing.                           */
    /*---------------------------------------*/

    DISABLE
    if ( npACB->Flags & ACBF_INTERRUPT )
    {
      if ( Claimed = ClearInterrupt( npACB ) )
      {
        npACB->Flags &= ~ACBF_INTERRUPT;

        if ( npACB->IRQTimerHandle )
        {
          f_ADD_CancelTimer( npACB->IRQTimerHandle );
          npACB->IRQTimerHandle = 0;
        }
        else
        {
          _asm { int 3 }
        }

        DevHelp_EOI( IRQLevel );

        ENABLE
        StartISM(npACB);
      }
    }
    /*------------------------------------------------------*/
    /* Unclaimed interrupts on uC machines may be valid     */
    /* due to sharing of IRQ levels.                        */
    /*                                                      */
    /* On ISA/EISA indicate claimed and EOI. Hopefully      */
    /* the interrupting condition was not persistent!       */
    /*                                                      */
    /* Note: Compaqs generate transient spurious interrupts */
    /*       the DMA_GATE is turned off in the DOR reg      */
    /*                                                      */
    /*------------------------------------------------------*/
    else if ( !uCMachine )
    {
      Claimed = 1;
      npACB->SpuriousIRQ++;
      DevHelp_EOI( IRQLevel );
    }
  }

  /*----------------------------------------*/
  /* Unclaimed interrupt with no owning ACB */
  /*----------------------------------------*/
  else
  {
    Claimed = 1;
    DevHelp_EOI( IRQLevel );
  }

  return( ~Claimed );
}

/*-----------------------------------------------------*/
/*                                                     */
/* Interrupt Timeout Entry Point                       */
/*                                                     */
/* If we are here, we timed out waiting for an         */
/* interrupt. We set the interrupt timeout flag        */
/* in the ACB and continue state processing.           */
/*                                                     */
/*-----------------------------------------------------*/


VOID FAR IRQTimeOutHandler( ULONG TimerHandle, ULONG Parm1, ULONG Parm2 )
{
  NPACB         npACB;

  DISABLE
  ADD_CancelTimer( TimerHandle );

  npACB = (NPACB) Parm1;

  npACB->IRQTimerHandle = 0;
  npACB->Flags &= ~ACBF_INTERRUPT;
  ENABLE;

  npACB = (NPACB) Parm1;

  npACB->TimerFlags |= ACBT_INTERRUPT;

  if ( npACB->State == ACBS_INTERRUPT )
  {
    StartISM( npACB );
  }
}

/*---------------------------------------------------------*/
/*                                                         */
/* Delay Interval Expired                                  */
/*                                                         */
/* State processing has been suspened for a timed          */
/* delay. The delay interval has now expired and           */
/* state processing is continued.                          */
/*                                                         */
/*---------------------------------------------------------*/

VOID FAR DelayTimerExpired( ULONG TimerHandle, ULONG Parm1, ULONG Parm2 )
{
  NPACB         npACB;

  ADD_CancelTimer( TimerHandle );

  npACB = (NPACB) Parm1;

  npACB->DelayTimerHandle = 0;

  if ( npACB->State == ACBS_TIMER )
  {
    StartISM( npACB );
  }

}


/*---------------------------------------------*/
/*                                             */
/*                                             */
/*                                             */
/*                                             */
/*                                             */
/*---------------------------------------------*/

USHORT NEAR ClearInterrupt( NPACB npACB )
{
  USHORT        Port;
  USHORT        Value;
  USHORT        Claimed = 1;


  if ( uCMachine )
  {
    Port = npACB->IOPorts[FCR_SRA];
    inp( Port, Value )
    npACB->IORegs[FCR_SRA] = Value;

    if ( !(Value & FCR_SRA_INT_PENDING) )
    {
      Claimed = 0;
    }
  }

  if ( Claimed )
  {
    if ( !npACB->CurResultsLen )
    {
      npACB->CurCmdLen     = FC_LEN_SENSE_INT_STATUS;
      npACB->CurResultsLen = FR_LEN_SENSE_INT_STATUS;
      npACB->CurCmd[0]     = FC_SENSE_INT_STATUS;

      if( !f_SendCommand( npACB, 0 ) )
      {
        f_ReadResults( npACB );
      }
    }
    else
    {
      f_ReadResults( npACB );
    }
  }

  return ( Claimed );
}
