Proof of concept; works, but has several hard-coded hacks.
[people/xl0/gpxe-arm.git] / src / arch / i386 / interface / pcbios / int13.c
index 798fb6d..a4563a0 100644 (file)
@@ -26,6 +26,7 @@
 #include <realmode.h>
 #include <bios.h>
 #include <biosint.h>
+#include <bootsector.h>
 #include <int13.h>
 
 /** @file
@@ -44,23 +45,6 @@ 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 );
 
@@ -114,6 +98,13 @@ static int int13_rw_sectors ( struct int13_drive *drive,
        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 );
@@ -129,13 +120,6 @@ static int int13_rw_sectors ( struct int13_drive *drive,
        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 );
 
-       /* Validate blocksize */
-       if ( blockdev->blksize != INT13_BLKSIZE ) {
-               DBG ( "Invalid blocksize (%zd) for non-extended read/write\n",
-                     blockdev->blksize );
-               return -INT13_STATUS_INVALID;
-       }
-       
        /* Read from / write to block device */
        if ( io ( blockdev, lba, count, buffer ) != 0 )
                return -INT13_STATUS_READ_ERROR;
@@ -333,20 +317,58 @@ 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 (%02x): ", command, drive->drive );
+               DBG ( "INT 13,%04x (%02x): ", ix86->regs.ax, drive->drive );
 
                switch ( command ) {
                case INT13_RESET:
@@ -379,6 +401,9 @@ 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;
@@ -390,15 +415,25 @@ static void int13 ( struct i386_all_regs *ix86 ) {
 
                /* Negative status indicates an error */
                if ( status < 0 ) {
-                       ix86->flags |= CF;
                        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;
 }
 
 /**
@@ -412,18 +447,42 @@ static void hook_int13 ( void ) {
         */
        __asm__  __volatile__ (
               TEXT16_CODE ( "\nint13_wrapper:\n\t"
-                            "orb $0, %%al\n\t" /* clear CF and OF */
-                            "pushl %0\n\t" /* call int13() */
+                            /* 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"
-                            "jo 1f\n\t" /* chain if OF not set */
+                            /* Chain if OF not set */
+                            "jo 1f\n\t"
                             "pushfw\n\t"
                             "lcall *%%cs:int13_vector\n\t"
                             "\n1:\n\t"
-                            "call 2f\n\t" /* return with flags intact */
-                            "lret $2\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"
-                            "ret $4\n\t" ) : : "i" ( int13 ) );
+                            /* 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 );
@@ -453,6 +512,10 @@ static void guess_int13_geometry ( struct int13_drive *drive ) {
        unsigned long blocks_per_cyl;
        unsigned int i;
 
+       /* 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.
         */
@@ -514,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",
@@ -564,6 +630,7 @@ void unregister_int13_drive ( struct int13_drive *drive ) {
 int int13_boot ( unsigned int drive ) {
        int status, signature;
        int discard_c, discard_d;
+       int rc;
 
        DBG ( "Booting from INT 13 drive %02x\n", drive );
 
@@ -593,43 +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 */
-       __asm__ __volatile__ ( REAL_CODE ( /* Save segment registers */
-                                          "pushw %%ds\n\t"
-                                          "pushw %%es\n\t"
-                                          "pushw %%fs\n\t"
-                                          "pushw %%gs\n\t"
-                                          /* Save stack pointer */
-                                          "movw %%ss, %%ax\n\t"
-                                          "movw %%ax, %%cs:int13_saved_ss\n\t"
-                                          "movw %%sp, %%cs:int13_saved_sp\n\t"
-                                          "ljmp $0, $0x7c00\n\t"
-                                          "\nint13_saved_ss: .word 0\n\t"
-                                          "\nint13_saved_sp: .word 0\n\t"
-                                          "\nint13_exec_fail:\n\t"
-                                          "movw %%cs:int13_saved_ss, %%ax\n\t"
-                                          "movw %%ax, %%ss\n\t"
-                                          "movw %%cs:int13_saved_sp, %%sp\n\t"
-                                          "popw %%gs\n\t"
-                                          "popw %%fs\n\t"
-                                          "popw %%es\n\t"
-                                          "popw %%ds\n\t" )
-                              : "=d" ( discard_d ) : "d" ( drive )
-                              : "eax", "ebx", "ecx", "esi", "edi", "ebp" );
-
-       DBG ( "Booted disk returned via INT 18 or 19\n" );
+       /* Jump to boot sector */
+       if ( ( rc = call_bootsector ( 0x0, 0x7c00, drive ) ) != 0 ) {
+               DBG ( "INT 13 drive %02x boot returned\n", drive );
+               return rc;
+       }
 
-       /* 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;
+       return -ECANCELED; /* -EIMPOSSIBLE */
 }