diff --git a/src/arch/i386/image/com32.c b/src/arch/i386/image/com32.c index 25a77e93a..381987ab9 100644 --- a/src/arch/i386/image/com32.c +++ b/src/arch/i386/image/com32.c @@ -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; + int rc, i; - filesz = image->len; + /* 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; diff --git a/src/arch/i386/include/comboot.h b/src/arch/i386/include/comboot.h index 99ce79e20..39d1d2f17 100644 --- a/src/arch/i386/include/comboot.h +++ b/src/arch/i386/include/comboot.h @@ -13,6 +13,50 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include +/** 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 ); diff --git a/src/arch/i386/interface/syslinux/com32_call.c b/src/arch/i386/interface/syslinux/com32_call.c index 7a7583734..47df64cb9 100644 --- a/src/arch/i386/interface/syslinux/com32_call.c +++ b/src/arch/i386/interface/syslinux/com32_call.c @@ -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 ) ); +} diff --git a/src/arch/i386/interface/syslinux/com32_wrapper.S b/src/arch/i386/interface/syslinux/com32_wrapper.S index 0ac6ff2d6..4cd882215 100644 --- a/src/arch/i386/interface/syslinux/com32_wrapper.S +++ b/src/arch/i386/interface/syslinux/com32_wrapper.S @@ -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