[comboot] Run com32 programs with a valid IDT
authorGeoff Lywood <glywood@vmware.com>
Wed, 7 Jul 2010 22:35:01 +0000 (15:35 -0700)
committerStefan Hajnoczi <stefanha@gmail.com>
Sun, 1 Aug 2010 11:56:51 +0000 (12:56 +0100)
COM32 binaries generally expect to run with interrupts enabled. Syslinux does
so, and COM32 programs will execute cli/sti pairs when running a critical
section, to provide mutual exclusion against BIOS interrupt handlers.
Previously, under gPXE, the IDT was not valid, so any interrupt (e.g. a timer
tick) would generally cause the machine to triple fault.

This change introduces code to:
- Create a valid IDT at the same location that syslinux uses
- Create an "interrupt jump buffer", which contains small pieces of code that
  simply record the vector number and jump to a common handler
- Thunk down to real mode and execute the BIOS's interrupt handler whenever
  an interrupt is received in a COM32 program
- Switch IDTs and enable/disable interrupts when context switching to and from
  COM32 binaries

Testing done:
- Booted VMware ESX using a COM32 multiboot loader (mboot.c32)
- Built with GDBSERIAL enabled, and tested breakpoints on int22 and com32_irq
- Put the following code in a COM32 program:
    asm volatile ( "sti" );
    while ( 1 );
  Before this change, the machine would triple fault immediately. After this
  change, it hangs as expected. Under Bochs, it is possible to see the
  interrupt handler run, and the current time in the BIOS data area gets
  incremented.

Signed-off-by: Stefan Hajnoczi <stefanha@gmail.com>
src/arch/i386/image/com32.c
src/arch/i386/include/comboot.h
src/arch/i386/interface/syslinux/com32_call.c
src/arch/i386/interface/syslinux/com32_wrapper.S

index 6ab347c..5e65c0a 100644 (file)
@@ -42,6 +42,13 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 struct image_type com32_image_type __image_type ( PROBE_NORMAL );
 
+struct idt_register com32_external_idtr = {
+       .limit = COM32_NUM_IDT_ENTRIES * sizeof ( struct idt_descriptor ) - 1,
+       .base = COM32_IDT
+};
+
+struct idt_register com32_internal_idtr;
+
 /**
  * Execute COMBOOT image
  *
@@ -89,9 +96,12 @@ static int com32_exec ( struct image *image ) {
                unregister_image ( image );
 
                __asm__ __volatile__ (
+                       "sidt com32_internal_idtr\n\t"
+                       "lidt com32_external_idtr\n\t"         /* Set up IDT */
                        "movl %%esp, (com32_internal_esp)\n\t" /* Save internal virtual address space ESP */
                        "movl (com32_external_esp), %%esp\n\t" /* Switch to COM32 ESP (top of available memory) */
                        "call _virt_to_phys\n\t"               /* Switch to flat physical address space */
+                       "sti\n\t"                              /* Enable interrupts */
                        "pushl %0\n\t"                         /* Pointer to CDECL helper function */
                        "pushl %1\n\t"                         /* Pointer to FAR call helper function */
                        "pushl %2\n\t"                         /* Size of low memory bounce buffer */
@@ -100,7 +110,9 @@ static int com32_exec ( struct image *image ) {
                        "pushl %5\n\t"                         /* Pointer to the command line arguments */
                        "pushl $6\n\t"                         /* Number of additional arguments */
                        "call *%6\n\t"                         /* Execute image */
-                       "call _phys_to_virt\n\t"               /* Switch back to internal virtual address space */
+                       "cli\n\t"                              /* Disable interrupts */
+                       "call _phys_to_virt\n\t"               /* Switch back to internal virtual address space */
+                       "lidt com32_internal_idtr\n\t"         /* Switch back to internal IDT (for debugging) */
                        "movl (com32_internal_esp), %%esp\n\t" /* Switch back to internal stack */
                :
                :
@@ -191,25 +203,55 @@ static int com32_identify ( struct image *image ) {
 
 
 /**
- * Load COM32 image into memory
+ * Load COM32 image into memory and set up the IDT
  * @v image            COM32 image
  * @ret rc             Return status code
  */
 static int comboot_load_image ( struct image *image ) {
+       physaddr_t com32_irq_wrapper_phys;
+       struct idt_descriptor *idt;
+       struct ijb_entry *ijb;
        size_t filesz, memsz;
        userptr_t buffer;
-       int rc;
-
-       filesz = image->len;
+       int rc, i;
+
+       /* The interrupt descriptor table, interrupt jump buffer, and
+        * image data are all contiguous in memory. Prepare them all at once.
+        */
+       filesz = image->len +
+               COM32_NUM_IDT_ENTRIES * sizeof ( struct idt_descriptor ) +
+               COM32_NUM_IDT_ENTRIES * sizeof ( struct ijb_entry );
        memsz = filesz;
-       buffer = phys_to_user ( COM32_START_PHYS );
+       buffer = phys_to_user ( COM32_IDT );
        if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) {
                DBGC ( image, "COM32 %p: could not prepare segment: %s\n",
                       image, strerror ( rc ) );
                return rc;
        }
 
+       /* Write the IDT and IJB */
+       idt = phys_to_virt ( COM32_IDT );
+       ijb = phys_to_virt ( COM32_IJB );
+       com32_irq_wrapper_phys = virt_to_phys ( com32_irq_wrapper );
+
+       for ( i = 0; i < COM32_NUM_IDT_ENTRIES; i++ ) {
+               uint32_t ijb_address = virt_to_phys ( &ijb[i] );
+
+               idt[i].offset_low = ijb_address & 0xFFFF;
+               idt[i].selector = PHYSICAL_CS;
+               idt[i].flags = IDT_INTERRUPT_GATE_FLAGS;
+               idt[i].offset_high = ijb_address >> 16;
+
+               ijb[i].pusha_instruction = IJB_PUSHA;
+               ijb[i].mov_instruction = IJB_MOV_AL_IMM8;
+               ijb[i].mov_value = i;
+               ijb[i].jump_instruction = IJB_JMP_REL32;
+               ijb[i].jump_destination = com32_irq_wrapper_phys -
+                       virt_to_phys ( &ijb[i + 1] );
+       }
+
        /* Copy image to segment */
+       buffer = phys_to_user ( COM32_START_PHYS );
        memcpy_user ( buffer, 0, image->data, 0, filesz );
 
        return 0;
index 1232f0a..c4761bf 100644 (file)
@@ -13,6 +13,50 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <setjmp.h>
 #include <gpxe/in.h>
 
+/** Descriptor in a 32-bit IDT */
+struct idt_descriptor {
+       uint16_t offset_low;
+       uint16_t selector;
+       uint16_t flags;
+       uint16_t offset_high;
+} __attribute__ (( packed ));
+
+/** Operand for the LIDT instruction */
+struct idt_register {
+       uint16_t limit;
+       uint32_t base;
+} __attribute__ (( packed ));
+
+/** Entry in the interrupt jump buffer */
+struct ijb_entry {
+       uint8_t pusha_instruction;
+       uint8_t mov_instruction;
+       uint8_t mov_value;
+       uint8_t jump_instruction;
+       uint32_t jump_destination;
+} __attribute__ (( packed ));
+
+/** The x86 opcode for "pushal" */
+#define IJB_PUSHA 0x60
+
+/** The x86 opcode for "movb $imm8,%al" */
+#define IJB_MOV_AL_IMM8 0xB0
+
+/** The x86 opcode for "jmp rel32" */
+#define IJB_JMP_REL32 0xE9
+
+/** Flags that specify a 32-bit interrupt gate with DPL=0 */
+#define IDT_INTERRUPT_GATE_FLAGS 0x8E00
+
+/** Address of COM32 interrupt descriptor table */
+#define COM32_IDT 0x100000
+
+/** Number of entries in a fully populated IDT */
+#define COM32_NUM_IDT_ENTRIES 256
+
+/** Address of COM32 interrupt jump buffer */
+#define COM32_IJB 0x100800
+
 /** Segment used for COMBOOT PSP and image */
 #define COMBOOT_PSP_SEG 0x07C0
 
@@ -109,6 +153,7 @@ extern void unhook_comboot_interrupts ( );
 extern void com32_intcall_wrapper ( );
 extern void com32_farcall_wrapper ( );
 extern void com32_cfarcall_wrapper ( );
+extern void com32_irq_wrapper ( );
 
 /* Resolve a hostname to an (IPv4) address */
 extern int comboot_resolv ( const char *name, struct in_addr *address );
index d2c3f91..462f24e 100644 (file)
@@ -188,3 +188,20 @@ int __asmcall com32_cfarcall ( uint32_t proc, physaddr_t stack, size_t stacksz )
 
        return eax;
 }
+
+/**
+ * IRQ handler
+ */
+void __asmcall com32_irq ( uint32_t vector ) {
+       uint32_t *ivt_entry = phys_to_virt( vector * 4 );
+
+       __asm__ __volatile__ (
+               REAL_CODE ( "pushfw\n\t"
+                           "pushw %%cs\n\t"
+                           "pushw $com32_irq_return\n\t"
+                           "pushl %0\n\t"
+                           "lret\n"
+                           "com32_irq_return:\n\t" )
+               : /* no outputs */
+               : "r" ( *ivt_entry ) );
+}
index 5c5bd13..f21ace9 100644 (file)
@@ -22,6 +22,26 @@ FILE_LICENCE ( GPL2_OR_LATER )
        .arch i386
        .code32
 
+       /*
+        * This code is entered after running the following sequence out of
+        * the interrupt jump buffer:
+        *
+        * pushal
+        * movb $vector, %al
+        * jmp com32_irq_wrapper
+        */
+
+       .globl com32_irq_wrapper
+com32_irq_wrapper:
+
+       movzbl %al,%eax
+       pushl %eax
+       movl $com32_irq, %eax
+       call com32_wrapper
+       popl %eax
+       popal
+       iret
+
        .globl com32_farcall_wrapper
 com32_farcall_wrapper:
 
@@ -43,10 +63,14 @@ com32_intcall_wrapper:
        /*jmp com32_wrapper*/ /* fall through */
 
 com32_wrapper:
+       cli
 
        /* Switch to internal virtual address space */
        call _phys_to_virt
 
+       /* Switch to internal IDT (if we have one for debugging) */
+       lidt com32_internal_idtr
+
        mov %eax, (com32_helper_function)
 
        /* Save external COM32 stack pointer */
@@ -74,9 +98,13 @@ com32_wrapper:
        movl %esp, (com32_internal_esp)
        movl (com32_external_esp), %esp
 
+       /* Switch to com32 IDT */
+       lidt com32_external_idtr
+
        /* Switch to external flat physical address space */
        call _virt_to_phys
 
+       sti
        ret