mirror of
https://github.com/ipxe/ipxe
synced 2025-12-28 10:32:52 +03:00
[comboot] Run com32 programs with a valid IDT
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 iPXE, 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>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
committed by
Michael Brown
parent
315524e703
commit
a4023e02e8
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user