[int13] Guard against BIOSes that "fix" the drive count
authorMichael Brown <mcb30@etherboot.org>
Wed, 18 Nov 2009 02:37:15 +0000 (02:37 +0000)
committerMichael Brown <mcb30@etherboot.org>
Wed, 18 Nov 2009 02:44:49 +0000 (02:44 +0000)
Some BIOSes (observed with an AMI BIOS on a SunFire X2200) seem to
reset the BIOS drive counter at 40:75 after a failed boot attempt.
This causes problems when attempting a Windows direct-to-iSCSI
installation: bootmgr.exe calls INT 13,0800 and gets told that there
are no hard disks, so never bothers to read the MBR in order to obtain
the boot disk signature.  The Windows iSCSI initiator will detect the
iBFT and connect to the target, and everything will appear to work
except for the error message "This computer's hardware may not support
booting to this disk.  Ensure that the disk's controller is enabled in
the computer's BIOS menu."

Fix by checking the BIOS drive counter on every INT 13 call, and
updating it whenever necessary.

src/arch/i386/interface/pcbios/int13.c

index 07439b9..87b613a 100644 (file)
@@ -51,6 +51,48 @@ extern void int13_wrapper ( void );
 /** List of registered emulated drives */
 static LIST_HEAD ( drives );
 
+/**
+ * Number of BIOS drives
+ *
+ * Note that this is the number of drives in the system as a whole
+ * (i.e. a mirror of the counter at 40:75), rather than a count of the
+ * number of emulated drives.
+ */
+static uint8_t num_drives;
+
+/**
+ * Update BIOS drive count
+ */
+static void int13_set_num_drives ( void ) {
+       struct int13_drive *drive;
+
+       /* Get current drive count */
+       get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
+
+       /* Ensure count is large enough to cover all of our emulated drives */
+       list_for_each_entry ( drive, &drives, list ) {
+               if ( num_drives <= ( drive->drive & 0x7f ) )
+                       num_drives = ( ( drive->drive & 0x7f ) + 1 );
+       }
+
+       /* Update current drive count */
+       put_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
+}
+
+/**
+ * Check number of drives
+ */
+static void int13_check_num_drives ( void ) {
+       uint8_t check_num_drives;
+
+       get_real ( check_num_drives, BDA_SEG, BDA_NUM_DRIVES );
+       if ( check_num_drives != num_drives ) {
+               int13_set_num_drives();
+               DBG ( "INT13 fixing up number of drives from %d to %d\n",
+                     check_num_drives, num_drives );
+       }
+}
+
 /**
  * INT 13, 00 - Reset disk system
  *
@@ -340,6 +382,9 @@ static __asmcall void int13 ( struct i386_all_regs *ix86 ) {
        struct int13_drive *drive;
        int status;
 
+       /* Check BIOS hasn't killed off our drive */
+       int13_check_num_drives();
+
        list_for_each_entry ( drive, &drives, list ) {
 
                if ( bios_drive != drive->drive ) {
@@ -555,7 +600,6 @@ void register_int13_drive ( struct int13_drive *drive ) {
        /* Assign natural drive number */
        get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
        drive->natural_drive = ( num_drives | 0x80 );
-       num_drives++;
 
        /* Assign drive number */
        if ( ( drive->drive & 0xff ) == 0xff ) {
@@ -564,13 +608,8 @@ void register_int13_drive ( struct int13_drive *drive ) {
        } else {
                /* Use specified drive number (+0x80 if necessary) */
                drive->drive |= 0x80;
-               if ( num_drives <= ( drive->drive & 0x7f ) )
-                       num_drives = ( ( drive->drive & 0x7f ) + 1 );
        }
 
-       /* Update BIOS drive count */
-       put_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
-
        DBG ( "Registered INT13 drive %02x (naturally %02x) with C/H/S "
              "geometry %d/%d/%d\n", drive->drive, drive->natural_drive,
              drive->cylinders, drive->heads, drive->sectors_per_track );
@@ -581,6 +620,9 @@ void register_int13_drive ( struct int13_drive *drive ) {
 
        /* Add to list of emulated drives */
        list_add ( &drive->list, &drives );
+
+       /* Update BIOS drive count */
+       int13_set_num_drives();
 }
 
 /**