/*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 = FLP1INIT.C
 *
 * DESCRIPTIVE NAME = ISA Diskette Driver - Initialization
 *
 *
 *
 * VERSION = V2.1
 *
 * DATE =  94/03/14
 *
 * DESCRIPTION :
 *
 * Purpose:       Contains discardable initialization code for driver.
 *
 *
 *
 *
 *
*/
#define INCL_NOBASEAPI
#define INCL_NOPMAPI
#define INCL_DOSERRORS
#include "os2.h"
#include "dos.h"
#include "bseerr.h"

#include "dskinit.h"

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

#define INCL_INITRP_ONLY
#include "reqpkt.h"

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

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Initialization                                                         */
/*                                                                        */
/*------------------------------------------------------------------------*/


VOID NEAR CreateRMStructures(VOID);

/*------------------------------------*/
/* DriveInit                          */
/*                                    */
/* Process Initialization Request     */
/* Packet from kernel.                */
/*                                    */
/*------------------------------------*/

VOID FAR DriveInit(PRPINITIN pRPH )
{
  PRPINITIN             pRPI = (PRPINITIN)  pRPH;
  PRPINITOUT            pRPO = (PRPINITOUT) pRPH;
  NPATBL                npAT = AdapterTable;
  NPACB                 npACB;
  PMACHINE_CONFIG_INFO  pMCHI;
  ULONG                 TimeOut;
  USHORT                i,j,k,n;
  USHORT                rc;
  USHORT                Lid = -1;
  USHORT                DriveId;
  ADJUNCT               AdjunctList;

  PBYTE                 pTimerPool;


  /*----------------------------------------*/
  /* Setup DevHelp service entry point for  */
  /* DHCALLS library.                       */
  /*----------------------------------------*/
  Device_Help = pRPH->DevHlpEP;

  /*-----------------------------------------*/
  /* Put name from Config.sys in DrvrName of */
  /* the DriverStruct structure.             */
  /*-----------------------------------------*/

  RMGetDriverName (pRPI, DriverStruct.DrvrName, DrvrNameSize);

  /*---------------------------------------------------*/
  /* Allocate Driver Handle                            */
  /*                                                   */
  /* Most of this data is 'canned' and is discard      */
  /* after initialization completes.                   */
  /*---------------------------------------------------*/
  if(RMCreateDriver(&DriverStruct,
                    &hDriver)){
  }


  /*---------------------------------------*/
  /* Point to initialization vector table. */
  /*                                       */
  /* See h\dskinit.h for contents.         */
  /*---------------------------------------*/
  pDDD_Parm_List = (PDDD_PARM_LIST) pRPI->InitArgs;

  /*-----------------------------------------------*/
  /* Transfer information from Machine Info packet */
  /*-----------------------------------------------*/
  pMCHI = MAKEP( SELECTOROF(pDDD_Parm_List),
                 (USHORT)   pDDD_Parm_List->machine_config_table );


  /*--------------------------------------*/
  /* Modify defaults in ADAPTER TABLE per */
  /* command line parameters.             */
  /*--------------------------------------*/

  if ( ParseCmdLine( pDDD_Parm_List, npAT ) )
  {
    TTYWrite( ParmErrMsg );
  }

  /*----------------------------------------------------------*/
  /* If this is a uChannel machine, with viable ABIOS support */
  /* then deinstall this driver.                              */
  /*----------------------------------------------------------*/

// Note that BUSINFO_OEM_ABIOS => BUSINFO_MCA.
// uCMachine = (pMCHI->BusInfo & BUSINFO_MCA) ? 1 : 0;
  uCMachine = pMCHI->BusInfo & (BUSINFO_MCA | BUSINFO_OEM_ABIOS); // @V92217.

  if ( pMCHI->BusInfo & BUSINFO_EISA ) {
     AdapterStruct.HostBusType  = AS_HOSTBUS_EISA;    /*V98451*/
     AdapterStruct.HostBusWidth = 16;                 /*V98451*/
  } else if (pMCHI->BusInfo & BUSINFO_MCA){
     AdapterStruct.HostBusType  = AS_HOSTBUS_uCHNL;   /*V98451*/
     AdapterStruct.HostBusWidth = 16;                 /*V98451*/
  } else {
     AdapterStruct.HostBusType  = AS_HOSTBUS_ISA;     /*V98451*/
     AdapterStruct.HostBusWidth = 16;                 /*V98451*/
  }

  if ( uCMachine && !uCInstall )
  // In config.sys:  BASEDEV=IBM1FLPY.ADD   (but no /MCA parameter).
  {
    if ( pMCHI->BusInfo & BUSINFO_ABIOS_PRESENT )
    {
      if ( pMCHI->BusInfo & BUSINFO_OEM_ABIOS )   // start of @V91237
      {
        ;
      }
      else
      {
        rc = 1;
        goto FLP1_Deinstall;
      }
    }                                             // end of @V91237
  }

  IBMModel    = pMCHI->Model;
  IBMSubModel = pMCHI->SubModel;

  rc = 0;

  pTimerPool = (PBYTE) InitAllocate( TIMER_POOL_SIZE );

  ADD_InitTimer( pTimerPool, TIMER_POOL_SIZE);


  /*-------------------------------------*/
  /* Setup Miscellaneous global pointers */
  /*-------------------------------------*/
  SetupADDVars();

  /*----------------------------------------*/
  /* Setup each Adapter. Scan each adapter  */
  /* for drives (units).                    */
  /*----------------------------------------*/

  for (i=0; i< MAX_ADAPTERS; i++, npAT++ )
  {
    if ( !(npAT->Flags & ATBF_DISABLED) )
    {
      if ( !ConfigureController( npAT ) )
      {
        if ( i == 0 )
        {
          GetBIOSDsktInfo();
        }

        for (j=0; j < MAX_DRIVES; j++ )
        {
          ConfigureUnit( npAT, j );
        }
      }
    }
    else
    {
      npAT->Status = ATS_SKIPPED;
    }

    CheckACBViable( npAT );                                          /*@V85053*/
  }


  // If there are no diskette drives, we want to deinstall so portables will
  // have max memory. (Ext. drives may be disconnected to save battery life.)

  if ( 0 == cUnits )        // No diskette drive units.          // @V97351
  {                                                              // @V97351
    goto NoUnits_Found;                                          // @V97351
  }                                                              // @V97351


  /*-----------------------------*/
  /* Setup Hardware Resource IDs */
  /*-----------------------------*/
  CheckHWConflicts();

  /*--------------------*/
  /* Setup DMA Buffers  */
  /*--------------------*/
  SetupDMABuffers();

  /*----------------------*/
  /* Enable 2.88MB Drives */
  /*----------------------*/
  Enable288Drives();

  /*---------------------------*/
  /* Adjust Specify Bytes for  */
  /* elderly uC systems        */
  /*---------------------------*/
  AdjustSpecifyBytes( &AdapterTable[0] );

  /*--------------------------------*/
  /* Return drives 0,1 to original  */
  /* seek locations                 */
  /*--------------------------------*/
  RestoreBIOSCyl();


  /*------------------------------------*/
  /* Display hardware configuration on  */
  /* screen if requested.               */
  /*------------------------------------*/
NoUnits_Found:                                                   // @V97351

  if ( Verbose )
  {
    PrintInfo( AdapterTable );
  }

  /*-------------------------------------------*/
  /* Check if we have successfully initialized */
  /* any devices.                              */
  /*                                           */
  /* If so, then register with the kernel      */
  /* as an ADD driver.                         */
  /*-------------------------------------------*/
  if ( !(rc=InstallCleanup()) )
  {
    StartMotorTimer();

    pRPO->CodeEnd     = (USHORT) &DriveInit;
    pRPO->DataEnd     = (USHORT) npACBPool;
    pRPO->rph.Status  = STDON;


    if ( DevHelp_RegisterDeviceClass( (NPSZ)     AdapterName,
                                      (PFN)      &FLP1ADDEntry,
                                      (USHORT)   0,
                                      (USHORT)   1,
                                      (PUSHORT)  &ADDHandle      )  )
    {
      _asm { int 3 }
    }
    CreateRMStructures();

  }

FLP1_Deinstall:

  if ( rc )
  {
    if ( Verbose )
    {
      TTYWrite( UninstallMsg );
    }

    RMDestroyDriver(hDriver);      /*V98451*/

    pRPO->CodeEnd     = 0;
    pRPO->DataEnd     = 0;
    pRPO->rph.Status  = STDON + STERR + ERROR_I24_QUIET_INIT_FAIL;
  }

}

/*------------------------------------*/
/* SetupADDVars                       */
/*                                    */
/* This routine sets up various global*/
/* variables for the ADD driver.      */
/*                                    */
/*------------------------------------*/

VOID NEAR SetupADDVars()
{
  SELDESCINFO           SwapCodeDesc;

  /*--------------------------------*/
  /* Linear Address of data segment */
  /*--------------------------------*/

  (PBYTE) plDataSeg = (PBYTE) &plDataSeg;
  if ( DevHelp_VirtToLin( (SEL)   SELECTOROF(plDataSeg),
                          (ULONG) 0,
                          (PLIN)  &plDataSeg   ) )
  {
    _asm int 3
  }

  plADDLockHandle  = plDataSeg + (USHORT) &ADDLockHandle[0];

  /*----------------------------------------*/
  /* Linear Address of swap code and length */
  /*----------------------------------------*/

  (PBYTE) plSwapCode = (PBYTE) &StartOSM;
  if ( DevHelp_GetDescInfo( (SEL)   SELECTOROF(plSwapCode),
                            (PBYTE) &SwapCodeDesc          ) )
  {
    _asm int 3
  }

  plSwapCode  = SwapCodeDesc.BaseAddr;
  SwapCodeLen = SwapCodeDesc.Limit + 1;

  /*-------------------------------------------*/
  /* Point to KERNEL Interrupt Level indicator */
  /*-------------------------------------------*/

  if ( DevHelp_GetDOSVar( (USHORT) DHGETDOSV_INTERRUPTLEV,
                          (USHORT) 0,
                          (PPVOID) &pCurIntLvl   )  )
  {
    _asm int 3
  }
}


/*-------------------------------------*/
/* SetupDMABuffers                     */
/*                                     */
/* This routine determines the unit    */
/* on each controller with the largest */
/* track size and allocates the        */
/* appropriate DMA buffer.             */
/*                                     */
/*-------------------------------------*/

VOID NEAR SetupDMABuffers()
{
  NPACB         npACB;
  NPUCB         npUCB;

  NPGEOMETRY    npGeo;

  USHORT        i;
  USHORT        MaxFormFactor = 0;

  for ( i=0; i < MAX_ADAPTERS; i++ )
  {
    /*----------------------------------------*/
    /* Scan units on this adapter for maximum */
    /* Form Factor unit                       */
    /*----------------------------------------*/

    if ( npACB = ACBPtrs[i].npACB )
    {
      if ( npUCB = npACB->npFirstUCB )                               /*@V95460*/
      {
        while( npUCB )
        {
          if ( npUCB->FormFactor > MaxFormFactor )
          {
            MaxFormFactor = npUCB->FormFactor;
          }
          npUCB = npUCB->npNextUCB;
        }

        /*--------------------------------------*/
        /* Convert Form Factor to track buffer  */
        /* length and allocate buffer.          */
        /*--------------------------------------*/
        if ( MaxFormFactor )
        {
          npGeo  = &MediaLookUp[MaxFormFactor].npMIF[0]->Geo;

          npACB->DMABufLen = (USHORT) npGeo->BytesPerSector *
                             (USHORT) npGeo->SectorsPerTrack;

          GetDMABuffer( npACB );
        }
      }                                                              /*@V95460*/
    }
  }
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* Hardware Initialization Routines                                       */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* ConfigureController                */
/*                                    */
/* This routine performs controller   */
/* level initizliation.               */
/*                                    */
/*------------------------------------*/

USHORT FAR ConfigureController( NPATBL npAT )
{
  NPUTBL         npUT;

  NPACB          npACB;
  NPACB          npACBX;

  USHORT         rc = 0;

  USHORT         Port;
  USHORT         Value;

  USHORT         i;
  RESOURCESTRUCT Resource;
  PAHRESOURCE    pResourceList;

  pResourceList = (PAHRESOURCE)&npAT->ResourceBuf;

  pResourceList->NumResource  = 4;
  pResourceList->hResource[RES_IRQ_ENTRY] = 0L;
  pResourceList->hResource[RES_IO1_ENTRY] = 0L;
  pResourceList->hResource[RES_IO2_ENTRY] = 0L;
  pResourceList->hResource[RES_DMA_ENTRY] = 0L;


  /*-----------------------*/
  /* AllocIRQResource      */
  /*-----------------------*/

  Resource.ResourceType            = RS_TYPE_IRQ;
  Resource.IRQResource.IRQLevel    = npAT->IRQLevel;
  Resource.IRQResource.PCIIrqPin   = RS_PCI_INT_NONE;
  Resource.IRQResource.IRQFlags    = RS_IRQ_MULTIPLEXED;

  if ( (rc = RMAllocResource( hDriver,
                              &pResourceList->hResource[RES_IRQ_ENTRY],
                              &Resource ))  )
  {
    npAT->Status = ATS_ALLOC_IRQ_FAILED;
    goto ConfigureControllerExit;
  }


  /*----------------------*/
  /* AllocIOResource      */
  /*----------------------*/

  Resource.ResourceType              = RS_TYPE_IO;
  Resource.IOResource.BaseIOPort     = npAT->BasePort;
  Resource.IOResource.NumIOPorts     = FCR_DATA+1;
  Resource.IOResource.IOFlags        = RS_IO_EXCLUSIVE;
  Resource.IOResource.IOAddressLines = 16;

  if ( (rc = RMAllocResource( hDriver,
                              &pResourceList->hResource[RES_IO1_ENTRY],
                              &Resource ))  )
  {
    npAT->Status = ATS_ALLOC_IO_FAILED;
    goto ConfigureControllerExit;
  }

  /*----------------------*/
  /* AllocIOResource      */
  /*----------------------*/

  Resource.ResourceType              = RS_TYPE_IO;
  Resource.IOResource.BaseIOPort     = npAT->BasePort+FCR_DIR;
  Resource.IOResource.NumIOPorts     = 1;
  Resource.IOResource.IOFlags        = RS_IO_EXCLUSIVE;
  Resource.IOResource.IOAddressLines = 16;

  if ( (rc = RMAllocResource( hDriver,
                              &pResourceList->hResource[RES_IO2_ENTRY],
                              &Resource ))  )
  {
    npAT->Status = ATS_ALLOC_IO_FAILED;
    goto ConfigureControllerExit;
  }

  /*-----------------------*/
  /* AllocDMAResource      */
  /*-----------------------*/

  Resource.ResourceType           = RS_TYPE_DMA;
  Resource.DMAResource.DMAChannel = npAT->DMAChannel;
  Resource.DMAResource.DMAFlags   = RS_DMA_MULTIPLEXED;

  if ( (rc = RMAllocResource( hDriver,
                              &pResourceList->hResource[RES_DMA_ENTRY],
                              &Resource ))  )
  {
    npAT->Status = ATS_ALLOC_DMA_FAILED;
    goto ConfigureControllerExit;                           /*V98451 End */
  }


  /*--------------------*/
  /* Allocate new ACB   */
  /*--------------------*/
  npUCBLast = 0;

  npACB = npAT->npACB = (NPACB) InitAllocate( sizeof(ACB) );

  ACBPtrs[cAdapters].npACB    = npACB;
  ACBPtrs[cAdapters].HWRIndex = 0;
  ACBPtrs[cAdapters].IRQLevel = npAT->IRQLevel;

  npAT->ACBIndex              = cAdapters;                           /*@V85053*/

  /*-----------------------------------------*/
  /* Setup IO Port addresses and preliminary */
  /* initialization                          */
  /*-----------------------------------------*/
  for (i = FCR_SRA; i <= FCR_DIR; i++ )
  {
    npACB->IOPorts[i] = npAT->BasePort + i;
  }

  npACB->CurUnitId = -1;
  npACB->CurCyl    = -1;
  npACB->Flags    |= ACBF_SM_IDLE;

  npACB->IRQLevel     = npAT->IRQLevel;
  npACB->IRQTimer     = IRQ_TIMEOUT;
  npACB->SettleTime   = SEEK_SETTLE_TIME;
  npACB->IRQEntry     = ACBPtrs[cAdapters].IRQHandler;
  npACB->pDMABuf = 0;
  npACB->DMAChannel = npAT->DMAChannel;


  /*----------------------------------*/
  /* Setup Context Hook for off-level */
  /* interrupt processing             */
  /*----------------------------------*/

  if ( DevHelp_AllocateCtxHook((NPFN)   StateCtxHookRtn,
                               (PULONG) &npACB->StateHookHandle ) )
  {
    _asm int 3
  }
  /*---------------------------------------------*/
  /* Allocate a selector to move data into/outof */
  /* the ACB's DMA buffer                        */
  /*---------------------------------------------*/

  if ( DevHelp_AllocGDTSelector( (PSEL)   &SELECTOROF(npACB->pDMABuf),
                                 (USHORT) 1  ) )
  {
    _asm int 3
  }

  /*------------------------------------*/
  /* If this is the first adapter, then */
  /* calibrate I/O loops                */
  /*------------------------------------*/

  if ( !cAdapters++ )
  {
    CalibrateTimers( npACB );
  }

  /*---------------------------------------------*/
  /* Activate the ACB                            */
  /*                                             */
  /* This locks the code in memory and allocates */
  /* the requested IRQ level                     */
  /*---------------------------------------------*/

  if ( ActivateACB( npACB ) )
  {
    npAT->Status = ATS_SET_IRQ_FAILED;
    goto ConfigureControllerExit;
  }

  /*------------------------------------------*/
  /* Setup the Hardware Resource structure    */
  /*                                          */
  /* Initially all controllers are assumed to */
  /* share the same hardware resources        */
  /*------------------------------------------*/

  npACB->HWResourceIndex    = 0;

  if ( npACBX = HWResource[0].npFirstACBX )                          /*@V95460*/
  {                                                                  /*@VVVVVV*/
    while( npACBX->npNextACBX )
    {
      npACBX = npACBX->npNextACBX;
    }
    npACBX->npNextACBX = npACB;

  }
  else
  {
    HWResource[0].npFirstACBX = npACB;                               /*@VAAAAA*/
  }                                                                  /*@V95460*/


  /*---------------------------------------------*/
  /* Some BIOS suppliers <Longshire> are sloppy  */
  /* concerning taking secondary controllers     */
  /* off line when they are not being used!      */
  /*---------------------------------------------*/
  if ( npAT == &AdapterTable[0] )
  {
    if ( !(AdapterTable[1].Flags & ATBF_DISABLED) )
    {
      Port  = AdapterTable[1].BasePort + 2;
      Value = FCR_DOR_RESET;
      outp( Port, Value );
    }
  }

  /*---------------------------------------------*/
  /* If the part was initially reset, remove the */
  /* reset condition.                            */
  /*---------------------------------------------*/
  DISABLE
  Port = npACB->IOPorts[FCR_DOR];
  Value = npACB->IORegs[FCR_DOR] = (FCR_DOR_RESET | FCR_DOR_DMA_GATE);
  outp( Port, Value );
  ENABLE
  IODelay();

  /*----------------------------------------------*/
  /* Perform an initial interrupt driven reset to */
  /* verify the controller is functioning         */
  /*----------------------------------------------*/

  if ( InitControllerReset( npACB ) )
  {
    npAT->Status = ATS_RESET_FAILED;
    goto ConfigureControllerExit;
  }

  /*--------------------------------------------*/
  /* Check if the part supports FIFO operation. */
  /* Configure the FIFO if supported            */
  /*--------------------------------------------*/
  CheckFIFOSupport( npAT );                                          /*@V95460*/

  /*---------------------------------------------*/
  /* Check for Media Sense support and determine */
  /* the polarity of the Changeline              */
  /*---------------------------------------------*/
  npAT->Flags |= CheckMediaIDSupport( npAT );

  npUT = npAT->Unit;

  for ( i=0; i < MAX_DRIVES; i++, npUT++ )
  {
    if ( !(npUT->Flags & UTBF_USER_MEDIASENSE) )                     /*@V85053*/
    {
      if ( npAT->Flags & ATBF_NO_MEDIASENSE )
      {
        npUT->Flags |= UTBF_NO_MEDIASENSE;
      }
    }

    if ( !(npUT->Flags & UTBF_USER_CHANGELINE) )
    {
      if ( npAT->Flags & ATBF_CHANGELINE_INVERT )
      {
        npUT->Flags |= UTBF_CHANGELINE_INVERT;
      }
    }
  }

ConfigureControllerExit:

  /*------------------------------------------*/
  /* If we had any problems with the adapter, */
  /* mark all attached units as non-installed */
  /* so we dont erroneously report them       */
  /*------------------------------------------*/


  if ( npAT->Status )
  {
    if(pResourceList->hResource[0])
       RMDeallocResource( hDriver, pResourceList->hResource[0]);  /*V98451*/
    if(pResourceList->hResource[1])
       RMDeallocResource( hDriver, pResourceList->hResource[1]);  /*V98451*/
    if(pResourceList->hResource[2])
       RMDeallocResource( hDriver, pResourceList->hResource[2]);  /*V98451*/

    for ( i = 0; i < MAX_DRIVES; i++ )
    {
      npAT->Unit[i].Flags &= ~UTBF_INSTALLED;
    }
  }
  return ( npAT->Status );
}

/*------------------------------------*/
/* ConfigureUnit                      */
/*                                    */
/* This routine performs drive level  */
/* initialization.                    */
/*                                    */
/*------------------------------------*/

USHORT NEAR ConfigureUnit( NPATBL npAT, USHORT UnitId )
{
  NPACB         npACB;
  NPUCB         npUCB;
  NPUTBL        npUT;

  NPMEDIAINFO   npMIA;
  NPMEDIAINFO   npMIF;

  USHORT        n;
  USHORT        i;

  USHORT        Port;
  USHORT        Value;

  npUT         = &npAT->Unit[UnitId];
  npUT->Status = 0;

  /*------------------------------------------------*/
  /* If the user has not defined the drive type     */
  /* on the command line, then check if there       */
  /* is BIOS information available for the drive.   */
  /*------------------------------------------------*/

  if ( !(npUT->Flags & UTBF_INSTALLED) )
  {
    if ( GetInt13Info( npUT, BIOSUnit ) )
    {
      if ( BIOSUnit == 1 )
      {
        BIOSUnit++;
      }
      goto ConfigureUnit_Exit;
    }
  }

  npACB = npAT->npACB;

  npUCB = (NPUCB) InitAllocate( sizeof(UCB) );

  /*----------------------------------------*/
  /* Initialize new UCB                     */
  /*                                        */
  /* Information is transfered from the     */
  /* UTBL structure which contains command  */
  /* line information to the new UCB.       */
  /*                                        */
  /*----------------------------------------*/

  npUCB->npACB  = npACB;
  npUCB->UnitId = UnitId;

  npUCB->FormFactor = npUT->FormFactor;

  if ( npUT->Flags & UTBF_NO_CHANGELINE )
  {
    npUCB->Flags |= UCBF_NO_CHANGELINE;
  }

  if ( npUT->Flags & UTBF_CHANGELINE_INVERT )
  {
    npUCB->Flags |= UCBF_CHANGELINE_INVERT;
  }

  if ( npUT->Flags & UTBF_NODASDSUPPORT )
  {
    npUCB->Flags |= UCBF_NO_DASD_SUPPORT;
  }

  /*-----------------------------------*/
  /* Media Sense is only allowed on    */
  /* 2.88MB floppy devices             */
  /*-----------------------------------*/
  if ( npUT->Flags & UTBF_NO_MEDIASENSE  ||
     ( !(npUT->Flags & UTBF_USER_MEDIASENSE) &&                      /*@V95460*/
       (npUT->FormFactor < MI_FMFC_2880) ) )                         /*@V95460*/
  {
    npUT->Flags  |= UTBF_NO_MEDIASENSE;
    npUCB->Flags |= UCBF_NO_MEDIASENSE;
  }

  if ( BIOSUnit > 1 )
  {
    npUCB->Flags |= UCBF_NOT_DRIVE_AB;
  }

  if ( npUT->Flags & UTBF_BIOS_SUPPORT )
  {
    npUCB->Flags |= UCBF_BIOS_SUPPORT;
  }

  npUCB->Flags |= UCBF_MEDIA_UNCERTAIN;

  npUCB->BIOSId = npUT->BIOSId;

  /*-----------------------------------*/
  /* Set increased Motor-On delay for  */
  /* 5.25 inch drives                  */
  /*-----------------------------------*/

  npUCB->MotorOnTimer = MOTOR_ON_DELAY_35INCH;

  if ( (npUT->FormFactor == MI_FMFC_0360)
          || (npUT->FormFactor == MI_FMFC_1200) )
  {
    npUCB->MotorOnTimer = MOTOR_ON_DELAY_525INCH;
  }

  /*-----------------------------------------------*/
  /* Setup MEDIAINFO structure                     */
  /*                                               */
  /* Lookup allowable settings/geometries for      */
  /* this drive FormFactor.                        */
  /*                                               */
  /* Allocate a MediaInfo structure for this drive */
  /* and copy the init table information into      */
  /* it. The first two slots in the MediaInfo      */
  /* table are reserved for Logical and User       */
  /* Defined settings                              */
  /*-----------------------------------------------*/

  n     = MediaLookUp[npUT->FormFactor].Count + MII_DEFAULT;

  npMIF = (NPMEDIAINFO) InitAllocate( n * sizeof(MEDIAINFO) );

  npUCB->npMediaInfo    = npMIF;
  npUCB->MediaInfoCount = n;
  npUCB->MediaInfoIndex = MII_DEFAULT;

  for ( i=0; i < n-MII_DEFAULT; i++ )
  {
    npMIA = MediaLookUp[npUT->FormFactor].npMIF[i];

     memcpy( &npMIF[MII_DEFAULT+i],
            npMIA,
            sizeof(MEDIAINFO) );

    /*-----------------------------------------------*/
    /* For implied seeks, increase Specify 2 to 15ms */
    /* This provides the settle time when an implied */
    /* seek is done.                                 */
    /*-----------------------------------------------*/
    npMIA = &npMIF[MII_DEFAULT+i];

    if ( (npACB->SeekFlags & ACBK_ISEEK_CAPABLE)
                         && !(npMIA->Flags & MIF_DOUBLE_STEP)  )

    {
      npMIA->Specify[1] = HLTDelay15ms[npMIA->DataRate] << 1;
    }

    /*---------------------------------------------------*/
    /* Some 1.2MB drives decrease their spindle speeds   */
    /* to 300RPM when reading low capacity media (360KB) */
    /* to retain compatibility with the original 360KB   */
    /* drives.                                           */
    /* We decrease the data rate to 250Kbps in this case.*/
    /*---------------------------------------------------*/
    if ( (npUT->Flags & UTBF_DUALSPEED)
             && (npUT->FormFactor == MI_FMFC_1200) )
    {
      npMIA           = &npMIF[MII_DEFAULT+MAX_MEDIA_1200-1];
      npMIA->DataRate = MI_RATE_0360;
    }

  }

  if ( npUT->Flags & UTBF_SPECIFY )
  {
    CalcSpecifyBytes( npUCB, npUT->Specify[0], npUT->Specify[1] );
  }

  /*-----------------------------------------*/
  /* Attach the new UCB to the ACB or to the */
  /* existing UCB chain                      */
  /*-----------------------------------------*/
  if ( npUCBLast )
  {
    npUCBLast->npNextUCB =  npUCB;
  }
  else
  {
    npACB->npFirstUCB = npUCB;
  }

  /*------------------------------------------*/
  /* RECAL the drive to make sure its really  */
  /* installed.                               */
  /*                                          */
  /* Non-installed drives will fail the RECAL */
  /* since the TRK0 indication will not occur */
  /* within 80 steps.                         */
  /*------------------------------------------*/

  if ( !CheckUnitInstalled( npUCB ) )
  {
    npUCBLast = npUCB;

    npAT->Unit[npUCB->UnitId].npUCB = npUCB;

    npACB->cUnits++;
    BIOSUnit++;
    npAT->cUnits++;
    npUT->Flags  |= UTBF_INSTALLED;
    cUnits++;

  }


  /*------------------------------------------*/
  /* Drive failed to RECAL                    */
  /*                                          */
  /* Turn off the drive motor and deallocate  */
  /* the UCB.                                 */
  /*------------------------------------------*/
  else
  {
    npUT->Status = UTS_RECAL_FAILED;

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

    if ( npUCBLast )
    {
      npUCBLast->npNextUCB =  0;
    }
    else
    {
      npACB->npFirstUCB = 0;
    }

    InitDeAllocate( sizeof(UCB) + n * sizeof(MEDIAINFO) );
  }

  ConfigureUnit_Exit:

  return ( npUT->Status );
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Optional Hardware Feature Detection                                    */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* CheckMediaIDSupport                */
/*                                    */
/* This routine determines whether    */
/* the controller can detect the      */
/* type of media (1MB, 2MB, 4MB)      */
/* by sensing the position of the     */
/* hole on the lower left side of     */
/* the diskette.                      */
/*                                    */
/*------------------------------------*/

USHORT NEAR CheckMediaIDSupport( NPATBL npAT )
{
  NPACB         npACB;                                               /*@V95460*/

  USHORT        DORPort;
  USHORT        TDRPort;
  USHORT        DIRPort;
  USHORT        DSRPort;

  USHORT        Flags = 0;

  USHORT        MediaIDSupported = 0;
  USHORT        i;
  UCHAR         Value;

  npACB   = npAT->npACB;                                             /*@V95460*/
  DORPort = npACB->IOPorts[FCR_DOR];                                 /*@V95460*/
  TDRPort = npACB->IOPorts[FCR_TDR];                                 /*@V95460*/
  DIRPort = npACB->IOPorts[FCR_DIR];                                 /*@V95460*/
  DSRPort = npACB->IOPorts[FCR_DSR];                                 /*@V95460*/

  /*------------------------------------------------------*/
  /* Read the Tape Drive Register                         */
  /*                                                      */
  /* The value in this register is read after selecting   */
  /* each drive to determine whether the register is      */
  /* actually implemented in the Floppy controller chip.  */
  /*                                                      */
  /*------------------------------------------------------*/

  /*--------------------------------------------*/
  /* Select drives 0-3 and turn on motor enable */
  /*--------------------------------------------*/

  npACB->IORegs[FCR_DOR] &= ~(FCR_DOR_MOTOR_ENABLE                   /*@V95460*/
                                          | FCR_DOR_DRIVE_SEL);      /*@V95460*/

  for ( i = 0; i < MAX_DRIVES; i++ )
  {
    Value = npACB->IORegs[FCR_DOR] | (FCR_DOR_MOTOR_0 << i) | i;     /*@V95460*/
    outp( DORPort, Value);
    IODelay();

    inp( TDRPort, Value );
    IODelay();

    /*--------------------------------------------------*/
    /* If the register always reads FC then it is not   */
    /* implemented at all!                              */
    /*                                                  */
    /* Otherwise verify Boot Drive ID bits 2-3 = 00     */
    /*--------------------------------------------------*/
    if ( (Value & ~FCR_TDR_TAPE_SEL) != (UCHAR) ~FCR_TDR_TAPE_SEL )
    {
      if ( !(Value & FCR_TDR_BOOT_ID) )                              /*@V85053*/
      {
        MediaIDSupported = 1;
      }
    }
  }


  if ( uCMachine & BUSINFO_OEM_ABIOS )                       // @V92217
  {                                                          // @V92217
    MediaIDSupported = 0;                                    // @V92217
  }                                                          // @V92217

  /*---------------------------------------------*/
  /* Deselect all drives and read ChangeLine     */
  /*                                             */
  /* If ChangeLine bit is non-zero then we have  */
  /* an inverted change indication.              */
  /*---------------------------------------------*/

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

  outp( DORPort, Value );
  IODelay();

  inp( DIRPort, Value );

  if ( Value & FCR_DIR_DSK_CHG )
  {
    Flags |= ATBF_CHANGELINE_INVERT;
  }

  if ( !MediaIDSupported )
  {
    Flags |= ATBF_NO_MEDIASENSE;
  }

  /*----------------------------------------------*/
  /* Check for quirky IBM Notebook models         */
  /* whose designers cant seem to make a diskette */
  /* controller that conforms to the rest of      */
  /* the industry.                                */
  /*----------------------------------------------*/

  if ( IBMModel == IBM_MODEL_386 )
  {
    /*-----------------------------------------*/
    /* Turn off diskette power management for  */
    /* IBM Model L40SX.                        */
    /*                                         */
    /* We need to do this since the genius     */
    /* who designed the dskt controller set    */
    /* auto powerdown interval to 500ms so the */
    /* controller powers down during the motor */
    /* spin-up interval!                       */
    /*                                         */
    /* This is done by setting the PRECOMP     */
    /* field of DSR (3F4) to 001               */
    /*-----------------------------------------*/
    if ( IBMSubModel == IBM_SUBMODEL_L40SX )
    {
      outp( DSRPort, IBM_L40SX_PWR_DISABLE )
    }

    /*-----------------------------------------*/
    /* The Yamato lab seems to be able to      */
    /* design dskt controllers that fool the   */
    /* automatic changeline polarity detection */
    /* in the driver.                          */
    /*-----------------------------------------*/
    if ( IBMSubModel == IBM_SUBMODEL_N1 ||
               IBMSubModel == IBM_SUBMODEL_N2 )
    {
      Flags &= ~ATBF_CHANGELINE_INVERT;
    }
  }

  return ( Flags );
}

/*------------------------------------*/
/* CheckFIFOSupport                   */
/*                                    */
/* This routine checks if a newer     */
/* floppy controller is present which */
/* has a speed matching FIFO that     */
/* makes DMA underruns less likely.   */
/*                                    */
/*------------------------------------*/

VOID NEAR CheckFIFOSupport( NPATBL npAT )
{
  USHORT        Is82077   = 0;
  USHORT        SendError = 0;
  NPACB         npACB;

  npACB = npAT->npACB;

  npACB->CurResultsLen = FR_LEN_VERSION;
  npACB->CurCmdLen     = FC_LEN_VERSION;

  npACB->CurCmd[0] = FC_VERSION;

  if ( !(SendError=f_SendCommand( npACB, 0 )) )
  {
    if ( !f_ReadResults( npACB ) )
    {
      if ( npACB->CurResults[0] == FR_VERSION_I82077 )
      {
        Is82077 = 1;
      }
    }
  }

  if ( Is82077 )
  {
    npACB->CurCmdLen     = FC_LEN_CONFIGURE;

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

    if ( !(SendError=f_SendCommand( npACB, 0 )) )
    {
      npACB->SeekFlags     = ACBK_ISEEK_CAPABLE;

      npACB->CurCmdLen     = FC_LEN_LOCK;
      npACB->CurResultsLen = FR_LEN_LOCK;
      npACB->CurCmd[0]     = FC_LOCK;

      if ( !(SendError=f_SendCommand( npACB, 0 )) )
      {
        if ( !f_ReadResults( npACB ) )
        {
          if ( npACB->CurResults[0] == FR_LOCK )
          {
            npAT->Flags |= ATBF_FIFO_SUPPORT;

            /*---------------------------------------------*/
            /* Check for National Semiconductor controller */
            /* series 8477, 87311...                       */
            /*---------------------------------------------*/
            npACB->CurCmdLen     = FC_LEN_NSC;
            npACB->CurCmd[0]     = FC_NSC;

            npACB->CurResultsLen = FR_LEN_NSC;

            if ( !(SendError=f_SendCommand( npACB, 0 )) )
            {
              if ( !f_ReadResults( npACB ) )
              {
                if ( (npACB->CurResults[0] == FR_VERSION_N8477A)
                      || (npACB->CurResults[0] == FR_VERSION_N8477B) )
                {
                  npACB->SeekFlags |= ACBK_NATIONAL_8477;
                }
              }
            }
          }
        }
      }
    }
  }

  /*------------------------------------------------*/
  /* If there was a timeout sending a command       */
  /* to the controller, it is likely the controller */
  /* rejected this command or the previous command  */
  /*                                                */
  /* Result byte ST0 is read to clear pending       */
  /* results for the rejected command.              */
  /*------------------------------------------------*/

  if ( SendError )
  {
    npACB->CurResultsLen = FR_LEN_INVALID_CMD;
    f_ReadResults( npACB );
  }
}

/*------------------------------------------*/
/* Enable288Drives                          */
/*                                          */
/* This routine scans the configuration     */
/* for 2.88MB drives and issues commands    */
/* to the controller so that the higher     */
/* capacity media is written properly       */
/*                                          */
/*------------------------------------------*/

USHORT NEAR Enable288Drives()
{
  NPATBL        npAT;
  NPUTBL        npUT;
  NPACB         npACB;
  NPUCB         npUCB;

  USHORT        i;
  USHORT        j;
  UCHAR         DriveMask288;

  for ( i=0; i < MAX_ADAPTERS; i++ )
  {
    npAT = &AdapterTable[i];

    if ( npACB = npAT->npACB )
    {
      npUT  = npAT->Unit;

      /*-------------------------------------------------*/
      /* Scan drives in UnitTable for with Form Factors  */
      /* 2.88 and above. Setup drive mask to enable      */
      /* Perpendicular Recording mode support.           */
      /*-------------------------------------------------*/

      DriveMask288 = 0;

      npUCB = npACB->npFirstUCB;

      while ( npUCB )
      {
        if ( npUCB->FormFactor >= MI_FMFC_2880 )
        {
          DriveMask288 |= (0x04 << npUCB->UnitId);
        }
        npUCB = npUCB->npNextUCB;
      }

      /*---------------------------------------------------*/
      /* If any 2.88 MB drives were found, send command    */
      /* to enable Perpendicular Recording mode for those  */
      /* drives.                                           */
      /*---------------------------------------------------*/

      if ( DriveMask288 )
      {
        npACB->CurCmdLen = FC_LEN_PERPENDICULAR_MODE;

        npACB->CurCmd[0] = FC_PERPENDICULAR_MODE;
        npACB->CurCmd[1] = DriveMask288 | 0x80;

        if ( f_SendCommand( npACB, 0 ) )
        {
          /*---------------------------------------------*/
          /* If command issues fails, mark drives as not */
          /* supported.                                  */
          /*---------------------------------------------*/

          for ( j=0; j < MAX_DRIVES; j++, npUT++ )
          {
            if ( npUT->FormFactor >= MI_FMFC_2880 )
            {
              npUT->Status        = UTS_INCOMPATIBLE;
              if ( npUT->npUCB )
              {
                npUT->npUCB->Flags |= UCBF_NO_DASD_SUPPORT;
              }
            }
          }
        }
      }
    }
  }
}



/*------------------------------------------------------------------------*/
/*                                                                        */
/* Initialization I/O Routines                                            */
/*                                                                        */
/* These routines interface to the Inner State Machine to provide         */
/* init-time I/O services.                                                */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* CheckUnitInstalled                 */
/*                                    */
/* This routine issues a RECALIBTATE  */
/* request to the drive and returns   */
/* the result to the caller.          */
/*                                    */
/*------------------------------------*/

USHORT NEAR CheckUnitInstalled( NPUCB npUCB )
{
  NPACB         npACB;
  USHORT        rc;

  npACB           = npUCB->npACB;
  npACB->ReqFlags = (ACBR_FORCE_SELECT | ACBR_SPECIFY |
                     ACBR_MOTOR_ON     | ACBR_RECAL);

  rc = InitIORequest( npUCB );

  return( rc );
}


/*---------------------------------------*/
/* InitControllerReset                   */
/*                                       */
/* This routine issues an a CONTROLLER   */
/* RESET to verify that the controller   */
/* is installed and its IRQ level is     */
/* known.                                */
/*                                       */
/*---------------------------------------*/

USHORT NEAR InitControllerReset( NPACB npACB )
{
  USHORT        IRQTimer;                                            /*@V88786*/

  FLP1IOWait = 1;

  IRQTimer          = npACB->IRQTimer;                               /*@V88786*/
  npACB->IRQTimer   = INIT_RESET_TIMEOUT;                            /*@V88786*/

  npACB->ReqFlags  |= ACBR_RESET;
  npACB->ISMDoneRtn = InitIODone;
  npACB->State      = ACBS_NEXT_REQUEST;
  npACB->ISMRecal   = 0;

  AllocateHWResources( npACB );

  StartISM( npACB );

  InitIOWait();

  FreeHWResources( npACB );

  npACB->IRQTimer   = IRQTimer;                                      /*@V88786*/

  return ( (npACB->CurStatus == REQS_NO_ERROR) ? 0 : 1 );
}

/*------------------------------------*/
/* InitIORequest                      */
/*                                    */
/* This routine interfaces with the   */
/* Inner State Machine code to start  */
/* I/O requests at init-time          */
/*                                    */
/*------------------------------------*/

USHORT NEAR InitIORequest( NPUCB npUCB )
{
  NPACB         npACB;

  FLP1IOWait = 1;

  npACB             = npUCB->npACB;

  npACB->ISMDoneRtn = InitIODone;

  npACB->State      = ACBS_NEXT_REQUEST;

  npACB->npUCB      = npUCB;
  npACB->UnitId     = npUCB->UnitId;
  npACB->ISMRecal   = 0;

  AllocateHWResources( npACB );

  StartISM( npACB );

  InitIOWait();

  FreeHWResources( npACB );

  return ( (npACB->CurStatus == REQS_NO_ERROR) ? 0 : 1 );
}

/*------------------------------------*/
/* InitIOWait                         */
/*                                    */
/* This routine waits for requests    */
/* issued by the init-time I/O        */
/* routines to complete.              */
/*------------------------------------*/
VOID  NEAR InitIOWait()
{
  DISABLE
  while ( FLP1IOWait )
  {
    DevHelp_ProcBlock( (ULONG) &FLP1IOWait,
                       (ULONG) -1L,
                       (USHORT) WAIT_IS_NOT_INTERRUPTABLE );
    DISABLE
  }
}

/*------------------------------------*/
/* InitIODone                         */
/*                                    */
/* This routine is called from the    */
/* ISM NotifyRoutine. It wakes        */
/* the I/O requestor waiting in       */
/* InitIOWait.                        */
/*------------------------------------*/

VOID FAR InitIODone( NPACB npACB )
{
  USHORT        AwakeCount;

  FLP1IOWait = 0;
  DevHelp_ProcRun( (ULONG)   &FLP1IOWait,
                   (PUSHORT) &AwakeCount  );

  npACB->Flags |= ACBF_WAITSTATE;
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* Calibration Routines                                                   */
/*                                                                        */
/* These routines adjust the loop counts in various I/O                   */
/* workers to achieve the correct real-time delays and                    */
/* timeout values.                                                        */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* CalibrateTimers                    */
/*                                    */
/* This routine does calibration of   */
/* the IODelay and CheckMSRSend       */
/* functions                          */
/*                                    */
/*------------------------------------*/
VOID FAR CalibrateTimers( NPACB npACB )
{
  /*----------------------------------------*/
  /* Set a known loop count for each worker */
  /*----------------------------------------*/

  MSRWaitCount    = CALIBRATE_LOOP_COUNT;
  IODelayCount    = CALIBRATE_LOOP_COUNT;

  /*----------------------------------------------------*/
  /* CalibrateWorker returns the loop count for the     */
  /* worker routine to achieve a delay of 1ms.          */
  /*                                                    */
  /* This loop count is scaled up (or down) to achieve  */
  /* the requested realtime delay.                      */
  /*                                                    */
  /* Note: CheckMSRSend is dependent on the IODelay     */
  /*       worker! Therefore IODelay must be calibrated */
  /*       first.                                       */
  /*----------------------------------------------------*/

  IODelayCount    = CalibrateWorker( npACB, (PCV) &IODelay ) / uSPerMS;

  if ( !IODelayCount ) IODelayCount = 1;

  MSRWaitCount = CalibrateWorker( npACB, (PCV) &CheckMSRSend ) * MAX_WAIT_MSR;

  if ( !MSRWaitCount ) MSRWaitCount = 100;
}


/*-----------------------------------------------*/
/* CalibrateWorker                               */
/*                                               */
/* We call the worker routine with a known loop  */
/* count for a known realtime interval.          */
/*                                               */
/*-----------------------------------------------*/
ULONG FAR CalibrateWorker( NPACB npACB, PCV pWorker )
{
  ULONG         CallCount      = 0;

  Calibrate      = 1;
  CallWorker     = 1;
  CallWorkerSync = 1;

  if ( ADD_StartTimerMS((PULONG) &CalibrateTimerHandle,
                        (ULONG)  CALIBRATE_TIMER_INTERVAL,
                        (PFN)    CalibrateTimer,
                        (ULONG)  0,
                        (ULONG)  0              ) )
  {
     _asm { int 3 }
  }


  /*--------------------------------------*/
  /* Synchronize with the begining of the */
  /* next timer interval                  */
  /*--------------------------------------*/
  while ( CallWorkerSync )
    ;

  /*--------------------------------------*/
  /* Call the worker until the interval   */
  /* expires.                             */
  /*--------------------------------------*/
  while ( CallWorker )
  {
    (*pWorker)( npACB );

    CallCount++;
  }

  ADD_CancelTimer( CalibrateTimerHandle );

  Calibrate = 0;

  /*-----------------------------------------------*/
  /* The number of calls to the worker times the   */
  /* known worker loop count divided by the        */
  /* realtime interval in miliseconds gives us     */
  /* the proper loop count for 1ms of delay.       */
  /*                                               */
  /* Note: The timer package adds 1 tick to the    */
  /*       calculated interval to compensate       */
  /*       for it not being aligned. We factor     */
  /*       this out by using this alternate        */
  /*       define for the interval.                */
  /*-----------------------------------------------*/

  CallCount *= CALIBRATE_LOOP_COUNT;

  return ( CallCount / CALIBRATE_INTERVAL_ACTUAL );
}


/*---------------------------------------*/
/* CalibrateTimer                        */
/*                                       */
/* This routine is called by the         */
/* timer handler each time a calibration */
/* interval expires                      */
/*                                       */
/* Normally two calibration intervals    */
/* elapse. One so that we can sync up    */
/* with the timer and one to do the      */
/* calibration.                          */
/*                                       */
/*---------------------------------------*/
VOID FAR CalibrateTimer( ULONG hCalibrateTimer, ULONG Unused1, ULONG Unused2 )
{
  if ( CallWorkerSync )
  {
    CallWorkerSync = 0;
  }
  else
  {
    CallWorker = 0;
  }
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Specify Byte Routines                                                  */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* CalcSpecifyBytes                   */
/*                                    */
/* This routine takes a pair of       */
/* specify bytes as input and adjusts */
/* the MediaInfo tables in the UCB    */
/* to use these specify bytes.        */
/*                                    */
/* The SRT field of Specify[1] is     */
/* adjusted to compensate for various */
/* data rates.                        */
/*                                    */
/*------------------------------------*/

VOID NEAR CalcSpecifyBytes(NPUCB npUCB, USHORT Specify1, USHORT Specify2)
{
  NPMEDIAINFO   npMIF;

  USHORT        DRate;
  USHORT        SRTValue;
  USHORT        SRTNewValue;
  USHORT        SRTuS10;
  USHORT        i;

  /*--------------------------------*/
  /* Get data rate index at default */
  /* drive settings.                */
  /*--------------------------------*/
  npMIF = &npUCB->npMediaInfo[MII_DEFAULT];
  DRate = npMIF->DataRate;

  /*-------------------------*/
  /* Isolate step rate index */
  /*-------------------------*/
  SRTValue = Specify1 >> 4;
  Specify1 &= ~FC_SRT_MASK;

  /*---------------------------------------*/
  /* Multiply step rate index by step rate */
  /* table entry to get actual step rate   */
  /* in microseconds * 10.                 */
  /*---------------------------------------*/
  SRTuS10  = (16-SRTValue) * SRTRate[DRate];

  /*--------------------------------------*/
  /* Adjust step rate index for to give   */
  /* same (or greater) step rate interval */
  /* at other data rates.                 */
  /*--------------------------------------*/
  for (i=MII_DEFAULT; i < npUCB->MediaInfoCount; i++, npMIF++ )
  {
    DRate = npMIF->DataRate;

    SRTNewValue  = 16 - (SRTuS10 / SRTRate[DRate]);
    SRTNewValue -= ((SRTuS10 % SRTRate[DRate]) ? 1 : 0);

    npMIF->Specify[0] = (Specify1 | (SRTNewValue << 4));
    npMIF->Specify[1] =  Specify2;
  }
}


/*------------------------------------*/
/* AdjustSpecifyBytes                 */
/*                                    */
/* This routine adjusts the specify   */
/* bytes for certain uC machines      */
/* with drives that cannot handle     */
/* the standard 3ms step rate.        */
/*                                    */
/*------------------------------------*/

VOID NEAR AdjustSpecifyBytes( NPATBL npAT )
{
  NPACB                 npACB;
  NPUCB                 npUCB;
  USHORT                Spec_0;
  USHORT                Spec_1;
  PDSKTPARMTABLE        pDPT;

  /*-----------------------------------------*/
  /* Old uC cannot handle the 3ms step rate  */
  /* everyone else uses!                     */
  /*-----------------------------------------*/

  if ( uCMachine )
  {
    npACB = npAT->npACB;

    if ( npACB && (npAT->Flags & ATBF_DSKTPARM_OBTAINED)  )
    {
      /*----------------------------------------*/
      /* Yes...Its a 1.44 drive but its a slug! */
      /*----------------------------------------*/
      pDPT   = &npAT->DsktParmTbl;
      Spec_0 = pDPT->Specify[0];
      Spec_1 = pDPT->Specify[1];

      if ( (pDPT->SectorsPerTrack == MI_SECT_1440)
             && ((Spec_0 & FC_SRT_MASK) < 0xD0)     )
      {
        npUCB = npACB->npFirstUCB;

        while ( npUCB )
        {
          CalcSpecifyBytes( npUCB, Spec_0, Spec_1 );
          npUCB = npUCB->npNextUCB;
        }
      }
    }
  }
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* BIOS Interface Routines                                                */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* RestoreBIOSCyl                     */
/*                                    */
/* This routine restores Drive 0      */
/* to its original position and data  */
/* rate asindicated in the BIOS data  */
/* area.                              */
/*                                    */
/*------------------------------------*/

VOID NEAR RestoreBIOSCyl()
{
  NPATBL        npAT;
  NPACB         npACB;
  NPUCB         npUCB;

  USHORT        i;

  UCHAR         BIOSDataRate;
  UCHAR         BIOSCyl;

  npAT = AdapterTable;


  /*--------------------------------------*/                         /*@V95460*/
  /* Turn all drive motors off.           */                         /*@VVVVVV*/
  /*                                      */
  /* The motor for Drive 0 will be turned */
  /* back on by the code below.           */
  /*--------------------------------------*/
  for ( i = 0; i < MAX_ADAPTERS; i++ )
  {
    if ( npACB = npAT[i].npACB )
    {                                                                /*@AAAAAA*/
      TurnMotorsOff( npACB );                                        /*@V95460*/
    }
  }

  if ( !(npAT->Flags & ATBF_DSKTPARM_OBTAINED) )
  {
    goto RestoreBIOSExit;
  }

  BIOSCyl      = npAT->Unit[0].BIOSCyl;
  BIOSDataRate = npAT->Unit[0].BIOSMediaState >> 6;


  /*---------------------------------------*/
  /* Locate UCB that corresponds to BIOS   */
  /* Unit 0                                */
  /*---------------------------------------*/

  npUCB = 0;

  for ( i = 0; i < MAX_ADAPTERS; i++, npAT++ )
  {
    if ( npACB = npAT->npACB )
    {
      npUCB = npACB->npFirstUCB;

      while ( npUCB )
      {
        if ( !npUCB->BIOSId )
        {
          goto Unit0Found;
        }
        npUCB = npUCB->npNextUCB;
      }
    }
  }

  Unit0Found:

  /*-----------------------------------*/
  /* Restore settings for BIOS Unit 0  */
  /*-----------------------------------*/
  if ( npUCB )
  {
    /*-----------------------------------------*/
    /* Select data rate for unit to match BIOS */
    /*-----------------------------------------*/
    for ( i=MII_DEFAULT; i < npUCB->MediaInfoCount; i++ )
    {
      if ( BIOSDataRate == npUCB->npMediaInfo[i].DataRate )
      {
        npUCB->MediaInfoIndex = i;
        break;
      }
    }
    /*----------------------------------*/
    /* Position drive to original BIOS  */
    /* cylinder                         */
    /*----------------------------------*/
      npACB->ReqFlags = (ACBR_FORCE_SELECT | ACBR_MOTOR_ON |
                         ACBR_SEEK         | ACBR_MOTOR_WAIT );

      npACB->ReqCyl   = BIOSCyl;

      InitIORequest( npUCB );
  }

  RestoreBIOSExit: ;
}


/*------------------------------------*/
/* GetInt13Info                       */
/*                                    */
/* This routine searches the ROMCFG   */
/* packets provided by the OS/2       */
/* loader to obtain BIOS infomation   */
/* describing the installed drives.   */
/*                                    */
/*------------------------------------*/

USHORT NEAR GetInt13Info( NPUTBL npUT, USHORT DriveId )
{
  PROMCFG       pROMCFG;
  PROMCFG       pROMCFG0;
  USHORT        rc = 1;
  GEOMETRY      Geo;

  pROMCFG0 = (PROMCFG) MAKEP(SELECTOROF(pDDD_Parm_List),
                              (USHORT) pDDD_Parm_List->disk_config_table);

  npUT->BIOSId = -1;

  pROMCFG = pROMCFG0;

  while ( OFFSETOF(pROMCFG) )
  {
    if ( (pROMCFG->ROMdevnbr == DriveId) && !(pROMCFG->ROMflags & ROMfixed) )
    {
      npUT->Flags |= UTBF_BIOS_SUPPORT;
      npUT->BIOSId = DriveId;

      /*---------------------------------------------------*/
      /* If the unit is already marked 'INSTALLED' then    */
      /* an override geometry was provided on the command  */
      /* line.                                             */
      /*---------------------------------------------------*/
      Geo.SectorsPerTrack = pROMCFG->ROMsecptrk;
      Geo.NumHeads        = pROMCFG->ROMheads;
      Geo.TotalCylinders  = pROMCFG->ROMcyls;

      if ( (npUT->FormFactor = GeomToFormFactor(&Geo)) != -1 )
      {
        rc = 0;
      }

      if ( !(npUT->Flags & UTBF_USER_CHANGELINE) )
      {
        if ( !(pROMCFG->ROMflags & ROMchgline) )
        {
          npUT->Flags |= UTBF_NO_CHANGELINE;
        }
      }
      break;
    }
    OFFSETOF(pROMCFG) = pROMCFG->ROMlink;
  }

  return( rc );

}

/*------------------------------------*/
/* GetBIOSDsktInfo                    */
/*                                    */
/* This routine reads the BIOS Data   */
/* Area (40:0) to obtain positioning  */
/* information for drives 0,1 so      */
/* we can restore the drive settings  */
/* after we have initialized.         */
/*                                    */
/* This routine also copies the       */
/* contents of the Diskette Parameter */
/* Table pointed by DOS Int 1E.       */
/*                                    */
/*------------------------------------*/

VOID NEAR GetBIOSDsktInfo()
{
  PDSKTPARMTABLE        pDDP;

  ULONG                 ppDDP;
  PULONG                BIOSVec;

  PUCHAR                pBIOS;

  NPATBL                npAT;
  NPUTBL                npUT;

  USHORT                ModeFlag;
  USHORT                i;


  npAT = &AdapterTable[0];

  /*------------------------------------*/
  /* Address Diskette Parameter Table.  */
  /*                                    */
  /* Copy contents to AdapterTable[0]   */
  /*------------------------------------*/
  if ( !DevHelp_PhysToVirt((ULONG)   (INT_1E * 4),
                           (USHORT)  sizeof(ULONG),
                           (PVOID)   &BIOSVec,
                           (PUSHORT) &ModeFlag          ) )

  {
    ppDDP = OFFSETOF(*BIOSVec) + (((ULONG) SELECTOROF(*BIOSVec)) << 4);

    if ( !DevHelp_PhysToVirt((ULONG)   ppDDP,
                             (USHORT)  sizeof(DSKTPARMTABLE),
                             (PVOID)   &pDDP,
                             (PUSHORT) &ModeFlag    ) )
    {
      npAT->DsktParmTbl  = *pDDP;
      npAT->Flags       |= ATBF_DSKTPARM_OBTAINED;
    }
  }


  /*-------------------------------------*/
  /* Address BIOS Data Area              */
  /*                                     */
  /* Copy Drive 0,1 cylinder positions   */
  /* to UnitTable[0-1].                  */
  /*-------------------------------------*/

  if ( !DevHelp_PhysToVirt((ULONG)   BIOS_DATA_PHYS,
                           (USHORT)  256,
                           (PVOID)   &pBIOS,
                           (PUSHORT) &ModeFlag    ) )
  {
    npAT->BIOSMotorStatus = pBIOS[BIOS_DSKT_MOTORSTATUS];

    for ( i=0; i < 2; i++ )
    {
      npUT                 = &npAT->Unit[i];
      npUT->BIOSCyl        = pBIOS[BIOS_DSKT_CYL+i];
      npUT->BIOSMediaState = pBIOS[BIOS_DSKT_MEDIASTATE+i];
    }
  }
}

/*------------------------------------*/
/* GeomToFormFactor                   */
/*                                    */
/* This routine converts a GEOMETRY   */
/* structure to a FormFactor value    */
/* used internally in this driver.    */
/*                                    */
/*------------------------------------*/

USHORT NEAR GeomToFormFactor( PGEOMETRY pGeo )
{
  USHORT        i;
  USHORT        FormFactor = -1;

  NPGEOMETRY    npGeoFF;

  /*------------------------------------------------------*/
  /* The MediaLookUP table is a set of lists of allowable */
  /* media for each drive FormFactor.                     */
  /*                                                      */
  /* Entry 0 of each MediaLoopUp list is the default      */
  /* media for the drive.                                 */
  /*                                                      */
  /* Media in the MediaLookUp table is represented by a   */
  /* MEDIAINFO structure which contains an imbedded       */
  /* GEOMETRY structure.                                  */
  /*                                                      */
  /* This loop tries to locate a MediaLoopUp list who's   */
  /* default geometry matches the passed geometry.        */
  /*                                                      */
  /*------------------------------------------------------*/

  for ( i=1; i < MAX_FORM_FACTOR; i++ )
  {
    npGeoFF = &MediaLookUp[i].npMIF[0]->Geo;

    if ( npGeoFF->TotalCylinders == pGeo->TotalCylinders
          && npGeoFF->SectorsPerTrack == pGeo->SectorsPerTrack
             && npGeoFF->NumHeads == pGeo->NumHeads             )
    {
      FormFactor = i;
      break;
    }
  }

  return( FormFactor );
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* Miscellaneous                                                          */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* StartMotorTimer                    */
/*                                    */
/* This routine starts to Motor       */
/* Idle watchdog routine. See         */
/* MotorIdleCheck() in FLP1OSM3.C     */
/* for further information.           */
/*                                    */
/*------------------------------------*/

VOID NEAR StartMotorTimer()
{
  if ( ADD_StartTimerMS((PULONG) &ADDMotorIdleHandle,
                        (ULONG)  MOTOR_CHECK_INTERVAL,
                        (PFN)    MotorIdleCheck,
                        (ULONG)  0,
                        (ULONG)  0              ) )
  {
     _asm { int 3 }
  }
}


/*--------------------------------------------------------------*/
/*                                                              */
/* Check for Hardware Conflicts                                 */
/*                                                              */
/* This routine assigns groups of ACBs with conflicting         */
/* hardware requirements a hardware resource ID so that         */
/* the ACBs can arbitrate for use of the hardware.              */
/*                                                              */
/*--------------------------------------------------------------*/

VOID NEAR CheckHWConflicts()
{
  NPATBL        npAT;
  NPACB         npACB;

  USHORT        i, j;                                                /*@V85053*/

  /*--------------------------------------------------*/
  /* We start out assuming that the configuration is  */
  /* conflict free and assign each ACB its own        */
  /* HWResource value.                                */
  /*--------------------------------------------------*/

  npAT = AdapterTable;

  for (i=0; i < MAX_ADAPTERS; i++ )
  {
    if ( npACB = npAT[i].npACB )
    {
      j = npAT[i].ACBIndex;                                          /*@V95460*/

      ACBPtrs[j].HWRIndex = npACB->HWResourceIndex = j;              /*@V85053*/
      HWResource[j].npFirstACBX = npACB;                             /*@V85053*/

      npACB->npNextACBX = 0;
    }
  }

  /*--------------------------------------------------*/
  /* We currently only solve the problem for the two  */
  /* adapter case. This saves us about a page of code.*/
  /*--------------------------------------------------*/

  if ( MAX_ADAPTERS > 2 )
  {
    _asm int 3
  }

  /*---------------------------------------------*/
  /* If there is a conflict assign Adapter 1 and */
  /* Adapter 0 both to the same Resource ID      */
  /*---------------------------------------------*/

  if ( cAdapters > 1 )
  {
    if ( (npAT[0].DMAChannel == npAT[1].DMAChannel)
                || (npAT[0].IRQLevel == npAT[1].IRQLevel) )
    {
      npAT[1].npACB->HWResourceIndex = npAT[0].npACB->HWResourceIndex;
      npAT[0].npACB->npNextACBX      = npAT[1].npACB;
      HWResource[1].npFirstACBX      = 0;
      ACBPtrs[1].HWRIndex            = ACBPtrs[0].HWRIndex;

    }

  }

}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* Initialization Clean-up                                                */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*----------------------------------------*/                         /*@V95460*/
/* CheckACBViable                         */                         /*VVVVVVV*/
/*                                        */
/* Insure the ACB has at least one        */
/* drive attached. Otherwise, deallocate  */
/* the ACB and any resources it holds.    */
/*----------------------------------------*/
VOID NEAR CheckACBViable( NPATBL npAT )
{
  NPACB npACB;

  if ( npACB = npAT->npACB )
  {

    if ( !npACB->cUnits )
    {
      if ( npACB->Flags & ACBF_SM_READY )
      {
        DeactivateACB( npACB );
      }

      if ( npACB->pDMABuf )
      {
        DevHelp_FreeGDTSelector( SELECTOROF(npACB->pDMABuf) );
        npACB->pDMABuf = NULL;                                       // @V97351
      }

    }

    if ( npAT->Status == ATS_RESET_FAILED )
    {
      if ( npACB->StateHookHandle )
      {
        DevHelp_FreeCtxHook( npACB->StateHookHandle );
        npACB->StateHookHandle = NULL;                               // @V97351
      }

      if ( HWResource[0].npFirstACBX == npACB )
      {
        HWResource[0].npFirstACBX = npACB->npNextACBX;
      }

      cAdapters--;
      cUnits = cUnits - npACB->cUnits;                               // @V97351
      ACBPtrs[cAdapters].npACB = 0;
      npAT->npACB              = 0;

      if (npAT->hAdapter)
         RMDestroyAdapter(hDriver, npAT->hAdapter);                       /*V98451*/
      InitDeAllocate( sizeof(ACB) );
    }
  }                                                                       /*AAAAAAA*/
}                                                                         /*@V95460*/

/*------------------------------------*/
/* InstallCleanup                     */
/*                                    */
/* This routine performs garbage      */
/* collection on ACBs which did not   */
/* fully initialize due to hardware   */
/* problems.                          */
/*------------------------------------*/
USHORT NEAR InstallCleanup()
{
  if ( !cUnits )                                                     // @V97351
  {                                                                  // @V97351
    cAdapters = 0;                                                   // @V97351
  }                                                                  // @V97351

  if ( !cAdapters )
  {
    ADD_DeInstallTimer();
  }

  return( (cAdapters) ? 0 : 1 );   // If cAdapters is 0, return 1.
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Display Configuration Information                                      */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* PrintInfo                          */
/*                                    */
/* This routine scans the ACB/UCB     */
/* control blocks and prints to       */
/* controller/unit configuration      */
/* on the user console.               */
/*                                    */
/*------------------------------------*/

VOID NEAR PrintInfo( NPATBL npAT )
{
  NPACB    npACB;
  NPUCB    npUCB;
  NPUTBL   npUT;

  USHORT   i;
  USHORT   j;

  UCHAR    Buf[122];
  PUCHAR   s = Buf;

  PSZ      pCL;
  PSZ      pMS;
  PSZ      pFI;

  TTYWrite( VersionMsg );

  for ( j = 0; j < MAX_ADAPTERS; j++, npAT++ )
  {
    if ( !(npAT->Flags & ATBF_DISABLED) && (npAT->Flags & ATBF_INSTALLED) )
    {
      pFI = (npAT->Flags & ATBF_FIFO_SUPPORT) ? AdptOpts[0] : MsgNull;

      sprntf( (PUCHAR) s,
              (PUCHAR) "Controller: %1d Port:%04x IRQ:%04x DMA:%04x  Options: %2s - Status = %s",
              (USHORT) j,
              (USHORT) npAT->BasePort,
              (USHORT) npAT->IRQLevel,
              (USHORT) npAT->DMAChannel,
              (PSZ)    pFI,
              (PSZ)    AdptMsgs[npAT->Status] );

      TTYWrite( s );

      for ( i=0; i < MAX_DRIVES; i++ )
      {
        npUT  = &npAT->Unit[i];

        if ( npUT->Flags & UTBF_INSTALLED )
        {
          pCL     = MsgNull;

          if ( !(npUT->Flags & UTBF_NO_CHANGELINE) )
          {
            pCL    = UnitOpts[0];
            pCL[2] = ( npUT->Flags & UTBF_CHANGELINE_INVERT ) ? '-' : '+';
          }

          pMS   = (npUT->Flags & UTBF_NO_MEDIASENSE) ? MsgNull : UnitOpts[1];


          sprntf( (PUCHAR) s,
                  (PUCHAR) "  Drive %1d - %6s  Options: %3s %2s - Status = %s",
                  (USHORT) i,
                  (PSZ)    UnitType[npUT->FormFactor],
                  (PSZ)    pCL,
                  (PSZ)    pMS,
                  (PSZ)    UnitMsgs[npUT->Status] );

          TTYWrite( s );
        }
      }

    }
  }

  sprntf( s, (PSZ) " " );
  TTYWrite( s );
}

VOID TTYWrite( PSZ Buf )
{
  InitMsg.MsgStrings[0] = Buf;

  DevHelp_Save_Message( (NPBYTE) &InitMsg );
}

/*------------------------------------------------------------------------*/
/*                                                                        */
/* Command Line Parsing                                                   */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*-----------------------------------------------------------*/
/* ParseCmdLine                                              */
/*                                                           */
/* This routine calls the command line parser Command_Parser */
/* which returns a tokenized version of the user command     */
/* line.                                                     */
/*                                                           */
/* The token stream is scanned and the appropriate flags     */
/* are set in the ATBL/UTBL structures to implement the      */
/* requested user parameters.                                */
/*                                                           */
/*-----------------------------------------------------------*/

#define PCF_ADAPTER0    0x80000000
#define PCF_ADAPTER1    0x40000000

#define PCF_IGNORE      0x20000000
#define PCF_PORT        0x10000000
#define PCF_IRQ         0x08000000
#define PCF_DMA         0x04000000
#define PCF_DM          0x02000000

#define PCF_UNIT0       0x01000000
#define PCF_UNIT1       0x00800000
#define PCF_UNIT2       0x00400000
#define PCF_UNIT3       0x00200000
#define PCF_SPECIFY     0x00100000
#define PCF_CHGLINE     0x00080000
#define PCF_FORMFACT    0x00040000
#define PCF_MEDIASENSE  0x00020000

#define PCF_CLEAR_ADAPTER (PCF_IGNORE | PCF_IRQ   | PCF_PORT  | PCF_DM | \
                           PCF_DMA    | PCF_UNIT0 | PCF_UNIT1 |          \
                           PCF_UNIT2  | PCF_UNIT3 )

#define PCF_CLEAR_UNIT    (PCF_FORMFACT | PCF_DM | PCF_SPECIFY | PCF_CHGLINE | \
                           PCF_MEDIASENSE )


USHORT NEAR ParseCmdLine( PDDD_PARM_LIST pADDParms, NPATBL npAT )
{
  PSZ           pCmdLine;
  CC            cc;

  NPUTBL        npUT;
  NPSZ          pOutBuf;


  ULONG         Flags = 0;
  ULONG         Mask;

  USHORT        i;
  USHORT        rc = 0;
  USHORT        Length;

  USHORT        AdptId;
  USHORT        UnitId;

  USHORT        DriveSec;
  USHORT        CLType;

  NPGEOMETRY    npGeo;

  pOutBuf = poutbuf;

  pCmdLine = MAKEP( SELECTOROF(pADDParms),
                    (USHORT)   pADDParms->cmd_line_args );

  cc = Command_Parser( (PSZ)          pCmdLine,
                       (POPTIONTABLE) &opttable,
                       (PBYTE)        pOutBuf,
                       (USHORT)       outbuf_len );

  if ( cc.ret_code == NO_ERR )
  {

    while ( !rc && pOutBuf[1] != (UCHAR) TOK_ID_END )
    {

      Length = pOutBuf[0];

      switch ( pOutBuf[1] )
      {

        /*-------------------------------------------*/
        /* Quite/Verbose Mode - /!V /V               */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_V:

          Verbose = 1;
          break;

        case TOK_ID_NOT_V:
          Verbose = 0;
          break;

        /*-------------------------------------------*/
        /* Install on uChannel machines              */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_MCA:
          uCInstall = 1;
          break;

        /*-------------------------------------------*/
        /* Adapter Id - /A:0 or /A:1                 */
        /*                                           */
        /* Check for  correct adapter index. Address */
        /* ADAPTERTABLE structure.                   */
        /*-------------------------------------------*/

        case TOK_ID_ADAPTER:

          AdptId = pOutBuf[2];
          Mask = (!AdptId) ? PCF_ADAPTER0 : PCF_ADAPTER1;
          if ( (AdptId > (MAX_ADAPTERS-1)) || (Flags & Mask))
          {
            rc = 1;
            break;
          }
          Flags |= Mask;
          Flags &= ~PCF_CLEAR_ADAPTER;
          npAT = &AdapterTable[AdptId];

          if ( AdptId > 0 )
          {
            npAT->Flags |= ATBF_INSTALLED;
          }
          break;

        /*-------------------------------------------*/
        /* Base Port - /P:hhhh                       */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_PORT:

          if ( (Flags & PCF_PORT) )
          {
            rc = 1;
            break;
          }
          npAT->BasePort = *(NPUSHORT)(pOutBuf+2);
          Flags |= PCF_PORT;

          break;


        /*-------------------------------------------*/
        /* IRQ Level - /IRQ:nn                       */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_IRQ:

          if ( (Flags & PCF_IRQ) )
          {
            rc = 1;
            break;
          }
          npAT->IRQLevel = pOutBuf[2];
          Flags |= PCF_IRQ;

          break;


        /*-------------------------------------------*/
        /* DMA Level - /DMA:nn                       */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_DMA:

          if ( (Flags & PCF_DMA) )
          {
            rc = 1;
            break;
          }
          npAT->DMAChannel = pOutBuf[2];
          Flags |= PCF_DMA;

          break;


        /*-------------------------------------------*/
        /* Ignore Adapter - /I                       */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_IGNORE:

          if ( (Flags & PCF_IGNORE) )
          {
            rc = 1;
            break;
          }

          Flags |= PCF_IGNORE;

          npAT->Flags |= ATBF_DISABLED;

          break;

        /*-------------------------------------------*/
        /* DASD Manager Support - /!DM, /DM          */
        /*                                           */
        /* If this is specified before /U (Unit Id)  */
        /* then this switch applies to all units.    */
        /* Otherwise, this switch applies to the     */
        /* current unit.                             */
        /*-------------------------------------------*/

        case TOK_ID_NOT_DM:

          if ( (Flags & PCF_DM) )
          {
            rc = 1;
            break;
          }
          Flags |= PCF_DM;

          if ( Flags & (PCF_UNIT0 | PCF_UNIT1) )
          {
            npUT->Flags |= UTBF_NODASDSUPPORT;
          }
          else
          {
            for (i = 0; i < MAX_DRIVES; i++ )
            {
              npAT->Unit[i].Flags |= UTBF_NODASDSUPPORT;
            }
          }
          break;

        case TOK_ID_DM:

          if ( (Flags & PCF_DM) )
          {
            rc = 1;
            break;
          }
          Flags |= PCF_DM;

          if ( Flags & (PCF_UNIT0 | PCF_UNIT1) )
          {
            npUT->Flags &= ~UTBF_NODASDSUPPORT;
          }

          break;

        /*-------------------------------------------*/
        /* Unit Number - /U:0, /U:1                  */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_UNIT:

          UnitId = pOutBuf[2];

          switch ( UnitId )
          {
            case 0:
              Mask = PCF_UNIT0;
              break;
            case 1:
              Mask = PCF_UNIT1;
              break;
            case 2:
              Mask = PCF_UNIT2;
              break;
            case 3:
              Mask = PCF_UNIT3;
              break;
            default:
              rc = 1;
          }

          if ( rc || (Flags & Mask))
          {
            rc = 1;
            break;
          }
          Flags &= ~(PCF_CLEAR_UNIT);
          Flags |= Mask;
          npUT   = &npAT->Unit[UnitId];

          break;

        /*-------------------------------------------*/
        /* Drive Capacity                            */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_FORMAT:

          if ( Flags & PCF_FORMFACT )
          {
             rc = 1;
             break;
          }
          Flags |= PCF_FORMFACT;

          DriveSec = (*(PUSHORT) &pOutBuf[2]) * 2;

          for ( i=1; i < MAX_FORM_FACTOR; i++ )
          {
            npGeo = &MediaLookUp[i].npMIF[0]->Geo;

            if ( (USHORT) npGeo->TotalSectors == DriveSec )
            {
              break;
            }
          }
          if ( i != MAX_FORM_FACTOR )
          {
            npUT->FormFactor  = i;
            npUT->Flags      |= UTBF_INSTALLED;
          }
          else
          {
            rc = 1;
          }
          break;

        /*-------------------------------------------*/
        /* Change Line Type                          */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_CHGLINE:

          if ( Flags & PCF_CHGLINE )
          {
             rc = 1;
             break;
          }
          Flags |= PCF_CHGLINE;

          CLType = pOutBuf[2];

         /*-------------------------------------------------*/
         /*                     0      1      2      3      */
         /*                                                 */
         /* chgline_opts[] = { "\0", "NONE", "AT", "PS2" }; */
         /*-------------------------------------------------*/
          switch ( CLType )
          {
            case 1:
              npUT->Flags |= UTBF_NO_CHANGELINE;
              break;

            case 2:
              npUT->Flags &= ~UTBF_NO_CHANGELINE;
              npUT->Flags &= ~UTBF_CHANGELINE_INVERT;
              break;

            case 3:
              npUT->Flags &= ~UTBF_NO_CHANGELINE;
              npUT->Flags |= UTBF_CHANGELINE_INVERT;
              break;
          }
          npUT->Flags |= UTBF_USER_CHANGELINE;

          break;

        /*-------------------------------------------*/
        /* Turn Off Media Sense (!MS)                */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_MS:

          if ( Flags & PCF_MEDIASENSE )
          {
             rc = 1;
             break;
          }
          Flags |= PCF_MEDIASENSE;

          npUT->Flags &= ~UTBF_NO_MEDIASENSE;                        /*@V85053*/
          npUT->Flags |=  UTBF_USER_MEDIASENSE;                      /*@V85053*/

          break;

        case TOK_ID_NO_MS:

          if ( Flags & PCF_MEDIASENSE )
          {
             rc = 1;
             break;
          }
          Flags |= PCF_MEDIASENSE;

          npUT->Flags |= UTBF_NO_MEDIASENSE;
          npUT->Flags |= UTBF_USER_MEDIASENSE;                       /*@V85053*/

          break;

        /*-------------------------------------------*/
        /* User provided Specify Bytes (/Spec:df,02) */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_S_BYTES:

          if ( Flags & PCF_SPECIFY )
          {
             rc = 1;
             break;
          }
          Flags |= PCF_SPECIFY;

          npUT->Specify[0]  = pOutBuf[2];
          npUT->Specify[1]  = pOutBuf[3];
          npUT->Flags      |= UTBF_SPECIFY;

          break;


        /*-------------------------------------------*/
        /* Dual-Speed 1.2MB drive (/DSP)             */
        /*                                           */
        /*-------------------------------------------*/

        case TOK_ID_DUALSP:
          npUT->Flags |= UTBF_DUALSPEED;

          break;

        /*-------------------------------------------*/
        /* Unknown Switch                            */
        /*                                           */
        /*-------------------------------------------*/

        default:

          rc = 1;
          break;
      }

      if ( !rc ) pOutBuf += Length;

    }
  }
  else if ( cc.ret_code != NO_OPTIONS_FND_ERR )
  {
    rc = 1;
  }

  return( rc );
}


/*------------------------------------------------------------------------*/
/*                                                                        */
/* Control Block Allocation                                               */
/*                                                                        */
/*------------------------------------------------------------------------*/

/*------------------------------------*/
/* InitAllocate                       */
/*                                    */
/* Allocate and clear a control block.*/
/* Return to control block to the     */
/* requestor.                         */
/*------------------------------------*/

NPBYTE FAR InitAllocate( USHORT Size )
{
  NPBYTE        p = (NPBYTE) -1;

  if ( ACBPoolAvail > Size )
  {
    p = npACBPool;

    memset((PBYTE) npACBPool, 0, Size );

    npACBPool    += Size;
    ACBPoolAvail -= Size;
  }
  return( p );
}

/*------------------------------------*/
/* InitDeallocate                     */
/*                                    */
/* Deallocate a control block.        */
/*                                    */
/* This is NOT a generalized heap     */
/* management service. Only the       */
/* most recently allocated control    */
/* block may be returned.             */
/*                                    */
/*------------------------------------*/

VOID FAR InitDeAllocate( USHORT Size )
{
    npACBPool    -= Size;
    ACBPoolAvail += Size;
}


/*------------------------------------*/
/* CreateRMStructures                 */
/*                                    */
/*                                    */
/*                                    */
/*------------------------------------*/


VOID NEAR CreateRMStructures()
{
   UCHAR   i, j, k;
   NPATBL  npAT;
   NPACB   npACB;
   NPUCB   npUCB;
   PUCHAR  pCapText;
   ADJUNCT AdjADD;
   ADJUNCT AdjDevNum;
   UCHAR   Buf[50];
   PUCHAR  s = Buf;


   /*-------------------------------*/
   /* Initialize AdjunctList        */
   /*   Adapter has ADAPTER_NUMBER  */
   /*   Device has ADAPTER_NUMBER   */
   /*    and ADD_UNIT               */
   /*-------------------------------*/

   AdjDevNum.AdjLength       = sizeof(ADJUNCT);
   AdjDevNum.AdjType         = ADJ_ADAPTER_NUMBER;

   AdjADD.pNextAdj           = NULL;
   AdjADD.AdjLength          = sizeof(ADJUNCT);
   AdjADD.AdjType            = ADJ_ADD_UNIT;
   AdjADD.Add_Unit.ADDHandle = ADDHandle;

   for (i=0, npAT = AdapterTable; i < cAdapters; i++, npAT++ )
   {
      if ( npAT->Status )
      {
         continue; /* Any status but 0 and the adapter is not used */
      }

      npACB = npAT->npACB;

      /*---------------------------------------------------*/
      /* increment controller number in adapter string:    */
      /* Original String is "FLOPPY_# Floppy Controller" */
      /*---------------------------------------------------*/


      AdjDevNum.Adapter_Number = i;

      AdapterStruct.pAdjunctList = &AdjDevNum;
      AdjDevNum.pNextAdj         = NULL;

      if (RMCreateAdapter( hDriver,
                           &npAT->hAdapter,
                           &AdapterStruct,
                           (HDEVICE)NULL,
                           (PAHRESOURCE)&npAT->ResourceBuf))
      {
         _asm int 3    /* Need to Halt */
      }

      npUCB = npACB->npFirstUCB;

      for (j=0; j < npACB->cUnits; j++ )
      {
         AdjDevNum.Device_Number    = j;
         AdjDevNum.pNextAdj         = &AdjADD;

         AdjADD.Add_Unit.UnitHandle = (USHORT)npUCB;

         DevStruct.pAdjunctList = &AdjDevNum;


         /*--------------------------------------*/
         /* Choose appropriate description based */
         /* on FormFactor.  @V98988              */
         /*--------------------------------------*/

         sprntf( (PUCHAR)s,
                 (PUCHAR)"DSKT_# %s Diskette Drive",
                 (PUCHAR)UnitType[npUCB->FormFactor]);

         DevStruct.DevDescriptName = s;

         if ( RMCreateDevice( hDriver,
                              &npUCB->hDevice,
                              &DevStruct,
                              npAT->hAdapter,
                              NULL))
         {
            _asm int 3
         }
         npUCB = npUCB->npNextUCB;
      }
   }
}                                                                    /*@VAAAAA*/
