[FREELDR] Hack: Boot ReactOS even when a cdrom-drive does not report a proper geometry
[reactos.git] / boot / freeldr / freeldr / arch / i386 / hwdisk.c
index 4c344e2..1717bdb 100644 (file)
 
 #include <freeldr.h>
 
-#define NDEBUG
 #include <debug.h>
-
 DBG_DEFAULT_CHANNEL(HWDETECT);
 
+/*
+ * This is the common code for harddisk for both the PC and the XBOX.
+ */
+
 typedef struct tagDISKCONTEXT
 {
     UCHAR DriveNumber;
@@ -35,35 +37,48 @@ typedef struct tagDISKCONTEXT
     ULONGLONG SectorNumber;
 } DISKCONTEXT;
 
-extern ULONG reactos_disk_count;
-extern ARC_DISK_SIGNATURE reactos_arc_disk_info[];
-extern CHAR reactos_arc_strings[32][256];
+static const CHAR Hex[] = "0123456789abcdef";
 
-static CHAR Hex[] = "0123456789abcdef";
+/* Data cache for BIOS disks pre-enumeration */
 UCHAR PcBiosDiskCount = 0;
-CHAR PcDiskIdentifier[32][20];
+static CHAR PcDiskIdentifier[32][20];
+
+PVOID DiskReadBuffer;
+SIZE_T DiskReadBufferSize;
+
 
+/* FUNCTIONS *****************************************************************/
 
-static LONG DiskClose(ULONG FileId)
+static ARC_STATUS
+DiskClose(ULONG FileId)
 {
     DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
-
     FrLdrTempFree(Context, TAG_HW_DISK_CONTEXT);
     return ESUCCESS;
 }
 
-static LONG DiskGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
+static ARC_STATUS
+DiskGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
 {
     DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
 
-    RtlZeroMemory(Information, sizeof(FILEINFORMATION));
-    Information->EndingAddress.QuadPart = (Context->SectorOffset + Context->SectorCount) * Context->SectorSize;
-    Information->CurrentAddress.QuadPart = (Context->SectorOffset + Context->SectorNumber) * Context->SectorSize;
+    RtlZeroMemory(Information, sizeof(*Information));
+
+    /*
+     * The ARC specification mentions that for partitions, StartingAddress and
+     * EndingAddress are the start and end positions of the partition in terms
+     * of byte offsets from the start of the disk.
+     * CurrentAddress is the current offset into (i.e. relative to) the partition.
+     */
+    Information->StartingAddress.QuadPart = Context->SectorOffset * Context->SectorSize;
+    Information->EndingAddress.QuadPart   = (Context->SectorOffset + Context->SectorCount) * Context->SectorSize;
+    Information->CurrentAddress.QuadPart  = Context->SectorNumber * Context->SectorSize;
 
     return ESUCCESS;
 }
 
-static LONG DiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
+static ARC_STATUS
+DiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
 {
     DISKCONTEXT* Context;
     UCHAR DriveNumber;
@@ -71,9 +86,15 @@ static LONG DiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
     ULONGLONG SectorOffset = 0;
     ULONGLONG SectorCount = 0;
     PARTITION_TABLE_ENTRY PartitionTableEntry;
-    CHAR FileName[1];
 
-    if (!DissectArcPath(Path, FileName, &DriveNumber, &DrivePartition))
+    if (DiskReadBufferSize == 0)
+    {
+        ERR("DiskOpen(): DiskReadBufferSize is 0, something is wrong.\n");
+        ASSERT(FALSE);
+        return ENOMEM;
+    }
+
+    if (!DissectArcPath(Path, NULL, &DriveNumber, &DrivePartition))
         return EINVAL;
 
     if (DrivePartition == 0xff)
@@ -83,9 +104,12 @@ static LONG DiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
     }
     else
     {
-        /* This is either a floppy disk device (DrivePartition == 0) or
-         * a hard disk device (DrivePartition != 0 && DrivePartition != 0xFF) but
-         * it doesn't matter which one because they both have 512 bytes per sector */
+        /*
+         * This is either a floppy disk device (DrivePartition == 0) or
+         * a hard disk device (DrivePartition != 0 && DrivePartition != 0xFF)
+         * but it doesn't matter which one because they both have 512 bytes
+         * per sector.
+         */
         SectorSize = 512;
     }
 
@@ -93,13 +117,30 @@ static LONG DiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
     {
         if (!DiskGetPartitionEntry(DriveNumber, DrivePartition, &PartitionTableEntry))
             return EINVAL;
+
         SectorOffset = PartitionTableEntry.SectorCountBeforePartition;
         SectorCount = PartitionTableEntry.PartitionSectorCount;
     }
+    else
+    {
+        GEOMETRY Geometry;
+        if (!MachDiskGetDriveGeometry(DriveNumber, &Geometry))
+            return EINVAL;
+
+        if (SectorSize != Geometry.BytesPerSector)
+        {
+            ERR("SectorSize (%lu) != Geometry.BytesPerSector (%lu), expect problems!\n",
+                SectorSize, Geometry.BytesPerSector);
+        }
+
+        SectorOffset = 0;
+        SectorCount = (ULONGLONG)Geometry.Cylinders * Geometry.Heads * Geometry.Sectors;
+    }
 
     Context = FrLdrTempAlloc(sizeof(DISKCONTEXT), TAG_HW_DISK_CONTEXT);
     if (!Context)
         return ENOMEM;
+
     Context->DriveNumber = DriveNumber;
     Context->SectorSize = SectorSize;
     Context->SectorOffset = SectorOffset;
@@ -110,7 +151,8 @@ static LONG DiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
     return ESUCCESS;
 }
 
-static LONG DiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
+static ARC_STATUS
+DiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
 {
     DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
     UCHAR* Ptr = (UCHAR*)Buffer;
@@ -118,11 +160,17 @@ static LONG DiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
     BOOLEAN ret;
     ULONGLONG SectorOffset;
 
+    ASSERT(DiskReadBufferSize > 0);
+
     TotalSectors = (N + Context->SectorSize - 1) / Context->SectorSize;
-    MaxSectors   = DISKREADBUFFER_SIZE / Context->SectorSize;
-    SectorOffset = Context->SectorNumber + Context->SectorOffset;
+    MaxSectors   = DiskReadBufferSize / Context->SectorSize;
+    SectorOffset = Context->SectorOffset + Context->SectorNumber;
+
+    // If MaxSectors is 0, this will lead to infinite loop.
+    // In release builds assertions are disabled, however we also have sanity checks in DiskOpen()
+    ASSERT(MaxSectors > 0);
 
-    ret = 1;
+    ret = TRUE;
 
     while (TotalSectors)
     {
@@ -130,11 +178,10 @@ static LONG DiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
         if (ReadSectors > MaxSectors)
             ReadSectors = MaxSectors;
 
-        ret = MachDiskReadLogicalSectors(
-            Context->DriveNumber,
-            SectorOffset,
-            ReadSectors,
-            (PVOID)DISKREADBUFFER);
+        ret = MachDiskReadLogicalSectors(Context->DriveNumber,
+                                         SectorOffset,
+                                         ReadSectors,
+                                         DiskReadBuffer);
         if (!ret)
             break;
 
@@ -142,7 +189,7 @@ static LONG DiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
         if (Length > N)
             Length = N;
 
-        RtlCopyMemory(Ptr, (PVOID)DISKREADBUFFER, Length);
+        RtlCopyMemory(Ptr, DiskReadBuffer, Length);
 
         Ptr += Length;
         N -= Length;
@@ -155,20 +202,40 @@ static LONG DiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
     return (!ret) ? EIO : ESUCCESS;
 }
 
-static LONG DiskSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
+static ARC_STATUS
+DiskSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
 {
     DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
+    LARGE_INTEGER NewPosition = *Position;
 
-    if (SeekMode != SeekAbsolute)
+    switch (SeekMode)
+    {
+        case SeekAbsolute:
+            break;
+        case SeekRelative:
+            NewPosition.QuadPart += (Context->SectorNumber * Context->SectorSize);
+            break;
+        default:
+            ASSERT(FALSE);
+            return EINVAL;
+    }
+
+    if (NewPosition.QuadPart & (Context->SectorSize - 1))
         return EINVAL;
-    if (Position->LowPart & (Context->SectorSize - 1))
+
+    /* Convert in number of sectors */
+    NewPosition.QuadPart /= Context->SectorSize;
+
+    /* HACK: CDROMs may have a SectorCount of 0 */
+    if (Context->SectorCount != 0 && NewPosition.QuadPart >= Context->SectorCount)
         return EINVAL;
 
-    Context->SectorNumber = (ULONG)(Position->QuadPart / Context->SectorSize);
+    Context->SectorNumber = NewPosition.QuadPart;
     return ESUCCESS;
 }
 
-static const DEVVTBL DiskVtbl = {
+static const DEVVTBL DiskVtbl =
+{
     DiskClose,
     DiskGetFileInformation,
     DiskOpen,
@@ -176,56 +243,58 @@ static const DEVVTBL DiskVtbl = {
     DiskSeek,
 };
 
+
 PCHAR
-GetHarddiskIdentifier(
-    UCHAR DriveNumber)
+GetHarddiskIdentifier(UCHAR DriveNumber)
 {
     return PcDiskIdentifier[DriveNumber - 0x80];
 }
 
-VOID
-GetHarddiskInformation(
-    UCHAR DriveNumber)
+static VOID
+GetHarddiskInformation(UCHAR DriveNumber)
 {
     PMASTER_BOOT_RECORD Mbr;
-    ULONG *Buffer;
+    PULONG Buffer;
     ULONG i;
     ULONG Checksum;
     ULONG Signature;
-    CHAR ArcName[256];
+    BOOLEAN ValidPartitionTable;
+    CHAR ArcName[MAX_PATH];
     PARTITION_TABLE_ENTRY PartitionTableEntry;
     PCHAR Identifier = PcDiskIdentifier[DriveNumber - 0x80];
 
+    /* Detect disk partition type */
+    DiskDetectPartitionType(DriveNumber);
+
     /* Read the MBR */
-    if (!MachDiskReadLogicalSectors(DriveNumber, 0ULL, 1, (PVOID)DISKREADBUFFER))
+    if (!MachDiskReadLogicalSectors(DriveNumber, 0ULL, 1, DiskReadBuffer))
     {
         ERR("Reading MBR failed\n");
+        /* We failed, use a default identifier */
+        sprintf(Identifier, "BIOSDISK%d", DriveNumber - 0x80 + 1);
         return;
     }
 
-    Buffer = (ULONG*)DISKREADBUFFER;
-    Mbr = (PMASTER_BOOT_RECORD)DISKREADBUFFER;
+    Buffer = (ULONG*)DiskReadBuffer;
+    Mbr = (PMASTER_BOOT_RECORD)DiskReadBuffer;
 
-    Signature =  Mbr->Signature;
+    Signature = Mbr->Signature;
     TRACE("Signature: %x\n", Signature);
 
     /* Calculate the MBR checksum */
     Checksum = 0;
-    for (i = 0; i < 128; i++)
+    for (i = 0; i < 512 / sizeof(ULONG); i++)
     {
         Checksum += Buffer[i];
     }
     Checksum = ~Checksum + 1;
     TRACE("Checksum: %x\n", Checksum);
 
+    ValidPartitionTable = (Mbr->MasterBootRecordMagic == 0xAA55);
+
     /* Fill out the ARC disk block */
-    reactos_arc_disk_info[reactos_disk_count].Signature = Signature;
-    reactos_arc_disk_info[reactos_disk_count].CheckSum = Checksum;
-    sprintf(ArcName, "multi(0)disk(0)rdisk(%lu)", reactos_disk_count);
-    strcpy(reactos_arc_strings[reactos_disk_count], ArcName);
-    reactos_arc_disk_info[reactos_disk_count].ArcName =
-        reactos_arc_strings[reactos_disk_count];
-    reactos_disk_count++;
+    sprintf(ArcName, "multi(0)disk(0)rdisk(%u)", DriveNumber - 0x80);
+    AddReactOSArcDiskInfo(ArcName, Signature, Checksum, ValidPartitionTable);
 
     sprintf(ArcName, "multi(0)disk(0)rdisk(%u)partition(0)", DriveNumber - 0x80);
     FsRegisterDevice(ArcName, &DiskVtbl);
@@ -263,96 +332,188 @@ GetHarddiskInformation(
     Identifier[15] = Hex[(Signature >> 4) & 0x0F];
     Identifier[16] = Hex[Signature & 0x0F];
     Identifier[17] = '-';
-    Identifier[18] = 'A';
+    Identifier[18] = (ValidPartitionTable ? 'A' : 'X');
     Identifier[19] = 0;
     TRACE("Identifier: %s\n", Identifier);
 }
 
-
-BOOLEAN
-HwInitializeBiosDisks(VOID)
+static UCHAR
+EnumerateHarddisks(OUT PBOOLEAN BootDriveReported)
 {
     UCHAR DiskCount, DriveNumber;
     ULONG i;
     BOOLEAN Changed;
-    CHAR BootPath[512];
-    BOOLEAN BootDriveReported = FALSE;
 
-    /* Count the number of visible drives */
+    *BootDriveReported = FALSE;
+
+    /* Count the number of visible harddisk drives */
     DiskReportError(FALSE);
     DiskCount = 0;
     DriveNumber = 0x80;
 
-    /* There are some really broken BIOSes out there. There are even BIOSes
-        * that happily report success when you ask them to read from non-existent
-        * harddisks. So, we set the buffer to known contents first, then try to
-        * read. If the BIOS reports success but the buffer contents haven't
-        * changed then we fail anyway */
-    memset((PVOID) DISKREADBUFFER, 0xcd, 512);
-    while (MachDiskReadLogicalSectors(DriveNumber, 0ULL, 1, (PVOID)DISKREADBUFFER))
+    ASSERT(DiskReadBufferSize > 0);
+
+    /*
+     * There are some really broken BIOSes out there. There are even BIOSes
+     * that happily report success when you ask them to read from non-existent
+     * harddisks. So, we set the buffer to known contents first, then try to
+     * read. If the BIOS reports success but the buffer contents haven't
+     * changed then we fail anyway.
+     */
+    memset(DiskReadBuffer, 0xcd, DiskReadBufferSize);
+    while (MachDiskReadLogicalSectors(DriveNumber, 0ULL, 1, DiskReadBuffer))
     {
         Changed = FALSE;
-        for (i = 0; ! Changed && i < 512; i++)
+        for (i = 0; !Changed && i < DiskReadBufferSize; i++)
         {
-            Changed = ((PUCHAR)DISKREADBUFFER)[i] != 0xcd;
+            Changed = ((PUCHAR)DiskReadBuffer)[i] != 0xcd;
         }
-        if (! Changed)
+        if (!Changed)
         {
-            TRACE("BIOS reports success for disk %d but data didn't change\n",
-                      (int)DiskCount);
+            TRACE("BIOS reports success for disk %d (0x%02X) but data didn't change\n",
+                  (int)DiskCount, DriveNumber);
             break;
         }
 
+        /* Cache the BIOS hard disk information for later use */
         GetHarddiskInformation(DriveNumber);
 
+        /* Check if we have seen the boot drive */
         if (FrldrBootDrive == DriveNumber)
-            BootDriveReported = TRUE;
+            *BootDriveReported = TRUE;
 
         DiskCount++;
         DriveNumber++;
-        memset((PVOID) DISKREADBUFFER, 0xcd, 512);
+        memset(DiskReadBuffer, 0xcd, DiskReadBufferSize);
     }
     DiskReportError(TRUE);
 
-    /* Get the drive we're booting from */
-    MachDiskGetBootPath(BootPath, sizeof(BootPath));
+    PcBiosDiskCount = DiskCount;
+    TRACE("BIOS reports %d harddisk%s\n",
+          (int)DiskCount, (DiskCount == 1) ? "" : "s");
+
+    return DiskCount;
+}
+
+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;
+}
+
+static BOOLEAN
+DiskGetBootPath(BOOLEAN IsPxe)
+{
+    if (*FrLdrBootPath)
+        return TRUE;
+
+    // FIXME! FIXME! Do this in some drive recognition procedure!!!!
+    if (IsPxe)
+    {
+        RtlStringCbCopyA(FrLdrBootPath, sizeof(FrLdrBootPath), "net(0)");
+    }
+    else
+    /* 0x49 is our magic ramdisk drive, so try to detect it first */
+    if (FrldrBootDrive == 0x49)
+    {
+        /* This is the ramdisk. See ArmInitializeBootDevices() too... */
+        // RtlStringCbPrintfA(FrLdrBootPath, sizeof(FrLdrBootPath), "ramdisk(%u)", 0);
+        RtlStringCbCopyA(FrLdrBootPath, sizeof(FrLdrBootPath), "ramdisk(0)");
+    }
+    else if (FrldrBootDrive < 0x80)
+    {
+        /* This is a floppy */
+        RtlStringCbPrintfA(FrLdrBootPath, sizeof(FrLdrBootPath),
+                           "multi(0)disk(0)fdisk(%u)", FrldrBootDrive);
+    }
+    else if (FrldrBootPartition == 0xFF)
+    {
+        /* Boot Partition 0xFF is the magic value that indicates booting from CD-ROM (see isoboot.S) */
+        RtlStringCbPrintfA(FrLdrBootPath, sizeof(FrLdrBootPath),
+                           "multi(0)disk(0)cdrom(%u)", FrldrBootDrive - 0x80);
+    }
+    else
+    {
+        ULONG BootPartition;
+        PARTITION_TABLE_ENTRY PartitionEntry;
+
+        /* This is a hard disk */
+        if (!DiskGetBootPartitionEntry(FrldrBootDrive, &PartitionEntry, &BootPartition))
+        {
+            ERR("Failed to get boot partition entry\n");
+            return FALSE;
+        }
+
+        FrldrBootPartition = BootPartition;
+
+        RtlStringCbPrintfA(FrLdrBootPath, sizeof(FrLdrBootPath),
+                           "multi(0)disk(0)rdisk(%u)partition(%lu)",
+                           FrldrBootDrive - 0x80, FrldrBootPartition);
+    }
+
+    return TRUE;
+}
+
+BOOLEAN
+PcInitializeBootDevices(VOID)
+{
+    UCHAR DiskCount;
+    BOOLEAN BootDriveReported = FALSE;
+    ULONG i;
+
+    DiskCount = EnumerateHarddisks(&BootDriveReported);
+
+    /* Initialize FrLdrBootPath, the boot path we're booting from (the "SystemPartition") */
+    DiskGetBootPath(PxeInit());
 
     /* Add it, if it's a floppy or cdrom */
     if ((FrldrBootDrive >= 0x80 && !BootDriveReported) ||
         DiskIsDriveRemovable(FrldrBootDrive))
     {
-        /* TODO: Check if it's really a cdrom drive */
-        ULONG* Buffer;
+        /* TODO: Check if it's really a CDROM drive */
+
+        PMASTER_BOOT_RECORD Mbr;
+        PULONG Buffer;
         ULONG Checksum = 0;
+        ULONG Signature;
 
         /* Read the MBR */
-        if (!MachDiskReadLogicalSectors(FrldrBootDrive, 16ULL, 1, (PVOID)DISKREADBUFFER))
+        if (!MachDiskReadLogicalSectors(FrldrBootDrive, 16ULL, 1, DiskReadBuffer))
         {
-          ERR("Reading MBR failed\n");
-          return FALSE;
+            ERR("Reading MBR failed\n");
+            return FALSE;
         }
 
-        Buffer = (ULONG*)DISKREADBUFFER;
+        Buffer = (ULONG*)DiskReadBuffer;
+        Mbr = (PMASTER_BOOT_RECORD)DiskReadBuffer;
+
+        Signature = Mbr->Signature;
+        TRACE("Signature: %x\n", Signature);
 
         /* Calculate the MBR checksum */
-        for (i = 0; i < 2048 / sizeof(ULONG); i++) Checksum += Buffer[i];
+        for (i = 0; i < 2048 / sizeof(ULONG); i++)
+        {
+            Checksum += Buffer[i];
+        }
         Checksum = ~Checksum + 1;
         TRACE("Checksum: %x\n", Checksum);
 
         /* Fill out the ARC disk block */
-        reactos_arc_disk_info[reactos_disk_count].CheckSum = Checksum;
-        strcpy(reactos_arc_strings[reactos_disk_count], BootPath);
-        reactos_arc_disk_info[reactos_disk_count].ArcName =
-            reactos_arc_strings[reactos_disk_count];
-        reactos_disk_count++;
+        AddReactOSArcDiskInfo(FrLdrBootPath, Signature, Checksum, TRUE);
 
-        FsRegisterDevice(BootPath, &DiskVtbl);
-        DiskCount++;
+        FsRegisterDevice(FrLdrBootPath, &DiskVtbl);
+        DiskCount++; // This is not accounted for in the number of pre-enumerated BIOS drives!
+        TRACE("Additional boot drive detected: 0x%02X\n", (int)FrldrBootDrive);
     }
 
-    PcBiosDiskCount = DiskCount;
-    TRACE("BIOS reports %d harddisk%s\n",
-          (int)DiskCount, (DiskCount == 1) ? "": "s");
-
-    return DiskCount != 0;
+    return (DiskCount != 0);
 }