diff options
Diffstat (limited to 'roms/ipxe/src/arch/i386/transitions')
| -rw-r--r-- | roms/ipxe/src/arch/i386/transitions/liba20.S | 309 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/transitions/libkir.S | 256 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/transitions/libpm.S | 0 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/transitions/librm.S | 671 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/transitions/librm_mgmt.c | 158 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/transitions/librm_test.c | 117 | 
6 files changed, 1511 insertions, 0 deletions
| diff --git a/roms/ipxe/src/arch/i386/transitions/liba20.S b/roms/ipxe/src/arch/i386/transitions/liba20.S new file mode 100644 index 00000000..68469752 --- /dev/null +++ b/roms/ipxe/src/arch/i386/transitions/liba20.S @@ -0,0 +1,309 @@ +/* + * 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. + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ) + +	.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 diff --git a/roms/ipxe/src/arch/i386/transitions/libkir.S b/roms/ipxe/src/arch/i386/transitions/libkir.S new file mode 100644 index 00000000..1176fcce --- /dev/null +++ b/roms/ipxe/src/arch/i386/transitions/libkir.S @@ -0,0 +1,256 @@ +/* + * libkir: a transition library for -DKEEP_IT_REAL + * + * Michael Brown <mbrown@fensystems.co.uk> + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ) + +/**************************************************************************** + * 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 diff --git a/roms/ipxe/src/arch/i386/transitions/libpm.S b/roms/ipxe/src/arch/i386/transitions/libpm.S new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/roms/ipxe/src/arch/i386/transitions/libpm.S diff --git a/roms/ipxe/src/arch/i386/transitions/librm.S b/roms/ipxe/src/arch/i386/transitions/librm.S new file mode 100644 index 00000000..2e447b03 --- /dev/null +++ b/roms/ipxe/src/arch/i386/transitions/librm.S @@ -0,0 +1,671 @@ +/* + * librm: a library for interfacing to real-mode code + * + * Michael Brown <mbrown@fensystems.co.uk> + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ) + +/* 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 diff --git a/roms/ipxe/src/arch/i386/transitions/librm_mgmt.c b/roms/ipxe/src/arch/i386/transitions/librm_mgmt.c new file mode 100644 index 00000000..cc4765de --- /dev/null +++ b/roms/ipxe/src/arch/i386/transitions/librm_mgmt.c @@ -0,0 +1,158 @@ +/* + * librm: a library for interfacing to real-mode code + * + * Michael Brown <mbrown@fensystems.co.uk> + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#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 = ( ( ( uint32_t ) vector ) & 0xffff ); +	idte->high = ( ( ( uint32_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 = ( ( uint32_t ) interrupt_wrapper - +				( uint32_t ) vec->next ); +		set_interrupt_vector ( intr, vec ); +	} +	DBGC ( &intr_vec[0], "INTn vector at %p+%xn (phys %#lx+%xn)\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__ (( cdecl, 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 ); diff --git a/roms/ipxe/src/arch/i386/transitions/librm_test.c b/roms/ipxe/src/arch/i386/transitions/librm_test.c new file mode 100644 index 00000000..e07cfccd --- /dev/null +++ b/roms/ipxe/src/arch/i386/transitions/librm_test.c @@ -0,0 +1,117 @@ +/* + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** @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; +	unsigned long started; +	unsigned long stopped; +	unsigned int 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 ), "=d" ( discard_d ) +				       : ); +		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 %0, %2\n\t" +						   "pushl %3\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, +}; + +REQUIRE_OBJECT ( test ); | 
