mirror of
https://github.com/ipxe/ipxe
synced 2025-12-22 04:50:25 +03:00
[bios] Add bin-x86_64-pcbios build platform
Move most arch/i386 files to arch/x86, and adjust the contents of the Makefiles and the include/bits/*.h headers to reflect the new locations. This patch makes no substantive code changes, as can be seen using a rename-aware diff (e.g. "git show -M5"). This patch does not make the pcbios platform functional for x86_64; it merely allows it to compile without errors. Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
112
src/arch/x86/interface/pcbios/apm.c
Normal file
112
src/arch/x86/interface/pcbios/apm.c
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Marin Hannache <ipxe@mareo.fr>.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Advanced Power Management
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <realmode.h>
|
||||
#include <ipxe/reboot.h>
|
||||
|
||||
/**
|
||||
* Power off the computer using APM
|
||||
*
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int apm_poweroff ( void ) {
|
||||
uint16_t apm_version;
|
||||
uint16_t apm_signature;
|
||||
uint16_t apm_flags;
|
||||
uint16_t carry;
|
||||
|
||||
/* APM check */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x15\n\t"
|
||||
"adc %%edx,0\n\t" )
|
||||
: "=a" ( apm_version ), "=b" ( apm_signature ),
|
||||
"=c" ( apm_flags ), "=d" ( carry )
|
||||
: "a" ( 0x5300 ), "b" ( 0x0000 ),
|
||||
"d" ( 0x0000 ) );
|
||||
if ( carry ) {
|
||||
DBG ( "APM not present\n" );
|
||||
return -ENOTSUP;
|
||||
}
|
||||
if ( apm_signature != 0x504d ) { /* signature 'PM' */
|
||||
DBG ( "APM not present\n" );
|
||||
return -ENOTSUP;
|
||||
}
|
||||
if ( apm_version < 0x0101 ) { /* Need version 1.1+ */
|
||||
DBG ( "APM 1.1+ not supported\n" );
|
||||
return -ENOTSUP;
|
||||
}
|
||||
if ( ( apm_flags & 0x8 ) == 0x8 ) {
|
||||
DBG ( "APM power management disabled\n" );
|
||||
return -EPERM;
|
||||
}
|
||||
DBG2 ( "APM check completed\n" );
|
||||
|
||||
/* APM initialisation */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x15\n\t"
|
||||
"adc %%edx,0\n\t" )
|
||||
: "=d" ( carry )
|
||||
: "a" ( 0x5301 ), "b" ( 0x0000 ),
|
||||
"d" ( 0x0000 ) );
|
||||
if ( carry ) {
|
||||
DBG ( "APM initialisation failed\n" );
|
||||
return -EIO;
|
||||
}
|
||||
DBG2 ( "APM initialisation completed\n" );
|
||||
|
||||
/* Set APM driver version */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x15\n\t"
|
||||
"adc %%edx,0\n\t" )
|
||||
: "=d" ( carry )
|
||||
: "a" ( 0x530e ), "b" ( 0x0000 ),
|
||||
"c" ( 0x0101 ), "d" ( 0x0000 ) );
|
||||
if ( carry ) {
|
||||
DBG ( "APM setting driver version failed\n" );
|
||||
return -EIO;
|
||||
}
|
||||
DBG2 ( "APM driver version set\n" );
|
||||
|
||||
/* Setting power state to off */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x15\n\t"
|
||||
"adc %%edx,0\n\t" )
|
||||
: "=d" ( carry )
|
||||
: "a" ( 0x5307 ), "b" ( 0x0001 ),
|
||||
"c" ( 0x0003 ), "d" ( 0x0000) );
|
||||
if ( carry ) {
|
||||
DBG ( "APM setting power state failed\n" );
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
/* Should never happen */
|
||||
return -ECANCELED;
|
||||
}
|
||||
|
||||
PROVIDE_REBOOT ( pcbios, poweroff, apm_poweroff );
|
||||
51
src/arch/x86/interface/pcbios/basemem.c
Normal file
51
src/arch/x86/interface/pcbios/basemem.c
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#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();
|
||||
}
|
||||
566
src/arch/x86/interface/pcbios/bios_console.c
Normal file
566
src/arch/x86/interface/pcbios/bios_console.c
Normal file
@@ -0,0 +1,566 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#include <assert.h>
|
||||
#include <realmode.h>
|
||||
#include <bios.h>
|
||||
#include <biosint.h>
|
||||
#include <ipxe/console.h>
|
||||
#include <ipxe/ansiesc.h>
|
||||
#include <ipxe/keys.h>
|
||||
#include <ipxe/keymap.h>
|
||||
#include <ipxe/init.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_BLINK 0x80
|
||||
|
||||
#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;
|
||||
|
||||
/** Keypress injection lock */
|
||||
static uint8_t __text16 ( bios_inject_lock );
|
||||
#define bios_inject_lock __use_text16 ( bios_inject_lock )
|
||||
|
||||
/** Vector for chaining to other INT 16 handlers */
|
||||
static struct segoff __text16 ( int16_vector );
|
||||
#define int16_vector __use_text16 ( int16_vector )
|
||||
|
||||
/** Assembly wrapper */
|
||||
extern void int16_wrapper ( void );
|
||||
|
||||
/**
|
||||
* 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 == 5 ) {
|
||||
bios_attr |= ATTR_BLINK;
|
||||
} else if ( aspect == 22 ) {
|
||||
bios_attr &= ~ATTR_BOLD;
|
||||
} else if ( aspect == 25 ) {
|
||||
bios_attr &= ~ATTR_BLINK;
|
||||
} 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 *bios_ansi_input = "";
|
||||
|
||||
/** A BIOS key */
|
||||
struct bios_key {
|
||||
/** Scancode */
|
||||
uint8_t scancode;
|
||||
/** Key code */
|
||||
uint16_t key;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** Mapping from BIOS scan codes to iPXE key codes */
|
||||
static const struct bios_key bios_keys[] = {
|
||||
{ 0x53, KEY_DC },
|
||||
{ 0x48, KEY_UP },
|
||||
{ 0x50, KEY_DOWN },
|
||||
{ 0x4b, KEY_LEFT },
|
||||
{ 0x4d, KEY_RIGHT },
|
||||
{ 0x47, KEY_HOME },
|
||||
{ 0x4f, KEY_END },
|
||||
{ 0x49, KEY_PPAGE },
|
||||
{ 0x51, KEY_NPAGE },
|
||||
{ 0x3f, KEY_F5 },
|
||||
{ 0x40, KEY_F6 },
|
||||
{ 0x41, KEY_F7 },
|
||||
{ 0x42, KEY_F8 },
|
||||
{ 0x43, KEY_F9 },
|
||||
{ 0x44, KEY_F10 },
|
||||
{ 0x85, KEY_F11 },
|
||||
{ 0x86, KEY_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 * bios_ansi_seq ( unsigned int scancode ) {
|
||||
static char buf[ 5 /* "[" + two digits + terminator + NUL */ ];
|
||||
unsigned int key;
|
||||
unsigned int terminator;
|
||||
unsigned int n;
|
||||
unsigned int i;
|
||||
char *tmp = buf;
|
||||
|
||||
/* Construct ANSI escape sequence for scancode, if known */
|
||||
for ( i = 0 ; i < ( sizeof ( bios_keys ) /
|
||||
sizeof ( bios_keys[0] ) ) ; i++ ) {
|
||||
|
||||
/* Look for matching scancode */
|
||||
if ( bios_keys[i].scancode != scancode )
|
||||
continue;
|
||||
|
||||
/* Construct escape sequence */
|
||||
key = bios_keys[i].key;
|
||||
n = KEY_ANSI_N ( key );
|
||||
terminator = KEY_ANSI_TERMINATOR ( key );
|
||||
*(tmp++) = '[';
|
||||
if ( n )
|
||||
tmp += sprintf ( tmp, "%d", n );
|
||||
*(tmp++) = terminator;
|
||||
*(tmp++) = '\0';
|
||||
assert ( tmp <= &buf[ sizeof ( buf ) ] );
|
||||
return buf;
|
||||
}
|
||||
|
||||
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 = *bios_ansi_input ) ) {
|
||||
bios_ansi_input++;
|
||||
return character;
|
||||
}
|
||||
|
||||
/* Do nothing if injection is in progress */
|
||||
if ( bios_inject_lock )
|
||||
return 0;
|
||||
|
||||
/* Read character from real BIOS console */
|
||||
bios_inject_lock++;
|
||||
__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
|
||||
"int $0x16\n\t"
|
||||
"cli\n\t" )
|
||||
: "=a" ( keypress )
|
||||
: "a" ( 0x1000 ), "m" ( bios_inject_lock ) );
|
||||
bios_inject_lock--;
|
||||
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 = bios_ansi_seq ( keypress >> 8 ) ) ) {
|
||||
/* Start of escape sequence: return ESC (0x1b) */
|
||||
bios_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 ( *bios_ansi_input )
|
||||
return 1;
|
||||
|
||||
/* Do nothing if injection is in progress */
|
||||
if ( bios_inject_lock )
|
||||
return 0;
|
||||
|
||||
/* Otherwise check the real BIOS console */
|
||||
bios_inject_lock++;
|
||||
__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 ), "m" ( bios_inject_lock ) );
|
||||
bios_inject_lock--;
|
||||
return ( ! ( flags & ZF ) );
|
||||
}
|
||||
|
||||
/** BIOS console */
|
||||
struct console_driver bios_console __console_driver = {
|
||||
.putchar = bios_putchar,
|
||||
.getchar = bios_getchar,
|
||||
.iskey = bios_iskey,
|
||||
.usage = CONSOLE_PCBIOS,
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject keypresses
|
||||
*
|
||||
* @v ix86 Registers as passed to INT 16
|
||||
*/
|
||||
static __asmcall void bios_inject ( struct i386_all_regs *ix86 ) {
|
||||
unsigned int discard_a;
|
||||
unsigned int scancode;
|
||||
unsigned int i;
|
||||
uint16_t keypress;
|
||||
int key;
|
||||
|
||||
/* If this is a blocking call, then loop until the
|
||||
* non-blocking variant of the call indicates that a keypress
|
||||
* is available. Do this without acquiring the injection
|
||||
* lock, so that injection may take place.
|
||||
*/
|
||||
if ( ( ix86->regs.ah & ~0x10 ) == 0x00 ) {
|
||||
__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
|
||||
"\n1:\n\t"
|
||||
"pushw %%ax\n\t"
|
||||
"int $0x16\n\t"
|
||||
"popw %%ax\n\t"
|
||||
"jc 2f\n\t"
|
||||
"jz 1b\n\t"
|
||||
"\n2:\n\t"
|
||||
"cli\n\t" )
|
||||
: "=a" ( discard_a )
|
||||
: "a" ( ix86->regs.eax | 0x0100 ),
|
||||
"m" ( bios_inject_lock ) );
|
||||
}
|
||||
|
||||
/* Acquire injection lock */
|
||||
bios_inject_lock++;
|
||||
|
||||
/* Check for keypresses */
|
||||
if ( iskey() ) {
|
||||
|
||||
/* Get key */
|
||||
key = getkey ( 0 );
|
||||
|
||||
/* Reverse internal CR->LF mapping */
|
||||
if ( key == '\n' )
|
||||
key = '\r';
|
||||
|
||||
/* Convert to keypress */
|
||||
keypress = ( ( key << 8 ) | key );
|
||||
|
||||
/* Handle special keys */
|
||||
if ( key >= KEY_MIN ) {
|
||||
for ( i = 0 ; i < ( sizeof ( bios_keys ) /
|
||||
sizeof ( bios_keys[0] ) ) ; i++ ) {
|
||||
if ( bios_keys[i].key == key ) {
|
||||
scancode = bios_keys[i].scancode;
|
||||
keypress = ( scancode << 8 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Inject keypress */
|
||||
DBGC ( &bios_console, "BIOS injecting keypress %04x\n",
|
||||
keypress );
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x16\n\t" )
|
||||
: "=a" ( discard_a )
|
||||
: "a" ( 0x0500 ), "c" ( keypress ),
|
||||
"m" ( bios_inject_lock ) );
|
||||
}
|
||||
|
||||
/* Release injection lock */
|
||||
bios_inject_lock--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start up keypress injection
|
||||
*
|
||||
*/
|
||||
static void bios_inject_startup ( void ) {
|
||||
|
||||
/* Assembly wrapper to call bios_inject() */
|
||||
__asm__ __volatile__ (
|
||||
TEXT16_CODE ( "\nint16_wrapper:\n\t"
|
||||
"pushfw\n\t"
|
||||
"cmpb $0, %%cs:bios_inject_lock\n\t"
|
||||
"jnz 1f\n\t"
|
||||
"pushl %0\n\t"
|
||||
"pushw %%cs\n\t"
|
||||
"call prot_call\n\t"
|
||||
"addw $4, %%sp\n\t"
|
||||
"\n1:\n\t"
|
||||
"popfw\n\t"
|
||||
"ljmp *%%cs:int16_vector\n\t" )
|
||||
: : "i" ( bios_inject ) );
|
||||
|
||||
/* Hook INT 16 */
|
||||
hook_bios_interrupt ( 0x16, ( ( intptr_t ) int16_wrapper ),
|
||||
&int16_vector );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down keypress injection
|
||||
*
|
||||
* @v booting System is shutting down for OS boot
|
||||
*/
|
||||
static void bios_inject_shutdown ( int booting __unused ) {
|
||||
|
||||
/* Unhook INT 16 */
|
||||
unhook_bios_interrupt ( 0x16, ( ( intptr_t ) int16_wrapper ),
|
||||
&int16_vector );
|
||||
}
|
||||
|
||||
/** Keypress injection startup function */
|
||||
struct startup_fn bios_inject_startup_fn __startup_fn ( STARTUP_NORMAL ) = {
|
||||
.startup = bios_inject_startup,
|
||||
.shutdown = bios_inject_shutdown,
|
||||
};
|
||||
16
src/arch/x86/interface/pcbios/bios_nap.c
Normal file
16
src/arch/x86/interface/pcbios/bios_nap.c
Normal file
@@ -0,0 +1,16 @@
|
||||
#include <ipxe/nap.h>
|
||||
#include <realmode.h>
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* Save power by halting the CPU until the next interrupt
|
||||
*
|
||||
*/
|
||||
static void bios_cpu_nap ( void ) {
|
||||
__asm__ __volatile__ ( "sti\n\t"
|
||||
"hlt\n\t"
|
||||
"cli\n\t" );
|
||||
}
|
||||
|
||||
PROVIDE_NAP ( pcbios, cpu_nap, bios_cpu_nap );
|
||||
52
src/arch/x86/interface/pcbios/bios_reboot.c
Normal file
52
src/arch/x86/interface/pcbios/bios_reboot.c
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/** @file
|
||||
*
|
||||
* Standard PC-BIOS reboot mechanism
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ipxe/reboot.h>
|
||||
#include <realmode.h>
|
||||
#include <bios.h>
|
||||
|
||||
/**
|
||||
* Reboot system
|
||||
*
|
||||
* @v warm Perform a warm reboot
|
||||
*/
|
||||
static void bios_reboot ( int warm ) {
|
||||
uint16_t flag;
|
||||
|
||||
/* Configure BIOS for cold/warm reboot */
|
||||
flag = ( warm ? BDA_REBOOT_WARM : 0 );
|
||||
put_real ( flag, BDA_SEG, BDA_REBOOT );
|
||||
|
||||
/* Jump to system reset vector */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "ljmp $0xf000, $0xfff0" ) : : );
|
||||
}
|
||||
|
||||
PROVIDE_REBOOT ( pcbios, reboot, bios_reboot );
|
||||
65
src/arch/x86/interface/pcbios/bios_smbios.c
Normal file
65
src/arch/x86/interface/pcbios/bios_smbios.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <ipxe/uaccess.h>
|
||||
#include <ipxe/smbios.h>
|
||||
#include <realmode.h>
|
||||
#include <pnpbios.h>
|
||||
|
||||
/** @file
|
||||
*
|
||||
* System Management BIOS
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find SMBIOS
|
||||
*
|
||||
* @v smbios SMBIOS entry point descriptor structure to fill in
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int bios_find_smbios ( struct smbios *smbios ) {
|
||||
struct smbios_entry entry;
|
||||
int rc;
|
||||
|
||||
/* Scan through BIOS segment to find SMBIOS entry point */
|
||||
if ( ( rc = find_smbios_entry ( real_to_user ( BIOS_SEG, 0 ), 0x10000,
|
||||
&entry ) ) != 0 )
|
||||
return rc;
|
||||
|
||||
/* Fill in entry point descriptor structure */
|
||||
smbios->address = phys_to_user ( entry.smbios_address );
|
||||
smbios->len = entry.smbios_len;
|
||||
smbios->count = entry.smbios_count;
|
||||
smbios->version = SMBIOS_VERSION ( entry.major, entry.minor );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PROVIDE_SMBIOS ( pcbios, find_smbios, bios_find_smbios );
|
||||
70
src/arch/x86/interface/pcbios/bios_timer.c
Normal file
70
src/arch/x86/interface/pcbios/bios_timer.c
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/** @file
|
||||
*
|
||||
* BIOS timer
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ipxe/timer.h>
|
||||
#include <realmode.h>
|
||||
#include <bios.h>
|
||||
|
||||
/**
|
||||
* Get current system time in ticks
|
||||
*
|
||||
* @ret ticks Current time, in ticks
|
||||
*
|
||||
* Use direct memory access to BIOS variables, longword 0040:006C
|
||||
* (ticks today) and byte 0040:0070 (midnight crossover flag) instead
|
||||
* of calling timeofday BIOS interrupt.
|
||||
*/
|
||||
static unsigned long bios_currticks ( void ) {
|
||||
static int days = 0;
|
||||
uint32_t ticks;
|
||||
uint8_t midnight;
|
||||
|
||||
/* Re-enable interrupts so that the timer interrupt can occur */
|
||||
__asm__ __volatile__ ( "sti\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"cli\n\t" );
|
||||
|
||||
get_real ( ticks, BDA_SEG, 0x006c );
|
||||
get_real ( midnight, BDA_SEG, 0x0070 );
|
||||
|
||||
if ( midnight ) {
|
||||
midnight = 0;
|
||||
put_real ( midnight, BDA_SEG, 0x0070 );
|
||||
days += 0x1800b0;
|
||||
}
|
||||
|
||||
return ( days + ticks );
|
||||
}
|
||||
|
||||
PROVIDE_TIMER_INLINE ( pcbios, udelay );
|
||||
PROVIDE_TIMER ( pcbios, currticks, bios_currticks );
|
||||
PROVIDE_TIMER_INLINE ( pcbios, ticks_per_sec );
|
||||
119
src/arch/x86/interface/pcbios/biosint.c
Normal file
119
src/arch/x86/interface/pcbios/biosint.c
Normal file
@@ -0,0 +1,119 @@
|
||||
#include <errno.h>
|
||||
#include <realmode.h>
|
||||
#include <biosint.h>
|
||||
|
||||
/**
|
||||
* @file BIOS interrupts
|
||||
*
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* Hook INT vector
|
||||
*
|
||||
* @v interrupt INT number
|
||||
* @v handler Offset within .text16 to interrupt handler
|
||||
* @v chain_vector Vector for chaining to previous handler
|
||||
*
|
||||
* Hooks in an i386 INT handler. The handler itself must reside
|
||||
* within the .text16 segment. @c chain_vector will be filled in with
|
||||
* the address of the previously-installed handler for this interrupt;
|
||||
* the handler should probably exit by ljmping via this vector.
|
||||
*/
|
||||
void hook_bios_interrupt ( unsigned int interrupt, unsigned int handler,
|
||||
struct segoff *chain_vector ) {
|
||||
struct segoff vector = {
|
||||
.segment = rm_cs,
|
||||
.offset = handler,
|
||||
};
|
||||
|
||||
DBG ( "Hooking INT %#02x to %04x:%04x\n",
|
||||
interrupt, rm_cs, handler );
|
||||
|
||||
if ( ( chain_vector->segment != 0 ) ||
|
||||
( chain_vector->offset != 0 ) ) {
|
||||
/* Already hooked; do nothing */
|
||||
DBG ( "...already hooked\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
copy_from_real ( chain_vector, 0, ( interrupt * 4 ),
|
||||
sizeof ( *chain_vector ) );
|
||||
DBG ( "...chaining to %04x:%04x\n",
|
||||
chain_vector->segment, chain_vector->offset );
|
||||
if ( DBG_LOG ) {
|
||||
char code[64];
|
||||
copy_from_real ( code, chain_vector->segment,
|
||||
chain_vector->offset, sizeof ( code ) );
|
||||
DBG_HDA ( *chain_vector, code, sizeof ( code ) );
|
||||
}
|
||||
|
||||
copy_to_real ( 0, ( interrupt * 4 ), &vector, sizeof ( vector ) );
|
||||
hooked_bios_interrupts++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unhook INT vector
|
||||
*
|
||||
* @v interrupt INT number
|
||||
* @v handler Offset within .text16 to interrupt handler
|
||||
* @v chain_vector Vector containing address of previous handler
|
||||
*
|
||||
* Unhooks an i386 interrupt handler hooked by hook_i386_vector().
|
||||
* Note that this operation may fail, if some external code has hooked
|
||||
* the vector since we hooked in our handler. If it fails, it means
|
||||
* that it is not possible to unhook our handler, and we must leave it
|
||||
* (and its chaining vector) resident in memory.
|
||||
*/
|
||||
int unhook_bios_interrupt ( unsigned int interrupt, unsigned int handler,
|
||||
struct segoff *chain_vector ) {
|
||||
struct segoff vector;
|
||||
|
||||
DBG ( "Unhooking INT %#02x from %04x:%04x\n",
|
||||
interrupt, rm_cs, handler );
|
||||
|
||||
copy_from_real ( &vector, 0, ( interrupt * 4 ), sizeof ( vector ) );
|
||||
if ( ( vector.segment != rm_cs ) || ( vector.offset != handler ) ) {
|
||||
DBG ( "...cannot unhook; vector points to %04x:%04x\n",
|
||||
vector.segment, vector.offset );
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
DBG ( "...restoring to %04x:%04x\n",
|
||||
chain_vector->segment, chain_vector->offset );
|
||||
copy_to_real ( 0, ( interrupt * 4 ), chain_vector,
|
||||
sizeof ( *chain_vector ) );
|
||||
|
||||
chain_vector->segment = 0;
|
||||
chain_vector->offset = 0;
|
||||
hooked_bios_interrupts--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump changes to interrupt vector table (for debugging)
|
||||
*
|
||||
*/
|
||||
void check_bios_interrupts ( void ) {
|
||||
static struct segoff vectors[256];
|
||||
static uint8_t initialised;
|
||||
struct segoff vector;
|
||||
unsigned int i;
|
||||
|
||||
/* Print any changed interrupt vectors */
|
||||
for ( i = 0; i < ( sizeof ( vectors ) / sizeof ( vectors[0] ) ); i++ ) {
|
||||
copy_from_real ( &vector, 0, ( i * sizeof ( vector ) ),
|
||||
sizeof ( vector ) );
|
||||
if ( memcmp ( &vector, &vectors[i], sizeof ( vector ) ) == 0 )
|
||||
continue;
|
||||
if ( initialised ) {
|
||||
dbg_printf ( "INT %02x changed %04x:%04x => "
|
||||
"%04x:%04x\n", i, vectors[i].segment,
|
||||
vectors[i].offset, vector.segment,
|
||||
vector.offset );
|
||||
}
|
||||
memcpy ( &vectors[i], &vector, sizeof ( vectors[i] ) );
|
||||
}
|
||||
initialised = 1;
|
||||
}
|
||||
589
src/arch/x86/interface/pcbios/e820mangler.S
Normal file
589
src/arch/x86/interface/pcbios/e820mangler.S
Normal file
@@ -0,0 +1,589 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )
|
||||
|
||||
.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
|
||||
98
src/arch/x86/interface/pcbios/fakee820.c
Normal file
98
src/arch/x86/interface/pcbios/fakee820.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#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, ( intptr_t ) int15_fakee820,
|
||||
&real_int15_vector );
|
||||
}
|
||||
|
||||
void unfake_e820 ( void ) {
|
||||
unhook_bios_interrupt ( 0x15, ( intptr_t ) int15_fakee820,
|
||||
&real_int15_vector );
|
||||
}
|
||||
234
src/arch/x86/interface/pcbios/hidemem.c
Normal file
234
src/arch/x86/interface/pcbios/hidemem.c
Normal file
@@ -0,0 +1,234 @@
|
||||
/* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#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 ( ( size_t ) _text16_memsz )
|
||||
extern char _data16_memsz[];
|
||||
#define _data16_memsz ( ( size_t ) _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, ( intptr_t ) 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, ( intptr_t ) 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,
|
||||
};
|
||||
1992
src/arch/x86/interface/pcbios/int13.c
Normal file
1992
src/arch/x86/interface/pcbios/int13.c
Normal file
File diff suppressed because it is too large
Load Diff
284
src/arch/x86/interface/pcbios/int13con.c
Normal file
284
src/arch/x86/interface/pcbios/int13con.c
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <ipxe/console.h>
|
||||
#include <ipxe/init.h>
|
||||
#include <realmode.h>
|
||||
#include <int13.h>
|
||||
#include <config/console.h>
|
||||
|
||||
/** @file
|
||||
*
|
||||
* INT13 disk log console
|
||||
*
|
||||
*/
|
||||
|
||||
/* Set default console usage if applicable */
|
||||
#if ! ( defined ( CONSOLE_INT13 ) && CONSOLE_EXPLICIT ( CONSOLE_INT13 ) )
|
||||
#undef CONSOLE_INT13
|
||||
#define CONSOLE_INT13 ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG )
|
||||
#endif
|
||||
|
||||
/** Disk drive number */
|
||||
#define INT13CON_DRIVE 0x80
|
||||
|
||||
/** Log partition type */
|
||||
#define INT13CON_PARTITION_TYPE 0xe0
|
||||
|
||||
/** Maximum number of outstanding unwritten characters */
|
||||
#define INT13CON_MAX_UNWRITTEN 64
|
||||
|
||||
/** Log partition header */
|
||||
struct int13con_header {
|
||||
/** Magic signature */
|
||||
char magic[10];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** Log partition magic signature */
|
||||
#define INT13CON_MAGIC "iPXE LOG\n\n"
|
||||
|
||||
/** Sector buffer */
|
||||
static uint8_t __bss16_array ( int13con_buffer, [INT13_BLKSIZE] );
|
||||
#define int13con_buffer __use_data16 ( int13con_buffer )
|
||||
|
||||
/** Disk address packet */
|
||||
static struct int13_disk_address __bss16 ( int13con_address );
|
||||
#define int13con_address __use_data16 ( int13con_address )
|
||||
|
||||
/** Current LBA */
|
||||
static uint64_t int13con_lba;
|
||||
|
||||
/** Maximum LBA */
|
||||
static uint64_t int13con_max_lba;
|
||||
|
||||
/** Current offset within sector */
|
||||
static size_t int13con_offset;
|
||||
|
||||
/** Number of unwritten characters */
|
||||
static size_t int13con_unwritten;
|
||||
|
||||
struct console_driver int13con __console_driver;
|
||||
|
||||
/**
|
||||
* Read/write disk sector
|
||||
*
|
||||
* @v op Operation
|
||||
* @v lba Logical block address
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int int13con_rw ( unsigned int op, uint64_t lba ) {
|
||||
uint8_t error;
|
||||
|
||||
/* Construct disk address packet */
|
||||
int13con_address.bufsize = sizeof ( int13con_address );
|
||||
int13con_address.count = 1;
|
||||
int13con_address.buffer.segment = rm_ds;
|
||||
int13con_address.buffer.offset = __from_data16 ( int13con_buffer );
|
||||
int13con_address.lba = lba;
|
||||
|
||||
/* Issue INT13 */
|
||||
__asm__ ( REAL_CODE ( "int $0x13\n\t" )
|
||||
: "=a" ( error )
|
||||
: "0" ( op << 8 ), "d" ( INT13CON_DRIVE ),
|
||||
"S" ( __from_data16 ( &int13con_address ) ) );
|
||||
if ( error ) {
|
||||
DBG ( "INT13CON operation %04x failed: %02x\n",
|
||||
op, error );
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write character to console
|
||||
*
|
||||
* @v character Character
|
||||
*/
|
||||
static void int13con_putchar ( int character ) {
|
||||
static int busy;
|
||||
int rc;
|
||||
|
||||
/* Ignore if we are already mid-logging */
|
||||
if ( busy )
|
||||
return;
|
||||
busy = 1;
|
||||
|
||||
/* Write character to buffer */
|
||||
int13con_buffer[int13con_offset++] = character;
|
||||
int13con_unwritten++;
|
||||
|
||||
/* Write sector to disk, if applicable */
|
||||
if ( ( int13con_offset == INT13_BLKSIZE ) ||
|
||||
( int13con_unwritten == INT13CON_MAX_UNWRITTEN ) ||
|
||||
( character == '\n' ) ) {
|
||||
|
||||
/* Write sector to disk */
|
||||
if ( ( rc = int13con_rw ( INT13_EXTENDED_WRITE,
|
||||
int13con_lba ) ) != 0 ) {
|
||||
DBG ( "INT13CON could not write log\n" );
|
||||
/* Ignore and continue; there's nothing we can do */
|
||||
}
|
||||
|
||||
/* Reset count of unwritten characters */
|
||||
int13con_unwritten = 0;
|
||||
}
|
||||
|
||||
/* Move to next sector, if applicable */
|
||||
if ( int13con_offset == INT13_BLKSIZE ) {
|
||||
|
||||
/* Disable console if we have run out of space */
|
||||
if ( int13con_lba >= int13con_max_lba )
|
||||
int13con.disabled = 1;
|
||||
|
||||
/* Clear log buffer */
|
||||
memset ( int13con_buffer, 0, sizeof ( int13con_buffer ) );
|
||||
int13con_offset = 0;
|
||||
|
||||
/* Move to next sector */
|
||||
int13con_lba++;
|
||||
}
|
||||
|
||||
/* Clear busy flag */
|
||||
busy = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find log partition
|
||||
*
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int int13con_find ( void ) {
|
||||
struct master_boot_record *mbr =
|
||||
( ( struct master_boot_record * ) int13con_buffer );
|
||||
struct int13con_header *hdr =
|
||||
( ( struct int13con_header * ) int13con_buffer );
|
||||
struct partition_table_entry part[4];
|
||||
unsigned int i;
|
||||
int rc;
|
||||
|
||||
/* Read MBR */
|
||||
if ( ( rc = int13con_rw ( INT13_EXTENDED_READ, 0 ) ) != 0 ) {
|
||||
DBG ( "INT13CON could not read MBR: %s\n", strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check MBR magic */
|
||||
if ( mbr->magic != INT13_MBR_MAGIC ) {
|
||||
DBG ( "INT13CON incorrect MBR magic\n" );
|
||||
DBG2_HDA ( 0, mbr, sizeof ( *mbr ) );
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Look for magic partition */
|
||||
memcpy ( part, mbr->partitions, sizeof ( part ) );
|
||||
for ( i = 0 ; i < ( sizeof ( part ) / sizeof ( part[0] ) ) ; i++ ) {
|
||||
|
||||
/* Skip partitions of the wrong type */
|
||||
if ( part[i].type != INT13CON_PARTITION_TYPE )
|
||||
continue;
|
||||
|
||||
/* Read partition header */
|
||||
if ( ( rc = int13con_rw ( INT13_EXTENDED_READ,
|
||||
part[i].start ) ) != 0 ) {
|
||||
DBG ( "INT13CON partition %d could not read header: "
|
||||
"%s\n", ( i + 1 ), strerror ( rc ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check partition header */
|
||||
if ( memcmp ( hdr->magic, INT13CON_MAGIC,
|
||||
sizeof ( hdr->magic ) ) != 0 ) {
|
||||
DBG ( "INT13CON partition %d bad magic\n", ( i + 1 ) );
|
||||
DBG2_HDA ( 0, hdr, sizeof ( *hdr ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Found log partition */
|
||||
DBG ( "INT13CON partition %d at [%08x,%08x)\n", ( i + 1 ),
|
||||
part[i].start, ( part[i].start + part[i].length ) );
|
||||
int13con_lba = part[i].start;
|
||||
int13con_max_lba = ( part[i].start + part[i].length - 1 );
|
||||
|
||||
/* Initialise log buffer */
|
||||
memset ( &int13con_buffer[ sizeof ( *hdr ) ], 0,
|
||||
( sizeof ( int13con_buffer ) - sizeof ( *hdr ) ) );
|
||||
int13con_offset = sizeof ( hdr->magic );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DBG ( "INT13CON found no log partition\n" );
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise INT13 console
|
||||
*
|
||||
*/
|
||||
static void int13con_init ( void ) {
|
||||
uint8_t error;
|
||||
uint16_t check;
|
||||
unsigned int discard_c;
|
||||
unsigned int discard_d;
|
||||
int rc;
|
||||
|
||||
/* Check for INT13 extensions */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x13\n\t"
|
||||
"setc %%al\n\t" )
|
||||
: "=a" ( error ), "=b" ( check ),
|
||||
"=c" ( discard_c ), "=d" ( discard_d )
|
||||
: "0" ( INT13_EXTENSION_CHECK << 8 ),
|
||||
"1" ( 0x55aa ), "3" ( INT13CON_DRIVE ) );
|
||||
if ( error || ( check != 0xaa55 ) ) {
|
||||
DBG ( "INT13CON missing extensions (%02x,%04x)\n",
|
||||
error, check );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Locate log partition */
|
||||
if ( ( rc = int13con_find() ) != 0)
|
||||
return;
|
||||
|
||||
/* Enable console */
|
||||
int13con.disabled = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* INT13 console initialisation function
|
||||
*/
|
||||
struct init_fn int13con_init_fn __init_fn ( INIT_CONSOLE ) = {
|
||||
.initialise = int13con_init,
|
||||
};
|
||||
|
||||
/** INT13 console driver */
|
||||
struct console_driver int13con __console_driver = {
|
||||
.putchar = int13con_putchar,
|
||||
.disabled = CONSOLE_DISABLED,
|
||||
.usage = CONSOLE_INT13,
|
||||
};
|
||||
343
src/arch/x86/interface/pcbios/memmap.c
Normal file
343
src/arch/x86/interface/pcbios/memmap.c
Normal file
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#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 );
|
||||
182
src/arch/x86/interface/pcbios/memtop_umalloc.c
Normal file
182
src/arch/x86/interface/pcbios/memtop_umalloc.c
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* External memory allocation
|
||||
*
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
#include <ipxe/uaccess.h>
|
||||
#include <ipxe/hidemem.h>
|
||||
#include <ipxe/io.h>
|
||||
#include <ipxe/memblock.h>
|
||||
#include <ipxe/umalloc.h>
|
||||
|
||||
/** Alignment of external allocated memory */
|
||||
#define EM_ALIGN ( 4 * 1024 )
|
||||
|
||||
/** Equivalent of NOWHERE for user pointers */
|
||||
#define UNOWHERE ( ~UNULL )
|
||||
|
||||
/** An external memory block */
|
||||
struct external_memory {
|
||||
/** Size of this memory block (excluding this header) */
|
||||
size_t size;
|
||||
/** Block is currently in use */
|
||||
int used;
|
||||
};
|
||||
|
||||
/** Top of heap */
|
||||
static userptr_t top = UNULL;
|
||||
|
||||
/** Bottom of heap (current lowest allocated block) */
|
||||
static userptr_t bottom = UNULL;
|
||||
|
||||
/** Remaining space on heap */
|
||||
static size_t heap_size;
|
||||
|
||||
/**
|
||||
* Initialise external heap
|
||||
*
|
||||
*/
|
||||
static void init_eheap ( void ) {
|
||||
userptr_t base;
|
||||
|
||||
heap_size = largest_memblock ( &base );
|
||||
bottom = top = userptr_add ( base, heap_size );
|
||||
DBG ( "External heap grows downwards from %lx (size %zx)\n",
|
||||
user_to_phys ( top, 0 ), heap_size );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect free blocks
|
||||
*
|
||||
*/
|
||||
static void ecollect_free ( void ) {
|
||||
struct external_memory extmem;
|
||||
size_t len;
|
||||
|
||||
/* Walk the free list and collect empty blocks */
|
||||
while ( bottom != top ) {
|
||||
copy_from_user ( &extmem, bottom, -sizeof ( extmem ),
|
||||
sizeof ( extmem ) );
|
||||
if ( extmem.used )
|
||||
break;
|
||||
DBG ( "EXTMEM freeing [%lx,%lx)\n", user_to_phys ( bottom, 0 ),
|
||||
user_to_phys ( bottom, extmem.size ) );
|
||||
len = ( extmem.size + sizeof ( extmem ) );
|
||||
bottom = userptr_add ( bottom, len );
|
||||
heap_size += len;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reallocate external memory
|
||||
*
|
||||
* @v old_ptr Memory previously allocated by umalloc(), or UNULL
|
||||
* @v new_size Requested size
|
||||
* @ret new_ptr Allocated memory, or UNULL
|
||||
*
|
||||
* Calling realloc() with a new size of zero is a valid way to free a
|
||||
* memory block.
|
||||
*/
|
||||
static userptr_t memtop_urealloc ( userptr_t ptr, size_t new_size ) {
|
||||
struct external_memory extmem;
|
||||
userptr_t new = ptr;
|
||||
size_t align;
|
||||
|
||||
/* (Re)initialise external memory allocator if necessary */
|
||||
if ( bottom == top )
|
||||
init_eheap();
|
||||
|
||||
/* Get block properties into extmem */
|
||||
if ( ptr && ( ptr != UNOWHERE ) ) {
|
||||
/* Determine old size */
|
||||
copy_from_user ( &extmem, ptr, -sizeof ( extmem ),
|
||||
sizeof ( extmem ) );
|
||||
} else {
|
||||
/* Create a zero-length block */
|
||||
if ( heap_size < sizeof ( extmem ) ) {
|
||||
DBG ( "EXTMEM out of space\n" );
|
||||
return UNULL;
|
||||
}
|
||||
ptr = bottom = userptr_add ( bottom, -sizeof ( extmem ) );
|
||||
heap_size -= sizeof ( extmem );
|
||||
DBG ( "EXTMEM allocating [%lx,%lx)\n",
|
||||
user_to_phys ( ptr, 0 ), user_to_phys ( ptr, 0 ) );
|
||||
extmem.size = 0;
|
||||
}
|
||||
extmem.used = ( new_size > 0 );
|
||||
|
||||
/* Expand/shrink block if possible */
|
||||
if ( ptr == bottom ) {
|
||||
/* Update block */
|
||||
if ( new_size > ( heap_size - extmem.size ) ) {
|
||||
DBG ( "EXTMEM out of space\n" );
|
||||
return UNULL;
|
||||
}
|
||||
new = userptr_add ( ptr, - ( new_size - extmem.size ) );
|
||||
align = ( user_to_phys ( new, 0 ) & ( EM_ALIGN - 1 ) );
|
||||
new_size += align;
|
||||
new = userptr_add ( new, -align );
|
||||
DBG ( "EXTMEM expanding [%lx,%lx) to [%lx,%lx)\n",
|
||||
user_to_phys ( ptr, 0 ),
|
||||
user_to_phys ( ptr, extmem.size ),
|
||||
user_to_phys ( new, 0 ),
|
||||
user_to_phys ( new, new_size ));
|
||||
memmove_user ( new, 0, ptr, 0, ( ( extmem.size < new_size ) ?
|
||||
extmem.size : new_size ) );
|
||||
bottom = new;
|
||||
heap_size -= ( new_size - extmem.size );
|
||||
extmem.size = new_size;
|
||||
} else {
|
||||
/* Cannot expand; can only pretend to shrink */
|
||||
if ( new_size > extmem.size ) {
|
||||
/* Refuse to expand */
|
||||
DBG ( "EXTMEM cannot expand [%lx,%lx)\n",
|
||||
user_to_phys ( ptr, 0 ),
|
||||
user_to_phys ( ptr, extmem.size ) );
|
||||
return UNULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write back block properties */
|
||||
copy_to_user ( new, -sizeof ( extmem ), &extmem,
|
||||
sizeof ( extmem ) );
|
||||
|
||||
/* Collect any free blocks and update hidden memory region */
|
||||
ecollect_free();
|
||||
hide_umalloc ( user_to_phys ( bottom, ( ( bottom == top ) ?
|
||||
0 : -sizeof ( extmem ) ) ),
|
||||
user_to_phys ( top, 0 ) );
|
||||
|
||||
return ( new_size ? new : UNOWHERE );
|
||||
}
|
||||
|
||||
PROVIDE_UMALLOC ( memtop, urealloc, memtop_urealloc );
|
||||
123
src/arch/x86/interface/pcbios/pcibios.c
Normal file
123
src/arch/x86/interface/pcbios/pcibios.c
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ipxe/pci.h>
|
||||
#include <realmode.h>
|
||||
|
||||
/** @file
|
||||
*
|
||||
* PCI configuration space access via PCI BIOS
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine number of PCI buses within system
|
||||
*
|
||||
* @ret num_bus Number of buses
|
||||
*/
|
||||
static int pcibios_num_bus ( void ) {
|
||||
int discard_a, discard_D;
|
||||
uint8_t max_bus;
|
||||
|
||||
/* We issue this call using flat real mode, to work around a
|
||||
* bug in some HP BIOSes.
|
||||
*/
|
||||
__asm__ __volatile__ ( REAL_CODE ( "call flatten_real_mode\n\t"
|
||||
"stc\n\t"
|
||||
"int $0x1a\n\t"
|
||||
"jnc 1f\n\t"
|
||||
"xorw %%cx, %%cx\n\t"
|
||||
"\n1:\n\t" )
|
||||
: "=c" ( max_bus ), "=a" ( discard_a ),
|
||||
"=D" ( discard_D )
|
||||
: "a" ( PCIBIOS_INSTALLATION_CHECK >> 16 ),
|
||||
"D" ( 0 )
|
||||
: "ebx", "edx" );
|
||||
|
||||
return ( max_bus + 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read configuration space via PCI BIOS
|
||||
*
|
||||
* @v pci PCI device
|
||||
* @v command PCI BIOS command
|
||||
* @v value Value read
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
int pcibios_read ( struct pci_device *pci, uint32_t command, uint32_t *value ){
|
||||
int discard_b, discard_D;
|
||||
int status;
|
||||
|
||||
__asm__ __volatile__ ( REAL_CODE ( "stc\n\t"
|
||||
"int $0x1a\n\t"
|
||||
"jnc 1f\n\t"
|
||||
"xorl %%eax, %%eax\n\t"
|
||||
"decl %%eax\n\t"
|
||||
"movl %%eax, %%ecx\n\t"
|
||||
"\n1:\n\t" )
|
||||
: "=a" ( status ), "=b" ( discard_b ),
|
||||
"=c" ( *value ), "=D" ( discard_D )
|
||||
: "a" ( command >> 16 ), "D" ( command ),
|
||||
"b" ( pci->busdevfn )
|
||||
: "edx" );
|
||||
|
||||
return ( ( status >> 8 ) & 0xff );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write configuration space via PCI BIOS
|
||||
*
|
||||
* @v pci PCI device
|
||||
* @v command PCI BIOS command
|
||||
* @v value Value to be written
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
int pcibios_write ( struct pci_device *pci, uint32_t command, uint32_t value ){
|
||||
int discard_b, discard_c, discard_D;
|
||||
int status;
|
||||
|
||||
__asm__ __volatile__ ( REAL_CODE ( "stc\n\t"
|
||||
"int $0x1a\n\t"
|
||||
"jnc 1f\n\t"
|
||||
"movb $0xff, %%ah\n\t"
|
||||
"\n1:\n\t" )
|
||||
: "=a" ( status ), "=b" ( discard_b ),
|
||||
"=c" ( discard_c ), "=D" ( discard_D )
|
||||
: "a" ( command >> 16 ), "D" ( command ),
|
||||
"b" ( pci->busdevfn ), "c" ( value )
|
||||
: "edx" );
|
||||
|
||||
return ( ( status >> 8 ) & 0xff );
|
||||
}
|
||||
|
||||
PROVIDE_PCIAPI ( pcbios, pci_num_bus, pcibios_num_bus );
|
||||
PROVIDE_PCIAPI_INLINE ( pcbios, pci_read_config_byte );
|
||||
PROVIDE_PCIAPI_INLINE ( pcbios, pci_read_config_word );
|
||||
PROVIDE_PCIAPI_INLINE ( pcbios, pci_read_config_dword );
|
||||
PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_byte );
|
||||
PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_word );
|
||||
PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_dword );
|
||||
114
src/arch/x86/interface/pcbios/pnpbios.c
Normal file
114
src/arch/x86/interface/pcbios/pnpbios.c
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#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;
|
||||
}
|
||||
199
src/arch/x86/interface/pcbios/rtc_entropy.c
Normal file
199
src/arch/x86/interface/pcbios/rtc_entropy.c
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/** @file
|
||||
*
|
||||
* RTC-based entropy source
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <biosint.h>
|
||||
#include <pic8259.h>
|
||||
#include <rtc.h>
|
||||
#include <ipxe/entropy.h>
|
||||
|
||||
/** RTC interrupt handler */
|
||||
extern void rtc_isr ( void );
|
||||
|
||||
/** Previous RTC interrupt handler */
|
||||
static struct segoff rtc_old_handler;
|
||||
|
||||
/**
|
||||
* Hook RTC interrupt handler
|
||||
*
|
||||
*/
|
||||
static void rtc_hook_isr ( void ) {
|
||||
|
||||
/* RTC interrupt handler */
|
||||
__asm__ __volatile__ (
|
||||
TEXT16_CODE ( "\nrtc_isr:\n\t"
|
||||
/* Preserve registers */
|
||||
"pushw %%ax\n\t"
|
||||
/* Set "interrupt triggered" flag */
|
||||
"movb $0x01, %%cs:rtc_flag\n\t"
|
||||
/* Read RTC status register C to
|
||||
* acknowledge interrupt
|
||||
*/
|
||||
"movb %2, %%al\n\t"
|
||||
"outb %%al, %0\n\t"
|
||||
"inb %1\n\t"
|
||||
/* Send EOI */
|
||||
"movb $0x20, %%al\n\t"
|
||||
"outb %%al, $0xa0\n\t"
|
||||
"outb %%al, $0x20\n\t"
|
||||
/* Restore registers and return */
|
||||
"popw %%ax\n\t"
|
||||
"iret\n\t"
|
||||
"\nrtc_flag:\n\t"
|
||||
".byte 0\n\t" )
|
||||
:
|
||||
: "i" ( CMOS_ADDRESS ), "i" ( CMOS_DATA ),
|
||||
"i" ( RTC_STATUS_C ) );
|
||||
|
||||
hook_bios_interrupt ( RTC_INT, ( intptr_t ) rtc_isr, &rtc_old_handler );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unhook RTC interrupt handler
|
||||
*
|
||||
*/
|
||||
static void rtc_unhook_isr ( void ) {
|
||||
int rc;
|
||||
|
||||
rc = unhook_bios_interrupt ( RTC_INT, ( intptr_t ) rtc_isr,
|
||||
&rtc_old_handler );
|
||||
assert ( rc == 0 ); /* Should always be able to unhook */
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable RTC interrupts
|
||||
*
|
||||
*/
|
||||
static void rtc_enable_int ( void ) {
|
||||
uint8_t status_b;
|
||||
|
||||
/* Set Periodic Interrupt Enable bit in status register B */
|
||||
outb ( ( RTC_STATUS_B | CMOS_DISABLE_NMI ), CMOS_ADDRESS );
|
||||
status_b = inb ( CMOS_DATA );
|
||||
outb ( ( RTC_STATUS_B | CMOS_DISABLE_NMI ), CMOS_ADDRESS );
|
||||
outb ( ( status_b | RTC_STATUS_B_PIE ), CMOS_DATA );
|
||||
|
||||
/* Re-enable NMI and reset to default address */
|
||||
outb ( CMOS_DEFAULT_ADDRESS, CMOS_ADDRESS );
|
||||
inb ( CMOS_DATA ); /* Discard; may be needed on some platforms */
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable RTC interrupts
|
||||
*
|
||||
*/
|
||||
static void rtc_disable_int ( void ) {
|
||||
uint8_t status_b;
|
||||
|
||||
/* Clear Periodic Interrupt Enable bit in status register B */
|
||||
outb ( ( RTC_STATUS_B | CMOS_DISABLE_NMI ), CMOS_ADDRESS );
|
||||
status_b = inb ( CMOS_DATA );
|
||||
outb ( ( RTC_STATUS_B | CMOS_DISABLE_NMI ), CMOS_ADDRESS );
|
||||
outb ( ( status_b & ~RTC_STATUS_B_PIE ), CMOS_DATA );
|
||||
|
||||
/* Re-enable NMI and reset to default address */
|
||||
outb ( CMOS_DEFAULT_ADDRESS, CMOS_ADDRESS );
|
||||
inb ( CMOS_DATA ); /* Discard; may be needed on some platforms */
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable entropy gathering
|
||||
*
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int rtc_entropy_enable ( void ) {
|
||||
|
||||
rtc_hook_isr();
|
||||
enable_irq ( RTC_IRQ );
|
||||
rtc_enable_int();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable entropy gathering
|
||||
*
|
||||
*/
|
||||
static void rtc_entropy_disable ( void ) {
|
||||
|
||||
rtc_disable_int();
|
||||
disable_irq ( RTC_IRQ );
|
||||
rtc_unhook_isr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure a single RTC tick
|
||||
*
|
||||
* @ret delta Length of RTC tick (in TSC units)
|
||||
*/
|
||||
uint8_t rtc_sample ( void ) {
|
||||
uint32_t before;
|
||||
uint32_t after;
|
||||
uint32_t temp;
|
||||
|
||||
__asm__ __volatile__ (
|
||||
REAL_CODE ( /* Enable interrupts */
|
||||
"sti\n\t"
|
||||
/* Wait for RTC interrupt */
|
||||
"movb %b2, %%cs:rtc_flag\n\t"
|
||||
"\n1:\n\t"
|
||||
"xchgb %b2, %%cs:rtc_flag\n\t" /* Serialize */
|
||||
"testb %b2, %b2\n\t"
|
||||
"jz 1b\n\t"
|
||||
/* Read "before" TSC */
|
||||
"rdtsc\n\t"
|
||||
/* Store "before" TSC on stack */
|
||||
"pushl %0\n\t"
|
||||
/* Wait for another RTC interrupt */
|
||||
"xorb %b2, %b2\n\t"
|
||||
"movb %b2, %%cs:rtc_flag\n\t"
|
||||
"\n1:\n\t"
|
||||
"xchgb %b2, %%cs:rtc_flag\n\t" /* Serialize */
|
||||
"testb %b2, %b2\n\t"
|
||||
"jz 1b\n\t"
|
||||
/* Read "after" TSC */
|
||||
"rdtsc\n\t"
|
||||
/* Retrieve "before" TSC on stack */
|
||||
"popl %1\n\t"
|
||||
/* Disable interrupts */
|
||||
"cli\n\t"
|
||||
)
|
||||
: "=a" ( after ), "=d" ( before ), "=q" ( temp )
|
||||
: "2" ( 0 ) );
|
||||
|
||||
return ( after - before );
|
||||
}
|
||||
|
||||
PROVIDE_ENTROPY_INLINE ( rtc, min_entropy_per_sample );
|
||||
PROVIDE_ENTROPY ( rtc, entropy_enable, rtc_entropy_enable );
|
||||
PROVIDE_ENTROPY ( rtc, entropy_disable, rtc_entropy_disable );
|
||||
PROVIDE_ENTROPY_INLINE ( rtc, get_noise );
|
||||
142
src/arch/x86/interface/pcbios/rtc_time.c
Normal file
142
src/arch/x86/interface/pcbios/rtc_time.c
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/** @file
|
||||
*
|
||||
* RTC-based time source
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <rtc.h>
|
||||
#include <ipxe/time.h>
|
||||
|
||||
/**
|
||||
* Read RTC register
|
||||
*
|
||||
* @v address Register address
|
||||
* @ret data Data
|
||||
*/
|
||||
static unsigned int rtc_readb ( int address ) {
|
||||
outb ( address, CMOS_ADDRESS );
|
||||
return inb ( CMOS_DATA );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if RTC update is in progress
|
||||
*
|
||||
* @ret is_busy RTC update is in progress
|
||||
*/
|
||||
static int rtc_is_busy ( void ) {
|
||||
return ( rtc_readb ( RTC_STATUS_A ) & RTC_STATUS_A_UPDATE_IN_PROGRESS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read RTC BCD register
|
||||
*
|
||||
* @v address Register address
|
||||
* @ret value Value
|
||||
*/
|
||||
static unsigned int rtc_readb_bcd ( int address ) {
|
||||
unsigned int bcd;
|
||||
|
||||
bcd = rtc_readb ( address );
|
||||
return ( bcd - ( 6 * ( bcd >> 4 ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read RTC time
|
||||
*
|
||||
* @ret time Time, in seconds
|
||||
*/
|
||||
static time_t rtc_read_time ( void ) {
|
||||
unsigned int status_b;
|
||||
int is_binary;
|
||||
int is_24hour;
|
||||
unsigned int ( * read_component ) ( int address );
|
||||
struct tm tm;
|
||||
int is_pm;
|
||||
unsigned int hour;
|
||||
time_t time;
|
||||
|
||||
/* Wait for any in-progress update to complete */
|
||||
while ( rtc_is_busy() ) {}
|
||||
|
||||
/* Determine RTC mode */
|
||||
status_b = rtc_readb ( RTC_STATUS_B );
|
||||
is_binary = ( status_b & RTC_STATUS_B_BINARY );
|
||||
is_24hour = ( status_b & RTC_STATUS_B_24_HOUR );
|
||||
read_component = ( is_binary ? rtc_readb : rtc_readb_bcd );
|
||||
|
||||
/* Read time values */
|
||||
tm.tm_sec = read_component ( RTC_SEC );
|
||||
tm.tm_min = read_component ( RTC_MIN );
|
||||
hour = read_component ( RTC_HOUR );
|
||||
if ( ! is_24hour ) {
|
||||
is_pm = ( hour >= 80 );
|
||||
hour = ( ( ( ( hour & 0x7f ) % 80 ) % 12 ) +
|
||||
( is_pm ? 12 : 0 ) );
|
||||
}
|
||||
tm.tm_hour = hour;
|
||||
tm.tm_mday = read_component ( RTC_MDAY );
|
||||
tm.tm_mon = ( read_component ( RTC_MON ) - 1 );
|
||||
tm.tm_year = ( read_component ( RTC_YEAR ) +
|
||||
100 /* Assume we are in the 21st century, since
|
||||
* this code was written in 2012 */ );
|
||||
|
||||
DBGC ( RTC_STATUS_A, "RTCTIME is %04d-%02d-%02d %02d:%02d:%02d "
|
||||
"(%s,%d-hour)\n", ( tm.tm_year + 1900 ), ( tm.tm_mon + 1 ),
|
||||
tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
( is_binary ? "binary" : "BCD" ), ( is_24hour ? 24 : 12 ) );
|
||||
|
||||
/* Convert to seconds since the Epoch */
|
||||
time = mktime ( &tm );
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current time in seconds
|
||||
*
|
||||
* @ret time Time, in seconds
|
||||
*/
|
||||
static time_t rtc_now ( void ) {
|
||||
time_t time = 0;
|
||||
time_t last_time;
|
||||
|
||||
/* Read time until we get two matching values in a row, in
|
||||
* case we end up reading a corrupted value in the middle of
|
||||
* an update.
|
||||
*/
|
||||
do {
|
||||
last_time = time;
|
||||
time = rtc_read_time();
|
||||
} while ( time != last_time );
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
PROVIDE_TIME ( rtc, time_now, rtc_now );
|
||||
540
src/arch/x86/interface/pcbios/vesafb.c
Normal file
540
src/arch/x86/interface/pcbios/vesafb.c
Normal file
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/** @file
|
||||
*
|
||||
* VESA frame buffer console
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <realmode.h>
|
||||
#include <ipxe/console.h>
|
||||
#include <ipxe/io.h>
|
||||
#include <ipxe/ansicol.h>
|
||||
#include <ipxe/fbcon.h>
|
||||
#include <ipxe/vesafb.h>
|
||||
#include <config/console.h>
|
||||
|
||||
/* Avoid dragging in BIOS console if not otherwise used */
|
||||
extern struct console_driver bios_console;
|
||||
struct console_driver bios_console __attribute__ (( weak ));
|
||||
|
||||
/* Disambiguate the various error causes */
|
||||
#define EIO_FAILED __einfo_error ( EINFO_EIO_FAILED )
|
||||
#define EINFO_EIO_FAILED \
|
||||
__einfo_uniqify ( EINFO_EIO, 0x01, \
|
||||
"Function call failed" )
|
||||
#define EIO_HARDWARE __einfo_error ( EINFO_EIO_HARDWARE )
|
||||
#define EINFO_EIO_HARDWARE \
|
||||
__einfo_uniqify ( EINFO_EIO, 0x02, \
|
||||
"Not supported in current configuration" )
|
||||
#define EIO_MODE __einfo_error ( EINFO_EIO_MODE )
|
||||
#define EINFO_EIO_MODE \
|
||||
__einfo_uniqify ( EINFO_EIO, 0x03, \
|
||||
"Invalid in current video mode" )
|
||||
#define EIO_VBE( code ) \
|
||||
EUNIQ ( EINFO_EIO, (code), EIO_FAILED, EIO_HARDWARE, EIO_MODE )
|
||||
|
||||
/* Set default console usage if applicable
|
||||
*
|
||||
* We accept either CONSOLE_FRAMEBUFFER or CONSOLE_VESAFB.
|
||||
*/
|
||||
#if ( defined ( CONSOLE_FRAMEBUFFER ) && ! defined ( CONSOLE_VESAFB ) )
|
||||
#define CONSOLE_VESAFB CONSOLE_FRAMEBUFFER
|
||||
#endif
|
||||
#if ! ( defined ( CONSOLE_VESAFB ) && CONSOLE_EXPLICIT ( CONSOLE_VESAFB ) )
|
||||
#undef CONSOLE_VESAFB
|
||||
#define CONSOLE_VESAFB ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG )
|
||||
#endif
|
||||
|
||||
/** Character height */
|
||||
#define VESAFB_CHAR_HEIGHT 16
|
||||
|
||||
/** Font corresponding to selected character width and height */
|
||||
#define VESAFB_FONT VBE_FONT_8x16
|
||||
|
||||
/* Forward declaration */
|
||||
struct console_driver vesafb_console __console_driver;
|
||||
|
||||
/** A VESA frame buffer */
|
||||
struct vesafb {
|
||||
/** Frame buffer console */
|
||||
struct fbcon fbcon;
|
||||
/** Physical start address */
|
||||
physaddr_t start;
|
||||
/** Pixel geometry */
|
||||
struct fbcon_geometry pixel;
|
||||
/** Colour mapping */
|
||||
struct fbcon_colour_map map;
|
||||
/** Font definition */
|
||||
struct fbcon_font font;
|
||||
/** Character glyphs */
|
||||
struct segoff glyphs;
|
||||
/** Saved VGA mode */
|
||||
uint8_t saved_mode;
|
||||
};
|
||||
|
||||
/** The VESA frame buffer */
|
||||
static struct vesafb vesafb;
|
||||
|
||||
/** Base memory buffer used for VBE calls */
|
||||
union vbe_buffer {
|
||||
/** VBE controller information block */
|
||||
struct vbe_controller_info controller;
|
||||
/** VBE mode information block */
|
||||
struct vbe_mode_info mode;
|
||||
};
|
||||
static union vbe_buffer __bss16 ( vbe_buf );
|
||||
#define vbe_buf __use_data16 ( vbe_buf )
|
||||
|
||||
/**
|
||||
* Convert VBE status code to iPXE status code
|
||||
*
|
||||
* @v status VBE status code
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int vesafb_rc ( unsigned int status ) {
|
||||
unsigned int code;
|
||||
|
||||
if ( ( status & 0xff ) != 0x4f )
|
||||
return -ENOTSUP;
|
||||
code = ( ( status >> 8 ) & 0xff );
|
||||
return ( code ? -EIO_VBE ( code ) : 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get character glyph
|
||||
*
|
||||
* @v character Character
|
||||
* @v glyph Character glyph to fill in
|
||||
*/
|
||||
static void vesafb_glyph ( unsigned int character, uint8_t *glyph ) {
|
||||
size_t offset = ( character * VESAFB_CHAR_HEIGHT );
|
||||
|
||||
copy_from_real ( glyph, vesafb.glyphs.segment,
|
||||
( vesafb.glyphs.offset + offset ), VESAFB_CHAR_HEIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get font definition
|
||||
*
|
||||
*/
|
||||
static void vesafb_font ( void ) {
|
||||
|
||||
/* Get font information
|
||||
*
|
||||
* Working around gcc bugs is icky here. The value we want is
|
||||
* returned in %ebp, but there's no way to specify %ebp in an
|
||||
* output constraint. We can't put %ebp in the clobber list,
|
||||
* because this tends to cause random build failures on some
|
||||
* gcc versions. We can't manually push/pop %ebp and return
|
||||
* the value via a generic register output constraint, because
|
||||
* gcc might choose to use %ebp to satisfy that constraint
|
||||
* (and we have no way to prevent it from so doing).
|
||||
*
|
||||
* Work around this hideous mess by using %ecx and %edx as the
|
||||
* output registers, since they get clobbered anyway.
|
||||
*/
|
||||
__asm__ __volatile__ ( REAL_CODE ( "pushw %%bp\n\t" /* gcc bug */
|
||||
"int $0x10\n\t"
|
||||
"movw %%es, %%cx\n\t"
|
||||
"movw %%bp, %%dx\n\t"
|
||||
"popw %%bp\n\t" /* gcc bug */ )
|
||||
: "=c" ( vesafb.glyphs.segment ),
|
||||
"=d" ( vesafb.glyphs.offset )
|
||||
: "a" ( VBE_GET_FONT ),
|
||||
"b" ( VESAFB_FONT ) );
|
||||
DBGC ( &vbe_buf, "VESAFB has font %04x at %04x:%04x\n",
|
||||
VESAFB_FONT, vesafb.glyphs.segment, vesafb.glyphs.offset );
|
||||
vesafb.font.height = VESAFB_CHAR_HEIGHT;
|
||||
vesafb.font.glyph = vesafb_glyph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VBE mode list
|
||||
*
|
||||
* @ret mode_numbers Mode number list (terminated with VBE_MODE_END)
|
||||
* @ret rc Return status code
|
||||
*
|
||||
* The caller is responsible for eventually freeing the mode list.
|
||||
*/
|
||||
static int vesafb_mode_list ( uint16_t **mode_numbers ) {
|
||||
struct vbe_controller_info *controller = &vbe_buf.controller;
|
||||
userptr_t video_mode_ptr;
|
||||
uint16_t mode_number;
|
||||
uint16_t status;
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
/* Avoid returning uninitialised data on error */
|
||||
*mode_numbers = NULL;
|
||||
|
||||
/* Get controller information block */
|
||||
controller->vbe_signature = 0;
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
|
||||
: "=a" ( status )
|
||||
: "a" ( VBE_CONTROLLER_INFO ),
|
||||
"D" ( __from_data16 ( controller ) )
|
||||
: "memory", "ebx", "edx" );
|
||||
if ( ( rc = vesafb_rc ( status ) ) != 0 ) {
|
||||
DBGC ( &vbe_buf, "VESAFB could not get controller information: "
|
||||
"[%04x] %s\n", status, strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
if ( controller->vbe_signature != VBE_CONTROLLER_SIGNATURE ) {
|
||||
DBGC ( &vbe_buf, "VESAFB invalid controller signature "
|
||||
"\"%c%c%c%c\"\n", ( controller->vbe_signature >> 0 ),
|
||||
( controller->vbe_signature >> 8 ),
|
||||
( controller->vbe_signature >> 16 ),
|
||||
( controller->vbe_signature >> 24 ) );
|
||||
DBGC_HDA ( &vbe_buf, 0, controller, sizeof ( *controller ) );
|
||||
return -EINVAL;
|
||||
}
|
||||
DBGC ( &vbe_buf, "VESAFB found VBE version %d.%d with mode list at "
|
||||
"%04x:%04x\n", controller->vbe_major_version,
|
||||
controller->vbe_minor_version,
|
||||
controller->video_mode_ptr.segment,
|
||||
controller->video_mode_ptr.offset );
|
||||
|
||||
/* Calculate length of mode list */
|
||||
video_mode_ptr = real_to_user ( controller->video_mode_ptr.segment,
|
||||
controller->video_mode_ptr.offset );
|
||||
len = 0;
|
||||
do {
|
||||
copy_from_user ( &mode_number, video_mode_ptr, len,
|
||||
sizeof ( mode_number ) );
|
||||
len += sizeof ( mode_number );
|
||||
} while ( mode_number != VBE_MODE_END );
|
||||
|
||||
/* Allocate and fill mode list */
|
||||
*mode_numbers = malloc ( len );
|
||||
if ( ! *mode_numbers )
|
||||
return -ENOMEM;
|
||||
copy_from_user ( *mode_numbers, video_mode_ptr, 0, len );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get video mode information
|
||||
*
|
||||
* @v mode_number Mode number
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int vesafb_mode_info ( unsigned int mode_number ) {
|
||||
struct vbe_mode_info *mode = &vbe_buf.mode;
|
||||
uint16_t status;
|
||||
int rc;
|
||||
|
||||
/* Get mode information */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
|
||||
: "=a" ( status )
|
||||
: "a" ( VBE_MODE_INFO ),
|
||||
"c" ( mode_number ),
|
||||
"D" ( __from_data16 ( mode ) )
|
||||
: "memory" );
|
||||
if ( ( rc = vesafb_rc ( status ) ) != 0 ) {
|
||||
DBGC ( &vbe_buf, "VESAFB could not get mode %04x information: "
|
||||
"[%04x] %s\n", mode_number, status, strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
DBGC ( &vbe_buf, "VESAFB mode %04x %dx%d %dbpp(%d:%d:%d:%d) model "
|
||||
"%02x [x%d]%s%s%s%s%s\n", mode_number, mode->x_resolution,
|
||||
mode->y_resolution, mode->bits_per_pixel, mode->rsvd_mask_size,
|
||||
mode->red_mask_size, mode->green_mask_size, mode->blue_mask_size,
|
||||
mode->memory_model, ( mode->number_of_image_pages + 1 ),
|
||||
( ( mode->mode_attributes & VBE_MODE_ATTR_SUPPORTED ) ?
|
||||
"" : " [unsupported]" ),
|
||||
( ( mode->mode_attributes & VBE_MODE_ATTR_TTY ) ?
|
||||
" [tty]" : "" ),
|
||||
( ( mode->mode_attributes & VBE_MODE_ATTR_GRAPHICS ) ?
|
||||
"" : " [text]" ),
|
||||
( ( mode->mode_attributes & VBE_MODE_ATTR_LINEAR ) ?
|
||||
"" : " [nonlinear]" ),
|
||||
( ( mode->mode_attributes & VBE_MODE_ATTR_TRIPLE_BUF ) ?
|
||||
" [buf]" : "" ) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set video mode
|
||||
*
|
||||
* @v mode_number Mode number
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int vesafb_set_mode ( unsigned int mode_number ) {
|
||||
struct vbe_mode_info *mode = &vbe_buf.mode;
|
||||
uint16_t status;
|
||||
int rc;
|
||||
|
||||
/* Get mode information */
|
||||
if ( ( rc = vesafb_mode_info ( mode_number ) ) != 0 )
|
||||
return rc;
|
||||
|
||||
/* Record mode parameters */
|
||||
vesafb.start = mode->phys_base_ptr;
|
||||
vesafb.pixel.width = mode->x_resolution;
|
||||
vesafb.pixel.height = mode->y_resolution;
|
||||
vesafb.pixel.len = ( ( mode->bits_per_pixel + 7 ) / 8 );
|
||||
vesafb.pixel.stride = mode->bytes_per_scan_line;
|
||||
DBGC ( &vbe_buf, "VESAFB mode %04x has frame buffer at %08x\n",
|
||||
mode_number, mode->phys_base_ptr );
|
||||
|
||||
/* Initialise font colours */
|
||||
vesafb.map.red_scale = ( 8 - mode->red_mask_size );
|
||||
vesafb.map.green_scale = ( 8 - mode->green_mask_size );
|
||||
vesafb.map.blue_scale = ( 8 - mode->blue_mask_size );
|
||||
vesafb.map.red_lsb = mode->red_field_position;
|
||||
vesafb.map.green_lsb = mode->green_field_position;
|
||||
vesafb.map.blue_lsb = mode->blue_field_position;
|
||||
|
||||
/* Select this mode */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
|
||||
: "=a" ( status )
|
||||
: "a" ( VBE_SET_MODE ),
|
||||
"b" ( mode_number ) );
|
||||
if ( ( rc = vesafb_rc ( status ) ) != 0 ) {
|
||||
DBGC ( &vbe_buf, "VESAFB could not set mode %04x: [%04x] %s\n",
|
||||
mode_number, status, strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select video mode
|
||||
*
|
||||
* @v mode_numbers Mode number list (terminated with VBE_MODE_END)
|
||||
* @v min_width Minimum required width (in pixels)
|
||||
* @v min_height Minimum required height (in pixels)
|
||||
* @v min_bpp Minimum required colour depth (in bits per pixel)
|
||||
* @ret mode_number Mode number, or negative error
|
||||
*/
|
||||
static int vesafb_select_mode ( const uint16_t *mode_numbers,
|
||||
unsigned int min_width, unsigned int min_height,
|
||||
unsigned int min_bpp ) {
|
||||
struct vbe_mode_info *mode = &vbe_buf.mode;
|
||||
int best_mode_number = -ENOENT;
|
||||
unsigned int best_score = INT_MAX;
|
||||
unsigned int score;
|
||||
uint16_t mode_number;
|
||||
int rc;
|
||||
|
||||
/* Find the first suitable mode */
|
||||
while ( ( mode_number = *(mode_numbers++) ) != VBE_MODE_END ) {
|
||||
|
||||
/* Force linear mode variant */
|
||||
mode_number |= VBE_MODE_LINEAR;
|
||||
|
||||
/* Get mode information */
|
||||
if ( ( rc = vesafb_mode_info ( mode_number ) ) != 0 )
|
||||
continue;
|
||||
|
||||
/* Skip unusable modes */
|
||||
if ( ( mode->mode_attributes & ( VBE_MODE_ATTR_SUPPORTED |
|
||||
VBE_MODE_ATTR_GRAPHICS |
|
||||
VBE_MODE_ATTR_LINEAR ) ) !=
|
||||
( VBE_MODE_ATTR_SUPPORTED | VBE_MODE_ATTR_GRAPHICS |
|
||||
VBE_MODE_ATTR_LINEAR ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( mode->memory_model != VBE_MODE_MODEL_DIRECT_COLOUR )
|
||||
continue;
|
||||
|
||||
/* Skip modes not meeting the requirements */
|
||||
if ( ( mode->x_resolution < min_width ) ||
|
||||
( mode->y_resolution < min_height ) ||
|
||||
( mode->bits_per_pixel < min_bpp ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Select this mode if it has the best (i.e. lowest)
|
||||
* score. We choose the scoring system to favour
|
||||
* modes close to the specified width and height;
|
||||
* within modes of the same width and height we prefer
|
||||
* a higher colour depth.
|
||||
*/
|
||||
score = ( ( mode->x_resolution * mode->y_resolution ) -
|
||||
mode->bits_per_pixel );
|
||||
if ( score < best_score ) {
|
||||
best_mode_number = mode_number;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
|
||||
if ( best_mode_number >= 0 ) {
|
||||
DBGC ( &vbe_buf, "VESAFB selected mode %04x\n",
|
||||
best_mode_number );
|
||||
} else {
|
||||
DBGC ( &vbe_buf, "VESAFB found no suitable mode\n" );
|
||||
}
|
||||
|
||||
return best_mode_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore video mode
|
||||
*
|
||||
*/
|
||||
static void vesafb_restore ( void ) {
|
||||
uint32_t discard_a;
|
||||
|
||||
/* Restore saved VGA mode */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
|
||||
: "=a" ( discard_a )
|
||||
: "a" ( VBE_SET_VGA_MODE | vesafb.saved_mode ) );
|
||||
DBGC ( &vbe_buf, "VESAFB restored VGA mode %#02x\n",
|
||||
vesafb.saved_mode );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise VESA frame buffer
|
||||
*
|
||||
* @v config Console configuration, or NULL to reset
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int vesafb_init ( struct console_configuration *config ) {
|
||||
uint32_t discard_b;
|
||||
uint16_t *mode_numbers;
|
||||
int mode_number;
|
||||
int rc;
|
||||
|
||||
/* Record current VGA mode */
|
||||
__asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
|
||||
: "=a" ( vesafb.saved_mode ), "=b" ( discard_b )
|
||||
: "a" ( VBE_GET_VGA_MODE ) );
|
||||
DBGC ( &vbe_buf, "VESAFB saved VGA mode %#02x\n", vesafb.saved_mode );
|
||||
|
||||
/* Get VESA mode list */
|
||||
if ( ( rc = vesafb_mode_list ( &mode_numbers ) ) != 0 )
|
||||
goto err_mode_list;
|
||||
|
||||
/* Select mode */
|
||||
if ( ( mode_number = vesafb_select_mode ( mode_numbers, config->width,
|
||||
config->height,
|
||||
config->depth ) ) < 0 ) {
|
||||
rc = mode_number;
|
||||
goto err_select_mode;
|
||||
}
|
||||
|
||||
/* Set mode */
|
||||
if ( ( rc = vesafb_set_mode ( mode_number ) ) != 0 )
|
||||
goto err_set_mode;
|
||||
|
||||
/* Get font data */
|
||||
vesafb_font();
|
||||
|
||||
/* Initialise frame buffer console */
|
||||
if ( ( rc = fbcon_init ( &vesafb.fbcon, phys_to_user ( vesafb.start ),
|
||||
&vesafb.pixel, &vesafb.map, &vesafb.font,
|
||||
config ) ) != 0 )
|
||||
goto err_fbcon_init;
|
||||
|
||||
free ( mode_numbers );
|
||||
return 0;
|
||||
|
||||
fbcon_fini ( &vesafb.fbcon );
|
||||
err_fbcon_init:
|
||||
err_set_mode:
|
||||
vesafb_restore();
|
||||
err_select_mode:
|
||||
free ( mode_numbers );
|
||||
err_mode_list:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalise VESA frame buffer
|
||||
*
|
||||
*/
|
||||
static void vesafb_fini ( void ) {
|
||||
|
||||
/* Finalise frame buffer console */
|
||||
fbcon_fini ( &vesafb.fbcon );
|
||||
|
||||
/* Restore saved VGA mode */
|
||||
vesafb_restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a character to current cursor position
|
||||
*
|
||||
* @v character Character
|
||||
*/
|
||||
static void vesafb_putchar ( int character ) {
|
||||
|
||||
fbcon_putchar ( &vesafb.fbcon, character );
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure console
|
||||
*
|
||||
* @v config Console configuration, or NULL to reset
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int vesafb_configure ( struct console_configuration *config ) {
|
||||
int rc;
|
||||
|
||||
/* Reset console, if applicable */
|
||||
if ( ! vesafb_console.disabled ) {
|
||||
vesafb_fini();
|
||||
bios_console.disabled &= ~CONSOLE_DISABLED_OUTPUT;
|
||||
ansicol_reset_magic();
|
||||
}
|
||||
vesafb_console.disabled = CONSOLE_DISABLED;
|
||||
|
||||
/* Do nothing more unless we have a usable configuration */
|
||||
if ( ( config == NULL ) ||
|
||||
( config->width == 0 ) || ( config->height == 0 ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Initialise VESA frame buffer */
|
||||
if ( ( rc = vesafb_init ( config ) ) != 0 )
|
||||
return rc;
|
||||
|
||||
/* Mark console as enabled */
|
||||
vesafb_console.disabled = 0;
|
||||
bios_console.disabled |= CONSOLE_DISABLED_OUTPUT;
|
||||
|
||||
/* Set magic colour to transparent if we have a background picture */
|
||||
if ( config->pixbuf )
|
||||
ansicol_set_magic_transparent();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** VESA frame buffer console driver */
|
||||
struct console_driver vesafb_console __console_driver = {
|
||||
.usage = CONSOLE_VESAFB,
|
||||
.putchar = vesafb_putchar,
|
||||
.configure = vesafb_configure,
|
||||
.disabled = CONSOLE_DISABLED,
|
||||
};
|
||||
Reference in New Issue
Block a user