[bios] Add bin-x86_64-pcbios build platform

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

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

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

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

View File

@@ -0,0 +1,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 );

View 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();
}

View 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,
};

View 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 );

View 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 );

View 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 );

View 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 );

View 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;
}

View 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

View 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 );
}

View 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,
};

File diff suppressed because it is too large Load Diff

View 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,
};

View 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 );

View 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 );

View 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 );

View 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;
}

View 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 );

View 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 );

View 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,
};

View File

@@ -0,0 +1,404 @@
/*
* 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 <ipxe/uaccess.h>
#include <ipxe/init.h>
#include <ipxe/profile.h>
#include <ipxe/netdevice.h>
#include <rmsetjmp.h>
#include <registers.h>
#include <biosint.h>
#include <pxe.h>
#include <pxe_call.h>
/** @file
*
* PXE API entry point
*/
/* Disambiguate the various error causes */
#define EINFO_EPXENBP \
__einfo_uniqify ( EINFO_EPLATFORM, 0x01, \
"External PXE NBP error" )
#define EPXENBP( status ) EPLATFORM ( EINFO_EPXENBP, status )
/** Vector for chaining INT 1A */
extern struct segoff __text16 ( pxe_int_1a_vector );
#define pxe_int_1a_vector __use_text16 ( pxe_int_1a_vector )
/** INT 1A handler */
extern void pxe_int_1a ( void );
/** INT 1A hooked flag */
static int int_1a_hooked = 0;
/** Real-mode code segment size */
extern char _text16_memsz[];
#define _text16_memsz ( ( size_t ) _text16_memsz )
/** Real-mode data segment size */
extern char _data16_memsz[];
#define _data16_memsz ( ( size_t ) _data16_memsz )
/** PXENV_UNDI_TRANSMIT API call profiler */
static struct profiler pxe_api_tx_profiler __profiler =
{ .name = "pxeapi.tx" };
/** PXENV_UNDI_ISR API call profiler */
static struct profiler pxe_api_isr_profiler __profiler =
{ .name = "pxeapi.isr" };
/** PXE unknown API call profiler
*
* This profiler can be used to measure the overhead of a dummy PXE
* API call.
*/
static struct profiler pxe_api_unknown_profiler __profiler =
{ .name = "pxeapi.unknown" };
/** Miscellaneous PXE API call profiler */
static struct profiler pxe_api_misc_profiler __profiler =
{ .name = "pxeapi.misc" };
/**
* Handle an unknown PXE API call
*
* @v pxenv_unknown Pointer to a struct s_PXENV_UNKNOWN
* @ret #PXENV_EXIT_FAILURE Always
* @err #PXENV_STATUS_UNSUPPORTED Always
*/
static PXENV_EXIT_t pxenv_unknown ( struct s_PXENV_UNKNOWN *pxenv_unknown ) {
pxenv_unknown->Status = PXENV_STATUS_UNSUPPORTED;
return PXENV_EXIT_FAILURE;
}
/** Unknown PXE API call list */
struct pxe_api_call pxenv_unknown_api __pxe_api_call =
PXE_API_CALL ( PXENV_UNKNOWN, pxenv_unknown, struct s_PXENV_UNKNOWN );
/**
* Locate PXE API call
*
* @v opcode Opcode
* @ret call PXE API call, or NULL
*/
static struct pxe_api_call * find_pxe_api_call ( uint16_t opcode ) {
struct pxe_api_call *call;
for_each_table_entry ( call, PXE_API_CALLS ) {
if ( call->opcode == opcode )
return call;
}
return NULL;
}
/**
* Determine applicable profiler (for debugging)
*
* @v opcode PXE opcode
* @ret profiler Profiler
*/
static struct profiler * pxe_api_profiler ( unsigned int opcode ) {
/* Determine applicable profiler */
switch ( opcode ) {
case PXENV_UNDI_TRANSMIT:
return &pxe_api_tx_profiler;
case PXENV_UNDI_ISR:
return &pxe_api_isr_profiler;
case PXENV_UNKNOWN:
return &pxe_api_unknown_profiler;
default:
return &pxe_api_misc_profiler;
}
}
/**
* Dispatch PXE API call
*
* @v bx PXE opcode
* @v es:di Address of PXE parameter block
* @ret ax PXE exit code
*/
__asmcall void pxe_api_call ( struct i386_all_regs *ix86 ) {
uint16_t opcode = ix86->regs.bx;
userptr_t uparams = real_to_user ( ix86->segs.es, ix86->regs.di );
struct profiler *profiler = pxe_api_profiler ( opcode );
struct pxe_api_call *call;
union u_PXENV_ANY params;
PXENV_EXIT_t ret;
/* Start profiling */
profile_start ( profiler );
/* Locate API call */
call = find_pxe_api_call ( opcode );
if ( ! call ) {
DBGC ( &pxe_netdev, "PXENV_UNKNOWN_%04x\n", opcode );
call = &pxenv_unknown_api;
}
/* Copy parameter block from caller */
copy_from_user ( &params, uparams, 0, call->params_len );
/* Set default status in case child routine fails to do so */
params.Status = PXENV_STATUS_FAILURE;
/* Hand off to relevant API routine */
ret = call->entry ( &params );
/* Copy modified parameter block back to caller and return */
copy_to_user ( uparams, 0, &params, call->params_len );
ix86->regs.ax = ret;
/* Stop profiling, if applicable */
profile_stop ( profiler );
}
/**
* Dispatch weak PXE API call with PXE stack available
*
* @v ix86 Registers for PXE call
* @ret present Zero (PXE stack present)
*/
int pxe_api_call_weak ( struct i386_all_regs *ix86 ) {
pxe_api_call ( ix86 );
return 0;
}
/**
* Dispatch PXE loader call
*
* @v es:di Address of PXE parameter block
* @ret ax PXE exit code
*/
__asmcall void pxe_loader_call ( struct i386_all_regs *ix86 ) {
userptr_t uparams = real_to_user ( ix86->segs.es, ix86->regs.di );
struct s_UNDI_LOADER params;
PXENV_EXIT_t ret;
/* Copy parameter block from caller */
copy_from_user ( &params, uparams, 0, sizeof ( params ) );
/* Fill in ROM segment address */
ppxe.UNDIROMID.segment = ix86->segs.ds;
/* Set default status in case child routine fails to do so */
params.Status = PXENV_STATUS_FAILURE;
/* Call UNDI loader */
ret = undi_loader ( &params );
/* Copy modified parameter block back to caller and return */
copy_to_user ( uparams, 0, &params, sizeof ( params ) );
ix86->regs.ax = ret;
}
/**
* Calculate byte checksum as used by PXE
*
* @v data Data
* @v size Length of data
* @ret sum Checksum
*/
static uint8_t pxe_checksum ( void *data, size_t size ) {
uint8_t *bytes = data;
uint8_t sum = 0;
while ( size-- ) {
sum += *bytes++;
}
return sum;
}
/**
* Initialise !PXE and PXENV+ structures
*
*/
static void pxe_init_structures ( void ) {
uint32_t rm_cs_phys = ( rm_cs << 4 );
uint32_t rm_ds_phys = ( rm_ds << 4 );
/* Fill in missing segment fields */
ppxe.EntryPointSP.segment = rm_cs;
ppxe.EntryPointESP.segment = rm_cs;
ppxe.Stack.segment_address = rm_ds;
ppxe.Stack.Physical_address = rm_ds_phys;
ppxe.UNDIData.segment_address = rm_ds;
ppxe.UNDIData.Physical_address = rm_ds_phys;
ppxe.UNDICode.segment_address = rm_cs;
ppxe.UNDICode.Physical_address = rm_cs_phys;
ppxe.UNDICodeWrite.segment_address = rm_cs;
ppxe.UNDICodeWrite.Physical_address = rm_cs_phys;
pxenv.RMEntry.segment = rm_cs;
pxenv.StackSeg = rm_ds;
pxenv.UNDIDataSeg = rm_ds;
pxenv.UNDICodeSeg = rm_cs;
pxenv.PXEPtr.segment = rm_cs;
/* Update checksums */
ppxe.StructCksum -= pxe_checksum ( &ppxe, sizeof ( ppxe ) );
pxenv.Checksum -= pxe_checksum ( &pxenv, sizeof ( pxenv ) );
}
/** PXE structure initialiser */
struct init_fn pxe_init_fn __init_fn ( INIT_NORMAL ) = {
.initialise = pxe_init_structures,
};
/**
* Activate PXE stack
*
* @v netdev Net device to use as PXE net device
*/
void pxe_activate ( struct net_device *netdev ) {
uint32_t discard_a;
uint32_t discard_b;
uint32_t discard_d;
/* Ensure INT 1A is hooked */
if ( ! int_1a_hooked ) {
hook_bios_interrupt ( 0x1a, ( intptr_t ) pxe_int_1a,
&pxe_int_1a_vector );
devices_get();
int_1a_hooked = 1;
}
/* Set PXE network device */
pxe_set_netdev ( netdev );
/* Notify BIOS of installation */
__asm__ __volatile__ ( REAL_CODE ( "pushw %%cs\n\t"
"popw %%es\n\t"
"int $0x1a\n\t" )
: "=a" ( discard_a ), "=b" ( discard_b ),
"=d" ( discard_d )
: "0" ( 0x564e ),
"1" ( __from_text16 ( &pxenv ) ) );
}
/**
* Deactivate PXE stack
*
* @ret rc Return status code
*/
int pxe_deactivate ( void ) {
int rc;
/* Clear PXE network device */
pxe_set_netdev ( NULL );
/* Ensure INT 1A is unhooked, if possible */
if ( int_1a_hooked ) {
if ( ( rc = unhook_bios_interrupt ( 0x1a,
( intptr_t ) pxe_int_1a,
&pxe_int_1a_vector ))!= 0){
DBGC ( &pxe_netdev, "PXE could not unhook INT 1A: %s\n",
strerror ( rc ) );
return rc;
}
devices_put();
int_1a_hooked = 0;
}
return 0;
}
/** Jump buffer for PXENV_RESTART_TFTP */
rmjmp_buf pxe_restart_nbp;
/**
* Start PXE NBP at 0000:7c00
*
* @ret rc Return status code
*/
int pxe_start_nbp ( void ) {
int jmp;
int discard_b, discard_c, discard_d, discard_D;
uint16_t status;
DBGC ( &pxe_netdev, "PXE NBP starting with netdev %s, code %04x:%04zx, "
"data %04x:%04zx\n", ( pxe_netdev ? pxe_netdev->name : "<none>"),
rm_cs, _text16_memsz, rm_ds, _data16_memsz );
/* Allow restarting NBP via PXENV_RESTART_TFTP */
jmp = rmsetjmp ( pxe_restart_nbp );
if ( jmp )
DBGC ( &pxe_netdev, "PXE NBP restarting (%x)\n", jmp );
/* Far call to PXE NBP */
__asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" /* gcc bug */
"movw %%cx, %%es\n\t"
"pushw %%es\n\t"
"pushw %%di\n\t"
"sti\n\t"
"lcall $0, $0x7c00\n\t"
"popl %%ebp\n\t" /* discard */
"popl %%ebp\n\t" /* gcc bug */ )
: "=a" ( status ), "=b" ( discard_b ),
"=c" ( discard_c ), "=d" ( discard_d ),
"=D" ( discard_D )
: "a" ( 0 ), "b" ( __from_text16 ( &pxenv ) ),
"c" ( rm_cs ),
"d" ( virt_to_phys ( &pxenv ) ),
"D" ( __from_text16 ( &ppxe ) )
: "esi", "memory" );
if ( status )
return -EPXENBP ( status );
return 0;
}
/**
* Notify BIOS of existence of network device
*
* @v netdev Network device
* @ret rc Return status code
*/
static int pxe_notify ( struct net_device *netdev ) {
/* Do nothing if we already have a network device */
if ( pxe_netdev )
return 0;
/* Activate (and deactivate) PXE stack to notify BIOS */
pxe_activate ( netdev );
pxe_deactivate();
return 0;
}
/** PXE BIOS notification driver */
struct net_driver pxe_driver __net_driver = {
.name = "PXE",
.probe = pxe_notify,
};
REQUIRING_SYMBOL ( pxe_api_call );
REQUIRE_OBJECT ( pxe_preboot );
REQUIRE_OBJECT ( pxe_undi );
REQUIRE_OBJECT ( pxe_udp );
REQUIRE_OBJECT ( pxe_tftp );
REQUIRE_OBJECT ( pxe_file );

View File

@@ -0,0 +1,221 @@
/*
* 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 )
.arch i386
/****************************************************************************
* !PXE structure
****************************************************************************
*/
.section ".text16.data", "aw", @progbits
.globl ppxe
.align 16
ppxe:
.ascii "!PXE" /* Signature */
.byte pxe_length /* StructLength */
.byte 0 /* StructCksum */
.byte 0 /* StructRev */
.byte 0 /* reserved_1 */
.word undiheader, 0 /* UNDIROMID */
.word 0, 0 /* BaseROMID */
.word pxe_entry_sp, 0 /* EntryPointSP */
.word pxe_entry_esp, 0 /* EntryPointESP */
.word -1, -1 /* StatusCallout */
.byte 0 /* reserved_2 */
.byte SegDescCnt /* SegDescCnt */
.word 0 /* FirstSelector */
pxe_segments:
.word 0, 0, 0, _data16_memsz /* Stack */
.word 0, 0, 0, _data16_memsz /* UNDIData */
.word 0, 0, 0, _text16_memsz /* UNDICode */
.word 0, 0, 0, _text16_memsz /* UNDICodeWrite */
.word 0, 0, 0, 0 /* BC_Data */
.word 0, 0, 0, 0 /* BC_Code */
.word 0, 0, 0, 0 /* BC_CodeWrite */
.equ SegDescCnt, ( ( . - pxe_segments ) / 8 )
.equ pxe_length, . - ppxe
.size ppxe, . - ppxe
/* Define undiheader=0 as a weak symbol for non-ROM builds */
.section ".weak", "a", @nobits
.weak undiheader
undiheader:
/****************************************************************************
* PXENV+ structure
****************************************************************************
*/
.section ".text16.data", "aw", @progbits
.globl pxenv
.align 16
pxenv:
.ascii "PXENV+" /* Signature */
.word 0x0201 /* Version */
.byte pxenv_length /* Length */
.byte 0 /* Checksum */
.word pxenv_entry, 0 /* RMEntry */
.long 0 /* PMEntry */
.word 0 /* PMSelector */
.word 0 /* StackSeg */
.word _data16_memsz /* StackSize */
.word 0 /* BC_CodeSeg */
.word 0 /* BC_CodeSize */
.word 0 /* BC_DataSeg */
.word 0 /* BC_DataSize */
.word 0 /* UNDIDataSeg */
.word _data16_memsz /* UNDIDataSize */
.word 0 /* UNDICodeSeg */
.word _text16_memsz /* UNDICodeSize */
.word ppxe, 0 /* PXEPtr */
.equ pxenv_length, . - pxenv
.size pxenv, . - pxenv
/****************************************************************************
* pxenv_entry (16-bit far call)
*
* PXE API call PXENV+ entry point
*
* Parameters:
* %es:di : Far pointer to PXE parameter structure
* %bx : PXE API call
* Returns:
* %ax : PXE exit status
* Corrupts:
* none
****************************************************************************
*/
/* Wyse Streaming Manager server (WLDRM13.BIN) assumes that
* the PXENV+ entry point is at UNDI_CS:0000; apparently,
* somebody at Wyse has difficulty distinguishing between the
* words "may" and "must"...
*/
.section ".text16.null", "ax", @progbits
.code16
pxenv_null_entry:
jmp pxenv_entry
.section ".text16", "ax", @progbits
.code16
pxenv_entry:
pushl $pxe_api_call
pushw %cs
call prot_call
addl $4, %esp
lret
.size pxenv_entry, . - pxenv_entry
/****************************************************************************
* pxe_entry
*
* PXE API call !PXE entry point
*
* Parameters:
* stack : Far pointer to PXE parameter structure
* stack : PXE API call
* Returns:
* %ax : PXE exit status
* Corrupts:
* none
****************************************************************************
*/
.section ".text16", "ax", @progbits
.code16
pxe_entry:
pxe_entry_sp:
/* Preserve original %esp */
pushl %esp
/* Zero high word of %esp to allow use of common code */
movzwl %sp, %esp
jmp pxe_entry_common
pxe_entry_esp:
/* Preserve %esp to match behaviour of pxe_entry_sp */
pushl %esp
pxe_entry_common:
/* Save PXENV+ API call registers */
pushw %es
pushw %di
pushw %bx
/* Load !PXE parameters from stack into PXENV+ registers */
addr32 movw 18(%esp), %bx
movw %bx, %es
addr32 movw 16(%esp), %di
addr32 movw 14(%esp), %bx
/* Make call as for PXENV+ */
pushw %cs
call pxenv_entry
/* Restore PXENV+ registers */
popw %bx
popw %di
popw %es
/* Restore original %esp and return */
popl %esp
lret
.size pxe_entry, . - pxe_entry
/****************************************************************************
* pxe_int_1a
*
* PXE INT 1A handler
*
* Parameters:
* %ax : 0x5650
* Returns:
* %ax : 0x564e
* %es:bx : Far pointer to the PXENV+ structure
* %edx : Physical address of the PXENV+ structure
* CF cleared
* Corrupts:
* none
****************************************************************************
*/
.section ".text16", "ax", @progbits
.code16
.globl pxe_int_1a
pxe_int_1a:
pushfw
cmpw $0x5650, %ax
jne 1f
/* INT 1A,5650 - PXE installation check */
xorl %edx, %edx
movw %cs, %dx
movw %dx, %es
movw $pxenv, %bx
shll $4, %edx
addl $pxenv, %edx
movw $0x564e, %ax
pushw %bp
movw %sp, %bp
andb $~0x01, 8(%bp) /* Clear CF on return */
popw %bp
popfw
iret
1: /* INT 1A,other - pass through */
popfw
ljmp *%cs:pxe_int_1a_vector
.section ".text16.data", "aw", @progbits
.globl pxe_int_1a_vector
pxe_int_1a_vector: .long 0

View File

@@ -0,0 +1,65 @@
/** @file
*
* PXE exit hook
*
*/
/*
* Copyright (C) 2010 Shao Miller <shao.miller@yrdsb.edu.on.ca>.
*
* 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 <pxe.h>
/** PXE exit hook */
extern segoff_t __data16 ( pxe_exit_hook );
#define pxe_exit_hook __use_data16 ( pxe_exit_hook )
/**
* FILE EXIT HOOK
*
* @v file_exit_hook Pointer to a struct
* s_PXENV_FILE_EXIT_HOOK
* @v s_PXENV_FILE_EXIT_HOOK::Hook SEG16:OFF16 to jump to
* @ret #PXENV_EXIT_SUCCESS Successfully set hook
* @ret #PXENV_EXIT_FAILURE We're not an NBP build
* @ret s_PXENV_FILE_EXIT_HOOK::Status PXE status code
*
*/
static PXENV_EXIT_t
pxenv_file_exit_hook ( struct s_PXENV_FILE_EXIT_HOOK *file_exit_hook ) {
DBG ( "PXENV_FILE_EXIT_HOOK" );
/* We'll jump to the specified SEG16:OFF16 during exit */
pxe_exit_hook.segment = file_exit_hook->Hook.segment;
pxe_exit_hook.offset = file_exit_hook->Hook.offset;
file_exit_hook->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/** PXE file API */
struct pxe_api_call pxe_file_api_exit_hook __pxe_api_call =
PXE_API_CALL ( PXENV_FILE_EXIT_HOOK, pxenv_file_exit_hook,
struct s_PXENV_FILE_EXIT_HOOK );

View File

@@ -0,0 +1,346 @@
/** @file
*
* PXE FILE API
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/uaccess.h>
#include <ipxe/posix_io.h>
#include <ipxe/features.h>
#include <pxe.h>
#include <realmode.h>
/*
* 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 );
FEATURE ( FEATURE_MISC, "PXEXT", DHCP_EB_FEATURE_PXE_EXT, 2 );
/**
* FILE OPEN
*
* @v file_open Pointer to a struct s_PXENV_FILE_OPEN
* @v s_PXENV_FILE_OPEN::FileName URL of file to open
* @ret #PXENV_EXIT_SUCCESS File was opened
* @ret #PXENV_EXIT_FAILURE File was not opened
* @ret s_PXENV_FILE_OPEN::Status PXE status code
* @ret s_PXENV_FILE_OPEN::FileHandle Handle of opened file
*
*/
static PXENV_EXIT_t pxenv_file_open ( struct s_PXENV_FILE_OPEN *file_open ) {
userptr_t filename;
size_t filename_len;
int fd;
DBG ( "PXENV_FILE_OPEN" );
/* Copy name from external program, and open it */
filename = real_to_user ( file_open->FileName.segment,
file_open->FileName.offset );
filename_len = strlen_user ( filename, 0 );
{
char uri_string[ filename_len + 1 ];
copy_from_user ( uri_string, filename, 0,
sizeof ( uri_string ) );
DBG ( " %s", uri_string );
fd = open ( uri_string );
}
if ( fd < 0 ) {
file_open->Status = PXENV_STATUS ( fd );
return PXENV_EXIT_FAILURE;
}
DBG ( " as file %d", fd );
file_open->FileHandle = fd;
file_open->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* FILE CLOSE
*
* @v file_close Pointer to a struct s_PXENV_FILE_CLOSE
* @v s_PXENV_FILE_CLOSE::FileHandle File handle
* @ret #PXENV_EXIT_SUCCESS File was closed
* @ret #PXENV_EXIT_FAILURE File was not closed
* @ret s_PXENV_FILE_CLOSE::Status PXE status code
*
*/
static PXENV_EXIT_t pxenv_file_close ( struct s_PXENV_FILE_CLOSE *file_close ) {
DBG ( "PXENV_FILE_CLOSE %d", file_close->FileHandle );
close ( file_close->FileHandle );
file_close->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* FILE SELECT
*
* @v file_select Pointer to a struct s_PXENV_FILE_SELECT
* @v s_PXENV_FILE_SELECT::FileHandle File handle
* @ret #PXENV_EXIT_SUCCESS File has been checked for readiness
* @ret #PXENV_EXIT_FAILURE File has not been checked for readiness
* @ret s_PXENV_FILE_SELECT::Status PXE status code
* @ret s_PXENV_FILE_SELECT::Ready Indication of readiness
*
*/
static PXENV_EXIT_t
pxenv_file_select ( struct s_PXENV_FILE_SELECT *file_select ) {
fd_set fdset;
int ready;
DBG ( "PXENV_FILE_SELECT %d", file_select->FileHandle );
FD_ZERO ( &fdset );
FD_SET ( file_select->FileHandle, &fdset );
if ( ( ready = select ( &fdset, 0 ) ) < 0 ) {
file_select->Status = PXENV_STATUS ( ready );
return PXENV_EXIT_FAILURE;
}
file_select->Ready = ( ready ? RDY_READ : 0 );
file_select->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* FILE READ
*
* @v file_read Pointer to a struct s_PXENV_FILE_READ
* @v s_PXENV_FILE_READ::FileHandle File handle
* @v s_PXENV_FILE_READ::BufferSize Size of data buffer
* @v s_PXENV_FILE_READ::Buffer Data buffer
* @ret #PXENV_EXIT_SUCCESS Data has been read from file
* @ret #PXENV_EXIT_FAILURE Data has not been read from file
* @ret s_PXENV_FILE_READ::Status PXE status code
* @ret s_PXENV_FILE_READ::Ready Indication of readiness
* @ret s_PXENV_FILE_READ::BufferSize Length of data read
*
*/
static PXENV_EXIT_t pxenv_file_read ( struct s_PXENV_FILE_READ *file_read ) {
userptr_t buffer;
ssize_t len;
DBG ( "PXENV_FILE_READ %d to %04x:%04x+%04x", file_read->FileHandle,
file_read->Buffer.segment, file_read->Buffer.offset,
file_read->BufferSize );
buffer = real_to_user ( file_read->Buffer.segment,
file_read->Buffer.offset );
if ( ( len = read_user ( file_read->FileHandle, buffer, 0,
file_read->BufferSize ) ) < 0 ) {
file_read->Status = PXENV_STATUS ( len );
return PXENV_EXIT_FAILURE;
}
DBG ( " read %04zx", ( ( size_t ) len ) );
file_read->BufferSize = len;
file_read->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* GET FILE SIZE
*
* @v get_file_size Pointer to a struct s_PXENV_GET_FILE_SIZE
* @v s_PXENV_GET_FILE_SIZE::FileHandle File handle
* @ret #PXENV_EXIT_SUCCESS File size has been determined
* @ret #PXENV_EXIT_FAILURE File size has not been determined
* @ret s_PXENV_GET_FILE_SIZE::Status PXE status code
* @ret s_PXENV_GET_FILE_SIZE::FileSize Size of file
*/
static PXENV_EXIT_t
pxenv_get_file_size ( struct s_PXENV_GET_FILE_SIZE *get_file_size ) {
ssize_t filesize;
DBG ( "PXENV_GET_FILE_SIZE %d", get_file_size->FileHandle );
filesize = fsize ( get_file_size->FileHandle );
if ( filesize < 0 ) {
get_file_size->Status = PXENV_STATUS ( filesize );
return PXENV_EXIT_FAILURE;
}
DBG ( " is %zd", ( ( size_t ) filesize ) );
get_file_size->FileSize = filesize;
get_file_size->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* FILE EXEC
*
* @v file_exec Pointer to a struct s_PXENV_FILE_EXEC
* @v s_PXENV_FILE_EXEC::Command Command to execute
* @ret #PXENV_EXIT_SUCCESS Command was executed successfully
* @ret #PXENV_EXIT_FAILURE Command was not executed successfully
* @ret s_PXENV_FILE_EXEC::Status PXE status code
*
*/
static PXENV_EXIT_t pxenv_file_exec ( struct s_PXENV_FILE_EXEC *file_exec ) {
userptr_t command;
size_t command_len;
int rc;
DBG ( "PXENV_FILE_EXEC" );
/* Copy name from external program, and exec it */
command = real_to_user ( file_exec->Command.segment,
file_exec->Command.offset );
command_len = strlen_user ( command, 0 );
{
char command_string[ command_len + 1 ];
copy_from_user ( command_string, command, 0,
sizeof ( command_string ) );
DBG ( " %s", command_string );
if ( ( rc = system ( command_string ) ) != 0 ) {
file_exec->Status = PXENV_STATUS ( rc );
return PXENV_EXIT_FAILURE;
}
}
file_exec->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* FILE CMDLINE
*
* @v file_cmdline Pointer to a struct s_PXENV_FILE_CMDLINE
* @v s_PXENV_FILE_CMDLINE::Buffer Buffer to contain command line
* @v s_PXENV_FILE_CMDLINE::BufferSize Size of buffer
* @ret #PXENV_EXIT_SUCCESS Command was executed successfully
* @ret #PXENV_EXIT_FAILURE Command was not executed successfully
* @ret s_PXENV_FILE_EXEC::Status PXE status code
* @ret s_PXENV_FILE_EXEC::BufferSize Length of command line (including NUL)
*
*/
static PXENV_EXIT_t
pxenv_file_cmdline ( struct s_PXENV_FILE_CMDLINE *file_cmdline ) {
userptr_t buffer;
size_t max_len;
size_t len;
DBG ( "PXENV_FILE_CMDLINE to %04x:%04x+%04x \"%s\"\n",
file_cmdline->Buffer.segment, file_cmdline->Buffer.offset,
file_cmdline->BufferSize, pxe_cmdline );
buffer = real_to_user ( file_cmdline->Buffer.segment,
file_cmdline->Buffer.offset );
len = file_cmdline->BufferSize;
max_len = ( pxe_cmdline ?
( strlen ( pxe_cmdline ) + 1 /* NUL */ ) : 0 );
if ( len > max_len )
len = max_len;
copy_to_user ( buffer, 0, pxe_cmdline, len );
file_cmdline->BufferSize = max_len;
file_cmdline->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* FILE API CHECK
*
* @v file_exec Pointer to a struct s_PXENV_FILE_API_CHECK
* @v s_PXENV_FILE_API_CHECK::Magic Inbound magic number (0x91d447b2)
* @ret #PXENV_EXIT_SUCCESS Command was executed successfully
* @ret #PXENV_EXIT_FAILURE Command was not executed successfully
* @ret s_PXENV_FILE_API_CHECK::Status PXE status code
* @ret s_PXENV_FILE_API_CHECK::Magic Outbound magic number (0xe9c17b20)
* @ret s_PXENV_FILE_API_CHECK::Provider "iPXE" (0x45585067)
* @ret s_PXENV_FILE_API_CHECK::APIMask API function bitmask
* @ret s_PXENV_FILE_API_CHECK::Flags Reserved
*
*/
static PXENV_EXIT_t
pxenv_file_api_check ( struct s_PXENV_FILE_API_CHECK *file_api_check ) {
struct pxe_api_call *call;
unsigned int mask = 0;
unsigned int offset;
DBG ( "PXENV_FILE_API_CHECK" );
/* Check for magic value */
if ( file_api_check->Magic != 0x91d447b2 ) {
file_api_check->Status = PXENV_STATUS_BAD_FUNC;
return PXENV_EXIT_FAILURE;
}
/* Check for required parameter size */
if ( file_api_check->Size < sizeof ( *file_api_check ) ) {
file_api_check->Status = PXENV_STATUS_OUT_OF_RESOURCES;
return PXENV_EXIT_FAILURE;
}
/* Determine supported calls */
for_each_table_entry ( call, PXE_API_CALLS ) {
offset = ( call->opcode - PXENV_FILE_MIN );
if ( offset <= ( PXENV_FILE_MAX - PXENV_FILE_MIN ) )
mask |= ( 1 << offset );
}
/* Fill in parameters */
file_api_check->Size = sizeof ( *file_api_check );
file_api_check->Magic = 0xe9c17b20;
file_api_check->Provider = 0x45585067; /* "iPXE" */
file_api_check->APIMask = mask;
file_api_check->Flags = 0; /* None defined */
file_api_check->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/** PXE file API */
struct pxe_api_call pxe_file_api[] __pxe_api_call = {
PXE_API_CALL ( PXENV_FILE_OPEN, pxenv_file_open,
struct s_PXENV_FILE_OPEN ),
PXE_API_CALL ( PXENV_FILE_CLOSE, pxenv_file_close,
struct s_PXENV_FILE_CLOSE ),
PXE_API_CALL ( PXENV_FILE_SELECT, pxenv_file_select,
struct s_PXENV_FILE_SELECT ),
PXE_API_CALL ( PXENV_FILE_READ, pxenv_file_read,
struct s_PXENV_FILE_READ ),
PXE_API_CALL ( PXENV_GET_FILE_SIZE, pxenv_get_file_size,
struct s_PXENV_GET_FILE_SIZE ),
PXE_API_CALL ( PXENV_FILE_EXEC, pxenv_file_exec,
struct s_PXENV_FILE_EXEC ),
PXE_API_CALL ( PXENV_FILE_CMDLINE, pxenv_file_cmdline,
struct s_PXENV_FILE_CMDLINE ),
PXE_API_CALL ( PXENV_FILE_API_CHECK, pxenv_file_api_check,
struct s_PXENV_FILE_API_CHECK ),
};

View File

@@ -0,0 +1,55 @@
/*
* 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 <ipxe/init.h>
#include "pxe.h"
#include "pxe_call.h"
/** @file
*
* PXE UNDI loader
*
*/
/* PXENV_UNDI_LOADER
*
*/
PXENV_EXIT_t undi_loader ( struct s_UNDI_LOADER *undi_loader ) {
/* Perform one-time initialisation (e.g. heap) */
initialise();
DBG ( "[PXENV_UNDI_LOADER to CS %04x DS %04x]",
undi_loader->UNDI_CS, undi_loader->UNDI_DS );
/* Fill in UNDI loader structure */
undi_loader->PXEptr.segment = rm_cs;
undi_loader->PXEptr.offset = __from_text16 ( &ppxe );
undi_loader->PXENVptr.segment = rm_cs;
undi_loader->PXENVptr.offset = __from_text16 ( &pxenv );
undi_loader->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}

View File

@@ -0,0 +1,397 @@
/** @file
*
* PXE Preboot API
*
*/
/* PXE API interface for Etherboot.
*
* Copyright (C) 2004 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 <stdlib.h>
#include <ipxe/uaccess.h>
#include <ipxe/dhcp.h>
#include <ipxe/fakedhcp.h>
#include <ipxe/device.h>
#include <ipxe/netdevice.h>
#include <ipxe/isapnp.h>
#include <ipxe/init.h>
#include <ipxe/if_ether.h>
#include <basemem_packet.h>
#include <biosint.h>
#include <rmsetjmp.h>
#include "pxe.h"
#include "pxe_call.h"
/* Avoid dragging in isapnp.o unnecessarily */
uint16_t isapnp_read_port;
/** Zero-based versions of PXENV_GET_CACHED_INFO::PacketType */
enum pxe_cached_info_indices {
CACHED_INFO_DHCPDISCOVER = ( PXENV_PACKET_TYPE_DHCP_DISCOVER - 1 ),
CACHED_INFO_DHCPACK = ( PXENV_PACKET_TYPE_DHCP_ACK - 1 ),
CACHED_INFO_BINL = ( PXENV_PACKET_TYPE_CACHED_REPLY - 1 ),
NUM_CACHED_INFOS
};
/** A cached DHCP packet */
union pxe_cached_info {
struct dhcphdr dhcphdr;
/* This buffer must be *exactly* the size of a BOOTPLAYER_t
* structure, otherwise WinPE will die horribly. It takes the
* size of *our* buffer and feeds it in to us as the size of
* one of *its* buffers. If our buffer is larger than it
* expects, we therefore end up overwriting part of its data
* segment, since it tells us to do so. (D'oh!)
*
* Note that a BOOTPLAYER_t is not necessarily large enough to
* hold a DHCP packet; this is a flaw in the PXE spec.
*/
BOOTPLAYER_t packet;
} __attribute__ (( packed ));
/** A PXE DHCP packet creator */
struct pxe_dhcp_packet_creator {
/** Create DHCP packet
*
* @v netdev Network device
* @v data Buffer for DHCP packet
* @v max_len Size of DHCP packet buffer
* @ret rc Return status code
*/
int ( * create ) ( struct net_device *netdev, void *data,
size_t max_len );
};
/** PXE DHCP packet creators */
static struct pxe_dhcp_packet_creator pxe_dhcp_packet_creators[] = {
[CACHED_INFO_DHCPDISCOVER] = { create_fakedhcpdiscover },
[CACHED_INFO_DHCPACK] = { create_fakedhcpack },
[CACHED_INFO_BINL] = { create_fakepxebsack },
};
/**
* Name PXENV_GET_CACHED_INFO packet type
*
* @v packet_type Packet type
* @ret name Name of packet type
*/
static inline __attribute__ (( always_inline )) const char *
pxenv_get_cached_info_name ( int packet_type ) {
switch ( packet_type ) {
case PXENV_PACKET_TYPE_DHCP_DISCOVER:
return "DHCPDISCOVER";
case PXENV_PACKET_TYPE_DHCP_ACK:
return "DHCPACK";
case PXENV_PACKET_TYPE_CACHED_REPLY:
return "BINL";
default:
return "<INVALID>";
}
}
/* The case in which the caller doesn't supply a buffer is really
* awkward to support given that we have multiple sources of options,
* and that we don't actually store the DHCP packets. (We may not
* even have performed DHCP; we may have obtained all configuration
* from non-volatile stored options or from the command line.)
*
* Some NBPs rely on the buffers we provide being persistent, so we
* can't just use the temporary packet buffer. 4.5kB of base memory
* always wasted just because some clients are too lazy to provide
* their own buffers...
*/
static union pxe_cached_info __bss16_array ( cached_info, [NUM_CACHED_INFOS] );
#define cached_info __use_data16 ( cached_info )
/**
* Construct cached DHCP packets
*
*/
void pxe_fake_cached_info ( void ) {
struct pxe_dhcp_packet_creator *creator;
union pxe_cached_info *info;
unsigned int i;
int rc;
/* Sanity check */
assert ( pxe_netdev != NULL );
/* Erase any stale packets */
memset ( cached_info, 0, sizeof ( cached_info ) );
/* Construct all DHCP packets */
for ( i = 0 ; i < ( sizeof ( pxe_dhcp_packet_creators ) /
sizeof ( pxe_dhcp_packet_creators[0] ) ) ; i++ ) {
/* Construct DHCP packet */
creator = &pxe_dhcp_packet_creators[i];
info = &cached_info[i];
if ( ( rc = creator->create ( pxe_netdev, info,
sizeof ( *info ) ) ) != 0 ) {
DBGC ( &pxe_netdev, " failed to build packet: %s\n",
strerror ( rc ) );
/* Continue constructing remaining packets */
}
}
}
/**
* UNLOAD BASE CODE STACK
*
* @v None -
* @ret ...
*
*/
static PXENV_EXIT_t
pxenv_unload_stack ( struct s_PXENV_UNLOAD_STACK *unload_stack ) {
DBGC ( &pxe_netdev, "PXENV_UNLOAD_STACK\n" );
unload_stack->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/* PXENV_GET_CACHED_INFO
*
* Status: working
*/
static PXENV_EXIT_t
pxenv_get_cached_info ( struct s_PXENV_GET_CACHED_INFO *get_cached_info ) {
union pxe_cached_info *info;
unsigned int idx;
size_t len;
userptr_t buffer;
DBGC ( &pxe_netdev, "PXENV_GET_CACHED_INFO %s to %04x:%04x+%x",
pxenv_get_cached_info_name ( get_cached_info->PacketType ),
get_cached_info->Buffer.segment,
get_cached_info->Buffer.offset, get_cached_info->BufferSize );
/* Sanity check */
idx = ( get_cached_info->PacketType - 1 );
if ( idx >= NUM_CACHED_INFOS ) {
DBGC ( &pxe_netdev, " bad PacketType %d\n",
get_cached_info->PacketType );
get_cached_info->Status = PXENV_STATUS_UNSUPPORTED;
return PXENV_EXIT_FAILURE;
}
info = &cached_info[idx];
/* Copy packet (if applicable) */
len = get_cached_info->BufferSize;
if ( len == 0 ) {
/* Point client at our cached buffer.
*
* To add to the fun, Intel decided at some point in
* the evolution of the PXE specification to add the
* BufferLimit field, which we are meant to fill in
* with the length of our packet buffer, so that the
* caller can safely modify the boot server reply
* packet stored therein. However, this field was not
* present in earlier versions of the PXE spec, and
* there is at least one PXE NBP (Altiris) which
* allocates only exactly enough space for this
* earlier, shorter version of the structure. If we
* actually fill in the BufferLimit field, we
* therefore risk trashing random areas of the
* caller's memory. If we *don't* fill it in, then
* the caller is at liberty to assume that whatever
* random value happened to be in that location
* represents the length of the buffer we've just
* passed back to it.
*
* Since older PXE stacks won't fill this field in
* anyway, it's probably safe to assume that no
* callers actually rely on it, so we choose to not
* fill it in.
*/
get_cached_info->Buffer.segment = rm_ds;
get_cached_info->Buffer.offset = __from_data16 ( info );
get_cached_info->BufferSize = sizeof ( *info );
DBGC ( &pxe_netdev, " using %04x:%04x+%04x['%x']",
get_cached_info->Buffer.segment,
get_cached_info->Buffer.offset,
get_cached_info->BufferSize,
get_cached_info->BufferLimit );
} else {
/* Copy packet to client buffer */
if ( len > sizeof ( *info ) )
len = sizeof ( *info );
if ( len < sizeof ( *info ) )
DBGC ( &pxe_netdev, " buffer may be too short" );
buffer = real_to_user ( get_cached_info->Buffer.segment,
get_cached_info->Buffer.offset );
copy_to_user ( buffer, 0, info, len );
get_cached_info->BufferSize = len;
}
DBGC ( &pxe_netdev, "\n" );
get_cached_info->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/* PXENV_RESTART_TFTP
*
* Status: working
*/
static PXENV_EXIT_t
pxenv_restart_tftp ( struct s_PXENV_TFTP_READ_FILE *restart_tftp ) {
PXENV_EXIT_t tftp_exit;
DBGC ( &pxe_netdev, "PXENV_RESTART_TFTP\n" );
/* Words cannot describe the complete mismatch between the PXE
* specification and any possible version of reality...
*/
restart_tftp->Buffer = PXE_LOAD_PHYS; /* Fixed by spec, apparently */
restart_tftp->BufferSize = ( 0xa0000 - PXE_LOAD_PHYS ); /* Near enough */
tftp_exit = pxenv_tftp_read_file ( restart_tftp );
if ( tftp_exit != PXENV_EXIT_SUCCESS )
return tftp_exit;
/* Restart NBP */
rmlongjmp ( pxe_restart_nbp, PXENV_RESTART_TFTP );
}
/* PXENV_START_UNDI
*
* Status: working
*/
static PXENV_EXIT_t pxenv_start_undi ( struct s_PXENV_START_UNDI *start_undi ) {
unsigned int bus_type;
unsigned int location;
struct net_device *netdev;
DBGC ( &pxe_netdev, "PXENV_START_UNDI %04x:%04x:%04x\n",
start_undi->AX, start_undi->BX, start_undi->DX );
/* Determine bus type and location. Use a heuristic to decide
* whether we are PCI or ISAPnP
*/
if ( ( start_undi->DX >= ISAPNP_READ_PORT_MIN ) &&
( start_undi->DX <= ISAPNP_READ_PORT_MAX ) &&
( start_undi->BX >= ISAPNP_CSN_MIN ) &&
( start_undi->BX <= ISAPNP_CSN_MAX ) ) {
bus_type = BUS_TYPE_ISAPNP;
location = start_undi->BX;
/* Record ISAPnP read port for use by isapnp.c */
isapnp_read_port = start_undi->DX;
} else {
bus_type = BUS_TYPE_PCI;
location = start_undi->AX;
}
/* Probe for devices, etc. */
startup();
/* Look for a matching net device */
netdev = find_netdev_by_location ( bus_type, location );
if ( ! netdev ) {
DBGC ( &pxe_netdev, "PXENV_START_UNDI could not find matching "
"net device\n" );
start_undi->Status = PXENV_STATUS_UNDI_CANNOT_INITIALIZE_NIC;
return PXENV_EXIT_FAILURE;
}
DBGC ( &pxe_netdev, "PXENV_START_UNDI found net device %s\n",
netdev->name );
/* Activate PXE */
pxe_activate ( netdev );
start_undi->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/* PXENV_STOP_UNDI
*
* Status: working
*/
static PXENV_EXIT_t pxenv_stop_undi ( struct s_PXENV_STOP_UNDI *stop_undi ) {
DBGC ( &pxe_netdev, "PXENV_STOP_UNDI\n" );
/* Deactivate PXE */
pxe_deactivate();
/* Prepare for unload */
shutdown_boot();
/* Check to see if we still have any hooked interrupts */
if ( hooked_bios_interrupts != 0 ) {
DBGC ( &pxe_netdev, "PXENV_STOP_UNDI failed: %d interrupts "
"still hooked\n", hooked_bios_interrupts );
stop_undi->Status = PXENV_STATUS_KEEP_UNDI;
return PXENV_EXIT_FAILURE;
}
stop_undi->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/* PXENV_START_BASE
*
* Status: won't implement (requires major structural changes)
*/
static PXENV_EXIT_t pxenv_start_base ( struct s_PXENV_START_BASE *start_base ) {
DBGC ( &pxe_netdev, "PXENV_START_BASE\n" );
start_base->Status = PXENV_STATUS_UNSUPPORTED;
return PXENV_EXIT_FAILURE;
}
/* PXENV_STOP_BASE
*
* Status: working
*/
static PXENV_EXIT_t pxenv_stop_base ( struct s_PXENV_STOP_BASE *stop_base ) {
DBGC ( &pxe_netdev, "PXENV_STOP_BASE\n" );
/* The only time we will be called is when the NBP is trying
* to shut down the PXE stack. There's nothing we need to do
* in this call.
*/
stop_base->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/** PXE preboot API */
struct pxe_api_call pxe_preboot_api[] __pxe_api_call = {
PXE_API_CALL ( PXENV_UNLOAD_STACK, pxenv_unload_stack,
struct s_PXENV_UNLOAD_STACK ),
PXE_API_CALL ( PXENV_GET_CACHED_INFO, pxenv_get_cached_info,
struct s_PXENV_GET_CACHED_INFO ),
PXE_API_CALL ( PXENV_RESTART_TFTP, pxenv_restart_tftp,
struct s_PXENV_TFTP_READ_FILE ),
PXE_API_CALL ( PXENV_START_UNDI, pxenv_start_undi,
struct s_PXENV_START_UNDI ),
PXE_API_CALL ( PXENV_STOP_UNDI, pxenv_stop_undi,
struct s_PXENV_STOP_UNDI ),
PXE_API_CALL ( PXENV_START_BASE, pxenv_start_base,
struct s_PXENV_START_BASE ),
PXE_API_CALL ( PXENV_STOP_BASE, pxenv_stop_base,
struct s_PXENV_STOP_BASE ),
};

View File

@@ -0,0 +1,595 @@
/** @file
*
* PXE TFTP API
*
*/
/*
* Copyright (C) 2004 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 <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/uaccess.h>
#include <ipxe/in.h>
#include <ipxe/tftp.h>
#include <ipxe/iobuf.h>
#include <ipxe/xfer.h>
#include <ipxe/open.h>
#include <ipxe/process.h>
#include <ipxe/uri.h>
#include <realmode.h>
#include <pxe.h>
/** A PXE TFTP connection */
struct pxe_tftp_connection {
/** Data transfer interface */
struct interface xfer;
/** Data buffer */
userptr_t buffer;
/** Size of data buffer */
size_t size;
/** Starting offset of data buffer */
size_t start;
/** File position */
size_t offset;
/** Maximum file position */
size_t max_offset;
/** Block size */
size_t blksize;
/** Block index */
unsigned int blkidx;
/** Overall return status code */
int rc;
};
/**
* Close PXE TFTP connection
*
* @v pxe_tftp PXE TFTP connection
* @v rc Final status code
*/
static void pxe_tftp_close ( struct pxe_tftp_connection *pxe_tftp, int rc ) {
intf_shutdown ( &pxe_tftp->xfer, rc );
pxe_tftp->rc = rc;
}
/**
* Check flow control window
*
* @v pxe_tftp PXE TFTP connection
* @ret len Length of window
*/
static size_t pxe_tftp_xfer_window ( struct pxe_tftp_connection *pxe_tftp ) {
return pxe_tftp->blksize;
}
/**
* Receive new data
*
* @v pxe_tftp PXE TFTP connection
* @v iobuf I/O buffer
* @v meta Transfer metadata
* @ret rc Return status code
*/
static int pxe_tftp_xfer_deliver ( struct pxe_tftp_connection *pxe_tftp,
struct io_buffer *iobuf,
struct xfer_metadata *meta ) {
size_t len = iob_len ( iobuf );
int rc = 0;
/* Calculate new buffer position */
if ( meta->flags & XFER_FL_ABS_OFFSET )
pxe_tftp->offset = 0;
pxe_tftp->offset += meta->offset;
/* Copy data block to buffer */
if ( len == 0 ) {
/* No data (pure seek); treat as success */
} else if ( pxe_tftp->offset < pxe_tftp->start ) {
DBG ( " buffer underrun at %zx (min %zx)",
pxe_tftp->offset, pxe_tftp->start );
rc = -ENOBUFS;
} else if ( ( pxe_tftp->offset + len ) >
( pxe_tftp->start + pxe_tftp->size ) ) {
DBG ( " buffer overrun at %zx (max %zx)",
( pxe_tftp->offset + len ),
( pxe_tftp->start + pxe_tftp->size ) );
rc = -ENOBUFS;
} else {
copy_to_user ( pxe_tftp->buffer,
( pxe_tftp->offset - pxe_tftp->start ),
iobuf->data, len );
}
/* Calculate new buffer position */
pxe_tftp->offset += len;
/* Record maximum offset as the file size */
if ( pxe_tftp->max_offset < pxe_tftp->offset )
pxe_tftp->max_offset = pxe_tftp->offset;
/* Terminate transfer on error */
if ( rc != 0 )
pxe_tftp_close ( pxe_tftp, rc );
free_iob ( iobuf );
return rc;
}
/** PXE TFTP connection interface operations */
static struct interface_operation pxe_tftp_xfer_ops[] = {
INTF_OP ( xfer_deliver, struct pxe_tftp_connection *,
pxe_tftp_xfer_deliver ),
INTF_OP ( xfer_window, struct pxe_tftp_connection *,
pxe_tftp_xfer_window ),
INTF_OP ( intf_close, struct pxe_tftp_connection *, pxe_tftp_close ),
};
/** PXE TFTP connection interface descriptor */
static struct interface_descriptor pxe_tftp_xfer_desc =
INTF_DESC ( struct pxe_tftp_connection, xfer, pxe_tftp_xfer_ops );
/** The PXE TFTP connection */
static struct pxe_tftp_connection pxe_tftp = {
.xfer = INTF_INIT ( pxe_tftp_xfer_desc ),
};
/**
* Open PXE TFTP connection
*
* @v ipaddress IP address
* @v port TFTP server port (in network byte order)
* @v filename File name
* @v blksize Requested block size
* @ret rc Return status code
*/
static int pxe_tftp_open ( IP4_t ipaddress, UDP_PORT_t port,
UINT8_t *filename, UINT16_t blksize ) {
union {
struct sockaddr sa;
struct sockaddr_in sin;
} server;
struct uri *uri;
int rc;
/* Reset PXE TFTP connection structure */
memset ( &pxe_tftp, 0, sizeof ( pxe_tftp ) );
intf_init ( &pxe_tftp.xfer, &pxe_tftp_xfer_desc, NULL );
if ( blksize < TFTP_DEFAULT_BLKSIZE )
blksize = TFTP_DEFAULT_BLKSIZE;
pxe_tftp.blksize = blksize;
pxe_tftp.rc = -EINPROGRESS;
/* Construct URI */
memset ( &server, 0, sizeof ( server ) );
server.sin.sin_family = AF_INET;
server.sin.sin_addr.s_addr = ipaddress;
server.sin.sin_port = port;
DBG ( " %s", sock_ntoa ( &server.sa ) );
if ( port )
DBG ( ":%d", ntohs ( port ) );
DBG ( ":%s", filename );
uri = pxe_uri ( &server.sa, ( ( char * ) filename ) );
if ( ! uri ) {
DBG ( " could not create URI\n" );
return -ENOMEM;
}
/* Open PXE TFTP connection */
if ( ( rc = xfer_open_uri ( &pxe_tftp.xfer, uri ) ) != 0 ) {
DBG ( " could not open (%s)\n", strerror ( rc ) );
return rc;
}
return 0;
}
/**
* TFTP OPEN
*
* @v tftp_open Pointer to a struct s_PXENV_TFTP_OPEN
* @v s_PXENV_TFTP_OPEN::ServerIPAddress TFTP server IP address
* @v s_PXENV_TFTP_OPEN::GatewayIPAddress Relay agent IP address, or 0.0.0.0
* @v s_PXENV_TFTP_OPEN::FileName Name of file to open
* @v s_PXENV_TFTP_OPEN::TFTPPort TFTP server UDP port
* @v s_PXENV_TFTP_OPEN::PacketSize TFTP blksize option to request
* @ret #PXENV_EXIT_SUCCESS File was opened
* @ret #PXENV_EXIT_FAILURE File was not opened
* @ret s_PXENV_TFTP_OPEN::Status PXE status code
* @ret s_PXENV_TFTP_OPEN::PacketSize Negotiated blksize
* @err #PXENV_STATUS_TFTP_INVALID_PACKET_SIZE Requested blksize too small
*
* Opens a TFTP connection for downloading a file a block at a time
* using pxenv_tftp_read().
*
* If s_PXENV_TFTP_OPEN::GatewayIPAddress is 0.0.0.0, normal IP
* routing will take place. See the relevant
* @ref pxe_routing "implementation note" for more details.
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*
* @note According to the PXE specification version 2.1, this call
* "opens a file for reading/writing", though how writing is to be
* achieved without the existence of an API call %pxenv_tftp_write()
* is not made clear.
*
* @note Despite the existence of the numerous statements within the
* PXE specification of the form "...if a TFTP/MTFTP or UDP connection
* is active...", you cannot use pxenv_tftp_open() and
* pxenv_tftp_read() to read a file via MTFTP; only via plain old
* TFTP. If you want to use MTFTP, use pxenv_tftp_read_file()
* instead. Astute readers will note that, since
* pxenv_tftp_read_file() is an atomic operation from the point of
* view of the PXE API, it is conceptually impossible to issue any
* other PXE API call "if an MTFTP connection is active".
*/
static PXENV_EXIT_t pxenv_tftp_open ( struct s_PXENV_TFTP_OPEN *tftp_open ) {
int rc;
DBG ( "PXENV_TFTP_OPEN" );
/* Guard against callers that fail to close before re-opening */
pxe_tftp_close ( &pxe_tftp, 0 );
/* Open connection */
if ( ( rc = pxe_tftp_open ( tftp_open->ServerIPAddress,
tftp_open->TFTPPort,
tftp_open->FileName,
tftp_open->PacketSize ) ) != 0 ) {
tftp_open->Status = PXENV_STATUS ( rc );
return PXENV_EXIT_FAILURE;
}
/* Wait for OACK to arrive so that we have the block size */
while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
( pxe_tftp.max_offset == 0 ) ) {
step();
}
pxe_tftp.blksize = xfer_window ( &pxe_tftp.xfer );
tftp_open->PacketSize = pxe_tftp.blksize;
DBG ( " blksize=%d", tftp_open->PacketSize );
/* EINPROGRESS is normal; we don't wait for the whole transfer */
if ( rc == -EINPROGRESS )
rc = 0;
tftp_open->Status = PXENV_STATUS ( rc );
return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
}
/**
* TFTP CLOSE
*
* @v tftp_close Pointer to a struct s_PXENV_TFTP_CLOSE
* @ret #PXENV_EXIT_SUCCESS File was closed successfully
* @ret #PXENV_EXIT_FAILURE File was not closed
* @ret s_PXENV_TFTP_CLOSE::Status PXE status code
* @err None -
*
* Close a connection previously opened with pxenv_tftp_open(). You
* must have previously opened a connection with pxenv_tftp_open().
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*/
static PXENV_EXIT_t pxenv_tftp_close ( struct s_PXENV_TFTP_CLOSE *tftp_close ) {
DBG ( "PXENV_TFTP_CLOSE" );
pxe_tftp_close ( &pxe_tftp, 0 );
tftp_close->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* TFTP READ
*
* @v tftp_read Pointer to a struct s_PXENV_TFTP_READ
* @v s_PXENV_TFTP_READ::Buffer Address of data buffer
* @ret #PXENV_EXIT_SUCCESS Data was read successfully
* @ret #PXENV_EXIT_FAILURE Data was not read
* @ret s_PXENV_TFTP_READ::Status PXE status code
* @ret s_PXENV_TFTP_READ::PacketNumber TFTP packet number
* @ret s_PXENV_TFTP_READ::BufferSize Length of data written into buffer
*
* Reads a single packet from a connection previously opened with
* pxenv_tftp_open() into the data buffer pointed to by
* s_PXENV_TFTP_READ::Buffer. You must have previously opened a
* connection with pxenv_tftp_open(). The data written into
* s_PXENV_TFTP_READ::Buffer is just the file data; the various
* network headers have already been removed.
*
* The buffer must be large enough to contain a packet of the size
* negotiated via the s_PXENV_TFTP_OPEN::PacketSize field in the
* pxenv_tftp_open() call. It is worth noting that the PXE
* specification does @b not require the caller to fill in
* s_PXENV_TFTP_READ::BufferSize before calling pxenv_tftp_read(), so
* the PXE stack is free to ignore whatever value the caller might
* place there and just assume that the buffer is large enough. That
* said, it may be worth the caller always filling in
* s_PXENV_TFTP_READ::BufferSize to guard against PXE stacks that
* mistake it for an input parameter.
*
* The length of the TFTP data packet will be returned via
* s_PXENV_TFTP_READ::BufferSize. If this length is less than the
* blksize negotiated via s_PXENV_TFTP_OPEN::PacketSize in the call to
* pxenv_tftp_open(), this indicates that the block is the last block
* in the file. Note that zero is a valid length for
* s_PXENV_TFTP_READ::BufferSize, and will occur when the length of
* the file is a multiple of the blksize.
*
* The PXE specification doesn't actually state that calls to
* pxenv_tftp_read() will return the data packets in strict sequential
* order, though most PXE stacks will probably do so. The sequence
* number of the packet will be returned in
* s_PXENV_TFTP_READ::PacketNumber. The first packet in the file has
* a sequence number of one, not zero.
*
* To guard against flawed PXE stacks, the caller should probably set
* s_PXENV_TFTP_READ::PacketNumber to one less than the expected
* returned value (i.e. set it to zero for the first call to
* pxenv_tftp_read() and then re-use the returned s_PXENV_TFTP_READ
* parameter block for subsequent calls without modifying
* s_PXENV_TFTP_READ::PacketNumber between calls). The caller should
* also guard against potential problems caused by flawed
* implementations returning the occasional duplicate packet, by
* checking that the value returned in s_PXENV_TFTP_READ::PacketNumber
* is as expected (i.e. one greater than that returned from the
* previous call to pxenv_tftp_read()).
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*/
static PXENV_EXIT_t pxenv_tftp_read ( struct s_PXENV_TFTP_READ *tftp_read ) {
int rc;
DBG ( "PXENV_TFTP_READ to %04x:%04x",
tftp_read->Buffer.segment, tftp_read->Buffer.offset );
/* Read single block into buffer */
pxe_tftp.buffer = real_to_user ( tftp_read->Buffer.segment,
tftp_read->Buffer.offset );
pxe_tftp.size = pxe_tftp.blksize;
pxe_tftp.start = pxe_tftp.offset;
while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
( pxe_tftp.offset == pxe_tftp.start ) )
step();
pxe_tftp.buffer = UNULL;
tftp_read->BufferSize = ( pxe_tftp.offset - pxe_tftp.start );
tftp_read->PacketNumber = ++pxe_tftp.blkidx;
/* EINPROGRESS is normal if we haven't reached EOF yet */
if ( rc == -EINPROGRESS )
rc = 0;
tftp_read->Status = PXENV_STATUS ( rc );
return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
}
/**
* TFTP/MTFTP read file
*
* @v tftp_read_file Pointer to a struct s_PXENV_TFTP_READ_FILE
* @v s_PXENV_TFTP_READ_FILE::FileName File name
* @v s_PXENV_TFTP_READ_FILE::BufferSize Size of the receive buffer
* @v s_PXENV_TFTP_READ_FILE::Buffer Address of the receive buffer
* @v s_PXENV_TFTP_READ_FILE::ServerIPAddress TFTP server IP address
* @v s_PXENV_TFTP_READ_FILE::GatewayIPAddress Relay agent IP address
* @v s_PXENV_TFTP_READ_FILE::McastIPAddress File's multicast IP address
* @v s_PXENV_TFTP_READ_FILE::TFTPClntPort Client multicast UDP port
* @v s_PXENV_TFTP_READ_FILE::TFTPSrvPort Server multicast UDP port
* @v s_PXENV_TFTP_READ_FILE::TFTPOpenTimeOut Time to wait for first packet
* @v s_PXENV_TFTP_READ_FILE::TFTPReopenDelay MTFTP inactivity timeout
* @ret #PXENV_EXIT_SUCCESS File downloaded successfully
* @ret #PXENV_EXIT_FAILURE File not downloaded
* @ret s_PXENV_TFTP_READ_FILE::Status PXE status code
* @ret s_PXENV_TFTP_READ_FILE::BufferSize Length of downloaded file
*
* Downloads an entire file via either TFTP or MTFTP into the buffer
* pointed to by s_PXENV_TFTP_READ_FILE::Buffer.
*
* The PXE specification does not make it clear how the caller
* requests that MTFTP be used rather than TFTP (or vice versa). One
* reasonable guess is that setting
* s_PXENV_TFTP_READ_FILE::McastIPAddress to 0.0.0.0 would cause TFTP
* to be used instead of MTFTP, though it is conceivable that some PXE
* stacks would interpret that as "use the DHCP-provided multicast IP
* address" instead. Some PXE stacks will not implement MTFTP at all,
* and will always use TFTP.
*
* It is not specified whether or not
* s_PXENV_TFTP_READ_FILE::TFTPSrvPort will be used as the TFTP server
* port for TFTP (rather than MTFTP) downloads. Callers should assume
* that the only way to access a TFTP server on a non-standard port is
* to use pxenv_tftp_open() and pxenv_tftp_read().
*
* If s_PXENV_TFTP_READ_FILE::GatewayIPAddress is 0.0.0.0, normal IP
* routing will take place. See the relevant
* @ref pxe_routing "implementation note" for more details.
*
* It is interesting to note that s_PXENV_TFTP_READ_FILE::Buffer is an
* #ADDR32_t type, i.e. nominally a flat physical address. Some PXE
* NBPs (e.g. NTLDR) are known to call pxenv_tftp_read_file() in real
* mode with s_PXENV_TFTP_READ_FILE::Buffer set to an address above
* 1MB. This means that PXE stacks must be prepared to write to areas
* outside base memory. Exactly how this is to be achieved is not
* specified, though using INT 15,87 is as close to a standard method
* as any, and should probably be used. Switching to protected-mode
* in order to access high memory will fail if pxenv_tftp_read_file()
* is called in V86 mode; it is reasonably to expect that a V86
* monitor would intercept the relatively well-defined INT 15,87 if it
* wants the PXE stack to be able to write to high memory.
*
* Things get even more interesting if pxenv_tftp_read_file() is
* called in protected mode, because there is then absolutely no way
* for the PXE stack to write to an absolute physical address. You
* can't even get around the problem by creating a special "access
* everything" segment in the s_PXE data structure, because the
* #SEGDESC_t descriptors are limited to 64kB in size.
*
* Previous versions of the PXE specification (e.g. WfM 1.1a) provide
* a separate API call, %pxenv_tftp_read_file_pmode(), specifically to
* work around this problem. The s_PXENV_TFTP_READ_FILE_PMODE
* parameter block splits s_PXENV_TFTP_READ_FILE::Buffer into
* s_PXENV_TFTP_READ_FILE_PMODE::BufferSelector and
* s_PXENV_TFTP_READ_FILE_PMODE::BufferOffset, i.e. it provides a
* protected-mode segment:offset address for the data buffer. This
* API call is no longer present in version 2.1 of the PXE
* specification.
*
* Etherboot makes the assumption that s_PXENV_TFTP_READ_FILE::Buffer
* is an offset relative to the caller's data segment, when
* pxenv_tftp_read_file() is called in protected mode.
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*/
PXENV_EXIT_t pxenv_tftp_read_file ( struct s_PXENV_TFTP_READ_FILE
*tftp_read_file ) {
int rc;
DBG ( "PXENV_TFTP_READ_FILE to %08x+%x", tftp_read_file->Buffer,
tftp_read_file->BufferSize );
/* Open TFTP file */
if ( ( rc = pxe_tftp_open ( tftp_read_file->ServerIPAddress, 0,
tftp_read_file->FileName, 0 ) ) != 0 ) {
tftp_read_file->Status = PXENV_STATUS ( rc );
return PXENV_EXIT_FAILURE;
}
/* Read entire file */
pxe_tftp.buffer = phys_to_user ( tftp_read_file->Buffer );
pxe_tftp.size = tftp_read_file->BufferSize;
while ( ( rc = pxe_tftp.rc ) == -EINPROGRESS )
step();
pxe_tftp.buffer = UNULL;
tftp_read_file->BufferSize = pxe_tftp.max_offset;
/* Close TFTP file */
pxe_tftp_close ( &pxe_tftp, rc );
tftp_read_file->Status = PXENV_STATUS ( rc );
return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
}
/**
* TFTP GET FILE SIZE
*
* @v tftp_get_fsize Pointer to a struct s_PXENV_TFTP_GET_FSIZE
* @v s_PXENV_TFTP_GET_FSIZE::ServerIPAddress TFTP server IP address
* @v s_PXENV_TFTP_GET_FSIZE::GatewayIPAddress Relay agent IP address
* @v s_PXENV_TFTP_GET_FSIZE::FileName File name
* @ret #PXENV_EXIT_SUCCESS File size was determined successfully
* @ret #PXENV_EXIT_FAILURE File size was not determined
* @ret s_PXENV_TFTP_GET_FSIZE::Status PXE status code
* @ret s_PXENV_TFTP_GET_FSIZE::FileSize File size
*
* Determine the size of a file on a TFTP server. This uses the
* "tsize" TFTP option, and so will not work with a TFTP server that
* does not support TFTP options, or that does not support the "tsize"
* option.
*
* The PXE specification states that this API call will @b not open a
* TFTP connection for subsequent use with pxenv_tftp_read(). (This
* is somewhat daft, since the only way to obtain the file size via
* the "tsize" option involves issuing a TFTP open request, but that's
* life.)
*
* You cannot call pxenv_tftp_get_fsize() while a TFTP or UDP
* connection is open.
*
* If s_PXENV_TFTP_GET_FSIZE::GatewayIPAddress is 0.0.0.0, normal IP
* routing will take place. See the relevant
* @ref pxe_routing "implementation note" for more details.
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*
* @note There is no way to specify the TFTP server port with this API
* call. Though you can open a file using a non-standard TFTP server
* port (via s_PXENV_TFTP_OPEN::TFTPPort or, potentially,
* s_PXENV_TFTP_READ_FILE::TFTPSrvPort), you can only get the size of
* a file from a TFTP server listening on the standard TFTP port.
* "Consistency" is not a word in Intel's vocabulary.
*/
static PXENV_EXIT_t pxenv_tftp_get_fsize ( struct s_PXENV_TFTP_GET_FSIZE
*tftp_get_fsize ) {
int rc;
DBG ( "PXENV_TFTP_GET_FSIZE" );
/* Open TFTP file */
if ( ( rc = pxe_tftp_open ( tftp_get_fsize->ServerIPAddress, 0,
tftp_get_fsize->FileName, 0 ) ) != 0 ) {
tftp_get_fsize->Status = PXENV_STATUS ( rc );
return PXENV_EXIT_FAILURE;
}
/* Wait for initial seek to arrive, and record size */
while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
( pxe_tftp.max_offset == 0 ) ) {
step();
}
tftp_get_fsize->FileSize = pxe_tftp.max_offset;
DBG ( " fsize=%d", tftp_get_fsize->FileSize );
/* EINPROGRESS is normal; we don't wait for the whole transfer */
if ( rc == -EINPROGRESS )
rc = 0;
/* Close TFTP file */
pxe_tftp_close ( &pxe_tftp, rc );
tftp_get_fsize->Status = PXENV_STATUS ( rc );
return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
}
/** PXE TFTP API */
struct pxe_api_call pxe_tftp_api[] __pxe_api_call = {
PXE_API_CALL ( PXENV_TFTP_OPEN, pxenv_tftp_open,
struct s_PXENV_TFTP_OPEN ),
PXE_API_CALL ( PXENV_TFTP_CLOSE, pxenv_tftp_close,
struct s_PXENV_TFTP_CLOSE ),
PXE_API_CALL ( PXENV_TFTP_READ, pxenv_tftp_read,
struct s_PXENV_TFTP_READ ),
PXE_API_CALL ( PXENV_TFTP_READ_FILE, pxenv_tftp_read_file,
struct s_PXENV_TFTP_READ_FILE ),
PXE_API_CALL ( PXENV_TFTP_GET_FSIZE, pxenv_tftp_get_fsize,
struct s_PXENV_TFTP_GET_FSIZE ),
};

View File

@@ -0,0 +1,474 @@
/** @file
*
* PXE UDP API
*
*/
#include <string.h>
#include <byteswap.h>
#include <ipxe/iobuf.h>
#include <ipxe/xfer.h>
#include <ipxe/udp.h>
#include <ipxe/uaccess.h>
#include <ipxe/process.h>
#include <realmode.h>
#include <pxe.h>
/*
* Copyright (C) 2004 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 );
/** A PXE UDP pseudo-header */
struct pxe_udp_pseudo_header {
/** Source IP address */
IP4_t src_ip;
/** Source port */
UDP_PORT_t s_port;
/** Destination IP address */
IP4_t dest_ip;
/** Destination port */
UDP_PORT_t d_port;
} __attribute__ (( packed ));
/** A PXE UDP connection */
struct pxe_udp_connection {
/** Data transfer interface to UDP stack */
struct interface xfer;
/** Local address */
struct sockaddr_in local;
/** List of received packets */
struct list_head list;
};
/**
* Receive PXE UDP data
*
* @v pxe_udp PXE UDP connection
* @v iobuf I/O buffer
* @v meta Data transfer metadata
* @ret rc Return status code
*
* Receives a packet as part of the current pxenv_udp_read()
* operation.
*/
static int pxe_udp_deliver ( struct pxe_udp_connection *pxe_udp,
struct io_buffer *iobuf,
struct xfer_metadata *meta ) {
struct pxe_udp_pseudo_header *pshdr;
struct sockaddr_in *sin_src;
struct sockaddr_in *sin_dest;
int rc;
/* Extract metadata */
assert ( meta );
sin_src = ( struct sockaddr_in * ) meta->src;
assert ( sin_src );
assert ( sin_src->sin_family == AF_INET );
sin_dest = ( struct sockaddr_in * ) meta->dest;
assert ( sin_dest );
assert ( sin_dest->sin_family == AF_INET );
/* Construct pseudo-header */
if ( ( rc = iob_ensure_headroom ( iobuf, sizeof ( *pshdr ) ) ) != 0 ) {
DBG ( "PXE could not prepend pseudo-header\n" );
rc = -ENOMEM;
goto drop;
}
pshdr = iob_push ( iobuf, sizeof ( *pshdr ) );
pshdr->src_ip = sin_src->sin_addr.s_addr;
pshdr->s_port = sin_src->sin_port;
pshdr->dest_ip = sin_dest->sin_addr.s_addr;
pshdr->d_port = sin_dest->sin_port;
/* Add to queue */
list_add_tail ( &iobuf->list, &pxe_udp->list );
return 0;
drop:
free_iob ( iobuf );
return rc;
}
/** PXE UDP data transfer interface operations */
static struct interface_operation pxe_udp_xfer_operations[] = {
INTF_OP ( xfer_deliver, struct pxe_udp_connection *, pxe_udp_deliver ),
};
/** PXE UDP data transfer interface descriptor */
static struct interface_descriptor pxe_udp_xfer_desc =
INTF_DESC ( struct pxe_udp_connection, xfer, pxe_udp_xfer_operations );
/** The PXE UDP connection */
static struct pxe_udp_connection pxe_udp = {
.xfer = INTF_INIT ( pxe_udp_xfer_desc ),
.local = {
.sin_family = AF_INET,
},
.list = LIST_HEAD_INIT ( pxe_udp.list ),
};
/**
* UDP OPEN
*
* @v pxenv_udp_open Pointer to a struct s_PXENV_UDP_OPEN
* @v s_PXENV_UDP_OPEN::src_ip IP address of this station, or 0.0.0.0
* @ret #PXENV_EXIT_SUCCESS Always
* @ret s_PXENV_UDP_OPEN::Status PXE status code
* @err #PXENV_STATUS_UDP_OPEN UDP connection already open
* @err #PXENV_STATUS_OUT_OF_RESOURCES Could not open connection
*
* Prepares the PXE stack for communication using pxenv_udp_write()
* and pxenv_udp_read().
*
* The IP address supplied in s_PXENV_UDP_OPEN::src_ip will be
* recorded and used as the local station's IP address for all further
* communication, including communication by means other than
* pxenv_udp_write() and pxenv_udp_read(). (If
* s_PXENV_UDP_OPEN::src_ip is 0.0.0.0, the local station's IP address
* will remain unchanged.)
*
* You can only have one open UDP connection at a time. This is not a
* meaningful restriction, since pxenv_udp_write() and
* pxenv_udp_read() allow you to specify arbitrary local and remote
* ports and an arbitrary remote address for each packet. According
* to the PXE specifiation, you cannot have a UDP connection open at
* the same time as a TFTP connection; this restriction does not apply
* to Etherboot.
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*
* @note The PXE specification does not make it clear whether the IP
* address supplied in s_PXENV_UDP_OPEN::src_ip should be used only
* for this UDP connection, or retained for all future communication.
* The latter seems more consistent with typical PXE stack behaviour.
*
* @note Etherboot currently ignores the s_PXENV_UDP_OPEN::src_ip
* parameter.
*
*/
static PXENV_EXIT_t pxenv_udp_open ( struct s_PXENV_UDP_OPEN *pxenv_udp_open ) {
int rc;
DBG ( "PXENV_UDP_OPEN" );
/* Record source IP address */
pxe_udp.local.sin_addr.s_addr = pxenv_udp_open->src_ip;
DBG ( " %s\n", inet_ntoa ( pxe_udp.local.sin_addr ) );
/* Open promiscuous UDP connection */
intf_restart ( &pxe_udp.xfer, 0 );
if ( ( rc = udp_open_promisc ( &pxe_udp.xfer ) ) != 0 ) {
DBG ( "PXENV_UDP_OPEN could not open promiscuous socket: %s\n",
strerror ( rc ) );
pxenv_udp_open->Status = PXENV_STATUS ( rc );
return PXENV_EXIT_FAILURE;
}
pxenv_udp_open->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* UDP CLOSE
*
* @v pxenv_udp_close Pointer to a struct s_PXENV_UDP_CLOSE
* @ret #PXENV_EXIT_SUCCESS Always
* @ret s_PXENV_UDP_CLOSE::Status PXE status code
* @err None -
*
* Closes a UDP connection opened with pxenv_udp_open().
*
* You can only have one open UDP connection at a time. You cannot
* have a UDP connection open at the same time as a TFTP connection.
* You cannot use pxenv_udp_close() to close a TFTP connection; use
* pxenv_tftp_close() instead.
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*
*/
static PXENV_EXIT_t
pxenv_udp_close ( struct s_PXENV_UDP_CLOSE *pxenv_udp_close ) {
struct io_buffer *iobuf;
struct io_buffer *tmp;
DBG ( "PXENV_UDP_CLOSE\n" );
/* Close UDP connection */
intf_restart ( &pxe_udp.xfer, 0 );
/* Discard any received packets */
list_for_each_entry_safe ( iobuf, tmp, &pxe_udp.list, list ) {
list_del ( &iobuf->list );
free_iob ( iobuf );
}
pxenv_udp_close->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* UDP WRITE
*
* @v pxenv_udp_write Pointer to a struct s_PXENV_UDP_WRITE
* @v s_PXENV_UDP_WRITE::ip Destination IP address
* @v s_PXENV_UDP_WRITE::gw Relay agent IP address, or 0.0.0.0
* @v s_PXENV_UDP_WRITE::src_port Source UDP port, or 0
* @v s_PXENV_UDP_WRITE::dst_port Destination UDP port
* @v s_PXENV_UDP_WRITE::buffer_size Length of the UDP payload
* @v s_PXENV_UDP_WRITE::buffer Address of the UDP payload
* @ret #PXENV_EXIT_SUCCESS Packet was transmitted successfully
* @ret #PXENV_EXIT_FAILURE Packet could not be transmitted
* @ret s_PXENV_UDP_WRITE::Status PXE status code
* @err #PXENV_STATUS_UDP_CLOSED UDP connection is not open
* @err #PXENV_STATUS_UNDI_TRANSMIT_ERROR Could not transmit packet
*
* Transmits a single UDP packet. A valid IP and UDP header will be
* prepended to the payload in s_PXENV_UDP_WRITE::buffer; the buffer
* should not contain precomputed IP and UDP headers, nor should it
* contain space allocated for these headers. The first byte of the
* buffer will be transmitted as the first byte following the UDP
* header.
*
* If s_PXENV_UDP_WRITE::gw is 0.0.0.0, normal IP routing will take
* place. See the relevant @ref pxe_routing "implementation note" for
* more details.
*
* If s_PXENV_UDP_WRITE::src_port is 0, port 2069 will be used.
*
* You must have opened a UDP connection with pxenv_udp_open() before
* calling pxenv_udp_write().
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*
* @note Etherboot currently ignores the s_PXENV_UDP_WRITE::gw
* parameter.
*
*/
static PXENV_EXIT_t
pxenv_udp_write ( struct s_PXENV_UDP_WRITE *pxenv_udp_write ) {
struct sockaddr_in dest;
struct xfer_metadata meta = {
.src = ( struct sockaddr * ) &pxe_udp.local,
.dest = ( struct sockaddr * ) &dest,
.netdev = pxe_netdev,
};
size_t len;
struct io_buffer *iobuf;
userptr_t buffer;
int rc;
DBG ( "PXENV_UDP_WRITE" );
/* Construct destination socket address */
memset ( &dest, 0, sizeof ( dest ) );
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = pxenv_udp_write->ip;
dest.sin_port = pxenv_udp_write->dst_port;
/* Set local (source) port. PXE spec says source port is 2069
* if not specified. Really, this ought to be set at UDP open
* time but hey, we didn't design this API.
*/
pxe_udp.local.sin_port = pxenv_udp_write->src_port;
if ( ! pxe_udp.local.sin_port )
pxe_udp.local.sin_port = htons ( 2069 );
/* FIXME: we ignore the gateway specified, since we're
* confident of being able to do our own routing. We should
* probably allow for multiple gateways.
*/
/* Allocate and fill data buffer */
len = pxenv_udp_write->buffer_size;
iobuf = xfer_alloc_iob ( &pxe_udp.xfer, len );
if ( ! iobuf ) {
DBG ( " out of memory\n" );
pxenv_udp_write->Status = PXENV_STATUS_OUT_OF_RESOURCES;
return PXENV_EXIT_FAILURE;
}
buffer = real_to_user ( pxenv_udp_write->buffer.segment,
pxenv_udp_write->buffer.offset );
copy_from_user ( iob_put ( iobuf, len ), buffer, 0, len );
DBG ( " %04x:%04x+%x %d->%s:%d\n", pxenv_udp_write->buffer.segment,
pxenv_udp_write->buffer.offset, pxenv_udp_write->buffer_size,
ntohs ( pxenv_udp_write->src_port ),
inet_ntoa ( dest.sin_addr ),
ntohs ( pxenv_udp_write->dst_port ) );
/* Transmit packet */
if ( ( rc = xfer_deliver ( &pxe_udp.xfer, iobuf, &meta ) ) != 0 ) {
DBG ( "PXENV_UDP_WRITE could not transmit: %s\n",
strerror ( rc ) );
pxenv_udp_write->Status = PXENV_STATUS ( rc );
return PXENV_EXIT_FAILURE;
}
pxenv_udp_write->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
}
/**
* UDP READ
*
* @v pxenv_udp_read Pointer to a struct s_PXENV_UDP_READ
* @v s_PXENV_UDP_READ::dest_ip Destination IP address, or 0.0.0.0
* @v s_PXENV_UDP_READ::d_port Destination UDP port, or 0
* @v s_PXENV_UDP_READ::buffer_size Size of the UDP payload buffer
* @v s_PXENV_UDP_READ::buffer Address of the UDP payload buffer
* @ret #PXENV_EXIT_SUCCESS A packet has been received
* @ret #PXENV_EXIT_FAILURE No packet has been received
* @ret s_PXENV_UDP_READ::Status PXE status code
* @ret s_PXENV_UDP_READ::src_ip Source IP address
* @ret s_PXENV_UDP_READ::dest_ip Destination IP address
* @ret s_PXENV_UDP_READ::s_port Source UDP port
* @ret s_PXENV_UDP_READ::d_port Destination UDP port
* @ret s_PXENV_UDP_READ::buffer_size Length of UDP payload
* @err #PXENV_STATUS_UDP_CLOSED UDP connection is not open
* @err #PXENV_STATUS_FAILURE No packet was ready to read
*
* Receive a single UDP packet. This is a non-blocking call; if no
* packet is ready to read, the call will return instantly with
* s_PXENV_UDP_READ::Status==PXENV_STATUS_FAILURE.
*
* If s_PXENV_UDP_READ::dest_ip is 0.0.0.0, UDP packets addressed to
* any IP address will be accepted and may be returned to the caller.
*
* If s_PXENV_UDP_READ::d_port is 0, UDP packets addressed to any UDP
* port will be accepted and may be returned to the caller.
*
* You must have opened a UDP connection with pxenv_udp_open() before
* calling pxenv_udp_read().
*
* On x86, you must set the s_PXE::StatusCallout field to a nonzero
* value before calling this function in protected mode. You cannot
* call this function with a 32-bit stack segment. (See the relevant
* @ref pxe_x86_pmode16 "implementation note" for more details.)
*
* @note The PXE specification (version 2.1) does not state that we
* should fill in s_PXENV_UDP_READ::dest_ip and
* s_PXENV_UDP_READ::d_port, but Microsoft Windows' NTLDR program
* expects us to do so, and will fail if we don't.
*
*/
static PXENV_EXIT_t pxenv_udp_read ( struct s_PXENV_UDP_READ *pxenv_udp_read ) {
struct in_addr dest_ip_wanted = { .s_addr = pxenv_udp_read->dest_ip };
struct in_addr dest_ip;
struct io_buffer *iobuf;
struct pxe_udp_pseudo_header *pshdr;
uint16_t d_port_wanted = pxenv_udp_read->d_port;
uint16_t d_port;
userptr_t buffer;
size_t len;
/* Try receiving a packet, if the queue is empty */
if ( list_empty ( &pxe_udp.list ) )
step();
/* Remove first packet from the queue */
iobuf = list_first_entry ( &pxe_udp.list, struct io_buffer, list );
if ( ! iobuf ) {
/* No packet received */
DBG2 ( "PXENV_UDP_READ\n" );
goto no_packet;
}
list_del ( &iobuf->list );
/* Strip pseudo-header */
assert ( iob_len ( iobuf ) >= sizeof ( *pshdr ) );
pshdr = iobuf->data;
iob_pull ( iobuf, sizeof ( *pshdr ) );
dest_ip.s_addr = pshdr->dest_ip;
d_port = pshdr->d_port;
DBG ( "PXENV_UDP_READ" );
/* Filter on destination address and/or port */
if ( dest_ip_wanted.s_addr &&
( dest_ip_wanted.s_addr != dest_ip.s_addr ) ) {
DBG ( " wrong IP %s", inet_ntoa ( dest_ip ) );
DBG ( " (wanted %s)\n", inet_ntoa ( dest_ip_wanted ) );
goto drop;
}
if ( d_port_wanted && ( d_port_wanted != d_port ) ) {
DBG ( " wrong port %d", htons ( d_port ) );
DBG ( " (wanted %d)\n", htons ( d_port_wanted ) );
goto drop;
}
/* Copy packet to buffer and record length */
buffer = real_to_user ( pxenv_udp_read->buffer.segment,
pxenv_udp_read->buffer.offset );
len = iob_len ( iobuf );
if ( len > pxenv_udp_read->buffer_size )
len = pxenv_udp_read->buffer_size;
copy_to_user ( buffer, 0, iobuf->data, len );
pxenv_udp_read->buffer_size = len;
/* Fill in source/dest information */
pxenv_udp_read->src_ip = pshdr->src_ip;
pxenv_udp_read->s_port = pshdr->s_port;
pxenv_udp_read->dest_ip = pshdr->dest_ip;
pxenv_udp_read->d_port = pshdr->d_port;
DBG ( " %04x:%04x+%x %s:", pxenv_udp_read->buffer.segment,
pxenv_udp_read->buffer.offset, pxenv_udp_read->buffer_size,
inet_ntoa ( *( ( struct in_addr * ) &pxenv_udp_read->src_ip ) ));
DBG ( "%d<-%s:%d\n", ntohs ( pxenv_udp_read->s_port ),
inet_ntoa ( *( ( struct in_addr * ) &pxenv_udp_read->dest_ip ) ),
ntohs ( pxenv_udp_read->d_port ) );
/* Free I/O buffer */
free_iob ( iobuf );
pxenv_udp_read->Status = PXENV_STATUS_SUCCESS;
return PXENV_EXIT_SUCCESS;
drop:
free_iob ( iobuf );
no_packet:
pxenv_udp_read->Status = PXENV_STATUS_FAILURE;
return PXENV_EXIT_FAILURE;
}
/** PXE UDP API */
struct pxe_api_call pxe_udp_api[] __pxe_api_call = {
PXE_API_CALL ( PXENV_UDP_OPEN, pxenv_udp_open,
struct s_PXENV_UDP_OPEN ),
PXE_API_CALL ( PXENV_UDP_CLOSE, pxenv_udp_close,
struct s_PXENV_UDP_CLOSE ),
PXE_API_CALL ( PXENV_UDP_WRITE, pxenv_udp_write,
struct s_PXENV_UDP_WRITE ),
PXE_API_CALL ( PXENV_UDP_READ, pxenv_udp_read,
struct s_PXENV_UDP_READ ),
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
/*
* Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/dhcp.h>
#include <ipxe/profile.h>
#include <pxeparent.h>
#include <pxe_api.h>
#include <pxe_types.h>
#include <pxe.h>
/** @file
*
* Call interface to parent PXE stack
*
*/
/* Disambiguate the various error causes */
#define EINFO_EPXECALL \
__einfo_uniqify ( EINFO_EPLATFORM, 0x01, \
"External PXE API error" )
#define EPXECALL( status ) EPLATFORM ( EINFO_EPXECALL, status )
/** A parent PXE API call profiler */
struct pxeparent_profiler {
/** Total time spent performing REAL_CALL() */
struct profiler total;
/** Time spent transitioning to real mode */
struct profiler p2r;
/** Time spent in external code */
struct profiler ext;
/** Time spent transitioning back to protected mode */
struct profiler r2p;
};
/** PXENV_UNDI_TRANSMIT profiler */
static struct pxeparent_profiler pxeparent_tx_profiler __profiler = {
{ .name = "pxeparent.tx" },
{ .name = "pxeparent.tx_p2r" },
{ .name = "pxeparent.tx_ext" },
{ .name = "pxeparent.tx_r2p" },
};
/** PXENV_UNDI_ISR profiler
*
* Note that this profiler will not see calls to
* PXENV_UNDI_ISR_IN_START, which are handled by the UNDI ISR and do
* not go via pxeparent_call().
*/
static struct pxeparent_profiler pxeparent_isr_profiler __profiler = {
{ .name = "pxeparent.isr" },
{ .name = "pxeparent.isr_p2r" },
{ .name = "pxeparent.isr_ext" },
{ .name = "pxeparent.isr_r2p" },
};
/** PXE unknown API call profiler
*
* This profiler can be used to measure the overhead of a dummy PXE
* API call.
*/
static struct pxeparent_profiler pxeparent_unknown_profiler __profiler = {
{ .name = "pxeparent.unknown" },
{ .name = "pxeparent.unknown_p2r" },
{ .name = "pxeparent.unknown_ext" },
{ .name = "pxeparent.unknown_r2p" },
};
/** Miscellaneous PXE API call profiler */
static struct pxeparent_profiler pxeparent_misc_profiler __profiler = {
{ .name = "pxeparent.misc" },
{ .name = "pxeparent.misc_p2r" },
{ .name = "pxeparent.misc_ext" },
{ .name = "pxeparent.misc_r2p" },
};
/**
* Name PXE API call
*
* @v function API call number
* @ret name API call name
*/
static inline __attribute__ (( always_inline )) const char *
pxeparent_function_name ( unsigned int function ) {
switch ( function ) {
case PXENV_START_UNDI:
return "PXENV_START_UNDI";
case PXENV_STOP_UNDI:
return "PXENV_STOP_UNDI";
case PXENV_UNDI_STARTUP:
return "PXENV_UNDI_STARTUP";
case PXENV_UNDI_CLEANUP:
return "PXENV_UNDI_CLEANUP";
case PXENV_UNDI_INITIALIZE:
return "PXENV_UNDI_INITIALIZE";
case PXENV_UNDI_RESET_ADAPTER:
return "PXENV_UNDI_RESET_ADAPTER";
case PXENV_UNDI_SHUTDOWN:
return "PXENV_UNDI_SHUTDOWN";
case PXENV_UNDI_OPEN:
return "PXENV_UNDI_OPEN";
case PXENV_UNDI_CLOSE:
return "PXENV_UNDI_CLOSE";
case PXENV_UNDI_TRANSMIT:
return "PXENV_UNDI_TRANSMIT";
case PXENV_UNDI_SET_MCAST_ADDRESS:
return "PXENV_UNDI_SET_MCAST_ADDRESS";
case PXENV_UNDI_SET_STATION_ADDRESS:
return "PXENV_UNDI_SET_STATION_ADDRESS";
case PXENV_UNDI_SET_PACKET_FILTER:
return "PXENV_UNDI_SET_PACKET_FILTER";
case PXENV_UNDI_GET_INFORMATION:
return "PXENV_UNDI_GET_INFORMATION";
case PXENV_UNDI_GET_STATISTICS:
return "PXENV_UNDI_GET_STATISTICS";
case PXENV_UNDI_CLEAR_STATISTICS:
return "PXENV_UNDI_CLEAR_STATISTICS";
case PXENV_UNDI_INITIATE_DIAGS:
return "PXENV_UNDI_INITIATE_DIAGS";
case PXENV_UNDI_FORCE_INTERRUPT:
return "PXENV_UNDI_FORCE_INTERRUPT";
case PXENV_UNDI_GET_MCAST_ADDRESS:
return "PXENV_UNDI_GET_MCAST_ADDRESS";
case PXENV_UNDI_GET_NIC_TYPE:
return "PXENV_UNDI_GET_NIC_TYPE";
case PXENV_UNDI_GET_IFACE_INFO:
return "PXENV_UNDI_GET_IFACE_INFO";
/*
* Duplicate case value; this is a bug in the PXE specification.
*
* case PXENV_UNDI_GET_STATE:
* return "PXENV_UNDI_GET_STATE";
*/
case PXENV_UNDI_ISR:
return "PXENV_UNDI_ISR";
case PXENV_GET_CACHED_INFO:
return "PXENV_GET_CACHED_INFO";
default:
return "UNKNOWN API CALL";
}
}
/**
* Determine applicable profiler pair (for debugging)
*
* @v function API call number
* @ret profiler Profiler
*/
static struct pxeparent_profiler * pxeparent_profiler ( unsigned int function ){
/* Determine applicable profiler */
switch ( function ) {
case PXENV_UNDI_TRANSMIT:
return &pxeparent_tx_profiler;
case PXENV_UNDI_ISR:
return &pxeparent_isr_profiler;
case PXENV_UNKNOWN:
return &pxeparent_unknown_profiler;
default:
return &pxeparent_misc_profiler;
}
}
/**
* PXE parent parameter block
*
* Used as the parameter block for all parent PXE API calls. Resides
* in base memory.
*/
static union u_PXENV_ANY __bss16 ( pxeparent_params );
#define pxeparent_params __use_data16 ( pxeparent_params )
/** PXE parent entry point
*
* Used as the indirection vector for all parent PXE API calls. Resides in
* base memory.
*/
SEGOFF16_t __bss16 ( pxeparent_entry_point );
#define pxeparent_entry_point __use_data16 ( pxeparent_entry_point )
/**
* Issue parent PXE API call
*
* @v entry Parent PXE stack entry point
* @v function API call number
* @v params PXE parameter block
* @v params_len Length of PXE parameter block
* @ret rc Return status code
*/
int pxeparent_call ( SEGOFF16_t entry, unsigned int function,
void *params, size_t params_len ) {
struct pxeparent_profiler *profiler = pxeparent_profiler ( function );
PXENV_EXIT_t exit;
unsigned long started;
unsigned long stopped;
int discard_D;
int rc;
/* Copy parameter block and entry point */
assert ( params_len <= sizeof ( pxeparent_params ) );
memcpy ( &pxeparent_params, params, params_len );
memcpy ( &pxeparent_entry_point, &entry, sizeof ( entry ) );
/* Call real-mode entry point. This calling convention will
* work with both the !PXE and the PXENV+ entry points.
*/
profile_start ( &profiler->total );
__asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" /* gcc bug */
"rdtsc\n\t"
"pushl %%eax\n\t"
"pushw %%es\n\t"
"pushw %%di\n\t"
"pushw %%bx\n\t"
"lcall *pxeparent_entry_point\n\t"
"movw %%ax, %%bx\n\t"
"rdtsc\n\t"
"addw $6, %%sp\n\t"
"popl %%edx\n\t"
"popl %%ebp\n\t" /* gcc bug */ )
: "=a" ( stopped ), "=d" ( started ),
"=b" ( exit ), "=D" ( discard_D )
: "b" ( function ),
"D" ( __from_data16 ( &pxeparent_params ) )
: "ecx", "esi" );
profile_stop ( &profiler->total );
profile_start_at ( &profiler->p2r, profile_started ( &profiler->total));
profile_stop_at ( &profiler->p2r, started );
profile_start_at ( &profiler->ext, started );
profile_stop_at ( &profiler->ext, stopped );
profile_start_at ( &profiler->r2p, stopped );
profile_stop_at ( &profiler->r2p, profile_stopped ( &profiler->total ));
/* Determine return status code based on PXENV_EXIT and
* PXENV_STATUS
*/
rc = ( ( exit == PXENV_EXIT_SUCCESS ) ?
0 : -EPXECALL ( pxeparent_params.Status ) );
/* If anything goes wrong, print as much debug information as
* it's possible to give.
*/
if ( rc != 0 ) {
SEGOFF16_t rm_params = {
.segment = rm_ds,
.offset = __from_data16 ( &pxeparent_params ),
};
DBG ( "PXEPARENT %s failed: %s\n",
pxeparent_function_name ( function ), strerror ( rc ) );
DBG ( "PXEPARENT parameters at %04x:%04x length "
"%#02zx, entry point at %04x:%04x\n",
rm_params.segment, rm_params.offset, params_len,
pxeparent_entry_point.segment,
pxeparent_entry_point.offset );
DBG ( "PXEPARENT parameters provided:\n" );
DBG_HDA ( rm_params, params, params_len );
DBG ( "PXEPARENT parameters returned:\n" );
DBG_HDA ( rm_params, &pxeparent_params, params_len );
}
/* Copy parameter block back */
memcpy ( params, &pxeparent_params, params_len );
return rc;
}