diff options
author | Michael Brown <mcb30@etherboot.org> | 2005-04-08 15:01:17 +0000 |
---|---|---|
committer | Michael Brown <mcb30@etherboot.org> | 2005-04-08 15:01:17 +0000 |
commit | 0ff80b477dcff0726ebdbed95e8a93971e59e82b (patch) | |
tree | 860b7150212a07c24a9529ea072f3fb12700974c /src/arch/i386/transitions | |
parent | de5d935135bd536666b5fd03445b04d2ea82f1b4 (diff) | |
download | ipxe-0ff80b477dcff0726ebdbed95e8a93971e59e82b.tar.gz |
Merged mcb30-realmode-redesign back to HEAD
Diffstat (limited to 'src/arch/i386/transitions')
-rw-r--r-- | src/arch/i386/transitions/libkir.S | 243 | ||||
-rw-r--r-- | src/arch/i386/transitions/libpm.S | 0 | ||||
-rw-r--r-- | src/arch/i386/transitions/librm.S | 691 | ||||
-rw-r--r-- | src/arch/i386/transitions/librm_mgmt.c | 87 |
4 files changed, 1021 insertions, 0 deletions
diff --git a/src/arch/i386/transitions/libkir.S b/src/arch/i386/transitions/libkir.S new file mode 100644 index 00000000..79a0aa00 --- /dev/null +++ b/src/arch/i386/transitions/libkir.S @@ -0,0 +1,243 @@ +/* + * libkir: a transition library for -DKEEP_IT_REAL + * + * Michael Brown <mbrown@fensystems.co.uk> + * + */ + +/**************************************************************************** + * This file defines libkir: an interface between external and + * internal environments when -DKEEP_IT_REAL is used, so that both + * internal and external environments are in real mode. It deals with + * switching data segments and the stack. It provides the following + * functions: + * + * ext_to_kir & switch between external and internal (kir) + * kir_to_ext environments, preserving all non-segment + * registers + * + * kir_call issue a call to an internal routine from external + * code + * + * libkir is written to avoid assuming that segments are anything + * other than opaque data types, and also avoids assuming that the + * stack pointer is 16-bit. This should enable it to run just as well + * in 16:16 or 16:32 protected mode as in real mode. + **************************************************************************** + */ + +/* Breakpoint for when debugging under bochs */ +#define BOCHSBP xchgw %bx, %bx + + .text + .arch i386 + .section ".text16", "awx", @progbits + .code16 + +/**************************************************************************** + * ext_to_kir (real-mode or 16:xx protected-mode near call) + * + * Switch from external stack and segment registers to internal stack + * and segment registers. %ss:sp is restored from the saved kir_ds + * and kir_sp. %ds, %es, %fs and %gs are all restored from the saved + * kir_ds. All other registers are preserved. + * + * %cs:0000 must point to the start of the runtime image code segment + * on entry. + * + * Note that this routine can be called *without* having first set up + * a stored kir_ds and kir_sp. If you do this, ext_to_kir will return + * without altering the segment registers or stack pointer. + * + * Parameters: none + **************************************************************************** + */ + + .globl ext_to_kir +ext_to_kir: + /* Record external segment registers */ + movw %ds, %cs:ext_ds + pushw %cs + popw %ds /* Set %ds = %cs for easier access to variables */ + movw %es, %ds:ext_es + movw %fs, %ds:ext_fs + movw %gs, %ds:ext_fs + + /* Preserve registers */ + movw %ax, %ds:save_ax + + /* Extract near return address from stack */ + popw %ds:save_retaddr + + /* Record external %ss:esp */ + movw %ss, %ds:ext_ss + movl %esp, %ds:ext_esp + + /* Load internal segment registers and stack pointer, if available */ + movw %ds:kir_ds, %ax + testw %ax, %ax + jz 1f + movw %ax, %ss + movzwl %ds:kir_sp, %esp + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs +1: + + /* Place return address on new stack */ + pushw %cs:save_retaddr + + /* Restore registers and return */ + movw %cs:save_ax, %ax + ret + +/**************************************************************************** + * kir_to_ext (real-mode or 16:xx protected-mode near call) + * + * Switch from internal stack and segment registers to external stack + * and segment registers. %ss:%esp is restored from the saved ext_ss + * and ext_esp. Other segment registers are restored from the + * corresponding locations. All other registers are preserved. + * + * Note that it is actually %ss that is recorded as kir_ds, on the + * assumption that %ss == %ds when kir_to_ext is called. + * + * Parameters: none + **************************************************************************** + */ + + .globl kir_to_ext +kir_to_ext: + /* Record near return address */ + pushw %cs + popw %ds /* Set %ds = %cs for easier access to variables */ + popw %ds:save_retaddr + + /* Record internal segment registers and %sp */ + movw %ss, %ds:kir_ds + movw %sp, %ds:kir_sp + + /* Load external segment registers and stack pointer */ + movw %ds:ext_ss, %ss + movl %ds:ext_esp, %esp + movw %ds:ext_gs, %gs + movw %ds:ext_fs, %fs + movw %ds:ext_es, %es + movw %ds:ext_ds, %ds + + /* Return */ + pushw %cs:save_retaddr + ret + +/**************************************************************************** + * kir_call (real-mode or 16:xx protected-mode far call) + * + * Call a specific C function in the internal code. The prototype of + * the C function must be + * void function ( struct real_mode_regs *rm_regs ); + * rm_regs will point to a struct containing the real-mode registers + * at entry to kir_call. + * + * All registers will be preserved across kir_call(), unless the C + * function explicitly overwrites values in rm_regs. Interrupt status + * will also be preserved. + * + * Parameters: + * function : (16-bit) virtual address of protected-mode function to call + * + * Example usage: + * pushw $pxe_api_call + * lcall $UNDI_CS, $kir_call + * addw $2, %sp + * to call in to the C function + * void pxe_api_call ( struct real_mode_regs *rm_regs ); + **************************************************************************** + */ + + .globl kir_call +kir_call: + + /* Preserve flags. Must do this before any operation that may + * affect flags. + */ + pushfl + popl %cs:save_flags + + /* Disable interrupts. We do funny things with the stack, and + * we're not re-entrant. + */ + cli + + /* Extract address of internal routine from stack. We must do + * this without using (%bp), because we may be called with + * either a 16-bit or a 32-bit stack segment. + */ + popl %cs:save_retaddr /* Scratch location */ + popw %cs:save_function + subl $6, %esp /* Restore %esp */ + + /* Switch to internal stack. Note that the external stack is + * inaccessible once we're running internally (since we have + * no concept of 48-bit far pointers) + */ + call ext_to_kir + + /* Store external registers on internal stack */ + pushl %cs:save_flags + pushal + pushl %cs:ext_fs_and_gs + pushl %cs:ext_ds_and_es + pushl %cs:ext_cs_and_ss + + /* Push &rm_regs on stack and call function */ + pushl %esp + data32 call *%cs:save_function + popl %eax /* discard */ + + /* Restore external registers from internal stack */ + popl %cs:ext_cs_and_ss + popl %cs:ext_ds_and_es + popl %cs:ext_fs_and_gs + popal + popl %cs:save_flags + + /* Switch to external stack */ + call kir_to_ext + + /* Restore flags */ + pushl %cs:save_flags + popfl + + /* Return */ + lret + +/**************************************************************************** + * Stored internal and external stack and segment registers + **************************************************************************** + */ + +ext_cs_and_ss: +ext_cs: .word 0 +ext_ss: .word 0 +ext_ds_and_es: +ext_ds: .word 0 +ext_es: .word 0 +ext_fs_and_gs: +ext_fs: .word 0 +ext_gs: .word 0 +ext_esp: .long 0 + + .globl kir_ds +kir_ds: .word 0 + .globl kir_sp +kir_sp: .word 0 + +/**************************************************************************** + * Temporary variables + **************************************************************************** + */ +save_ax: .word 0 +save_retaddr: .word 0 +save_flags: .long 0 +save_function: .long 0 diff --git a/src/arch/i386/transitions/libpm.S b/src/arch/i386/transitions/libpm.S new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/arch/i386/transitions/libpm.S diff --git a/src/arch/i386/transitions/librm.S b/src/arch/i386/transitions/librm.S new file mode 100644 index 00000000..6dc6b4fd --- /dev/null +++ b/src/arch/i386/transitions/librm.S @@ -0,0 +1,691 @@ +/* + * librm: a library for interfacing to real-mode code + * + * Michael Brown <mbrown@fensystems.co.uk> + * + */ + +/* Drag in local definitions */ +#include "librm.h" + +/**************************************************************************** + * This file defines librm: a block of code that is designed to reside + * permanently in base memory and provide the interface between + * real-mode code running in base memory and protected-mode code + * running in high memory. It provides the following functions: + * + * real_to_prot & switch between real and protected mode + * prot_to_real while running in base memory, preserving + * all non-segment registers + * + * real_call issue a call to a real-mode routine from + * protected-mode code running in high memory + * + * prot_call issue a call to a protected-mode routine from + * real-mode code running in base memory + * + * librm requires the following functions to be present in the + * protected-mode code: + * + * _phys_to_virt Switch from physical to virtual addressing. This + * routine must be position-independent and must + * *not* assume that it is genuinely running with + * flat physical addresses + * + * _virt_to_phys Switch from virtual to physical addresses. + * + * gateA20_set Enable the A20 line to permit access to the odd + * megabytes of RAM. (This function will be called + * with virtual addresses set up). + * + * librm needs to be linked against the protected-mode binary so that + * it can import the symbols for these functions. + * + * librm requires that the protected-mode code set up the following + * segments: + * + * PHYSICAL_CS 32-bit pmode code and data segments with flat + * PHYSICAL_DS physical addresses. + * + * VIRTUAL_CS 32-bit pmode code segment with virtual + * addressing, such that a protected-mode routine + * can always be found at $VIRTUAL_CS:routine. + * + * These segments must be set as #define constants when compiling + * librm. Edit librm.h to change the values. + * + * librm does not know the location of the code executing in high + * memory. It relies on the code running in high memory setting up a + * GDT such that the high-memory code is accessible at virtual + * addresses fixed at compile-time. + * + * librm symbols are exported as absolute values and represent offsets + * into librm. This is the most useful form of the symbols, since + * librm is basically a binary blob that you place somewhere in base + * memory. + * + * librm.h provides convenient ways to use these symbols: you simply + * set the pointer ( char * ) installed_librm to point to wherever + * librm is installed, and can then use e.g. inst_rm_stack just like + * any other variable and have it automatically refer to the value of + * rm_stack in the installed librm. Macro trickery makes this + * completely transparent, and the resulting assembler code is + * amazingly efficient. + * + * Note that librm must be called in genuine real mode, not 16:16 or + * 16:32 protected mode. It makes the assumption that + * physical_address = 16*segment+offset, and also that it can use + * OFFSET(%bp) to access stack variables. The former assumption will + * break in either protected mode, the latter may break in 16:32 + * protected mode. + **************************************************************************** + */ + +/* + * Default values for pmode segments if not defined + */ +#ifndef PHYSICAL_CS +#warning "Assuming PHYSICAL_CS = 0x08" +#define PHYSICAL_CS 0x08 +#endif +#ifndef PHYSICAL_DS +#warning "Assuming PHYSICAL_DS = 0x10" +#define PHYSICAL_DS 0x10 +#endif +#ifndef VIRTUAL_CS +#warning "Assuming VIRTUAL_CS = 0x18" +#define VIRTUAL_CS 0x18 +#endif + +/* For switches to/from protected mode */ +#define CR0_PE 1 + +/* Size of various C data structures */ +#define SIZEOF_I386_SEG_REGS 12 +#define SIZEOF_I386_REGS 32 +#define SIZEOF_I386_ALL_REGS ( SIZEOF_I386_SEG_REGS + SIZEOF_I386_REGS ) +#define SIZEOF_I386_FLAGS 4 +#define SIZEOF_REAL_MODE_REGS ( SIZEOF_I386_ALL_REGS + SIZEOF_I386_FLAGS ) +#define SIZEOF_SEGOFF_T 4 +#define SIZEOF_REAL_CALL_PARAMS ( SIZEOF_I386_ALL_REGS + 2 * SIZEOF_SEGOFF_T ) + + .text + .arch i386 + .section ".librm", "awx", @progbits + .align 16 + + .globl librm +librm: + +_librm_start: + +#undef OFFSET +#define OFFSET(sym) ( sym - _librm_start ) + +#undef EXPORT +#define EXPORT(sym) \ + .globl sym ; \ + .globl _ ## sym ; \ + .equ _ ## sym, OFFSET(sym) ; \ + sym + +/**************************************************************************** + * GDT for initial transition to protected mode + * + * PHYSICAL_CS and PHYSICAL_DS are defined in an external header file. + * We use only those selectors, and construct our GDT to match the + * selector values we're asked to use. Use PHYSICAL_CS=0x08 and + * PHYSICAL_DS=0x10 to minimise the space occupied by this GDT. + * + * Note: pm_gdt is also used to store the location of the + * protected-mode GDT as recorded on entry to prot_to_real. + **************************************************************************** + */ + .align 16 +pm_gdt: +pm_gdt_limit: .word pm_gdt_length - 1 +pm_gdt_addr: .long 0 + .word 0 /* padding */ + + .org pm_gdt + PHYSICAL_CS +pm_gdt_pm_cs: + /* 32 bit protected mode code segment, physical addresses */ + .word 0xffff, 0 + .byte 0, 0x9f, 0xcf, 0 + + .org pm_gdt + PHYSICAL_DS +pm_gdt_pm_ds: + /* 32 bit protected mode data segment, physical addresses */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 + +pm_gdt_end: + .equ pm_gdt_length, pm_gdt_end - pm_gdt + +/**************************************************************************** + * GDT for transition to real mode + * + * This is used primarily to set 64kB segment limits. Define + * FLATTEN_REAL_MODE if you want to use so-called "flat real mode" + * with 4GB limits instead. The base address of each of the segments + * will be adjusted at run-time. + * + * NOTE: This must be located before prot_to_real, otherwise gas + * throws a "can't handle non absolute segment in `ljmp'" error due to + * not knowing the value of RM_CS when the ljmp is encountered. + * + * Note also that putting ".word rm_gdt_end - rm_gdt - 1" directly + * into rm_gdt_limit, rather than going via rm_gdt_length, will also + * produce the "non absolute segment" error. This is most probably a + * bug in gas. + **************************************************************************** + */ + +#ifdef FLATTEN_REAL_MODE +#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x8f +#else +#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x00 +#endif + .align 16 +rm_gdt: +rm_gdt_limit: .word rm_gdt_length - 1 +rm_gdt_base: .long 0 + .word 0 /* padding */ + +rm_gdt_rm_cs: /* 16 bit real mode code segment */ + .equ RM_CS, rm_gdt_rm_cs - rm_gdt + .word 0xffff,(0&0xffff) + .byte (0>>16),0x9b,RM_LIMIT_16_19__AVL__SIZE__GRANULARITY,(0>>24) + +rm_gdt_rm_ds: /* 16 bit real mode data segment */ + .equ RM_DS, rm_gdt_rm_ds - rm_gdt + .word 0xffff,(0&0xffff) + .byte (0>>16),0x93,RM_LIMIT_16_19__AVL__SIZE__GRANULARITY,(0>>24) + +rm_gdt_end: + .equ rm_gdt_length, rm_gdt_end - rm_gdt + +/**************************************************************************** + * real_to_prot (real-mode far call) + * + * Switch from 16-bit real-mode to 32-bit protected mode with flat + * physical addresses. %esp is restored from the saved pm_esp. All + * segment registers are set to flat physical-mode values. All other + * registers are preserved. Interrupts are disabled. + * + * Note that this routine can be called *without* having first set up + * a stored pm_esp or stored GDT. If you do this, real_to_prot will + * return with a temporary stack that is only *FOUR BYTES* in size. + * This is just enough to enable you to do a "call 1f; popl %ebp" + * sequence in order to find out your physical address and then load a + * proper 32-bit protected-mode stack pointer. Do *NOT* use more than + * four bytes since this will overwrite code in librm! + * + * Parameters: none + **************************************************************************** + */ + + .code16 +EXPORT(real_to_prot): + /* Disable interrupts */ + cli + + /* Set %ds = %cs, for easier access to variables */ + pushw %cs + popw %ds + + /* Preserve registers */ + movl %eax, %ds:OFFSET(save_eax) + movl %ebx, %ds:OFFSET(save_ebx) + + /* Extract real-mode far return address from stack */ + popl %ds:OFFSET(save_retaddr) + + /* Record real-mode stack pointer */ + movw %sp, %ds:OFFSET(rm_sp) + pushw %ss + popw %ds:OFFSET(rm_ss) + + /* Physical base address of librm to %ebx */ + xorl %ebx, %ebx + movw %cs, %bx + shll $4, %ebx + + /* Check base address of stored protected-mode GDT. If it's + * zero, set it up to use our internal GDT (with physical + * segments only). + */ + movl %ds:OFFSET(pm_gdt_addr), %eax + testl %eax, %eax + jnz 1f + /* Use internal GDT */ + movl %ebx, %eax + addl $OFFSET(pm_gdt), %eax + movl %eax, %ds:OFFSET(pm_gdt_addr) +1: + + /* Set up protected-mode continuation address on real-mode stack */ + pushl $PHYSICAL_CS + movl %ebx, %eax + addl $OFFSET(1f), %eax + pushl %eax + + /* Restore protected-mode GDT */ + lgdt %ds:OFFSET(pm_gdt) + + /* Switch to protected mode */ + movl %cr0, %eax + orb $CR0_PE, %al + movl %eax, %cr0 + + /* Flush prefetch queue and reload %cs:eip */ + data32 lret +1: .code32 + + /* Set up protected-mode stack and data segments */ + movw $PHYSICAL_DS, %ax + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs + movw %ax, %ss + + /* Switch to saved protected-mode stack. Note that there may + * not actually *be* a saved protected-mode stack. + */ + movl OFFSET(pm_esp)(%ebx), %esp + testl %esp, %esp + jnz 1f + /* No stack - use save_retaddr as a 4-byte temporary stack */ + leal OFFSET(save_retaddr+4)(%ebx), %esp +1: + + /* Convert real-mode far return address to physical address + * and place on stack + */ + pushl OFFSET(save_retaddr)(%ebx) + xorl %eax, %eax + xchgw 2(%esp), %ax + shll $4, %eax + addl %eax, 0(%esp) + + /* Restore registers and return */ + movl OFFSET(save_eax)(%ebx), %eax + movl OFFSET(save_ebx)(%ebx), %ebx + ret + +/**************************************************************************** + * prot_to_real (protected-mode near call, physical addresses) + * + * Switch from 32-bit protected mode with flat physical addresses to + * 16-bit real mode. %ss:sp is restored from the saved rm_ss and + * rm_sp. %cs is set such that %cs:0000 is the start of librm. All + * other segment registers are set to %ss. All other registers are + * preserved. Interrupts are *not* enabled, since we want to be able + * to use this routine inside an ISR. + * + * Note that since %cs:0000 points to the start of librm on exit, it + * follows that the code calling prot_to_real must be located within + * 64kB of the start of librm. + * + * Parameters: none + **************************************************************************** + */ + + .code32 +EXPORT(prot_to_real): + /* Calculate physical base address of librm in %ebx, preserve + * original %eax and %ebx in save_eax and save_ebx + */ + pushl %ebx + call 1f +1: popl %ebx + subl $OFFSET(1b), %ebx + popl OFFSET(save_ebx)(%ebx) + movl %eax, OFFSET(save_eax)(%ebx) + + /* Extract return address from the stack, convert to offset + * within librm and save in save_retaddr + */ + popl %eax + subl %ebx, %eax + movl %eax, OFFSET(save_retaddr)(%ebx) + + /* Record protected-mode stack pointer */ + movl %esp, OFFSET(pm_esp)(%ebx) + + /* Record protected-mode GDT */ + sgdt OFFSET(pm_gdt)(%ebx) + + /* Set up real-mode GDT */ + leal OFFSET(rm_gdt)(%ebx), %eax + movl %eax, OFFSET(rm_gdt_base)(%ebx) + movl %ebx, %eax + rorl $16, %eax + movw %bx, OFFSET(rm_gdt_rm_cs+2)(%ebx) + movb %al, OFFSET(rm_gdt_rm_cs+4)(%ebx) + movw %bx, OFFSET(rm_gdt_rm_ds+2)(%ebx) + movb %al, OFFSET(rm_gdt_rm_ds+4)(%ebx) + + /* Switch to real-mode GDT and reload segment registers to get + * 64kB limits. Stack is invalidated by this process. + */ + lgdt OFFSET(rm_gdt)(%ebx) + ljmp $RM_CS, $1f +1: .code16 + movw $RM_DS, %ax + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs + movw %ax, %ss + + /* Calculate real-mode code segment in %ax and store in ljmp + * instruction + */ + movl %ebx, %eax + shrl $4, %eax + movw %ax, OFFSET(p2r_ljmp) + 3 + + /* Switch to real mode */ + movl %cr0, %ebx + andb $0!CR0_PE, %bl + movl %ebx, %cr0 + + /* Intersegment jump to flush prefetch queue and reload + * %cs:eip. The segment gets filled in by the above code. We + * can't just use lret to achieve this, because we have no + * stack at the moment. + */ +p2r_ljmp: + ljmp $0, $OFFSET(1f) +1: + + /* Set %ds to point to code segment for easier data access */ + movw %ax, %ds + + /* Restore registers */ + movl OFFSET(save_eax), %eax + movl OFFSET(save_ebx), %ebx + + /* Set up real-mode data segments and stack */ + movw OFFSET(rm_ss), %ss + movw OFFSET(rm_sp), %sp + pushw %ss + pushw %ss + pushw %ss + pushw %ss + popw %ds + popw %es + popw %fs + popw %gs + + /* Set up return address on stack and return */ + pushw %cs:OFFSET(save_retaddr) + ret + +/**************************************************************************** + * prot_call (real-mode far call) + * + * Call a specific C function in the protected-mode code. The + * prototype of the C function must be + * void function ( struct real_mode_regs *rm_regs, + * void (*retaddr) (void) ); + * rm_regs will point to a struct containing the real-mode registers + * at entry to prot_call. retaddr will point to the (virtual) return + * address from "function". This return address will point into + * librm. It is included so that "function" may, if desired, relocate + * librm and return via the new copy. It must not be directly called + * as a function, i.e. you may not do "*retaddr()"; you must instead + * do something like: + * *retaddr += ( new_librm_location - old_librm_location ); + * return; + * + * All registers will be preserved across prot_call(), unless the C + * function explicitly overwrites values in rm_regs. Interrupt status + * will also be preserved. Gate A20 will be enabled. + * + * Parameters: + * function : virtual address of protected-mode function to call + * + * Example usage: + * pushl $pxe_api_call + * lcall $LIBRM_SEGMENT, $prot_call + * addw $4, %sp + * to call in to the C function + * void pxe_api_call ( struct real_mode_regs *rm_regs ); + **************************************************************************** + */ + +#define PC_OFFSET_RM_REGS ( 0 ) +#define PC_OFFSET_RETADDR ( PC_OFFSET_RM_REGS + SIZEOF_REAL_MODE_REGS ) +#define PC_OFFSET_FUNCTION ( PC_OFFSET_RETADDR + 4 ) + + .code16 +EXPORT(prot_call): + /* Preserve registers and flags on RM stack */ + pushfl + pushal + pushw %gs + pushw %fs + pushw %es + pushw %ds + pushw %ss + pushw %cs + + /* Record RM stack pointer */ + xorl %ebp, %ebp + movw %sp, %bp + + /* Physical address of RM stack pointer to %esi */ + xorl %esi, %esi + pushw %ss + popw %si + shll $4, %esi + addl %ebp, %esi + + /* Address of pmode function to %ebx */ + movl %ss:(PC_OFFSET_FUNCTION)(%bp), %ebx + + /* Switch to protected mode */ + pushw %cs + call real_to_prot + .code32 + + /* Copy rm_regs from RM stack to PM stack */ + movl $SIZEOF_REAL_MODE_REGS, %ecx + subl %ecx, %esp + movl %esp, %edi + cld + rep movsb + + /* Switch to virtual addresses. */ + call 1f + jmp 2f +1: ljmp $VIRTUAL_CS, $_phys_to_virt +2: + + /* Enable A20 line */ + pushal + lcall $VIRTUAL_CS, $gateA20_set + popl %eax /* discard */ + popal + + /* Push &rm_regs and &retaddr on the stack, and call function */ + movl %esp, %ebp + pushl %esp + subl $12, 0(%esp) + pushl %ebp + call *%ebx + popl %eax /* discard */ + popl %eax /* discard */ + + /* Switch to physical addresses, discard PM register store */ + lcall $VIRTUAL_CS, $_virt_to_phys + addl $SIZEOF_REAL_MODE_REGS+4, %esp /* also discard lcall seg */ + + /* Switch to real mode */ + call prot_to_real + .code16 + + /* Restore registers and flags, and return */ + popw %ax /* skip %cs */ + popw %ax /* skip %ss */ + popw %ds + popw %es + popw %fs + popw %gs + popal + popfl + lret + +/**************************************************************************** + * real_call (protected-mode near call, virtual addresses) + * + * Call a real-mode function from protected-mode code. + * + * The non-segment register values will be passed directly to the + * real-mode code. The segment registers will be set as per + * prot_to_real. The non-segment register values set by the real-mode + * function will be passed back to the protected-mode caller. A + * result of this is that this routine cannot be called directly from + * C code, since it clobbers registers that the C ABI expects the + * callee to preserve. Gate A20 will be re-enabled in case the + * real-mode routine disabled it. + * + * librm.h defines two convenient macros for using real_call: + * REAL_CALL and REAL_EXEC. See librm.h and realmode.h for details + * and examples. + * + * Parameters: + * far pointer to real-mode function to call + * + * Returns: none + **************************************************************************** + */ + +#define RC_OFFSET_PRESERVE_REGS ( 0 ) +#define RC_OFFSET_RETADDR ( RC_OFFSET_PRESERVE_REGS + 8 ) +#define RC_OFFSET_RM_FUNCTION ( RC_OFFSET_RETADDR + 4 ) + + .code32 +EXPORT(real_call): + /* Preserve registers */ + pushl %ebp + pushl %eax + + /* Switch to physical addresses */ + lcall $VIRTUAL_CS, $_virt_to_phys + addl $4, %esp + + /* Extract real-mode function address and store in ljmp instruction */ + call 1f +1: popl %ebp + movl RC_OFFSET_RM_FUNCTION(%esp), %eax + movl %eax, (rc_ljmp + 1 - 1b)(%ebp) + + /* Restore registers */ + popl %eax + popl %ebp + + /* Switch to real mode, preserving non-segment registers */ + call prot_to_real + .code16 + + /* Far call to real-mode routine */ + pushw %cs + call rc_ljmp + jmp 2f +rc_ljmp: + ljmp $0, $0 /* address filled in by above code */ +2: + + /* Switch to protected mode */ + pushw %cs + call real_to_prot + .code32 + + /* Switch to virtual addresses */ + call 1f + jmp 2f +1: ljmp $VIRTUAL_CS, $_phys_to_virt +2: + + /* Enable A20 line */ + pushal + lcall $VIRTUAL_CS, $gateA20_set + popl %eax /* discard */ + popal + + /* Return */ + ret + +/**************************************************************************** + * Relocation lock counter + * + * librm may be moved in base memory only when this counter is zero. + * The counter gets incremented whenever a reference to librm is + * generated (e.g. a real_call is made, resulting in a return address + * pointing to librm being placed on the stack), and decremented when + * the reference goes out of scope (e.g. the real_call returns). + **************************************************************************** + */ +EXPORT(librm_ref_count): .byte 0 + +/**************************************************************************** + * Stored real-mode and protected-mode stack pointers + * + * The real-mode stack pointer is stored here whenever real_to_prot + * is called and restored whenever prot_to_real is called. The + * converse happens for the protected-mode stack pointer. + * + * Despite initial appearances this scheme is, in fact re-entrant, + * because program flow dictates that we always return via the point + * we left by. For example: + * PXE API call entry + * 1 real => prot + * ... + * Print a text string + * ... + * 2 prot => real + * INT 10 + * 3 real => prot + * ... + * ... + * 4 prot => real + * PXE API call exit + * + * At point 1, the RM mode stack value, say RPXE, is stored in + * rm_ss,sp. We want this value to still be present in rm_ss,sp when + * we reach point 4. + * + * At point 2, the RM stack value is restored from RPXE. At point 3, + * the RM stack value is again stored in rm_ss,sp. This *does* + * overwrite the RPXE that we have stored there, but it's the same + * value, since the code between points 2 and 3 has managed to return + * to us. + **************************************************************************** + */ + +EXPORT(rm_stack): /* comprises rm_ss and rm_sp */ +rm_sp: .word 0 +rm_ss: .word 0 + +EXPORT(pm_stack): +pm_esp: .long 0 + +/**************************************************************************** + * Temporary variables + **************************************************************************** + */ +save_eax: .long 0 +save_ebx: .long 0 +save_retaddr: .long 0 + +/**************************************************************************** + * End of librm + **************************************************************************** + */ +_librm_end: + .globl _librm_size + .equ _librm_size, _librm_end - _librm_start diff --git a/src/arch/i386/transitions/librm_mgmt.c b/src/arch/i386/transitions/librm_mgmt.c new file mode 100644 index 00000000..bfe963d4 --- /dev/null +++ b/src/arch/i386/transitions/librm_mgmt.c @@ -0,0 +1,87 @@ +/* + * librm: a library for interfacing to real-mode code + * + * Michael Brown <mbrown@fensystems.co.uk> + * + */ + +#ifdef KEEP_IT_REAL +/* Build a null object under -DKEEP_IT_REAL */ +#else + +#include "stdint.h" +#include "stddef.h" +#include "string.h" +#include "librm.h" + +/* + * This file provides functions for managing librm. + * + */ + +/* Current location of librm in base memory */ +char *installed_librm = librm; + +/* + * Install librm to base memory + * + */ +void install_librm ( void *addr ) { + memcpy ( addr, librm, librm_size ); + installed_librm = addr; +} + +/* + * Increment lock count of librm + * + */ +void lock_librm ( void ) { + inst_librm_ref_count++; +} + +/* + * Decrement lock count of librm + * + */ +void unlock_librm ( void ) { +#ifdef DEBUG_LIBRM + if ( inst_librm_ref_count == 0 ) { + printf ( "librm: ref count gone negative\n" ); + lockup(); + } +#endif + inst_librm_ref_count--; +} + +/* + * Allocate space on the real-mode stack and copy data there. + * + */ +uint16_t copy_to_rm_stack ( void *data, size_t size ) { +#ifdef DEBUG_LIBRM + if ( inst_rm_stack.offset <= size ) { + printf ( "librm: out of space in RM stack\n" ); + lockup(); + } +#endif + inst_rm_stack.offset -= size; + copy_to_real ( inst_rm_stack.segment, inst_rm_stack.offset, + data, size ); + return inst_rm_stack.offset; +}; + +/* + * Deallocate space on the real-mode stack, optionally copying back + * data. + * + */ +void remove_from_rm_stack ( void *data, size_t size ) { + if ( data ) { + copy_from_real ( data, + inst_rm_stack.segment, inst_rm_stack.offset, + size ); + } + inst_rm_stack.offset += size; +}; + +#endif /* KEEP_IT_REAL */ |