[FREELDR] Cache INT13h drive data in pcdisk.c (#2097)
[reactos.git] / boot / freeldr / freeldr / arch / i386 / pcdisk.c
index 6a04730..37058bc 100644 (file)
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+// #if defined(__i386__) || defined(_M_AMD64)
+
 #include <freeldr.h>
-#include <debug.h>
 
+#include <debug.h>
 DBG_DEFAULT_CHANNEL(DISK);
 
+/* Enable this line if you want to support multi-drive caching (increases FreeLdr size!) */
+// #define CACHE_MULTI_DRIVES
+
 #include <pshpack2.h>
+
 typedef struct
 {
-       UCHAR           PacketSize;                             // 00h - Size of packet (10h or 18h)
-       UCHAR           Reserved;                               // 01h - Reserved (0)
-       USHORT          LBABlockCount;                  // 02h - Number of blocks to transfer (max 007Fh for Phoenix EDD)
-       USHORT          TransferBufferOffset;   // 04h - Transfer buffer offset (seg:off)
-       USHORT          TransferBufferSegment;  //       Transfer buffer segment (seg:off)
-       ULONGLONG               LBAStartBlock;                  // 08h - Starting absolute block number
-       //ULONGLONG             TransferBuffer64;               // 10h - (EDD-3.0, optional) 64-bit flat address of transfer buffer
-                                                                       //       used if DWORD at 04h is FFFFh:FFFFh
-                                                                       //       Commented since some earlier BIOSes refuse to work with
-                                                                       //       such extended structure
+    UCHAR       PacketSize;             // 00h - Size of packet (10h or 18h)
+    UCHAR       Reserved;               // 01h - Reserved (0)
+    USHORT      LBABlockCount;          // 02h - Number of blocks to transfer (max 007Fh for Phoenix EDD)
+    USHORT      TransferBufferOffset;   // 04h - Transfer buffer offset (seg:off)
+    USHORT      TransferBufferSegment;  //       Transfer buffer segment (seg:off)
+    ULONGLONG   LBAStartBlock;          // 08h - Starting absolute block number
+//  ULONGLONG   TransferBuffer64;       // 10h - (EDD-3.0, optional) 64-bit flat address of transfer buffer
+                                        //       used if DWORD at 04h is FFFFh:FFFFh
+                                        //       Commented since some earlier BIOSes refuse to work with
+                                        //       such extended structure
 } I386_DISK_ADDRESS_PACKET, *PI386_DISK_ADDRESS_PACKET;
+
+typedef struct
+{
+    UCHAR   PacketSize;     // 00h - Size of packet in bytes (13h)
+    UCHAR   MediaType;      // 01h - Boot media type (see #00282)
+    UCHAR   DriveNumber;    /* 02h - Drive number:
+                             *   00h Floppy image
+                             *   80h Bootable hard disk
+                             *   81h-FFh Nonbootable or no emulation
+                             */
+    UCHAR   Controller;     // 03h - CD-ROM controller number
+    ULONG   LBAImage;       // 04h - Logical Block Address of disk image to emulate
+    USHORT  DeviceSpec;     /* 08h - Device specification (see also #00282)
+                             * (IDE) Bit 0:
+                             *     Drive is slave instead of master
+                             * (SCSI) Bits 7-0:
+                             *     LUN and PUN
+                             * Bits 15-8:
+                             *     Bus number
+                             */
+    USHORT  Buffer;         // 0Ah - Segment of 3K buffer for caching CD-ROM reads
+    USHORT  LoadSeg;        // 0Ch - Load segment for initial boot image.
+                            //       If 0000h, load at segment 07C0h.
+    USHORT  SectorCount;    // 0Eh - Number of 512-byte virtual sectors to load
+                            //       (only valid for AH=4Ch).
+    UCHAR   CHSGeometry[3]; /* 10h - Low byte of cylinder count (for INT 13/AH=08h)
+                             * 11h - Sector count, high bits of cylinder count (for INT 13/AH=08h)
+                             * 12h - Head count (for INT 13/AH=08h)
+                             */
+    UCHAR   Reserved;
+} I386_CDROM_SPEC_PACKET, *PI386_CDROM_SPEC_PACKET;
+
 #include <poppack.h>
 
-/////////////////////////////////////////////////////////////////////////////////////////////
-// FUNCTIONS
-/////////////////////////////////////////////////////////////////////////////////////////////
+typedef struct _PC_DISK_DRIVE
+{
+    /* Disk geometry (legacy BIOS and INT13 extended) */
+    GEOMETRY Geometry;
+    EXTENDED_GEOMETRY ExtGeometry;
+
+    /* TRUE when INT 13h extensions are supported */
+    BOOLEAN Int13ExtensionsSupported;
+
+    /*
+     * 'IsRemovable' flag: TRUE when the drive is removable (e.g. floppy, CD-ROM...).
+     * In that case some of the cached information might need to be refreshed regularly.
+     */
+    BOOLEAN IsRemovable;
+
+#ifdef CACHE_MULTI_DRIVES
+    /*
+     * 'Initialized' flag: if TRUE then the drive has been initialized;
+     * if FALSE then it needs to be initialized; if its high bit is set
+     * then there has been an error; don't try to use it.
+     */
+    BOOLEAN Initialized;
+#endif
+} PC_DISK_DRIVE, *PPC_DISK_DRIVE;
+
+#ifdef CACHE_MULTI_DRIVES
+/* Cache of all possible PC disk drives */
+// Maximum number of disks is 0x100, indexed from 0x00 to 0xFF.
+static PC_DISK_DRIVE PcDiskDrive[0x100];
+#else
+/* Cached data for the last-accessed PC disk drive */
+// We use a USHORT so that we can initialize it with a drive number that cannot exist
+// on the system (they are <= 0xFF), therefore forcing drive caching on first access.
+static USHORT LastDriveNumber = 0xFFFF;
+static PC_DISK_DRIVE PcDiskDrive;
+#endif /* CACHE_MULTI_DRIVES */
+
+/* DISK IO ERROR SUPPORT *****************************************************/
+
+static LONG lReportError = 0; // >= 0: display errors; < 0: hide errors.
+
+LONG DiskReportError(BOOLEAN bShowError)
+{
+    /* Set the reference count */
+    if (bShowError) ++lReportError;
+    else            --lReportError;
+    return lReportError;
+}
 
-static BOOLEAN PcDiskResetController(UCHAR DriveNumber)
+static PCSTR DiskGetErrorCodeString(ULONG ErrorCode)
 {
-       REGS    RegsIn;
-       REGS    RegsOut;
+    switch (ErrorCode)
+    {
+    case 0x00:  return "no error";
+    case 0x01:  return "bad command passed to driver";
+    case 0x02:  return "address mark not found or bad sector";
+    case 0x03:  return "diskette write protect error";
+    case 0x04:  return "sector not found";
+    case 0x05:  return "fixed disk reset failed";
+    case 0x06:  return "diskette changed or removed";
+    case 0x07:  return "bad fixed disk parameter table";
+    case 0x08:  return "DMA overrun";
+    case 0x09:  return "DMA access across 64k boundary";
+    case 0x0A:  return "bad fixed disk sector flag";
+    case 0x0B:  return "bad fixed disk cylinder";
+    case 0x0C:  return "unsupported track/invalid media";
+    case 0x0D:  return "invalid number of sectors on fixed disk format";
+    case 0x0E:  return "fixed disk controlled data address mark detected";
+    case 0x0F:  return "fixed disk DMA arbitration level out of range";
+    case 0x10:  return "ECC/CRC error on disk read";
+    case 0x11:  return "recoverable fixed disk data error, data fixed by ECC";
+    case 0x20:  return "controller error (NEC for floppies)";
+    case 0x40:  return "seek failure";
+    case 0x80:  return "time out, drive not ready";
+    case 0xAA:  return "fixed disk drive not ready";
+    case 0xBB:  return "fixed disk undefined error";
+    case 0xCC:  return "fixed disk write fault on selected drive";
+    case 0xE0:  return "fixed disk status error/Error reg = 0";
+    case 0xFF:  return "sense operation failed";
+
+    default:    return "unknown error code";
+    }
+}
+
+static VOID DiskError(PCSTR ErrorString, ULONG ErrorCode)
+{
+    CHAR ErrorCodeString[200];
 
-       WARN("PcDiskResetController(0x%x) DISK OPERATION FAILED -- RESETTING CONTROLLER\n", DriveNumber);
+    if (lReportError < 0)
+        return;
 
-       // BIOS Int 13h, function 0 - Reset disk system
-       // AH = 00h
-       // DL = drive (if bit 7 is set both hard disks and floppy disks reset)
-       // Return:
-       // AH = status
-       // CF clear if successful
-       // CF set on error
-       RegsIn.b.ah = 0x00;
-       RegsIn.b.dl = DriveNumber;
+    sprintf(ErrorCodeString, "%s\n\nError Code: 0x%lx\nError: %s",
+            ErrorString, ErrorCode, DiskGetErrorCodeString(ErrorCode));
 
-       // Reset the disk controller
-       Int386(0x13, &RegsIn, &RegsOut);
+    ERR("%s\n", ErrorCodeString);
 
-       return INT386_SUCCESS(RegsOut);
+    UiMessageBox(ErrorCodeString);
 }
 
-static BOOLEAN PcDiskReadLogicalSectorsLBA(UCHAR DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
+/* FUNCTIONS *****************************************************************/
+
+BOOLEAN DiskResetController(UCHAR DriveNumber)
 {
-       REGS                                            RegsIn;
-       REGS                                            RegsOut;
-       ULONG                                                   RetryCount;
-       PI386_DISK_ADDRESS_PACKET       Packet = (PI386_DISK_ADDRESS_PACKET)(BIOSCALLBUFFER);
+    REGS RegsIn, RegsOut;
+
+    WARN("DiskResetController(0x%x) DISK OPERATION FAILED -- RESETTING CONTROLLER\n", DriveNumber);
+
+    /*
+     * BIOS Int 13h, function 0 - Reset disk system
+     * AH = 00h
+     * DL = drive (if bit 7 is set both hard disks and floppy disks reset)
+     * Return:
+     * AH = status
+     * CF clear if successful
+     * CF set on error
+     */
+    RegsIn.b.ah = 0x00;
+    RegsIn.b.dl = DriveNumber;
+
+    /* Reset the disk controller */
+    Int386(0x13, &RegsIn, &RegsOut);
+
+    return INT386_SUCCESS(RegsOut);
+}
 
-       TRACE("PcDiskReadLogicalSectorsLBA() DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d Buffer: 0x%x\n", DriveNumber, SectorNumber, SectorCount, Buffer);
-    ASSERT(((ULONG_PTR)Buffer) <= 0xFFFFF);
+static BOOLEAN
+DiskIsDriveRemovable(UCHAR DriveNumber)
+{
+    /*
+     * Hard disks use drive numbers >= 0x80 . So if the drive number
+     * indicates a hard disk then return FALSE.
+     * 0x49 is our magic ramdisk drive, so return FALSE for that too.
+     */
+    if ((DriveNumber >= 0x80) || (DriveNumber == 0x49))
+        return FALSE;
+
+    /* The drive is a floppy diskette so return TRUE */
+    return TRUE;
+}
 
-       // BIOS int 0x13, function 42h - IBM/MS INT 13 Extensions - EXTENDED READ
-       RegsIn.b.ah = 0x42;                                     // Subfunction 42h
-       RegsIn.b.dl = DriveNumber;                      // Drive number in DL (0 - floppy, 0x80 - harddisk)
-       RegsIn.x.ds = BIOSCALLBUFSEGMENT;       // DS:SI -> disk address packet
-       RegsIn.w.si = BIOSCALLBUFOFFSET;
-
-       // Setup disk address packet
-       RtlZeroMemory(Packet, sizeof(I386_DISK_ADDRESS_PACKET));
-       Packet->PacketSize = sizeof(I386_DISK_ADDRESS_PACKET);
-       Packet->Reserved = 0;
-       Packet->LBABlockCount = (USHORT)SectorCount;
-       ASSERT(Packet->LBABlockCount == SectorCount);
-       Packet->TransferBufferOffset = ((ULONG_PTR)Buffer) & 0x0F;
-       Packet->TransferBufferSegment = (USHORT)(((ULONG_PTR)Buffer) >> 4);
-       Packet->LBAStartBlock = SectorNumber;
-
-       // BIOS int 0x13, function 42h - IBM/MS INT 13 Extensions - EXTENDED READ
-       // Return:
-       // CF clear if successful
-       // AH = 00h
-       // CF set on error
-       // AH = error code
-       // disk address packet's block count field set to the
-       // number of blocks successfully transferred
-
-       // Retry 3 times
-       for (RetryCount=0; RetryCount<3; RetryCount++)
-       {
-               Int386(0x13, &RegsIn, &RegsOut);
-
-               // If it worked return TRUE
-               if (INT386_SUCCESS(RegsOut))
-               {
-                       return TRUE;
-               }
-               // If it was a corrected ECC error then the data is still good
-               else if (RegsOut.b.ah == 0x11)
-               {
-                       return TRUE;
-               }
-               // If it failed the do the next retry
-               else
-               {
-                       PcDiskResetController(DriveNumber);
-
-                       continue;
-               }
-       }
-
-       // If we get here then the read failed
-       ERR("Disk Read Failed in LBA mode: %x (DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d)\n", RegsOut.b.ah, DriveNumber, SectorNumber, SectorCount);
-
-       return FALSE;
+static BOOLEAN
+DiskInt13ExtensionsSupported(IN UCHAR DriveNumber)
+{
+    REGS RegsIn, RegsOut;
+
+    /*
+     * Some BIOSes report that extended disk access functions are not supported
+     * when booting from a CD (e.g. Phoenix BIOS v6.00PG and Insyde BIOS shipping
+     * with Intel Macs). Therefore we just return TRUE if we're booting from a CD
+     * - we can assume that all El Torito capable BIOSes support INT 13 extensions.
+     * We simply detect whether we're booting from CD by checking whether the drive
+     * number is >= 0x8A. It's 0x90 on the Insyde BIOS, and 0x9F on most other BIOSes.
+     */
+    if (DriveNumber >= 0x8A)
+        return TRUE;
+
+    /*
+     * IBM/MS INT 13 Extensions - INSTALLATION CHECK
+     * AH = 41h
+     * BX = 55AAh
+     * DL = drive (80h-FFh)
+     * Return:
+     * CF set on error (extensions not supported)
+     * AH = 01h (invalid function)
+     * CF clear if successful
+     * BX = AA55h if installed
+     * AH = major version of extensions
+     * 01h = 1.x
+     * 20h = 2.0 / EDD-1.0
+     * 21h = 2.1 / EDD-1.1
+     * 30h = EDD-3.0
+     * AL = internal use
+     * CX = API subset support bitmap
+     * DH = extension version (v2.0+ ??? -- not present in 1.x)
+     *
+     * Bitfields for IBM/MS INT 13 Extensions API support bitmap
+     * Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
+     * Bit 1, removable drive controller functions (AH=45h,46h,48h,49h,INT 15/AH=52h) supported
+     * Bit 2, enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported
+     *        extended drive parameter table is valid
+     * Bits 3-15 reserved
+     */
+    RegsIn.b.ah = 0x41;
+    RegsIn.w.bx = 0x55AA;
+    RegsIn.b.dl = DriveNumber;
+
+    /* Reset the disk controller */
+    Int386(0x13, &RegsIn, &RegsOut);
+    if (!INT386_SUCCESS(RegsOut))
+    {
+        /* CF set on error (extensions not supported) */
+        return FALSE;
+    }
+
+    if (RegsOut.w.bx != 0xAA55)
+    {
+        /* BX = AA55h if installed */
+        return FALSE;
+    }
+
+    if (!(RegsOut.w.cx & 0x0001))
+    {
+        /*
+         * CX = API subset support bitmap.
+         * Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported.
+         */
+        WARN("Suspicious API subset support bitmap 0x%x on device 0x%lx\n",
+             RegsOut.w.cx, DriveNumber);
+        return FALSE;
+    }
+
+    return TRUE;
 }
 
-static BOOLEAN PcDiskReadLogicalSectorsCHS(UCHAR DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
+static BOOLEAN
+DiskGetExtendedDriveParameters(
+    IN UCHAR DriveNumber,
+    IN PPC_DISK_DRIVE DiskDrive,
+    OUT PVOID Buffer,
+    IN USHORT BufferSize)
 {
-       UCHAR                   PhysicalSector;
-       UCHAR                   PhysicalHead;
-       ULONG                   PhysicalTrack;
-       GEOMETRY        DriveGeometry;
-       ULONG                   NumberOfSectorsToRead;
-       REGS            RegsIn;
-       REGS            RegsOut;
-       ULONG                   RetryCount;
-
-       TRACE("PcDiskReadLogicalSectorsCHS()\n");
-
-       //
-       // Get the drive geometry
-       //
-       if (!MachDiskGetDriveGeometry(DriveNumber, &DriveGeometry) ||
-           DriveGeometry.Sectors == 0 ||
-           DriveGeometry.Heads == 0)
-       {
-               return FALSE;
-       }
-
-       while (SectorCount)
-       {
-
-               //
-               // Calculate the physical disk offsets
-               // Note: DriveGeometry.Sectors < 64
-               //
-               PhysicalSector = 1 + (UCHAR)(SectorNumber % DriveGeometry.Sectors);
-               PhysicalHead = (UCHAR)((SectorNumber / DriveGeometry.Sectors) % DriveGeometry.Heads);
-               PhysicalTrack = (ULONG)((SectorNumber / DriveGeometry.Sectors) / DriveGeometry.Heads);
-
-               //
-               // Calculate how many sectors we need to read this round
-               //
-               if (PhysicalSector > 1)
-               {
-                       if (SectorCount >= (DriveGeometry.Sectors - (PhysicalSector - 1)))
-                               NumberOfSectorsToRead = (DriveGeometry.Sectors - (PhysicalSector - 1));
-                       else
-                               NumberOfSectorsToRead = SectorCount;
-               }
-               else
-               {
-                       if (SectorCount >= DriveGeometry.Sectors)
-                               NumberOfSectorsToRead = DriveGeometry.Sectors;
-                       else
-                               NumberOfSectorsToRead = SectorCount;
-               }
-
-               //
-               // Make sure the read is within the geometry boundaries
-               //
-               if ((PhysicalHead >= DriveGeometry.Heads) ||
-                       (PhysicalTrack >= DriveGeometry.Cylinders) ||
-                       ((NumberOfSectorsToRead + PhysicalSector) > (DriveGeometry.Sectors + 1)) ||
-                       (PhysicalSector > DriveGeometry.Sectors))
-               {
-                       DiskError("Disk read exceeds drive geometry limits.", 0);
-                       return FALSE;
-               }
-
-               // BIOS Int 13h, function 2 - Read Disk Sectors
-               // AH = 02h
-               // AL = number of sectors to read (must be nonzero)
-               // CH = low eight bits of cylinder number
-               // CL = sector number 1-63 (bits 0-5)
-               //      high two bits of cylinder (bits 6-7, hard disk only)
-               // DH = head number
-               // DL = drive number (bit 7 set for hard disk)
-               // ES:BX -> data buffer
-               // Return:
-               // CF set on error
-               // if AH = 11h (corrected ECC error), AL = burst length
-               // CF clear if successful
-               // AH = status
-               // AL = number of sectors transferred
-               //  (only valid if CF set for some BIOSes)
-               RegsIn.b.ah = 0x02;
-               RegsIn.b.al = (UCHAR)NumberOfSectorsToRead;
-               RegsIn.b.ch = (PhysicalTrack & 0xFF);
-               RegsIn.b.cl = (UCHAR)(PhysicalSector + ((PhysicalTrack & 0x300) >> 2));
-               RegsIn.b.dh = PhysicalHead;
-               RegsIn.b.dl = DriveNumber;
-               RegsIn.w.es = (USHORT)(((ULONG_PTR)Buffer) >> 4);
-               RegsIn.w.bx = ((ULONG_PTR)Buffer) & 0x0F;
-
-               //
-               // Perform the read
-               // Retry 3 times
-               //
-               for (RetryCount=0; RetryCount<3; RetryCount++)
-               {
-                       Int386(0x13, &RegsIn, &RegsOut);
-
-                       // If it worked break out
-                       if (INT386_SUCCESS(RegsOut))
-                       {
-                               break;
-                       }
-                       // If it was a corrected ECC error then the data is still good
-                       else if (RegsOut.b.ah == 0x11)
-                       {
-                               break;
-                       }
-                       // If it failed the do the next retry
-                       else
-                       {
-                               PcDiskResetController(DriveNumber);
-
-                               continue;
-                       }
-               }
-
-               // If we retried 3 times then fail
-               if (RetryCount >= 3)
-               {
-                       ERR("Disk Read Failed in CHS mode, after retrying 3 times: %x\n", RegsOut.b.ah);
-                       return FALSE;
-               }
-
-               // I have learned that not all bioses return
-               // the sector read count in the AL register (at least mine doesn't)
-               // even if the sectors were read correctly. So instead
-               // of checking the sector read count we will rely solely
-               // on the carry flag being set on error
-
-               Buffer = (PVOID)((ULONG_PTR)Buffer + (NumberOfSectorsToRead * DriveGeometry.BytesPerSector));
-               SectorCount -= NumberOfSectorsToRead;
-               SectorNumber += NumberOfSectorsToRead;
-       }
-
-       return TRUE;
+    REGS RegsIn, RegsOut;
+    PUSHORT Ptr = (PUSHORT)(BIOSCALLBUFFER);
+
+    TRACE("DiskGetExtendedDriveParameters(0x%x)\n", DriveNumber);
+
+    if (!DiskDrive->Int13ExtensionsSupported)
+        return FALSE;
+
+    /* Initialize transfer buffer */
+    *Ptr = BufferSize;
+
+    /*
+     * BIOS Int 13h, function 48h - Get drive parameters
+     * AH = 48h
+     * DL = drive (bit 7 set for hard disk)
+     * DS:SI = result buffer
+     * Return:
+     * CF set on error
+     * AH = status (07h)
+     * CF clear if successful
+     * AH = 00h
+     * DS:SI -> result buffer
+     */
+    RegsIn.b.ah = 0x48;
+    RegsIn.b.dl = DriveNumber;
+    RegsIn.x.ds = BIOSCALLBUFSEGMENT;   // DS:SI -> result buffer
+    RegsIn.w.si = BIOSCALLBUFOFFSET;
+
+    /* Get drive parameters */
+    Int386(0x13, &RegsIn, &RegsOut);
+    if (!INT386_SUCCESS(RegsOut))
+        return FALSE;
+
+    RtlCopyMemory(Buffer, Ptr, BufferSize);
+
+#if DBG
+    TRACE("size of buffer:                          %x\n", Ptr[0]);
+    TRACE("information flags:                       %x\n", Ptr[1]);
+    TRACE("number of physical cylinders on drive:   %u\n", *(PULONG)&Ptr[2]);
+    TRACE("number of physical heads on drive:       %u\n", *(PULONG)&Ptr[4]);
+    TRACE("number of physical sectors per track:    %u\n", *(PULONG)&Ptr[6]);
+    TRACE("total number of sectors on drive:        %I64u\n", *(unsigned long long*)&Ptr[8]);
+    TRACE("bytes per sector:                        %u\n", Ptr[12]);
+    if (Ptr[0] >= 0x1e)
+    {
+        TRACE("EED configuration parameters:            %x:%x\n", Ptr[13], Ptr[14]);
+        if (Ptr[13] != 0xffff && Ptr[14] != 0xffff)
+        {
+            PUCHAR SpecPtr = (PUCHAR)(ULONG_PTR)((Ptr[13] << 4) + Ptr[14]);
+            TRACE("SpecPtr:                                 %x\n", SpecPtr);
+            TRACE("physical I/O port base address:          %x\n", *(PUSHORT)&SpecPtr[0]);
+            TRACE("disk-drive control port address:         %x\n", *(PUSHORT)&SpecPtr[2]);
+            TRACE("drive flags:                             %x\n", SpecPtr[4]);
+            TRACE("proprietary information:                 %x\n", SpecPtr[5]);
+            TRACE("IRQ for drive:                           %u\n", SpecPtr[6]);
+            TRACE("sector count for multi-sector transfers: %u\n", SpecPtr[7]);
+            TRACE("DMA control:                             %x\n", SpecPtr[8]);
+            TRACE("programmed I/O control:                  %x\n", SpecPtr[9]);
+            TRACE("drive options:                           %x\n", *(PUSHORT)&SpecPtr[10]);
+        }
+    }
+    if (Ptr[0] >= 0x42)
+    {
+        TRACE("signature:                             %x\n", Ptr[15]);
+    }
+#endif
+
+    return TRUE;
 }
 
-BOOLEAN PcDiskReadLogicalSectors(UCHAR DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
+static BOOLEAN
+InitDriveGeometry(
+    IN UCHAR DriveNumber,
+    IN PPC_DISK_DRIVE DiskDrive)
 {
-       BOOLEAN ExtensionsSupported;
-
-       TRACE("PcDiskReadLogicalSectors() DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d Buffer: 0x%x\n", DriveNumber, SectorNumber, SectorCount, Buffer);
-
-       //
-       // Check to see if it is a fixed disk drive
-       // If so then check to see if Int13 extensions work
-       // If they do then use them, otherwise default back to BIOS calls
-       //
-       ExtensionsSupported = DiskInt13ExtensionsSupported(DriveNumber);
-
-       if ((DriveNumber >= 0x80) && ExtensionsSupported)
-       {
-               TRACE("Using Int 13 Extensions for read. DiskInt13ExtensionsSupported(%d) = %s\n", DriveNumber, ExtensionsSupported ? "TRUE" : "FALSE");
-
-               //
-               // LBA is easy, nothing to calculate
-               // Just do the read
-               //
-               return PcDiskReadLogicalSectorsLBA(DriveNumber, SectorNumber, SectorCount, Buffer);
-       }
-       else
-       {
-               // LBA is not supported default to the CHS calls
-               return PcDiskReadLogicalSectorsCHS(DriveNumber, SectorNumber, SectorCount, Buffer);
-       }
-
-       return TRUE;
+    BOOLEAN Success;
+    REGS RegsIn, RegsOut;
+    ULONG Cylinders;
+
+    /* Get the extended geometry first */
+    DiskDrive->ExtGeometry.Size = sizeof(DiskDrive->ExtGeometry);
+    Success = DiskGetExtendedDriveParameters(DriveNumber, DiskDrive,
+                                             &DiskDrive->ExtGeometry,
+                                             DiskDrive->ExtGeometry.Size);
+    if (!Success)
+    {
+        /* Failed, zero it out */
+        RtlZeroMemory(&DiskDrive->ExtGeometry, sizeof(DiskDrive->ExtGeometry));
+    }
+    else
+    {
+        TRACE("DiskGetExtendedDriveParameters(0x%x) returned:\n"
+              "Cylinders  : 0x%x\n"
+              "Heads      : 0x%x\n"
+              "Sects/Track: 0x%x\n"
+              "Bytes/Sect : 0x%x\n",
+              DriveNumber,
+              DiskDrive->ExtGeometry.Cylinders,
+              DiskDrive->ExtGeometry.Heads,
+              DiskDrive->ExtGeometry.SectorsPerTrack,
+              DiskDrive->ExtGeometry.BytesPerSector);
+    }
+
+    /* Now try the legacy geometry */
+    RtlZeroMemory(&DiskDrive->Geometry, sizeof(DiskDrive->Geometry));
+
+    /*
+     * BIOS Int 13h, function 08h - Get drive parameters
+     * AH = 08h
+     * DL = drive (bit 7 set for hard disk)
+     * ES:DI = 0000h:0000h to guard against BIOS bugs
+     * Return:
+     * CF set on error
+     * AH = status (07h)
+     * CF clear if successful
+     * AH = 00h
+     * AL = 00h on at least some BIOSes
+     * BL = drive type (AT/PS2 floppies only)
+     * CH = low eight bits of maximum cylinder number
+     * CL = maximum sector number (bits 5-0)
+     *      high two bits of maximum cylinder number (bits 7-6)
+     * DH = maximum head number
+     * DL = number of drives
+     * ES:DI -> drive parameter table (floppies only)
+     */
+    RegsIn.b.ah = 0x08;
+    RegsIn.b.dl = DriveNumber;
+    RegsIn.w.es = 0x0000;
+    RegsIn.w.di = 0x0000;
+
+    /* Get drive parameters */
+    Int386(0x13, &RegsIn, &RegsOut);
+    if (!INT386_SUCCESS(RegsOut))
+    {
+        /* We failed, return the result of the previous call (extended geometry) */
+        return Success;
+    }
+    /* OR it with the old result, so that we return TRUE whenever either call succeeded */
+    Success |= TRUE;
+
+    Cylinders = (RegsOut.b.cl & 0xC0) << 2;
+    Cylinders += RegsOut.b.ch;
+    Cylinders++;
+    DiskDrive->Geometry.Cylinders = Cylinders;
+    DiskDrive->Geometry.Heads = RegsOut.b.dh + 1;
+    DiskDrive->Geometry.Sectors = RegsOut.b.cl & 0x3F;
+    DiskDrive->Geometry.BytesPerSector = 512;     /* Just assume 512 bytes per sector */
+
+    TRACE("Regular Int13h(0x%x) returned:\n"
+          "Cylinders  : 0x%x\n"
+          "Heads      : 0x%x\n"
+          "Sects/Track: 0x%x (original 0x%x)\n"
+          "Bytes/Sect : 0x%x\n",
+          DriveNumber,
+          DiskDrive->Geometry.Cylinders,
+          DiskDrive->Geometry.Heads,
+          DiskDrive->Geometry.Sectors, RegsOut.b.cl,
+          DiskDrive->Geometry.BytesPerSector);
+
+    return Success;
 }
 
-BOOLEAN
-PcDiskGetDriveGeometry(UCHAR DriveNumber, PGEOMETRY Geometry)
+static BOOLEAN
+PcDiskDriveInit(
+    IN UCHAR DriveNumber,
+    IN OUT PPC_DISK_DRIVE DiskDrive)
 {
-  EXTENDED_GEOMETRY ExtGeometry;
-  REGS RegsIn;
-  REGS RegsOut;
-  ULONG Cylinders;
-
-  TRACE("DiskGetDriveGeometry()\n");
-
-  /* Try to get the extended geometry first */
-  ExtGeometry.Size = sizeof(EXTENDED_GEOMETRY);
-  if (DiskGetExtendedDriveParameters(DriveNumber, &ExtGeometry, ExtGeometry.Size))
-  {
-    Geometry->Cylinders = ExtGeometry.Cylinders;
-    Geometry->Heads = ExtGeometry.Heads;
-    Geometry->Sectors = ExtGeometry.SectorsPerTrack;
-    Geometry->BytesPerSector = ExtGeometry.BytesPerSector;
+    DiskDrive->IsRemovable = DiskIsDriveRemovable(DriveNumber);
+
+    /*
+     * Check to see if it is a fixed disk drive.
+     * If so then check to see if INT 13h extensions work.
+     * If they do then use them, otherwise default back to BIOS calls.
+     */
+    DiskDrive->Int13ExtensionsSupported = DiskInt13ExtensionsSupported(DriveNumber);
+
+    if (!InitDriveGeometry(DriveNumber, DiskDrive))
+        return FALSE;
+
+    TRACE("\n"
+          "DriveNumber: 0x%x\n"
+          "IsRemovable              = %s\n"
+          "Int13ExtensionsSupported = %s\n",
+          DriveNumber,
+          DiskDrive->IsRemovable ? "TRUE" : "FALSE",
+          DiskDrive->Int13ExtensionsSupported ? "TRUE" : "FALSE");
 
     return TRUE;
-  }
-
-  /* BIOS Int 13h, function 08h - Get drive parameters
-   * AH = 08h
-   * DL = drive (bit 7 set for hard disk)
-   * ES:DI = 0000h:0000h to guard against BIOS bugs
-   * Return:
-   * CF set on error
-   * AH = status (07h)
-   * CF clear if successful
-   * AH = 00h
-   * AL = 00h on at least some BIOSes
-   * BL = drive type (AT/PS2 floppies only)
-   * CH = low eight bits of maximum cylinder number
-   * CL = maximum sector number (bits 5-0)
-   *      high two bits of maximum cylinder number (bits 7-6)
-   * DH = maximum head number
-   * DL = number of drives
-   * ES:DI -> drive parameter table (floppies only)
-   */
-  RegsIn.b.ah = 0x08;
-  RegsIn.b.dl = DriveNumber;
-  RegsIn.w.es = 0x0000;
-  RegsIn.w.di = 0x0000;
-
-  /* Get drive parameters */
-  Int386(0x13, &RegsIn, &RegsOut);
-
-  if (! INT386_SUCCESS(RegsOut))
+}
+
+static inline
+PPC_DISK_DRIVE
+PcDiskDriveNumberToDrive(IN UCHAR DriveNumber)
+{
+#ifdef CACHE_MULTI_DRIVES
+    PPC_DISK_DRIVE DiskDrive;
+
+    ASSERT((0 <= DriveNumber) && (DriveNumber < RTL_NUMBER_OF(PcDiskDrive)));
+
+    /* Retrieve a slot */
+    DiskDrive = &PcDiskDrive[DriveNumber];
+
+    /* If the drive has not been initialized before... */
+    if (!DiskDrive->Initialized)
     {
-      return FALSE;
+        /* ... try to initialize it now. */
+        if (!PcDiskDriveInit(DriveNumber, DiskDrive))
+        {
+            /*
+             * If we failed, there is no drive at this number
+             * and flag it as such (set its high bit).
+             */
+            DiskDrive->Initialized |= 0x80;
+            return NULL;
+        }
+        DiskDrive->Initialized = TRUE;
     }
+    else if (DiskDrive->Initialized & 0x80)
+    {
+        /*
+         * The disk failed to be initialized previously, reset its flag to give
+         * it chance to be initialized again later, but just fail for the moment.
+         */
+        DiskDrive->Initialized = FALSE;
+        return NULL;
+    }
+
+    return DiskDrive;
+#else
+    static PC_DISK_DRIVE NewDiskDrive;
 
-  Cylinders = (RegsOut.b.cl & 0xC0) << 2;
-  Cylinders += RegsOut.b.ch;
-  Cylinders++;
-  Geometry->Cylinders = Cylinders;
-  Geometry->Heads = RegsOut.b.dh + 1;
-  Geometry->Sectors = RegsOut.b.cl & 0x3F;
-  Geometry->BytesPerSector = 512;            /* Just assume 512 bytes per sector */
+    ASSERT((0 <= DriveNumber) && (DriveNumber <= 0xFF));
+
+    /* Update cached information */
+
+    /* If the drive has not been accessed last before... */
+    if ((USHORT)DriveNumber != LastDriveNumber)
+    {
+        /* ... try to (re-)initialize and cache it now. */
+        RtlZeroMemory(&NewDiskDrive, sizeof(NewDiskDrive));
+        if (!PcDiskDriveInit(DriveNumber, &NewDiskDrive))
+        {
+            /*
+             * If we failed, there is no drive at this number.
+             * Keep the last-accessed valid drive cached.
+             */
+            return NULL;
+        }
+        /* We succeeded, cache the drive data */
+        PcDiskDrive = NewDiskDrive;
+        LastDriveNumber = (USHORT)DriveNumber;
+    }
 
-  return TRUE;
+    return &PcDiskDrive;
+#endif /* CACHE_MULTI_DRIVES */
 }
 
-ULONG
-PcDiskGetCacheableBlockCount(UCHAR DriveNumber)
+static BOOLEAN
+PcDiskReadLogicalSectorsLBA(
+    IN UCHAR DriveNumber,
+    IN ULONGLONG SectorNumber,
+    IN ULONG SectorCount,
+    OUT PVOID Buffer)
 {
-  GEOMETRY     Geometry;
+    REGS RegsIn, RegsOut;
+    ULONG RetryCount;
+    PI386_DISK_ADDRESS_PACKET Packet = (PI386_DISK_ADDRESS_PACKET)(BIOSCALLBUFFER);
+
+    /* Setup disk address packet */
+    RtlZeroMemory(Packet, sizeof(*Packet));
+    Packet->PacketSize = sizeof(*Packet);
+    Packet->Reserved = 0;
+    Packet->LBABlockCount = (USHORT)SectorCount;
+    ASSERT(Packet->LBABlockCount == SectorCount);
+    Packet->TransferBufferOffset = ((ULONG_PTR)Buffer) & 0x0F;
+    Packet->TransferBufferSegment = (USHORT)(((ULONG_PTR)Buffer) >> 4);
+    Packet->LBAStartBlock = SectorNumber;
+
+    /*
+     * BIOS int 0x13, function 42h - IBM/MS INT 13 Extensions - EXTENDED READ
+     * Return:
+     * CF clear if successful
+     * AH = 00h
+     * CF set on error
+     * AH = error code
+     * Disk address packet's block count field set to the
+     * number of blocks successfully transferred.
+     */
+    RegsIn.b.ah = 0x42;                 // Subfunction 42h
+    RegsIn.b.dl = DriveNumber;          // Drive number in DL (0 - floppy, 0x80 - harddisk)
+    RegsIn.x.ds = BIOSCALLBUFSEGMENT;   // DS:SI -> disk address packet
+    RegsIn.w.si = BIOSCALLBUFOFFSET;
+
+    /* Retry 3 times */
+    for (RetryCount = 0; RetryCount < 3; ++RetryCount)
+    {
+        Int386(0x13, &RegsIn, &RegsOut);
+
+        /* If it worked return TRUE */
+        if (INT386_SUCCESS(RegsOut))
+        {
+            return TRUE;
+        }
+        /* If it was a corrected ECC error then the data is still good */
+        else if (RegsOut.b.ah == 0x11)
+        {
+            return TRUE;
+        }
+        /* If it failed then do the next retry */
+        else
+        {
+            DiskResetController(DriveNumber);
+            continue;
+        }
+    }
+
+    /* If we get here then the read failed */
+    DiskError("Disk Read Failed in LBA mode", RegsOut.b.ah);
+    ERR("Disk Read Failed in LBA mode: %x (%s) (DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d)\n",
+        RegsOut.b.ah, DiskGetErrorCodeString(RegsOut.b.ah),
+        DriveNumber, SectorNumber, SectorCount);
 
-  /* If LBA is supported then the block size will be 64 sectors (32k)
-   * If not then the block size is the size of one track */
-  if (DiskInt13ExtensionsSupported(DriveNumber))
+    return FALSE;
+}
+
+static BOOLEAN
+PcDiskReadLogicalSectorsCHS(
+    IN UCHAR DriveNumber,
+    IN PPC_DISK_DRIVE DiskDrive,
+    IN ULONGLONG SectorNumber,
+    IN ULONG SectorCount,
+    OUT PVOID Buffer)
+{
+    UCHAR PhysicalSector;
+    UCHAR PhysicalHead;
+    ULONG PhysicalTrack;
+    GEOMETRY DriveGeometry;
+    ULONG NumberOfSectorsToRead;
+    REGS RegsIn, RegsOut;
+    ULONG RetryCount;
+
+    DriveGeometry = DiskDrive->Geometry;
+    if (DriveGeometry.Sectors == 0 || DriveGeometry.Heads == 0)
+        return FALSE;
+
+    while (SectorCount > 0)
     {
-      return 64;
+        /*
+         * Calculate the physical disk offsets.
+         * Note: DriveGeometry.Sectors < 64
+         */
+        PhysicalSector = 1 + (UCHAR)(SectorNumber % DriveGeometry.Sectors);
+        PhysicalHead = (UCHAR)((SectorNumber / DriveGeometry.Sectors) % DriveGeometry.Heads);
+        PhysicalTrack = (ULONG)((SectorNumber / DriveGeometry.Sectors) / DriveGeometry.Heads);
+
+        /* Calculate how many sectors we need to read this round */
+        if (PhysicalSector > 1)
+        {
+            if (SectorCount >= (DriveGeometry.Sectors - (PhysicalSector - 1)))
+                NumberOfSectorsToRead = (DriveGeometry.Sectors - (PhysicalSector - 1));
+            else
+                NumberOfSectorsToRead = SectorCount;
+        }
+        else
+        {
+            if (SectorCount >= DriveGeometry.Sectors)
+                NumberOfSectorsToRead = DriveGeometry.Sectors;
+            else
+                NumberOfSectorsToRead = SectorCount;
+        }
+
+        /* Make sure the read is within the geometry boundaries */
+        if ((PhysicalHead >= DriveGeometry.Heads) ||
+            (PhysicalTrack >= DriveGeometry.Cylinders) ||
+            ((NumberOfSectorsToRead + PhysicalSector) > (DriveGeometry.Sectors + 1)) ||
+            (PhysicalSector > DriveGeometry.Sectors))
+        {
+            DiskError("Disk read exceeds drive geometry limits.", 0);
+            return FALSE;
+        }
+
+        /*
+         * BIOS Int 13h, function 2 - Read Disk Sectors
+         * AH = 02h
+         * AL = number of sectors to read (must be nonzero)
+         * CH = low eight bits of cylinder number
+         * CL = sector number 1-63 (bits 0-5)
+         *      high two bits of cylinder (bits 6-7, hard disk only)
+         * DH = head number
+         * DL = drive number (bit 7 set for hard disk)
+         * ES:BX -> data buffer
+         * Return:
+         * CF set on error
+         * if AH = 11h (corrected ECC error), AL = burst length
+         * CF clear if successful
+         * AH = status
+         * AL = number of sectors transferred
+         *  (only valid if CF set for some BIOSes)
+         */
+        RegsIn.b.ah = 0x02;
+        RegsIn.b.al = (UCHAR)NumberOfSectorsToRead;
+        RegsIn.b.ch = (PhysicalTrack & 0xFF);
+        RegsIn.b.cl = (UCHAR)(PhysicalSector + ((PhysicalTrack & 0x300) >> 2));
+        RegsIn.b.dh = PhysicalHead;
+        RegsIn.b.dl = DriveNumber;
+        RegsIn.w.es = (USHORT)(((ULONG_PTR)Buffer) >> 4);
+        RegsIn.w.bx = ((ULONG_PTR)Buffer) & 0x0F;
+
+        /* Perform the read. Retry 3 times. */
+        for (RetryCount = 0; RetryCount < 3; ++RetryCount)
+        {
+            Int386(0x13, &RegsIn, &RegsOut);
+
+            /* If it worked break out */
+            if (INT386_SUCCESS(RegsOut))
+            {
+                break;
+            }
+            /* If it was a corrected ECC error then the data is still good */
+            else if (RegsOut.b.ah == 0x11)
+            {
+                break;
+            }
+            /* If it failed then do the next retry */
+            else
+            {
+                DiskResetController(DriveNumber);
+                continue;
+            }
+        }
+
+        /* If we retried 3 times then fail */
+        if (RetryCount >= 3)
+        {
+            DiskError("Disk Read Failed in CHS mode, after retrying 3 times", RegsOut.b.ah);
+            ERR("Disk Read Failed in CHS mode, after retrying 3 times: %x (%s) (DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d)\n",
+                RegsOut.b.ah, DiskGetErrorCodeString(RegsOut.b.ah),
+                DriveNumber, SectorNumber, SectorCount);
+            return FALSE;
+        }
+
+        /*
+         * I have learned that not all BIOSes return
+         * the sector read count in the AL register (at least mine doesn't)
+         * even if the sectors were read correctly. So instead
+         * of checking the sector read count we will rely solely
+         * on the carry flag being set on error.
+         */
+
+        Buffer = (PVOID)((ULONG_PTR)Buffer + (NumberOfSectorsToRead * DriveGeometry.BytesPerSector));
+        SectorCount -= NumberOfSectorsToRead;
+        SectorNumber += NumberOfSectorsToRead;
     }
-  /* Get the disk geometry
-   * If this fails then we will just return 1 sector to be safe */
-  else if (! PcDiskGetDriveGeometry(DriveNumber, &Geometry))
+
+    return TRUE;
+}
+
+BOOLEAN
+PcDiskReadLogicalSectors(
+    IN UCHAR DriveNumber,
+    IN ULONGLONG SectorNumber,
+    IN ULONG SectorCount,
+    OUT PVOID Buffer)
+{
+    PPC_DISK_DRIVE DiskDrive;
+
+    TRACE("PcDiskReadLogicalSectors() DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d Buffer: 0x%x\n",
+          DriveNumber, SectorNumber, SectorCount, Buffer);
+
+    /* 16-bit BIOS addressing limitation */
+    ASSERT(((ULONG_PTR)Buffer) <= 0xFFFFF);
+
+    DiskDrive = PcDiskDriveNumberToDrive(DriveNumber);
+    if (!DiskDrive)
+        return FALSE;
+
+    if ((DriveNumber >= 0x80) && DiskDrive->Int13ExtensionsSupported)
     {
-      return 1;
+        /* LBA is easy, nothing to calculate. Just do the read. */
+        TRACE("--> Using LBA\n");
+        return PcDiskReadLogicalSectorsLBA(DriveNumber, SectorNumber, SectorCount, Buffer);
     }
-  else
+    else
     {
-      return Geometry.Sectors;
+        /* LBA is not supported, default to CHS */
+        TRACE("--> Using CHS\n");
+        return PcDiskReadLogicalSectorsCHS(DriveNumber, DiskDrive, SectorNumber, SectorCount, Buffer);
     }
 }
 
+#if defined(__i386__) || defined(_M_AMD64)
+VOID __cdecl DiskStopFloppyMotor(VOID)
+{
+    WRITE_PORT_UCHAR((PUCHAR)0x3F2, 0x0C); // DOR_FDC_ENABLE | DOR_DMA_IO_INTERFACE_ENABLE
+}
+#endif // defined __i386__ || defined(_M_AMD64)
+
 BOOLEAN
-PcDiskGetBootPath(char *BootPath, unsigned Size)
+PcDiskGetDriveGeometry(UCHAR DriveNumber, PGEOMETRY Geometry)
 {
-  if (PxeInit())
+    PPC_DISK_DRIVE DiskDrive;
+
+    TRACE("PcDiskGetDriveGeometry(0x%x)\n", DriveNumber);
+
+    DiskDrive = PcDiskDriveNumberToDrive(DriveNumber);
+    if (!DiskDrive)
+        return FALSE;
+
+    /* Try to get the extended geometry first */
+    if (DiskDrive->ExtGeometry.Size == sizeof(DiskDrive->ExtGeometry))
     {
-      strcpy(BootPath, "net(0)");
-      return 0;
+        /* Extended geometry has been initialized, return it */
+        Geometry->Cylinders = DiskDrive->ExtGeometry.Cylinders;
+        Geometry->Heads = DiskDrive->ExtGeometry.Heads;
+        Geometry->Sectors = DiskDrive->ExtGeometry.SectorsPerTrack;
+        Geometry->BytesPerSector = DiskDrive->ExtGeometry.BytesPerSector;
     }
-  return DiskGetBootPath(BootPath, Size);
+    else
+    /* Fall back to legacy BIOS geometry */
+    {
+        *Geometry = DiskDrive->Geometry;
+    }
+
+    return TRUE;
+}
+
+ULONG
+PcDiskGetCacheableBlockCount(UCHAR DriveNumber)
+{
+    PPC_DISK_DRIVE DiskDrive;
+
+    DiskDrive = PcDiskDriveNumberToDrive(DriveNumber);
+    if (!DiskDrive)
+        return 1; // Unknown count.
+
+    /*
+     * If LBA is supported then the block size will be 64 sectors (32k).
+     * If not then the block size is the size of one track.
+     */
+    if (DiskDrive->Int13ExtensionsSupported)
+        return 64;
+    else
+        return DiskDrive->Geometry.Sectors;
 }
 
 /* EOF */