Proof of concept; works, but has several hard-coded hacks.
[people/xl0/gpxe-arm.git] / src / arch / i386 / interface / pcbios / int13.c
index d4d15c1..a4563a0 100644 (file)
@@ -26,6 +26,7 @@
 #include <realmode.h>
 #include <bios.h>
 #include <biosint.h>
+#include <bootsector.h>
 #include <int13.h>
 
 /** @file
@@ -44,117 +45,9 @@ static struct segoff __text16 ( int13_vector );
 /** Assembly wrapper */
 extern void int13_wrapper ( void );
 
-/** Vector for storing original INT 18 handler
- *
- * We do not chain to this vector, so there is no need to place it in
- * .text16.
- */
-static struct segoff int18_vector;
-
-/** Vector for storing original INT 19 handler
- *
- * We do not chain to this vector, so there is no need to place it in
- * .text16.
- */
-static struct segoff int19_vector;
-
-/** Restart point for INT 18 or 19 */
-extern void int13_exec_fail ( void );
-
 /** List of registered emulated drives */
 static LIST_HEAD ( drives );
 
-/**
- * Convert CHS address to linear address
- *
- * @v drive            Emulated drive
- * @v ch               Low bits of cylinder number
- * @v cl (bits 7:6)    High bits of cylinder number
- * @v cl (bits 5:0)    Sector number
- * @v dh               Head number
- * @ret lba            LBA address
- * 
- */
-static unsigned long chs_to_lba ( struct int13_drive *drive,
-                                 struct i386_all_regs *ix86 ) {
-       unsigned int cylinder;
-       unsigned int head;
-       unsigned int sector;
-       unsigned long lba;
-
-       cylinder = ( ( ( ix86->regs.cl & 0xc0 ) << 8 ) | ix86->regs.ch );
-       head = ix86->regs.dh;
-       sector = ( ix86->regs.cl & 0x3f );
-
-       assert ( cylinder < drive->cylinders );
-       assert ( head < drive->heads );
-       assert ( ( sector >= 1 ) && ( sector <= drive->sectors_per_track ) );
-
-       lba = ( ( ( ( cylinder * drive->heads ) + head )
-                 * drive->sectors_per_track ) + sector - 1 );
-
-       DBG ( "C/H/S address %x/%x/%x -> LBA %x\n",
-             cylinder, head, sector, lba );
-
-       return lba;
-}
-
-/**
- * Read from drive to real-mode data buffer
- *
- * @v drive            Emulated drive
- * @v lba              LBA starting sector number
- * @v data             Data buffer
- * @v count            Number of sectors to read
- * @ret status         Status code
- */
-static int int13_read ( struct int13_drive *drive, uint64_t lba,
-                       struct segoff data, unsigned long count ) {
-       struct block_device *blockdev = drive->blockdev;
-       size_t blksize = blockdev->blksize;
-       uint8_t buffer[blksize];
-       int rc;
-
-       DBG ( "Read %lx sectors from %llx to %04x:%04x\n", count,
-             ( unsigned long long ) lba, data.segment, data.offset );
-       while ( count-- ) {
-               if ( ( rc = blockdev->read ( blockdev, lba, buffer ) ) != 0 )
-                       return INT13_STATUS_READ_ERROR;
-               copy_to_real ( data.segment, data.offset, buffer, blksize );
-               data.offset += blksize;
-               lba++;
-       }
-       return 0;
-}
-
-/**
- * Write from real-mode data buffer to drive
- *
- * @v drive            Emulated drive
- * @v lba              LBA starting sector number
- * @v data             Data buffer
- * @v count            Number of sectors to read
- * @ret status         Status code
- */
-static int int13_write ( struct int13_drive *drive, uint64_t lba,
-                        struct segoff data, unsigned long count ) {
-       struct block_device *blockdev = drive->blockdev;
-       size_t blksize = blockdev->blksize;
-       uint8_t buffer[blksize];
-       int rc;
-
-       DBG ( "Write %lx sectors from %04x:%04x to %llx\n", count,
-             data.segment, data.offset, ( unsigned long long ) lba );
-       while ( count-- ) {
-               copy_from_real ( buffer, data.segment, data.offset, blksize );
-               if ( ( rc = blockdev->write ( blockdev, lba, buffer ) ) != 0 )
-                       return INT13_STATUS_WRITE_ERROR;
-               data.offset += blksize;
-               lba++;
-       }
-       return 0;
-}
-
 /**
  * INT 13, 00 - Reset disk system
  *
@@ -179,6 +72,61 @@ static int int13_get_last_status ( struct int13_drive *drive,
        return drive->last_status;
 }
 
+/**
+ * Read / write sectors
+ *
+ * @v drive            Emulated drive
+ * @v al               Number of sectors to read or write (must be nonzero)
+ * @v ch               Low bits of cylinder number
+ * @v cl (bits 7:6)    High bits of cylinder number
+ * @v cl (bits 5:0)    Sector number
+ * @v dh               Head number
+ * @v es:bx            Data buffer
+ * @v io               Read / write method
+ * @ret status         Status code
+ * @ret al             Number of sectors read or written
+ */
+static int int13_rw_sectors ( struct int13_drive *drive,
+                             struct i386_all_regs *ix86,
+                             int ( * io ) ( struct block_device *blockdev,
+                                            uint64_t block,
+                                            unsigned long count,
+                                            userptr_t buffer ) ) {
+       struct block_device *blockdev = drive->blockdev;
+       unsigned int cylinder, head, sector;
+       unsigned long lba;
+       unsigned int count;
+       userptr_t buffer;
+
+       /* Validate blocksize */
+       if ( blockdev->blksize != INT13_BLKSIZE ) {
+               DBG ( "Invalid blocksize (%zd) for non-extended read/write\n",
+                     blockdev->blksize );
+               return -INT13_STATUS_INVALID;
+       }
+       
+       /* Calculate parameters */
+       cylinder = ( ( ( ix86->regs.cl & 0xc0 ) << 2 ) | ix86->regs.ch );
+       assert ( cylinder < drive->cylinders );
+       head = ix86->regs.dh;
+       assert ( head < drive->heads );
+       sector = ( ix86->regs.cl & 0x3f );
+       assert ( ( sector >= 1 ) && ( sector <= drive->sectors_per_track ) );
+       lba = ( ( ( ( cylinder * drive->heads ) + head )
+                 * drive->sectors_per_track ) + sector - 1 );
+       count = ix86->regs.al;
+       buffer = real_to_user ( ix86->segs.es, ix86->regs.bx );
+
+       DBG ( "C/H/S %d/%d/%d = LBA %#lx <-> %04x:%04x (count %d)\n", cylinder,
+             head, sector, lba, ix86->segs.es, ix86->regs.bx, count );
+
+       /* Read from / write to block device */
+       if ( io ( blockdev, lba, count, buffer ) != 0 )
+               return -INT13_STATUS_READ_ERROR;
+
+       return 0;
+}
+
 /**
  * INT 13, 02 - Read sectors
  *
@@ -194,20 +142,8 @@ static int int13_get_last_status ( struct int13_drive *drive,
  */
 static int int13_read_sectors ( struct int13_drive *drive,
                                struct i386_all_regs *ix86 ) {
-       unsigned long lba = chs_to_lba ( drive, ix86 );
-       unsigned int count = ix86->regs.al;
-       struct segoff data = {
-               .segment = ix86->segs.es,
-               .offset = ix86->regs.bx,
-       };
-
-       if ( drive->blockdev->blksize != INT13_BLKSIZE ) {
-               DBG ( "Invalid blocksize (%d) for non-extended read\n",
-                     drive->blockdev->blksize );
-               return INT13_STATUS_INVALID;
-       }
-
-       return int13_read ( drive, lba, data, count );
+       DBG ( "Read: " );
+       return int13_rw_sectors ( drive, ix86, drive->blockdev->read );
 }
 
 /**
@@ -225,20 +161,8 @@ static int int13_read_sectors ( struct int13_drive *drive,
  */
 static int int13_write_sectors ( struct int13_drive *drive,
                                 struct i386_all_regs *ix86 ) {
-       unsigned long lba = chs_to_lba ( drive, ix86 );
-       unsigned int count = ix86->regs.al;
-       struct segoff data = {
-               .segment = ix86->segs.es,
-               .offset = ix86->regs.bx,
-       };
-
-       if ( drive->blockdev->blksize != INT13_BLKSIZE ) {
-               DBG ( "Invalid blocksize (%d) for non-extended write\n",
-                     drive->blockdev->blksize );
-               return INT13_STATUS_INVALID;
-       }
-
-       return int13_write ( drive, lba, data, count );
+       DBG ( "Write: " );
+       return int13_rw_sectors ( drive, ix86, drive->blockdev->write );
 }
 
 /**
@@ -267,6 +191,22 @@ static int int13_get_parameters ( struct int13_drive *drive,
        return 0;
 }
 
+/**
+ * INT 13, 15 - Get disk type
+ *
+ * @v drive            Emulated drive
+ * @ret ah             Type code
+ * @ret cx:dx          Sector count
+ * @ret status         Status code / disk type
+ */
+static int int13_get_disk_type ( struct int13_drive *drive,
+                                struct i386_all_regs *ix86 ) {
+       DBG ( "Get disk type\n" );
+       ix86->regs.cx = ( drive->cylinders >> 16 );
+       ix86->regs.dx = ( drive->cylinders & 0xffff );
+       return INT13_DISK_TYPE_HDD;
+}
+
 /**
  * INT 13, 41 - Extensions installation check
  *
@@ -274,7 +214,7 @@ static int int13_get_parameters ( struct int13_drive *drive,
  * @v bx               0x55aa
  * @ret bx             0xaa55
  * @ret cx             Extensions API support bitmap
- * @ret status         Status code
+ * @ret status         Status code / API version
  */
 static int int13_extension_check ( struct int13_drive *drive __unused,
                                   struct i386_all_regs *ix86 ) {
@@ -282,12 +222,48 @@ static int int13_extension_check ( struct int13_drive *drive __unused,
                DBG ( "INT 13 extensions installation check\n" );
                ix86->regs.bx = 0xaa55;
                ix86->regs.cx = INT13_EXTENSION_LINEAR;
-               return 0;
+               return INT13_EXTENSION_VER_1_X;
        } else {
-               return INT13_STATUS_INVALID;
+               return -INT13_STATUS_INVALID;
        }
 }
 
+/**
+ * Extended read / write
+ *
+ * @v drive            Emulated drive
+ * @v ds:si            Disk address packet
+ * @v io               Read / write method
+ * @ret status         Status code
+ */
+static int int13_extended_rw ( struct int13_drive *drive,
+                              struct i386_all_regs *ix86,
+                              int ( * io ) ( struct block_device *blockdev,
+                                             uint64_t block,
+                                             unsigned long count,
+                                             userptr_t buffer ) ) {
+       struct block_device *blockdev = drive->blockdev;
+       struct int13_disk_address addr;
+       uint64_t lba;
+       unsigned long count;
+       userptr_t buffer;
+
+       /* Read parameters from disk address structure */
+       copy_from_real ( &addr, ix86->segs.ds, ix86->regs.si, sizeof ( addr ));
+       lba = addr.lba;
+       count = addr.count;
+       buffer = real_to_user ( addr.buffer.segment, addr.buffer.offset );
+
+       DBG ( "LBA %#llx <-> %04x:%04x (count %ld)\n", (unsigned long long)lba,
+             addr.buffer.segment, addr.buffer.offset, count );
+       
+       /* Read from / write to block device */
+       if ( io ( blockdev, lba, count, buffer ) != 0 )
+               return -INT13_STATUS_READ_ERROR;
+
+       return 0;
+}
+
 /**
  * INT 13, 42 - Extended read
  *
@@ -297,11 +273,8 @@ static int int13_extension_check ( struct int13_drive *drive __unused,
  */
 static int int13_extended_read ( struct int13_drive *drive,
                                 struct i386_all_regs *ix86 ) {
-       struct int13_disk_address addr;
-
-       copy_from_real ( &addr, ix86->segs.ds, ix86->regs.si,
-                        sizeof ( addr ) );
-       return int13_read ( drive, addr.lba, addr.buffer, addr.count );
+       DBG ( "Extended read: " );
+       return int13_extended_rw ( drive, ix86, drive->blockdev->read );
 }
 
 /**
@@ -313,11 +286,8 @@ static int int13_extended_read ( struct int13_drive *drive,
  */
 static int int13_extended_write ( struct int13_drive *drive,
                                  struct i386_all_regs *ix86 ) {
-       struct int13_disk_address addr;
-
-       copy_from_real ( &addr, ix86->segs.ds, ix86->regs.si,
-                        sizeof ( addr ) );
-       return int13_write ( drive, addr.lba, addr.buffer, addr.count );
+       DBG ( "Extended write: " );
+       return int13_extended_rw ( drive, ix86, drive->blockdev->write );
 }
 
 /**
@@ -347,22 +317,60 @@ static int int13_get_extended_parameters ( struct int13_drive *drive,
        return 0;
 }
 
+struct int13_cdrom_specification {
+       /** Size of packet in bytes */
+       uint8_t size;
+       /** Boot media type */
+       uint8_t media_type;
+       /** Drive number */
+       uint8_t drive;
+};
+
+/**
+ * INT 13, 4b - Get CD-ROM status / terminate emulation
+ *
+ * @v drive            Emulated drive
+ * @v ds:si            El Torito specification packet to fill in
+ * @ret status         Status code
+ */
+static int int13_cdrom_status_terminate ( struct int13_drive *drive,
+                                         struct i386_all_regs *ix86 ) {
+       struct int13_cdrom_specification specification;
+
+       DBG ( "Get CD-ROM emulation parameters to %04x:%04x\n",
+             ix86->segs.ds, ix86->regs.di );
+
+       memset ( &specification, 0, sizeof ( specification ) );
+       specification.size = sizeof ( specification );
+       specification.drive = drive->drive;
+
+       copy_to_real ( ix86->segs.ds, ix86->regs.si, &specification,
+                      sizeof ( specification ) );
+       return 0;
+}
+
 /**
  * INT 13 handler
  *
  */
 static void int13 ( struct i386_all_regs *ix86 ) {
+       int command = ix86->regs.ah;
+       unsigned int bios_drive = ix86->regs.dl;
+       unsigned int original_bios_drive = bios_drive;
        struct int13_drive *drive;
        int status;
 
        list_for_each_entry ( drive, &drives, list ) {
-               if ( drive->drive != ix86->regs.dl )
+               if ( drive->drive > bios_drive )
+                       continue;
+               if ( drive->drive < bios_drive ) {
+                       original_bios_drive--;
                        continue;
+               }
                
-               DBG ( "INT 13, %02x on drive %02x\n", ix86->regs.ah,
-                     ix86->regs.dl );
-       
-               switch ( ix86->regs.ah ) {
+               DBG ( "INT 13,%04x (%02x): ", ix86->regs.ax, drive->drive );
+
+               switch ( command ) {
                case INT13_RESET:
                        status = int13_reset ( drive, ix86 );
                        break;
@@ -378,6 +386,9 @@ static void int13 ( struct i386_all_regs *ix86 ) {
                case INT13_GET_PARAMETERS:
                        status = int13_get_parameters ( drive, ix86 );
                        break;
+               case INT13_GET_DISK_TYPE:
+                       status = int13_get_disk_type ( drive, ix86 );
+                       break;
                case INT13_EXTENSION_CHECK:
                        status = int13_extension_check ( drive, ix86 );
                        break;
@@ -390,23 +401,39 @@ static void int13 ( struct i386_all_regs *ix86 ) {
                case INT13_GET_EXTENDED_PARAMETERS:
                        status = int13_get_extended_parameters ( drive, ix86 );
                        break;
+               case INT13_CDROM_STATUS_TERMINATE:
+                       status = int13_cdrom_status_terminate ( drive, ix86 );
+                       break;
                default:
-                       DBG ( "Unrecognised INT 13\n" );
-                       status = INT13_STATUS_INVALID;
+                       DBG ( "*** Unrecognised INT 13 ***\n" );
+                       status = -INT13_STATUS_INVALID;
                        break;
                }
 
                /* Store status for INT 13,01 */
                drive->last_status = status;
-               /* All functions return status via %ah and CF */
-               ix86->regs.ah = status;
-               if ( status ) {
-                       ix86->flags |= CF;
+
+               /* Negative status indicates an error */
+               if ( status < 0 ) {
+                       status = -status;
                        DBG ( "INT13 failed with status %x\n", status );
+               } else {
+                       ix86->flags &= ~CF;
                }
+               ix86->regs.ah = status;
+
                /* Set OF to indicate to wrapper not to chain this call */
                ix86->flags |= OF;
+
+               break;
+       }
+
+       /* Remap BIOS drive */
+       if ( bios_drive != original_bios_drive ) {
+               DBG ( "INT 13,%04x (%02x) remapped to (%02x)\n",
+                     ix86->regs.ax, bios_drive, original_bios_drive );
        }
+       ix86->regs.dl = original_bios_drive;
 }
 
 /**
@@ -418,23 +445,44 @@ static void hook_int13 ( void ) {
         * should not chain to the previous handler.  (The wrapper
         * clears CF and OF before calling int13()).
         */
-       __asm__  __volatile__ ( ".section \".text16\", \"ax\", @progbits\n\t"
-                               ".code16\n\t"
-                               "\nint13_wrapper:\n\t"
-                               "orb $0, %%al\n\t" /* clear CF and OF */
-                               "pushl %0\n\t" /* call int13() */
-                               "data32 call prot_call\n\t"
-                               "jo 1f\n\t" /* chain if OF not set */
-                               "pushfw\n\t"
-                               "lcall *%%cs:int13_vector\n\t"
-                               "\n1:\n\t"
-                               "call 2f\n\t" /* return with flags intact */
-                               "lret $2\n\t"
-                               "\n2:\n\t"
-                               "ret $4\n\t"
-                               ".previous\n\t"
-                               ".code32\n\t" : :
-                               "i" ( int13 ) );
+       __asm__  __volatile__ (
+              TEXT16_CODE ( "\nint13_wrapper:\n\t"
+                            /* Preserve %ax and %dx for future reference */
+                            "pushw %%bp\n\t"
+                            "movw %%sp, %%bp\n\t"                           
+                            "pushw %%ax\n\t"
+                            "pushw %%dx\n\t"
+                            /* Clear OF, set CF, call int13() */
+                            "orb $0, %%al\n\t" 
+                            "stc\n\t"
+                            "pushl %0\n\t"
+                            "pushw %%cs\n\t"
+                            "call prot_call\n\t"
+                            /* Chain if OF not set */
+                            "jo 1f\n\t"
+                            "pushfw\n\t"
+                            "lcall *%%cs:int13_vector\n\t"
+                            "\n1:\n\t"
+                            /* Overwrite flags for iret */
+                            "pushfw\n\t"
+                            "popw 6(%%bp)\n\t"
+                            /* Restore %dl (except for %ah=0x08 or 0x15) */
+                            "cmpb $0x08, -1(%%bp)\n\t"
+
+                            "jne 7f\n\t"
+                            "movb $2, %%dl\n\t"
+                            "jmp 2f\n\t"
+                            "\n7:\n\t"
+
+                            "je 2f\n\t"
+                            "cmpb $0x15, -1(%%bp)\n\t"
+                            "je 2f\n\t"
+                            "movb -4(%%bp), %%dl\n\t"
+                            "\n2:\n\t"
+                            /* Return */
+                            "movw %%bp, %%sp\n\t"
+                            "popw %%bp\n\t"
+                            "iret\n\t" ) : : "i" ( int13 ) );
 
        hook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper,
                              &int13_vector );
@@ -449,26 +497,51 @@ static void unhook_int13 ( void ) {
 }
 
 /**
- * Register INT 13 emulated drive
+ * Guess INT 13 drive geometry
  *
  * @v drive            Emulated drive
  *
- * Registers the drive with the INT 13 emulation subsystem, and hooks
- * the INT 13 interrupt vector (if not already hooked).
- *
- * The underlying block device must be valid.  A drive number and
- * geometry will be assigned if left blank.
+ * Guesses the drive geometry by inspecting the partition table.
  */
-void register_int13_drive ( struct int13_drive *drive ) {
-       uint8_t num_drives;
+static void guess_int13_geometry ( struct int13_drive *drive ) {
+       struct master_boot_record mbr;
+       struct partition_table_entry *partition;
+       unsigned int guessed_heads = 255;
+       unsigned int guessed_sectors_per_track = 63;
        unsigned long blocks;
        unsigned long blocks_per_cyl;
+       unsigned int i;
 
-       /* Give drive a default geometry if none specified */
+       /* Don't even try when the blksize is invalid for C/H/S access */
+       if ( drive->blockdev->blksize != INT13_BLKSIZE )
+               return;
+
+       /* Scan through partition table and modify guesses for heads
+        * and sectors_per_track if we find any used partitions.
+        */
+       if ( drive->blockdev->read ( drive->blockdev, 0, 1,
+                                    virt_to_user ( &mbr ) ) == 0 ) {
+               for ( i = 0 ; i < 4 ; i++ ) {
+                       partition = &mbr.partitions[i];
+                       if ( ! partition->type )
+                               continue;
+                       guessed_heads =
+                               ( PART_HEAD ( partition->chs_end ) + 1 );
+                       guessed_sectors_per_track = 
+                               PART_SECTOR ( partition->chs_end );
+                       DBG ( "Guessing C/H/S xx/%d/%d based on partition "
+                             "%d\n", guessed_heads,
+                             guessed_sectors_per_track, ( i + 1 ) );
+               }
+       } else {
+               DBG ( "Could not read partition table to guess geometry\n" );
+       }
+
+       /* Apply guesses if no geometry already specified */
        if ( ! drive->heads )
-               drive->heads = 255;
+               drive->heads = guessed_heads;
        if ( ! drive->sectors_per_track )
-               drive->sectors_per_track = 63;
+               drive->sectors_per_track = guessed_sectors_per_track;
        if ( ! drive->cylinders ) {
                /* Avoid attempting a 64-bit divide on a 32-bit system */
                blocks = ( ( drive->blockdev->blocks <= ULONG_MAX ) ?
@@ -476,7 +549,27 @@ void register_int13_drive ( struct int13_drive *drive ) {
                blocks_per_cyl = ( drive->heads * drive->sectors_per_track );
                assert ( blocks_per_cyl != 0 );
                drive->cylinders = ( blocks / blocks_per_cyl );
+               if ( drive->cylinders > 1024 )
+                       drive->cylinders = 1024;
        }
+}
+
+/**
+ * Register INT 13 emulated drive
+ *
+ * @v drive            Emulated drive
+ *
+ * Registers the drive with the INT 13 emulation subsystem, and hooks
+ * the INT 13 interrupt vector (if not already hooked).
+ *
+ * The underlying block device must be valid.  A drive number and
+ * geometry will be assigned if left blank.
+ */
+void register_int13_drive ( struct int13_drive *drive ) {
+       uint8_t num_drives;
+
+       /* Give drive a default geometry if none specified */
+       guess_int13_geometry ( drive );
 
        /* Assign drive number if none specified, update BIOS drive count */
        get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
@@ -484,6 +577,9 @@ void register_int13_drive ( struct int13_drive *drive ) {
                drive->drive = ( num_drives | 0x80 );
        if ( num_drives <= ( drive->drive & 0x7f ) )
                num_drives = ( ( drive->drive & 0x7f ) + 1 );
+
+       num_drives = 2;
+
        put_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
 
        DBG ( "Registered INT13 drive %02x with C/H/S geometry %d/%d/%d\n",
@@ -533,25 +629,27 @@ void unregister_int13_drive ( struct int13_drive *drive ) {
  */
 int int13_boot ( unsigned int drive ) {
        int status, signature;
-       int d0, d1;
+       int discard_c, discard_d;
+       int rc;
 
        DBG ( "Booting from INT 13 drive %02x\n", drive );
 
        /* Use INT 13 to read the boot sector */
-       REAL_EXEC ( rm_int13_boot,
-                   "pushw $0\n\t"
-                   "popw %%es\n\t"
-                   "int $0x13\n\t"
-                   "jc 1f\n\t"
-                   "xorl %%eax, %%eax\n\t"
-                   "\n1:\n\t"
-                   "movzwl %%es:0x7dfe, %%ebx\n\t",
-                   4,
-                   OUT_CONSTRAINTS ( "=a" ( status ), "=b" ( signature ),
-                                     "=c" ( d0 ), "=d" ( drive ) ),
-                   IN_CONSTRAINTS ( "0" ( 0x0201 ), "1" ( 0x7c00 ),
-                                    "2" ( 0x0001 ), "3" ( drive ) ),
-                   CLOBBER ( "ebp" ) );
+       __asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t"
+                                          "pushw $0\n\t"
+                                          "popw %%es\n\t"
+                                          "stc\n\t"
+                                          "int $0x13\n\t"
+                                          "sti\n\t" /* BIOS bugs */
+                                          "jc 1f\n\t"
+                                          "xorl %%eax, %%eax\n\t"
+                                          "\n1:\n\t"
+                                          "movzwl %%es:0x7dfe, %%ebx\n\t"
+                                          "popw %%es\n\t" )
+                              : "=a" ( status ), "=b" ( signature ),
+                                "=c" ( discard_c ), "=d" ( discard_d )
+                              : "a" ( 0x0201 ), "b" ( 0x7c00 ),
+                                "c" ( 1 ), "d" ( drive ) );
        if ( status )
                return -EIO;
 
@@ -562,37 +660,11 @@ int int13_boot ( unsigned int drive ) {
                return -ENOEXEC;
        }
 
-       /* Hook INTs 18 and 19 to capture failure paths */
-       hook_bios_interrupt ( 0x18, ( unsigned int ) int13_exec_fail,
-                             &int18_vector );
-       hook_bios_interrupt ( 0x19, ( unsigned int ) int13_exec_fail,
-                             &int19_vector );
-
-       /* Boot the loaded sector */
-       REAL_EXEC ( rm_int13_exec,
-                   "movw %%ss, %%ax\n\t" /* Preserve stack pointer */
-                   "movw %%ax, %%cs:int13_exec_saved_ss\n\t"
-                   "movw %%sp, %%cs:int13_exec_saved_sp\n\t"
-                   "ljmp $0, $0x7c00\n\t"
-                   "\nint13_exec_saved_ss: .word 0\n\t"
-                   "\nint13_exec_saved_sp: .word 0\n\t"
-                   "\nint13_exec_fail:\n\t"
-                   "movw %%cs:int13_exec_saved_ss, %%ax\n\t"
-                   "movw %%ax, %%ss\n\t"
-                   "movw %%cs:int13_exec_saved_sp, %%sp\n\t"
-                   "\n99:\n\t",
-                   1,
-                   OUT_CONSTRAINTS ( "=d" ( d1 ) ),
-                   IN_CONSTRAINTS ( "0" ( drive ) ),
-                   CLOBBER ( "eax", "ebx", "ecx", "esi", "edi", "ebp" ) );
-
-       DBG ( "Booted disk returned via INT 18 or 19\n" );
-
-       /* Unhook INTs 18 and 19 */
-       unhook_bios_interrupt ( 0x18, ( unsigned int ) int13_exec_fail,
-                               &int18_vector );
-       unhook_bios_interrupt ( 0x19, ( unsigned int ) int13_exec_fail,
-                               &int19_vector );
-       
-       return -ECANCELED;
+       /* Jump to boot sector */
+       if ( ( rc = call_bootsector ( 0x0, 0x7c00, drive ) ) != 0 ) {
+               DBG ( "INT 13 drive %02x boot returned\n", drive );
+               return rc;
+       }
+
+       return -ECANCELED; /* -EIMPOSSIBLE */
 }