david/ipxe
david
/
ipxe
Archived
1
0
Fork 0
This repository has been archived on 2020-12-06. You can view files and clone it, but cannot push or open issues or pull requests.
ipxe/src/arch/i386/core/realmode_asm.S

696 lines
18 KiB
ArmAsm

/* Real-mode interface: assembly-language portions.
*
* Initial version by Michael Brown <mbrown@fensystems.co.uk>, January 2004.
*/
#include "realmode.h"
#include "callbacks.h"
#if 1 /* CODE16 */
#define BOCHSBP xchgw %bx,%bx
#define NUM_PUSHA_REGS (8)
#define NUM_SEG_REGS (6)
.text
.arch i386
.section ".text16.nocompress", "ax", @progbits
.code16
.equ CR0_PE,1
#ifdef GAS291
#define DATA32 data32;
#define ADDR32 addr32;
#define LJMPI(x) ljmp x
#else
#define DATA32 data32
#define ADDR32 addr32
/* newer GAS295 require #define LJMPI(x) ljmp *x */
#define LJMPI(x) ljmp x
#endif
/****************************************************************************
* REAL-MODE CALLBACK INTERFACE
*
* This must be copied down to base memory in order for external
* programs to be able to make calls in to Etherboot. Store the
* current physical address of Etherboot (i.e. virt_to_phys(_text)) in
* (uint32_t)rm_etherboot_location, then copy
* (uint16_t)rm_callback_interface_size bytes starting at
* &((void)rm_callback_interface).
*
* There are two defined entry points:
* Offset RM_IN_CALL = 0 Near call entry point
* Offset RM_IN_CALL_FAR = 2 Far call entry point
*
* Note that the routines _prot_to_real and _real_to_prot double as
* trampoline fragments for external calls (calls from Etherboot to
* real-mode code). _prot_to_real does not automatically re-enable
* interrupts; this is to allow for the potential of using Etherboot
* code as an ISR. _real_to_prot does automatically disable
* interrupts, since we don't have a protected-mode IDT.
****************************************************************************
*/
.globl rm_callback_interface
.code16
rm_callback_interface:
.globl _rm_in_call
_rm_in_call:
jmp _real_in_call
.globl _rm_in_call_far
_rm_in_call_far:
jmp _real_in_call_far
/****************************************************************************
* _real_in_call
*
* Parameters:
* 16-bit real-mode near/far return address (implicit from [l]call
* to routine) Other parameters as for _in_call_far().
*
* This routine will convert the 16-bit real-mode far return address
* to a 32-bit real-mode far return address, switch to protected mode
* using _real_to_prot and call in to _in_call_far.
****************************************************************************
*/
#define RIC_PRESERVE ( 8 )
#define RIC_OFFSET_CALLADDR ( RIC_PRESERVE )
#define RIC_OFFSET_CALLADDR_E ( RIC_OFFSET_CALLADDR + 4 )
#define RIC_OFFSET_CONTADDR ( RIC_OFFSET_CALLADDR_E )
#define RIC_OFFSET_CONTADDR_E ( RIC_OFFSET_CONTADDR + 4 )
#define RIC_OFFSET_OPCODE ( RIC_OFFSET_CONTADDR_E )
#define RIC_OFFSET_OPCODE_E ( RIC_OFFSET_OPCODE + 4 )
#define RIC_OFFSET_SEG_REGS ( RIC_OFFSET_OPCODE_E )
#define RIC_OFFSET_SEG_REGS_E ( RIC_OFFSET_SEG_REGS + ( NUM_SEG_REGS * 2 ) )
#define RIC_OFFSET_PAD ( RIC_OFFSET_SEG_REGS_E )
#define RIC_OFFSET_PAD_E ( RIC_OFFSET_PAD + 2 )
#define RIC_OFFSET_FLAGS ( RIC_OFFSET_PAD_E )
#define RIC_OFFSET_FLAGS_E ( RIC_OFFSET_FLAGS + 2 )
#define RIC_OFFSET_RETADDR ( RIC_OFFSET_FLAGS_E )
#define RIC_OFFSET_RETADDR_E ( RIC_OFFSET_RETADDR + 4 )
#define RIC_OFFSET_ORIG_OPCODE ( RIC_OFFSET_RETADDR_E )
#define RIC_INSERT_LENGTH ( RIC_OFFSET_OPCODE_E - RIC_OFFSET_CALLADDR )
.code16
_real_in_call:
/* Expand near return address to far return address
*/
pushw %ax /* Extend stack, store %ax */
pushfw
pushw %bp
movw %sp, %bp
movw %cs, %ax
xchgw %ax, 6(%bp)
xchgw %ax, 4(%bp) /* also restores %ax */
popw %bp
popfw
/* Fall through to _real_in_call_far */
_real_in_call_far:
/* Store flags and pad */
pushfw
pushw %ax
/* Store segment registers. Order matches that of seg_regs_t */
pushw %gs
pushw %fs
pushw %es
pushw %ds
pushw %ss
pushw %cs
/* Switch to protected mode */
call _real_to_prot
.code32
/* Allow space for expanded stack */
subl $RIC_INSERT_LENGTH, %esp
/* Store temporary registers */
pushl %ebp
pushl %eax
/* Copy opcode, set EB_CALL_FROM_REAL_MODE and EP_SKIP_OPCODE.
* Copy it because _in_call() and i386_in_call() expect it at
* a fixed position, not as part of the va_list.
*/
movl RIC_OFFSET_ORIG_OPCODE(%esp), %eax
orl $(EB_CALL_FROM_REAL_MODE|EB_SKIP_OPCODE), %eax
movl %eax, RIC_OFFSET_OPCODE(%esp)
/* Set up call and return addresses */
call 1f
1: popl %ebp
subl $1b, %ebp /* %ebp = offset */
movl rm_etherboot_location(%ebp), %eax /* Etherboot phys addr */
subl $_text, %eax
addl $_in_call, %eax /* _in_call phys addr */
movl %eax, RIC_OFFSET_CALLADDR(%esp)
leal 2f(%ebp), %eax /* continuation address */
movl %eax, RIC_OFFSET_CONTADDR(%esp)
/* Restore temporary registers */
popl %eax
popl %ebp
/* Call to _in_call */
ret
/* opcode will be popped automatically thanks to EB_SKIP_OPCODE */
2: /* Continuation point */
call _prot_to_real /* Return to real mode */
/* Note: the first two words of our segment register store
* happens to be exactly what we need to pass as parameters to
* _prot_to_real.
*/
.code16
popw %ds /* Restore segment registers */
popw %ds /* (skip cs&ss since these */
popw %ds /* have already been set by */
popw %es /* _prot_to_real */
popw %fs
popw %gs
addw $2, %sp /* skip pad */
/* Check for EB_SKIP_OPCODE */
pushw %bp
movw %sp, %bp
testl $EB_SKIP_OPCODE, 6(%bp)
popw %bp
jnz 1f
/* Normal return */
popfw /* Restore interrupt status */
lret /* Back to caller */
1: /* Return and skip opcode */
popfw
lret $4
/****************************************************************************
* rm_etherboot_location: the current physical location of Etherboot.
* Needed so that real-mode callback routines can locate Etherboot.
****************************************************************************
*/
.globl rm_etherboot_location
rm_etherboot_location: .long 0
/****************************************************************************
* _prot_to_real_prefix
*
* Trampoline fragment. Switch from 32-bit protected mode with flat
* physical addresses to 16-bit real mode. Store registers in the
* trampoline for restoration by _real_to_prot_suffix. Switch to
* stack in base memory.
****************************************************************************
*/
.globl _prot_to_real_prefix
.code32
_prot_to_real_prefix:
/* Registers to preserve */
pushl %ebx
pushl %esi
pushl %edi
pushl %ebp
/* Calculate offset */
call 1f
1: popl %ebp
subl $1b, %ebp /* %ebp = offset for labels in p2r*/
/* Preserve registers and return address in r2p_params */
movl p2r_r2p_params(%ebp), %ebx
subl $r2p_params, %ebx /* %ebx = offset for labels in r2p */
popl r2p_ebp(%ebx)
popl r2p_edi(%ebx)
popl r2p_esi(%ebx)
popl r2p_ebx(%ebx)
popl r2p_ret_addr(%ebx)
movl %esp, r2p_esp(%ebx)
/* Switch stacks */
movl p2r_esp(%ebp), %esp
/* Switch to real mode */
pushl p2r_segments(%ebp)
call _prot_to_real
.code16
addw $4, %sp
/* Fall through to next trampoline fragment */
jmp _prot_to_real_prefix_end
/****************************************************************************
* _prot_to_real
*
* Switch from 32-bit protected mode with flat physical addresses to
* 16-bit real mode. Stack and code must be in base memory when
* called. %cs, %ss, %eip, %esp are changed to real-mode values,
* other segment registers are destroyed, all other registers are
* preserved. Interrupts are *not* enabled.
*
* Parameters:
* %cs Real-mode code segment (word)
* %ss Real-mode stack segment (word)
****************************************************************************
*/
#define P2R_PRESERVE ( 12 )
#define P2R_OFFSET_RETADDR ( P2R_PRESERVE )
#define P2R_OFFSET_RETADDR_E ( P2R_OFFSET_RETADDR + 4 )
#define P2R_OFFSET_CS ( P2R_OFFSET_RETADDR_E )
#define P2R_OFFSET_CS_E ( P2R_OFFSET_CS + 2 )
#define P2R_OFFSET_SS ( P2R_OFFSET_CS_E )
#define P2R_OFFSET_SS_E ( P2R_OFFSET_SS + 2 )
.globl _prot_to_real
.code32
_prot_to_real:
/* Preserve registers */
pushl %ebp
pushl %ebx
pushl %eax
/* Calculate offset */
call 1f
1: popl %ebp
subl $1b, %ebp /* %ebp = offset for labels in p2r*/
/* Set up GDT with real-mode limits and appropriate bases for
* real-mode %cs and %ss. Set up protected-mode continuation
* point on stack.
*/
/* Fixup GDT */
leal p2r_gdt(%ebp), %eax
movl %eax, p2r_gdt_addr(%ebp)
/* Calculate CS base address: set GDT code segment, adjust
* return address, set up continuation address on stack.
*/
movzwl P2R_OFFSET_CS(%esp), %eax
shll $4, %eax
/* Change return address to real-mode far address */
subl %eax, P2R_OFFSET_RETADDR(%esp)
movl %eax, %ebx
shrl $4, %ebx
movw %bx, (P2R_OFFSET_RETADDR+2)(%esp)
/* First real mode address */
movl %eax, %ebx
shrl $4, %ebx
pushw %bx
leal 3f(%ebp), %ebx
subl %eax, %ebx
pushw %bx
/* Continuation address */
pushl $(p2r_rmcs - p2r_gdt)
leal 2f(%ebp), %ebx
subl %eax, %ebx
pushl %ebx
/* Code segment in GDT */
movw %ax, (p2r_rmcs+2)(%ebp)
shrl $16, %eax /* Remainder of cs base addr */
movb %al, (p2r_rmcs+4)(%ebp)
movb %ah, (p2r_rmcs+7)(%ebp)
/* Calculate SS base address: set GDT data segment, retain to
* use for adjusting %esp.
*/
movzwl (12+P2R_OFFSET_SS)(%esp), %eax /* Set ss base address */
shll $4, %eax
movw %ax, (p2r_rmds+2)(%ebp)
movl %eax, %ebx
shrl $16, %ebx
movb %bl, (p2r_rmds+4)(%ebp)
movb %bh, (p2r_rmds+7)(%ebp)
/* Load GDT */
lgdt p2r_gdt(%ebp)
/* Reload all segment registers and adjust %esp */
movw $(p2r_rmds - p2r_gdt), %bx /* Pmode DS */
movw %bx, %ss
subl %eax, %esp /* %esp now less than 0x10000 */
movw %bx, %ds
movw %bx, %es
movw %bx, %fs
movw %bx, %gs
lret /* %cs:eip */
2: /* Segment registers now have 16-bit limits. */
.code16
/* Switch to real mode */
movl %cr0, %ebx
andb $0!CR0_PE, %bl
movl %ebx, %cr0
/* Make intersegment jmp to flush the processor pipeline
* and reload %cs:%eip (to clear upper 16 bits of %eip).
*/
lret
3:
/* Load real-mode segment value to %ss. %sp already OK */
shrl $4, %eax
movw %ax, %ss
/* Restore registers */
popl %eax
popl %ebx
popl %ebp
/* Return to caller in real-mode */
lret
#ifdef FLATTEN_REAL_MODE
#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x8f
#else
#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x00
#endif
p2r_gdt:
p2r_gdtarg:
p2r_gdt_limit: .word p2r_gdt_end - p2r_gdt - 1
p2r_gdt_addr: .long 0
p2r_gdt_padding: .word 0
p2r_rmcs:
/* 16 bit real mode code segment */
.word 0xffff,(0&0xffff)
.byte (0>>16),0x9b,RM_LIMIT_16_19__AVL__SIZE__GRANULARITY,(0>>24)
p2r_rmds:
/* 16 bit real mode data segment */
.word 0xffff,(0&0xffff)
.byte (0>>16),0x93,RM_LIMIT_16_19__AVL__SIZE__GRANULARITY,(0>>24)
p2r_gdt_end:
/* This is the end of the trampoline prefix code. When used
* as a prefix, fall through to the following code in the
* trampoline.
*/
p2r_params: /* Structure must match prot_to_real_params_t in realmode.h */
p2r_esp: .long 0
p2r_segments:
p2r_cs: .word 0
p2r_ss: .word 0
p2r_r2p_params: .long 0
.globl _prot_to_real_prefix_end
_prot_to_real_prefix_end:
.globl _prot_to_real_prefix_size
.equ _prot_to_real_prefix_size, _prot_to_real_prefix_end - _prot_to_real_prefix
.globl prot_to_real_prefix_size
prot_to_real_prefix_size:
.word _prot_to_real_prefix_size
/****************************************************************************
* _real_to_prot_suffix
*
* Trampoline fragment. Switch from 16-bit real-mode to 32-bit
* protected mode with flat physical addresses. Copy returned stack
* parameters to output_stack. Restore registers preserved by
* _prot_to_real_prefix. Restore stack to previous location.
****************************************************************************
*/
.globl _real_to_prot_suffix
.code16
_real_to_prot_suffix:
/* Switch to protected mode */
call _real_to_prot
.code32
/* Calculate offset */
call 1f
1: popl %ebp
subl $1b, %ebp /* %ebp = offset for labels in r2p */
/* Copy stack to out_stack */
movl r2p_out_stack(%ebp), %edi
movl r2p_out_stack_len(%ebp), %ecx
movl %esp, %esi
cld
rep movsb
/* Switch back to original stack */
movl r2p_esp(%ebp), %esp
/* Restore registers and return */
pushl r2p_ret_addr(%ebp) /* Set up return address on stack */
movl r2p_ebx(%ebp), %ebx
movl r2p_esi(%ebp), %esi
movl r2p_edi(%ebp), %edi
movl r2p_ebp(%ebp), %ebp
ret
/****************************************************************************
* _real_to_prot
*
* Switch from 16-bit real-mode to 32-bit protected mode with flat
* physical addresses. All segment registers are destroyed, %eip and
* %esp are changed to flat physical values, all other registers are
* preserved. Interrupts are disabled.
*
* Parameters: none
****************************************************************************
*/
#define R2P_PRESERVE ( 12 )
#define R2P_OFFSET_RETADDR ( R2P_PRESERVE )
#define R2P_OFFSET_ORIG_RETADDR ( R2P_OFFSET_RETADDR + 2 )
.globl _real_to_prot
.code16
_real_to_prot:
/* Disable interrupts */
cli
/* zero extend the return address */
pushw $0
/* Preserve registers */
pushl %ebp
pushl %ebx
pushl %eax
/* Convert 16-bit real-mode near return address to
* 32-bit pmode physical near return address
*/
movw %sp, %bp
xorl %ebx, %ebx
push %cs
popw %bx
movw %bx, %ds
shll $4, %ebx
movzwl %ss:R2P_OFFSET_ORIG_RETADDR(%bp), %eax
addl %ebx, %eax
movl %eax, %ss:(R2P_OFFSET_RETADDR)(%bp)
/* Store the code segment physical base address in %ebp */
movl %ebx, %ebp
/* Find the offset within the code segment that I am running at */
xorl %ebx, %ebx
call 1f
1: popw %bx
/* Set up GDT */
leal (r2p_gdt-1b)(%bx), %eax /* %ds:ebx = %ds:bx = &(r2p_gdt) */
addl %ebp, %eax /* %eax = &r2p_gdt (physical) */
movl %eax, %ds:(r2p_gdt-1b+2)(%bx) /* Set phys. addr. in r2p_gdt */
/* Compute the first protected mode physical address */
leal (2f-1b)(%bx), %eax
addl %ebp, %eax
movl %eax, %ds:(r2p_paddr-1b)(%bx)
/* Calculate new %esp */
xorl %eax, %eax
push %ss
popw %ax
shll $4, %eax
movzwl %sp, %ebp
addl %eax, %ebp /* %ebp = new %esp */
/* Load GDT */
DATA32 lgdt %ds:(r2p_gdt-1b)(%bx) /* Load GDT */
/* Switch to protected mode */
movl %cr0, %eax
orb $CR0_PE, %al
movl %eax, %cr0
/* flush prefetch queue, and reload %cs:%eip */
DATA32 ljmp %ds:(r2p_paddr-1b)(%bx)
.code32
2:
/* Load segment registers, adjust %esp */
movw $(r2p_pmds-r2p_gdt), %ax
movw %ax, %ss
movl %ebp, %esp
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
/* Restore registers */
popl %eax
popl %ebx
popl %ebp
/* return to caller */
ret
r2p_gdt:
.word r2p_gdt_end - r2p_gdt - 1 /* limit */
.long 0 /* addr */
.word 0
r2p_pmcs:
/* 32 bit protected mode code segment, physical addresses */
.word 0xffff, 0
.byte 0, 0x9f, 0xcf, 0
r2p_pmds:
/* 32 bit protected mode data segment, physical addresses */
.word 0xffff,0
.byte 0,0x93,0xcf,0
r2p_gdt_end:
r2p_paddr:
.long 2b
.word r2p_pmcs - r2p_gdt, 0
/* This is the end of the trampoline suffix code.
*/
r2p_params: /* Structure must match real_to_prot_params_t in realmode.h */
r2p_ret_addr: .long 0
r2p_esp: .long 0
r2p_ebx: .long 0
r2p_esi: .long 0
r2p_edi: .long 0
r2p_ebp: .long 0
r2p_out_stack: .long 0
r2p_out_stack_len: .long 0
.globl _real_to_prot_suffix_end
_real_to_prot_suffix_end:
.globl _real_to_prot_suffix_size
.equ _real_to_prot_suffix_size, _real_to_prot_suffix_end - _real_to_prot_suffix
.globl real_to_prot_suffix_size
real_to_prot_suffix_size:
.word _real_to_prot_suffix_size
rm_callback_interface_end:
.globl _rm_callback_interface_size
.equ _rm_callback_interface_size, rm_callback_interface_end - rm_callback_interface
.globl rm_callback_interface_size
rm_callback_interface_size:
.word _rm_callback_interface_size
/****************************************************************************
* END OF REAL-MODE CALLBACK INTERFACE
****************************************************************************
*/
#ifdef PXE_EXPORT
/****************************************************************************
* PXE CALLBACK INTERFACE
*
* Prepend this to rm_callback_interface to create a real-mode PXE
* callback interface.
****************************************************************************
*/
.section ".text16", "ax", @progbits
.globl pxe_callback_interface
.code16
pxe_callback_interface:
/* Macro to calculate offset of labels within code segment in
* installed copy of code.
*/
#define INSTALLED(x) ( (x) - pxe_callback_interface )
/****************************************************************************
* PXE entry points (!PXE and PXENV+ APIs)
****************************************************************************
*/
/* in_call mechanism for !PXE API calls */
.globl _pxe_in_call_far
_pxe_in_call_far:
/* Prepend "PXE API call" and "API version 0x201" to stack */
pushl $0x201
jmp 1f
/* in_call mechanism for PXENV+ API calls */
.globl _pxenv_in_call_far
_pxenv_in_call_far:
/* Prepend "PXE API call" and "API version 0x200" to stack */
pushl $0x200
1: pushl $EB_OPCODE_PXE
/* Perform real-mode in_call */
call pxe_rm_in_call
/* Return */
addw $8, %sp
lret
/****************************************************************************
* PXE installation check (INT 1A) code
****************************************************************************
*/
.globl _pxe_intercept_int1a
_pxe_intercept_int1a:
pushfw
cmpw $0x5650, %ax
jne 2f
1: /* INT 1A,5650 - Intercept */
popfw
/* Set up return values according to PXE spec: */
movw $0x564e, %ax /* AX := 564Eh (VN) */
pushw %cs:INSTALLED(_pxe_pxenv_segment)
popw %es /* ES:BX := &(PXENV+ structure) */
movw %cs:INSTALLED(_pxe_pxenv_offset), %bx
clc /* CF is cleared */
lret $2 /* 'iret' without reloading flags */
2: /* INT 1A,other - Do not intercept */
popfw
ljmp %cs:*INSTALLED(_pxe_intercepted_int1a)
.globl _pxe_intercepted_int1a
_pxe_intercepted_int1a: .word 0,0
.globl _pxe_pxenv_location
_pxe_pxenv_location:
_pxe_pxenv_offset: .word 0
_pxe_pxenv_segment: .word 0
pxe_rm_in_call:
pxe_attach_rm:
/* rm_callback_interface must be appended here */
pxe_callback_interface_end:
.globl _pxe_callback_interface_size
.equ _pxe_callback_interface_size, pxe_callback_interface_end - pxe_callback_interface
.globl pxe_callback_interface_size
pxe_callback_interface_size:
.word _pxe_callback_interface_size
#else /* PXE_EXPORT */
/* Define symbols used by the linker scripts, to prevent link errors */
.globl _pxe_callback_interface_size
.equ _pxe_callback_interface_size, 0
#endif /* PXE_EXPORT */
#else /* CODE16 */
/* Define symbols used by the linker scripts, to prevent link errors */
.globl _rm_callback_interface_size
.equ _rm_callback_interface_size, 0
.globl _pxe_callback_interface_size
.equ _pxe_callback_interface_size, 0
#endif /* CODE16 */