/*
 *  ReactOS kernel
 *  Copyright (C) 2001, 2002 ReactOS Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/* $Id: atapi.c,v 1.29 2002/09/19 16:17:35 ekohl Exp $
 *
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS ATAPI miniport driver
 * FILE:        services/storage/atapi/atapi.c
 * PURPOSE:     ATAPI miniport driver
 * PROGRAMMERS: Eric Kohl (ekohl@rz-online.de)
 * REVISIONS:
 *              09-09-2001 Created
 */

/*
 * Note:
 *   This driver is derived from Rex Jolliff's ide driver. Lots of his
 *   routines are still in here although they belong into the higher level
 *   drivers. They will be moved away as soon as possible.
 */

/*
 * TODO:
 *	- implement sending of atapi commands
 *	- handle removable atapi non-cdrom drives
 */

#define ENABLE_PCI
#define ENABLE_ISA

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

#include <ddk/ntddk.h>
#include <ddk/srb.h>
#include <ddk/scsi.h>
#include <ddk/ntddscsi.h>

#include "atapi.h"

#define NDEBUG
#include <debug.h>

#define VERSION  "0.0.1"


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

//    ATAPI_MINIPORT_EXTENSION
//
//  DESCRIPTION:
//    Extension to be placed in each port device object
//
//  ACCESS:
//    Allocated from NON-PAGED POOL
//    Available at any IRQL
//

typedef struct _ATAPI_MINIPORT_EXTENSION
{
  IDE_DRIVE_IDENTIFY DeviceParams[2];
  BOOLEAN DevicePresent[2];
  BOOLEAN DeviceAtapi[2];

  ULONG CommandPortBase;
  ULONG ControlPortBase;
  ULONG BusMasterRegisterBase;

  BOOLEAN ExpectingInterrupt;
  PSCSI_REQUEST_BLOCK CurrentSrb;

  PUSHORT DataBuffer;
} ATAPI_MINIPORT_EXTENSION, *PATAPI_MINIPORT_EXTENSION;


typedef struct _UNIT_EXTENSION
{
  ULONG Dummy;
} UNIT_EXTENSION, *PUNIT_EXTENSION;


//  -----------------------------------------------  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, 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 ULONG STDCALL
AtapiFindCompatiblePciController(PVOID DeviceExtension,
				 PVOID HwContext,
				 PVOID BusInformation,
				 PCHAR ArgumentString,
				 PPORT_CONFIGURATION_INFORMATION ConfigInfo,
				 PBOOLEAN Again);

static ULONG STDCALL
AtapiFindIsaBusController(PVOID DeviceExtension,
			  PVOID HwContext,
			  PVOID BusInformation,
			  PCHAR ArgumentString,
			  PPORT_CONFIGURATION_INFORMATION ConfigInfo,
			  PBOOLEAN Again);

static ULONG STDCALL
AtapiFindNativePciController(PVOID DeviceExtension,
			     PVOID HwContext,
			     PVOID BusInformation,
			     PCHAR ArgumentString,
			     PPORT_CONFIGURATION_INFORMATION ConfigInfo,
			     PBOOLEAN Again);

static BOOLEAN STDCALL
AtapiInitialize(IN PVOID DeviceExtension);

static BOOLEAN STDCALL
AtapiResetBus(IN PVOID DeviceExtension,
	      IN ULONG PathId);

static BOOLEAN STDCALL
AtapiStartIo(IN PVOID DeviceExtension,
	     IN PSCSI_REQUEST_BLOCK Srb);

static BOOLEAN STDCALL
AtapiInterrupt(IN PVOID DeviceExtension);

static BOOLEAN
AtapiFindDevices(PATAPI_MINIPORT_EXTENSION DeviceExtension,
		 PPORT_CONFIGURATION_INFORMATION ConfigInfo);

static BOOLEAN
AtapiIdentifyDevice(IN ULONG CommandPort,
		    IN ULONG ControlPort,
		    IN ULONG DriveNum,
		    IN BOOLEAN Atapi,
		    OUT PIDE_DRIVE_IDENTIFY DrvParms);

static BOOLEAN
IDEResetController(IN ULONG CommandPort,
		   IN ULONG ControlPort);

static int
AtapiPolledRead(IN ULONG CommandPort,
		IN ULONG ControlPort,
		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 ULONG
AtapiSendAtapiCommand(IN PATAPI_MINIPORT_EXTENSION DeviceExtension,
		      IN PSCSI_REQUEST_BLOCK Srb);

static ULONG
AtapiSendIdeCommand(IN PATAPI_MINIPORT_EXTENSION DeviceExtension,
		    IN PSCSI_REQUEST_BLOCK Srb);

static ULONG
AtapiInquiry(IN PATAPI_MINIPORT_EXTENSION DeviceExtension,
	     IN PSCSI_REQUEST_BLOCK Srb);

static ULONG
AtapiReadCapacity(IN PATAPI_MINIPORT_EXTENSION DeviceExtension,
		  IN PSCSI_REQUEST_BLOCK Srb);

static ULONG
AtapiReadWrite(IN PATAPI_MINIPORT_EXTENSION DeviceExtension,
	       IN PSCSI_REQUEST_BLOCK Srb);


static UCHAR
AtapiErrorToScsi(PVOID DeviceExtension,
		 PSCSI_REQUEST_BLOCK Srb);

//  ----------------------------------------------------------------  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)
{
  HW_INITIALIZATION_DATA InitData;
  NTSTATUS Status;

  DPRINT("ATAPI Driver %s\n", VERSION);
  DPRINT("RegistryPath: '%wZ'\n", RegistryPath);

  /* Initialize data structure */
  RtlZeroMemory(&InitData,
		sizeof(HW_INITIALIZATION_DATA));
  InitData.HwInitializationDataSize = sizeof(HW_INITIALIZATION_DATA);
  InitData.HwInitialize = AtapiInitialize;
  InitData.HwResetBus = AtapiResetBus;
  InitData.HwStartIo = AtapiStartIo;
  InitData.HwInterrupt = AtapiInterrupt;

  InitData.DeviceExtensionSize = sizeof(ATAPI_MINIPORT_EXTENSION);
  InitData.SpecificLuExtensionSize = sizeof(UNIT_EXTENSION);

  InitData.MapBuffers = TRUE;

  /* Search the PCI bus for compatibility mode ide controllers */
#ifdef ENABLE_PCI
  InitData.HwFindAdapter = AtapiFindCompatiblePciController;
  InitData.NumberOfAccessRanges = 3;
  InitData.AdapterInterfaceType = PCIBus;

  InitData.VendorId = NULL;
  InitData.VendorIdLength = 0;
  InitData.DeviceId = NULL;
  InitData.DeviceIdLength = 0;

  Status = ScsiPortInitialize(DriverObject,
			      RegistryPath,
			      &InitData,
			      NULL);
//  if (newStatus < statusToReturn)
//    statusToReturn = newStatus;
#endif

  /* Search the ISA bus for ide controllers */
#ifdef ENABLE_ISA
  InitData.HwFindAdapter = AtapiFindIsaBusController;
  InitData.NumberOfAccessRanges = 2;
  InitData.AdapterInterfaceType = Isa;

  InitData.VendorId = NULL;
  InitData.VendorIdLength = 0;
  InitData.DeviceId = NULL;
  InitData.DeviceIdLength = 0;

  Status = ScsiPortInitialize(DriverObject,
			      RegistryPath,
			      &InitData,
			      NULL);
//      if (newStatus < statusToReturn)
//        statusToReturn = newStatus;
#endif

  /* Search the PCI bus for native mode ide controllers */
#if 0
  InitData.HwFindAdapter = AtapiFindNativePciController;
  InitData.NumberOfAccessRanges = 2;
  InitData.AdapterInterfaceType = PCIBus;

  InitData.VendorId = NULL;
  InitData.VendorIdLength = 0;
  InitData.DeviceId = NULL;
  InitData.DeviceIdLength = 0;

  Status = ScsiPortInitialize(DriverObject,
			      RegistryPath,
			      &InitData,
			      (PVOID)i);
//  if (newStatus < statusToReturn)
//    statusToReturn = newStatus;
#endif

  DPRINT("Returning from DriverEntry\n");

  return(Status);
}


static ULONG STDCALL
AtapiFindCompatiblePciController(PVOID DeviceExtension,
				 PVOID HwContext,
				 PVOID BusInformation,
				 PCHAR ArgumentString,
				 PPORT_CONFIGURATION_INFORMATION ConfigInfo,
				 PBOOLEAN Again)
{
  PATAPI_MINIPORT_EXTENSION DevExt = (PATAPI_MINIPORT_EXTENSION)DeviceExtension;
  PCI_SLOT_NUMBER SlotNumber;
  PCI_COMMON_CONFIG PciConfig;
  ULONG DataSize;
  ULONG FunctionNumber;
  BOOLEAN ChannelFound;
  BOOLEAN DeviceFound;

  DPRINT("AtapiFindCompatiblePciController() Bus: %lu  Slot: %lu\n",
	  ConfigInfo->SystemIoBusNumber,
	  ConfigInfo->SlotNumber);

  *Again = FALSE;

  /* both channels were claimed: exit */
  if (ConfigInfo->AtdiskPrimaryClaimed == TRUE &&
      ConfigInfo->AtdiskSecondaryClaimed == TRUE)
    return(SP_RETURN_NOT_FOUND);


  SlotNumber.u.AsULONG = 0;
  for (FunctionNumber = 0 /*ConfigInfo->SlotNumber*/; FunctionNumber < 256; FunctionNumber++)
    {
      SlotNumber.u.AsULONG = FunctionNumber;

      ChannelFound = FALSE;
      DeviceFound = FALSE;

      DataSize = ScsiPortGetBusData(DeviceExtension,
				    PCIConfiguration,
				    0,
				    SlotNumber.u.AsULONG,
				    &PciConfig,
				    sizeof(PCI_COMMON_CONFIG));
//      if (DataSize != sizeof(PCI_COMMON_CONFIG) ||
//	  PciConfig.VendorID == PCI_INVALID_VENDORID)
      if (DataSize == 0)
	{
//	  if ((SlotNumber.u.AsULONG & 0x07) == 0)
//	    return(SP_RETURN_ERROR); /* No bus found */

	  continue;
//	  return(SP_RETURN_ERROR);
	}

      if (PciConfig.BaseClass == 0x01 &&
	  PciConfig.SubClass == 0x01) // &&
//	  (PciConfig.ProgIf & 0x05) == 0)
	{
	  /* both channels are in compatibility mode */
	  DPRINT("Bus %1lu  Device %2lu  Func %1lu  VenID 0x%04hx  DevID 0x%04hx\n",
		 ConfigInfo->SystemIoBusNumber,
		 SlotNumber.u.bits.DeviceNumber,
		 SlotNumber.u.bits.FunctionNumber,
		 PciConfig.VendorID,
		 PciConfig.DeviceID);
	  DPRINT("ProgIF 0x%02hx\n", PciConfig.ProgIf);

	  DPRINT("Found IDE controller in compatibility mode!\n");

	  ConfigInfo->NumberOfBuses = 1;
	  ConfigInfo->MaximumNumberOfTargets = 2;
	  ConfigInfo->MaximumTransferLength = 0x10000; /* max 64Kbyte */

	  if (PciConfig.ProgIf & 0x80)
	    {
	      DPRINT("Found IDE Bus Master controller!\n");
	      if (PciConfig.u.type0.BaseAddresses[4] & 0x00000001)
		{
		  DPRINT("  IDE Bus Master Registers at IO %lx\n",
			  PciConfig.u.type0.BaseAddresses[4] & ~0x00000003);
		}
	    }

	  if (ConfigInfo->AtdiskPrimaryClaimed == FALSE)
	    {
	      /* Both channels unclaimed: Claim primary channel */
	      DPRINT("Primary channel!\n");

	      DevExt->CommandPortBase = 0x01F0;
	      DevExt->ControlPortBase = 0x03F6;

	      ConfigInfo->BusInterruptLevel = 14;
	      ConfigInfo->BusInterruptVector = 14;
	      ConfigInfo->InterruptMode = LevelSensitive;

	      ConfigInfo->AccessRanges[0].RangeStart =
		ScsiPortConvertUlongToPhysicalAddress(0x01F0);
	      ConfigInfo->AccessRanges[0].RangeLength = 8;
	      ConfigInfo->AccessRanges[0].RangeInMemory = FALSE;

	      ConfigInfo->AccessRanges[1].RangeStart =
		ScsiPortConvertUlongToPhysicalAddress(0x03F6);
	      ConfigInfo->AccessRanges[1].RangeLength = 1;
	      ConfigInfo->AccessRanges[1].RangeInMemory = FALSE;

	      /* Claim bus master registers */
	      if (PciConfig.ProgIf & 0x80)
		{
		  DevExt->BusMasterRegisterBase =
		    PciConfig.u.type0.BaseAddresses[4] & ~0x00000003;

		  ConfigInfo->AccessRanges[2].RangeStart =
		    ScsiPortConvertUlongToPhysicalAddress(DevExt->BusMasterRegisterBase);
		  ConfigInfo->AccessRanges[2].RangeLength = 8;
		  ConfigInfo->AccessRanges[2].RangeInMemory = FALSE;
		}

	      ConfigInfo->AtdiskPrimaryClaimed = TRUE;
	      ChannelFound = TRUE;
	      *Again = TRUE;
	    }
	  else if (ConfigInfo->AtdiskSecondaryClaimed == FALSE)
	    {
	      /* Primary channel already claimed: claim secondary channel */
	      DPRINT("Secondary channel!\n");

	      DevExt->CommandPortBase = 0x0170;
	      DevExt->ControlPortBase = 0x0376;

	      ConfigInfo->BusInterruptLevel = 15;
	      ConfigInfo->BusInterruptVector = 15;
	      ConfigInfo->InterruptMode = LevelSensitive;

	      ConfigInfo->AccessRanges[0].RangeStart =
		ScsiPortConvertUlongToPhysicalAddress(0x0170);
	      ConfigInfo->AccessRanges[0].RangeLength = 8;
	      ConfigInfo->AccessRanges[0].RangeInMemory = FALSE;

	      ConfigInfo->AccessRanges[1].RangeStart =
		ScsiPortConvertUlongToPhysicalAddress(0x0376);
	      ConfigInfo->AccessRanges[1].RangeLength = 1;
	      ConfigInfo->AccessRanges[1].RangeInMemory = FALSE;

	      /* Claim bus master registers */
	      if (PciConfig.ProgIf & 0x80)
		{
		  DevExt->BusMasterRegisterBase =
		    (PciConfig.u.type0.BaseAddresses[4] & ~0x00000003) + 8;

		  ConfigInfo->AccessRanges[2].RangeStart =
		    ScsiPortConvertUlongToPhysicalAddress(DevExt->BusMasterRegisterBase);
		  ConfigInfo->AccessRanges[2].RangeLength = 8;
		  ConfigInfo->AccessRanges[2].RangeInMemory = FALSE;
		}

	      ConfigInfo->AtdiskSecondaryClaimed = TRUE;
	      ChannelFound = TRUE;
	      *Again = FALSE;
	    }

	  /* Find attached devices */
	  if (ChannelFound == TRUE)
	    {
	      DeviceFound = AtapiFindDevices(DevExt,
					     ConfigInfo);
	    }
	  DPRINT("AtapiFindCompatiblePciController() returns: SP_RETURN_FOUND\n");
	  return(SP_RETURN_FOUND);
        }
    }

  DPRINT("AtapiFindCompatiblePciController() returns: SP_RETURN_NOT_FOUND\n");

  return(SP_RETURN_NOT_FOUND);
}


static ULONG STDCALL
AtapiFindIsaBusController(PVOID DeviceExtension,
			  PVOID HwContext,
			  PVOID BusInformation,
			  PCHAR ArgumentString,
			  PPORT_CONFIGURATION_INFORMATION ConfigInfo,
			  PBOOLEAN Again)
{
  PATAPI_MINIPORT_EXTENSION DevExt = (PATAPI_MINIPORT_EXTENSION)DeviceExtension;
  BOOLEAN ChannelFound = FALSE;
  BOOLEAN DeviceFound = FALSE;

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

  *Again = FALSE;

  ConfigInfo->NumberOfBuses = 1;
  ConfigInfo->MaximumNumberOfTargets = 2;
  ConfigInfo->MaximumTransferLength = 0x10000; /* max 64Kbyte */

  if (ConfigInfo->AtdiskPrimaryClaimed == FALSE)
    {
      /* Both channels unclaimed: Claim primary channel */
      DPRINT("Primary channel!\n");

      DevExt->CommandPortBase = 0x01F0;
      DevExt->ControlPortBase = 0x03F6;

      ConfigInfo->BusInterruptLevel = 14;
      ConfigInfo->BusInterruptVector = 14;
      ConfigInfo->InterruptMode = LevelSensitive;

      ConfigInfo->AccessRanges[0].RangeStart = ScsiPortConvertUlongToPhysicalAddress(0x01F0);
      ConfigInfo->AccessRanges[0].RangeLength = 8;
      ConfigInfo->AccessRanges[0].RangeInMemory = FALSE;

      ConfigInfo->AccessRanges[1].RangeStart = ScsiPortConvertUlongToPhysicalAddress(0x03F6);
      ConfigInfo->AccessRanges[1].RangeLength = 1;
      ConfigInfo->AccessRanges[1].RangeInMemory = FALSE;

      ConfigInfo->AtdiskPrimaryClaimed = TRUE;
      ChannelFound = TRUE;
      *Again = FALSE/*TRUE*/;
    }
  else if (ConfigInfo->AtdiskSecondaryClaimed == FALSE)
    {
      /* Primary channel already claimed: claim secondary channel */
      DPRINT("Secondary channel!\n");

      DevExt->CommandPortBase = 0x0170;
      DevExt->ControlPortBase = 0x0376;

      ConfigInfo->BusInterruptLevel = 15;
      ConfigInfo->BusInterruptVector = 15;
      ConfigInfo->InterruptMode = LevelSensitive;

      ConfigInfo->AccessRanges[0].RangeStart = ScsiPortConvertUlongToPhysicalAddress(0x0170);
      ConfigInfo->AccessRanges[0].RangeLength = 8;
      ConfigInfo->AccessRanges[0].RangeInMemory = FALSE;

      ConfigInfo->AccessRanges[1].RangeStart = ScsiPortConvertUlongToPhysicalAddress(0x0376);
      ConfigInfo->AccessRanges[1].RangeLength = 1;
      ConfigInfo->AccessRanges[1].RangeInMemory = FALSE;

      ConfigInfo->AtdiskSecondaryClaimed = TRUE;
      ChannelFound = TRUE;
      *Again = FALSE;
    }
  else
    {
      DPRINT("AtapiFindIsaBusController() both channels claimed. Returns: SP_RETURN_NOT_FOUND\n");
      *Again = FALSE;
      return(SP_RETURN_NOT_FOUND);
    }

  /* Find attached devices */
  if (ChannelFound)
    {
      DeviceFound = AtapiFindDevices(DevExt,
				     ConfigInfo);
    }

  DPRINT("AtapiFindIsaBusController() returns: SP_RETURN_FOUND\n");
  return(SP_RETURN_FOUND);
}


static ULONG STDCALL
AtapiFindNativePciController(PVOID DeviceExtension,
			     PVOID HwContext,
			     PVOID BusInformation,
			     PCHAR ArgumentString,
			     PPORT_CONFIGURATION_INFORMATION ConfigInfo,
			     PBOOLEAN Again)
{
  PATAPI_MINIPORT_EXTENSION DevExt = (PATAPI_MINIPORT_EXTENSION)DeviceExtension;

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

  *Again = FALSE;

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

  return(SP_RETURN_NOT_FOUND);
}


static BOOLEAN STDCALL
AtapiInitialize(IN PVOID DeviceExtension)
{
  return(TRUE);
}


static BOOLEAN STDCALL
AtapiResetBus(IN PVOID DeviceExtension,
	      IN ULONG PathId)
{
  return(TRUE);
}


static BOOLEAN STDCALL
AtapiStartIo(IN PVOID DeviceExtension,
	     IN PSCSI_REQUEST_BLOCK Srb)
{
  PATAPI_MINIPORT_EXTENSION DevExt;
  ULONG Result;

  DPRINT("AtapiStartIo() called\n");

  DevExt = (PATAPI_MINIPORT_EXTENSION)DeviceExtension;

  switch (Srb->Function)
    {
      case SRB_FUNCTION_EXECUTE_SCSI:
	DevExt->CurrentSrb = Srb;
	if (DevExt->DeviceAtapi[Srb->TargetId] == TRUE)
	  {
	    Result = AtapiSendAtapiCommand(DevExt,
					   Srb);
	  }
	else
	  {
	    Result = AtapiSendIdeCommand(DevExt,
					 Srb);
	  }
	break;

    }

  Srb->SrbStatus = Result;


  if (Result != SRB_STATUS_PENDING)
    {
      DevExt->CurrentSrb = NULL;
      Srb->SrbStatus = (UCHAR)Result;

      ScsiPortNotification(RequestComplete,
			   DeviceExtension,
			   Srb);
      ScsiPortNotification(NextRequest,
			   DeviceExtension,
			   NULL);
    }
  else
    {
      DPRINT("SrbStatus = SRB_STATUS_PENDING\n");
    }

  DPRINT("AtapiStartIo() done\n");

  return(TRUE);
}


static BOOLEAN STDCALL
AtapiInterrupt(IN PVOID DeviceExtension)
{
  PATAPI_MINIPORT_EXTENSION DevExt;
  PSCSI_REQUEST_BLOCK Srb;
  ULONG CommandPortBase;
  ULONG ControlPortBase;

  UCHAR DeviceStatus;
  BOOLEAN IsLastBlock;
  BOOLEAN IsAtapi;
  ULONG Retries;
  PUCHAR TargetAddress;
  ULONG SectorSize;
  ULONG TransferSize;

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

  DevExt = (PATAPI_MINIPORT_EXTENSION)DeviceExtension;
  if (DevExt->ExpectingInterrupt == FALSE)
    {
      DPRINT("AtapiInterrupt(): Unexpected interrupt\n");
      return(FALSE);
    }

  Srb = DevExt->CurrentSrb;
  DPRINT("Srb: %p\n", Srb);

  CommandPortBase = DevExt->CommandPortBase;
  ControlPortBase = DevExt->ControlPortBase;
  DPRINT("CommandPortBase: %lx  ControlPortBase: %lx\n", CommandPortBase, ControlPortBase);

  IsAtapi = DevExt->DeviceAtapi[Srb->TargetId];
  DPRINT("IsAtapi == %s\n", (IsAtapi) ? "TRUE" : "FALSE");

  IsLastBlock = FALSE;

  DeviceStatus = IDEReadStatus(CommandPortBase);
  DPRINT("DeviceStatus: %x\n", DeviceStatus);

  if (DeviceStatus & IDE_SR_BUSY)
    {
      /* Wait for BUSY to drop */
      for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES; Retries++)
	{
	  DeviceStatus = IDEReadStatus(CommandPortBase);
	  if (!(DeviceStatus & IDE_SR_BUSY))
	    {
	      break;
	    }
	  ScsiPortStallExecution(10);
	}
      if (Retries >= IDE_MAX_BUSY_RETRIES)
	{
	  DPRINT1("Drive is BUSY for too long\n");
	  /* FIXME: handle timeout */
	}
    }

  if ((DeviceStatus & IDE_SR_ERR) &&
      (Srb->Cdb[0] != SCSIOP_REQUEST_SENSE))
    {
      /* Report error condition */
      Srb->SrbStatus = SRB_STATUS_ERROR;
      IsLastBlock = TRUE;
    }
  else
    {
      if (Srb->SrbFlags & SRB_FLAGS_DATA_IN)
	{
	  DPRINT("Read data\n");

	  /* Update controller/device state variables */
	  TargetAddress = Srb->DataBuffer;

	  if (IsAtapi)
	    {
	      TransferSize = IDEReadCylinderLow(CommandPortBase);
	      TransferSize += IDEReadCylinderHigh(CommandPortBase) << 8;
	    }
	  else
	    {
	      TransferSize = DevExt->DeviceParams[Srb->TargetId].BytesPerSector;
	    }

	  DPRINT("TransferLength: %lu\n", Srb->DataTransferLength);
	  DPRINT("TransferSize: %lu\n", TransferSize);

	  if (Srb->DataTransferLength <= TransferSize)
	    {
	      Srb->DataTransferLength = 0;
	      IsLastBlock = TRUE;
	    }
	  else
	    {
	      Srb->DataTransferLength -= TransferSize;
	      IsLastBlock = FALSE;
	    }
	  Srb->DataBuffer += TransferSize;
	  DPRINT("IsLastBlock == %s\n", (IsLastBlock) ? "TRUE" : "FALSE");

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

	  /* Copy the block of data */
	  IDEReadBlock(CommandPortBase,
		       TargetAddress,
		       TransferSize);

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

	      /* Check for data overrun */
	      if (IDEReadStatus(CommandPortBase) & IDE_SR_DRQ)
		{
		  /* FIXME: Handle error! */
		  DPRINT1("AtapiInterrupt(): data overrun error!");
		}
	    }

	  Srb->SrbStatus = SRB_STATUS_SUCCESS;
	}
      else if (Srb->SrbFlags & SRB_FLAGS_DATA_OUT)
	{
	  DPRINT("Write data\n");

	  if (Srb->DataTransferLength == 0)
	    {

	      for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES &&
		   (IDEReadStatus(CommandPortBase) & IDE_SR_BUSY);
		   Retries++)
		{
		  KeStallExecutionProcessor(10);
		}

	      /* Check for data overrun */
	      if (IDEReadStatus(CommandPortBase) & IDE_SR_DRQ)
		{
		  /* FIXME: Handle error! */
		  DPRINT1("AtapiInterrupt(): data overrun error!");
		}

	      DevExt->ExpectingInterrupt = FALSE;
	      IsLastBlock = TRUE;
	    }
	  else
	    {
	      /* Update SRB data */
	      SectorSize = DevExt->DeviceParams[Srb->TargetId].BytesPerSector;

	      TargetAddress = Srb->DataBuffer;
	      Srb->DataBuffer += SectorSize;
	      Srb->DataTransferLength -= SectorSize;

	      /* Write the sector */
	      IDEWriteBlock(CommandPortBase,
			    TargetAddress,
			    SectorSize);
	    }

	  Srb->SrbStatus = SRB_STATUS_SUCCESS;
	}
      else
	{
	  DPRINT1("Unspecified transfer direction!\n");
	  Srb->SrbStatus = SRB_STATUS_SUCCESS; // SRB_STATUS_ERROR;
	  IsLastBlock = TRUE;
	}
    }


  if (Srb->SrbStatus == SRB_STATUS_ERROR)
    {
      Srb->SrbStatus = AtapiErrorToScsi(DeviceExtension,
					Srb);
    }


  /* complete this packet */
  if (IsLastBlock)
    {
      DevExt->ExpectingInterrupt = FALSE;

      ScsiPortNotification(RequestComplete,
			   DeviceExtension,
			   Srb);

      ScsiPortNotification(NextRequest,
			   DeviceExtension,
			   NULL);
    }

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

  return(TRUE);
}






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


/**********************************************************************
 * NAME							INTERNAL
 *	AtapiFindDevices
 *
 * DESCRIPTION
 *	Searches for devices on the given port.
 *
 * RUN LEVEL
 *	PASSIVE_LEVEL
 *
 * ARGUMENTS
 *	DeviceExtension
 *		Port device specific information.
 *
 *	ConfigInfo
 *		Port configuration information.
 *
 * RETURN VALUE
 *	TRUE: At least one device is attached to the port.
 *	FALSE: No device is attached to the port.
 */

static BOOLEAN
AtapiFindDevices(PATAPI_MINIPORT_EXTENSION DeviceExtension,
		 PPORT_CONFIGURATION_INFORMATION ConfigInfo)
{
  BOOLEAN DeviceFound = FALSE;
  ULONG CommandPortBase;
  ULONG ControlPortBase;
  ULONG UnitNumber;
  ULONG Retries;
  BYTE High, Low;

  DPRINT("AtapiFindDevices() called\n");

  CommandPortBase = ScsiPortConvertPhysicalAddressToUlong(ConfigInfo->AccessRanges[0].RangeStart);
  DPRINT("  CommandPortBase: %x\n", CommandPortBase);

  ControlPortBase = ScsiPortConvertPhysicalAddressToUlong(ConfigInfo->AccessRanges[1].RangeStart);
  DPRINT("  ControlPortBase: %x\n", ControlPortBase);

  for (UnitNumber = 0; UnitNumber < 2; UnitNumber++)
    {
      /* Select drive */
      IDEWriteDriveHead(CommandPortBase,
			IDE_DH_FIXED | (UnitNumber ? IDE_DH_DRV1 : 0));
      ScsiPortStallExecution(500);

      /* Disable interrupts */
      IDEWriteDriveControl(ControlPortBase,
			   IDE_DC_nIEN);
      ScsiPortStallExecution(500);

      IDEWriteCylinderHigh(CommandPortBase, 0);
      IDEWriteCylinderLow(CommandPortBase, 0);
      IDEWriteCommand(CommandPortBase, IDE_CMD_RESET);

      for (Retries = 0; Retries < 20000; Retries++)
	{
	  if (!(IDEReadStatus(CommandPortBase) & IDE_SR_BUSY))
	    {
	      break;
	    }
	  ScsiPortStallExecution(150);
	}
      if (Retries >= 20000)
	{
	  DPRINT("Timeout on drive %lu\n", UnitNumber);
	  DeviceExtension->DevicePresent[UnitNumber] = FALSE;
	  continue;
	}

      High = IDEReadCylinderHigh(CommandPortBase);
      Low = IDEReadCylinderLow(CommandPortBase);

      DPRINT("  Check drive %lu: High 0x%x Low 0x%x\n",
	     UnitNumber,
	     High,
	     Low);

      if (High == 0xEB && Low == 0x14)
	{
	  if (AtapiIdentifyDevice(CommandPortBase,
				  ControlPortBase,
				  UnitNumber,
				  TRUE,
				  &DeviceExtension->DeviceParams[UnitNumber]))
	    {
	      DPRINT("  ATAPI drive found!\n");
	      DeviceExtension->DevicePresent[UnitNumber] = TRUE;
	      DeviceExtension->DeviceAtapi[UnitNumber] = TRUE;
	      DeviceFound = TRUE;
	    }
	  else
	    {
	      DPRINT("  No ATAPI drive found!\n");
	    }
	}
      else
	{
	  if (AtapiIdentifyDevice(CommandPortBase,
				  ControlPortBase,
				  UnitNumber,
				  FALSE,
				  &DeviceExtension->DeviceParams[UnitNumber]))
	    {
	      DPRINT("  IDE drive found!\n");
	      DeviceExtension->DevicePresent[UnitNumber] = TRUE;
	      DeviceExtension->DeviceAtapi[UnitNumber] = FALSE;
	      DeviceFound = TRUE;
	    }
	  else
	    {
	      DPRINT("  No IDE drive found!\n");
	    }
	}
    }


  DPRINT("AtapiFindDrives() done (DeviceFound %s)\n", (DeviceFound) ? "TRUE" : "FALSE");

  return(DeviceFound);
}


//    AtapiResetController
//
//  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:
//

static BOOLEAN
AtapiResetController(IN ULONG CommandPort,
		     IN ULONG ControlPort)
{
  int Retries;

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

  /* Wait for min. 25 microseconds */
  ScsiPortStallExecution(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;
	}
      ScsiPortStallExecution(10);
    }

  if (Retries >= IDE_RESET_BUSY_TIMEOUT * 1000)
    {
      return(FALSE);
    }

    //  return TRUE if controller came back to life. and
    //  the registers are initialized correctly
  return(IDEReadError(CommandPort) == 1);
}

/*
 *  AtapiIdentifyDevice
 *
 *  DESCRIPTION:
 *	Get the identification block from the drive
 *
 *  RUN LEVEL:
 *	PASSIVE_LEVEL
 *
 *  ARGUMENTS:
 *	CommandPort
 *		Address of the command port
 *	ControlPort
 *		Address of the control port
 *	DriveNum
 *		The drive index (0,1)
 *	Atapi
 *		Send an ATA(FALSE) or an ATAPI(TRUE) identify comand
 *	DrvParms
 *		Address to write drive ident block
 *
 *  RETURNS:
 *	TRUE: The drive identification block was retrieved successfully
 *	FALSE: an error ocurred
 */

static BOOLEAN
AtapiIdentifyDevice(IN ULONG CommandPort,
		    IN ULONG ControlPort,
		    IN ULONG DriveNum,
		    IN BOOLEAN Atapi,
		    OUT PIDE_DRIVE_IDENTIFY DrvParms)
{
  LONG i;

  /*  Get the Drive Identify block from drive or die  */
  if (AtapiPolledRead(CommandPort,
		      ControlPort,
		      0,
		      1,
		      0,
		      0,
		      0,
		      (DriveNum ? IDE_DH_DRV1 : 0),
		      (Atapi ? IDE_CMD_IDENT_ATAPI_DRV : IDE_CMD_IDENT_ATA_DRV),
		      (BYTE *)DrvParms) != 0)
    {
      DPRINT("IDEPolledRead() failed\n");
      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));

  DPRINT("BytesPerSector %d\n", DrvParms->BytesPerSector);
  if (DrvParms->BytesPerSector == 0)
    {
      DrvParms->BytesPerSector = 512;
    }
  else
    {
      for (i = 15; i >= 0; i--)
	{
	  if (DrvParms->BytesPerSector & (1 << i))
	    {
	      DrvParms->BytesPerSector = 1 << i;
	      break;
	    }
	}
    }
  DPRINT("BytesPerSector %d\n", DrvParms->BytesPerSector);

  return(TRUE);
}


//    AtapiPolledRead
//
//  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
AtapiPolledRead(IN ULONG CommandPort,
		IN ULONG ControlPort,
		IN BYTE PreComp,
		IN BYTE SectorCnt,
		IN BYTE SectorNum,
		IN BYTE CylinderLow,
		IN BYTE CylinderHigh,
		IN BYTE DrvHead,
		IN BYTE Command,
		OUT BYTE *Buffer)
{
  ULONG SectorCount = 0;
  ULONG RetryCount;
  BOOLEAN Junk = FALSE;
  UCHAR Status;
  UCHAR Control;

//#if 0
  /* Wait for BUSY to clear */
  for (RetryCount = 0; RetryCount < IDE_MAX_BUSY_RETRIES; RetryCount++)
    {
      Status = IDEReadStatus(CommandPort);
      if (!(Status & IDE_SR_BUSY))
        {
          break;
        }
      ScsiPortStallExecution(10);
    }
  DPRINT("status=%02x\n", Status);
  DPRINT("waited %ld usecs for busy to clear\n", RetryCount * 10);
  if (RetryCount >= IDE_MAX_BUSY_RETRIES)
    {
      DPRINT("Drive is BUSY for too long\n");
      return(IDE_ER_ABRT);
    }
//#endif

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

  /* Disable interrupts */
  Control = IDEReadAltStatus(ControlPort);
  IDEWriteDriveControl(ControlPort, Control | IDE_DC_nIEN);
  ScsiPortStallExecution(500);

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

  /*  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(CommandPort, PreComp);
  IDEWriteSectorCount(CommandPort, SectorCnt);
  IDEWriteSectorNum(CommandPort, SectorNum);
  IDEWriteCylinderHigh(CommandPort, CylinderHigh);
  IDEWriteCylinderLow(CommandPort, CylinderLow);
  IDEWriteDriveHead(CommandPort, IDE_DH_FIXED | DrvHead);

  /*  Issue the command  */
  IDEWriteCommand(CommandPort, Command);
  ScsiPortStallExecution(50);

  /*  wait for DRQ or error  */
  for (RetryCount = 0; RetryCount < IDE_MAX_POLL_RETRIES; RetryCount++)
    {
      Status = IDEReadStatus(CommandPort);
      if (!(Status & IDE_SR_BUSY))
	{
	  if (Status & IDE_SR_ERR)
	    {
	      IDEWriteDriveControl(ControlPort, Control & ~IDE_DC_nIEN);
	      return(IDE_ER_ABRT);
	    }
	  if (Status & IDE_SR_DRQ)
	    {
	      break;
	    }
	  else
	    {
	      IDEWriteDriveControl(ControlPort, Control & ~IDE_DC_nIEN);
	      return(IDE_ER_ABRT);
	    }
	}
      ScsiPortStallExecution(10);
    }

  /*  timed out  */
  if (RetryCount >= IDE_MAX_POLL_RETRIES)
    {
      IDEWriteDriveControl(ControlPort, Control & ~IDE_DC_nIEN);
      return(IDE_ER_ABRT);
    }

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

      /*  Check for error or more sectors to read  */
      for (RetryCount = 0; RetryCount < IDE_MAX_BUSY_RETRIES; RetryCount++)
	{
	  Status = IDEReadStatus(CommandPort);
	  if (!(Status & IDE_SR_BUSY))
	    {
	      if (Status & IDE_SR_ERR)
		{
		  IDEWriteDriveControl(ControlPort, Control & ~IDE_DC_nIEN);
		  return(IDE_ER_ABRT);
		}
	      if (Status & IDE_SR_DRQ)
		{
		  if (SectorCount >= SectorCnt)
		    {
		      DPRINT("Buffer size exceeded!\n");
		      Junk = TRUE;
		    }
		  break;
		}
	      else
		{
		  if (SectorCount > SectorCnt)
		    {
		      DPRINT("Read %lu sectors of junk!\n",
			     SectorCount - SectorCnt);
		    }
		  IDEWriteDriveControl(ControlPort, Control & ~IDE_DC_nIEN);
		  return(0);
		}
	    }
	}
    }
}


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

static ULONG
AtapiSendAtapiCommand(IN PATAPI_MINIPORT_EXTENSION DeviceExtension,
		      IN PSCSI_REQUEST_BLOCK Srb)
{
  UCHAR ByteCountHigh;
  UCHAR ByteCountLow;
  ULONG Retries;
  ULONG CdbSize;
  UCHAR Status;

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

  if (Srb->PathId != 0)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_PATH_ID;
      return(SRB_STATUS_INVALID_PATH_ID);
    }

  if (Srb->TargetId > 1)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_TARGET_ID;
      return(SRB_STATUS_INVALID_TARGET_ID);
    }

  if (Srb->Lun != 0)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_LUN;
      return(SRB_STATUS_INVALID_LUN);
    }

  if (DeviceExtension->DevicePresent[Srb->TargetId] == FALSE)
    {
      Srb->SrbStatus = SRB_STATUS_NO_DEVICE;
      return(SRB_STATUS_NO_DEVICE);
    }

  DPRINT("AtapiSendAtapiCommand(): TargetId: %lu\n",
	 Srb->TargetId);

  if (Srb->Cdb[0] == SCSIOP_INQUIRY)
    return(AtapiInquiry(DeviceExtension,
			Srb));

  /* Set pointer to data buffer. */
  DeviceExtension->DataBuffer = (PUSHORT)Srb->DataBuffer;
  DeviceExtension->CurrentSrb = Srb;

  /* Wait for BUSY to clear */
  for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES; Retries++)
    {
      Status = IDEReadStatus(DeviceExtension->CommandPortBase);
      if (!(Status & IDE_SR_BUSY))
        {
          break;
        }
      ScsiPortStallExecution(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");
      return(SRB_STATUS_BUSY);
    }

  /* Select the desired drive */
  IDEWriteDriveHead(DeviceExtension->CommandPortBase,
		    IDE_DH_FIXED | (Srb->TargetId ? IDE_DH_DRV1 : 0));

  /* Wait a little while */
  ScsiPortStallExecution(50);

#if 0
  /* Wait for BUSY to clear and DRDY to assert */
  for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES; Retries++)
    {
      Status = IDEReadStatus(DeviceExtension->CommandPortBase);
      if (!(Status & IDE_SR_BUSY) && (Status & IDE_SR_DRDY))
	{
	  break;
	}
      ScsiPortStallExecution(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");
      return(SRB_STATUS_BUSY);
    }
#endif

  if (Srb->DataTransferLength < 0x10000)
    {
      ByteCountLow = (UCHAR)(Srb->DataTransferLength & 0xFF);
      ByteCountHigh = (UCHAR)(Srb->DataTransferLength >> 8);
    }
  else
    {
      ByteCountLow = 0xFF;
      ByteCountHigh = 0xFF;
    }

  /* Set feature register */
  IDEWritePrecomp(DeviceExtension->CommandPortBase, 0);

  /* Set command packet length */
  IDEWriteCylinderHigh(DeviceExtension->CommandPortBase, ByteCountHigh);
  IDEWriteCylinderLow(DeviceExtension->CommandPortBase, ByteCountLow);

  /* Issue command to drive */
  IDEWriteCommand(DeviceExtension->CommandPortBase, 0xA0); /* Packet command */

  /* Wait for DRQ to assert */
  for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES; Retries++)
    {
      Status = IDEReadStatus(DeviceExtension->CommandPortBase);
      if ((Status & IDE_SR_DRQ))
	{
	  break;
	}
      ScsiPortStallExecution(10);
    }

  CdbSize = (DeviceExtension->DeviceParams[Srb->TargetId].ConfigBits & 0x3 == 1) ? 16 : 12;
  DPRINT("CdbSize: %lu\n", CdbSize);

  /* Write command packet */
  IDEWriteBlock(DeviceExtension->CommandPortBase,
		(PUSHORT)Srb->Cdb,
		CdbSize);

  DeviceExtension->ExpectingInterrupt = TRUE;

  DPRINT("AtapiSendAtapiCommand() done\n");

  return(SRB_STATUS_PENDING);
}


static ULONG
AtapiSendIdeCommand(IN PATAPI_MINIPORT_EXTENSION DeviceExtension,
		    IN PSCSI_REQUEST_BLOCK Srb)
{
  ULONG SrbStatus = SRB_STATUS_SUCCESS;

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

  DPRINT("PathId: %lu  TargetId: %lu  Lun: %lu\n",
	 Srb->PathId,
	 Srb->TargetId,
	 Srb->Lun);

  switch (Srb->Cdb[0])
    {
      case SCSIOP_INQUIRY:
	SrbStatus = AtapiInquiry(DeviceExtension,
				 Srb);
	break;

      case SCSIOP_READ_CAPACITY:
	SrbStatus = AtapiReadCapacity(DeviceExtension,
				      Srb);
	break;

      case SCSIOP_READ:
      case SCSIOP_WRITE:
	SrbStatus = AtapiReadWrite(DeviceExtension,
				   Srb);
	break;

      case SCSIOP_MODE_SENSE:
      case SCSIOP_TEST_UNIT_READY:
      case SCSIOP_VERIFY:
      case SCSIOP_START_STOP_UNIT:
      case SCSIOP_REQUEST_SENSE:
	break;

     default:
	DbgPrint("AtapiSendIdeCommand():unknown command %x\n",
		 Srb->Cdb[0]);
	SrbStatus = SRB_STATUS_INVALID_REQUEST;
	break;
    }

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

  return(SrbStatus);
}


static ULONG
AtapiInquiry(PATAPI_MINIPORT_EXTENSION DeviceExtension,
	     PSCSI_REQUEST_BLOCK Srb)
{
  PIDE_DRIVE_IDENTIFY DeviceParams;
  PINQUIRYDATA InquiryData;
  ULONG i;

  DPRINT("SCSIOP_INQUIRY: DeviceExtension %p  TargetId: %lu\n",
	 DeviceExtension, Srb->TargetId);

  if (Srb->PathId != 0)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_PATH_ID;
      return(SRB_STATUS_INVALID_PATH_ID);
    }

  if (Srb->TargetId > 1)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_TARGET_ID;
      return(SRB_STATUS_INVALID_TARGET_ID);
    }

  if (Srb->Lun != 0)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_LUN;
      return(SRB_STATUS_INVALID_LUN);
    }

  if (DeviceExtension->DevicePresent[Srb->TargetId] == FALSE)
    {
      Srb->SrbStatus = SRB_STATUS_NO_DEVICE;
      return(SRB_STATUS_NO_DEVICE);
    }

  InquiryData = Srb->DataBuffer;
  DeviceParams = &DeviceExtension->DeviceParams[Srb->TargetId];

  /* clear buffer */
  for (i = 0; i < Srb->DataTransferLength; i++)
    {
      ((PUCHAR)Srb->DataBuffer)[i] = 0;
    }

  /* set device class */
  if (DeviceExtension->DeviceAtapi[Srb->TargetId] == FALSE)
    {
      /* hard-disk */
      InquiryData->DeviceType = DIRECT_ACCESS_DEVICE;
    }
  else
    {
      /* get it from the ATAPI configuration word */
      InquiryData->DeviceType = (DeviceParams->ConfigBits >> 8) & 0x1F;
      DPRINT("Device class: %u\n", InquiryData->DeviceType);
    }

  DPRINT("ConfigBits: 0x%x\n", DeviceParams->ConfigBits);
  if (DeviceParams->ConfigBits & 0x80)
    {
      DPRINT("Removable media!\n");
      InquiryData->RemovableMedia = 1;
    }

  for (i = 0; i < 20; i += 2)
    {
      InquiryData->VendorId[i] =
	((PUCHAR)DeviceParams->ModelNumber)[i];
      InquiryData->VendorId[i+1] =
	((PUCHAR)DeviceParams->ModelNumber)[i+1];
    }

  for (i = 0; i < 4; i++)
    {
      InquiryData->ProductId[12+i] = ' ';
    }

  for (i = 0; i < 4; i += 2)
    {
      InquiryData->ProductRevisionLevel[i] =
	((PUCHAR)DeviceParams->FirmwareRev)[i];
      InquiryData->ProductRevisionLevel[i+1] =
	((PUCHAR)DeviceParams->FirmwareRev)[i+1];
    }

  DPRINT("VendorId: '%.20s'\n", InquiryData->VendorId);

  Srb->SrbStatus = SRB_STATUS_SUCCESS;
  return(SRB_STATUS_SUCCESS);
}


static ULONG
AtapiReadCapacity(PATAPI_MINIPORT_EXTENSION DeviceExtension,
		  PSCSI_REQUEST_BLOCK Srb)
{
  PREAD_CAPACITY_DATA CapacityData;
  PIDE_DRIVE_IDENTIFY DeviceParams;
  ULONG LastSector;

  DPRINT("SCSIOP_READ_CAPACITY: TargetId: %lu\n", Srb->TargetId);

  if (Srb->PathId != 0)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_PATH_ID;
      return(SRB_STATUS_INVALID_PATH_ID);
    }

  if (Srb->TargetId > 1)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_TARGET_ID;
      return(SRB_STATUS_INVALID_TARGET_ID);
    }

  if (Srb->Lun != 0)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_LUN;
      return(SRB_STATUS_INVALID_LUN);
    }

  if (DeviceExtension->DevicePresent[Srb->TargetId] == FALSE)
    {
      Srb->SrbStatus = SRB_STATUS_NO_DEVICE;
      return(SRB_STATUS_NO_DEVICE);
    }

  CapacityData = (PREAD_CAPACITY_DATA)Srb->DataBuffer;
  DeviceParams = &DeviceExtension->DeviceParams[Srb->TargetId];

  /* Set sector (block) size to 512 bytes (big-endian). */
  CapacityData->BytesPerBlock = 0x20000;

  /* Calculate last sector (big-endian). */
  if (DeviceParams->Capabilities & IDE_DRID_LBA_SUPPORTED)
    {
      LastSector = (ULONG)((DeviceParams->TMSectorCountHi << 16) +
			    DeviceParams->TMSectorCountLo) - 1;
    }
  else
    {
      LastSector = (ULONG)(DeviceParams->LogicalCyls *
			   DeviceParams->LogicalHeads *
			   DeviceParams->SectorsPerTrack)-1;
    }

  CapacityData->LogicalBlockAddress = (((PUCHAR)&LastSector)[0] << 24) |
				      (((PUCHAR)&LastSector)[1] << 16) |
				      (((PUCHAR)&LastSector)[2] << 8) |
				      ((PUCHAR)&LastSector)[3];

  DPRINT("LastCount: %lu (%08lx / %08lx)\n",
	 LastSector,
	 LastSector,
	 CapacityData->LogicalBlockAddress);

  Srb->SrbStatus = SRB_STATUS_SUCCESS;
  return(SRB_STATUS_SUCCESS);
}


static ULONG
AtapiReadWrite(PATAPI_MINIPORT_EXTENSION DeviceExtension,
	       PSCSI_REQUEST_BLOCK Srb)
{
  PIDE_DRIVE_IDENTIFY DeviceParams;
  ULONG StartingSector;
  ULONG SectorCount;
  UCHAR CylinderHigh;
  UCHAR CylinderLow;
  UCHAR DrvHead;
  UCHAR SectorNumber;
  UCHAR Command;
  ULONG Retries;
  UCHAR Status;

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

  if (Srb->PathId != 0)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_PATH_ID;
      return(SRB_STATUS_INVALID_PATH_ID);
    }

  if (Srb->TargetId > 1)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_TARGET_ID;
      return(SRB_STATUS_INVALID_TARGET_ID);
    }

  if (Srb->Lun != 0)
    {
      Srb->SrbStatus = SRB_STATUS_INVALID_LUN;
      return(SRB_STATUS_INVALID_LUN);
    }

  if (DeviceExtension->DevicePresent[Srb->TargetId] == FALSE)
    {
      Srb->SrbStatus = SRB_STATUS_NO_DEVICE;
      return(SRB_STATUS_NO_DEVICE);
    }

  DPRINT("SCSIOP_WRITE: TargetId: %lu\n",
	 Srb->TargetId);

  DeviceParams = &DeviceExtension->DeviceParams[Srb->TargetId];

  /* Get starting sector number from CDB. */
  StartingSector = ((PCDB)Srb->Cdb)->CDB10.LogicalBlockByte3 |
		   ((PCDB)Srb->Cdb)->CDB10.LogicalBlockByte2 << 8 |
		   ((PCDB)Srb->Cdb)->CDB10.LogicalBlockByte1 << 16 |
		   ((PCDB)Srb->Cdb)->CDB10.LogicalBlockByte0 << 24;

  SectorCount = (Srb->DataTransferLength + DeviceParams->BytesPerSector - 1) /
		DeviceParams->BytesPerSector;

  DPRINT("Starting sector %lu  Number of bytes %lu  Sectors %lu\n",
	 StartingSector,
	 Srb->DataTransferLength,
	 SectorCount);

  if (DeviceParams->Capabilities & IDE_DRID_LBA_SUPPORTED)
    {
      SectorNumber = StartingSector & 0xff;
      CylinderLow = (StartingSector >> 8) & 0xff;
      CylinderHigh = (StartingSector >> 16) & 0xff;
      DrvHead = ((StartingSector >> 24) & 0x0f) | 
          (Srb->TargetId ? IDE_DH_DRV1 : 0) |
          IDE_DH_LBA;
    }
  else
    {
      SectorNumber = (StartingSector % DeviceParams->SectorsPerTrack) + 1;
      StartingSector /= DeviceParams->SectorsPerTrack;
      DrvHead = (StartingSector % DeviceParams->LogicalHeads) | 
          (Srb->TargetId ? IDE_DH_DRV1 : 0);
      StartingSector /= DeviceParams->LogicalHeads;
      CylinderLow = StartingSector & 0xff;
      CylinderHigh = StartingSector >> 8;
    }


  if (Srb->SrbFlags & SRB_FLAGS_DATA_IN)
    {
      Command = IDE_CMD_READ;
    }
  else
    {
      Command = IDE_CMD_WRITE;
    }

  if (DrvHead & IDE_DH_LBA)
    {
      DPRINT("%s:BUS=%04x:DRV=%d:LBA=1:BLK=%08d:SC=%02x:CM=%02x\n",
	     (Srb->SrbFlags & SRB_FLAGS_DATA_IN) ? "READ" : "WRITE",
	     DeviceExtension->CommandPortBase,
	     DrvHead & IDE_DH_DRV1 ? 1 : 0,
	     ((DrvHead & 0x0f) << 24) +
	       (CylinderHigh << 16) + (CylinderLow << 8) + SectorNumber,
	     SectorCount,
	     Command);
    }
  else
    {
      DPRINT("%s:BUS=%04x:DRV=%d:LBA=0:CH=%02x:CL=%02x:HD=%01x:SN=%02x:SC=%02x:CM=%02x\n",
             (Srb->SrbFlags & SRB_FLAGS_DATA_IN) ? "READ" : "WRITE",
             DeviceExtension->CommandPortBase,
             DrvHead & IDE_DH_DRV1 ? 1 : 0, 
             CylinderHigh,
             CylinderLow,
             DrvHead & 0x0f,
             SectorNumber,
             SectorCount,
             Command);
    }

  /* Set pointer to data buffer. */
  DeviceExtension->DataBuffer = (PUSHORT)Srb->DataBuffer;

  DeviceExtension->CurrentSrb = Srb;
  DeviceExtension->ExpectingInterrupt = TRUE;



  /*  wait for BUSY to clear  */
  for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES; Retries++)
    {
      Status = IDEReadStatus(DeviceExtension->CommandPortBase);
      if (!(Status & IDE_SR_BUSY))
        {
          break;
        }
      ScsiPortStallExecution(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");
      return(SRB_STATUS_BUSY);
#if 0
      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;
        }
#endif
    }

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

  ScsiPortStallExecution(10);
#if 0
  /*  wait for BUSY to clear and DRDY to assert */
  for (Retries = 0; Retries < IDE_MAX_BUSY_RETRIES; Retries++)
    {
      Status = IDEReadStatus(DeviceExtension->CommandPortBase);
      if (!(Status & IDE_SR_BUSY) && (Status & IDE_SR_DRDY))
	{
	  break;
	}
      ScsiPortStallExecution(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");
      return(SRB_STATUS_BUSY);
#if 0
      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;
        }
#endif
    }
#endif

  /* Indicate expecting an interrupt. */
  DeviceExtension->ExpectingInterrupt = TRUE;

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

  /* Issue command to drive */
  IDEWriteCommand(DeviceExtension->CommandPortBase, Command);

  /* Write data block */
  if (Command == IDE_CMD_WRITE)
    {
      PUCHAR TargetAddress;
      ULONG SectorSize;

      /* Wait for controller ready */
      for (Retries = 0; Retries < IDE_MAX_WRITE_RETRIES; Retries++)
	{
	  BYTE  Status = IDEReadStatus(DeviceExtension->CommandPortBase);
	  if (!(Status & IDE_SR_BUSY) || (Status & IDE_SR_ERR))
	    {
	      break;
	    }
	  KeStallExecutionProcessor(10);
	}
      if (Retries >= IDE_MAX_BUSY_RETRIES)
	{
	  DPRINT("Drive is BUSY for too long after sending write command\n");
	  return(SRB_STATUS_BUSY);
#if 0
          if (DeviceExtension->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;
            }
#endif
	}

      /* Update SRB data */
      SectorSize = DeviceExtension->DeviceParams[Srb->TargetId].BytesPerSector;
      TargetAddress = Srb->DataBuffer;
      Srb->DataBuffer += SectorSize;
      Srb->DataTransferLength -= SectorSize;

      /* Write data block */
      IDEWriteBlock(DeviceExtension->CommandPortBase,
		    TargetAddress,
		    SectorSize);
    }


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

  /* Wait for interrupt. */
  return(SRB_STATUS_PENDING);
}


static UCHAR
AtapiErrorToScsi(PVOID DeviceExtension,
		 PSCSI_REQUEST_BLOCK Srb)
{
  PATAPI_MINIPORT_EXTENSION DevExt;
  ULONG CommandPortBase;
  ULONG ControlPortBase;
  UCHAR ErrorReg;
  UCHAR ScsiStatus;
  UCHAR SrbStatus;

  DPRINT("AtapiErrorToScsi() called\n");

  DevExt = (PATAPI_MINIPORT_EXTENSION)DeviceExtension;

  CommandPortBase = DevExt->CommandPortBase;
  ControlPortBase = DevExt->ControlPortBase;

  ErrorReg = IDEReadError(CommandPortBase);

  if (DevExt->DeviceAtapi[Srb->TargetId])
    {
      switch (ErrorReg >> 4)
	{
	  case SCSI_SENSE_NO_SENSE:
	    DPRINT("ATAPI error: SCSI_SENSE_NO_SENSE\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  case SCSI_SENSE_RECOVERED_ERROR:
	    DPRINT("ATAPI error: SCSI_SENSE_RECOVERED_SENSE\n");
	    ScsiStatus = 0;
	    SrbStatus = SRB_STATUS_SUCCESS;
	    break;

	  case SCSI_SENSE_NOT_READY:
	    DPRINT("ATAPI error: SCSI_SENSE_NOT_READY\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  case SCSI_SENSE_MEDIUM_ERROR:
	    DPRINT("ATAPI error: SCSI_SENSE_MEDIUM_ERROR\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  case SCSI_SENSE_HARDWARE_ERROR:
	    DPRINT("ATAPI error: SCSI_SENSE_HARDWARE_ERROR\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  case SCSI_SENSE_ILLEGAL_REQUEST:
	    DPRINT("ATAPI error: SCSI_SENSE_ILLEGAL_REQUEST\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  case SCSI_SENSE_UNIT_ATTENTION:
	    DPRINT("ATAPI error: SCSI_SENSE_UNIT_ATTENTION\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  case SCSI_SENSE_DATA_PROTECT:
	    DPRINT("ATAPI error: SCSI_SENSE_DATA_PROTECT\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  case SCSI_SENSE_BLANK_CHECK:
	    DPRINT("ATAPI error: SCSI_SENSE_BLANK_CHECK\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  case SCSI_SENSE_ABORTED_COMMAND:
	    DPRINT("ATAPI error: SCSI_SENSE_ABORTED_COMMAND\n");
	    ScsiStatus = SCSISTAT_CHECK_CONDITION;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;

	  default:
	    DPRINT("ATAPI error: Invalid sense key\n");
	    ScsiStatus = 0;
	    SrbStatus = SRB_STATUS_ERROR;
	    break;
	}
    }
  else
    {
      DPRINT1("IDE error: %02x\n", ErrorReg);

      ScsiStatus = 0;
      SrbStatus = SRB_STATUS_ERROR;

#if 0
      UCHAR SectorCount, SectorNum, CylinderLow, CylinderHigh;
      UCHAR DriveHead;

      CylinderLow = IDEReadCylinderLow(CommandPortBase);
      CylinderHigh = IDEReadCylinderHigh(CommandPortBase);
      DriveHead = IDEReadDriveHead(CommandPortBase);
      SectorCount = IDEReadSectorCount(CommandPortBase);
      SectorNum = IDEReadSectorNum(CommandPortBase);

      DPRINT1("IDE Error: ERR:%02x CYLLO:%02x CYLHI:%02x SCNT:%02x SNUM:%02x\n",
	      ErrorReg,
	      CylinderLow,
	      CylinderHigh,
	      SectorCount,
	      SectorNum);
#endif
    }



  Srb->ScsiStatus = ScsiStatus;

  DPRINT("AtapiErrorToScsi() done\n");

  return(SrbStatus);
}

/* EOF */
