diff options
Diffstat (limited to 'src/arch/i386/core/virtaddr.S')
-rw-r--r-- | src/arch/i386/core/virtaddr.S | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/src/arch/i386/core/virtaddr.S b/src/arch/i386/core/virtaddr.S new file mode 100644 index 00000000..ed495c35 --- /dev/null +++ b/src/arch/i386/core/virtaddr.S @@ -0,0 +1,317 @@ +/* + * Functions to support the virtual addressing method of relocation + * that Etherboot uses. + * + */ + +#include "virtaddr.h" + + .arch i386 + +/**************************************************************************** + * GDT for initial transition to protected mode + * + * The segment values, PHYSICAL_CS et al, are defined in an external + * header file virtaddr.h, since they need to be shared with librm. + **************************************************************************** + */ + .data + .align 16 + +gdt: +gdt_limit: .word gdt_length - 1 +gdt_addr: .long 0 + .word 0 /* padding */ + + .org gdt + PHYSICAL_CS +physical_cs: + /* 32 bit protected mode code segment, physical addresses */ + .word 0xffff,0 + .byte 0,0x9f,0xcf,0 + + .org gdt + PHYSICAL_DS +physical_ds: + /* 32 bit protected mode data segment, physical addresses */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 + + .org gdt + VIRTUAL_CS +virtual_cs: + /* 32 bit protected mode code segment, virtual addresses */ + .word 0xffff,0 + .byte 0,0x9f,0xcf,0 + + .org gdt + VIRTUAL_DS +virtual_ds: + /* 32 bit protected mode data segment, virtual addresses */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 + +#ifdef CONFIG_X86_64 + + .org gdt + LONG_CS +long_cs: + /* 64bit long mode code segment, base 0 */ + .word 0xffff, 0 + .byte 0x00, 0x9f, 0xaf , 0x00 + + .org gdt + LONG_DS +long_ds: + /* 64bit long mode data segment, base 0 */ + .word 0xffff, 0 + .byte 0x00, 0x93, 0xcf, 0x00 + +#endif /* CONFIG_X86_64 */ + +gdt_end: + .equ gdt_length, gdt_end - gdt + + /* The virtual address offset */ + .globl virt_offset +virt_offset: .long 0 + + .text + .code32 + +/**************************************************************************** + * run_here (flat physical addressing, position-independent) + * + * Set up a GDT to run Etherboot at the current location with virtual + * addressing. This call does not switch to virtual addresses or move + * the stack pointer. The GDT will be located within the copy of + * Etherboot. All registers are preserved. + * + * This gets called at startup and at any subsequent relocation of + * Etherboot. + * + * Parameters: none + **************************************************************************** + */ + .globl run_here +run_here: + /* Preserve registers */ + pushl %eax + pushl %ebp + + /* Find out where we're running */ + call 1f +1: popl %ebp + subl $1b, %ebp + + /* Store as virt_offset */ + movl %ebp, virt_offset(%ebp) + + /* Set segment base addresses in GDT */ + leal virtual_cs(%ebp), %eax + pushl %eax + pushl %ebp + call set_seg_base + popl %eax /* discard */ + popl %eax /* discard */ + + /* Set physical location of GDT */ + leal gdt(%ebp), %eax + movl %eax, gdt_addr(%ebp) + + /* Load the new GDT */ + lgdt gdt(%ebp) + + /* Reload new flat physical segment registers */ + movl $PHYSICAL_DS, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %fs + movl %eax, %gs + movl %eax, %ss + + /* Restore registers, convert return address to far return + * address. + */ + popl %ebp + movl $PHYSICAL_CS, %eax + xchgl %eax, 4(%esp) /* cs now on stack, ret offset now in eax */ + xchgl %eax, 0(%esp) /* ret offset now on stack, eax restored */ + + /* Return to caller, reloading %cs with new value */ + lret + +/**************************************************************************** + * set_seg_base (any addressing, position-independent) + * + * Set the base address of a pair of segments in the GDT. This relies + * on the layout of the GDT being (CS,DS) pairs. + * + * Parameters: + * uint32_t base_address + * struct gdt_entry * code_segment + * Returns: + * none + **************************************************************************** + */ + .globl set_seg_base +set_seg_base: + pushl %eax + pushl %ebx + movl 12(%esp), %eax /* %eax = base address */ + movl 16(%esp), %ebx /* %ebx = &code_descriptor */ + movw %ax, (0+2)(%ebx) /* CS base bits 0-15 */ + movw %ax, (8+2)(%ebx) /* DS base bits 0-15 */ + shrl $16, %eax + movb %al, (0+4)(%ebx) /* CS base bits 16-23 */ + movb %al, (8+4)(%ebx) /* DS base bits 16-23 */ + movb %ah, (0+7)(%ebx) /* CS base bits 24-31 */ + movb %ah, (8+7)(%ebx) /* DS base bits 24-31 */ + popl %ebx + popl %eax + ret + +/**************************************************************************** + * _virt_to_phys (virtual addressing) + * + * Switch from virtual to flat physical addresses. %esp is adjusted + * to a physical value. Segment registers are set to flat physical + * selectors. All other registers are preserved. Flags are + * preserved. + * + * Parameters: none + * Returns: none + **************************************************************************** + */ + .globl _virt_to_phys +_virt_to_phys: + /* Preserve registers and flags */ + pushfl + pushl %eax + pushl %ebp + + /* Change return address to a physical address */ + movl virt_offset, %ebp + addl %ebp, 12(%esp) + + /* Switch to physical code segment */ + pushl $PHYSICAL_CS + leal 1f(%ebp), %eax + pushl %eax + lret +1: + /* Reload other segment registers and adjust %esp */ + movl $PHYSICAL_DS, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %fs + movl %eax, %gs + movl %eax, %ss + addl %ebp, %esp + + /* Restore registers and flags, and return */ + popl %ebp + popl %eax + popfl + ret + +/**************************************************************************** + * _phys_to_virt (flat physical addressing) + * + * Switch from flat physical to virtual addresses. %esp is adjusted + * to a virtual value. Segment registers are set to virtual + * selectors. All other registers are preserved. Flags are + * preserved. + * + * Note that this depends on the GDT already being correctly set up + * (e.g. by a call to run_here()). + * + * Parameters: none + * Returns: none + **************************************************************************** + */ + .globl _phys_to_virt +_phys_to_virt: + /* Preserve registers and flags */ + pushfl + pushl %eax + pushl %ebp + + /* Switch to virtual code segment */ + ljmp $VIRTUAL_CS, $1f +1: + /* Reload data segment registers */ + movl $VIRTUAL_DS, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %fs + movl %eax, %gs + + /* Reload stack segment and adjust %esp */ + movl virt_offset, %ebp + movl %eax, %ss + subl %ebp, %esp + + /* Change the return address to a virtual address */ + subl %ebp, 12(%esp) + + /* Restore registers and flags, and return */ + popl %ebp + popl %eax + popfl + ret + +/**************************************************************************** + * relocate_to (virtual addressing) + * + * Relocate Etherboot to the specified address. The runtime image + * (excluding the prefix, decompressor and compressed image) is copied + * to a new location, and execution continues in the new copy. This + * routine is designed to be called from C code. + * + * Parameters: + * uint32_t new_phys_addr + **************************************************************************** + */ + .globl relocate_to +relocate_to: + /* Save the callee save registers */ + pushl %ebp + pushl %esi + pushl %edi + + /* Compute the physical source address and data length */ + movl $_text, %esi + movl $_end, %ecx + subl %esi, %ecx + addl virt_offset, %esi + + /* Compute the physical destination address */ + movl 16(%esp), %edi + + /* Switch to flat physical addressing */ + call _virt_to_phys + + /* Do the copy */ + cld + rep movsb + + /* Calculate offset to new image */ + subl %esi, %edi + + /* Switch to executing in new image */ + call 1f +1: popl %ebp + leal (2f-1b)(%ebp,%edi), %eax + jmpl *%eax +2: + /* Switch to stack in new image */ + addl %edi, %esp + + /* Call run_here() to set up GDT */ + call run_here + + /* Switch to virtual addressing */ + call _phys_to_virt + + /* Restore the callee save registers */ + popl %edi + popl %esi + popl %ebp + + /* return */ + ret |