/*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 = FLP1OSM1.C
 *
 * DESCRIPTIVE NAME = Outer State Machine - Module 1
 *
 *
 *
 * VERSION = V2.1
 *
 * DATE = 94/03/14
 *
 * DESCRIPTION :
 *
 * Purpose:  Functions are implemented this OSM Module:
 *
 *               FLP1OSM1.C - IORB Queuing/Routing
 *                            IORB Start/Completion
 *                            IORB processing for
 *                              - IOCC_CONFIGURATION
 *                              - IOCC_UNIT_STATUS
 *                              - IOCC_UNIT_CONTROL
 *                              - IOCC_GEOMETRY
 *                            Media Determination logic
 *
 *
 *
*/
#define INCL_NOBASEAPI
#define INCL_NOPMAPI
#include "os2.h"
#include "dos.h"

#include "dskinit.h"
#include "devcmd.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"


/*------------------------------------------------------------------------*/
/* OS/2 Strategy Request Router                                           */
/*                                                                        */
/* This routine receives the OS/2 Initialization Request Packet. Any      */
/* other request types are rejected.                                      */
/*------------------------------------------------------------------------*/

VOID NEAR FLP1Str1()
{
  PRPH pRPH;                   /* Pointer to RPH (Request Packet Header)      */
  USHORT        Cmd;           /* Local variable                              */

  _asm
  {
    mov word ptr pRPH[0], bx       /*  pRPH is initialize to                  */
    mov word ptr pRPH[2], es       /*  ES:BX passed from the kernel           */
  }

  pRPH->Status = STATUS_DONE;
  Cmd = pRPH->Cmd;

  if ( Cmd == CMDInitBase )
  {
    DriveInit( (PRPINITIN) pRPH );
  }
  else
  {
    StatusError( pRPH, STATUS_DONE | STATUS_ERR_UNKCMD );
  }

  _asm
  {
    leave
    retf
  }

 }

VOID FAR StatusError(PRPH pRPH, USHORT ErrorCode )
{
  pRPH->Status = ErrorCode;
  return;
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* IORB Queuing / IORB Preprocessing                                      */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------*/
/* ADDEntryPoint                */
/* -------------                */
/*                              */
/* ADD IORB command entry.      */
/*                              */
/*------------------------------*/


VOID FAR  _loadds FLP1ADDEntry( PIORBH pIORB )
{
  NPHWRESOURCE  npHWR;

  PIORB         pFirstIORB;
  PIORBH        pLastIORB;
  NPACB         npACB;
  NPUCB         npUCB;
  USHORT        i;

  pFirstIORB = pIORB;

  /*------------------------------------------*/
  /* Queue IORB commands which dont address a */
  /* specific unit to ACB 0 Unit 0            */
  /*------------------------------------------*/

  if (pFirstIORB->CommandCode == IOCC_CONFIGURATION )
  {
    for ( i=0; i < MAX_ADAPTERS; i++ )
    {
      if ( ACBPtrs[i].npACB )
      {
        pFirstIORB->UnitHandle = (USHORT) (npUCB = ACBPtrs[i].npACB->npFirstUCB);
        break;
      }
    }
  }
  else
  {
    npUCB = (NPUCB) pFirstIORB->UnitHandle;
  }

  npACB = npUCB->npACB;

  if ( !(pLastIORB = PreProcessIORBs( npACB, &pFirstIORB )) )
  {
    goto ADDEntryExit;
  }

  DISABLE

  /*-----------------------------------*/
  /* Add new IORBs to end of the ACB Q */
  /*-----------------------------------*/
  if (!npACB->pHeadIORB)
  {
    npACB->pHeadIORB = pFirstIORB;
  }
  else
  {
    npACB->pFootIORB->pNxtIORB = pFirstIORB;
  }

  /*--------------------------------------*/
  /* Point to the last IORB on the ACB Q  */
  /*--------------------------------------*/
  npACB->pFootIORB = pLastIORB;

  npHWR = &HWResource[npACB->HWResourceIndex];

  DISABLE
  if ( !(npHWR->Flags & HWRF_SUSPEND)
         && !(npACB->Flags & ACBF_SM_ACTIVE) )
  {
    npACB->Flags |=  ACBF_SM_ACTIVE;
    npACB->Flags &= ~ACBF_SM_IDLE;

    NextIORB( npACB );
    ENABLE

    npACB->OState = ACBO_START_IORB;

    if ( !ActivateACBCheck( npACB ) )
    {
      StartOSM( npACB );
    }
  }

  ADDEntryExit: ;

  ENABLE
}

/*-------------------------------------------------------*/
/* PreProcessIORBs                                       */
/*                                                       */
/* This filters incomming IORBs as they are passed       */
/* to the ADD driver.                                    */
/*                                                       */
/* Certain IORBs such as SUSPEND/RESUME are removed      */
/* from the IORB stream and routed directly to their     */
/* handling routines.                                    */
/*                                                       */
/* The remaining IORBs are placed on the device queue.   */
/*                                                       */
/* In the case of EXECUTE_IO and FORMAT some preliminary */
/* initizliation of the IORB WorkSpace takes place.      */
/*                                                       */
/* Chained IORBs are evaluated to see if they can        */
/* be marked FASTPATH which reduces the amount of        */
/* reinitizlization needed in the State Machine between  */
/* IORBs.                                                */
/*                                                       */
/*-------------------------------------------------------*/

PIORB NEAR PreProcessIORBs( NPACB npACB, PPIORB ppFirstIORB )
{
  PIORB         pIORB, pIORBPrev, pIORBNext;
  PADDWSP       pWSP;
  PCHS_ADDR     pCHS;
  USHORT        FastPath    = 1;
  USHORT        BufRequired = 0;
  USHORT        CurCyl;


  pIORB     = *ppFirstIORB;
  pIORBPrev = 0;

  do
  {
    pIORBNext = (pIORB->RequestControl & IORB_CHAIN) ? pIORB->pNxtIORB : 0;

    pWSP        = (PADDWSP) pIORB->ADDWorkSpace;
    memset( (PBYTE) pWSP, 0, sizeof(ADDWSP) );

    switch( pIORB->CommandCode )
    {
      case IOCC_CONFIGURATION:
        pWSP->Flags |= WSF_SYNC_ACTIVATE;
        FastPath     = 0;
        break;

      case IOCC_FORMAT:
        pWSP->Flags |= WSF_DATA_TO_DSKT;
        FastPath     = 0;
        break;

      case IOCC_DEVICE_CONTROL:
        switch ( pIORB->CommandModifier )
        {
          case IOCM_SUSPEND:
            pIORBNext = RemoveIORB( pIORB, pIORBPrev, ppFirstIORB );
            SuspendIORB( npACB, pIORB );

            continue;

          case IOCM_RESUME:
            pIORBNext = RemoveIORB( pIORB, pIORBPrev, ppFirstIORB );
            ResumeIORB( npACB, pIORB );

            continue;
        }
        break;

      case IOCC_EXECUTE_IO:
        pWSP->N = BlkSizeToN( ((PIORB_EXECUTEIO)pIORB)->BlockSize );

        switch ( pIORB->CommandModifier )
        {
          case IOCM_READ:
            pWSP->Flags |= WSF_DATA_TO_HOST;
            break;
          case IOCM_WRITE:
          case IOCM_WRITE_VERIFY:
            pWSP->Flags |= WSF_DATA_TO_DSKT;
        }

        if ( pIORB->RequestControl & IORB_CHS_ADDRESSING )
        {
          pCHS = (PCHS_ADDR) &((PIORB_EXECUTEIO)pIORB)->RBA;

          if ( FastPath && pIORB == *ppFirstIORB )
          {
            CurCyl = pCHS->Cylinder;
          }
          else if ( FastPath && pCHS->Cylinder == CurCyl )
          {
            pWSP->Flags |= WSF_FAST_PATH;
          }
          else
          {
            FastPath = 0;
          }
        }
        else
        {
          FastPath = 0;
        }
        break;

      default:
        FastPath = 0;
    }

    if ( pWSP->Flags & (OPF_DATA_TO_DSKT | OPF_DATA_TO_HOST) )
    {
      if ( CheckBufRequired( (PIORB_EXECUTEIO) pIORB ) )
      {
        pWSP->Flags |= WSF_DMA_BUF_REQD;
        BufRequired =  1;
      }
    }

    pIORBPrev = pIORB;
  }
  while ( pIORB = pIORBNext );


  if ( pIORBPrev )
  {
    if ( FastPath && BufRequired )
    {
      ((PADDWSP) ((*ppFirstIORB)->ADDWorkSpace))->Flags |= WSF_DMA_BUF_REQD;
    }

    pIORBPrev->pNxtIORB = 0;
  }

  return ( pIORBPrev );
}

/*------------------------------------*/
/* RemoveIORB                         */
/*                                    */
/* This routine removes an IOBR       */
/* we decided not to place on the     */
/* Device queue, and repairs the IORB */
/* chain if necessary.                */
/*                                    */
/*------------------------------------*/

VOID NEAR RemoveIORB( PIORB pIORB, PIORB pIORBPrev, PPIORB pIORBFirst )
{
  PIORB         pIORBNext;

  pIORBNext = (pIORB->RequestControl & IORB_CHAIN) ? pIORB->pNxtIORB : 0;

  if ( pIORBPrev )
  {
    pIORBPrev->pNxtIORB = pIORBNext;
  }
  else
  {
    *pIORBFirst = pIORBNext;
  }

  return( pIORBNext );
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* IORB Queuing / IORB Preprocessing                                      */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*----------------------------------------------------------------*/
/* StartOSM                                                       */
/*                                                                */
/* This is the main state handling for the Outer State Machine    */
/*                                                                */
/* The following are the major states:                            */
/*                                                                */
/*                                                                */
/* ACBO_START_IORB                                                */
/*                                                                */
/*   The state machine should start processing on                 */
/*   the IORB pointed to by the pIORB field of the                */
/*   ACB. This DOES NOT cause an IORB to be pulled                */
/*   from the waiting queue, it only start processing             */
/*   on the current IORB.                                         */
/*                                                                */
/*   If conditions are not acceptable for processing              */
/*   an IORB, i.e. we dont have a DMA buffer, or must             */
/*   wait for a controller resource then we stop processing       */
/*   and remain in this state.                                    */
/*                                                                */
/*   In these case when the condition causing the blockage        */
/*   is eliminated we simply call StartOSM again to               */
/*   resume processing.                                           */
/*                                                                */
/*                                                                */
/* ACBO_ISM_COMPLETE                                              */
/*                                                                */
/*   This state is entered when we are waiting on the             */
/*   Inner State Machine to finish an I/O request on              */
/*   behalf of the Outer State machine.                           */
/*                                                                */
/*   In this case the OSM has set a NotifyAddress and             */
/*   the the StartOSM calls this address when the                 */
/*   Inner State Machine completes its processing.                */
/*                                                                */
/*                                                                */
/* ACBO_DETERMINE_MEDIA                                           */
/*                                                                */
/*   An IORB was started however, the IORB processing             */
/*   logic required that the type of media in the                 */
/*   drive be recognized before processing could                  */
/*   continue.                                                    */
/*                                                                */
/*   The IORB processing sets this state to initiate              */
/*   media determination. Once the media has been                 */
/*   successfully determined, the Media Determination             */
/*   state restarts processing on the current IORB                */
/*   by setting state ACBO_START_IORB.                            */
/*                                                                */
/*                                                                */
/* ACBO_COMPLETE_IORB                                             */
/*                                                                */
/*   The current IORB should be completed. An error               */
/*   analysis routine is called to derive the                     */
/*   appropriate IORB ErrorCode.                                  */
/*                                                                */
/*   The current IORB is returned to the external                 */
/*   requestor via the NotifyAddress and the                      */
/*   next IORB on the device queue (if any) is                    */
/*   obtained.                                                    */
/*                                                                */
/*----------------------------------------------------------------*/

VOID FAR StartOSM( NPACB npACB )
{
  npACB->OSMUseCount++;

  if ( npACB->OSMUseCount == 1 )
  {
    do
    {
      ENABLE
      do
      {
        npACB->Flags &= ~ACBF_OSM_WAITSTATE;

        switch ( npACB->OState )
        {
          case ACBO_START_IORB:
            StartIORB( npACB );
            break;

          case ACBO_ISM_COMPLETE:
            (*npACB->OSMNotifyRtn)( npACB );
            break;


          case ACBO_DETERMINE_MEDIA:
            StartMediaDetermine( npACB );
            break;

          case ACBO_COMPLETE_IORB:
            CompleteIORB( npACB );
            break;
        }
      }
      while ( !(npACB->Flags & ACBF_OSM_WAITSTATE) );

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

/*-----------------------------------------------*/
/* CompleteIORB                                  */
/*                                               */
/* This routine calculates the IORB ErrorCode    */
/* and calls the IORB NotifyAddress.             */
/*                                               */
/* This routine also trys to get the next        */
/* IORB from the device queue and returns        */
/* to state ACBO_START_IORB.                     */
/*                                               */
/*-----------------------------------------------*/

VOID NEAR CompleteIORB( NPACB npACB )
{
  PIORB         pIORB;
  USHORT        ErrorCode = 0;
  NPUCB         npUCB;

  pIORB = npACB->pIORB;
  npUCB = npACB->npUCB;

  if ( npACB->IORBError )
  {
    ErrorCode = npACB->IORBError;
  }
  else if ( npACB->TimerFlags )
  {
    ErrorCode = IOERR_UNIT_NOT_READY;
  }
  else if ( npACB->CurStatus != REQS_NO_ERROR )
  {
    switch ( npACB->CurStatus )
    {
      case REQS_NOT_READY:
        ErrorCode = IOERR_MEDIA_NOT_PRESENT;
        break;
      case REQS_MEDIA_CHANGED:
        ErrorCode = IOERR_MEDIA_CHANGED;
        break;
      case REQS_INVALID_GEOMETRY:
        ErrorCode = IOERR_MEDIA_NOT_SUPPORTED;
        break;
      case REQS_MEDIA_UNFORMATTED:
        ErrorCode = IOERR_MEDIA_NOT_FORMATTED;
        break;
      case REQS_COMMAND_FAIL:
        ErrorCode = ResultsToIORBError( npACB );
        break;
      default:
        ErrorCode = IOERR_DEVICE_NONSPECIFIC;
    }
  }

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

  pIORB->Status |= IORB_DONE;

  npACB->pIORB = 0;

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

  npUCB->MotorOffCount = MOTOR_OFF_COUNT;

  npACB->OState        = ACBO_START_IORB;

  if ( FreeHWResources( npACB ) )
  {
    if ( NextIORB( npACB ) )
    {
      npACB->Flags &= ~ACBF_SM_ACTIVE;
      npACB->Flags |= ACBF_OSM_WAITSTATE;
      ENABLE
    }
  }
}


/*----------------------------------------------*/
/* ActivateACBCheck                             */
/*                                              */
/* This routine checks if the ACB requires      */
/* Activation.                                  */
/*                                              */
/* Activation consists of locking the swappable */
/* sections of the driver in memory and         */
/* hooking the IRQ level (DevHelp_SetIRQ)       */
/*                                              */
/*----------------------------------------------*/
USHORT NEAR ActivateACBCheck( NPACB npACB )
{
   USHORT        rc = 0;				   /*@V90990*/
  PADDWSP       pWSP;

  pWSP = (PADDWSP) npACB->pIORB->ADDWorkSpace;

  /*-------------------------------------------------------*/
  /*                                                       */
  /*                                                       */
  /* Some device managers/adds sit in a spin-loop waiting  */
  /* for a GET_DEVICETABLE to complete. This prevents us   */
  /* from dispatching our Context worker to lock the code. */
  /*                                                       */
  /* The ADDs/DMDs SHOULD BLOCK but some dont! In order    */
  /* not break these existing ADDs/DMDs, we lock the code  */
  /* directly off the caller's thread for GET_DEVICETABLE  */
  /* provided we are running a TASK time.                  */
  /*-------------------------------------------------------*/

  if ( !(npACB->Flags & ACBF_SM_READY) )
  {
    rc = 1;                                                /*@V90990*/
    if ( pWSP->Flags & WSF_SYNC_ACTIVATE && *pCurIntLvl == -1L )
    {
       rc = ActivateACB( npACB );                          /*@V90990*/
    }

    if (rc)                                                /*@V90990*/
    {
      StartActivateACB( npACB );
      rc = 1;
    }
  }

  return ( rc );
}


/*---------------------------------------------*/
/* ResultsToIORBError                          */
/*                                             */
/* This routine maps the floppy controller     */
/* result bytes ST0-ST3 into an IOERR_* type   */
/* IORB ErrorCode.                             */
/*                                             */
/*---------------------------------------------*/

#define ST1_DATA_ERRORS         (ST1_DATA_ERROR)

#define ST2_DATA_ERRORS         (ST2_DATA_ERROR)

#define ST1_ADDRESS_ERRORS      (ST1_END_OF_CYL | ST1_NO_DATA |\
                                 ST1_NO_ADDRESS_MARK)

#define ST2_ADDRESS_ERRORS      (ST2_CONTROL_MARK | ST2_BAD_CYL |\
                                 ST2_NO_DATA_ADDRESS_MARK)

#define ST1_PROTECT_ERRORS      (ST1_WRITE_PROTECT)

#define ST3_PROTECT_ERRORS      (ST3_WRITE_PROTECT)

USHORT NEAR ResultsToIORBError( NPACB npACB )
{
  USHORT        ErrorCode = IOERR_DEVICE_NONSPECIFIC;

  if ( npACB->CurResults[0] & ST0_INT_CODE )
  {
    if ( npACB->CurResults[1] & ST1_DATA_ERRORS
                || npACB->CurResults[2] & ST2_DATA_ERRORS )
    {
      ErrorCode = IOERR_RBA_CRC_ERROR;
    }
    else if ( npACB->CurResults[1] & ST1_ADDRESS_ERRORS
                || npACB->CurResults[2] & ST2_ADDRESS_ERRORS )
    {
      ErrorCode = IOERR_RBA_ADDRESSING_ERROR;
    }
    else if ( npACB->CurResults[1] & ST1_PROTECT_ERRORS
                || npACB->CurResults[3] & ST3_PROTECT_ERRORS)
    {
      ErrorCode = IOERR_MEDIA_WRITE_PROTECT;
    }
    else if ( npACB->CurResults[1] & ST1_OVERRUN )
    {
      ErrorCode = IOERR_ADAPTER_OVERRUN;
    }
  }

  return ( ErrorCode );
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* IORB Routing                                                           */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*-------------------------------*/
/* StartIORB                     */
/*                               */
/* This routine routes the IORB  */
/* we are currently processing   */
/* to the appropriate handling   */
/* routine.                      */
/*                               */
/* In addition this routine sets */
/* various working variables     */
/* in the ACB based on the       */
/* current IORB.                 */
/*-------------------------------*/

VOID NEAR StartIORB( NPACB npACB )
{
  NPUCB         npUCB;

  PIORB         pIORB = npACB->pIORB;
  PADDWSP       pWSP;

  USHORT        CmdError = 0;

  if ( AllocateHWResources( npACB ) )
  {
    goto StartIORBExit;
  }

  pWSP           = (PADDWSP) pIORB->ADDWorkSpace;
  npACB->OpFlags = pWSP->Flags;

  npACB->IORBError  = 0;
  npACB->TimerFlags = 0;
  npACB->CurStatus  = REQS_NO_ERROR;

  npUCB             = (NPUCB) pIORB->UnitHandle;
  npACB->npUCB      = npUCB;
  npACB->UnitId     = npACB->npUCB->UnitId;
  npACB->npMIF      = &npUCB->npMediaInfo[npUCB->MediaInfoIndex];

  /*-------------------------------------------------------*/
  /* Route IORBs to correct handlers based on CommandCode  */
  /* and CommandModifier fields.                           */
  /*-------------------------------------------------------*/
  switch ( pIORB->CommandCode )
  {
    /*-----------------------------*/
    /* Process CONFIGURATION IOBRs */
    /*-----------------------------*/
    case IOCC_CONFIGURATION:
      switch ( pIORB->CommandModifier )
      {
        case IOCM_GET_DEVICE_TABLE:
          GetDeviceTable( npACB );
          break;

        case IOCM_COMPLETE_INIT:
          CompleteInit( npACB );
          break;

        default:
          CmdError = 1;
      }
      break;

    /*-----------------------------*/
    /* Process EXECUTE_IO IOBRs    */
    /*-----------------------------*/
    case IOCC_EXECUTE_IO:
      if ( npACB->OpFlags & OPF_FAST_PATH )
      {
        ExecuteIO( npACB );
        break;
      }

      switch ( pIORB->CommandModifier )
      {
        case IOCM_READ:
        case IOCM_READ_VERIFY:
        case IOCM_WRITE:
        case IOCM_WRITE_VERIFY:
          break;
        default:
          CmdError = 1;
      }
      if ( !CmdError )
      {
        ExecuteIO( npACB );
      }
      break;

    /*-----------------------------*/
    /* Process FORMAT IOBRs        */
    /*-----------------------------*/
    case IOCC_FORMAT:
      switch ( pIORB->CommandModifier )
      {
        case IOCM_FORMAT_TRACK:
          Format( npACB );
          break;

        default:
          CmdError = 1;
      }
      break;

    /*-------------------------------------*/
    /* Process DEVICE CONTROL IORBs        */
    /*                                     */
    /* Note: IOCM_SUSPEND/IOCM_RESUME are  */
    /*       handled in PreProcessIORBs.   */
    /*-------------------------------------*/


    /*----------------------------*/
    /* Process UNIT CONTROL IORBs */
    /*----------------------------*/
    case IOCC_UNIT_CONTROL:
      switch ( pIORB->CommandModifier )
      {
        case IOCM_ALLOCATE_UNIT:
          AllocateUnit( npACB );
          break;
        case IOCM_DEALLOCATE_UNIT:
          DeallocateUnit( npACB );
          break;
        case IOCM_CHANGE_UNITINFO:
          ChangeUnitInfo( npACB );
          break;

        default:
          CmdError = 1;
      }
      break;

    /*---------------------------*/
    /* Process UNIT STATUS IORBs */
    /*---------------------------*/
    case IOCC_UNIT_STATUS:
      switch ( pIORB->CommandModifier )
      {
        case IOCM_GET_CHANGELINE_STATE:
          CheckChangeLine( npACB );
          break;
        case IOCM_GET_MEDIA_SENSE:
          ReadMediaSense( npACB );
          break;

        default:
          CmdError = 1;

      }
      break;

    /*-------------------------*/
    /* Process GEOMETRY IORBs  */
    /*-------------------------*/
    case IOCC_GEOMETRY:
      switch ( pIORB->CommandModifier )
      {
        case IOCM_GET_MEDIA_GEOMETRY:
          GetMediaGeometry( npACB );
          break;
        case IOCM_SET_MEDIA_GEOMETRY:
          SetMediaGeometry( npACB );
          break;
        case IOCM_GET_DEVICE_GEOMETRY:
          GetDeviceGeometry( npACB );
          break;
        case IOCM_SET_LOGICAL_GEOMETRY:
          SetLogicalGeometry( npACB );
          break;

        default:
          CmdError = 1;
      }
      break;

    default:
      CmdError = 1;
  }

  if ( CmdError )
  {
    npACB->IORBError  = IOERR_CMD_NOT_SUPPORTED;
    npACB->OState     = ACBO_COMPLETE_IORB;
  }

  StartIORBExit: ;

}


/*-----------------------------------*/
/* NextIORB                          */
/* --------                          */
/* Obtains the an IORB on the ACB    */
/* IORB Queue.                       */
/*-----------------------------------*/


USHORT FAR NextIORB(NPACB npACB)
{
  PIORB         pIORB;

  /*------------------------------------------*/
  /* Process the IORB Queue for this ACB      */
  /*------------------------------------------*/
  DISABLE
  if ( npACB->pIORB = npACB->pHeadIORB )
  {
    if ( !(npACB->pHeadIORB = npACB->pIORB->pNxtIORB) )
    {
      npACB->pFootIORB = 0;
    }
  }

  return ( (npACB->pIORB) ? 0 : 1);
}

/*------------------------------------------------------------------------*/
/* Media Detemination                                                     */
/*                                                                        */
/* This set of states attemts to determine the type of media in the       */
/* drive by trying to read it at the media densities expected for         */
/* the type of drive being supported.                                     */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*-------------------------------*/
/* StareMediaDetermine           */
/*                               */
/* This routine just checks the  */
/* ChangeLine to make sure that  */
/* we have media in the drive.   */
/*                               */
/*-------------------------------*/

VOID NEAR StartMediaDetermine( NPACB npACB )
{
  NPUCB         npUCB;

  /*----------------------------------------------*/
  /* Call the ISM to check for a media change.    */
  /*                                              */
  /* If the media has changed, the ISM will reset */
  /* the MEDIA_UNFORMATTED indicator. Original    */
  /* IORB will be returned to the user with       */
  /* a NOT_READY or CHANGED error code.           */
  /*----------------------------------------------*/

  npACB->OSMNotifyRtn  = DetermineMedia1;

  StartOSMRequest( npACB, ACBRG_CHECK_MEDIA );
}


/*------------------------------------------*/
/* DetermineMedia1                          */
/*                                          */
/* This routine starts media determination  */
/* starting with the default drive density  */
/*                                          */
/*------------------------------------------*/

VOID FAR DetermineMedia1( NPACB npACB )
{
  NPUCB         npUCB;

  npUCB = npACB->npUCB;

  /*-------------------------------------------------*/
  /* If Media has changed or drive is not ready then */
  /* report this status back to the requestor.       */
  /*-------------------------------------------------*/

  if ( npACB->CurStatus != REQS_NO_ERROR )
  {
    npACB->OState = ACBO_COMPLETE_IORB;
    goto Determine1Exit;
  }

  /*----------------------------------------------------*/
  /* If the media in the drive is unformatted then      */
  /* set the result code and exit.                      */
  /*                                                    */
  /* UCBF_MEDIA_UNFORMATTED is reset if the user        */
  /* changes media or an IOCM_SET_MEDIA_GEOMETRY        */
  /* is issued.                                         */
  /*----------------------------------------------------*/
  if ( npUCB->Flags & UCBF_MEDIA_UNFORMATTED )
  {
    npACB->CurStatus = REQS_MEDIA_UNFORMATTED;
    npACB->OState    = ACBO_COMPLETE_IORB;
    goto Determine1Exit;
  }

  /*----------------------------------------------------*/
  /* If the media in the drive has not been extablished */
  /* then start the media determination sequence        */
  /*----------------------------------------------------*/
  if ( npUCB->Flags & UCBF_MEDIA_UNCERTAIN )
  {
    /*------------------------------------------*/
    /* Set the default (highest) media capacity */
    /*------------------------------------------*/
    npUCB->MediaInfoIndex = MII_DEFAULT;
    npACB->npMIF          = &npUCB->npMediaInfo[MII_DEFAULT];

    npACB->ReqCmdLen      = FC_LEN_READ_ID;
    npACB->ReqResultsLen  = FR_LEN_READ_ID;

    npACB->ReqDataLen     = 0;

    npACB->ReqCmd[0]      = FC_READ_ID;
    npACB->ReqCmd[1]      = npACB->UnitId;

    npACB->ExecIOError    = 0;                                       /*@V85053*/

    npACB->OSMNotifyRtn   = DetermineMedia2;

    StartOSMRequest( npACB, ACBRG_DETERMINE_MEDIA );
  }

  Determine1Exit: ;

}

/*------------------------------------------*/
/* DetermineMedia2                          */
/*                                          */
/* This routine checks if the previous      */
/* media determination attempt was          */
/* successful. If not the next              */
/* media density is tried.                  */
/*                                          */
/* If we were able to read the media at     */
/* the last density set, then we mark       */
/* that the media type was established      */
/* and we set state ACBO_START_IORB         */
/* to restart processing of the current     */
/* IORB.                                    */
/*                                          */
/*------------------------------------------*/

VOID FAR DetermineMedia2( NPACB npACB )
{
  NPUCB         npUCB;

  npUCB = npACB->npUCB;

  /*----------------------------------------------------*/
  /* If we successfully Read Ids from Track 0, then     */
  /* media determination was successful. Processing of  */
  /* the current IORB can continue.                     */
  /*----------------------------------------------------*/
  if ( npACB->CurStatus == REQS_NO_ERROR )
  {
    npUCB->Flags  &= ~(UCBF_MEDIA_UNCERTAIN | UCBF_MEDIA_UNFORMATTED);
    npACB->OState  =  ACBO_START_IORB;
  }

  /*----------------------------------------------------*/
  /* If we could not Read Ids from Track 0, then try    */
  /* the next (lower density).                          */
  /*----------------------------------------------------*/
  else
  {
    if ( ++npACB->ExecIOError < MAX_DETERMINE_ERROR)                 /*@V85053*/
    {                                                                /*@V85053*/
      StartOSMRequest( npACB, ACBRG_DETERMINE_MEDIA );               /*@V85053*/
    }                                                                /*@V85053*/
    else if ( ++npUCB->MediaInfoIndex < npUCB->MediaInfoCount )      /*@V85053*/
    {
      npACB->npMIF++;

      npACB->ExecIOError = 0;                                        /*@V85053*/

      StartOSMRequest( npACB, ACBRG_DETERMINE_MEDIA );
    }

    /*-------------------------------------------------*/
    /* If all known densities have been tried, then we */
    /* either have unformatted or non-standard media   */
    /*-------------------------------------------------*/
    else
    {
      /*------------------------------------------------*/
      /* If the drive does not have CHANGELINE support  */
      /* we cannot adequately differentiate Drive Not   */
      /* Ready from Unformatted Media.                  */
      /*------------------------------------------------*/

      if ( !(npUCB->Flags & UCBF_NO_CHANGELINE) )
      {
        npUCB->Flags       |= UCBF_MEDIA_UNFORMATTED;
      }

      npUCB->Flags         |= UCBF_MEDIA_UNCERTAIN;
      npACB->CurStatus      = REQS_MEDIA_UNFORMATTED;

      npUCB->MediaInfoIndex = MII_DEFAULT;
      npACB->npMIF          = &npUCB->npMediaInfo[MII_DEFAULT];

      npACB->OState         = ACBO_COMPLETE_IORB;
    }
  }
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/*  Process IOCC_UNIT_STATUS IORBs                                        */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*-------------------------------*/
/* CheckChangeLine               */
/*                               */
/* This routine starts an ISM    */
/* request to read the drive's   */
/* Changeline state.             */
/*-------------------------------*/

VOID NEAR CheckChangeLine( NPACB npACB )
{
  PIORB_UNIT_STATUS     pIORB;

  pIORB = (PIORB_UNIT_STATUS) npACB->pIORB;
  pIORB->UnitStatus = 0;

  npACB->OSMNotifyRtn  = CheckChangeLine1;

  StartOSMRequest( npACB, ACBRG_CHECK_MEDIA );
}

/*-------------------------------*/
/* CheckChangeLine1              */
/*                               */
/* This routine is called when   */
/* the ISM completes the read of */
/* the ChangeLine                */
/*-------------------------------*/

VOID FAR CheckChangeLine1( NPACB npACB )
{
  if ( npACB->CurStatus != REQS_NO_ERROR )
  {
    ((PIORB_UNIT_STATUS) npACB->pIORB)->UnitStatus |= US_CHANGELINE_ACTIVE;

  }
  npACB->CurStatus = REQS_NO_ERROR;

  npACB->OState = ACBO_COMPLETE_IORB;
}


/*-------------------------------*/
/* ReadMediaSense                */
/*                               */
/* This routine starts an ISM    */
/* request to start the drive's  */
/* motor.                        */
/*                               */
/*-------------------------------*/

VOID NEAR ReadMediaSense( NPACB npACB )
{
  PIORB_UNIT_STATUS     pIORB;

  pIORB = (PIORB_UNIT_STATUS) npACB->pIORB;
  pIORB->UnitStatus = 0;

  if ( !(npACB->npUCB->Flags & UCBF_NO_MEDIASENSE) ) // @V92217 backout @V89964.
  {
    npACB->OSMNotifyRtn  = ReadMediaSenseDone;

    StartOSMRequest( npACB, ACBRG_MOTOR_ON );
  }
  else
  {
    pIORB->UnitStatus = US_MEDIA_UNKNOWN;
    npACB->CurStatus  = REQS_NO_ERROR;
    npACB->OState     = ACBO_COMPLETE_IORB;
  }
}

/*-------------------------------*/
/* ReadMediaSenseDone            */
/*                               */
/* Once the drive is selected    */
/* and its motor is started,     */
/* this routine reads, the       */
/* media type directly from      */
/* the controller and translates */
/* the result to one of the IORB */
/* US_MEDIA_* codes.             */
/*                               */
/*-------------------------------*/

VOID FAR ReadMediaSenseDone( NPACB npACB )
{
  PIORB_UNIT_STATUS     pIORB;

  USHORT                TDRPort;
  UCHAR                 Value;

  pIORB = (PIORB_UNIT_STATUS) npACB->pIORB;

  npACB->CurStatus = REQS_NO_ERROR;
  npACB->OState    = ACBO_COMPLETE_IORB;

  DISABLE
  TDRPort = npACB->IOPorts[FCR_TDR];
  inp( TDRPort, Value );
  ENABLE

  switch( Value & FCR_TDR_MEDIA_ID )
  {
    case FCR_TDR_MEDIA_ID_4MB:
      pIORB->UnitStatus = US_MEDIA_288MB;
      break;
    case FCR_TDR_MEDIA_ID_2MB:
      pIORB->UnitStatus = US_MEDIA_144MB;
      break;
    case FCR_TDR_MEDIA_ID_1MB:
      pIORB->UnitStatus = US_MEDIA_720KB;
      break;
    case FCR_TDR_MEDIA_ID_INVALID:
      pIORB->UnitStatus = US_MEDIA_UNKNOWN;
      break;
  }
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/*  Process IOCC_GEOMETRY IORBs                                           */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*---------------------------------------------------------*/
/* GetMediaGeometry                                        */
/*                                                         */
/* This routine starts processing of a GET_MEDIA_GEOMETRY  */
/* request.                                                */
/*                                                         */
/* First an ISM request is issued to verify that there     */
/* is media in the drive and whether the current           */
/* media information is valid                              */
/*                                                         */
/*---------------------------------------------------------*/

VOID NEAR GetMediaGeometry( NPACB npACB )
{
  /*-------------------------------------------*/
  /* Read the ChangeLine for the drive.        */
  /*                                           */
  /* If the media has not changed, then the    */
  /* current media information in the UCB is   */
  /* valid.                                    */
  /*-------------------------------------------*/
  npACB->OSMNotifyRtn  = GetMediaGeometry1;

  StartOSMRequest( npACB, ACBRG_CHECK_MEDIA );
}


/*---------------------------------------------------------*/
/* GetMediaGeometry1                                       */
/*                                                         */
/* If the media in the drive has not been determined       */
/* then a Media Determination sequence is begun.           */
/*                                                         */
/* Otherwise, we return the requested media information    */
/* in the GEOMETRY structure passes in the requestor's     */
/* IORB.                                                   */
/*---------------------------------------------------------*/

VOID FAR GetMediaGeometry1( NPACB npACB )
{
  PIORB_GEOMETRY        pIORB;

  (PIORB) pIORB = npACB->pIORB;

  /*--------------------------------------------*/
  /* If the media has changed, then attempt to  */
  /* determine the media in the drive.          */
  /*--------------------------------------------*/
  if ( npACB->CurStatus == REQS_MEDIA_CHANGED )
  {
    npACB->OState = ACBO_DETERMINE_MEDIA;
  }
  /*-------------------------------------*/
  /* If the drive is not ready then fail */
  /* the Get Geometry request            */
  /*-------------------------------------*/
  else if ( npACB->CurStatus == REQS_NOT_READY )
  {
    npACB->OState = ACBO_COMPLETE_IORB;
  }
  /*---------------------------------------------*/
  /* If media in the drive has not changed,      */
  /* then return info for the media in the drive */
  /*---------------------------------------------*/
  else if ( npACB->CurStatus == REQS_NO_ERROR )
  {
    if ( !(npACB->npUCB->Flags & UCBF_MEDIA_UNCERTAIN) )
    {
      *pIORB->pGeometry = npACB->npMIF->Geo;
      npACB->OState     = ACBO_COMPLETE_IORB;
    }
    else if ( npACB->npUCB->Flags & UCBF_MEDIA_UNFORMATTED )
    {
      npACB->CurStatus = REQS_MEDIA_UNFORMATTED;
      npACB->OState    = ACBO_COMPLETE_IORB;
    }
    else
    {
      npACB->OState = ACBO_DETERMINE_MEDIA;
    }
  }
  else
  {
    npACB->OState = ACBO_COMPLETE_IORB;
  }

}

/*---------------------------------------------------------*/
/* GetMediaGeometry                                        */
/*                                                         */
/* This routine starts processing of a GET_DEVICE_GEOMETRY */
/* request.                                                */
/*                                                         */
/* The default geometry for the device is returned to      */
/* the GEOMETRY structure passed in the requestor's        */
/* IORB.                                                   */
/*---------------------------------------------------------*/

VOID NEAR GetDeviceGeometry( NPACB npACB )
{
  PIORB_GEOMETRY        pIORB;

  pIORB = (PIORB_GEOMETRY) npACB->pIORB;

  /*------------------------------------------*/
  /* Return the Default Device Geometry for   */
  /* the requested unit.                      */
  /*------------------------------------------*/
  *pIORB->pGeometry = npACB->npUCB->npMediaInfo[MII_DEFAULT].Geo;

  npACB->OState    = ACBO_COMPLETE_IORB;
}

/*------------------------------------------------------------*/
/* SetMediaGeometry                                           */
/*                                                            */
/* This routine starts processing of a SET_MEDIA_GEOMETRY     */
/* request.                                                   */
/*                                                            */
/* This IORB is used to set a device to a capacity other than */
/* its default operating capacity.                            */
/*                                                            */
/*------------------------------------------------------------*/

VOID NEAR SetMediaGeometry( NPACB npACB )
{

  PIORB_GEOMETRY        pIORB;
  PGEOMETRY             pGEO;

  NPUCB                 npUCB;

  NPMEDIAINFO           npMIF, npMIU, npMID;

  USHORT                i,n;

  npUCB         = npACB->npUCB;

  pIORB = (PIORB_GEOMETRY) npACB->pIORB;
  pGEO  = pIORB->pGeometry;

  npACB->CurStatus = 0;

  npMIU = &npUCB->npMediaInfo[MII_USER_DEFINED];
  npMID = &npUCB->npMediaInfo[MII_DEFAULT];

  /*-----------------------------------------------*/
  /* Set ACB/UCB pointers to USER_DEFINED geometry */
  /*-----------------------------------------------*/
  npACB->npMIF           = npMIU;
  npUCB->MediaInfoIndex  = MII_USER_DEFINED;

  /*---------------------------------------------------*/
  /* If user geometry uses standard sector sizes then  */
  /* search standard geometries.                       */
  /*---------------------------------------------------*/
  if ( pGEO->BytesPerSector == 512 )
  {
    i     = npUCB->MediaInfoCount-1;
    npMIF = &npUCB->npMediaInfo[i];

    for (  ; i > MII_DEFAULT-1; i--, npMIF-- )
    {
      if ( npMIF->Geo.SectorsPerTrack == pGEO->SectorsPerTrack
            && npMIF->Geo.TotalCylinders == pGEO->TotalCylinders )
      {
        *npMIU     = *npMIF;
        npMIU->Geo = *pGEO;
        break;
      }
    }
    /*----------------------------------------------------*/
    /* If User Values do not match standard geometries    */
    /* then use user Geometry with default device density.*/
    /*----------------------------------------------------*/
    if ( i == MII_DEFAULT-1 )
    {
      *npMIU = npUCB->npMediaInfo[MII_DEFAULT];
      npMIU->Geo = *pGEO;
    }
  }
  else
  {
    /*---------------------------------------------------*/
    /* If user selected non-standard sector sizes then,  */
    /* look up appropriate GAP lengths per Intel/National*/
    /* spec.                                             */
    /* Use default device density settings for other     */
    /* parameters.                                       */
    /*---------------------------------------------------*/

    npACB->CurStatus = REQS_INVALID_GEOMETRY;

    if ( (n=BlkSizeToN(pGEO->BytesPerSector)) != -1 )
    {
      *npMIU     = npUCB->npMediaInfo[MII_DEFAULT];
      npMIU->Geo = *pGEO;

      npACB->CurStatus = REQS_NO_ERROR;

      switch ( npMIU->DataRate )
      {
        case FCR_DSR_RATE_1MB:
        case FCR_DSR_RATE_500KB:
          if ( n > 3 )
          {
            npMIU->FmtGapLength = NS_FMGL_500K_2048;
          }
          else if ( n == 3 )
          {
            npMIU->FmtGapLength = NS_FMGL_500K_1024;
          }
          else if ( n < 2 )
          {
            npMIU->FmtGapLength = NS_FMGL_500K_256;
          }
          break;


        case FCR_DSR_RATE_250KB:
          if ( n > 3 )
          {
            npMIU->FmtGapLength = NS_FMGL_250K_2048;
          }
          else if ( n == 3 )
          {
            npMIU->FmtGapLength = NS_FMGL_250K_1024;
          }
          else if ( n < 2 )
          {
            npMIU->FmtGapLength = NS_FMGL_250K_256;
          }
          break;

        default:
          npACB->CurStatus = REQS_INVALID_GEOMETRY;
      }
    }
    /*---------------------*/
    /* Invalid Sector Size */
    /*---------------------*/
    else
    {
      npACB->CurStatus = REQS_INVALID_GEOMETRY;
    }
  }

  /*---------------------------------------------*/
  /* Force drive select on next access to insure */
  /* new data rate and specify bytes take effect */
  /*---------------------------------------------*/
  if ( npACB->CurStatus == REQS_NO_ERROR )
  {
    npUCB->Flags    &= ~(UCBF_MEDIA_UNFORMATTED | UCBF_MEDIA_UNCERTAIN);
    npUCB->ReqFlags |= UCBR_FORCE_SELECT;
  }
  /*------------------------------------------------*/
  /* Reestablish media if Set Media Geometry failed */
  /*------------------------------------------------*/
  else
  {
    npUCB->Flags |= UCBF_MEDIA_UNCERTAIN;
  }

  npACB->OState = ACBO_COMPLETE_IORB;
}


/*------------------------------------------------------------*/
/* SetLogicalGeometry                                         */
/*                                                            */
/* This routine starts processing of a SET_LOGICAL_GEOMETRY   */
/* request.                                                   */
/*                                                            */
/* This IORB is used to tell the driver to interpret the      */
/* established media with an alternate CHS scheme.            */
/*                                                            */
/* An example would be reading a 1.44MB diskette (18 Sectors  */
/* Per Track) with a 1.2MB image recorded on in (15 Sectors   */
/* Per Track).                                                */
/*                                                            */
/* Since the ADD interfaces are primarily RBA oriented, we    */
/* must interpret RBAs in this case with the 15 Sector Per    */
/* Track scheme.                                              */
/*                                                            */
/* This IORB is processed by generating a hybrid MediaInfo    */
/* structure containing the normal operating parameters       */
/* for the drive but with the requested geometry.             */
/*------------------------------------------------------------*/


VOID NEAR SetLogicalGeometry( NPACB npACB )
{
  USHORT BufferLength;
  USHORT rc;
  NPUCB          npUCB;
  PIORB_GEOMETRY pIORB;
  NPGEOMETRY     npGeometry;                           // @V104373

  pIORB = (PIORB_GEOMETRY) npACB->pIORB;

  npUCB = npACB->npUCB;

  if ( !(npUCB->Flags & UCBF_MEDIA_UNCERTAIN) )
  {
    npUCB->npMediaInfo[MII_LOGICAL] = *npACB->npMIF;

    npUCB->npMediaInfo[MII_LOGICAL].Geo = *pIORB->pGeometry;

    // Start @V104373.
    npGeometry = &(npUCB->npMediaInfo[MII_LOGICAL].Geo);
    if ( (BufferLength = npGeometry->SectorsPerTrack * npGeometry->BytesPerSector)
          > npACB->DMABufLen )
    {
      npACB->DMABufLen = BufferLength;
      if ( npACB->DMABuf )
      {
        DISABLE
        rc = DevHelp_VMFree( npACB->DMABufLin );
        npACB->DMABuf = 0;
        ENABLE
      }
    }
    // End @V104373.

    npUCB->MediaInfoIndex = MII_LOGICAL;
    npACB->npMIF          = &npUCB->npMediaInfo[MII_LOGICAL];
    npACB->OState         = ACBO_COMPLETE_IORB;
  }
  else
  {
    npACB->OState = ACBO_DETERMINE_MEDIA;
  }
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/*  Process IOCC_UNIT_CONTROL IORBs                                       */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*----------------------------------------------*/
/* AllocateUnit                                 */
/*                                              */
/* This routine checks if the Unit is allocated.*/
/*                                              */
/* If the unit is not currently allocated, then */
/* it is marked as allocated.                   */
/*                                              */
/* If the unit was previously allocated, then   */
/* the user's IORB is returned with an error.   */
/*----------------------------------------------*/

VOID NEAR AllocateUnit( NPACB npACB )
{
  NPUCB         npUCB;

  npUCB = npACB->npUCB;

  if ( npUCB->Flags & UCBF_ALLOCATED )
  {
    npACB->IORBError = IOERR_UNIT_ALLOCATED;
  }
  else
  {
    npUCB->Flags |= UCBF_ALLOCATED;
  }
  npACB->OState     = ACBO_COMPLETE_IORB;
}

/*----------------------------------------------*/
/* DeallocateUnit                               */
/*                                              */
/* This routine deallocates the requested       */
/* unit.                                        */
/*----------------------------------------------*/

VOID NEAR DeallocateUnit( NPACB npACB )
{
  npACB->npUCB->Flags &= ~UCBF_ALLOCATED;
  npACB->OState        = ACBO_COMPLETE_IORB;
}

/*------------------------------------------------*/
/* ChangeUnitInfo                                 */
/*                                                */
/* This routine replaces the UnitInfo structure   */
/* the ADD driver broadcasts in its DeviceTable   */
/* with the UnitInfo structure passed by the      */
/* caller.                                        */
/*------------------------------------------------*/

VOID NEAR ChangeUnitInfo( NPACB npACB )
{
  npACB->npUCB->pUnitInfo = ((PIORB_UNIT_CONTROL) npACB->pIORB)->pUnitInfo;
  npACB->OState           = ACBO_COMPLETE_IORB;
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/*  Process IOCC_CONFIGURATION IORBs                                      */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------------*/
/* GetDeviceTable                           */
/*                                          */
/*                                          */
/* Handles an IOCM_GET_DEVICETABLE request  */
/*------------------------------------------*/

VOID NEAR GetDeviceTable(NPACB npACB)
{
  PIORB_CONFIGURATION pIORB = (PIORB_CONFIGURATION)npACB->pIORB;

  USHORT        LengthNeeded;
  USHORT        i;
  USHORT        j;
  USHORT        n;
  PADAPTERINFO  pADPT;
  PUNITINFO     pUI, pUI2, pUIDriveA;
  PDEVICETABLE  pDT;
  NPACB         npACBP;
  NPUCB         npUCB;
  USHORT        ABCount = 0;

  npACB->OState = ACBO_COMPLETE_IORB;

  /*------------------------------------------*/
  /* Make sure the IORB Device Table area is  */
  /* large enough to accept our configuration */
  /*------------------------------------------*/

  LengthNeeded = sizeof(DEVICETABLE) + cAdapters * sizeof(ADAPTERINFO) +
                 (cUnits * sizeof(UNITINFO));

  if ( pIORB->DeviceTableLen < LengthNeeded )
  {
    npACB->IORBError = IOERR_CMD_SYNTAX;
    return;
  }

  /*-------------------------------*/
  /* Setup the device table header */
  /*-------------------------------*/

  pDT = pIORB->pDeviceTable;

  pDT->ADDLevelMajor = ADD_LEVEL_MAJOR;
  pDT->ADDLevelMinor = ADD_LEVEL_MINOR;
  pDT->ADDHandle     = ADDHandle;
  pDT->TotalAdapters = cAdapters;

  pADPT = (PADAPTERINFO) (((PBYTE)(pDT+1)) +
                                 (cAdapters-1) * sizeof(NPADAPTERINFO));

  /*------------------------------------------*/
  /* Scan through the Adapter Control Blocks  */
  /*------------------------------------------*/

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

    /*--------------------------------------------------*/
    /* For each adapter, build an ADAPTERINFO structure */
    /* in the DeviceTable                               */
    /*--------------------------------------------------*/

    pDT->pAdapter[i]  = (NPADAPTERINFO) pADPT;

    memcpy( pADPT->AdapterName, AdapterName, sizeof(AdapterName) );

    pADPT->AdapterUnits         = npACBP->cUnits;
    pADPT->AdapterDevBus        = AI_DEVBUS_FLOPPY | AI_DEVBUS_8BIT;
    pADPT->AdapterIOAccess      = AI_IOACCESS_DMA_SLAVE;
    pADPT->AdapterHostBus       = AI_HOSTBUS_ISA | AI_BUSWIDTH_8BIT;
    pADPT->AdapterSCSITargetID  = 0;
    pADPT->AdapterSCSILUN       = 0;
    pADPT->AdapterFlags         = AF_16M;
    pADPT->MaxHWSGList          = 0;
    pADPT->MaxCDBTransferLength = 0L;


    /*------------------------------------------------*/
    /* For each UNIT attached to this adapter, create */
    /* a UNITINFO structure                           */
    /*------------------------------------------------*/

    npUCB = npACBP->npFirstUCB;

    for ( j=0; j < npACBP->cUnits; j++ )
    {
      pUI = &pADPT->UnitInfo[j];

      if ( !(pUI2=npUCB->pUnitInfo) ) /* If unit info was not changed */
      {
        memset( (PBYTE) pUI, 0, sizeof(UNITINFO) );

        pUI->AdapterIndex     = i;
        pUI->UnitIndex        = j;
        pUI->UnitHandle       = (USHORT) npUCB;
        pUI->FilterADDHandle  = 0;
        pUI->UnitType         = UIB_TYPE_DISK;
        pUI->QueuingCount     = 1;
        pUI->UnitFlags        = UF_NOSCSI_SUPT | UF_REMOVABLE;

        if ( !(npUCB->Flags & UCBF_NO_CHANGELINE) )
        {
          pUI->UnitFlags |= UF_CHANGELINE;
        }

        /*-------------------------------------------*/
        /* If UCB allows A:/B: drive assignment      */
        /* and both A: and B: have not been assigned */
        /*-------------------------------------------*/
        if ( (ABCount < 2 ) &&  !(npUCB->Flags & UCBF_NOT_DRIVE_AB) )
        {
          /*-----------------------------------------*/
          /* If unit has BIOS support assign A:/B:   */
          /* consistent with BIOS unit designation   */
          /*-----------------------------------------*/
          if ( npUCB->Flags & UCBF_BIOS_SUPPORT )
          {
            switch ( npUCB->BIOSId )
            {
              case 0:
                pUI->UnitFlags |= UF_A_DRIVE;
                pUIDriveA       = pUI;
                ABCount = 1;
                break;
              case 1:
                pUI->UnitFlags |= UF_B_DRIVE;
                ABCount = 2;
                break;
            }
          }
          /*-----------------------------------------*/
          /* If non-BIOS supported unit assign A:/B: */
          /* as available.                           */
          /*-----------------------------------------*/
          else
          {
            switch ( ABCount )
            {
              case 0:
                pUI->UnitFlags |= UF_A_DRIVE;
                pUIDriveA       = pUI;
                ABCount = 1;
                break;
              case 1:
                pUI->UnitFlags |= UF_B_DRIVE;
                ABCount = 2;
                break;
            }
          }
        }


        if ( npUCB->Flags & UCBF_NO_DASD_SUPPORT )
        {
          pUI->UnitFlags |= UF_NODASD_SUPT;
        }
      }
      /*----------------------------------------------*/
      /* An alternate UNITINFO structure was set via  */
      /* IOCM_CHANGE_UNITINFO.                        */
      /*                                              */
      /* Put this UNITINFO structure                  */
      /* into the DeviceTable rather than the default */
      /* UNITINFO structure.                          */
      /*----------------------------------------------*/
      else
      {
        *pUI = *pUI2;
      }
      npUCB = npUCB->npNextUCB;

    }
    pADPT = (PADAPTERINFO) (pUI+1);
    i++;
  }

  if ( ABCount == 1 )
  {
    pUIDriveA->UnitFlags |= UF_B_DRIVE;
  }

}


/*------------------------------------------*/
/* CompleteInit                             */
/*                                          */
/* Handles an IOCM_COMPLETE_INIT   request  */
/*------------------------------------------*/

VOID NEAR CompleteInit( NPACB npACB )
{
  InitComplete = 1;

  npACB->OState = ACBO_COMPLETE_IORB;
}
