/* $Id: ide.c,v 1.57 2002/12/09 19:57:56 hbirr Exp $
 *
 *  IDE.C - IDE Disk driver 
 *     written by Rex Jolliff
 *     with help from various documentation sources and a few peeks at
 *       linux and freebsd sources.
 * 
 *    This driver supports PCI controllers and up to 4 ISA controllers
 *    with up to 2 drives each.
 *    The device names are assigned as follows:
 *      \Devices\HarddiskX\Partition0
 *    for the raw device, and 
 *      \Devices\HarddiskX\PartitionY
 *    for partitions
 *    where:
 *      X is computed by counting the available drives from the following
 *          sequence: the controller number (0=0x1f0, 1=0x170, 2=0x1e8, 
 *          3=0x168) * 2 plus the drive number (0,1)
 *      Y is the partition number
 * 
 *     The driver exports the following function:
 * 
 *       DriverEntry() - NT device driver initialization routine
 * 
 *     And the following functions are exported implicitly:
 * 
 *       IDEStartIo() - called to start an I/O request packet
 *       IDEDispatchOpenClose() - Called to open/close the device.  a NOOP
 *       IDEDispatchReadWrite() - Called to read/write the device.
 *       IDEDispatchQueryInformation() - Called to get device information 
 *       IDEDispatchSetInformation() - Called to set device information
 *       IDEDispatchDeviceControl() - Called to execute device control requests
 * 
 *   Modification History:
 *     05/25/98  RJJ  Created.
 *     05/30/98  RJJ  Removed IRQ handler and inserted busy waits
 *                    just to get something working...
 *     07/18/98  RJJ  Made drastic changes so that the driver
 *                    resembles a WinNT driver.
 *     08/05/98  RJJ  Changed to .C extension
 *     09/19/98  RJJ  First release (run for cover!)
 *
 *   Test List:
 *     09/17/98  RJJ  Pri/MST: 14.12X19 WDC AC31000H  Test Passed
 *                    Pri/SLV: None.
 *     
 *
 *   To Do:
 * FIXME: a timer should be used to watch for device timeouts and errors
 * FIXME: errors should be retried
 * FIXME: a drive reset/recalibrate should be attempted if errors occur
 * FIXME: Use DMA for transfers if drives support it
 * FIXME: should we support unloading of this driver???
 * FIXME: the device information should come from AUTODETECT (via registry)
 * FIXME: really big devices need to be handled correctly
 * FIXME: should get device info from the registry
 * FIXME: should report hardware usage to iomgr
 * FIXME: finish implementation of QueryInformation
 * FIXME: finish implementation of SetInformation
 * FIXME: finish implementation of DeviceControl
 * FIXME: bring up to ATA-3 spec
 * FIXME: add general support for ATAPI devices
 * FIXME: add support for ATAPI CDROMs
 * FIXME: add support for ATAPI ZIP drives/RHDs
 * FIXME: add support for ATAPI tape drives
 */

//  -------------------------------------------------------------------------

#include <ddk/ntddk.h>

#define NDEBUG
#include <debug.h>

#include "ide.h"
#include "partitio.h"

#define  VERSION  "V0.1.5"

/* uncomment the following line to enable the secondary ide channel */
//#define ENABLE_SECONDARY_IDE_CHANNEL

//  -------------------------------------------------------  File Static Data


typedef struct _IDE_CONTROLLER_PARAMETERS
{
  int              CommandPortBase;
  int              CommandPortSpan;
  int              ControlPortBase;
  int              ControlPortSpan;
  int              Vector;
  KINTERRUPT_MODE  InterruptMode;
} IDE_CONTROLLER_PARAMETERS, *PIDE_CONTROLLER_PARAMETERS;

//  NOTE: Do not increase max drives above 2

#define  IDE_MAX_DRIVES       2

#define  IDE_MAX_CONTROLLERS  2
IDE_CONTROLLER_PARAMETERS Controllers[IDE_MAX_CONTROLLERS] = 
{
  {0x01f0, 8, 0x03f6, 1, 14, Latched},
  {0x0170, 8, 0x0376, 1, 15, Latched}
/*  {0x01E8, 8, 0x03ee, 1, 11, LevelSensitive},
  {0x0168, 8, 0x036e, 1, 10, LevelSensitive}*/
};

static BOOLEAN IDEInitialized = FALSE;

//  -----------------------------------------------  Discardable Declarations

#ifdef  ALLOC_PRAGMA

//  make the initialization routines discardable, so that they 
//  don't waste space

#pragma  alloc_text(init, DriverEntry)
#pragma  alloc_text(init, IDECreateController)
#pragma  alloc_text(init, IDECreateDevices)
#pragma  alloc_text(init, IDECreateDevice)
#pragma  alloc_text(init, IDEPolledRead)

//  make the PASSIVE_LEVEL routines pageable, so that they don't
//  waste nonpaged memory

#pragma  alloc_text(page, IDEShutdown)
#pragma  alloc_text(page, IDEDispatchOpenClose)
#pragma  alloc_text(page, IDEDispatchRead)
#pragma  alloc_text(page, IDEDispatchWrite)

#endif  /*  ALLOC_PRAGMA  */

//  ---------------------------------------------------- Forward Declarations

static NTSTATUS
IdeFindControllers(IN PDRIVER_OBJECT DriverObject);

static NTSTATUS
IdeCreateController(IN PDRIVER_OBJECT DriverObject,
                    IN PIDE_CONTROLLER_PARAMETERS ControllerParams,
                    IN int ControllerIdx);

static BOOLEAN IDEResetController(IN WORD CommandPort, IN WORD ControlPort);
static BOOLEAN IDECreateDevices(IN PDRIVER_OBJECT DriverObject,
                                IN PCONTROLLER_OBJECT ControllerObject,
                                IN PIDE_CONTROLLER_EXTENSION ControllerExtension,
                                IN int DriveIdx,
                                IN int HarddiskIdx);
static BOOLEAN IDEGetDriveIdentification(IN int CommandPort,
                                         IN int DriveNum,
                                         OUT PIDE_DRIVE_IDENTIFY DrvParms);
static NTSTATUS IDECreateDiskDevice(IN PDRIVER_OBJECT DriverObject,
                                OUT PDEVICE_OBJECT *DeviceObject,
                                IN PCONTROLLER_OBJECT ControllerObject,
                                IN int UnitNumber,
                                IN ULONG DiskNumber,
                                IN PIDE_DRIVE_IDENTIFY DrvParms,
                                IN ULONG SectorCount);
static NTSTATUS IDECreatePartitionDevice(IN PDRIVER_OBJECT DriverObject,
                                         OUT PDEVICE_OBJECT *DeviceObject,
                                         IN PCONTROLLER_OBJECT ControllerObject,
                                         IN PVOID DiskDeviceExtension,
                                         IN int UnitNumber,
                                         IN ULONG DiskNumber,
                                         IN PIDE_DRIVE_IDENTIFY DrvParms,
                                         IN PPARTITION_INFORMATION PartitionInfo);
static int IDEPolledRead(IN WORD Address,
                         IN BYTE PreComp,
                         IN BYTE SectorCnt,
                         IN BYTE SectorNum,
                         IN BYTE CylinderLow,
                         IN BYTE CylinderHigh,
                         IN BYTE DrvHead,
                         IN BYTE Command,
                         OUT BYTE *Buffer);
static NTSTATUS STDCALL IDEDispatchOpenClose(IN PDEVICE_OBJECT pDO, IN PIRP Irp);
static NTSTATUS STDCALL IDEDispatchReadWrite(IN PDEVICE_OBJECT pDO, IN PIRP Irp);
static NTSTATUS STDCALL IDEDispatchDeviceControl(IN PDEVICE_OBJECT pDO, IN PIRP Irp);
static VOID STDCALL IDEStartIo(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
static IO_ALLOCATION_ACTION STDCALL
IDEAllocateController(IN PDEVICE_OBJECT DeviceObject,
                      IN PIRP Irp,
                      IN PVOID MapRegisterBase,
                      IN PVOID Ccontext);
static BOOLEAN STDCALL
IDEStartController(IN OUT PVOID Context);
VOID IDEBeginControllerReset(PIDE_CONTROLLER_EXTENSION ControllerExtension);
static BOOLEAN STDCALL IDEIsr(IN PKINTERRUPT Interrupt, IN PVOID ServiceContext);
static VOID IDEDpcForIsr(IN PKDPC Dpc,
                         IN PDEVICE_OBJECT DpcDeviceObject,
                         IN PIRP DpcIrp,
                         IN PVOID DpcContext);
static VOID IDEFinishOperation(PIDE_CONTROLLER_EXTENSION ControllerExtension);
static VOID STDCALL IDEIoTimer(PDEVICE_OBJECT DeviceObject, PVOID Context);

//  ----------------------------------------------------------------  Inlines

void
IDESwapBytePairs(char *Buf,
                 int Cnt)
{
  char  t;
  int   i;

  for (i = 0; i < Cnt; i += 2)
    {
      t = Buf[i];
      Buf[i] = Buf[i+1];
      Buf[i+1] = t;
    }
}

//  -------------------------------------------------------  Public Interface

//    DriverEntry
//
//  DESCRIPTION:
//    This function initializes the driver, locates and claims 
//    hardware resources, and creates various NT objects needed
//    to process I/O requests.
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    IN  PDRIVER_OBJECT   DriverObject  System allocated Driver Object
//                                       for this driver
//    IN  PUNICODE_STRING  RegistryPath  Name of registry driver service 
//                                       key
//
//  RETURNS:
//    NTSTATUS  

STDCALL NTSTATUS
DriverEntry(IN PDRIVER_OBJECT DriverObject,
            IN PUNICODE_STRING RegistryPath)
{
  NTSTATUS Status;

  DPRINT("IDE Driver %s\n", VERSION);

  /* Export other driver entry points... */
  DriverObject->DriverStartIo = IDEStartIo;
  DriverObject->MajorFunction[IRP_MJ_CREATE] = IDEDispatchOpenClose;
  DriverObject->MajorFunction[IRP_MJ_CLOSE] = IDEDispatchOpenClose;
  DriverObject->MajorFunction[IRP_MJ_READ] = IDEDispatchReadWrite;
  DriverObject->MajorFunction[IRP_MJ_WRITE] = IDEDispatchReadWrite;
//  DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = IDEDispatchQueryInformation;
//  DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = IDEDispatchSetInformation;
  DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IDEDispatchDeviceControl;

  Status = IdeFindControllers(DriverObject);
  if (NT_SUCCESS(Status))
    {
      IDEInitialized = TRUE;
    }

  return(Status);
}

//  ----------------------------------------------------  Discardable statics

static NTSTATUS
IdeFindControllers(IN PDRIVER_OBJECT DriverObject)
{
  PCI_COMMON_CONFIG PciConfig;
  ULONG Bus;
  ULONG Slot;
  ULONG Size;
  ULONG i;
  NTSTATUS ReturnedStatus = STATUS_NO_SUCH_DEVICE;
  NTSTATUS Status;
  INT ControllerIdx = 0;
  PCONFIGURATION_INFORMATION ConfigInfo;

  DPRINT("IdeFindControllers() called!\n");

  ConfigInfo = IoGetConfigurationInformation();

  /* Search PCI busses for IDE controllers */
  for (Bus = 0; Bus < 8; Bus++)
    {
      for (Slot = 0; Slot < 256; Slot++)
	{
	  Size = HalGetBusData(PCIConfiguration,
			       Bus,
			       Slot,
			       &PciConfig,
			       sizeof(PCI_COMMON_CONFIG));
	  if (Size != 0)
	    {
	      if ((PciConfig.BaseClass == 0x01) &&
		  (PciConfig.SubClass == 0x01))
		{
		  DPRINT("IDE controller found!\n");

		  DPRINT("Bus %1lu  Device %2lu  Func %1lu  VenID 0x%04hx  DevID 0x%04hx\n",
			Bus,
			Slot>>3,
			Slot & 0x07,
			PciConfig.VendorID,
			PciConfig.DeviceID);
		  if ((PciConfig.HeaderType & 0x7FFFFFFF) == 0)
		    {
		      DPRINT("  IPR 0x%X  ILR 0x%X\n",
			      PciConfig.u.type0.InterruptPin,
			      PciConfig.u.type0.InterruptLine);
		    }

		  if (PciConfig.ProgIf & 0x01)
		    {
		      DPRINT("Primary channel: PCI native mode\n");
		    }
		  else
		    {
		      DPRINT("Primary channel: Compatibility mode\n");
		      if (ConfigInfo->AtDiskPrimaryAddressClaimed == FALSE)
			{
			  Status = IdeCreateController(DriverObject,
						       &Controllers[0],
						       ControllerIdx);
			  if (NT_SUCCESS(Status))
			    {
			      ControllerIdx++;
			      ConfigInfo->AtDiskPrimaryAddressClaimed = TRUE;
			      ConfigInfo->ScsiPortCount++;
			      ReturnedStatus = Status;
			    }
			}
		      else
			{
			  /*
			   * FIXME: Switch controller to native pci mode
			   *        if it is programmable.
			   */
			}
		    }
		  if (PciConfig.ProgIf & 0x02)
		    {
		      DPRINT("Primary channel: programmable\n");
		    }
		  else
		    {
		      DPRINT("Primary channel: not programmable\n");
		    }

#ifdef ENABLE_SECONDARY_IDE_CHANNEL
		  if (PciConfig.ProgIf & 0x04)
		    {
		      DPRINT("Secondary channel: PCI native mode\n");
		    }
		  else
		    {
		      DPRINT("Secondary channel: Compatibility mode\n");
		      if (ConfigInfo->AtDiskSecondaryAddressClaimed == FALSE)
			{
			  Status = IdeCreateController(DriverObject,
						       &Controllers[1],
						       ControllerIdx);
			  if (NT_SUCCESS(Status))
			    {
			      ControllerIdx++;
			      ConfigInfo->AtDiskSecondaryAddressClaimed = TRUE;
			      ConfigInfo->ScsiPortCount++;
			      ReturnedStatus = Status;
			    }
			}
		      else
			{
			  /*
			   * FIXME: Switch controller to native pci mode
			   *        if it is programmable.
			   */
			}
		    }
		  if (PciConfig.ProgIf & 0x08)
		    {
		      DPRINT("Secondary channel: programmable\n");
		    }
		  else
		    {
		      DPRINT("Secondary channel: not programmable\n");
		    }

		  if (PciConfig.ProgIf & 0x80)
		    {
		      DPRINT("Master IDE device: 1\n");
		    }
		  else
		    {
		      DPRINT("Master IDE device: 0\n");
		    }

		  for (i = 0; i < PCI_TYPE0_ADDRESSES; i++)
		    {
		      DPRINT("BaseAddress: 0x%08X\n", PciConfig.u.type0.BaseAddresses[i]);
		    }
#endif
		}
	    }
	}
    }

  /* Search for ISA IDE controller if no primary controller was found */
  if (ConfigInfo->AtDiskPrimaryAddressClaimed == FALSE)
    {
      DPRINT("Searching for primary ISA IDE controller!\n");

      if (IDEResetController(Controllers[0].CommandPortBase,
			     Controllers[0].ControlPortBase))
	{
	  Status = IdeCreateController(DriverObject,
				       &Controllers[0],
				       0);
	  if (NT_SUCCESS(Status))
	    {
	      DPRINT("  Found primary ISA IDE controller!\n");
	      ControllerIdx++;
	      ConfigInfo->AtDiskPrimaryAddressClaimed = TRUE;
	      ConfigInfo->ScsiPortCount++;
	      ReturnedStatus = Status;
	    }
	}
    }

  /* Search for ISA IDE controller if no secondary controller was found */
#ifdef ENABLE_SECONDARY_IDE_CHANNEL
  if (ConfigInfo->AtDiskSecondaryAddressClaimed == FALSE)
    {
      DPRINT("Searching for secondary ISA IDE controller!\n");

      if (IDEResetController(Controllers[1].CommandPortBase,
			     Controllers[1].ControlPortBase))
	{
	  Status = IdeCreateController(DriverObject,
				       &Controllers[1],
				       1);
	  if (NT_SUCCESS(Status))
	    {
	      DPRINT("  Found secondary ISA IDE controller!\n");
	      ControllerIdx++;
	      ConfigInfo->AtDiskSecondaryAddressClaimed = TRUE;
	      ConfigInfo->ScsiPortCount++;
	      ReturnedStatus = Status;
	    }
	}
    }
#endif

  DPRINT("IdeFindControllers() done!\n");

  return(ReturnedStatus);
}


//    IdeCreateController
//
//  DESCRIPTION:
//    Creates a controller object and a device object for each valid
//    device on the controller
//
//  RUN LEVEL:
//    PASSIVE LEVEL
//
//  ARGUMENTS:
//    IN  PDRIVER_OBJECT  DriverObject  The system created driver object
//    IN  PIDE_CONTROLLER_PARAMETERS    The parameter block for this
//                    ControllerParams  controller
//    IN  int            ControllerIdx  The index of this controller
//
//  RETURNS:
//    TRUE   Devices where found on this controller
//    FALSE  The controller does not respond or there are no devices on it
//

static NTSTATUS
IdeCreateController(IN PDRIVER_OBJECT DriverObject,
                    IN PIDE_CONTROLLER_PARAMETERS ControllerParams,
                    IN int ControllerIdx)
{
  BOOLEAN                    CreatedDevices, ThisDriveExists;
  int                        DriveIdx;
  NTSTATUS                   RC;
  PCONTROLLER_OBJECT         ControllerObject;
  PIDE_CONTROLLER_EXTENSION  ControllerExtension;
  ULONG MappedIrq;
  KIRQL Dirql;
  KAFFINITY Affinity;

  ControllerObject = IoCreateController(sizeof(IDE_CONTROLLER_EXTENSION));
  if (ControllerObject == NULL)
    {
      DbgPrint ("Could not create controller object for controller %d\n",
                ControllerIdx);
      return STATUS_NO_SUCH_DEVICE;
    }

  MappedIrq = HalGetInterruptVector(Isa,
				    0,
				    ControllerParams->Vector,
				    ControllerParams->Vector,
				    &Dirql,
				    &Affinity);

    //  Fill out Controller extension data
  ControllerExtension = (PIDE_CONTROLLER_EXTENSION)
      ControllerObject->ControllerExtension;
  ControllerExtension->Number = ControllerIdx;
  ControllerExtension->CommandPortBase = ControllerParams->CommandPortBase;
  ControllerExtension->ControlPortBase = ControllerParams->ControlPortBase;
  ControllerExtension->Vector = MappedIrq;
  ControllerExtension->DMASupported = FALSE;
  ControllerExtension->ControllerInterruptBug = FALSE;
  ControllerExtension->OperationInProgress = FALSE;

    //  Initialize the spin lock in the controller extension
  KeInitializeSpinLock(&ControllerExtension->SpinLock);

    //  Register an interrupt handler for this controller
  RC = IoConnectInterrupt(&ControllerExtension->Interrupt,
                          IDEIsr,
                          ControllerExtension,
                          &ControllerExtension->SpinLock,
                          ControllerExtension->Vector,
                          Dirql,
                          Dirql,
                          ControllerParams->InterruptMode,
                          FALSE,
                          Affinity,
                          FALSE);
  if (!NT_SUCCESS(RC))
    {
      DbgPrint ("Could not Connect Interrupt %d\n", 
                ControllerExtension->Vector);
      IoDeleteController (ControllerObject);
      return RC;
    }

/* TEST */
   IDEInitialized = TRUE;

    //  Create device objects for each raw device (and for partitions) 
  CreatedDevices = FALSE;
  for (DriveIdx = 0; DriveIdx < IDE_MAX_DRIVES; DriveIdx++) 
    {
      ThisDriveExists = IDECreateDevices(DriverObject, 
                                         ControllerObject,
                                         ControllerExtension, 
                                         DriveIdx, 
                                         ControllerIdx * 2 + DriveIdx);
      if (ThisDriveExists) 
        {
          CreatedDevices = TRUE;
        }
    }

  if (!CreatedDevices)
    {
      DbgPrint ("Did not find any devices for controller %d\n", 
                ControllerIdx);
      IoDisconnectInterrupt (ControllerExtension->Interrupt);
      IoDeleteController (ControllerObject);
    }
  else
    {
      IDEResetController(ControllerParams->CommandPortBase,
                         ControllerParams->ControlPortBase);
      IoStartTimer(ControllerExtension->TimerDevice);
    }

  return((CreatedDevices == TRUE)?STATUS_SUCCESS:STATUS_NO_SUCH_DEVICE);
}


//    IDEResetController
//
//  DESCRIPTION:
//    Reset the controller and report completion status
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    IN  WORD  CommandPort  The address of the command port
//    IN  WORD  ControlPort  The address of the control port
//
//  RETURNS:
//

BOOLEAN  
IDEResetController(IN WORD CommandPort, 
                   IN WORD ControlPort) 
{
  int  Retries;

    //  Assert drive reset line
  IDEWriteDriveControl(ControlPort, IDE_DC_SRST);

    //  Wait for min. 25 microseconds
  KeStallExecutionProcessor(IDE_RESET_PULSE_LENGTH);

    //  Negate drive reset line
  IDEWriteDriveControl(ControlPort, 0);

    //  Wait for BUSY negation
  for (Retries = 0; Retries < IDE_RESET_BUSY_TIMEOUT * 1000; Retries++)
    {
      if (!(IDEReadStatus(CommandPort) & IDE_SR_BUSY))
        {
          break;
        }
      KeStallExecutionProcessor(10);
    }
  CHECKPOINT;
  if (Retries >= IDE_RESET_BUSY_TIMEOUT * 1000)
    {
      return FALSE;
    }
  CHECKPOINT;
    //  return TRUE if controller came back to life. and
    //  the registers are initialized correctly
  return  IDEReadError(CommandPort) == 1;
}


//    IDECreateDevices
//
//  DESCRIPTION:
//    Create the raw device and any partition devices on this drive
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    IN  PDRIVER_OBJECT  DriverObject  The system created driver object
//    IN  PCONTROLLER_OBJECT         ControllerObject
//    IN  PIDE_CONTROLLER_EXTENSION  ControllerExtension
//                                      The IDE controller extension for
//                                      this device
//    IN  int             DriveIdx      The index of the drive on this
//                                      controller
//    IN  int             HarddiskIdx   The NT device number for this
//                                      drive
//
//  RETURNS:
//    TRUE   Drive exists and devices were created
//    FALSE  no devices were created for this device
//

BOOLEAN
IDECreateDevices(IN PDRIVER_OBJECT DriverObject,
                 IN PCONTROLLER_OBJECT ControllerObject,
                 IN PIDE_CONTROLLER_EXTENSION ControllerExtension,
                 IN int DriveIdx,
                 IN int HarddiskIdx)
{
  WCHAR                  NameBuffer[IDE_MAX_NAME_LENGTH];
  int                    CommandPort;
  NTSTATUS               Status;
  IDE_DRIVE_IDENTIFY     DrvParms;
  PDEVICE_OBJECT         DiskDeviceObject;
  PDEVICE_OBJECT         PartitionDeviceObject;
  PIDE_DEVICE_EXTENSION  DiskDeviceExtension;
  UNICODE_STRING         UnicodeDeviceDirName;
  OBJECT_ATTRIBUTES      DeviceDirAttributes;
  HANDLE                 Handle;
  ULONG                  SectorCount = 0;
  PDRIVE_LAYOUT_INFORMATION PartitionList = NULL;
  PPARTITION_INFORMATION PartitionEntry;
  ULONG i;

   /* Copy I/O port offsets for convenience */
  CommandPort = ControllerExtension->CommandPortBase;
//  ControlPort = ControllerExtension->ControlPortBase;
  DPRINT("probing IDE controller %d Addr %04lx Drive %d\n",
         ControllerExtension->Number,
         CommandPort,
         DriveIdx);

  /* Get the Drive Identification Data */
  if (!IDEGetDriveIdentification(CommandPort, DriveIdx, &DrvParms))
    {
      DPRINT("No ATA drive %d found on controller %d...\n",
             DriveIdx,
             ControllerExtension->Number);
      return(FALSE);
    }

  DPRINT("Found ATA drive %d on controller %d...\n",
           DriveIdx,
           ControllerExtension->Number);

  /* Create the harddisk device directory */
  swprintf (NameBuffer,
            L"\\Device\\Harddisk%d",
            HarddiskIdx);
  RtlInitUnicodeString(&UnicodeDeviceDirName,
                       NameBuffer);
  InitializeObjectAttributes(&DeviceDirAttributes,
                             &UnicodeDeviceDirName,
                             0,
                             NULL,
                             NULL);
  Status = ZwCreateDirectoryObject(&Handle, 0, &DeviceDirAttributes);
  if (!NT_SUCCESS(Status))
    {
      DbgPrint("Could not create device dir object\n");
      return(FALSE);
    }

  /* Create the disk device */
  if (DrvParms.Capabilities & IDE_DRID_LBA_SUPPORTED)
    {
      SectorCount =
         (ULONG)((DrvParms.TMSectorCountHi << 16) + DrvParms.TMSectorCountLo);
    }
  else
    {
      SectorCount =
         (ULONG)(DrvParms.LogicalCyls * DrvParms.LogicalHeads * DrvParms.SectorsPerTrack);
    }
  DPRINT("SectorCount %lu\n", SectorCount);

  Status = IDECreateDiskDevice(DriverObject,
                               &DiskDeviceObject,
                               ControllerObject,
                               DriveIdx,
                               HarddiskIdx,
                               &DrvParms,
                               SectorCount);
  if (!NT_SUCCESS(Status))
    {
      DbgPrint("IDECreateDevice call failed for raw device\n");
      return FALSE;
    }

  /* Increase number of available physical disk drives */
  IoGetConfigurationInformation()->DiskCount++;

  /*
   * Initialize the controller timer here
   * (since it has to be tied to a device)
   */
  if (DriveIdx == 0)
    {
      ControllerExtension->TimerState = IDETimerIdle;
      ControllerExtension->TimerCount = 0;
      ControllerExtension->TimerDevice = DiskDeviceObject;
      IoInitializeTimer(DiskDeviceObject,
                        IDEIoTimer,
                        ControllerExtension);
    }

  DPRINT("DrvParms.BytesPerSector %ld\n",DrvParms.BytesPerSector);

  /* Read partition table */
  Status = IoReadPartitionTable(DiskDeviceObject,
                                DrvParms.BytesPerSector,
                                TRUE,
                                &PartitionList);
  if (!NT_SUCCESS(Status))
    {
      DbgPrint("IoReadPartitionTable() failed\n");
      return FALSE;
    }

  DPRINT("  Number of partitions: %u\n", PartitionList->PartitionCount);
  for (i=0;i < PartitionList->PartitionCount; i++)
    {
      PartitionEntry = &PartitionList->PartitionEntry[i];

      DPRINT("Partition %02ld: nr: %d boot: %1x type: %x offset: %I64d size: %I64d\n",
             i,
             PartitionEntry->PartitionNumber,
             PartitionEntry->BootIndicator,
             PartitionEntry->PartitionType,
             PartitionEntry->StartingOffset.QuadPart / DrvParms.BytesPerSector,
             PartitionEntry->PartitionLength.QuadPart / DrvParms.BytesPerSector);

      /* Create device for partition */
      Status = IDECreatePartitionDevice(DriverObject,
                                        &PartitionDeviceObject,
                                        ControllerObject,
                                        DiskDeviceObject->DeviceExtension,
                                        DriveIdx,
                                        HarddiskIdx,
                                        &DrvParms,
                                        PartitionEntry);
      if (!NT_SUCCESS(Status))
        {
          DbgPrint("IDECreateDevice() failed\n");
          break;
        }
    }

  if (PartitionList != NULL)
    ExFreePool(PartitionList);

  return  TRUE;
}

//    IDEGetDriveIdentification
//
//  DESCRIPTION:
//    Get the identification block from the drive
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    IN   int                  CommandPort  Address of the command port
//    IN   int                  DriveNum     The drive index (0,1)
//    OUT  PIDE_DRIVE_IDENTIFY  DrvParms     Address to write drive ident block
//
//  RETURNS:
//    TRUE  The drive identification block was retrieved successfully
//

static BOOLEAN
IDEGetDriveIdentification(IN int CommandPort,
                          IN int DriveNum,
                          OUT PIDE_DRIVE_IDENTIFY DrvParms)
{

  /* Get the Drive Identify block from drive or die */
  if (IDEPolledRead(CommandPort, 0, 1, 0, 0, 0, (DriveNum ? IDE_DH_DRV1 : 0),
                    IDE_CMD_IDENT_DRV, (BYTE *)DrvParms) != 0)
    {
      return(FALSE);
    }

  /* Report on drive parameters if debug mode */
  IDESwapBytePairs(DrvParms->SerialNumber, 20);
  IDESwapBytePairs(DrvParms->FirmwareRev, 8);
  IDESwapBytePairs(DrvParms->ModelNumber, 40);
  DPRINT("Config:%04x  Cyls:%5d  Heads:%2d  Sectors/Track:%3d  Gaps:%02d %02d\n",
         DrvParms->ConfigBits,
         DrvParms->LogicalCyls,
         DrvParms->LogicalHeads,
         DrvParms->SectorsPerTrack,
         DrvParms->InterSectorGap,
         DrvParms->InterSectorGapSize);
  DPRINT("Bytes/PLO:%3d  Vendor Cnt:%2d  Serial number:[%.20s]\n",
         DrvParms->BytesInPLO,
         DrvParms->VendorUniqueCnt,
         DrvParms->SerialNumber);
  DPRINT("Cntlr type:%2d  BufSiz:%5d  ECC bytes:%3d  Firmware Rev:[%.8s]\n",
         DrvParms->ControllerType,
         DrvParms->BufferSize * IDE_SECTOR_BUF_SZ,
         DrvParms->ECCByteCnt,
         DrvParms->FirmwareRev);
  DPRINT("Model:[%.40s]\n", DrvParms->ModelNumber);
  DPRINT("RWMult?:%02x  LBA:%d  DMA:%d  MinPIO:%d ns  MinDMA:%d ns\n",
         (DrvParms->RWMultImplemented) & 0xff,
         (DrvParms->Capabilities & IDE_DRID_LBA_SUPPORTED) ? 1 : 0,
         (DrvParms->Capabilities & IDE_DRID_DMA_SUPPORTED) ? 1 : 0,
         DrvParms->MinPIOTransTime,
         DrvParms->MinDMATransTime);
  DPRINT("TM:Cyls:%d  Heads:%d  Sectors/Trk:%d Capacity:%ld\n",
         DrvParms->TMCylinders,
         DrvParms->TMHeads,
         DrvParms->TMSectorsPerTrk,
         (ULONG)(DrvParms->TMCapacityLo + (DrvParms->TMCapacityHi << 16)));
  DPRINT("TM:SectorCount: 0x%04x%04x = %lu\n",
         DrvParms->TMSectorCountHi,
         DrvParms->TMSectorCountLo,
         (ULONG)((DrvParms->TMSectorCountHi << 16) + DrvParms->TMSectorCountLo));

  /*
   * Fix default ATA sector size.
   * Attention: Default ATAPI sector size is 2048 bytes!!
   */
  DPRINT("BytesPerSector %d\n", DrvParms->BytesPerSector);
  DrvParms->BytesPerSector = 512;
  DPRINT("BytesPerSector %d\n", DrvParms->BytesPerSector);

  return(TRUE);
}


//    IDECreateDiskDevice
//
//  DESCRIPTION:
//    Creates the disk device object
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    IN   PDRIVER_OBJECT      DriverObject      The system supplied driver object
//    OUT  PDEVICE_OBJECT     *DeviceObject      The created device object
//    IN   PCONTROLLER_OBJECT  ControllerObject  The Controller for the device
//    IN   BOOLEAN             LBASupported      Does the drive support LBA addressing?
//    IN   BOOLEAN             DMASupported      Does the drive support DMA?
//    IN   int                 SectorsPerLogCyl  Sectors per cylinder
//    IN   int                 SectorsPerLogTrk  Sectors per track
//    IN   DWORD               Offset            First valid sector for this device
//    IN   DWORD               Size              Count of valid sectors for this device
//
//  RETURNS:
//    NTSTATUS
//

NTSTATUS
IDECreateDiskDevice(IN PDRIVER_OBJECT DriverObject,
                    OUT PDEVICE_OBJECT *DeviceObject,
                    IN PCONTROLLER_OBJECT ControllerObject,
                    IN int UnitNumber,
                    IN ULONG DiskNumber,
                    IN PIDE_DRIVE_IDENTIFY DrvParms,
                    IN ULONG SectorCount)
{
  WCHAR                  NameBuffer[IDE_MAX_NAME_LENGTH];
  UNICODE_STRING         DeviceName;
  NTSTATUS               RC;
  PIDE_DEVICE_EXTENSION  DeviceExtension;

  /* Create a unicode device name */
  swprintf(NameBuffer,
           L"\\Device\\Harddisk%d\\Partition0",
           DiskNumber);
  RtlInitUnicodeString(&DeviceName,
                       NameBuffer);

  /* Create the device */
  RC = IoCreateDevice(DriverObject,
                      sizeof(IDE_DEVICE_EXTENSION),
                      &DeviceName,
                      FILE_DEVICE_DISK,
                      0,
                      TRUE,
                      DeviceObject);
  if (!NT_SUCCESS(RC))
    {
      DbgPrint("IoCreateDevice call failed\n");
      return(RC);
    }

  /* Set the buffering strategy here... */
  (*DeviceObject)->Flags |= DO_DIRECT_IO;
  (*DeviceObject)->AlignmentRequirement = FILE_WORD_ALIGNMENT;

  /* Fill out Device extension data */
  DeviceExtension = (PIDE_DEVICE_EXTENSION) (*DeviceObject)->DeviceExtension;
  DeviceExtension->DeviceObject = (*DeviceObject);
  DeviceExtension->ControllerObject = ControllerObject;
  DeviceExtension->DiskDeviceExtension = DeviceExtension;
  DeviceExtension->UnitNumber = UnitNumber;
  DeviceExtension->LBASupported = 
    (DrvParms->Capabilities & IDE_DRID_LBA_SUPPORTED) ? 1 : 0;
  DeviceExtension->DMASupported = 
    (DrvParms->Capabilities & IDE_DRID_DMA_SUPPORTED) ? 1 : 0;
  DeviceExtension->BytesPerSector = DrvParms->BytesPerSector;
  DeviceExtension->SectorsPerLogCyl =
    DrvParms->LogicalHeads * DrvParms->SectorsPerTrack;
  DeviceExtension->SectorsPerLogTrk = DrvParms->SectorsPerTrack;
  DeviceExtension->LogicalHeads = DrvParms->LogicalHeads;
  DeviceExtension->LogicalCylinders = 
    (DrvParms->Capabilities & IDE_DRID_LBA_SUPPORTED) ? DrvParms->TMCylinders : DrvParms->LogicalCyls;

  DeviceExtension->StartingOffset.QuadPart = 0;
  DeviceExtension->PartitionLength.QuadPart = (ULONGLONG)SectorCount *
                                              (ULONGLONG)DrvParms->BytesPerSector;
  DeviceExtension->HiddenSectors = 0;
  DeviceExtension->PartitionNumber = 0;
  DeviceExtension->PartitionType = 0;
  DeviceExtension->BootIndicator = FALSE;

  DPRINT("%wZ: offset %I64d length %I64d\n",
         &DeviceName,
         DeviceExtension->StartingOffset.QuadPart,
         DeviceExtension->PartitionLength.QuadPart);

  /* Initialize the DPC object here */
  IoInitializeDpcRequest(*DeviceObject,
                         IDEDpcForIsr);

  return  RC;
}


//    IDECreatePartitionDevice
//
//  DESCRIPTION:
//    Creates a partition device object
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    IN   PDRIVER_OBJECT      DriverObject      The system supplied driver object
//    OUT  PDEVICE_OBJECT     *DeviceObject      The created device object
//    IN   PCONTROLLER_OBJECT  ControllerObject  The Controller for the device
//    IN   BOOLEAN             LBASupported      Does the drive support LBA addressing?
//    IN   BOOLEAN             DMASupported      Does the drive support DMA?
//    IN   int                 SectorsPerLogCyl  Sectors per cylinder
//    IN   int                 SectorsPerLogTrk  Sectors per track
//    IN   DWORD               Offset            First valid sector for this device
//    IN   DWORD               Size              Count of valid sectors for this device
//
//  RETURNS:
//    NTSTATUS
//

NTSTATUS
IDECreatePartitionDevice(IN PDRIVER_OBJECT DriverObject,
                         OUT PDEVICE_OBJECT *DeviceObject,
                         IN PCONTROLLER_OBJECT ControllerObject,
                         IN PVOID DiskDeviceExtension,
                         IN int UnitNumber,
                         IN ULONG DiskNumber,
                         IN PIDE_DRIVE_IDENTIFY DrvParms,
                         IN PPARTITION_INFORMATION PartitionInfo)
{
  WCHAR                  NameBuffer[IDE_MAX_NAME_LENGTH];
  UNICODE_STRING         DeviceName;
  NTSTATUS               RC;
  PIDE_DEVICE_EXTENSION  DeviceExtension;

  /* Create a unicode device name */
  swprintf(NameBuffer,
           L"\\Device\\Harddisk%d\\Partition%d",
           DiskNumber,
           PartitionInfo->PartitionNumber);
  RtlInitUnicodeString(&DeviceName,
                       NameBuffer);

  /* Create the device */
  RC = IoCreateDevice(DriverObject, sizeof(IDE_DEVICE_EXTENSION),
      &DeviceName, FILE_DEVICE_DISK, 0, TRUE, DeviceObject);
  if (!NT_SUCCESS(RC))
    {
      DbgPrint ("IoCreateDevice call failed\n");
      return  RC;
    }

  /* Set the buffering strategy here... */
  (*DeviceObject)->Flags |= DO_DIRECT_IO;
  (*DeviceObject)->AlignmentRequirement = FILE_WORD_ALIGNMENT;

  /* Fill out Device extension data */
  DeviceExtension = (PIDE_DEVICE_EXTENSION)(*DeviceObject)->DeviceExtension;
  DeviceExtension->DeviceObject = (*DeviceObject);
  DeviceExtension->ControllerObject = ControllerObject;
  DeviceExtension->DiskDeviceExtension = DiskDeviceExtension;
  DeviceExtension->UnitNumber = UnitNumber;
  DeviceExtension->LBASupported =
    (DrvParms->Capabilities & IDE_DRID_LBA_SUPPORTED) ? 1 : 0;
  DeviceExtension->DMASupported =
    (DrvParms->Capabilities & IDE_DRID_DMA_SUPPORTED) ? 1 : 0;
  DeviceExtension->BytesPerSector = DrvParms->BytesPerSector;
  DeviceExtension->SectorsPerLogCyl = DrvParms->LogicalHeads *
      DrvParms->SectorsPerTrack;
  DeviceExtension->SectorsPerLogTrk = DrvParms->SectorsPerTrack;
  DeviceExtension->LogicalHeads = DrvParms->LogicalHeads;
  DeviceExtension->LogicalCylinders =
    (DrvParms->Capabilities & IDE_DRID_LBA_SUPPORTED) ? DrvParms->TMCylinders : DrvParms->LogicalCyls;

  DeviceExtension->StartingOffset = PartitionInfo->StartingOffset;
  DeviceExtension->PartitionLength = PartitionInfo->PartitionLength;
  DeviceExtension->HiddenSectors = PartitionInfo->HiddenSectors;
  DeviceExtension->PartitionNumber = PartitionInfo->PartitionNumber;
  DeviceExtension->PartitionType = PartitionInfo->PartitionType;
  DeviceExtension->BootIndicator = PartitionInfo->BootIndicator;

  DPRINT("%wZ: offset %I64d size %I64d\n",
         &DeviceName,
         DeviceExtension->StartingOffset.QuadPart,
         DeviceExtension->PartitionLength.QuadPart);

  /* Initialize the DPC object here */
  IoInitializeDpcRequest(*DeviceObject, IDEDpcForIsr);

  DPRINT("%wZ %I64dMB\n",
         &DeviceName,
         DeviceExtension->PartitionLength.QuadPart >> 20);

  return  RC;
}


//    IDEPolledRead
//
//  DESCRIPTION:
//    Read a sector of data from the drive in a polled fashion.
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    IN   WORD  Address       Address of command port for drive
//    IN   BYTE  PreComp       Value to write to precomp register
//    IN   BYTE  SectorCnt     Value to write to sectorCnt register
//    IN   BYTE  SectorNum     Value to write to sectorNum register
//    IN   BYTE  CylinderLow   Value to write to CylinderLow register
//    IN   BYTE  CylinderHigh  Value to write to CylinderHigh register
//    IN   BYTE  DrvHead       Value to write to Drive/Head register
//    IN   BYTE  Command       Value to write to Command register
//    OUT  BYTE  *Buffer       Buffer for output data
//
//  RETURNS:
//    int  0 is success, non 0 is an error code
//

static int
IDEPolledRead(IN WORD Address,
              IN BYTE PreComp,
              IN BYTE SectorCnt,
              IN BYTE SectorNum,
              IN BYTE CylinderLow,
              IN BYTE CylinderHigh,
              IN BYTE DrvHead,
              IN BYTE Command,
              OUT BYTE *Buffer)
{
  BYTE    Status;
  int     RetryCount;
  BOOLEAN ReadJunk = FALSE;
  ULONG   SectorCount = 0;

  /*  Wait for STATUS.BUSY and STATUS.DRQ to clear  */
  for (RetryCount = 0; RetryCount < IDE_MAX_BUSY_RETRIES; RetryCount++)
    {
      Status = IDEReadStatus(Address);
      if (!(Status & IDE_SR_BUSY) && !(Status & IDE_SR_DRQ))
        {
          break;
        }
      KeStallExecutionProcessor(10);
    }
  if (RetryCount == IDE_MAX_BUSY_RETRIES)
    {
      return IDE_ER_ABRT;
    }

  /*  Write Drive/Head to select drive  */
  IDEWriteDriveHead(Address, IDE_DH_FIXED | DrvHead);

  /*  Wait for STATUS.BUSY and STATUS.DRQ to clear  */
  for (RetryCount = 0; RetryCount < IDE_MAX_BUSY_RETRIES; RetryCount++)
    {
      Status = IDEReadStatus(Address);
      if (!(Status & IDE_SR_BUSY) && !(Status & IDE_SR_DRQ))
        {
          break;
        }
      KeStallExecutionProcessor(10);
    }
  if (RetryCount == IDE_MAX_BUSY_RETRIES)
    {
      return IDE_ER_ABRT;
    }

  /*  Issue command to drive  */
  if (DrvHead & IDE_DH_LBA)
    {
      DPRINT("READ:DRV=%d:LBA=1:BLK=%08d:SC=%02x:CM=%02x\n",
             DrvHead & IDE_DH_DRV1 ? 1 : 0,
             ((DrvHead & 0x0f) << 24) + (CylinderHigh << 16) + (CylinderLow << 8) + SectorNum,
             SectorCnt,
             Command);
    }
  else
    {
      DPRINT("READ:DRV=%d:LBA=0:CH=%02x:CL=%02x:HD=%01x:SN=%02x:SC=%02x:CM=%02x\n",
             DrvHead & IDE_DH_DRV1 ? 1 : 0,
             CylinderHigh,
             CylinderLow,
             DrvHead & 0x0f,
             SectorNum,
             SectorCnt,
             Command);
    }

  /*  Setup command parameters  */
  IDEWritePrecomp(Address, PreComp);
  IDEWriteSectorCount(Address, SectorCnt);
  IDEWriteSectorNum(Address, SectorNum);
  IDEWriteCylinderHigh(Address, CylinderHigh);
  IDEWriteCylinderLow(Address, CylinderLow);
  IDEWriteDriveHead(Address, IDE_DH_FIXED | DrvHead);

  /*  Issue the command  */
  IDEWriteCommand(Address, Command);
  KeStallExecutionProcessor(50);

      /*  wait for DRQ or error  */
      for (RetryCount = 0; RetryCount < IDE_MAX_POLL_RETRIES; RetryCount++)
        {
          Status = IDEReadStatus(Address);
          if (!(Status & IDE_SR_BUSY))
            {
              if (Status & IDE_SR_ERR)
                {
                  DPRINT("IDE_SR_ERR asserted!\n");
                }
              if ((Status & IDE_SR_DRQ) && !(Status & IDE_SR_ERR))
                {
                  break;
                }
              else
                {
                  return IDE_ER_ABRT;
                }
            }
          KeStallExecutionProcessor(10);
        }
      if (RetryCount >= IDE_MAX_POLL_RETRIES)
        {
          return IDE_ER_ABRT;
        }

  while (1)
    {
      /*  Read data into buffer  */
      if (ReadJunk == TRUE)
        {
          UCHAR JunkBuffer[IDE_SECTOR_BUF_SZ];
          IDEReadBlock(Address, JunkBuffer, IDE_SECTOR_BUF_SZ);
        }
      else
        {
          IDEReadBlock(Address, Buffer, IDE_SECTOR_BUF_SZ);
          Buffer += IDE_SECTOR_BUF_SZ;
        }
      SectorCount++;

      /*  Check for more sectors to read  */
      for (RetryCount = 0; RetryCount < IDE_MAX_BUSY_RETRIES; RetryCount++)
        {
          Status = IDEReadStatus(Address);
          if (!(Status & IDE_SR_BUSY))
            {
              if (Status & IDE_SR_ERR)
                {
                  DPRINT("IDE_SR_ERR asserted!\n");
                }
              if (Status & IDE_SR_DRQ)
                {
                  if (SectorCount >= SectorCnt)
                    ReadJunk = TRUE;
                  break;
                }
              else
                {
                  return 0;
                }
            }
        }
    }
}

//  -------------------------------------------  Nondiscardable statics

//    IDEDispatchOpenClose
//
//  DESCRIPTION:
//    Answer requests for Open/Close calls: a null operation
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    Standard dispatch arguments
//
//  RETURNS:
//    NTSTATUS
//

static NTSTATUS STDCALL
IDEDispatchOpenClose(IN PDEVICE_OBJECT pDO,
                     IN PIRP Irp)
{
  Irp->IoStatus.Status = STATUS_SUCCESS;
  Irp->IoStatus.Information = FILE_OPENED;
  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  return STATUS_SUCCESS;
}

//    IDEDispatchReadWrite
//
//  DESCRIPTION:
//    Answer requests for reads and writes
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    Standard dispatch arguments
//
//  RETURNS:
//    NTSTATUS
//

static NTSTATUS STDCALL
IDEDispatchReadWrite(IN PDEVICE_OBJECT DeviceObject,
                     IN PIRP Irp)
{
  ULONG                 IrpInsertKey;
  LARGE_INTEGER         AdjustedOffset, AdjustedExtent, PartitionExtent, InsertKeyLI;
  PIO_STACK_LOCATION    IrpStack;
  PIDE_DEVICE_EXTENSION DeviceExtension;

  IrpStack = IoGetCurrentIrpStackLocation(Irp);
  DeviceExtension = (PIDE_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //  Validate operation parameters
  AdjustedOffset.QuadPart = DeviceExtension->StartingOffset.QuadPart +
                            IrpStack->Parameters.Read.ByteOffset.QuadPart;
  DPRINT("AdjustedOffset: %I64x\n", AdjustedOffset.QuadPart);

  AdjustedExtent.QuadPart = AdjustedOffset.QuadPart +
                            (ULONGLONG)IrpStack->Parameters.Read.Length;
  DPRINT("AdjustedExtent: %I64x\n", AdjustedExtent.QuadPart);

  PartitionExtent.QuadPart = DeviceExtension->StartingOffset.QuadPart +
                             DeviceExtension->PartitionLength.QuadPart;
  DPRINT("PartitionExtent: %I64x\n", PartitionExtent.QuadPart);

  if ((AdjustedExtent.QuadPart > PartitionExtent.QuadPart) ||
      (IrpStack->Parameters.Read.Length & (DeviceExtension->BytesPerSector - 1)))
    {
      DPRINT("Request failed on bad parameters\n",0);
      DPRINT("AdjustedExtent=%I64x PartitionExtent=%I64x ReadLength=%lx\n",
             AdjustedExtent.QuadPart,
             PartitionExtent.QuadPart,
             IrpStack->Parameters.Read.Length);
      Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
      IoCompleteRequest(Irp, IO_NO_INCREMENT);
      return  STATUS_INVALID_PARAMETER;
    }

    //  Adjust operation to absolute sector offset
  IrpStack->Parameters.Read.ByteOffset = AdjustedOffset;

    //  Start the packet and insert the request in order of sector offset
  assert(DeviceExtension->BytesPerSector == 512);
  InsertKeyLI.QuadPart = IrpStack->Parameters.Read.ByteOffset.QuadPart >> 9;
  IrpInsertKey = InsertKeyLI.u.LowPart;
  IoStartPacket(DeviceObject, Irp, &IrpInsertKey, NULL);
  
  DPRINT("Returning STATUS_PENDING\n");
  return  STATUS_PENDING;
}

//    IDEDispatchDeviceControl
//
//  DESCRIPTION:
//    Answer requests for device control calls
//
//  RUN LEVEL:
//    PASSIVE_LEVEL
//
//  ARGUMENTS:
//    Standard dispatch arguments
//
//  RETURNS:
//    NTSTATUS
//

static NTSTATUS STDCALL
IDEDispatchDeviceControl(IN PDEVICE_OBJECT DeviceObject,
                         IN PIRP Irp)
{
  NTSTATUS  RC;
  ULONG     ControlCode, InputLength, OutputLength;
  PIO_STACK_LOCATION     IrpStack;
  PIDE_DEVICE_EXTENSION  DeviceExtension;
  PIDE_DEVICE_EXTENSION  DiskDeviceExtension;
  CCHAR Increment;

  RC = STATUS_SUCCESS;
  IrpStack = IoGetCurrentIrpStackLocation(Irp);
  ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
  InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
  OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
  DeviceExtension = (PIDE_DEVICE_EXTENSION) DeviceObject->DeviceExtension;
  DiskDeviceExtension = (PIDE_DEVICE_EXTENSION)DeviceExtension->DiskDeviceExtension;
  Increment = IO_NO_INCREMENT;

    //  A huge switch statement in a Windows program?! who would have thought?
  switch (ControlCode)
    {
    case IOCTL_DISK_GET_DRIVE_GEOMETRY:
      if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(DISK_GEOMETRY))
        {
          Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
        }
      else
        {
          PDISK_GEOMETRY Geometry;

          Geometry = (PDISK_GEOMETRY) Irp->AssociatedIrp.SystemBuffer;

          Geometry->MediaType = FixedMedia;
          Geometry->Cylinders.QuadPart = DiskDeviceExtension->LogicalCylinders;
          Geometry->TracksPerCylinder = DiskDeviceExtension->SectorsPerLogCyl /
              DiskDeviceExtension->SectorsPerLogTrk;
          Geometry->SectorsPerTrack = DiskDeviceExtension->SectorsPerLogTrk;
          Geometry->BytesPerSector = DiskDeviceExtension->BytesPerSector;
DPRINT("DiskDeviceExtension->BytesPerSector %lu\n", DiskDeviceExtension->BytesPerSector);
          Irp->IoStatus.Status = STATUS_SUCCESS;
          Irp->IoStatus.Information = sizeof(DISK_GEOMETRY);
        }
      break;

    case IOCTL_DISK_GET_PARTITION_INFO:
      DPRINT("IOCTL_DISK_GET_PARTITION_INFO\n");
      if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength <
          sizeof(PARTITION_INFORMATION))
        {
          Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
        }
      else if (DeviceExtension->PartitionNumber == 0)
        {
          Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
        }
      else
        {
          PPARTITION_INFORMATION PartitionInfo;

          PartitionInfo = (PPARTITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer;

          PartitionInfo->PartitionType = DeviceExtension->PartitionType;
          PartitionInfo->StartingOffset = DeviceExtension->StartingOffset;
          PartitionInfo->PartitionLength = DeviceExtension->PartitionLength;
          PartitionInfo->HiddenSectors = DeviceExtension->HiddenSectors;
          PartitionInfo->PartitionNumber = DeviceExtension->PartitionNumber;
          PartitionInfo->BootIndicator = DeviceExtension->BootIndicator;
          PartitionInfo->RewritePartition = FALSE;
          PartitionInfo->RecognizedPartition =
            IsRecognizedPartition(DeviceExtension->PartitionType);

          Irp->IoStatus.Status = STATUS_SUCCESS;
          Irp->IoStatus.Information = sizeof(PARTITION_INFORMATION);
        }
      break;

    case IOCTL_DISK_SET_PARTITION_INFO:
      RC = STATUS_INVALID_DEVICE_REQUEST;
      Irp->IoStatus.Status = RC;
      Irp->IoStatus.Information = 0;
      break;

    case IOCTL_DISK_GET_DRIVE_LAYOUT:
      if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength <
          sizeof(DRIVE_LAYOUT_INFORMATION))
        {
          Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
        }
      else
        {
          PDRIVE_LAYOUT_INFORMATION PartitionList;

          RC = IoReadPartitionTable(DiskDeviceExtension->DeviceObject,
                                    DiskDeviceExtension->BytesPerSector,
                                    FALSE,
                                    &PartitionList);
          if (!NT_SUCCESS(RC))
            {
              Irp->IoStatus.Status = RC;
            }
          else
            {
              ULONG BufferSize;

              BufferSize = FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION,
                                        PartitionEntry[0]);
              BufferSize += PartitionList->PartitionCount * sizeof(PARTITION_INFORMATION);

              if (BufferSize > IrpStack->Parameters.DeviceIoControl.OutputBufferLength)
                {
                  Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
                }
              else
                {
                  RtlMoveMemory(Irp->AssociatedIrp.SystemBuffer,
                                PartitionList,
                                BufferSize);
                  Irp->IoStatus.Status = STATUS_SUCCESS;
                  Irp->IoStatus.Information = BufferSize;
                }
              ExFreePool(PartitionList);
            }
        }
        Increment = IO_DISK_INCREMENT;
        break;

    case IOCTL_DISK_SET_DRIVE_LAYOUT:
    case IOCTL_DISK_VERIFY:
    case IOCTL_DISK_FORMAT_TRACKS:
    case IOCTL_DISK_PERFORMANCE:
    case IOCTL_DISK_IS_WRITABLE:
    case IOCTL_DISK_LOGGING:
    case IOCTL_DISK_FORMAT_TRACKS_EX:
    case IOCTL_DISK_HISTOGRAM_STRUCTURE:
    case IOCTL_DISK_HISTOGRAM_DATA:
    case IOCTL_DISK_HISTOGRAM_RESET:
    case IOCTL_DISK_REQUEST_STRUCTURE:
    case IOCTL_DISK_REQUEST_DATA:

      //  If we get here, something went wrong.  inform the requestor
    default:
      RC = STATUS_INVALID_DEVICE_REQUEST;
      Irp->IoStatus.Status = RC;
      Irp->IoStatus.Information = 0;
      break;
    }

  IoCompleteRequest(Irp, Increment);

  return  RC;
}

//    IDEStartIo
//
//  DESCRIPTION:
//    Get the next requested I/O packet started
//
//  RUN LEVEL:
//    DISPATCH_LEVEL
//
//  ARGUMENTS:
//    Dispatch routine standard arguments
//
//  RETURNS:
//    NTSTATUS
//

static  VOID  
STDCALL IDEStartIo(IN PDEVICE_OBJECT DeviceObject, 
           IN PIRP Irp) 
{
  LARGE_INTEGER              SectorLI;
  PIO_STACK_LOCATION         IrpStack;
  PIDE_DEVICE_EXTENSION      DeviceExtension;
  KIRQL                      OldIrql;
  
  DPRINT("IDEStartIo() called!\n");
  
  IrpStack = IoGetCurrentIrpStackLocation(Irp);
  DeviceExtension = (PIDE_DEVICE_EXTENSION) DeviceObject->DeviceExtension;

    // FIXME: implement the supported functions

  switch (IrpStack->MajorFunction) 
    {
    case IRP_MJ_READ:
    case IRP_MJ_WRITE:
      DeviceExtension->Operation = IrpStack->MajorFunction;
      DeviceExtension->BytesRequested = IrpStack->Parameters.Read.Length;
      assert(DeviceExtension->BytesPerSector == 512);
      SectorLI = RtlLargeIntegerShiftRight(IrpStack->Parameters.Read.ByteOffset, 9);
      DeviceExtension->StartingSector = SectorLI.u.LowPart;
      if (DeviceExtension->BytesRequested > DeviceExtension->BytesPerSector * 
          IDE_MAX_SECTORS_PER_XFER) 
        {
          DeviceExtension->BytesToTransfer = DeviceExtension->BytesPerSector * 
              IDE_MAX_SECTORS_PER_XFER;
        } 
      else 
        {
          DeviceExtension->BytesToTransfer = DeviceExtension->BytesRequested;
        }
      DeviceExtension->BytesRequested -= DeviceExtension->BytesToTransfer;
      DeviceExtension->SectorsTransferred = 0;
      DeviceExtension->TargetAddress = (BYTE *)MmGetSystemAddressForMdl(Irp->MdlAddress);
      KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
      IoAllocateController(DeviceExtension->ControllerObject,
                           DeviceObject, 
                           IDEAllocateController, 
                           NULL);
      KeLowerIrql(OldIrql);
      break;

    default:
      Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
      Irp->IoStatus.Information = 0;
      KeBugCheck((ULONG)Irp);
      IoCompleteRequest(Irp, IO_NO_INCREMENT);
      IoStartNextPacket(DeviceObject, FALSE);
      break;
    }
  DPRINT("IDEStartIo() finished!\n");
}

//    IDEAllocateController

static IO_ALLOCATION_ACTION STDCALL
IDEAllocateController(IN PDEVICE_OBJECT DeviceObject,
                      IN PIRP Irp,
                      IN PVOID MapRegisterBase,
                      IN PVOID Ccontext) 
{
  PIDE_DEVICE_EXTENSION  DeviceExtension;
  PIDE_CONTROLLER_EXTENSION  ControllerExtension;

  DeviceExtension = (PIDE_DEVICE_EXTENSION) DeviceObject->DeviceExtension;
  ControllerExtension = (PIDE_CONTROLLER_EXTENSION) 
      DeviceExtension->ControllerObject->ControllerExtension;
  ControllerExtension->CurrentIrp = Irp;
  ControllerExtension->Retries = 0;
  return KeSynchronizeExecution(ControllerExtension->Interrupt, 
                                IDEStartController,
                                DeviceExtension) ? KeepObject : 
                                  DeallocateObject;
}

//    IDEStartController

BOOLEAN STDCALL
IDEStartController(IN OUT PVOID Context)
{
  BYTE  SectorCnt, SectorNum, CylinderLow, CylinderHigh;
  BYTE  DrvHead, Command;
  BYTE  Status;
  int  Retries;
  ULONG  StartingSector;
  PIDE_DEVICE_EXTENSION  DeviceExtension;
  PIDE_CONTROLLER_EXTENSION  ControllerExtension;
  PIRP  Irp;

  DeviceExtension = (PIDE_DEVICE_EXTENSION) Context;
  ControllerExtension = (PIDE_CONTROLLER_EXTENSION)
      DeviceExtension->ControllerObject->ControllerExtension;
  ControllerExtension->OperationInProgress = TRUE;
  ControllerExtension->DeviceForOperation = DeviceExtension;

    //  Write controller registers to start opteration
  StartingSector = DeviceExtension->StartingSector;
  SectorCnt = DeviceExtension->BytesToTransfer / 
      DeviceExtension->BytesPerSector;
  if (DeviceExtension->LBASupported) 
    {
      SectorNum = StartingSector & 0xff;
      CylinderLow = (StartingSector >> 8) & 0xff;
      CylinderHigh = (StartingSector >> 16) & 0xff;
      DrvHead = ((StartingSector >> 24) & 0x0f) | 
          (DeviceExtension->UnitNumber ? IDE_DH_DRV1 : 0) |
          IDE_DH_LBA;
    } 
  else 
    {
      SectorNum = (StartingSector % DeviceExtension->SectorsPerLogTrk) + 1;
      StartingSector /= DeviceExtension->SectorsPerLogTrk;
      DrvHead = (StartingSector % DeviceExtension->LogicalHeads) | 
          (DeviceExtension->UnitNumber ? IDE_DH_DRV1 : 0);
      StartingSector /= DeviceExtension->LogicalHeads;
      CylinderLow = StartingSector & 0xff;
      CylinderHigh = StartingSector >> 8;
    }
  Command = DeviceExtension->Operation == IRP_MJ_READ ? 
     IDE_CMD_READ : IDE_CMD_WRITE;
  if (DrvHead & IDE_DH_LBA) 
    {
      DPRINT("%s:BUS=%04x:DRV=%d:LBA=1:BLK=%08d:SC=%02x:CM=%02x\n",
             DeviceExtension->Operation == IRP_MJ_READ ? "READ" : "WRITE",
             ControllerExtension->CommandPortBase,
             DrvHead & IDE_DH_DRV1 ? 1 : 0, 
             ((DrvHead & 0x0f) << 24) +
             (CylinderHigh << 16) + (CylinderLow << 8) + SectorNum,
             SectorCnt, 
             Command);
    } 
  else 
    {
      DPRINT("%s:BUS=%04x:DRV=%d:LBA=0:CH=%02x:CL=%02x:HD=%01x:SN=%02x:SC=%02x:CM=%02x\n",
             DeviceExtension->Operation == IRP_MJ_READ ? "READ" : "WRITE",
             ControllerExtension->CommandPortBase,
             DrvHead & IDE_DH_DRV1 ? 1 : 0, 
             CylinderHigh, 
             CylinderLow, 
             DrvHead & 0x0f, 
             SectorNum, 
             SectorCnt, 
             Command);
    }

  /*  wait for BUSY to clear  */
  for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES; Retries++)
    {
      Status = IDEReadStatus(ControllerExtension->CommandPortBase);
      if (!(Status & IDE_SR_BUSY)) 
        {
          break;
        }
      KeStallExecutionProcessor(10);
    }
  DPRINT ("status=%02x\n", Status);
  DPRINT ("waited %ld usecs for busy to clear\n", Retries * 10);
  if (Retries >= IDE_MAX_BUSY_RETRIES)
    {
      DPRINT ("Drive is BUSY for too long\n");
      if (++ControllerExtension->Retries > IDE_MAX_CMD_RETRIES)
        {
          DbgPrint ("Max Retries on Drive reset reached, returning failure\n");
          Irp = ControllerExtension->CurrentIrp;
          Irp->IoStatus.Status = STATUS_DISK_OPERATION_FAILED;
          Irp->IoStatus.Information = 0;

          return FALSE;
        }
      else
        {
          DPRINT ("Beginning drive reset sequence\n");
          IDEBeginControllerReset(ControllerExtension);

          return TRUE;
        }
    }

  /*  Select the desired drive  */
  IDEWriteDriveHead(ControllerExtension->CommandPortBase, IDE_DH_FIXED | DrvHead);

  /*  wait for BUSY to clear and DRDY to assert */
  for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES; Retries++)
    {
      Status = IDEReadStatus(ControllerExtension->CommandPortBase);
//      if (!(Status & IDE_SR_BUSY) && (Status & IDE_SR_DRDY))
      if (!(Status & IDE_SR_BUSY) && !(Status & IDE_SR_DRQ))
        {
          break;
        }
      KeStallExecutionProcessor(10);
    }
  DPRINT ("waited %ld usecs for busy to clear after drive select\n", Retries * 10);
  if (Retries >= IDE_MAX_BUSY_RETRIES)
    {
      DPRINT ("Drive is BUSY for too long after drive select\n");
      if (ControllerExtension->Retries++ > IDE_MAX_CMD_RETRIES)
        {
          DbgPrint ("Max Retries on Drive reset reached, returning failure\n");
          Irp = ControllerExtension->CurrentIrp;
          Irp->IoStatus.Status = STATUS_DISK_OPERATION_FAILED;
          Irp->IoStatus.Information = 0;

          return FALSE;
        }
      else
        {
          DPRINT ("Beginning drive reset sequence\n");
          IDEBeginControllerReset(ControllerExtension);

          return TRUE;
        }
    }

  /*  Setup command parameters  */
  IDEWritePrecomp(ControllerExtension->CommandPortBase, 0);
  IDEWriteSectorCount(ControllerExtension->CommandPortBase, SectorCnt);
  IDEWriteSectorNum(ControllerExtension->CommandPortBase, SectorNum);
  IDEWriteCylinderHigh(ControllerExtension->CommandPortBase, CylinderHigh);
  IDEWriteCylinderLow(ControllerExtension->CommandPortBase, CylinderLow);
  IDEWriteDriveHead(ControllerExtension->CommandPortBase, IDE_DH_FIXED | DrvHead);

  /*  Issue command to drive  */
  IDEWriteCommand(ControllerExtension->CommandPortBase, Command);
  ControllerExtension->TimerState = IDETimerCmdWait;
  ControllerExtension->TimerCount = IDE_CMD_TIMEOUT;
  
  if (DeviceExtension->Operation == IRP_MJ_WRITE) 
    {

        //  Wait for controller ready
      for (Retries = 0; Retries < IDE_MAX_WRITE_RETRIES; Retries++) 
        {
          BYTE  Status = IDEReadStatus(ControllerExtension->CommandPortBase);
          if (!(Status & IDE_SR_BUSY) || (Status & IDE_SR_ERR)) 
            {
              break;
            }
          KeStallExecutionProcessor(10);
        }
      if (Retries >= IDE_MAX_BUSY_RETRIES)
        {
          if (ControllerExtension->Retries++ > IDE_MAX_CMD_RETRIES)
            {
              Irp = ControllerExtension->CurrentIrp;
              Irp->IoStatus.Status = STATUS_DISK_OPERATION_FAILED;
              Irp->IoStatus.Information = 0;

              return FALSE;
            }
          else
            {
              IDEBeginControllerReset(ControllerExtension);

              return TRUE;
            }
        }

        //  Load the first sector of data into the controller
      IDEWriteBlock(ControllerExtension->CommandPortBase, 
                    DeviceExtension->TargetAddress,
                    IDE_SECTOR_BUF_SZ);
      DeviceExtension->TargetAddress += IDE_SECTOR_BUF_SZ;
      DeviceExtension->BytesToTransfer -= DeviceExtension->BytesPerSector;
      DeviceExtension->SectorsTransferred++;
    }
  DPRINT ("Command issued to drive, IDEStartController done\n");

  return  TRUE;
}

//    IDEBeginControllerReset

VOID 
IDEBeginControllerReset(PIDE_CONTROLLER_EXTENSION ControllerExtension)
{
  int Retries;

  DPRINT("Controller Reset initiated on %04x\n", 
         ControllerExtension->ControlPortBase);

    /*  Assert drive reset line  */
  DPRINT("Asserting reset line\n");
  IDEWriteDriveControl(ControllerExtension->ControlPortBase, IDE_DC_SRST);

    /*  Wait for BSY assertion  */
  DPRINT("Waiting for BSY assertion\n");
  for (Retries = 0; Retries < IDE_MAX_RESET_RETRIES; Retries++) 
    {
      BYTE Status = IDEReadStatus(ControllerExtension->CommandPortBase);
      if ((Status & IDE_SR_BUSY)) 
        {
          break;
        }
      KeStallExecutionProcessor(10);
    }
  if (Retries == IDE_MAX_RESET_RETRIES)
    {
      DPRINT("Timeout on BSY assertion\n");
    }

    /*  Negate drive reset line  */
  DPRINT("Negating reset line\n");
  IDEWriteDriveControl(ControllerExtension->ControlPortBase, 0);

  // FIXME: handle case of no device 0

    /*  Set timer to check for end of reset  */
  ControllerExtension->TimerState = IDETimerResetWaitForBusyNegate;
  ControllerExtension->TimerCount = IDE_RESET_BUSY_TIMEOUT;
}

//    IDEIsr
//
//  DESCIPTION:
//    Handle interrupts for IDE devices
//
//  RUN LEVEL:
//    DIRQL
//
//  ARGUMENTS:
//    IN  PKINTERRUPT  Interrupt       The interrupt level in effect
//    IN  PVOID        ServiceContext  The driver supplied context
//                                     (the controller extension)
//  RETURNS:
//    TRUE   This ISR handled the interrupt
//    FALSE  Another ISR must handle this interrupt

static BOOLEAN STDCALL
IDEIsr(IN PKINTERRUPT Interrupt,
       IN PVOID ServiceContext)
{
  BOOLEAN   IsLastBlock, AnErrorOccured, RequestIsComplete;
  BYTE     *TargetAddress;
  int       Retries;
  NTSTATUS  ErrorStatus;
  ULONG     ErrorInformation;
  PIRP  Irp;
  PIDE_DEVICE_EXTENSION  DeviceExtension;
  PIDE_CONTROLLER_EXTENSION  ControllerExtension;

  if (IDEInitialized == FALSE)
    {
      return FALSE;
    }
  DPRINT ("IDEIsr called\n");

  ControllerExtension = (PIDE_CONTROLLER_EXTENSION) ServiceContext;

    //  Read the status port to clear the interrtupt (even if it's not ours).
  ControllerExtension->DeviceStatus = IDEReadStatus(ControllerExtension->CommandPortBase);

    //  If the interrupt is not ours, get the heck outta dodge.
  if (!ControllerExtension->OperationInProgress)
    {
      return FALSE;
    }

  DeviceExtension = ControllerExtension->DeviceForOperation;
  IsLastBlock = FALSE;
  AnErrorOccured = FALSE;
  RequestIsComplete = FALSE;
  ErrorStatus = STATUS_SUCCESS;
  ErrorInformation = 0;

    //  Handle error condition if it exists
  if (ControllerExtension->DeviceStatus & IDE_SR_ERR) 
    {
      BYTE ErrorReg, SectorCount, SectorNum, CylinderLow, CylinderHigh;
      BYTE DriveHead;

        //  Log the error
      ErrorReg = IDEReadError(ControllerExtension->CommandPortBase);
      CylinderLow = IDEReadCylinderLow(ControllerExtension->CommandPortBase);
      CylinderHigh = IDEReadCylinderHigh(ControllerExtension->CommandPortBase);
      DriveHead = IDEReadDriveHead(ControllerExtension->CommandPortBase);
      SectorCount = IDEReadSectorCount(ControllerExtension->CommandPortBase);
      SectorNum = IDEReadSectorNum(ControllerExtension->CommandPortBase);
        // FIXME: should use the NT error logging facility
      DbgPrint ("IDE Error: OP:%02x STAT:%02x ERR:%02x CYLLO:%02x CYLHI:%02x SCNT:%02x SNUM:%02x\n", 
                DeviceExtension->Operation, 
                ControllerExtension->DeviceStatus, 
                ErrorReg, 
                CylinderLow,
                CylinderHigh, 
                SectorCount, 
                SectorNum);

        // FIXME: should retry the command and perhaps recalibrate the drive

        //  Set error status information
      AnErrorOccured = TRUE;
      ErrorStatus = STATUS_DISK_OPERATION_FAILED;
      ErrorInformation = 
          (((((((CylinderHigh << 8) + CylinderLow) * 
              DeviceExtension->LogicalHeads) + 
              (DriveHead % DeviceExtension->LogicalHeads)) * 
              DeviceExtension->SectorsPerLogTrk) + SectorNum - 1) -
          DeviceExtension->StartingSector) * DeviceExtension->BytesPerSector;
    } 
  else 
    {

        // Check controller and setup for next transfer
      switch (DeviceExtension->Operation) 
        {
        case  IRP_MJ_READ:

            //  Update controller/device state variables
          TargetAddress = DeviceExtension->TargetAddress;
          DeviceExtension->TargetAddress += DeviceExtension->BytesPerSector;
          DeviceExtension->BytesToTransfer -= DeviceExtension->BytesPerSector;
          DeviceExtension->SectorsTransferred++;

            //  Remember whether DRQ should be low at end (last block read)
          IsLastBlock = DeviceExtension->BytesToTransfer == 0;

            //  Wait for DRQ assertion
          for (Retries = 0; Retries < IDE_MAX_DRQ_RETRIES &&
              !(IDEReadStatus(ControllerExtension->CommandPortBase) & IDE_SR_DRQ);
              Retries++) 
            {
              KeStallExecutionProcessor(10);
            }

            //  Copy the block of data
          IDEReadBlock(ControllerExtension->CommandPortBase, 
                       TargetAddress,
                       IDE_SECTOR_BUF_SZ);

            //  check DRQ
          if (IsLastBlock) 
            {
              for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES &&
                  (IDEReadStatus(ControllerExtension->CommandPortBase) & IDE_SR_BUSY);
                  Retries++) 
                {
                  KeStallExecutionProcessor(10);
                }

                //  Check for data overrun
              if (IDEReadStatus(ControllerExtension->CommandPortBase) & IDE_SR_DRQ) 
                {
                  AnErrorOccured = TRUE;
                  ErrorStatus = STATUS_DATA_OVERRUN;
                  ErrorInformation = 0;
                } 
              else 
                {

                    //  Setup next transfer or set RequestIsComplete
                  if (DeviceExtension->BytesRequested > 
                      DeviceExtension->BytesPerSector * IDE_MAX_SECTORS_PER_XFER) 
                    {
                      DeviceExtension->StartingSector += DeviceExtension->SectorsTransferred;
                      DeviceExtension->SectorsTransferred = 0;
                      DeviceExtension->BytesToTransfer = 
                      DeviceExtension->BytesPerSector * IDE_MAX_SECTORS_PER_XFER;
                      DeviceExtension->BytesRequested -= DeviceExtension->BytesToTransfer;
                    } 
                  else if (DeviceExtension->BytesRequested > 0) 
                    {
                      DeviceExtension->StartingSector += DeviceExtension->SectorsTransferred;
                      DeviceExtension->SectorsTransferred = 0;
                      DeviceExtension->BytesToTransfer = DeviceExtension->BytesRequested;
                      DeviceExtension->BytesRequested -= DeviceExtension->BytesToTransfer;
                    } 
                  else
                    {
                      RequestIsComplete = TRUE;
                    }
                }
            }
          break;

        case IRP_MJ_WRITE:

            //  check DRQ
          if (DeviceExtension->BytesToTransfer == 0) 
            {
              for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES &&
                  (IDEReadStatus(ControllerExtension->CommandPortBase) & IDE_SR_BUSY);
                   Retries++) 
                {
                  KeStallExecutionProcessor(10);
                }

                //  Check for data overrun
              if (IDEReadStatus(ControllerExtension->CommandPortBase) & IDE_SR_DRQ) 
                {
                  AnErrorOccured = TRUE;
                  ErrorStatus = STATUS_DATA_OVERRUN;
                  ErrorInformation = 0;
                } 
              else 
                {

                    //  Setup next transfer or set RequestIsComplete
                  IsLastBlock = TRUE;
                  if (DeviceExtension->BytesRequested > 
                      DeviceExtension->BytesPerSector * IDE_MAX_SECTORS_PER_XFER) 
                    {
                      DeviceExtension->StartingSector += DeviceExtension->SectorsTransferred;
                      DeviceExtension->SectorsTransferred = 0;
                      DeviceExtension->BytesToTransfer = 
                          DeviceExtension->BytesPerSector * IDE_MAX_SECTORS_PER_XFER;
                      DeviceExtension->BytesRequested -= DeviceExtension->BytesToTransfer;
                    } 
                  else if (DeviceExtension->BytesRequested > 0) 
                    {
                      DeviceExtension->StartingSector += DeviceExtension->SectorsTransferred;
                      DeviceExtension->SectorsTransferred = 0;
                      DeviceExtension->BytesToTransfer = DeviceExtension->BytesRequested;
                      DeviceExtension->BytesRequested -= DeviceExtension->BytesToTransfer;
                    } 
                  else 
                    {
                      RequestIsComplete = TRUE;
                    }
                }
            } 
          else 
            {

                //  Update controller/device state variables
              TargetAddress = DeviceExtension->TargetAddress;
              DeviceExtension->TargetAddress += DeviceExtension->BytesPerSector;
              DeviceExtension->BytesToTransfer -= DeviceExtension->BytesPerSector;
              DeviceExtension->SectorsTransferred++;

                //  Write block to controller
              IDEWriteBlock(ControllerExtension->CommandPortBase, 
                            TargetAddress,
                            IDE_SECTOR_BUF_SZ);
            }
          break;
        }
    }

  //  If there was an error or the request is done, complete the packet
  if (AnErrorOccured || RequestIsComplete) 
    {
      //  Set the return status and info values
      Irp = ControllerExtension->CurrentIrp;
      Irp->IoStatus.Status = ErrorStatus;
      Irp->IoStatus.Information = ErrorInformation;

      //  Clear out controller fields
      ControllerExtension->OperationInProgress = FALSE;
      ControllerExtension->DeviceStatus = 0;

      //  Queue the Dpc to finish up
      IoRequestDpc(DeviceExtension->DeviceObject, 
                   Irp, 
                   ControllerExtension);
    } 
  else if (IsLastBlock)
    {
      //  Else more data is needed, setup next device I/O
      IDEStartController((PVOID)DeviceExtension);
    }

  return TRUE;
}

//    IDEDpcForIsr
//  DESCRIPTION:
//
//  RUN LEVEL:
//
//  ARGUMENTS:
//    IN PKDPC          Dpc
//    IN PDEVICE_OBJECT DpcDeviceObject
//    IN PIRP           DpcIrp
//    IN PVOID          DpcContext
//
static VOID
IDEDpcForIsr(IN PKDPC Dpc,
             IN PDEVICE_OBJECT DpcDeviceObject,
             IN PIRP DpcIrp,
             IN PVOID DpcContext)
{
  DPRINT("IDEDpcForIsr()\n");
  IDEFinishOperation((PIDE_CONTROLLER_EXTENSION) DpcContext);
}

//    IDEFinishOperation

static VOID
IDEFinishOperation(PIDE_CONTROLLER_EXTENSION ControllerExtension)
{
  PIDE_DEVICE_EXTENSION DeviceExtension;
  PIRP Irp;
  ULONG Operation;

  DeviceExtension = ControllerExtension->DeviceForOperation;
  Irp = ControllerExtension->CurrentIrp;
  Operation = DeviceExtension->Operation;
  ControllerExtension->OperationInProgress = FALSE;
  ControllerExtension->DeviceForOperation = 0;
  ControllerExtension->CurrentIrp = 0;

  //  Deallocate the controller
  IoFreeController(DeviceExtension->ControllerObject);

  //  Start the next packet
  IoStartNextPacketByKey(DeviceExtension->DeviceObject, 
                         FALSE, 
                         DeviceExtension->StartingSector);

  //  Flush cache if necessary
  if (Operation == IRP_MJ_READ) 
    {
      KeFlushIoBuffers(Irp->MdlAddress, TRUE, FALSE);
    }
  //  Issue completion of the current packet
  //  return status information too
  Irp->IoStatus.Status = STATUS_SUCCESS;
  Irp->IoStatus.Information = DeviceExtension->SectorsTransferred * DeviceExtension->BytesPerSector;
  IoCompleteRequest(Irp, IO_DISK_INCREMENT);
}

//    IDEIoTimer
//  DESCRIPTION:
//    This function handles timeouts and other time delayed processing
//
//  RUN LEVEL:
//
//  ARGUMENTS:
//    IN  PDEVICE_OBJECT  DeviceObject  Device object registered with timer
//    IN  PVOID           Context       the Controller extension for the
//                                      controller the device is on
//
static VOID STDCALL
IDEIoTimer(PDEVICE_OBJECT DeviceObject,
           PVOID Context)
{
  PIDE_CONTROLLER_EXTENSION  ControllerExtension;

    //  Setup Extension pointer
  ControllerExtension = (PIDE_CONTROLLER_EXTENSION) Context;
  DPRINT("Timer activated for %04lx\n", ControllerExtension->CommandPortBase);

    //  Handle state change if necessary
  switch (ControllerExtension->TimerState) 
    {
      case IDETimerResetWaitForBusyNegate:
        if (!(IDEReadStatus(ControllerExtension->CommandPortBase) & 
            IDE_SR_BUSY))
          {
            DPRINT("Busy line has negated, waiting for DRDY assert\n");
            ControllerExtension->TimerState = IDETimerResetWaitForDrdyAssert;
            ControllerExtension->TimerCount = IDE_RESET_DRDY_TIMEOUT;
            return;
          }
        break;
        
      case IDETimerResetWaitForDrdyAssert:
        if (IDEReadStatus(ControllerExtension->CommandPortBase) & 
            IDE_SR_DRQ)
          {
            DPRINT("DRDY has asserted, reset complete\n");
            ControllerExtension->TimerState = IDETimerIdle;
            ControllerExtension->TimerCount = 0;

              // FIXME: get diagnostic code from drive 0

              /*  Start current packet command again  */
            if (!KeSynchronizeExecution(ControllerExtension->Interrupt, 
                                        IDEStartController,
                                        ControllerExtension->DeviceForOperation))
              {
                IDEFinishOperation(ControllerExtension);
              }
            return;
          }
        break;

      default:
        break;
    }

    //  If we're counting down, then count.
  if (ControllerExtension->TimerCount > 0) 
    {
      ControllerExtension->TimerCount--;

      //  Else we'll check the state and process if necessary
    } 
  else 
    {
      switch (ControllerExtension->TimerState) 
        {
          case IDETimerIdle:
            break;

          case IDETimerCmdWait:
              /*  Command timed out, reset drive and try again or fail  */
            DPRINT("Timeout waiting for command completion\n");
            if (++ControllerExtension->Retries > IDE_MAX_CMD_RETRIES)
              {
		 if (ControllerExtension->CurrentIrp != NULL)
		   {
                      DbgPrint ("Max retries has been reached, IRP finished with error\n");
		      ControllerExtension->CurrentIrp->IoStatus.Status = STATUS_IO_TIMEOUT;
		      ControllerExtension->CurrentIrp->IoStatus.Information = 0;
		      IDEFinishOperation(ControllerExtension);
		   }
                ControllerExtension->TimerState = IDETimerIdle;
                ControllerExtension->TimerCount = 0;
              }
            else
              {
                IDEBeginControllerReset(ControllerExtension);
              }
            break;

          case IDETimerResetWaitForBusyNegate:
          case IDETimerResetWaitForDrdyAssert:
            if (ControllerExtension->CurrentIrp != NULL)
              {
                DbgPrint ("Timeout waiting for drive reset, giving up on IRP\n");
                ControllerExtension->CurrentIrp->IoStatus.Status = 
                  STATUS_IO_TIMEOUT;
                ControllerExtension->CurrentIrp->IoStatus.Information = 0;
                IDEFinishOperation(ControllerExtension);
              }
            ControllerExtension->TimerState = IDETimerIdle;
            ControllerExtension->TimerCount = 0;
            break;
        }
    }
}


