318 lines
7.4 KiB
ArmAsm
318 lines
7.4 KiB
ArmAsm
|
/*
|
||
|
* 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
|