diff options
| author | fishsoupisgood <github@madingley.org> | 2019-04-29 01:17:54 +0100 | 
|---|---|---|
| committer | fishsoupisgood <github@madingley.org> | 2019-05-27 03:43:43 +0100 | 
| commit | 3f2546b2ef55b661fd8dd69682b38992225e86f6 (patch) | |
| tree | 65ca85f13617aee1dce474596800950f266a456c /roms/ipxe/src/arch/i386/firmware/pcbios | |
| download | qemu-master.tar.gz qemu-master.tar.bz2 qemu-master.zip | |
Diffstat (limited to 'roms/ipxe/src/arch/i386/firmware/pcbios')
| -rw-r--r-- | roms/ipxe/src/arch/i386/firmware/pcbios/basemem.c | 47 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/firmware/pcbios/bios_console.c | 385 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/firmware/pcbios/e820mangler.S | 585 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/firmware/pcbios/fakee820.c | 94 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/firmware/pcbios/hidemem.c | 231 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/firmware/pcbios/memmap.c | 339 | ||||
| -rw-r--r-- | roms/ipxe/src/arch/i386/firmware/pcbios/pnpbios.c | 110 | 
7 files changed, 1791 insertions, 0 deletions
| diff --git a/roms/ipxe/src/arch/i386/firmware/pcbios/basemem.c b/roms/ipxe/src/arch/i386/firmware/pcbios/basemem.c new file mode 100644 index 00000000..b23f2c35 --- /dev/null +++ b/roms/ipxe/src/arch/i386/firmware/pcbios/basemem.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 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 ); + +#include <stdint.h> +#include <realmode.h> +#include <bios.h> +#include <basemem.h> +#include <ipxe/hidemem.h> + +/** @file + * + * Base memory allocation + * + */ + +/** + * Set the BIOS free base memory counter + * + * @v new_fbms		New free base memory counter (in kB) + */ +void set_fbms ( unsigned int new_fbms ) { +	uint16_t fbms = new_fbms; + +	/* Update the BIOS memory counter */ +	put_real ( fbms, BDA_SEG, BDA_FBMS ); + +	/* Update our hidden memory region map */ +	hide_basemem(); +} diff --git a/roms/ipxe/src/arch/i386/firmware/pcbios/bios_console.c b/roms/ipxe/src/arch/i386/firmware/pcbios/bios_console.c new file mode 100644 index 00000000..bd73838b --- /dev/null +++ b/roms/ipxe/src/arch/i386/firmware/pcbios/bios_console.c @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2006 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 ); + +#include <assert.h> +#include <realmode.h> +#include <bios.h> +#include <ipxe/console.h> +#include <ipxe/ansiesc.h> +#include <ipxe/keymap.h> +#include <config/console.h> + +#define ATTR_BOLD		0x08 + +#define ATTR_FCOL_MASK		0x07 +#define ATTR_FCOL_BLACK		0x00 +#define ATTR_FCOL_BLUE		0x01 +#define ATTR_FCOL_GREEN		0x02 +#define ATTR_FCOL_CYAN		0x03 +#define ATTR_FCOL_RED		0x04 +#define ATTR_FCOL_MAGENTA	0x05 +#define ATTR_FCOL_YELLOW	0x06 +#define ATTR_FCOL_WHITE		0x07 + +#define ATTR_BCOL_MASK		0x70 +#define ATTR_BCOL_BLACK		0x00 +#define ATTR_BCOL_BLUE		0x10 +#define ATTR_BCOL_GREEN		0x20 +#define ATTR_BCOL_CYAN		0x30 +#define ATTR_BCOL_RED		0x40 +#define ATTR_BCOL_MAGENTA	0x50 +#define ATTR_BCOL_YELLOW	0x60 +#define ATTR_BCOL_WHITE		0x70 + +#define ATTR_DEFAULT		ATTR_FCOL_WHITE + +/* Set default console usage if applicable */ +#if ! ( defined ( CONSOLE_PCBIOS ) && CONSOLE_EXPLICIT ( CONSOLE_PCBIOS ) ) +#undef CONSOLE_PCBIOS +#define CONSOLE_PCBIOS ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG ) +#endif + +/** Current character attribute */ +static unsigned int bios_attr = ATTR_DEFAULT; + +/** + * Handle ANSI CUP (cursor position) + * + * @v ctx		ANSI escape sequence context + * @v count		Parameter count + * @v params[0]		Row (1 is top) + * @v params[1]		Column (1 is left) + */ +static void bios_handle_cup ( struct ansiesc_context *ctx __unused, +			      unsigned int count __unused, int params[] ) { +	int cx = ( params[1] - 1 ); +	int cy = ( params[0] - 1 ); + +	if ( cx < 0 ) +		cx = 0; +	if ( cy < 0 ) +		cy = 0; + +	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t" +					   "int $0x10\n\t" +					   "cli\n\t" ) +			       : : "a" ( 0x0200 ), "b" ( 1 ), +			           "d" ( ( cy << 8 ) | cx ) ); +} + +/** + * Handle ANSI ED (erase in page) + * + * @v ctx		ANSI escape sequence context + * @v count		Parameter count + * @v params[0]		Region to erase + */ +static void bios_handle_ed ( struct ansiesc_context *ctx __unused, +			     unsigned int count __unused, +			     int params[] __unused ) { +	/* We assume that we always clear the whole screen */ +	assert ( params[0] == ANSIESC_ED_ALL ); + +	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t" +					   "int $0x10\n\t" +					   "cli\n\t" ) +			       : : "a" ( 0x0600 ), "b" ( bios_attr << 8 ), +				   "c" ( 0 ), +				   "d" ( ( ( console_height - 1 ) << 8 ) | +					 ( console_width - 1 ) ) ); +} + +/** + * Handle ANSI SGR (set graphics rendition) + * + * @v ctx		ANSI escape sequence context + * @v count		Parameter count + * @v params		List of graphic rendition aspects + */ +static void bios_handle_sgr ( struct ansiesc_context *ctx __unused, +			      unsigned int count, int params[] ) { +	static const uint8_t bios_attr_fcols[10] = { +		ATTR_FCOL_BLACK, ATTR_FCOL_RED, ATTR_FCOL_GREEN, +		ATTR_FCOL_YELLOW, ATTR_FCOL_BLUE, ATTR_FCOL_MAGENTA, +		ATTR_FCOL_CYAN, ATTR_FCOL_WHITE, +		ATTR_FCOL_WHITE, ATTR_FCOL_WHITE /* defaults */ +	}; +	static const uint8_t bios_attr_bcols[10] = { +		ATTR_BCOL_BLACK, ATTR_BCOL_RED, ATTR_BCOL_GREEN, +		ATTR_BCOL_YELLOW, ATTR_BCOL_BLUE, ATTR_BCOL_MAGENTA, +		ATTR_BCOL_CYAN, ATTR_BCOL_WHITE, +		ATTR_BCOL_BLACK, ATTR_BCOL_BLACK /* defaults */ +	}; +	unsigned int i; +	int aspect; + +	for ( i = 0 ; i < count ; i++ ) { +		aspect = params[i]; +		if ( aspect == 0 ) { +			bios_attr = ATTR_DEFAULT; +		} else if ( aspect == 1 ) { +			bios_attr |= ATTR_BOLD; +		} else if ( aspect == 22 ) { +			bios_attr &= ~ATTR_BOLD; +		} else if ( ( aspect >= 30 ) && ( aspect <= 39 ) ) { +			bios_attr &= ~ATTR_FCOL_MASK; +			bios_attr |= bios_attr_fcols[ aspect - 30 ]; +		} else if ( ( aspect >= 40 ) && ( aspect <= 49 ) ) { +			bios_attr &= ~ATTR_BCOL_MASK; +			bios_attr |= bios_attr_bcols[ aspect - 40 ]; +		} +	} +} + +/** + * Handle ANSI DECTCEM set (show cursor) + * + * @v ctx		ANSI escape sequence context + * @v count		Parameter count + * @v params		List of graphic rendition aspects + */ +static void bios_handle_dectcem_set ( struct ansiesc_context *ctx __unused, +				      unsigned int count __unused, +				      int params[] __unused ) { +	uint8_t height; + +	/* Get character height */ +	get_real ( height, BDA_SEG, BDA_CHAR_HEIGHT ); + +	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t" +					   "int $0x10\n\t" +					   "cli\n\t" ) +			       : : "a" ( 0x0100 ), +				   "c" ( ( ( height - 2 ) << 8 ) | +					 ( height - 1 ) ) ); +} + +/** + * Handle ANSI DECTCEM reset (hide cursor) + * + * @v ctx		ANSI escape sequence context + * @v count		Parameter count + * @v params		List of graphic rendition aspects + */ +static void bios_handle_dectcem_reset ( struct ansiesc_context *ctx __unused, +					unsigned int count __unused, +					int params[] __unused ) { + +	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t" +					   "int $0x10\n\t" +					   "cli\n\t" ) +			       : : "a" ( 0x0100 ), "c" ( 0x2000 ) ); +} + +/** BIOS console ANSI escape sequence handlers */ +static struct ansiesc_handler bios_ansiesc_handlers[] = { +	{ ANSIESC_CUP, bios_handle_cup }, +	{ ANSIESC_ED, bios_handle_ed }, +	{ ANSIESC_SGR, bios_handle_sgr }, +	{ ANSIESC_DECTCEM_SET, bios_handle_dectcem_set }, +	{ ANSIESC_DECTCEM_RESET, bios_handle_dectcem_reset }, +	{ 0, NULL } +}; + +/** BIOS console ANSI escape sequence context */ +static struct ansiesc_context bios_ansiesc_ctx = { +	.handlers = bios_ansiesc_handlers, +}; + +/** + * Print a character to BIOS console + * + * @v character		Character to be printed + */ +static void bios_putchar ( int character ) { +	int discard_a, discard_b, discard_c; + +	/* Intercept ANSI escape sequences */ +	character = ansiesc_process ( &bios_ansiesc_ctx, character ); +	if ( character < 0 ) +		return; + +	/* Print character with attribute */ +	__asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" /* gcc bug */ +					   "sti\n\t" +					   /* Skip non-printable characters */ +					   "cmpb $0x20, %%al\n\t" +					   "jb 1f\n\t" +					   /* Read attribute */ +					   "movb %%al, %%cl\n\t" +					   "movb $0x08, %%ah\n\t" +					   "int $0x10\n\t" +					   "xchgb %%al, %%cl\n\t" +					   /* Skip if attribute matches */ +					   "cmpb %%ah, %%bl\n\t" +					   "je 1f\n\t" +					   /* Set attribute */ +					   "movw $0x0001, %%cx\n\t" +					   "movb $0x09, %%ah\n\t" +					   "int $0x10\n\t" +					   "\n1:\n\t" +					   /* Print character */ +					   "xorw %%bx, %%bx\n\t" +					   "movb $0x0e, %%ah\n\t" +					   "int $0x10\n\t" +					   "cli\n\t" +					   "popl %%ebp\n\t" /* gcc bug */ ) +			       : "=a" ( discard_a ), "=b" ( discard_b ), +			         "=c" ( discard_c ) +			       : "a" ( character ), "b" ( bios_attr ) ); +} + +/** + * Pointer to current ANSI output sequence + * + * While we are in the middle of returning an ANSI sequence for a + * special key, this will point to the next character to return.  When + * not in the middle of such a sequence, this will point to a NUL + * (note: not "will be NULL"). + */ +static const char *ansi_input = ""; + +/** A mapping from a BIOS scan code to an ANSI escape sequence */ +#define BIOS_KEY( key, ansi ) key ansi "\0" + +/** Mapping from BIOS scan codes to ANSI escape sequences */ +static const char ansi_sequences[] = { +	BIOS_KEY ( "\x53", "[3~" )	/* Delete */ +	BIOS_KEY ( "\x48", "[A" )	/* Up arrow */ +	BIOS_KEY ( "\x50", "[B" )	/* Down arrow */ +	BIOS_KEY ( "\x4b", "[D" )	/* Left arrow */ +	BIOS_KEY ( "\x4d", "[C" )	/* Right arrow */ +	BIOS_KEY ( "\x47", "[H" )	/* Home */ +	BIOS_KEY ( "\x4f", "[F" )	/* End */ +	BIOS_KEY ( "\x49", "[5~" )	/* Page up */ +	BIOS_KEY ( "\x51", "[6~" )	/* Page down */ +	BIOS_KEY ( "\x3f", "[15~" )	/* F5 */ +	BIOS_KEY ( "\x40", "[17~" )	/* F6 */ +	BIOS_KEY ( "\x41", "[18~" )	/* F7 */ +	BIOS_KEY ( "\x42", "[19~" )	/* F8 (required for PXE) */ +	BIOS_KEY ( "\x43", "[20~" )	/* F9 */ +	BIOS_KEY ( "\x44", "[21~" )	/* F10 */ +	BIOS_KEY ( "\x85", "[23~" )	/* F11 */ +	BIOS_KEY ( "\x86", "[24~" )	/* F12 */ +}; + +/** + * Get ANSI escape sequence corresponding to BIOS scancode + * + * @v scancode		BIOS scancode + * @ret ansi_seq	ANSI escape sequence, if any, otherwise NULL + */ +static const char * scancode_to_ansi_seq ( unsigned int scancode ) { +	const char *seq = ansi_sequences; + +	while ( *seq ) { +		if ( *(seq++) == ( ( char ) scancode ) ) +			return seq; +		seq += ( strlen ( seq ) + 1 ); +	} +	DBG ( "Unrecognised BIOS scancode %02x\n", scancode ); +	return NULL; +} + +/** + * Map a key + * + * @v character		Character read from console + * @ret character	Mapped character + */ +static int bios_keymap ( unsigned int character ) { +	struct key_mapping *mapping; + +	for_each_table_entry ( mapping, KEYMAP ) { +		if ( mapping->from == character ) +			return mapping->to; +	} +	return character; +} + +/** + * Get character from BIOS console + * + * @ret character	Character read from console + */ +static int bios_getchar ( void ) { +	uint16_t keypress; +	unsigned int character; +	const char *ansi_seq; + +	/* If we are mid-sequence, pass out the next byte */ +	if ( ( character = *ansi_input ) ) { +		ansi_input++; +		return character; +	} + +	/* Read character from real BIOS console */ +	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t" +					   "int $0x16\n\t" +					   "cli\n\t" ) +			       : "=a" ( keypress ) : "a" ( 0x1000 ) ); +	character = ( keypress & 0xff ); + +	/* If it's a normal character, just map and return it */ +	if ( character && ( character < 0x80 ) ) +		return bios_keymap ( character ); + +	/* Otherwise, check for a special key that we know about */ +	if ( ( ansi_seq = scancode_to_ansi_seq ( keypress >> 8 ) ) ) { +		/* Start of escape sequence: return ESC (0x1b) */ +		ansi_input = ansi_seq; +		return 0x1b; +	} + +	return 0; +} + +/** + * Check for character ready to read from BIOS console + * + * @ret True		Character available to read + * @ret False		No character available to read + */ +static int bios_iskey ( void ) { +	unsigned int discard_a; +	unsigned int flags; + +	/* If we are mid-sequence, we are always ready */ +	if ( *ansi_input ) +		return 1; + +	/* Otherwise check the real BIOS console */ +	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t" +					   "int $0x16\n\t" +					   "pushfw\n\t" +					   "popw %w0\n\t" +					   "cli\n\t" ) +			       : "=r" ( flags ), "=a" ( discard_a ) +			       : "a" ( 0x1100 ) ); +	return ( ! ( flags & ZF ) ); +} + +struct console_driver bios_console __console_driver = { +	.putchar = bios_putchar, +	.getchar = bios_getchar, +	.iskey = bios_iskey, +	.usage = CONSOLE_PCBIOS, +}; diff --git a/roms/ipxe/src/arch/i386/firmware/pcbios/e820mangler.S b/roms/ipxe/src/arch/i386/firmware/pcbios/e820mangler.S new file mode 100644 index 00000000..cea17ef8 --- /dev/null +++ b/roms/ipxe/src/arch/i386/firmware/pcbios/e820mangler.S @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2006 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 ) + +	.text +	.arch i386 +	.code16 + +#define SMAP 0x534d4150 + +/* Most documentation refers to the E820 buffer as being 20 bytes, and + * the API makes it perfectly legitimate to pass only a 20-byte buffer + * and expect to get valid data.  However, some morons at ACPI decided + * to extend the data structure by adding an extra "extended + * attributes" field and by including critical information within this + * field, such as whether or not the region is enabled.  A caller who + * passes in only a 20-byte buffer therefore risks getting very, very + * misleading information. + * + * I have personally witnessed an HP BIOS that returns a value of + * 0x0009 in the extended attributes field.  If we don't pass this + * value through to the caller, 32-bit WinPE will die, usually with a + * PAGE_FAULT_IN_NONPAGED_AREA blue screen of death. + * + * Allow a ridiculously large maximum value (64 bytes) for the E820 + * buffer as a guard against insufficiently creative idiots in the + * future. + */ +#define E820MAXSIZE	64 + +/**************************************************************************** + * + * Allowed memory windows + * + * There are two ways to view this list.  The first is as a list of + * (non-overlapping) allowed memory regions, sorted by increasing + * address.  The second is as a list of (non-overlapping) hidden + * memory regions, again sorted by increasing address.  The second + * view is offset by half an entry from the first: think about this + * for a moment and it should make sense. + * + * xxx_memory_window is used to indicate an "allowed region" + * structure, hidden_xxx_memory is used to indicate a "hidden region" + * structure.  Each structure is 16 bytes in length. + * + **************************************************************************** + */ +	.section ".data16", "aw", @progbits +	.align 16 +	.globl hidemem_base +	.globl hidemem_umalloc +	.globl hidemem_textdata +memory_windows: +base_memory_window:	.long 0x00000000, 0x00000000 /* Start of memory */ + +hidemem_base:		.long 0x000a0000, 0x00000000 /* Changes at runtime */ +ext_memory_window:	.long 0x000a0000, 0x00000000 /* 640kB mark */ + +hidemem_umalloc:	.long 0xffffffff, 0xffffffff /* Changes at runtime */ +			.long 0xffffffff, 0xffffffff /* Changes at runtime */ + +hidemem_textdata:	.long 0xffffffff, 0xffffffff /* Changes at runtime */ +			.long 0xffffffff, 0xffffffff /* Changes at runtime */ + +			.long 0xffffffff, 0xffffffff /* End of memory */ +memory_windows_end: + +/**************************************************************************** + * Truncate region to memory window + * + * Parameters: + *  %edx:%eax	Start of region + *  %ecx:%ebx	Length of region + *  %si		Memory window + * Returns: + *  %edx:%eax	Start of windowed region + *  %ecx:%ebx	Length of windowed region + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +window_region: +	/* Convert (start,len) to (start, end) */ +	addl	%eax, %ebx +	adcl	%edx, %ecx +	/* Truncate to window start */ +	cmpl	4(%si), %edx +	jne	1f +	cmpl	0(%si), %eax +1:	jae	2f +	movl	4(%si), %edx +	movl	0(%si), %eax +2:	/* Truncate to window end */ +	cmpl	12(%si), %ecx +	jne	1f +	cmpl	8(%si), %ebx +1:	jbe	2f +	movl	12(%si), %ecx +	movl	8(%si), %ebx +2:	/* Convert (start, end) back to (start, len) */ +	subl	%eax, %ebx +	sbbl	%edx, %ecx +	/* If length is <0, set length to 0 */ +	jae	1f +	xorl	%ebx, %ebx +	xorl	%ecx, %ecx +	ret +	.size	window_region, . - window_region + +/**************************************************************************** + * Patch "memory above 1MB" figure + * + * Parameters: + *  %ax		Memory above 1MB, in 1kB blocks + * Returns: + *  %ax		Modified memory above 1M in 1kB blocks + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +patch_1m: +	pushal +	/* Convert to (start,len) format and call truncate */ +	xorl	%ecx, %ecx +	movzwl	%ax, %ebx +	shll	$10, %ebx +	xorl	%edx, %edx +	movl	$0x100000, %eax +	movw	$ext_memory_window, %si +	call	window_region +	/* Convert back to "memory above 1MB" format and return via %ax */ +	pushfw +	shrl	$10, %ebx +	popfw +	movw	%sp, %bp +	movw	%bx, 28(%bp) +	popal +	ret +	.size patch_1m, . - patch_1m + +/**************************************************************************** + * Patch "memory above 16MB" figure + * + * Parameters: + *  %bx		Memory above 16MB, in 64kB blocks + * Returns: + *  %bx		Modified memory above 16M in 64kB blocks + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +patch_16m: +	pushal +	/* Convert to (start,len) format and call truncate */ +	xorl	%ecx, %ecx +	shll	$16, %ebx +	xorl	%edx, %edx +	movl	$0x1000000, %eax +	movw	$ext_memory_window, %si +	call	window_region +	/* Convert back to "memory above 16MB" format and return via %bx */ +	pushfw +	shrl	$16, %ebx +	popfw +	movw	%sp, %bp +	movw	%bx, 16(%bp) +	popal +	ret +	.size patch_16m, . - patch_16m + +/**************************************************************************** + * Patch "memory between 1MB and 16MB" and "memory above 16MB" figures + * + * Parameters: + *  %ax		Memory between 1MB and 16MB, in 1kB blocks + *  %bx		Memory above 16MB, in 64kB blocks + * Returns: + *  %ax		Modified memory between 1MB and 16MB, in 1kB blocks + *  %bx		Modified memory above 16MB, in 64kB blocks + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +patch_1m_16m: +	call	patch_1m +	call	patch_16m +	/* If 1M region is no longer full-length, kill off the 16M region */ +	cmpw	$( 15 * 1024 ), %ax +	je	1f +	xorw	%bx, %bx +1:	ret +	.size patch_1m_16m, . - patch_1m_16m + +/**************************************************************************** + * Get underlying e820 memory region to underlying_e820 buffer + * + * Parameters: + *   As for INT 15,e820 + * Returns: + *   As for INT 15,e820 + * + * Wraps the underlying INT 15,e820 call so that the continuation + * value (%ebx) is a 16-bit simple sequence counter (with the high 16 + * bits ignored), and termination is always via CF=1 rather than + * %ebx=0. + * + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +get_underlying_e820: + +	/* If the requested region is in the cache, return it */ +	cmpw	%bx, underlying_e820_index +	jne	2f +	pushw	%di +	pushw	%si +	movw	$underlying_e820_cache, %si +	cmpl	underlying_e820_cache_size, %ecx +	jbe	1f +	movl	underlying_e820_cache_size, %ecx +1:	pushl	%ecx +	rep movsb +	popl	%ecx +	popw	%si +	popw	%di +	incw	%bx +	movl	%edx, %eax +	clc +	ret +2:	 +	/* If the requested region is earlier than the cached region, +	 * invalidate the cache. +	 */ +	cmpw	%bx, underlying_e820_index +	jbe	1f +	movw	$0xffff, underlying_e820_index +1: +	/* If the cache is invalid, reset the underlying %ebx */ +	cmpw	$0xffff, underlying_e820_index +	jne	1f +	andl	$0, underlying_e820_ebx +1:	 +	/* If the cache is valid but the continuation value is zero, +	 * this means that the previous underlying call returned with +	 * %ebx=0.  Return with CF=1 in this case. +	 */ +	cmpw	$0xffff, underlying_e820_index +	je	1f +	cmpl	$0, underlying_e820_ebx +	jne	1f +	stc +	ret +1:	 +	/* Get the next region into the cache */ +	pushl	%eax +	pushl	%ebx +	pushl	%ecx +	pushl	%edx +	pushl	%esi	/* Some implementations corrupt %esi, so we	*/ +	pushl	%edi	/* preserve %esi, %edi and %ebp to be paranoid	*/ +	pushl	%ebp +	pushw	%es +	pushw	%ds +	popw	%es +	movw	$underlying_e820_cache, %di +	cmpl	$E820MAXSIZE, %ecx +	jbe	1f +	movl	$E820MAXSIZE, %ecx +1:	movl	underlying_e820_ebx, %ebx +	stc +	pushfw +	lcall	*%cs:int15_vector +	popw	%es +	popl	%ebp +	popl	%edi +	popl	%esi +	/* Check for error return from underlying e820 call */ +	jc	2f /* CF set: error */ +	cmpl	$SMAP, %eax +	je	3f /* 'SMAP' missing: error */ +2:	/* An error occurred: return values returned by underlying e820 call */ +	stc	/* Force CF set if SMAP was missing */ +	addr32 leal 16(%esp), %esp /* avoid changing other flags */ +	ret +3:	/* No error occurred */ +	movl	%ebx, underlying_e820_ebx +	movl	%ecx, underlying_e820_cache_size +	popl	%edx +	popl	%ecx +	popl	%ebx +	popl	%eax +	/* Mark cache as containing this result */ +	incw	underlying_e820_index + +	/* Loop until found */ +	jmp	get_underlying_e820 +	.size	get_underlying_e820, . - get_underlying_e820 + +	.section ".data16", "aw", @progbits +underlying_e820_index: +	.word	0xffff /* Initialise to an invalid value */ +	.size underlying_e820_index, . - underlying_e820_index + +	.section ".bss16", "aw", @nobits +underlying_e820_ebx: +	.long	0 +	.size underlying_e820_ebx, . - underlying_e820_ebx + +	.section ".bss16", "aw", @nobits +underlying_e820_cache: +	.space	E820MAXSIZE +	.size underlying_e820_cache, . - underlying_e820_cache + +	.section ".bss16", "aw", @nobits +underlying_e820_cache_size: +	.long	0 +	.size	underlying_e820_cache_size, . - underlying_e820_cache_size + +/**************************************************************************** + * Get windowed e820 region, without empty region stripping + * + * Parameters: + *   As for INT 15,e820 + * Returns: + *   As for INT 15,e820 + * + * Wraps the underlying INT 15,e820 call so that each underlying + * region is returned N times, windowed to fit within N visible-memory + * windows.  Termination is always via CF=1. + * + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +get_windowed_e820: + +	/* Preserve registers */ +	pushl	%esi +	pushw	%bp + +	/* Split %ebx into %si:%bx, store original %bx in %bp */ +	pushl	%ebx +	popw	%bp +	popw	%si + +	/* %si == 0 => start of memory_windows list */ +	testw	%si, %si +	jne	1f +	movw	$memory_windows, %si +1:	 +	/* Get (cached) underlying e820 region to buffer */ +	call	get_underlying_e820 +	jc	99f /* Abort on error */ + +	/* Preserve registers */ +	pushal +	/* start => %edx:%eax, len => %ecx:%ebx */ +	movl	%es:0(%di), %eax +	movl	%es:4(%di), %edx +	movl	%es:8(%di), %ebx +	movl	%es:12(%di), %ecx +	/* Truncate region to current window */ +	call	window_region +1:	/* Store modified values in e820 map entry */ +	movl	%eax, %es:0(%di) +	movl	%edx, %es:4(%di) +	movl	%ebx, %es:8(%di) +	movl	%ecx, %es:12(%di) +	/* Restore registers */ +	popal + +	/* Derive continuation value for next call */ +	addw	$16, %si +	cmpw	$memory_windows_end, %si +	jne	1f +	/* End of memory windows: reset %si and allow %bx to continue */ +	xorw	%si, %si +	jmp	2f +1:	/* More memory windows to go: restore original %bx */ +	movw	%bp, %bx +2:	/* Construct %ebx from %si:%bx */ +	pushw	%si +	pushw	%bx +	popl	%ebx + +98:	/* Clear CF */ +	clc +99:	/* Restore registers and return */ +	popw	%bp +	popl	%esi +	ret +	.size get_windowed_e820, . - get_windowed_e820 + +/**************************************************************************** + * Get windowed e820 region, with empty region stripping + * + * Parameters: + *   As for INT 15,e820 + * Returns: + *   As for INT 15,e820 + * + * Wraps the underlying INT 15,e820 call so that each underlying + * region is returned up to N times, windowed to fit within N + * visible-memory windows.  Empty windows are never returned. + * Termination is always via CF=1. + * + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +get_nonempty_e820: + +	/* Record entry parameters */ +	pushl	%eax +	pushl	%ecx +	pushl	%edx + +	/* Get next windowed region */ +	call	get_windowed_e820 +	jc	99f /* abort on error */ + +	/* If region is non-empty, finish here */ +	cmpl	$0, %es:8(%di) +	jne	98f +	cmpl	$0, %es:12(%di) +	jne	98f + +	/* Region was empty: restore entry parameters and go to next region */ +	popl	%edx +	popl	%ecx +	popl	%eax +	jmp	get_nonempty_e820 + +98:	/* Clear CF */ +	clc +99:	/* Return values from underlying call */ +	addr32 leal 12(%esp), %esp /* avoid changing flags */ +	ret +	.size get_nonempty_e820, . - get_nonempty_e820 + +/**************************************************************************** + * Get mangled e820 region, with empty region stripping + * + * Parameters: + *   As for INT 15,e820 + * Returns: + *   As for INT 15,e820 + * + * Wraps the underlying INT 15,e820 call so that underlying regions + * are windowed to the allowed memory regions.  Empty regions are + * stripped from the map.  Termination is always via %ebx=0. + * + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +get_mangled_e820: + +	/* Get a nonempty region */ +	call	get_nonempty_e820 +	jc	99f /* Abort on error */ + +	/* Peek ahead to see if there are any further nonempty regions */ +	pushal +	pushw	%es +	movw	%sp, %bp +	subw	%cx, %sp +	movl	$0xe820, %eax +	movl	$SMAP, %edx +	pushw	%ss +	popw	%es +	movw	%sp, %di +	call	get_nonempty_e820 +	movw	%bp, %sp +	popw	%es +	popal +	jnc	99f /* There are further nonempty regions */ + +	/* No futher nonempty regions: zero %ebx and clear CF */ +	xorl	%ebx, %ebx +	 +99:	/* Return */ +	ret +	.size get_mangled_e820, . - get_mangled_e820 + +/**************************************************************************** + * INT 15,e820 handler + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +int15_e820: +	pushw	%ds +	pushw	%cs:rm_ds +	popw	%ds +	call	get_mangled_e820 +	popw	%ds +	call	patch_cf +	iret +	.size int15_e820, . - int15_e820 +	 +/**************************************************************************** + * INT 15,e801 handler + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +int15_e801: +	/* Call previous handler */ +	pushfw +	lcall	*%cs:int15_vector +	call	patch_cf +	/* Edit result */ +	pushw	%ds +	pushw	%cs:rm_ds +	popw	%ds +	call	patch_1m_16m +	xchgw	%ax, %cx +	xchgw	%bx, %dx +	call	patch_1m_16m +	xchgw	%ax, %cx +	xchgw	%bx, %dx +	popw	%ds +	iret +	.size int15_e801, . - int15_e801 +	 +/**************************************************************************** + * INT 15,88 handler + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +int15_88: +	/* Call previous handler */ +	pushfw +	lcall	*%cs:int15_vector +	call	patch_cf +	/* Edit result */ +	pushw	%ds +	pushw	%cs:rm_ds +	popw	%ds +	call	patch_1m +	popw	%ds +	iret +	.size int15_88, . - int15_88 +		 +/**************************************************************************** + * INT 15 handler + **************************************************************************** + */ +	.section ".text16", "ax", @progbits +	.globl int15 +int15: +	/* See if we want to intercept this call */ +	pushfw +	cmpw	$0xe820, %ax +	jne	1f +	cmpl	$SMAP, %edx +	jne	1f +	popfw +	jmp	int15_e820 +1:	cmpw	$0xe801, %ax +	jne	2f +	popfw +	jmp	int15_e801 +2:	cmpb	$0x88, %ah +	jne	3f +	popfw +	jmp	int15_88 +3:	popfw +	ljmp	*%cs:int15_vector +	.size int15, . - int15 +	 +	.section ".text16.data", "aw", @progbits +	.globl int15_vector +int15_vector: +	.long 0 +	.size int15_vector, . - int15_vector diff --git a/roms/ipxe/src/arch/i386/firmware/pcbios/fakee820.c b/roms/ipxe/src/arch/i386/firmware/pcbios/fakee820.c new file mode 100644 index 00000000..e5f71372 --- /dev/null +++ b/roms/ipxe/src/arch/i386/firmware/pcbios/fakee820.c @@ -0,0 +1,94 @@ +/* Copyright (C) 2008 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 ); + +#include <realmode.h> +#include <biosint.h> + +/** Assembly routine in inline asm */ +extern void int15_fakee820(); + +/** Original INT 15 handler */ +static struct segoff __text16 ( real_int15_vector ); +#define real_int15_vector __use_text16 ( real_int15_vector ) + +/** An INT 15,e820 memory map entry */ +struct e820_entry { +	/** Start of region */ +	uint64_t start; +	/** Length of region */ +	uint64_t len; +	/** Type of region */ +	uint32_t type; +} __attribute__ (( packed )); + +#define E820_TYPE_RAM		1 /**< Normal memory */ +#define E820_TYPE_RSVD		2 /**< Reserved and unavailable */ +#define E820_TYPE_ACPI		3 /**< ACPI reclaim memory */ +#define E820_TYPE_NVS		4 /**< ACPI NVS memory */ + +/** Fake e820 map */ +static struct e820_entry __text16_array ( e820map, [] ) __used = { +	{ 0x00000000ULL, ( 0x000a0000ULL - 0x00000000ULL ), E820_TYPE_RAM }, +	{ 0x00100000ULL, ( 0xcfb50000ULL - 0x00100000ULL ), E820_TYPE_RAM }, +	{ 0xcfb50000ULL, ( 0xcfb64000ULL - 0xcfb50000ULL ), E820_TYPE_RSVD }, +	{ 0xcfb64000ULL, ( 0xcfb66000ULL - 0xcfb64000ULL ), E820_TYPE_RSVD }, +	{ 0xcfb66000ULL, ( 0xcfb85c00ULL - 0xcfb66000ULL ), E820_TYPE_ACPI }, +	{ 0xcfb85c00ULL, ( 0xd0000000ULL - 0xcfb85c00ULL ), E820_TYPE_RSVD }, +	{ 0xe0000000ULL, ( 0xf0000000ULL - 0xe0000000ULL ), E820_TYPE_RSVD }, +	{ 0xfe000000ULL, (0x100000000ULL - 0xfe000000ULL ), E820_TYPE_RSVD }, +	{0x100000000ULL, (0x230000000ULL -0x100000000ULL ), E820_TYPE_RAM }, +}; +#define e820map __use_text16 ( e820map ) + +void fake_e820 ( void ) { +	__asm__ __volatile__ ( +		TEXT16_CODE ( "\nint15_fakee820:\n\t" +			      "pushfw\n\t" +			      "cmpl $0xe820, %%eax\n\t" +			      "jne 99f\n\t" +			      "cmpl $0x534d4150, %%edx\n\t" +			      "jne 99f\n\t" +			      "pushaw\n\t" +			      "movw %%sp, %%bp\n\t" +			      "andb $~0x01, 22(%%bp)\n\t" /* Clear return CF */ +			      "leaw e820map(%%bx), %%si\n\t" +			      "cs rep movsb\n\t" +			      "popaw\n\t" +			      "movl %%edx, %%eax\n\t" +			      "addl $20, %%ebx\n\t" +			      "cmpl %0, %%ebx\n\t" +			      "jne 1f\n\t" +			      "xorl %%ebx,%%ebx\n\t" +			      "\n1:\n\t" +			      "popfw\n\t" +			      "iret\n\t" +			      "\n99:\n\t" +			      "popfw\n\t" +			      "ljmp *%%cs:real_int15_vector\n\t" ) +		: : "i" ( sizeof ( e820map ) ) ); + +	hook_bios_interrupt ( 0x15, ( unsigned int ) int15_fakee820, +			      &real_int15_vector ); +} + +void unfake_e820 ( void ) { +	unhook_bios_interrupt ( 0x15, ( unsigned int ) int15_fakee820, +				&real_int15_vector ); +} diff --git a/roms/ipxe/src/arch/i386/firmware/pcbios/hidemem.c b/roms/ipxe/src/arch/i386/firmware/pcbios/hidemem.c new file mode 100644 index 00000000..8f3069e1 --- /dev/null +++ b/roms/ipxe/src/arch/i386/firmware/pcbios/hidemem.c @@ -0,0 +1,231 @@ +/* Copyright (C) 2006 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 ); + +#include <assert.h> +#include <realmode.h> +#include <biosint.h> +#include <basemem.h> +#include <fakee820.h> +#include <ipxe/init.h> +#include <ipxe/io.h> +#include <ipxe/hidemem.h> + +/** Set to true if you want to test a fake E820 map */ +#define FAKE_E820 0 + +/** Alignment for hidden memory regions */ +#define ALIGN_HIDDEN 4096   /* 4kB page alignment should be enough */ + +/** + * A hidden region of iPXE + * + * This represents a region that will be edited out of the system's + * memory map. + * + * This structure is accessed by assembly code, so must not be + * changed. + */ +struct hidden_region { +	/** Physical start address */ +	uint64_t start; +	/** Physical end address */ +	uint64_t end; +}; + +/** Hidden base memory */ +extern struct hidden_region __data16 ( hidemem_base ); +#define hidemem_base __use_data16 ( hidemem_base ) + +/** Hidden umalloc memory */ +extern struct hidden_region __data16 ( hidemem_umalloc ); +#define hidemem_umalloc __use_data16 ( hidemem_umalloc ) + +/** Hidden text memory */ +extern struct hidden_region __data16 ( hidemem_textdata ); +#define hidemem_textdata __use_data16 ( hidemem_textdata ) + +/** Assembly routine in e820mangler.S */ +extern void int15(); + +/** Vector for storing original INT 15 handler */ +extern struct segoff __text16 ( int15_vector ); +#define int15_vector __use_text16 ( int15_vector ) + +/* The linker defines these symbols for us */ +extern char _textdata[]; +extern char _etextdata[]; +extern char _text16_memsz[]; +#define _text16_memsz ( ( unsigned int ) _text16_memsz ) +extern char _data16_memsz[]; +#define _data16_memsz ( ( unsigned int ) _data16_memsz ) + +/** + * Hide region of memory from system memory map + * + * @v region		Hidden memory region + * @v start		Start of region + * @v end		End of region + */ +static void hide_region ( struct hidden_region *region, +			  physaddr_t start, physaddr_t end ) { + +	/* Some operating systems get a nasty shock if a region of the +	 * E820 map seems to start on a non-page boundary.  Make life +	 * safer by rounding out our edited region. +	 */ +	region->start = ( start & ~( ALIGN_HIDDEN - 1 ) ); +	region->end = ( ( end + ALIGN_HIDDEN - 1 ) & ~( ALIGN_HIDDEN - 1 ) ); + +	DBG ( "Hiding region [%llx,%llx)\n", region->start, region->end ); +} + +/** + * Hide used base memory + * + */ +void hide_basemem ( void ) { +	/* Hide from the top of free base memory to 640kB.  Don't use +	 * hide_region(), because we don't want this rounded to the +	 * nearest page boundary. +	 */ +	hidemem_base.start = ( get_fbms() * 1024 ); +} + +/** + * Hide umalloc() region + * + */ +void hide_umalloc ( physaddr_t start, physaddr_t end ) { +	assert ( end <= virt_to_phys ( _textdata ) ); +	hide_region ( &hidemem_umalloc, start, end ); +} + +/** + * Hide .text and .data + * + */ +void hide_textdata ( void ) { +	hide_region ( &hidemem_textdata, virt_to_phys ( _textdata ), +		      virt_to_phys ( _etextdata ) ); +} + +/** + * Hide Etherboot + * + * Installs an INT 15 handler to edit Etherboot out of the memory map + * returned by the BIOS. + */ +static void hide_etherboot ( void ) { +	struct memory_map memmap; +	unsigned int rm_ds_top; +	unsigned int rm_cs_top; +	unsigned int fbms; + +	/* Dump memory map before mangling */ +	DBG ( "Hiding iPXE from system memory map\n" ); +	get_memmap ( &memmap ); + +	/* Hook in fake E820 map, if we're testing one */ +	if ( FAKE_E820 ) { +		DBG ( "Hooking in fake E820 map\n" ); +		fake_e820(); +		get_memmap ( &memmap ); +	} + +	/* Initialise the hidden regions */ +	hide_basemem(); +	hide_umalloc ( virt_to_phys ( _textdata ), virt_to_phys ( _textdata ) ); +	hide_textdata(); + +	/* Some really moronic BIOSes bring up the PXE stack via the +	 * UNDI loader entry point and then don't bother to unload it +	 * before overwriting the code and data segments.  If this +	 * happens, we really don't want to leave INT 15 hooked, +	 * because that will cause any loaded OS to die horribly as +	 * soon as it attempts to fetch the system memory map. +	 * +	 * We use a heuristic to guess whether or not we are being +	 * loaded sensibly. +	 */ +	rm_cs_top = ( ( ( rm_cs << 4 ) + _text16_memsz + 1024 - 1 ) >> 10 ); +	rm_ds_top = ( ( ( rm_ds << 4 ) + _data16_memsz + 1024 - 1 ) >> 10 ); +	fbms = get_fbms(); +	if ( ( rm_cs_top < fbms ) && ( rm_ds_top < fbms ) ) { +		DBG ( "Detected potentially unsafe UNDI load at CS=%04x " +		      "DS=%04x FBMS=%dkB\n", rm_cs, rm_ds, fbms ); +		DBG ( "Disabling INT 15 memory hiding\n" ); +		return; +	} + +	/* Hook INT 15 */ +	hook_bios_interrupt ( 0x15, ( unsigned int ) int15, +			      &int15_vector ); + +	/* Dump memory map after mangling */ +	DBG ( "Hidden iPXE from system memory map\n" ); +	get_memmap ( &memmap ); +} + +/** + * Unhide Etherboot + * + * Uninstalls the INT 15 handler installed by hide_etherboot(), if + * possible. + */ +static void unhide_etherboot ( int flags __unused ) { +	struct memory_map memmap; +	int rc; + +	/* If we have more than one hooked interrupt at this point, it +	 * means that some other vector is still hooked, in which case +	 * we can't safely unhook INT 15 because we need to keep our +	 * memory protected.  (We expect there to be at least one +	 * hooked interrupt, because INT 15 itself is still hooked). +	 */ +	if ( hooked_bios_interrupts > 1 ) { +		DBG ( "Cannot unhide: %d interrupt vectors still hooked\n", +		      hooked_bios_interrupts ); +		return; +	} + +	/* Try to unhook INT 15 */ +	if ( ( rc = unhook_bios_interrupt ( 0x15, ( unsigned int ) int15, +					    &int15_vector ) ) != 0 ) { +		DBG ( "Cannot unhook INT15: %s\n", strerror ( rc ) ); +		/* Leave it hooked; there's nothing else we can do, +		 * and it should be intrinsically safe (though +		 * wasteful of RAM). +		 */ +	} + +	/* Unhook fake E820 map, if used */ +	if ( FAKE_E820 ) +		unfake_e820(); + +	/* Dump memory map after unhiding */ +	DBG ( "Unhidden iPXE from system memory map\n" ); +	get_memmap ( &memmap ); +} + +/** Hide Etherboot startup function */ +struct startup_fn hide_etherboot_startup_fn __startup_fn ( STARTUP_EARLY ) = { +	.startup = hide_etherboot, +	.shutdown = unhide_etherboot, +}; diff --git a/roms/ipxe/src/arch/i386/firmware/pcbios/memmap.c b/roms/ipxe/src/arch/i386/firmware/pcbios/memmap.c new file mode 100644 index 00000000..0937a7ce --- /dev/null +++ b/roms/ipxe/src/arch/i386/firmware/pcbios/memmap.c @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2006 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 ); + +#include <stdint.h> +#include <errno.h> +#include <realmode.h> +#include <bios.h> +#include <memsizes.h> +#include <ipxe/io.h> + +/** + * @file + * + * Memory mapping + * + */ + +/** Magic value for INT 15,e820 calls */ +#define SMAP ( 0x534d4150 ) + +/** An INT 15,e820 memory map entry */ +struct e820_entry { +	/** Start of region */ +	uint64_t start; +	/** Length of region */ +	uint64_t len; +	/** Type of region */ +	uint32_t type; +	/** Extended attributes (optional) */ +	uint32_t attrs; +} __attribute__ (( packed )); + +#define E820_TYPE_RAM		1 /**< Normal memory */ +#define E820_TYPE_RESERVED	2 /**< Reserved and unavailable */ +#define E820_TYPE_ACPI		3 /**< ACPI reclaim memory */ +#define E820_TYPE_NVS		4 /**< ACPI NVS memory */ + +#define E820_ATTR_ENABLED	0x00000001UL +#define E820_ATTR_NONVOLATILE	0x00000002UL +#define E820_ATTR_UNKNOWN	0xfffffffcUL + +#define E820_MIN_SIZE		20 + +/** Buffer for INT 15,e820 calls */ +static struct e820_entry __bss16 ( e820buf ); +#define e820buf __use_data16 ( e820buf ) + +/** We are running during POST; inhibit INT 15,e820 and INT 15,e801 */ +uint8_t __bss16 ( memmap_post ); +#define memmap_post __use_data16 ( memmap_post ) + +/** + * Get size of extended memory via INT 15,e801 + * + * @ret extmem		Extended memory size, in kB, or 0 + */ +static unsigned int extmemsize_e801 ( void ) { +	uint16_t extmem_1m_to_16m_k, extmem_16m_plus_64k; +	uint16_t confmem_1m_to_16m_k, confmem_16m_plus_64k; +	unsigned int flags; +	unsigned int extmem; + +	/* Inhibit INT 15,e801 during POST */ +	if ( memmap_post ) { +		DBG ( "INT 15,e801 not available during POST\n" ); +		return 0; +	} + +	__asm__ __volatile__ ( REAL_CODE ( "stc\n\t" +					   "int $0x15\n\t" +					   "pushfw\n\t" +					   "popw %w0\n\t" ) +			       : "=r" ( flags ), +				 "=a" ( extmem_1m_to_16m_k ), +				 "=b" ( extmem_16m_plus_64k ), +				 "=c" ( confmem_1m_to_16m_k ), +				 "=d" ( confmem_16m_plus_64k ) +			       : "a" ( 0xe801 ) ); + +	if ( flags & CF ) { +		DBG ( "INT 15,e801 failed with CF set\n" ); +		return 0; +	} + +	if ( ! ( extmem_1m_to_16m_k | extmem_16m_plus_64k ) ) { +		DBG ( "INT 15,e801 extmem=0, using confmem\n" ); +		extmem_1m_to_16m_k = confmem_1m_to_16m_k; +		extmem_16m_plus_64k = confmem_16m_plus_64k; +	} + +	extmem = ( extmem_1m_to_16m_k + ( extmem_16m_plus_64k * 64 ) ); +	DBG ( "INT 15,e801 extended memory size %d+64*%d=%d kB " +	      "[100000,%llx)\n", extmem_1m_to_16m_k, extmem_16m_plus_64k, +	      extmem, ( 0x100000 + ( ( ( uint64_t ) extmem ) * 1024 ) ) ); + +	/* Sanity check.  Some BIOSes report the entire 4GB address +	 * space as available, which cannot be correct (since that +	 * would leave no address space available for 32-bit PCI +	 * BARs). +	 */ +	if ( extmem == ( 0x400000 - 0x400 ) ) { +		DBG ( "INT 15,e801 reported whole 4GB; assuming insane\n" ); +		return 0; +	} + +	return extmem; +} + +/** + * Get size of extended memory via INT 15,88 + * + * @ret extmem		Extended memory size, in kB + */ +static unsigned int extmemsize_88 ( void ) { +	uint16_t extmem; + +	/* Ignore CF; it is not reliable for this call */ +	__asm__ __volatile__ ( REAL_CODE ( "int $0x15" ) +			       : "=a" ( extmem ) : "a" ( 0x8800 ) ); + +	DBG ( "INT 15,88 extended memory size %d kB [100000, %x)\n", +	      extmem, ( 0x100000 + ( extmem * 1024 ) ) ); +	return extmem; +} + +/** + * Get size of extended memory + * + * @ret extmem		Extended memory size, in kB + * + * Note that this is only an approximation; for an accurate picture, + * use the E820 memory map obtained via get_memmap(); + */ +unsigned int extmemsize ( void ) { +	unsigned int extmem_e801; +	unsigned int extmem_88; + +	/* Try INT 15,e801 first, then fall back to INT 15,88 */ +	extmem_88 = extmemsize_88(); +	extmem_e801 = extmemsize_e801(); +	return ( extmem_e801 ? extmem_e801 : extmem_88 ); +} + +/** + * Get e820 memory map + * + * @v memmap		Memory map to fill in + * @ret rc		Return status code + */ +static int meme820 ( struct memory_map *memmap ) { +	struct memory_region *region = memmap->regions; +	struct memory_region *prev_region = NULL; +	uint32_t next = 0; +	uint32_t smap; +	size_t size; +	unsigned int flags; +	unsigned int discard_D; + +	/* Inhibit INT 15,e820 during POST */ +	if ( memmap_post ) { +		DBG ( "INT 15,e820 not available during POST\n" ); +		return -ENOTTY; +	} + +	/* Clear the E820 buffer.  Do this once before starting, +	 * rather than on each call; some BIOSes rely on the contents +	 * being preserved between calls. +	 */ +	memset ( &e820buf, 0, sizeof ( e820buf ) ); + +	do { +		/* Some BIOSes corrupt %esi for fun. Guard against +		 * this by telling gcc that all non-output registers +		 * may be corrupted. +		 */ +		__asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" +						   "stc\n\t" +						   "int $0x15\n\t" +						   "pushfw\n\t" +						   "popw %%dx\n\t" +						   "popl %%ebp\n\t" ) +				       : "=a" ( smap ), "=b" ( next ), +					 "=c" ( size ), "=d" ( flags ), +					 "=D" ( discard_D ) +				       : "a" ( 0xe820 ), "b" ( next ), +					 "D" ( __from_data16 ( &e820buf ) ), +					 "c" ( sizeof ( e820buf ) ), +					 "d" ( SMAP ) +				       : "esi", "memory" ); + +		if ( smap != SMAP ) { +			DBG ( "INT 15,e820 failed SMAP signature check\n" ); +			return -ENOTSUP; +		} + +		if ( size < E820_MIN_SIZE ) { +			DBG ( "INT 15,e820 returned only %zd bytes\n", size ); +			return -EINVAL; +		} + +		if ( flags & CF ) { +			DBG ( "INT 15,e820 terminated on CF set\n" ); +			break; +		} + +		/* If first region is not RAM, assume map is invalid */ +		if ( ( memmap->count == 0 ) && +		     ( e820buf.type != E820_TYPE_RAM ) ) { +		       DBG ( "INT 15,e820 failed, first entry not RAM\n" ); +		       return -EINVAL; +		} + +		DBG ( "INT 15,e820 region [%llx,%llx) type %d", +		      e820buf.start, ( e820buf.start + e820buf.len ), +		      ( int ) e820buf.type ); +		if ( size > offsetof ( typeof ( e820buf ), attrs ) ) { +			DBG ( " (%s", ( ( e820buf.attrs & E820_ATTR_ENABLED ) +					? "enabled" : "disabled" ) ); +			if ( e820buf.attrs & E820_ATTR_NONVOLATILE ) +				DBG ( ", non-volatile" ); +			if ( e820buf.attrs & E820_ATTR_UNKNOWN ) +				DBG ( ", other [%08x]", e820buf.attrs ); +			DBG ( ")" ); +		} +		DBG ( "\n" ); + +		/* Discard non-RAM regions */ +		if ( e820buf.type != E820_TYPE_RAM ) +			continue; + +		/* Check extended attributes, if present */ +		if ( size > offsetof ( typeof ( e820buf ), attrs ) ) { +			if ( ! ( e820buf.attrs & E820_ATTR_ENABLED ) ) +				continue; +			if ( e820buf.attrs & E820_ATTR_NONVOLATILE ) +				continue; +		} + +		region->start = e820buf.start; +		region->end = e820buf.start + e820buf.len; + +		/* Check for adjacent regions and merge them */ +		if ( prev_region && ( region->start == prev_region->end ) ) { +			prev_region->end = region->end; +		} else { +			prev_region = region; +			region++; +			memmap->count++; +		} + +		if ( memmap->count >= ( sizeof ( memmap->regions ) / +					sizeof ( memmap->regions[0] ) ) ) { +			DBG ( "INT 15,e820 too many regions returned\n" ); +			/* Not a fatal error; what we've got so far at +			 * least represents valid regions of memory, +			 * even if we couldn't get them all. +			 */ +			break; +		} +	} while ( next != 0 ); + +	/* Sanity checks.  Some BIOSes report complete garbage via INT +	 * 15,e820 (especially at POST time), despite passing the +	 * signature checks.  We currently check for a base memory +	 * region (starting at 0) and at least one high memory region +	 * (starting at 0x100000). +	 */ +	if ( memmap->count < 2 ) { +		DBG ( "INT 15,e820 returned only %d regions; assuming " +		      "insane\n", memmap->count ); +		return -EINVAL; +	} +	if ( memmap->regions[0].start != 0 ) { +		DBG ( "INT 15,e820 region 0 starts at %llx (expected 0); " +		      "assuming insane\n", memmap->regions[0].start ); +		return -EINVAL; +	} +	if ( memmap->regions[1].start != 0x100000 ) { +		DBG ( "INT 15,e820 region 1 starts at %llx (expected 100000); " +		      "assuming insane\n", memmap->regions[0].start ); +		return -EINVAL; +	} + +	return 0; +} + +/** + * Get memory map + * + * @v memmap		Memory map to fill in + */ +void x86_get_memmap ( struct memory_map *memmap ) { +	unsigned int basemem, extmem; +	int rc; + +	DBG ( "Fetching system memory map\n" ); + +	/* Clear memory map */ +	memset ( memmap, 0, sizeof ( *memmap ) ); + +	/* Get base and extended memory sizes */ +	basemem = basememsize(); +	DBG ( "FBMS base memory size %d kB [0,%x)\n", +	      basemem, ( basemem * 1024 ) ); +	extmem = extmemsize(); +	 +	/* Try INT 15,e820 first */ +	if ( ( rc = meme820 ( memmap ) ) == 0 ) { +		DBG ( "Obtained system memory map via INT 15,e820\n" ); +		return; +	} + +	/* Fall back to constructing a map from basemem and extmem sizes */ +	DBG ( "INT 15,e820 failed; constructing map\n" ); +	memmap->regions[0].end = ( basemem * 1024 ); +	memmap->regions[1].start = 0x100000; +	memmap->regions[1].end = 0x100000 + ( extmem * 1024 ); +	memmap->count = 2; +} + +PROVIDE_IOAPI ( x86, get_memmap, x86_get_memmap ); diff --git a/roms/ipxe/src/arch/i386/firmware/pcbios/pnpbios.c b/roms/ipxe/src/arch/i386/firmware/pcbios/pnpbios.c new file mode 100644 index 00000000..5c74b043 --- /dev/null +++ b/roms/ipxe/src/arch/i386/firmware/pcbios/pnpbios.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 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 ); + +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <realmode.h> +#include <pnpbios.h> + +/** @file + * + * PnP BIOS + * + */ + +/** PnP BIOS structure */ +struct pnp_bios { +	/** Signature +	 * +	 * Must be equal to @c PNP_BIOS_SIGNATURE +	 */ +	uint32_t signature; +	/** Version as BCD (e.g. 1.0 is 0x10) */ +	uint8_t version; +	/** Length of this structure */ +	uint8_t length; +	/** System capabilities */ +	uint16_t control; +	/** Checksum */ +	uint8_t checksum; +} __attribute__ (( packed )); + +/** Signature for a PnP BIOS structure */ +#define PNP_BIOS_SIGNATURE \ +	( ( '$' << 0 ) + ( 'P' << 8 ) + ( 'n' << 16 ) + ( 'P' << 24 ) ) + +/** + * Test address for PnP BIOS structure + * + * @v offset		Offset within BIOS segment to test + * @ret rc		Return status code + */ +static int is_pnp_bios ( unsigned int offset ) { +	union { +		struct pnp_bios pnp_bios; +		uint8_t bytes[256]; /* 256 is maximum length possible */ +	} u; +	size_t len; +	unsigned int i; +	uint8_t sum = 0; + +	/* Read start of header and verify signature */ +	copy_from_real ( &u.pnp_bios, BIOS_SEG, offset, sizeof ( u.pnp_bios )); +	if ( u.pnp_bios.signature != PNP_BIOS_SIGNATURE ) +		return -EINVAL; + +	/* Read whole header and verify checksum */ +	len = u.pnp_bios.length; +	copy_from_real ( &u.bytes, BIOS_SEG, offset, len ); +	for ( i = 0 ; i < len ; i++ ) { +		sum += u.bytes[i]; +	} +	if ( sum != 0 ) +		return -EINVAL; + +	DBG ( "Found PnP BIOS at %04x:%04x\n", BIOS_SEG, offset ); + +	return 0; +} + +/** + * Locate Plug-and-Play BIOS + * + * @ret pnp_offset	Offset of PnP BIOS structure within BIOS segment + * + * The PnP BIOS structure will be at BIOS_SEG:pnp_offset.  If no PnP + * BIOS is found, -1 is returned. + */ +int find_pnp_bios ( void ) { +	static int pnp_offset = 0; + +	if ( pnp_offset ) +		return pnp_offset; + +	for ( pnp_offset = 0 ; pnp_offset < 0x10000 ; pnp_offset += 0x10 ) { +		if ( is_pnp_bios ( pnp_offset ) == 0 ) +			return pnp_offset; +	} + +	pnp_offset = -1; +	return pnp_offset; +} | 
