/*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.      */
/*                                                                           */
/*****************************************************************************/
/*static char *SCCSID = "%w% %e%";*/
/**************************************************************************
 *
 * SOURCE FILE NAME = FLP1OSM3.C
 *
 * DESCRIPTIVE NAME = Outer State Machine - Module 3
 *
 *
 *
 * VERSION = V2.1
 *
 * DATE = 94/03/14
 *
 * DESCRIPTION :
 *
 * Purpose:  Functions are implemented this OSM Module:
 *
 *               FLP1OSM3.C - IORB processing for
 *                              - IOCC_DEVICE_CONTROL
 *                                - IOCM_SUSPEND
 *                                - IOCM_RESUME
 *                            ACB Activation/Deactivation
 *                            DMA Buffer Allocate/Deallocate
 *                            Motor Idle Watchdog routine
 *
 *
 *
*/
#define INCL_NOBASEAPI
#define INCL_NOPMAPI
#include "os2.h"
#include "dos.h"

#include "dskinit.h"

#include "iorb.h"
#include "addcalls.h"
#include "dhcalls.h"
#include "apmcalls.h"                                               //@V149969(A)
#include "doccalls.h"                                               //@V149969(A)

#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"

/*------------------------------------------------------------------------*/
/*  SUSPEND FUNCTIONS                                                     */
/*  -----------------                                                     */
/*------------------------------------------------------------------------*/

/*------------------------------------------------*/
/* SuspendIORB                                    */
/*                                                */
/* This routine handles suspend IORBs. The IORB   */
/* is queued on the HWResource structure for      */
/* the UNIT it is directed to.                    */
/*                                                */
/* If the ACB is not processing IORBs, then       */
/* suspend processing may be initited by this     */
/* routine. This depends on whether the ACB       */
/* is IDLE, i.e. the drive motors have timed      */
/* out and whether the flags in the SUSPEND       */
/* IORB indicate an immediate of deferred         */
/* suspend.                                       */
/*                                                */
/*------------------------------------------------*/

USHORT NEAR SuspendIORB( NPACB npACB, PIORB pIORB )
{
  USHORT        InitiateSuspend = 0;
  USHORT        DCFlags;

  NPHWRESOURCE  npHWR;


  pIORB->pNxtIORB = 0;
  DCFlags         = ((PIORB_DEVICE_CONTROL) pIORB)->Flags;
  npHWR           = &HWResource[npACB->HWResourceIndex];

  DISABLE
  /*--------------------------------------------------------*/
  /* Queue IMMEDIATE SUSPEND IORBs at head of ACB Suspend Q */
  /* and DEFERRED SUSPEND IORBs at foot of ACB Suspend Q    */
  /*--------------------------------------------------------*/

  if ( DCFlags & DC_SUSPEND_IMMEDIATE )
  {
    pIORB->pNxtIORB     = npHWR->pSuspendHead;
    npHWR->pSuspendHead = pIORB;

    if ( !npHWR->pSuspendFoot )
    {
      npHWR->pSuspendFoot = pIORB;
    }
  }
  else
  {
    if ( npHWR->pSuspendFoot )
    {
      npHWR->pSuspendFoot->pNxtIORB = pIORB;
    }
    else
    {
      npHWR->pSuspendHead = pIORB;
    }
    npHWR->pSuspendFoot = pIORB;
  }

  /*-----------------------------------------------------*/
  /* An IMMEDIATE SUSPEND is processed after the current */
  /* active request. If we are not processing a request, */
  /* then start the SUSPEND processing.                  */
  /*                                                     */
  /* A DEFERRED SUSPEND is processed after the device    */
  /* is IDLE. If the ACB is already IDLE then start the  */
  /* suspend processing.                                 */
  /*-----------------------------------------------------*/

  if ( !(npHWR->Flags & HWRF_SUSPEND) )
  {
    if ( DCFlags & DC_SUSPEND_IMMEDIATE )
    {
      if ( !npHWR->npOwnerACB )
      {
        InitiateSuspend = 1;
      }
    }
    else
    {
      if ( !CheckACBsIdle( npHWR ) )
      {
        InitiateSuspend = 1;
      }
    }
  }

  if ( InitiateSuspend )
  {
    npHWR->Flags |=  HWRF_SUSPEND;
    ENABLE

    StartSuspend( npACB );
  }
  ENABLE
}

/*-------------------------------------------------*/
/* ImmediateSuspendCheck                           */
/*                                                 */
/* This routine is called by FreeHWResources       */
/* to determine whether an Immedate Suspend        */
/* is pending on any user of those resources.      */
/*                                                 */
/* If an immediate suspend is pending, then        */
/* suspend processing is begun.                    */
/*                                                 */
/*-------------------------------------------------*/
USHORT NEAR ImmediateSuspendCheck( NPACB npACB )
{
  NPHWRESOURCE          npHWR;
  PIORB_DEVICE_CONTROL  pIORB;
  USHORT        rc = 1;

  npHWR = &HWResource[npACB->HWResourceIndex];


  DISABLE
  if ( pIORB = (PIORB_DEVICE_CONTROL) npHWR->pSuspendHead )
  {
    if ( pIORB->Flags & DC_SUSPEND_IMMEDIATE )
    {
      npHWR->Flags |=  HWRF_SUSPEND;
      ENABLE

      StartSuspend( npACB );
      rc = 0;
    }
  }
  ENABLE

  return( rc );
}

/*-----------------------------------------------------------*/
/* DeferredSuspendCheck                                      */
/*                                                           */
/* This routine is called by the MotorIdleCheck when it is   */
/* found that all drive motors have been turned off on       */
/* a controller.                                             */
/*                                                           */
/* If a suspend is pending on the resources held by this     */
/* controller, then processing of the suspend IORB is        */
/* begun.                                                    */
/*                                                           */
/*-----------------------------------------------------------*/
USHORT NEAR DeferredSuspendCheck( NPACB npACB )
{
  NPHWRESOURCE          npHWR;
  PIORB_DEVICE_CONTROL  pIORB;
  USHORT        rc = 1;

  npHWR = &HWResource[npACB->HWResourceIndex];

  DISABLE

  if ( pIORB = (PIORB_DEVICE_CONTROL) npHWR->pSuspendHead )
  {
    if ( !CheckACBsIdle( npHWR ) )
    {
      npHWR->Flags |= HWRF_SUSPEND;
      ENABLE

      StartSuspend( npACB );
      rc = 0;
    }
  }
  ENABLE

  return( rc );
}


/*---------------------------------------------------------*/
/* CheckACBsIdle                                           */
/*                                                         */
/* This routine is called by SuspendIORB to determine      */
/* whether all ACBs associated with a particular hardware  */
/* resource are IDLE.                                      */
/*                                                         */
/*---------------------------------------------------------*/

USHORT CheckACBsIdle( NPHWRESOURCE npHWR )
{
  NPACB         npACBX;
  USHORT        rc = 0;

  npACBX = npHWR->npFirstACBX;
  while ( npACBX )
  {
    if ( !(npACBX->Flags & ACBF_SM_IDLE) )
    {
      rc = 1;
      break;
    }
    npACBX = npACBX->npNextACBX;
  }

  return( rc );
}


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

USHORT NEAR ResumeIORB( NPACB npACB, PIORB pIORB )
{
  NPHWRESOURCE          npHWR;

  npHWR = &HWResource[npACB->HWResourceIndex];

  DISABLE

  if ( npHWR->Flags & HWRF_SUSPEND )
  {
    if ( !npHWR->pResumeHead )
    {
      npHWR->pResumeHead = pIORB;

      npACB->SwapReq |= ACBX_PENDING_RESUME;
      StartStateChange( npACB );
    }
    else
    {
      pIORB->ErrorCode = IOERR_CMD_SYNTAX;
    }
  }
  else
  {
      pIORB->ErrorCode = IOERR_CMD_SYNTAX;
  }

  ENABLE

  if ( pIORB->ErrorCode )
  {
    pIORB->Status = IORB_ERROR | IORB_DONE;

    if ( pIORB->RequestControl & IORB_ASYNC_POST )
    {
      ENABLE
      (*pIORB->NotifyAddress)( (PIORB) pIORB );
    }
  }
}

/*-------------------------------------------------------*/
/* AllocateHWResources                                   */
/*                                                       */
/* This routine controls the allocation of Hardware      */
/* Resources, i.e. DMA, IRQ. Between two controllers     */
/* which may share these resources.                      */
/*                                                       */
/* If we are in a 'shared' scenario, this routine        */
/* serializes the requestors of the shared resource.     */
/*                                                       */
/*-------------------------------------------------------*/

USHORT FAR  AllocateHWResources( NPACB npACB )
{
  NPHWRESOURCE  npHWR;
  USHORT        ResourceIndex;

  NPACB         npACBX;

  USHORT        rc = 0;
  USHORT        i;

  npHWR = &HWResource[npACB->HWResourceIndex];

  npACB->npNextHWR = 0;

  DISABLE

  /*------------------------------------------*/
  /* If we are SUSPENDED, then indicate that  */
  /* resources were not allocated to the      */
  /* requestor.                               */
  /*------------------------------------------*/
  if ( npHWR->Flags & HWRF_SUSPEND )
  {
    rc = 1;
  }
  /*------------------------------------------------*/
  /* If there is no current owner for the resources */
  /* make the requesting ACB the current owner      */
  /*------------------------------------------------*/
  else if ( !npHWR->npOwnerACB )
  {
    npHWR->npOwnerACB = npACB;
  }
  /*-----------------------------------------------*/
  /* If someone is requesting resources and they   */
  /* do not match the current owner, then they get */
  /* queued.                                       */
  /*-----------------------------------------------*/

  else if ( npHWR->npOwnerACB != npACB )
  {
    rc = 1;
  }


  /*---------------------------------------------*/
  /* If rc != 0 as determined by the logic above */
  /* the requestor must be blocked.              */
  /*                                             */
  /* The requesting ACB is placed on a queue for */
  /* the resource.                               */
  /*---------------------------------------------*/

  if ( rc )
  {
    if ( npHWR->npFootACB )
    {
      npHWR->npFootACB->npNextHWR = npACB;
    }
    else
    {
      npHWR->npHeadACB = npACB;
    }
    npHWR->npFootACB = npACB;

    npACB->Flags |= ACBF_OSM_WAITSTATE;

    rc = 1;
  }

  ENABLE

  /*------------------------------------------------------*/
  /* Fix for brain-dead hardware                          */
  /*                                                      */
  /* If two controllers share the same IRQ level in       */
  /* some cases one (or both) of the controllers hold     */
  /* the IRQ line so that the other controller cannot     */
  /* generate a level transition to cause an interrupt.   */
  /*                                                      */
  /* The floppy controller has a bit in the DOR register  */
  /* that effectively disconnects the controller from     */
  /* the bus. We use this if we are dealing with          */
  /* a shared controller configuration                    */
  /*------------------------------------------------------*/

  if ( !rc )
  {
    /*---------------------------------------------*/
    /* Turn on the controller for the current ACB  */
    /*---------------------------------------------*/
    SetControllerState( npACB, 1 );                                  /*@V95460*/

    /*-----------------------------------------------*/
    /* If we find anyone else with the same hardware */
    /* Resource ID, then turn off their controller.  */
    /*-----------------------------------------------*/

    npACBX = HWResource[npACB->HWResourceIndex].npFirstACBX;
    while ( npACBX )
    {
      if ( npACBX != npACB )
      {
        SetControllerState( npACBX, 0 );
      }
      npACBX = npACBX->npNextACBX;
    }


  }

  return ( rc );
}

/*--------------------------------------------------*/
/* FreeHWResources                                  */
/*                                                  */
/* This routine releases Hardware Resources owned   */
/* by an ACB.                                       */
/*                                                  */
/* If there are other ACBs waiting and an Immediate */
/* Suspend is not pending, then restart processing  */
/* of the queued requestor for the resources.       */
/*                                                  */
/*--------------------------------------------------*/

USHORT FAR  FreeHWResources( NPACB npACB )
{
  NPHWRESOURCE  npHWR;
  USHORT        rc;

  npHWR = &HWResource[npACB->HWResourceIndex];

  /*-----------------------------------------------------------*/
  /* If this ACB is not the primary owner of the controller    */
  /* resources, then take the controller off the bus           */
  /* when we are done.                                         */
  /*                                                           */
  /* This will help the BIOS to recover if the user reboots    */
  /* the system with the secondary controller selected.        */
  /*                                                           */
  /* We first enable to primary controller. This should prevent*/
  /* spurious interrupts since if we disabled all controllers  */
  /* the IRQ line would go from driven (-'ve) to floating or   */
  /* + depending on whether the system board provides pullups. */
  /*                                                           */
  /*-----------------------------------------------------------*/

  if ( npHWR->npOwnerACB != npHWR->npFirstACBX )
  {
    SetControllerState( npHWR->npFirstACBX, 1 );                     /*@V95460*/
    SetControllerState( npACB, 0 );
  }

  DISABLE

  npHWR->npOwnerACB = 0;

  if ( rc = ImmediateSuspendCheck( npACB ) )
  {
    if ( npHWR->npOwnerACB = npHWR->npHeadACB )
    {
      if ( !(npHWR->npHeadACB = npHWR->npOwnerACB->npNextHWR) )
      {
        npHWR->npFootACB = 0;
      }

      npHWR->npOwnerACB->npNextHWR = 0;

      ENABLE

      StartOSM( npHWR->npOwnerACB );
    }
  }
  ENABLE

  return( rc );
}


/*----------------------------------------------------*/
/* StartActivateACB                                   */
/*                                                    */
/* This routine schedules a request to the Context    */
/* Thread to Activate the ACB.                        */
/*                                                    */
/* If a previous request to IDLE the ACB is pending   */
/* it is cancelled.                                   */
/*                                                    */
/*----------------------------------------------------*/

USHORT FAR StartActivateACB( NPACB npACB )
{
  DISABLE

  /*----------------------------------------*/
  /* Cancel any pending IDLE request        */
  /*----------------------------------------*/
  npACB->SwapReq &= ~ACBX_PENDING_IDLE;
  npACB->SwapReq |=  ACBX_PENDING_ACTIVATE;
  StartStateChange( npACB );

  ENABLE
}



/*--------------------------------------------------*/
/* StartDeactivateACB                               */
/*                                                  */
/* This routine schedules a request to the Context  */
/* Thread to Deactivate the ACB.                    */
/*                                                  */
/*--------------------------------------------------*/

USHORT FAR StartDeactivateACB( NPACB npACB )
{
  DISABLE

  npACB->SwapReq |= ACBX_PENDING_IDLE;
  StartStateChange( npACB );

  ENABLE
}

/*----------------------------------------------------*/
/* StartSuspend                                       */
/*                                                    */
/* This routine schedules a request to the Context    */
/* Thread to Suspend the ACB.                         */
/*                                                    */
/* Suspend processing is similar to ACB Deactivation  */
/* except that the Suspend IORB is returned to        */
/* the submitter when the suspend completes.          */
/*                                                    */
/*----------------------------------------------------*/

USHORT FAR StartSuspend( NPACB npACB )
{
  npACB->SwapReq &= ~ACBX_PENDING_IDLE;
  npACB->SwapReq |=  ACBX_PENDING_SUSPEND;
  StartStateChange( npACB );

  ENABLE
}

/*---------------------------------------------------*/
/* StartBufferAlloc                                  */
/*                                                   */
/* This routine schedules a request to the Context   */
/* thread to allocate a DMA buffer to the ACB.       */
/*                                                   */
/*---------------------------------------------------*/

USHORT FAR StartBufferAlloc( NPACB npACB )
{
  DISABLE

  npACB->Flags   |= ACBF_SM_BUFWAIT;
  npACB->SwapReq |= ACBX_PENDING_GETBUF;
  StartStateChange( npACB );

  ENABLE
}


/*---------------------------------------------------------*/
/* StartStateChange                                        */
/*                                                         */
/* This routine ARM the schedules our Context Thread.      */
/*                                                         */
/* If the thread is scheduled but has not run, then we     */
/* do nothing.                                             */
/*                                                         */
/*---------------------------------------------------------*/

VOID FAR StartStateChange( NPACB npACB )
{
  if ( !(npACB->SwapReq & ACBX_PENDING_STATECHG) )
  {
    npACB->SwapReq |= ACBX_PENDING_STATECHG;
    ENABLE
    DevHelp_ArmCtxHook( (ULONG) npACB, npACB->StateHookHandle );
  }
}
/*---------------------------------------------------------*/
/* StateCtxHookRtn                                         */
/*                                                         */
/* This routine meets the interface the kernel presents    */
/* us with when our Context Thread starts.                 */
/*                                                         */
/* Unfortunately the kernel group has not realized yet     */
/* that we have been writting devices drivers in C         */
/* for the past two years and still leaves us with these   */
/* clunky assembler interfaces!                            */
/*                                                         */
/* In addition, the kernel is sensitive the the contents   */
/* of EBX, ESI, EDI and we must save the 32-bit regs.      */
/*                                                         */
/*                                                         */
/*---------------------------------------------------------*/

VOID FAR PASCAL StateCtxHookRtn( VOID )
{
  NPACB         npACB;

  _asm {  mov   npACB, ax

         _emit  66h
          push  si

         _emit  66h
          push  di

         _emit  66h
          push  bx
       };

  CompleteStateChange( npACB );

  _asm { _emit  66h
          pop   bx

         _emit  66h
          pop   di

         _emit  66h
          pop   si
       }
}



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

VOID FAR CompleteStateChange( NPACB npACB )
{
  PIORB         pIORB;
  NPACB        npACBX;
  NPUCB         npUCB;
  NPHWRESOURCE  npHWR;
  USHORT        SwapReq;

  DISABLE
  SwapReq         = npACB->SwapReq & ~ACBX_PENDING_STATECHG;
  npACB->SwapReq  = 0;
  ENABLE

  /*---------------------------------------------------------*/
  /* IDLE REQUEST                                            */
  /*                                                         */
  /*---------------------------------------------------------*/

  if ( SwapReq & ACBX_PENDING_IDLE )
  {
    DeactivateACB( npACB );

    SwapReq &= ~ACBX_PENDING_IDLE;

  }
  /*---------------------------------------------------------*/
  /* ACTIVATE REQUEST                                        */
  /*                                                         */
  /* Note: This request assumes that an IORB is currently    */
  /*       being processed, i.e. the requestor did the       */
  /*       NEXTIORB() and set the ACB_SM_* flags.            */
  /*---------------------------------------------------------*/
  if ( SwapReq & ACBX_PENDING_ACTIVATE )
  {
     if (ActivateACB( npACB ))                                       /*@V90990*/
     {                                                               /*@V90990*/
        DISABLE                                                      /*@V90990*/
        npACB->SwapReq |= SwapReq;                                   /*@V90990*/
        ENABLE                                                       /*@V90990*/

        ResourceRetry(npACB);                                        /*@V90990*/
        goto CompleteStateChangeEnd;                                 /*@V90990*/
     }

    SwapReq &= ~ACBX_PENDING_ACTIVATE;

    StartOSM( npACB );

  }
  /*---------------------------------------------------------*/
  /* GET BUFFER REQUEST                                      */
  /*                                                         */
  /* Note: This request assumes that an IORB is currently    */
  /*       being processed, i.e. the requestor did the       */
  /*       NEXTIORB() and set the ACB_SM_* flags.            */
  /*---------------------------------------------------------*/
  if ( SwapReq & ACBX_PENDING_GETBUF )
  {
     if (GetDMABuffer( npACB ))                                      /*@V90990*/
     {                                                               /*@V90990*/
        DISABLE                                                      /*@V90990*/
        npACB->SwapReq |= SwapReq;                                   /*@V90990*/
        ENABLE                                                       /*@V90990*/

        ResourceRetry(npACB);                                        /*@V90990*/
        goto CompleteStateChangeEnd;                                 /*@V90990*/
     }

    SwapReq  &= ~ACBX_PENDING_GETBUF;
    npACB->Flags    &= ~ACBF_SM_BUFWAIT;

    StartOSM ( npACB );
  }
  /*---------------------------------------------------------*/
  /* SUSPEND REQUEST                                         */
  /*                                                         */
  /*---------------------------------------------------------*/
  if ( SwapReq & ACBX_PENDING_SUSPEND )
  {
    npHWR  = &HWResource[npACB->HWResourceIndex];
    npACBX = npHWR->npFirstACBX;

    while( npACBX )
    {
      DeactivateACB( npACBX );
      if ( npACBX != npACB )                       // @V90024
      // Leave requested floppy controller on line.
      {
        SetControllerState( npACBX, 0 );
      }
      npACBX = npACBX->npNextACBX;
    }


    CompleteSuspend( npHWR );

    SwapReq &= ~ACBX_PENDING_SUSPEND;
  }
  /*---------------------------------------------------------*/
  /* RESUME REQUEST                                          */
  /*                                                         */
  /*---------------------------------------------------------*/
  if ( SwapReq & ACBX_PENDING_RESUME )
  {
    /*-------------------------------------------*/
    /* Set pending RESET and UNCERTAIN MEDIA     */
    /* on all units related to the suspend       */
    /* operation.                                */
    /*-------------------------------------------*/
    npHWR  = &HWResource[npACB->HWResourceIndex];

    npACBX = npHWR->npFirstACBX;                                     /*@V95460*/
    while ( npACBX )                                                 /*@V95460*/
    {
      npUCB = npACBX->npFirstUCB;                                    /*@V95460*/
      while ( npUCB )
      {
        npUCB->ReqFlags |= (UCBR_RESET | UCBR_FORCE_SELECT);
        npUCB->Flags    |= UCBF_MEDIA_UNCERTAIN;
        npUCB = npUCB->npNextUCB;
      }
      npACBX = npACBX->npNextACBX;                                   /*@V95460*/
    }                                                                /*@V95460*/

    /*-------------------------------------------*/
    /* If there is work to do. ACTIVATE the ADD  */
    /* code and start the state machine          */
    /*-------------------------------------------*/
    npACBX = npHWR->npFirstACBX;

    DISABLE
    while ( npACBX )
    {
      if ( npACBX->pIORB || !NextIORB( npACBX ) )
      {
        npACBX->Flags |=  ACBF_SM_ACTIVE;
        npACBX->Flags &= ~ACBF_SM_IDLE;


        ENABLE

        if (ActivateACB( npACBX ))                                   /*@V90990*/
           {                                                         /*@V90990*/
              ResourceRetry(npACBX);                                 /*@V90990*/
              goto CompleteStateChangeEnd;                           /*@V90990*/
           }                                                         /*@V90990*/

        npHWR->Flags &= ~HWRF_SUSPEND;                               /*@V90990*/

        StartOSM( npACBX );
      }
      /*------------------------------------------------*/
      /* The ADD was RESUMED but has nothing to process.*/
      /*------------------------------------------------*/
      else
      {
        npACBX->Flags |= ACBF_SM_IDLE;
        ENABLE

      }
      npACBX = npACBX->npNextACBX;
      DISABLE
    }

    ENABLE
    /*----------------------------------------*/
    /* If all the RESUMEd ACBs were found     */
    /* to be idle, we process any additional  */
    /* SUSPEND IORBs.                         */
    /*----------------------------------------*/
    if ( npHWR->Flags & HWRF_SUSPEND )
    {
      if ( CompleteSuspend( npHWR ) )
      {
        npHWR->Flags &= ~HWRF_SUSPEND;
      }
    }

    /*-----------------------------------*/
    /* Remove and Notify the Resume IORB */
    /*-----------------------------------*/
    pIORB              = npHWR->pResumeHead;
    npHWR->pResumeHead = 0;

    pIORB->Status = IORB_DONE;
    if (pIORB->RequestControl & IORB_ASYNC_POST )
    {
      (*pIORB->NotifyAddress)( (PIORB) pIORB );
    }

    SwapReq  &= ~ACBX_PENDING_RESUME;
  }
CompleteStateChangeEnd:
  return;
}

/*---------------------------------------------------------*/
/* RETRYTimerHandler                                       */
/*                                                         */
/* This routine handles the timeouts possible when VMLock  */
/* or VMAlloc fail in ActivateACB and GetDMABuffer.        */
/*                                                         */
/* This is part of the fix for @                           */
/*---------------------------------------------------------*/

VOID FAR RETRYTimerHandler( ULONG TimerHandle, PACB pACB, ULONG Parm2)
{
   NPACB npACB;

   DISABLE
   npACB=(NPACB)OFFSETOF(pACB);

   if (TimerHandle != npACB->RETRYTimerHandle)
      _asm int 3

   npACB->RETRYTimerHandle = 0;

   f_ADD_CancelTimer(TimerHandle);

   ENABLE

   StartStateChange(npACB);
}

/*---------------------------------------------------------*/
/* ResourceRetry                                           */
/*                                                         */
/* This routine sets up a timer to schedule the retries    */
/* when VMLock or VMAlloc fail in ActivateACB or           */
/* GetDMABuffer.                                           */
/*                                                         */
/* This is part of the fix for @V90990                     */
/*---------------------------------------------------------*/

VOID FAR ResourceRetry(NPACB npACB)
{
  if (f_ADD_StartTimerMS((PULONG) &npACB->RETRYTimerHandle,
                         (ULONG)  RETRY_TIMEOUT,
                         (PFN)    RETRYTimerHandler,
                         (PVOID)  npACB,
                         (ULONG)  0                         ))
      _asm int 3;
}

/*--------------------------------------------------*/
/* ActivateACB                                      */
/*                                                  */
/* If our swappable code is not locked into memory  */
/* then issue the VMLOCK call. Otherwise the        */
/* code lock count is incremented.                  */
/*                                                  */
/* Similar considerations of the IRQ level.         */
/*                                                  */
/* This code was changed extensively to fix defect  */
/* @V90990.  It now returns an error if it can't    */
/* lock the swappable code or hook the interrupt.   */
/* The appropriate counters are not incremented if  */
/* the dev_help calls fail.  It now also returns 0  */
/* for success and a non zero value for failure.    */
/*--------------------------------------------------*/

USHORT FAR ActivateACB( NPACB npACB )
{
  ULONG         PageListCount;
  USHORT        rc = 0;
  USHORT        did_lock = 0;                               /* @V181288 */

  DISABLE
  if ( !(npACB->Flags & ACBF_SM_READY) )
  {
    ENABLE
    if ( !ADDLockCount )
    {
      if (rc = DevHelp_VMLock( (ULONG)  VMDHL_LONG,         /* assign and test rc */
                           (LIN)    plSwapCode,
                           (ULONG)  SwapCodeLen,
                           (LIN)    -1L,
                           (LIN)    plADDLockHandle,
                           (PULONG) &PageListCount  ))               /*@V90990*/
      {
        goto ActivateACBEnd;
      }

      ADDLockCount++;
      did_lock = 1;                                         /* @V181288 */
    }

    if ( !IRQUseCount[npACB->IRQLevel] )
    {
      if (rc = DevHelp_SetIRQ( (NPFN)   npACB->IRQEntry,    /* assign and test rc */
                           (USHORT) npACB->IRQLevel,
                           (USHORT) 0                ))
      {
         if ( did_lock )                                    /* @V181288 */
         {                                                  /* @V181288 */
            ADDLockCount--;                                 /* @V181288 */
            DevHelp_VMUnLock( (LIN)plADDLockHandle );       /* @V181288 */
            goto ActivateACBEnd;                            /* rc != 0  */
         }                                                  /* @V181288 */
      }
    }
    IRQUseCount[npACB->IRQLevel]++;
  }

  npACB->Flags |= ACBF_SM_READY;

  /*--------------------------------*/                          /* @V143950 */
  /* If APMFlag is set, Attach to   */                          /* @V143950 */
  /* the APM driver.                */                          /* @V143950 */
  /*--------------------------------*/                          /* @V143950 */
                                                                /* @V143950 */
  if (APMFlag && !APMAttached)                                  /* @V143950 */
  {                                                             /* @V143950 */
    if (DevHelp_AttachDD((NPSZ) APMName,(NPBYTE) &IDCTable))    /* @V143950 */
      APMAttached = FALSE;          /* unsuccessful Attach */   /* @V143950 */
    else                                                        /* @V143950 */
    {                                                           /* @V143950 */
      APMAttached = TRUE;                                       /* @V143950 */
       APMAdd = (PFN) IDCTable.ProtIDCEntry;                    /* @V143950 */
    }                                                           /* @V143950 */
  }                                                             /* @V143950 */
                                                                /* @V143950 */
  DISABLE                                                       /* @V143950 */
  if (APMAttached && !APMDisabled)                              /* @V143950 */
  {                                                             /* @V143950 */
    APMSendEventPkt.SubId    = APMEVENT_DisableAPM;             /* @V143950 */
    APMSendEventPkt.Function = APMIDC_SendEvent;                /* @V143950 */
    APMSendEventPkt.reserved = 0;                               /* @V143950 */
    APMSendEventPkt.DevId    = 0;                               /* @V143950 */
    APMSendEventPkt.PwrState = 0;                               /* @V143950 */
    APMSendPacket (&APMSendEventPkt);                           /* @V143950 */
    APMDisabled++;                                              /* @V143950 */
    IODelay();                                                  /* @V147381 */
    IODelay();                                                  /* @V147381 */
  }                                                             /* @V143950 */

ActivateACBEnd:

  ENABLE
  return ( rc );
}

/*---------------------------------------------------------*/
/* DeactivateACB                                           */
/*                                                         */
/* If this ACB is currenly marked SM_READY, we deactivate  */
/* it by releasing its DMA buffer (if any) and decrementing*/
/* its Code and IRQ use counts.                            */
/*                                                         */
/* If the Code or IRQ use counts go to zero, the underlying*/
/* resources are unlocked/released.                        */
/*                                                         */
/*---------------------------------------------------------*/

VOID FAR DeactivateACB( NPACB npACB )
{
  DISABLE
  if ( npACB->Flags & ACBF_SM_READY )
  {
    npACB->Flags &= ~ACBF_SM_READY;

    ENABLE

    TurnMotorsOff( npACB );


    if ( npACB->DMABuf )
    {
      if ( DevHelp_VMFree( (LIN) npACB->DMABufLin ) )
      {
        _asm int 3
      }

      npACB->DMABufLin = 0;
      npACB->DMABuf    = 0;

    }

    if ( !--IRQUseCount[npACB->IRQLevel] )
    {
      DevHelp_UnSetIRQ( (USHORT) npACB->IRQLevel );
    }

    if ( !--ADDLockCount )
    {
      if ( DevHelp_VMUnLock( (LIN) plADDLockHandle ) )
      {
        _asm int 3
      }
    }

    /*
    **  Now re-enable the APM if it was disabled - @V143950
    */
                                                                  /* @V143950 */
    DISABLE                                                       /* @V143950 */
    if (APMAttached && APMDisabled)                               /* @V143950 */
    {                                                             /* @V143950 */
      APMSendEventPkt.SubId    = APMEVENT_EnableAPM;              /* @V143950 */
      APMSendEventPkt.Function = APMIDC_SendEvent;                /* @V143950 */
      APMSendEventPkt.reserved = 0;                               /* @V143950 */
      APMSendEventPkt.DevId    = 0;                               /* @V143950 */
      APMSendEventPkt.PwrState = 0;                               /* @V143950 */
      APMSendPacket (&APMSendEventPkt);                           /* @V143950 */
      APMDisabled--;                                              /* @V143950 */
    }                                                             /* @V143950 */
  }
  ENABLE
}

/*------------------------------------------------------*/
/* GetDMABuffer                                         */
/*                                                      */
/* This routine allocates the DMA buffer needed for     */
/* the requesting ACB. It insures that the allocated    */
/* buffer meets ISA DMA constraints.                    */
/*                                                      */
/* This code was changed to fix defect @     .          */
/* It now allows for a failure of VMAlloc.  If VMAlloc  */
/* fails it continues trying, until it's main loop is   */
/* finished, then exits with an error code              */
/*------------------------------------------------------*/

USHORT FAR GetDMABuffer( NPACB npACB )                               /*@V90990*/
{
  PVOID         SelOffset;
  LIN           plPhysAddr;

  LIN           LinBuf;
  LIN           LinBufBase;
  ULONG         PhysBuf;

  USHORT        PhysLow;
  USHORT        PhysHigh;

  ULONG         BufSize;

  USHORT        i;
  USHORT        rc = 0;                                              /*@V90990*/

  plPhysAddr = plDataSeg + (USHORT) &npACB->DMABuf;

  BufSize = (npACB->DMABufLen + C4KB - 1) & ~(C4KB-1);

  /*--------------------------------------------------------*/
  /* Allocate a DMA Buffer.                                 */
  /*                                                        */
  /* If the buffer crosses a 64KB physical address boundary */
  /* then evaluate the buffer sizes on either size of       */
  /* the 64KB line. If neither buffer is large enough then  */
  /* release the current buffer, increase the request size  */
  /* by 4KB and try again.                                  */
  /*--------------------------------------------------------*/

  for ( i=0; i < MAX_BUFFER_RETRY; i++ )
  {                                                                  /*@V90990*/
    rc = DevHelp_VMAlloc( (ULONG)  VMDHA_16M | VMDHA_FIXED | VMDHA_CONTIG,
                          (ULONG)  BufSize,
                          (LIN)    plPhysAddr,
                          (PLIN)   &LinBufBase,
                          (PPVOID) &SelOffset );

    if (!rc)                                                         /*@V90990*/
    {
      PhysBuf = npACB->DMABuf;
      LinBuf  = LinBufBase;

      /*----------------------------------------------*/
      /* PhysLow is the distance from the base of the */
      /* buffer to the 64KB line.                     */
      /*                                              */
      /* If the buffer actually crosses the 64KB line */
      /* PhysHigh is the amount above 64KB. PhysHigh  */
      /* is only valid when a buffer crosses 64KB     */
      /*----------------------------------------------*/

      PhysLow  =   - (USHORT) PhysBuf;
      PhysHigh =   BufSize - PhysLow;

      if ( PhysLow >= npACB->DMABufLen )
         break;
      else if ( PhysHigh >= npACB->DMABufLen )
      {
        /*-----------------------------------------------*/
        /* Adjust buffer pointers to get rid of unusable */
        /* portion below 64KB                            */
        /*-----------------------------------------------*/

        PhysBuf   += PhysLow;
        LinBuf    += PhysLow;
        break;
      }

      if ( DevHelp_VMFree( (LIN) LinBufBase ) )
         _asm int 3

      BufSize += C4KB;
    }                                                                /*@V90990*/
  }                                                                  /*@V90990*/

  if ( i != MAX_BUFFER_RETRY )
  {
    npACB->DMABufLin = LinBufBase;
    npACB->DMABuf    = PhysBuf;

    if ( DevHelp_LinToGDTSelector( (SEL)   SELECTOROF(npACB->pDMABuf),
                                   (LIN)   LinBuf,
                                   (ULONG) npACB->DMABufLen          ) )
      _asm int 3
  }
  else
    rc = 1;                                                          /*@V90990*/

  return (rc);
}

/*-------------------------------------------------------------*/
/* CompleteSuspend                                             */
/*                                                             */
/* This removes a completed Suspend IORB from the HWResource   */
/* structure and returns it to the submitter.                  */
/*                                                             */
/*-------------------------------------------------------------*/

USHORT NEAR CompleteSuspend( NPHWRESOURCE npHWR )
{
  PIORB         pIORB;
  USHORT        rc = 1;

  if ( npHWR->pSuspendHead )
  {
    /*----------------------------------------*/
    /* Remove and Notify the IORB at the head */
    /* of the ACB Suspend Q                   */
    /*----------------------------------------*/
    pIORB = npHWR->pSuspendHead;
    if ( !(npHWR->pSuspendHead = pIORB->pNxtIORB) )
    {
      npHWR->pSuspendFoot = 0;
    }
    pIORB->ErrorCode = 0;
    pIORB->Status    = IORB_DONE;

    if (pIORB->RequestControl & IORB_ASYNC_POST )
    {
      (*pIORB->NotifyAddress)( (PIORB) pIORB );
    }
    rc = 0;
  }
  return( rc );
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/*  Drive Motor Control Routines                                          */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------------------------*/
/* MotorIdleCheck                                       */
/*                                                      */
/* This routine maintains the Motor countdown fields    */
/* the UCBs. When a countdown transitions to zero, the  */
/* corresponding drive motor is turned off.             */
/*                                                      */
/* If all drive motors associated with a controller are */
/* off then the corresponding ACB is Deactivated.       */
/*                                                      */
/*------------------------------------------------------*/

VOID FAR MotorIdleCheck(ULONG TimerHandle, ULONG Parm1, ULONG Parm2 )
{
  NPACB         npACB;
  NPUCB         npUCB;

  USHORT        Port;
  UCHAR         Value;

  USHORT        MotorsIdle;

  USHORT        i;

  MotorsIdle = 1;

  for ( i=0; i < cAdapters; i++ )
  {
    if( !(npACB = ACBPtrs[i].npACB) )
    {
      continue;
    }

    npUCB = npACB->npFirstUCB;

    while( npUCB )
    {

      /*----------------------------------------------*/
      /* MotorOffCount                                */
      /*      -1  - Active request on drive           */
      /*       0  - Drive Motor is OFF                */
      /*     > 0  - Drive Motor is ON but drive is    */
      /*            idle.                             */
      /*----------------------------------------------*/

      DISABLE
      if ( (SHORT) npUCB->MotorOffCount > 0 )
      {
        if ( !(--npUCB->MotorOffCount) )
        {
          /*---------------------------------------*/
          /* MotorOffCount has transitioned to 0.  */
          /* Turn off the drive motor              */
          /*---------------------------------------*/

          Port   = npACB->IOPorts[FCR_DOR];

          Value  = npACB->IORegs[FCR_DOR];
          Value &= ~(FCR_DOR_MOTOR_0 << npUCB->UnitId);

          npACB->IORegs[FCR_DOR] = Value;

          outp( Port, Value );
          IODelay();

          npUCB->Flags &= ~(UCBF_MOTOR_OK | UCBF_MOTOR_ON | UCBF_MOTOR_ON_PREV);
        }
      }

      if ( npUCB->MotorOffCount )
      {
        MotorsIdle = 0;
      }

      ENABLE
      npUCB = npUCB->npNextUCB;
    }

    /*-----------------------------------------------*/
    /* If all drive motors are OFF for this ACB and  */
    /* the ACB is not in a resource wait, then       */
    /* Deactivate the ACB.                           */
    /*-----------------------------------------------*/
    DISABLE
    if ( InitComplete
          && MotorsIdle
            && !(npACB->Flags & ACBF_SM_ACTIVE)
                && (npACB->Flags & ACBF_SM_READY) )
    {
      npACB->Flags |= ACBF_SM_IDLE;

      if ( DeferredSuspendCheck( npACB ) )
      {
        StartDeactivateACB( npACB );
      }
    }
    ENABLE
  }
}

/*---------------------------------------------------------*/
/* TurnMotorsOff                                           */
/*                                                         */
/* This routine the motors for all drives on the selected  */
/* ACB off.                                                */
/*                                                         */
/* This is useful when an Immediate Suspend is processed   */
/* and we dont want to hand the controller off with the    */
/* motors still running.                                   */
/*                                                         */
/*---------------------------------------------------------*/

VOID FAR TurnMotorsOff( NPACB npACB )
{
  USHORT        Port;
  UCHAR         Value;

  NPUCB         npUCB;

  npUCB = npACB->npFirstUCB;

  while( npUCB )
  {
    DISABLE
    if ( npUCB->Flags & UCBF_MOTOR_ON )
    {
      Port   = npACB->IOPorts[FCR_DOR];

      Value  = npACB->IORegs[FCR_DOR];
      Value &= ~(FCR_DOR_MOTOR_0 << npUCB->UnitId);

      npACB->IORegs[FCR_DOR] = Value;                                /*@V95460*/

      outp( Port, Value );
      IODelay();

      npUCB->Flags &= ~(UCBF_MOTOR_OK | UCBF_MOTOR_ON | UCBF_MOTOR_ON_PREV);
      npUCB->MotorOffCount = 0;
    }
    ENABLE
    npUCB = npUCB->npNextUCB;
  }
}

/*-----------------------------------------------------*/
/* SetControllerState                                  */
/*                                                     */
/* This routine manipulates the DOR register DMA_GATE  */
/* bit allowing us to connect/disconnect a controller  */
/* from the IRQ/DMA lines.                             */
/*                                                     */
/*-----------------------------------------------------*/

VOID NEAR SetControllerState( NPACB npACB, USHORT Enable )
{
  USHORT        Port;
  USHORT        Value;

  DISABLE

  if ( !Enable )
  {
    npACB->IORegs[FCR_DOR] &= ~FCR_DOR_DMA_GATE;
  }
  else
  {
    npACB->IORegs[FCR_DOR] |= FCR_DOR_DMA_GATE;
  }

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

  ENABLE
}
