[bios] Add bin-x86_64-pcbios build platform

Move most arch/i386 files to arch/x86, and adjust the contents of the
Makefiles and the include/bits/*.h headers to reflect the new
locations.

This patch makes no substantive code changes, as can be seen using a
rename-aware diff (e.g. "git show -M5").

This patch does not make the pcbios platform functional for x86_64; it
merely allows it to compile without errors.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2016-02-16 15:19:01 +00:00
parent 43515f9f1a
commit f468f12b1e
155 changed files with 198 additions and 240 deletions

View File

@@ -0,0 +1,313 @@
/*
* Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )
.arch i386
/****************************************************************************
* test_a20_short, test_a20_long
*
* Check to see if A20 line is enabled
*
* Parameters:
* none
* Returns:
* CF set if A20 line is not enabled
* Corrupts:
* none
****************************************************************************
*/
#define TEST_A20_SHORT_MAX_RETRIES 0x20
#define TEST_A20_LONG_MAX_RETRIES 0x200000
.section ".text16.early", "awx", @progbits
.code16
test_a20_short:
pushl %ecx
movl $TEST_A20_SHORT_MAX_RETRIES, %ecx
jmp 1f
.size test_a20_short, . - test_a20_short
test_a20_long:
pushl %ecx
movl $TEST_A20_LONG_MAX_RETRIES, %ecx
1: pushw %ax
pushw %ds
pushw %es
/* Set up segment registers for access across the 1MB boundary */
xorw %ax, %ax
movw %ax, %ds
decw %ax
movw %ax, %es
2: /* Modify and check test pattern; succeed if we see a difference */
pushfw
cli
xchgw %ds:0, %cx
movw %es:0x10, %ax
xchgw %ds:0, %cx
popfw
cmpw %ax, %cx
clc
jnz 99f
/* Delay and retry */
outb %al, $0x80
addr32 loop 2b
stc
99: /* Restore registers and return */
popw %es
popw %ds
popw %ax
popl %ecx
ret
.size test_a20_long, . - test_a20_long
/****************************************************************************
* enable_a20_bios
*
* Try enabling A20 line via BIOS
*
* Parameters:
* none
* Returns:
* CF set if A20 line is not enabled
* Corrupts:
* none
****************************************************************************
*/
.section ".text16.early", "awx", @progbits
.code16
enable_a20_bios:
/* Preserve registers. Be very paranoid, since some BIOSes
* are reported to clobber %ebx
*/
pushal
/* Attempt INT 15,2401 */
movw $0x2401, %ax
int $0x15
jc 99f
/* Check that success was really successful */
call test_a20_short
99: /* Restore registers and return */
popal
ret
.size enable_a20_bios, . - enable_a20_bios
/****************************************************************************
* enable_a20_kbc
*
* Try enabling A20 line via keyboard controller
*
* Parameters:
* none
* Returns:
* CF set if A20 line is not enabled
* Corrupts:
* none
****************************************************************************
*/
#define KC_RDWR 0x60
#define KC_RDWR_SET_A20 0xdf
#define KC_CMD 0x64
#define KC_CMD_WOUT 0xd1
#define KC_CMD_NULL 0xff
#define KC_STATUS 0x64
#define KC_STATUS_OBUF_FULL 0x01
#define KC_STATUS_IBUF_FULL 0x02
#define KC_MAX_RETRIES 100000
.section ".text16.early", "awx", @progbits
.code16
enable_a20_kbc:
/* Preserve registers */
pushw %ax
/* Try keyboard controller */
call empty_kbc
movb $KC_CMD_WOUT, %al
outb %al, $KC_CMD
call empty_kbc
movb $KC_RDWR_SET_A20, %al
outb %al, $KC_RDWR
call empty_kbc
movb $KC_CMD_NULL, %al
outb %al, $KC_CMD
call empty_kbc
/* Check to see if it worked */
call test_a20_long
/* Restore registers and return */
popw %ax
ret
.size enable_a20_kbc, . - enable_a20_kbc
.section ".text16.early", "awx", @progbits
.code16
empty_kbc:
/* Preserve registers */
pushl %ecx
pushw %ax
/* Wait for KBC to become empty */
movl $KC_MAX_RETRIES, %ecx
1: outb %al, $0x80
inb $KC_STATUS, %al
testb $( KC_STATUS_OBUF_FULL | KC_STATUS_IBUF_FULL ), %al
jz 99f
testb $KC_STATUS_OBUF_FULL, %al
jz 2f
outb %al, $0x80
inb $KC_RDWR, %al
2: addr32 loop 1b
99: /* Restore registers and return */
popw %ax
popl %ecx
ret
.size empty_kbc, . - empty_kbc
/****************************************************************************
* enable_a20_fast
*
* Try enabling A20 line via "Fast Gate A20"
*
* Parameters:
* none
* Returns:
* CF set if A20 line is not enabled
* Corrupts:
* none
****************************************************************************
*/
#define SCP_A 0x92
.section ".text16.early", "awx", @progbits
.code16
enable_a20_fast:
/* Preserve registers */
pushw %ax
/* Try "Fast Gate A20" */
inb $SCP_A, %al
orb $0x02, %al
andb $~0x01, %al
outb %al, $SCP_A
/* Check to see if it worked */
call test_a20_long
/* Restore registers and return */
popw %ax
ret
.size enable_a20_fast, . - enable_a20_fast
/****************************************************************************
* enable_a20
*
* Try enabling A20 line via any available method
*
* Parameters:
* none
* Returns:
* CF set if A20 line is not enabled
* Corrupts:
* none
****************************************************************************
*/
#define ENABLE_A20_RETRIES 255
.section ".text16.early", "awx", @progbits
.code16
.globl enable_a20
enable_a20:
/* Preserve registers */
pushl %ecx
pushw %ax
/* Check to see if A20 is already enabled */
call test_a20_short
jnc 99f
/* Use known working method, if we have one */
movw %cs:enable_a20_method, %ax
testw %ax, %ax
jz 1f
call *%ax
jmp 99f
1:
/* Try all methods in turn until one works */
movl $ENABLE_A20_RETRIES, %ecx
2: movw $enable_a20_bios, %ax
movw %ax, %cs:enable_a20_method
call *%ax
jnc 99f
movw $enable_a20_kbc, %ax
movw %ax, %cs:enable_a20_method
call *%ax
jnc 99f
movw $enable_a20_fast, %ax
movw %ax, %cs:enable_a20_method
call *%ax
jnc 99f
addr32 loop 2b
/* Failure; exit with carry set */
movw $0, %cs:enable_a20_method
stc
99: /* Restore registers and return */
popw %ax
popl %ecx
ret
.section ".text16.early.data", "aw", @progbits
.align 2
enable_a20_method:
.word 0
.size enable_a20_method, . - enable_a20_method
/****************************************************************************
* access_highmem (real mode far call)
*
* Open up access to high memory with A20 enabled
*
* Parameters:
* none
* Returns:
* CF set if high memory could not be accessed
* Corrupts:
* none
****************************************************************************
*/
.section ".text16.early", "awx", @progbits
.code16
.globl access_highmem
access_highmem:
/* Enable A20 line */
call enable_a20
lret
.size access_highmem, . - access_highmem

View File

@@ -0,0 +1,256 @@
/*
* libkir: a transition library for -DKEEP_IT_REAL
*
* Michael Brown <mbrown@fensystems.co.uk>
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )
/****************************************************************************
* 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
/****************************************************************************
* init_libkir (real-mode or 16:xx protected-mode far call)
*
* Initialise libkir ready for transitions to the kir environment
*
* Parameters:
* %cs : .text16 segment
* %ds : .data16 segment
****************************************************************************
*/
.globl init_libkir
init_libkir:
/* Record segment registers */
pushw %ds
popw %cs:kir_ds
lret
/****************************************************************************
* 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.
*
* 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 */
movw %ds:kir_ds, %ax
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 i386_all_resg *ix86 );
* ix86 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 ix86. Interrupt status
* will also be preserved.
*
* Parameters:
* function : (32-bit) virtual address of C function to call
*
* Example usage:
* pushl $pxe_api_call
* lcall $UNDI_CS, $kir_call
* addw $4, %sp
* to call in to the C function
* void pxe_api_call ( struct i386_all_regs *ix86 );
****************************************************************************
*/
.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 */
popl %cs:save_function
subl $8, %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 &ix86 on stack and call function */
sti
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 _estack
/****************************************************************************
* Temporary variables
****************************************************************************
*/
save_ax: .word 0
save_retaddr: .long 0
save_flags: .long 0
save_function: .long 0

View File

View File

@@ -0,0 +1,671 @@
/*
* librm: a library for interfacing to real-mode code
*
* Michael Brown <mbrown@fensystems.co.uk>
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )
/* Drag in local definitions */
#include "librm.h"
/* 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_REAL_MODE_REGS ( SIZEOF_I386_SEG_REGS + SIZEOF_I386_REGS )
#define SIZEOF_I386_FLAGS 4
#define SIZEOF_I386_ALL_REGS ( SIZEOF_REAL_MODE_REGS + SIZEOF_I386_FLAGS )
.arch i386
/****************************************************************************
* Global descriptor table
*
* Call init_librm to set up the GDT before attempting to use any
* protected-mode code.
*
* 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 REAL_CS when the ljmp is encountered.
*
* Note also that putting ".word gdt_end - gdt - 1" directly into
* gdt_limit, rather than going via gdt_length, will also produce the
* "non absolute segment" error. This is most probably a bug in gas.
****************************************************************************
*/
.section ".data16", "aw", @progbits
.align 16
gdt:
gdtr: /* The first GDT entry is unused, the GDTR can fit here. */
gdt_limit: .word gdt_length - 1
gdt_base: .long 0
.word 0 /* padding */
.org gdt + VIRTUAL_CS, 0
virtual_cs: /* 32 bit protected mode code segment, virtual addresses */
.word 0xffff, 0
.byte 0, 0x9f, 0xcf, 0
.org gdt + VIRTUAL_DS, 0
virtual_ds: /* 32 bit protected mode data segment, virtual addresses */
.word 0xffff, 0
.byte 0, 0x93, 0xcf, 0
.org gdt + PHYSICAL_CS, 0
physical_cs: /* 32 bit protected mode code segment, physical addresses */
.word 0xffff, 0
.byte 0, 0x9f, 0xcf, 0
.org gdt + PHYSICAL_DS, 0
physical_ds: /* 32 bit protected mode data segment, physical addresses */
.word 0xffff, 0
.byte 0, 0x93, 0xcf, 0
.org gdt + REAL_CS, 0
real_cs: /* 16 bit real mode code segment */
.word 0xffff, 0
.byte 0, 0x9b, 0x00, 0
.org gdt + REAL_DS
real_ds: /* 16 bit real mode data segment */
.word 0xffff, ( REAL_DS << 4 )
.byte 0, 0x93, 0x00, 0
gdt_end:
.equ gdt_length, gdt_end - gdt
/****************************************************************************
* init_librm (real-mode far call, 16-bit real-mode far return address)
*
* Initialise the GDT ready for transitions to protected mode.
*
* Parameters:
* %cs : .text16 segment
* %ds : .data16 segment
* %edi : Physical base of protected-mode code (virt_offset)
****************************************************************************
*/
.section ".text16", "ax", @progbits
.code16
.globl init_librm
init_librm:
/* Preserve registers */
pushl %eax
pushl %ebx
/* Store virt_offset and set up virtual_cs and virtual_ds segments */
movl %edi, %eax
movw $virtual_cs, %bx
call set_seg_base
movw $virtual_ds, %bx
call set_seg_base
movl %edi, rm_virt_offset
/* Negate virt_offset */
negl %edi
/* Store rm_cs and text16, set up real_cs segment */
xorl %eax, %eax
movw %cs, %ax
movw %ax, %cs:rm_cs
shll $4, %eax
movw $real_cs, %bx
call set_seg_base
addr32 leal (%eax, %edi), %ebx
movl %ebx, rm_text16
/* Store rm_ds and data16 */
xorl %eax, %eax
movw %ds, %ax
movw %ax, %cs:rm_ds
shll $4, %eax
addr32 leal (%eax, %edi), %ebx
movl %ebx, rm_data16
/* Set GDT base */
movl %eax, gdt_base
addl $gdt, gdt_base
/* Initialise IDT */
pushl $init_idt
pushw %cs
call prot_call
popl %eax /* discard */
/* Restore registers */
negl %edi
popl %ebx
popl %eax
lret
.section ".text16", "ax", @progbits
.code16
set_seg_base:
1: movw %ax, 2(%bx)
rorl $16, %eax
movb %al, 4(%bx)
movb %ah, 7(%bx)
roll $16, %eax
ret
/****************************************************************************
* real_to_prot (real-mode near call, 32-bit virtual return address)
*
* Switch from 16-bit real-mode to 32-bit protected mode with virtual
* addresses. The real-mode %ss:sp is stored in rm_ss and rm_sp, and
* the protected-mode %esp is restored from the saved pm_esp.
* Interrupts are disabled. All other registers may be destroyed.
*
* The return address for this function should be a 32-bit virtual
* address.
*
* Parameters:
* %ecx : number of bytes to move from RM stack to PM stack
*
****************************************************************************
*/
.section ".text16", "ax", @progbits
.code16
real_to_prot:
/* Enable A20 line */
call enable_a20
/* A failure at this point is fatal, and there's nothing we
* can do about it other than lock the machine to make the
* problem immediately visible.
*/
1: jc 1b
/* Make sure we have our data segment available */
movw %cs:rm_ds, %ax
movw %ax, %ds
/* Add virt_offset, text16 and data16 to stack to be
* copied, and also copy the return address.
*/
pushl rm_virt_offset
pushl rm_text16
pushl rm_data16
addw $16, %cx /* %ecx must be less than 64kB anyway */
/* Real-mode %ss:%sp => %ebp:%edx and virtual address => %esi */
xorl %ebp, %ebp
movw %ss, %bp
movzwl %sp, %edx
movl %ebp, %eax
shll $4, %eax
addr32 leal (%eax,%edx), %esi
subl rm_virt_offset, %esi
/* Load protected-mode global descriptor table */
data32 lgdt gdtr
/* Zero segment registers. This wastes around 12 cycles on
* real hardware, but saves a substantial number of emulated
* instructions under KVM.
*/
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
/* Switch to protected mode */
cli
movl %cr0, %eax
orb $CR0_PE, %al
movl %eax, %cr0
data32 ljmp $VIRTUAL_CS, $r2p_pmode
.section ".text", "ax", @progbits
.code32
r2p_pmode:
/* Set up protected-mode data segments and stack pointer */
movw $VIRTUAL_DS, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
movl pm_esp, %esp
/* Load protected-mode interrupt descriptor table */
lidt idtr
/* Record real-mode %ss:sp (after removal of data) */
movw %bp, rm_ss
addl %ecx, %edx
movw %dx, rm_sp
/* Move data from RM stack to PM stack */
subl %ecx, %esp
movl %esp, %edi
rep movsb
/* Publish virt_offset, text16 and data16 for PM code to use */
popl data16
popl text16
popl virt_offset
/* Return to virtual address */
ret
/****************************************************************************
* prot_to_real (protected-mode near call, 32-bit real-mode return address)
*
* Switch from 32-bit protected mode with virtual addresses to 16-bit
* real mode. The protected-mode %esp is stored in pm_esp and the
* real-mode %ss:sp is restored from the saved rm_ss and rm_sp. The
* high word of the real-mode %esp is set to zero. All real-mode data
* segment registers are loaded from the saved rm_ds. Interrupts are
* *not* enabled, since we want to be able to use prot_to_real in an
* ISR. All other registers may be destroyed.
*
* The return address for this function should be a 32-bit (sic)
* real-mode offset within .code16.
*
* Parameters:
* %ecx : number of bytes to move from PM stack to RM stack
* %esi : real-mode global and interrupt descriptor table registers
*
****************************************************************************
*/
.section ".text", "ax", @progbits
.code32
prot_to_real:
/* Copy real-mode global descriptor table register to RM code segment */
movl text16, %edi
leal rm_gdtr(%edi), %edi
movsw
movsl
/* Load real-mode interrupt descriptor table register */
lidt (%esi)
/* Add return address to data to be moved to RM stack */
addl $4, %ecx
/* Real-mode %ss:sp => %ebp:edx and virtual address => %edi */
movzwl rm_ss, %ebp
movzwl rm_sp, %edx
subl %ecx, %edx
movl %ebp, %eax
shll $4, %eax
leal (%eax,%edx), %edi
subl virt_offset, %edi
/* Move data from PM stack to RM stack */
movl %esp, %esi
rep movsb
/* Record protected-mode %esp (after removal of data) */
movl %esi, pm_esp
/* Load real-mode segment limits */
movw $REAL_DS, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
ljmp $REAL_CS, $p2r_rmode
.section ".text16", "ax", @progbits
.code16
p2r_rmode:
/* Load real-mode GDT */
data32 lgdt %cs:rm_gdtr
/* Switch to real mode */
movl %cr0, %eax
andb $0!CR0_PE, %al
movl %eax, %cr0
p2r_ljmp_rm_cs:
ljmp $0, $1f
1:
/* Set up real-mode data segments and stack pointer */
movw %cs:rm_ds, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %bp, %ss
movl %edx, %esp
/* Return to real-mode address */
data32 ret
/* Real-mode code and data segments. Assigned by the call to
* init_librm. rm_cs doubles as the segment part of the jump
* instruction used by prot_to_real. Both are located in
* .text16 rather than .data16: rm_cs since it forms part of
* the jump instruction within the code segment, and rm_ds
* since real-mode code needs to be able to locate the data
* segment with no other reference available.
*/
.globl rm_cs
.equ rm_cs, ( p2r_ljmp_rm_cs + 3 )
.section ".text16.data", "aw", @progbits
.globl rm_ds
rm_ds: .word 0
/* Real-mode global and interrupt descriptor table registers */
.section ".text16.data", "aw", @progbits
rm_gdtr:
.word 0 /* Limit */
.long 0 /* Base */
/****************************************************************************
* prot_call (real-mode far call, 16-bit real-mode far return address)
*
* Call a specific C function in the protected-mode code. The
* prototype of the C function must be
* void function ( struct i386_all_regs *ix86 );
* ix86 will point to a struct containing the real-mode registers
* at entry to prot_call.
*
* All registers will be preserved across prot_call(), unless the C
* function explicitly overwrites values in ix86. Interrupt status
* and GDT will also be preserved. Gate A20 will be enabled.
*
* Note that prot_call() does not rely on the real-mode stack
* remaining intact in order to return, since everything relevant is
* copied to the protected-mode stack for the duration of the call.
* In particular, this means that a real-mode prefix can make a call
* to main() which will return correctly even if the prefix's stack
* gets vapourised during the Etherboot run. (The prefix cannot rely
* on anything else on the stack being preserved, so should move any
* critical data to registers before calling main()).
*
* Parameters:
* function : virtual address of protected-mode function to call
*
* Example usage:
* pushl $pxe_api_call
* call prot_call
* addw $4, %sp
* to call in to the C function
* void pxe_api_call ( struct i386_all_regs *ix86 );
****************************************************************************
*/
#define PC_OFFSET_GDT ( 0 )
#define PC_OFFSET_IDT ( PC_OFFSET_GDT + 6 )
#define PC_OFFSET_IX86 ( PC_OFFSET_IDT + 6 )
#define PC_OFFSET_RETADDR ( PC_OFFSET_IX86 + SIZEOF_I386_ALL_REGS )
#define PC_OFFSET_FUNCTION ( PC_OFFSET_RETADDR + 4 )
#define PC_OFFSET_END ( PC_OFFSET_FUNCTION + 4 )
.section ".text16", "ax", @progbits
.code16
.globl prot_call
prot_call:
/* Preserve registers, flags and GDT on external RM stack */
pushfl
pushal
pushw %gs
pushw %fs
pushw %es
pushw %ds
pushw %ss
pushw %cs
subw $PC_OFFSET_IX86, %sp
movw %sp, %bp
sidt PC_OFFSET_IDT(%bp)
sgdt PC_OFFSET_GDT(%bp)
/* For sanity's sake, clear the direction flag as soon as possible */
cld
/* Switch to protected mode and move register dump to PM stack */
movl $PC_OFFSET_END, %ecx
pushl $pc_pmode
jmp real_to_prot
.section ".text", "ax", @progbits
.code32
pc_pmode:
/* Call function */
leal PC_OFFSET_IX86(%esp), %eax
pushl %eax
call *(PC_OFFSET_FUNCTION+4)(%esp)
popl %eax /* discard */
/* Switch to real mode and move register dump back to RM stack */
movl $PC_OFFSET_END, %ecx
movl %esp, %esi
pushl $pc_rmode
jmp prot_to_real
.section ".text16", "ax", @progbits
.code16
pc_rmode:
/* Restore registers and flags and return */
addw $( PC_OFFSET_IX86 + 4 /* also skip %cs and %ss */ ), %sp
popw %ds
popw %es
popw %fs
popw %gs
popal
/* popal skips %esp. We therefore want to do "movl -20(%sp),
* %esp", but -20(%sp) is not a valid 80386 expression.
* Fortunately, prot_to_real() zeroes the high word of %esp, so
* we can just use -20(%esp) instead.
*/
addr32 movl -20(%esp), %esp
popfl
lret
/****************************************************************************
* real_call (protected-mode near call, 32-bit virtual return address)
*
* 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.
*
* librm.h defines a convenient macro REAL_CODE() for using real_call.
* See librm.h and realmode.h for details and examples.
*
* Parameters:
* (32-bit) near pointer to real-mode function to call
*
* Returns: none
****************************************************************************
*/
#define RC_OFFSET_PRESERVE_REGS ( 0 )
#define RC_OFFSET_RETADDR ( RC_OFFSET_PRESERVE_REGS + SIZEOF_I386_REGS )
#define RC_OFFSET_FUNCTION ( RC_OFFSET_RETADDR + 4 )
#define RC_OFFSET_END ( RC_OFFSET_FUNCTION + 4 )
.section ".text", "ax", @progbits
.code32
.globl real_call
real_call:
/* Create register dump and function pointer copy on PM stack */
pushal
pushl RC_OFFSET_FUNCTION(%esp)
/* Switch to real mode and move register dump to RM stack */
movl $( RC_OFFSET_RETADDR + 4 /* function pointer copy */ ), %ecx
pushl $rc_rmode
movl $rm_default_gdtr_idtr, %esi
jmp prot_to_real
.section ".text16", "ax", @progbits
.code16
rc_rmode:
/* Call real-mode function */
popl rc_function
popal
call *rc_function
pushal
/* For sanity's sake, clear the direction flag as soon as possible */
cld
/* Switch to protected mode and move register dump back to PM stack */
movl $RC_OFFSET_RETADDR, %ecx
pushl $rc_pmode
jmp real_to_prot
.section ".text", "ax", @progbits
.code32
rc_pmode:
/* Restore registers and return */
popal
ret
/* Function vector, used because "call xx(%sp)" is not a valid
* 16-bit expression.
*/
.section ".data16", "aw", @progbits
rc_function: .word 0, 0
/* Default real-mode global and interrupt descriptor table registers */
.section ".data", "aw", @progbits
rm_default_gdtr_idtr:
.word 0 /* Global descriptor table limit */
.long 0 /* Global descriptor table base */
.word 0x03ff /* Interrupt descriptor table limit */
.long 0 /* Interrupt descriptor table base */
/****************************************************************************
* flatten_real_mode (real-mode near call)
*
* Switch to flat real mode
*
****************************************************************************
*/
.section ".text16", "ax", @progbits
.code16
.globl flatten_real_mode
flatten_real_mode:
/* Modify GDT to use flat real mode */
movb $0x8f, real_cs + 6
movb $0x8f, real_ds + 6
/* Call dummy protected-mode function */
pushl $flatten_dummy
pushw %cs
call prot_call
addw $4, %sp
/* Restore GDT */
movb $0x00, real_cs + 6
movb $0x00, real_ds + 6
/* Return */
ret
.section ".text", "ax", @progbits
.code32
flatten_dummy:
ret
/****************************************************************************
* Interrupt wrapper
*
* Used by the protected-mode interrupt vectors to call the
* interrupt() function.
*
* May be entered with either physical or virtual stack segment.
****************************************************************************
*/
.globl interrupt_wrapper
interrupt_wrapper:
/* Preserve segment registers and original %esp */
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushl %ss
pushl %esp
/* Switch to virtual addressing */
call _intr_to_virt
/* Expand IRQ number to whole %eax register */
movzbl %al, %eax
/* Call interrupt handler */
call interrupt
/* Restore original stack and segment registers */
lss (%esp), %esp
popl %ss
popl %gs
popl %fs
popl %es
popl %ds
/* Restore registers and return */
popal
iret
/****************************************************************************
* 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.
****************************************************************************
*/
.section ".data", "aw", @progbits
.globl rm_sp
rm_sp: .word 0
.globl rm_ss
rm_ss: .word 0
pm_esp: .long _estack
/****************************************************************************
* Virtual address offsets
*
* These are used by the protected-mode code to map between virtual
* and physical addresses, and to access variables in the .text16 or
* .data16 segments.
****************************************************************************
*/
/* Internal copies, created by init_librm (which runs in real mode) */
.section ".data16", "aw", @progbits
rm_virt_offset: .long 0
rm_text16: .long 0
rm_data16: .long 0
/* Externally-visible copies, created by real_to_prot */
.section ".data", "aw", @progbits
.globl virt_offset
virt_offset: .long 0
.globl text16
text16: .long 0
.globl data16
data16: .long 0

View File

@@ -0,0 +1,158 @@
/*
* librm: a library for interfacing to real-mode code
*
* Michael Brown <mbrown@fensystems.co.uk>
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <ipxe/profile.h>
#include <realmode.h>
#include <pic8259.h>
/*
* This file provides functions for managing librm.
*
*/
/** The interrupt wrapper */
extern char interrupt_wrapper[];
/** The interrupt vectors */
static struct interrupt_vector intr_vec[NUM_INT];
/** The interrupt descriptor table */
struct interrupt_descriptor idt[NUM_INT] __attribute__ (( aligned ( 16 ) ));
/** The interrupt descriptor table register */
struct idtr idtr = {
.limit = ( sizeof ( idt ) - 1 ),
};
/** Timer interrupt profiler */
static struct profiler timer_irq_profiler __profiler = { .name = "irq.timer" };
/** Other interrupt profiler */
static struct profiler other_irq_profiler __profiler = { .name = "irq.other" };
/**
* Allocate space on the real-mode stack and copy data there from a
* user buffer
*
* @v data User buffer
* @v size Size of stack data
* @ret sp New value of real-mode stack pointer
*/
uint16_t copy_user_to_rm_stack ( userptr_t data, size_t size ) {
userptr_t rm_stack;
rm_sp -= size;
rm_stack = real_to_user ( rm_ss, rm_sp );
memcpy_user ( rm_stack, 0, data, 0, size );
return rm_sp;
};
/**
* Deallocate space on the real-mode stack, optionally copying back
* data to a user buffer.
*
* @v data User buffer
* @v size Size of stack data
*/
void remove_user_from_rm_stack ( userptr_t data, size_t size ) {
if ( data ) {
userptr_t rm_stack = real_to_user ( rm_ss, rm_sp );
memcpy_user ( rm_stack, 0, data, 0, size );
}
rm_sp += size;
};
/**
* Set interrupt vector
*
* @v intr Interrupt number
* @v vector Interrupt vector, or NULL to disable
*/
void set_interrupt_vector ( unsigned int intr, void *vector ) {
struct interrupt_descriptor *idte;
idte = &idt[intr];
idte->segment = VIRTUAL_CS;
idte->attr = ( vector ? ( IDTE_PRESENT | IDTE_TYPE_IRQ32 ) : 0 );
idte->low = ( ( ( intptr_t ) vector ) & 0xffff );
idte->high = ( ( ( intptr_t ) vector ) >> 16 );
}
/**
* Initialise interrupt descriptor table
*
*/
void init_idt ( void ) {
struct interrupt_vector *vec;
unsigned int intr;
/* Initialise the interrupt descriptor table and interrupt vectors */
for ( intr = 0 ; intr < NUM_INT ; intr++ ) {
vec = &intr_vec[intr];
vec->pushal = PUSHAL_INSN;
vec->movb = MOVB_INSN;
vec->intr = intr;
vec->jmp = JMP_INSN;
vec->offset = ( ( intptr_t ) interrupt_wrapper -
( intptr_t ) vec->next );
set_interrupt_vector ( intr, vec );
}
DBGC ( &intr_vec[0], "INTn vector at %p+%zxn (phys %#lx+%zxn)\n",
intr_vec, sizeof ( intr_vec[0] ),
virt_to_phys ( intr_vec ), sizeof ( intr_vec[0] ) );
/* Initialise the interrupt descriptor table register */
idtr.base = virt_to_phys ( idt );
}
/**
* Determine interrupt profiler (for debugging)
*
* @v intr Interrupt number
* @ret profiler Profiler
*/
static struct profiler * interrupt_profiler ( int intr ) {
switch ( intr ) {
case IRQ_INT ( 0 ) :
return &timer_irq_profiler;
default:
return &other_irq_profiler;
}
}
/**
* Interrupt handler
*
* @v intr Interrupt number
*/
void __attribute__ (( regparm ( 1 ) )) interrupt ( int intr ) {
struct profiler *profiler = interrupt_profiler ( intr );
uint32_t discard_eax;
/* Reissue interrupt in real mode */
profile_start ( profiler );
__asm__ __volatile__ ( REAL_CODE ( "movb %%al, %%cs:(1f + 1)\n\t"
"\n1:\n\t"
"int $0x00\n\t" )
: "=a" ( discard_eax ) : "0" ( intr ) );
profile_stop ( profiler );
profile_exclude ( profiler );
}
PROVIDE_UACCESS_INLINE ( librm, phys_to_user );
PROVIDE_UACCESS_INLINE ( librm, user_to_phys );
PROVIDE_UACCESS_INLINE ( librm, virt_to_user );
PROVIDE_UACCESS_INLINE ( librm, user_to_virt );
PROVIDE_UACCESS_INLINE ( librm, userptr_add );
PROVIDE_UACCESS_INLINE ( librm, memcpy_user );
PROVIDE_UACCESS_INLINE ( librm, memmove_user );
PROVIDE_UACCESS_INLINE ( librm, memset_user );
PROVIDE_UACCESS_INLINE ( librm, strlen_user );
PROVIDE_UACCESS_INLINE ( librm, memchr_user );

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** @file
*
* Real mode transition self-tests
*
* This file allows for easy measurement of the time taken to perform
* real mode transitions, which may have a substantial overhead when
* running under a hypervisor.
*
*/
/* Forcibly enable assertions */
#undef NDEBUG
#include <ipxe/test.h>
#include <ipxe/profile.h>
#include <realmode.h>
/** Number of sample iterations for profiling */
#define PROFILE_COUNT 4096
/** Protected-to-real mode transition profiler */
static struct profiler p2r_profiler __profiler = { .name = "p2r" };
/** Real-to-protected mode transition profiler */
static struct profiler r2p_profiler __profiler = { .name = "r2p" };
/** Real-mode call profiler */
static struct profiler real_call_profiler __profiler = { .name = "real_call" };
/** Protected-mode call profiler */
static struct profiler prot_call_profiler __profiler = { .name = "prot_call" };
/**
* Dummy protected-mode function
*/
static void librm_test_prot_call ( void ) {
/* Do nothing */
}
/**
* Perform real mode transition self-tests
*
*/
static void librm_test_exec ( void ) {
unsigned int i;
unsigned long timestamp;
uint32_t timestamp_lo;
uint32_t timestamp_hi;
uint32_t started;
uint32_t stopped;
uint32_t discard_d;
/* Profile mode transitions. We want to profile each
* direction of the transition separately, so perform an RDTSC
* while in real mode and tweak the profilers' start/stop
* times appropriately.
*/
for ( i = 0 ; i < PROFILE_COUNT ; i++ ) {
profile_start ( &p2r_profiler );
__asm__ __volatile__ ( REAL_CODE ( "rdtsc\n\t" )
: "=a" ( timestamp_lo ),
"=d" ( timestamp_hi )
: );
timestamp = timestamp_lo;
if ( sizeof ( timestamp ) > sizeof ( timestamp_lo ) )
timestamp |= ( ( ( uint64_t ) timestamp_hi ) << 32 );
profile_start_at ( &r2p_profiler, timestamp );
profile_stop ( &r2p_profiler );
profile_stop_at ( &p2r_profiler, timestamp );
}
/* Profile complete real-mode call cycle */
for ( i = 0 ; i < PROFILE_COUNT ; i++ ) {
profile_start ( &real_call_profiler );
__asm__ __volatile__ ( REAL_CODE ( "" ) : : );
profile_stop ( &real_call_profiler );
}
/* Profile complete protected-mode call cycle */
for ( i = 0 ; i < PROFILE_COUNT ; i++ ) {
__asm__ __volatile__ ( REAL_CODE ( "rdtsc\n\t"
"movl %k0, %k2\n\t"
"pushl %k3\n\t"
"pushw %%cs\n\t"
"call prot_call\n\t"
"addw $4, %%sp\n\t"
"rdtsc\n\t" )
: "=a" ( stopped ), "=d" ( discard_d ),
"=R" ( started )
: "i" ( librm_test_prot_call ) );
profile_start_at ( &prot_call_profiler, started );
profile_stop_at ( &prot_call_profiler, stopped );
}
}
/** Real mode transition self-test */
struct self_test librm_test __self_test = {
.name = "librm",
.exec = librm_test_exec,
};
REQUIRING_SYMBOL ( librm_test );
REQUIRE_OBJECT ( test );