mirror of
https://github.com/ipxe/ipxe
synced 2025-12-25 17:12:40 +03:00
Initial revision
This commit is contained in:
317
src/arch/i386/firmware/pcbios/basemem.c
Normal file
317
src/arch/i386/firmware/pcbios/basemem.c
Normal file
@@ -0,0 +1,317 @@
|
||||
#ifdef PCBIOS
|
||||
|
||||
#include "etherboot.h"
|
||||
#include "realmode.h" /* for real_mode_stack */
|
||||
|
||||
/* Routines to allocate base memory in a BIOS-compatible way, by
|
||||
* updating the Free Base Memory Size counter at 40:13h.
|
||||
*
|
||||
* Michael Brown <mbrown@fensystems.co.uk> (mcb30)
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#define fbms ( ( uint16_t * ) phys_to_virt ( 0x413 ) )
|
||||
#define BASE_MEMORY_MAX ( 640 )
|
||||
#define FREE_BLOCK_MAGIC ( ('!'<<0) + ('F'<<8) + ('R'<<16) + ('E'<<24) )
|
||||
#define FREE_BASE_MEMORY ( (uint32_t) ( *fbms << 10 ) )
|
||||
|
||||
/* Prototypes */
|
||||
void * _allot_base_memory ( size_t size );
|
||||
void _forget_base_memory ( void *ptr, size_t size );
|
||||
|
||||
typedef struct free_base_memory_block {
|
||||
uint32_t magic;
|
||||
uint16_t size_kb;
|
||||
} free_base_memory_block_t;
|
||||
|
||||
/* Return amount of free base memory in bytes
|
||||
*/
|
||||
|
||||
uint32_t get_free_base_memory ( void ) {
|
||||
return FREE_BASE_MEMORY;
|
||||
}
|
||||
|
||||
/* Start of our image in base memory.
|
||||
*/
|
||||
#define __text16_nocompress __attribute__ ((section (".text16.nocompress")))
|
||||
uint32_t image_basemem __text16_nocompress = 0;
|
||||
uint32_t image_basemem_size __text16_nocompress = 0;
|
||||
|
||||
/* Allot/free the real-mode stack
|
||||
*/
|
||||
|
||||
void allot_real_mode_stack ( void )
|
||||
{
|
||||
void *new_real_mode_stack;
|
||||
|
||||
if ( lock_real_mode_stack )
|
||||
return;
|
||||
|
||||
/* This is evil hack.
|
||||
* Until we have a real_mode stack use 0x7c00.
|
||||
* Except for 0 - 0x600 membory below 0x7c00 is hardly every used.
|
||||
* This stack should never be used unless the stack allocation fails,
|
||||
* or if someone has placed a print statement in a dangerous location.
|
||||
*/
|
||||
if (!real_mode_stack) {
|
||||
real_mode_stack = 0x7c00;
|
||||
}
|
||||
new_real_mode_stack = _allot_base_memory ( real_mode_stack_size );
|
||||
if ( ! new_real_mode_stack ) {
|
||||
printf ( "FATAL: No real-mode stack\n" );
|
||||
while ( 1 ) {};
|
||||
}
|
||||
real_mode_stack = virt_to_phys ( new_real_mode_stack );
|
||||
get_memsizes();
|
||||
}
|
||||
|
||||
void forget_real_mode_stack ( void )
|
||||
{
|
||||
if ( lock_real_mode_stack )
|
||||
return;
|
||||
|
||||
if ( real_mode_stack) {
|
||||
_forget_base_memory ( phys_to_virt(real_mode_stack),
|
||||
real_mode_stack_size );
|
||||
/* get_memsizes() uses the real_mode stack we just freed
|
||||
* for it's BIOS calls.
|
||||
*/
|
||||
get_memsizes();
|
||||
real_mode_stack = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate N bytes of base memory. Amount allocated will be rounded
|
||||
* up to the nearest kB, since that's the granularity of the BIOS FBMS
|
||||
* counter. Returns NULL if memory cannot be allocated.
|
||||
*/
|
||||
|
||||
static void * _allot_base_memory ( size_t size )
|
||||
{
|
||||
uint16_t size_kb = ( size + 1023 ) >> 10;
|
||||
void *ptr = NULL;
|
||||
|
||||
#ifdef DEBUG_BASEMEM
|
||||
printf ( "Trying to allocate %d kB of base memory from %d kB free\n",
|
||||
size_kb, *fbms );
|
||||
#endif
|
||||
|
||||
/* Free up any unused memory before we start */
|
||||
free_unused_base_memory();
|
||||
|
||||
/* Check available base memory */
|
||||
if ( size_kb > *fbms ) { return NULL; }
|
||||
|
||||
/* Reduce available base memory */
|
||||
*fbms -= size_kb;
|
||||
|
||||
/* Calculate address of memory allocated */
|
||||
ptr = phys_to_virt ( FREE_BASE_MEMORY );
|
||||
|
||||
/* Zero out memory. We do this so that allocation of
|
||||
* already-used space will show up in the form of a crash as
|
||||
* soon as possible.
|
||||
*
|
||||
* Update: there's another reason for doing this. If we don't
|
||||
* zero the contents, then they could still retain our "free
|
||||
* block" markers and be liable to being freed whenever a
|
||||
* base-memory allocation routine is next called.
|
||||
*/
|
||||
memset ( ptr, 0, size_kb << 10 );
|
||||
|
||||
#ifdef DEBUG_BASEMEM
|
||||
printf ( "Allocated %d kB at [%x,%x)\n", size_kb,
|
||||
virt_to_phys ( ptr ),
|
||||
virt_to_phys ( ptr ) + size_kb * 1024 );
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void * allot_base_memory ( size_t size )
|
||||
{
|
||||
void *ptr;
|
||||
|
||||
/* Free real-mode stack, allocate memory, reallocate real-mode
|
||||
* stack.
|
||||
*/
|
||||
forget_real_mode_stack();
|
||||
ptr = _allot_base_memory ( size );
|
||||
get_memsizes();
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* Free base memory allocated by allot_base_memory. The BIOS provides
|
||||
* nothing better than a LIFO mechanism for freeing memory (i.e. it
|
||||
* just has the single "total free memory" counter), but we improve
|
||||
* upon this slightly; as long as you free all the allotted blocks, it
|
||||
* doesn't matter what order you free them in. (This will only work
|
||||
* for blocks that are freed via forget_base_memory()).
|
||||
*
|
||||
* Yes, it's annoying that you have to remember the size of the blocks
|
||||
* you've allotted. However, since our granularity of allocation is
|
||||
* 1K, the alternative is to risk wasting the occasional kB of base
|
||||
* memory, which is a Bad Thing. Really, you should be using as
|
||||
* little base memory as possible, so consider the awkwardness of the
|
||||
* API to be a feature! :-)
|
||||
*/
|
||||
|
||||
static void _forget_base_memory ( void *ptr, size_t size )
|
||||
{
|
||||
uint16_t remainder = virt_to_phys(ptr) & 1023;
|
||||
uint16_t size_kb = ( size + remainder + 1023 ) >> 10;
|
||||
free_base_memory_block_t *free_block =
|
||||
( free_base_memory_block_t * ) ( ptr - remainder );
|
||||
|
||||
if ( ( ptr == NULL ) || ( size == 0 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_BASEMEM
|
||||
printf ( "Trying to free %d bytes base memory at 0x%x\n",
|
||||
size, virt_to_phys ( ptr ) );
|
||||
if ( remainder > 0 ) {
|
||||
printf ( "WARNING: destructively expanding free block "
|
||||
"downwards to 0x%x\n",
|
||||
virt_to_phys ( ptr - remainder ) );
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Mark every kilobyte within this block as free. This is
|
||||
* overkill for normal purposes, but helps when something has
|
||||
* allocated base memory with a granularity finer than the
|
||||
* BIOS granularity of 1kB. PXE ROMs tend to do this when
|
||||
* they allocate their own memory. This method allows us to
|
||||
* free their blocks (admittedly in a rather dangerous,
|
||||
* tread-on-anything-either-side sort of way, but there's no
|
||||
* other way to do it).
|
||||
*
|
||||
* Since we're marking every kB as free, there's actually no
|
||||
* need for recording the size of the blocks. However, we
|
||||
* keep this in so that debug messages are friendlier. It
|
||||
* probably adds around 8 bytes to the overall code size.
|
||||
*/
|
||||
while ( size_kb > 0 ) {
|
||||
/* Mark this block as unused */
|
||||
free_block->magic = FREE_BLOCK_MAGIC;
|
||||
free_block->size_kb = size_kb;
|
||||
/* Move up by 1 kB */
|
||||
free_block = (void *)(((char *)free_block) + (1 << 10));
|
||||
size_kb--;
|
||||
}
|
||||
|
||||
/* Free up unused base memory */
|
||||
free_unused_base_memory();
|
||||
}
|
||||
|
||||
void forget_base_memory ( void *ptr, size_t size )
|
||||
{
|
||||
/* Free memory, free real-mode stack, re-allocate real-mode
|
||||
* stack. Do this so that we don't end up wasting a huge
|
||||
* block of memory trapped behind the real-mode stack.
|
||||
*/
|
||||
_forget_base_memory ( ptr, size );
|
||||
forget_real_mode_stack();
|
||||
get_memsizes();
|
||||
}
|
||||
|
||||
/* Do the actual freeing of memory. This is split out from
|
||||
* forget_base_memory() so that it may be called separately. It
|
||||
* should be called whenever base memory is deallocated by an external
|
||||
* entity (if we can detect that it has done so) so that we get the
|
||||
* chance to free up our own blocks.
|
||||
*/
|
||||
static void free_unused_base_memory ( void ) {
|
||||
free_base_memory_block_t *free_block = NULL;
|
||||
|
||||
/* Try to release memory back to the BIOS. Free all
|
||||
* consecutive blocks marked as free.
|
||||
*/
|
||||
while ( 1 ) {
|
||||
/* Calculate address of next potential free block */
|
||||
free_block = ( free_base_memory_block_t * )
|
||||
phys_to_virt ( FREE_BASE_MEMORY );
|
||||
|
||||
/* Stop processing if we're all the way up to 640K or
|
||||
* if this is not a free block
|
||||
*/
|
||||
if ( ( *fbms == BASE_MEMORY_MAX ) ||
|
||||
( free_block->magic != FREE_BLOCK_MAGIC ) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Return memory to BIOS */
|
||||
*fbms += free_block->size_kb;
|
||||
|
||||
#ifdef DEBUG_BASEMEM
|
||||
printf ( "Freed %d kB base memory, %d kB now free\n",
|
||||
free_block->size_kb, *fbms );
|
||||
#endif
|
||||
|
||||
/* Zero out freed block. We do this in case
|
||||
* the block contained any structures that
|
||||
* might be located by scanning through
|
||||
* memory.
|
||||
*/
|
||||
memset ( free_block, 0, free_block->size_kb << 10 );
|
||||
}
|
||||
}
|
||||
|
||||
/* Free base memory used by the prefix. Called once at start of
|
||||
* Etherboot by arch_main().
|
||||
*/
|
||||
void forget_prefix_base_memory ( void )
|
||||
{
|
||||
/* runtime_start_kb is _text rounded down to a physical kB boundary */
|
||||
uint32_t runtime_start_kb = virt_to_phys(_text) & ~0x3ff;
|
||||
/* prefix_size_kb is the prefix size excluding any portion
|
||||
* that overlaps into the first kB used by the runtime image
|
||||
*/
|
||||
uint32_t prefix_size_kb = runtime_start_kb - image_basemem;
|
||||
|
||||
#ifdef DEBUG_BASEMEM
|
||||
printf ( "Attempting to free base memory used by prefix\n" );
|
||||
#endif
|
||||
|
||||
/* If the decompressor is in allocated base memory
|
||||
* *and* the Etherboot text is in base
|
||||
* memory, then free the decompressor.
|
||||
*/
|
||||
if ( ( image_basemem >= FREE_BASE_MEMORY ) &&
|
||||
( runtime_start_kb >= FREE_BASE_MEMORY ) &&
|
||||
( runtime_start_kb <= ( BASE_MEMORY_MAX << 10 ) ) )
|
||||
{
|
||||
forget_base_memory ( phys_to_virt ( image_basemem ),
|
||||
prefix_size_kb );
|
||||
/* Update image_basemem and image_basemem_size to
|
||||
* indicate that our allocation now starts with _text
|
||||
*/
|
||||
image_basemem = runtime_start_kb;
|
||||
image_basemem_size -= prefix_size_kb;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free base memory used by the runtime image. Called after
|
||||
* relocation by arch_relocated_from().
|
||||
*/
|
||||
void forget_runtime_base_memory ( unsigned long old_addr )
|
||||
{
|
||||
/* text_start_kb is old _text rounded down to a physical KB boundary */
|
||||
uint32_t old_text_start_kb = old_addr & ~0x3ff;
|
||||
|
||||
#ifdef DEBUG_BASEMEM
|
||||
printf ( "Attempting to free base memory used by runtime image\n" );
|
||||
#endif
|
||||
|
||||
if ( ( image_basemem >= FREE_BASE_MEMORY ) &&
|
||||
( image_basemem == old_text_start_kb ) )
|
||||
{
|
||||
forget_base_memory ( phys_to_virt ( image_basemem ),
|
||||
image_basemem_size );
|
||||
/* Update image_basemem to show no longer in use */
|
||||
image_basemem = 0;
|
||||
image_basemem_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PCBIOS */
|
||||
155
src/arch/i386/firmware/pcbios/bios.c
Normal file
155
src/arch/i386/firmware/pcbios/bios.c
Normal file
@@ -0,0 +1,155 @@
|
||||
/* Etherboot routines for PCBIOS firmware.
|
||||
*
|
||||
* Body of routines taken from old pcbios.S
|
||||
*/
|
||||
|
||||
#ifdef PCBIOS
|
||||
|
||||
#include "etherboot.h"
|
||||
#include "realmode.h"
|
||||
#include "segoff.h"
|
||||
|
||||
#define CF ( 1 << 0 )
|
||||
|
||||
/**************************************************************************
|
||||
CURRTICKS - Get Time
|
||||
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.
|
||||
**************************************************************************/
|
||||
#if defined(CONFIG_TSC_CURRTICKS)
|
||||
#undef CONFIG_BIOS_CURRTICKS
|
||||
#else
|
||||
#define CONFIG_BIOS_CURRTICKS 1
|
||||
#endif
|
||||
#if defined(CONFIG_BIOS_CURRTICKS)
|
||||
unsigned long currticks (void)
|
||||
{
|
||||
static uint32_t days = 0;
|
||||
uint32_t *ticks = VIRTUAL(0x0040,0x006c);
|
||||
uint8_t *midnight = VIRTUAL(0x0040,0x0070);
|
||||
|
||||
/* Re-enable interrupts so that the timer interrupt can occur
|
||||
*/
|
||||
RM_FRAGMENT(rm_currticks,
|
||||
"sti\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
real_call ( rm_currticks, NULL, NULL );
|
||||
|
||||
if ( *midnight ) {
|
||||
*midnight = 0;
|
||||
days += 0x1800b0;
|
||||
}
|
||||
return ( days + *ticks );
|
||||
}
|
||||
#endif /* CONFIG_BIOS_CURRTICKS */
|
||||
|
||||
/**************************************************************************
|
||||
INT15 - Call Interrupt 0x15
|
||||
**************************************************************************/
|
||||
int int15 ( int ax )
|
||||
{
|
||||
struct {
|
||||
reg16_t ax;
|
||||
} PACKED in_stack;
|
||||
struct {
|
||||
reg16_t flags;
|
||||
} PACKED out_stack;
|
||||
reg16_t ret_ax;
|
||||
|
||||
RM_FRAGMENT(rm_int15,
|
||||
"sti\n\t"
|
||||
"popw %ax\n\t"
|
||||
"stc\n\t"
|
||||
"int $0x15\n\t"
|
||||
"pushf\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
in_stack.ax.word = ax;
|
||||
ret_ax.word = real_call ( rm_int15, &in_stack, &out_stack );
|
||||
|
||||
/* Carry flag clear indicates function not supported */
|
||||
if ( ! ( out_stack.flags.word & CF ) ) return 0;
|
||||
return ret_ax.h;
|
||||
}
|
||||
|
||||
#ifdef POWERSAVE
|
||||
/**************************************************************************
|
||||
CPU_NAP - Save power by halting the CPU until the next interrupt
|
||||
**************************************************************************/
|
||||
void cpu_nap ( void )
|
||||
{
|
||||
RM_FRAGMENT(rm_cpu_nap,
|
||||
"sti\n\t"
|
||||
"hlt\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
real_call ( rm_cpu_nap, NULL, NULL );
|
||||
}
|
||||
#endif /* POWERSAVE */
|
||||
|
||||
#if (TRY_FLOPPY_FIRST > 0)
|
||||
/**************************************************************************
|
||||
DISK_INIT - Initialize the disk system
|
||||
**************************************************************************/
|
||||
void disk_init ( void )
|
||||
{
|
||||
RM_FRAGMENT(rm_disk_init,
|
||||
"sti\n\t"
|
||||
"xorw %ax,%ax\n\t"
|
||||
"movb $0x80,%dl\n\t"
|
||||
"int $0x13\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
real_call ( rm_disk_init, NULL, NULL );
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
DISK_READ - Read a sector from disk
|
||||
**************************************************************************/
|
||||
unsigned int pcbios_disk_read ( int drive, int cylinder, int head, int sector,
|
||||
char *buf ) {
|
||||
struct {
|
||||
reg16_t ax;
|
||||
reg16_t cx;
|
||||
reg16_t dx;
|
||||
segoff_t buffer;
|
||||
} PACKED in_stack;
|
||||
struct {
|
||||
reg16_t flags;
|
||||
} PACKED out_stack;
|
||||
reg16_t ret_ax;
|
||||
|
||||
RM_FRAGMENT(rm_pcbios_disk_read,
|
||||
"sti\n\t"
|
||||
"popw %ax\n\t"
|
||||
"popw %cx\n\t"
|
||||
"popw %dx\n\t"
|
||||
"popw %bx\n\t"
|
||||
"popw %es\n\t"
|
||||
"int $0x13\n\t"
|
||||
"pushfw\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
in_stack.ax.h = 2; /* INT 13,2 - Read disk sector */
|
||||
in_stack.ax.l = 1; /* Read one sector */
|
||||
in_stack.cx.h = cylinder & 0xff;
|
||||
in_stack.cx.l = ( ( cylinder >> 8 ) & 0x3 ) | sector;
|
||||
in_stack.dx.h = head;
|
||||
in_stack.dx.l = drive;
|
||||
in_stack.buffer.segment = SEGMENT ( buf );
|
||||
in_stack.buffer.offset = OFFSET ( buf );
|
||||
ret_ax.word = real_call ( rm_pcbios_disk_read, &in_stack, &out_stack );
|
||||
return ( out_stack.flags.word & CF ) ? ret_ax.word : 0;
|
||||
}
|
||||
#endif /* TRY_FLOPPY_FIRST */
|
||||
|
||||
#endif /* PCBIOS */
|
||||
85
src/arch/i386/firmware/pcbios/console.c
Normal file
85
src/arch/i386/firmware/pcbios/console.c
Normal file
@@ -0,0 +1,85 @@
|
||||
/* Etherboot routines for PCBIOS firmware.
|
||||
*
|
||||
* Body of routines taken from old pcbios.S
|
||||
*/
|
||||
|
||||
#ifdef PCBIOS
|
||||
|
||||
#include "etherboot.h"
|
||||
#include "realmode.h"
|
||||
#include "segoff.h"
|
||||
|
||||
#define ZF ( 1 << 6 )
|
||||
|
||||
/**************************************************************************
|
||||
CONSOLE_PUTC - Print a character on console
|
||||
**************************************************************************/
|
||||
void console_putc ( int character )
|
||||
{
|
||||
struct {
|
||||
reg16_t ax;
|
||||
} PACKED in_stack;
|
||||
|
||||
RM_FRAGMENT(rm_console_putc,
|
||||
"sti\n\t"
|
||||
"popw %ax\n\t"
|
||||
"movb $0x0e, %ah\n\t"
|
||||
"movl $1, %ebx\n\t"
|
||||
"int $0x10\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
in_stack.ax.l = character;
|
||||
real_call ( rm_console_putc, &in_stack, NULL );
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
CONSOLE_GETC - Get a character from console
|
||||
**************************************************************************/
|
||||
int console_getc ( void )
|
||||
{
|
||||
RM_FRAGMENT(rm_console_getc,
|
||||
"sti\n\t"
|
||||
"xorw %ax, %ax\n\t"
|
||||
"int $0x16\n\t"
|
||||
"xorb %ah, %ah\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
return real_call ( rm_console_getc, NULL, NULL );
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
CONSOLE_ISCHAR - Check for keyboard interrupt
|
||||
**************************************************************************/
|
||||
int console_ischar ( void )
|
||||
{
|
||||
RM_FRAGMENT(rm_console_ischar,
|
||||
"sti\n\t"
|
||||
"movb $1, %ah\n\t"
|
||||
"int $0x16\n\t"
|
||||
"pushfw\n\t"
|
||||
"popw %ax\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
return ( ( real_call ( rm_console_ischar, NULL, NULL ) & ZF ) == 0 );
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
GETSHIFT - Get keyboard shift state
|
||||
**************************************************************************/
|
||||
int getshift ( void )
|
||||
{
|
||||
RM_FRAGMENT(rm_getshift,
|
||||
"sti\n\t"
|
||||
"movb $2, %ah\n\t"
|
||||
"int $0x16\n\t"
|
||||
"andw $0x3, %ax\n\t"
|
||||
"cli\n\t"
|
||||
);
|
||||
|
||||
return real_call ( rm_getshift, NULL, NULL );
|
||||
}
|
||||
|
||||
#endif /* PCBIOS */
|
||||
296
src/arch/i386/firmware/pcbios/e820mangler.S
Normal file
296
src/arch/i386/firmware/pcbios/e820mangler.S
Normal file
@@ -0,0 +1,296 @@
|
||||
#undef CODE16
|
||||
#if defined(PCBIOS)
|
||||
#define CODE16
|
||||
#endif
|
||||
|
||||
#ifdef CODE16
|
||||
|
||||
#define BOCHSBP xchgw %bx,%bx
|
||||
|
||||
.text
|
||||
.arch i386
|
||||
.section ".text16", "ax", @progbits
|
||||
.code16
|
||||
|
||||
/****************************************************************************
|
||||
* Memory map mangling code
|
||||
****************************************************************************
|
||||
*/
|
||||
|
||||
.globl e820mangler
|
||||
e820mangler:
|
||||
|
||||
/* Macro to calculate offset of labels within code segment in
|
||||
* installed copy of code.
|
||||
*/
|
||||
#define INSTALLED(x) ( (x) - e820mangler )
|
||||
|
||||
/****************************************************************************
|
||||
* Intercept INT 15 memory calls and remove the hidden memory ranges
|
||||
* from the resulting memory map.
|
||||
****************************************************************************
|
||||
*/
|
||||
.globl _intercept_int15
|
||||
_intercept_int15:
|
||||
/* Preserve registers */
|
||||
pushw %bp
|
||||
/* Store %ax for future reference */
|
||||
pushw %ax
|
||||
/* Make INT-style call to old INT15 routine */
|
||||
pushfw
|
||||
lcall %cs:*INSTALLED(_intercepted_int15)
|
||||
/* Preserve flags returned by original E820 routine */
|
||||
pushfw
|
||||
/* Check for valid INT15 routine */
|
||||
jc intercept_int15_exit
|
||||
/* Check for a routine we want to intercept */
|
||||
movw %sp, %bp
|
||||
cmpw $0xe820, 2(%bp)
|
||||
je intercept_e820
|
||||
cmpw $0xe801, 2(%bp)
|
||||
je intercept_e801
|
||||
cmpb $0x88, 3(%bp)
|
||||
je intercept_88
|
||||
intercept_int15_exit:
|
||||
/* Restore registers and return */
|
||||
popfw
|
||||
popw %bp /* discard original %ax */
|
||||
popw %bp
|
||||
lret $2 /* 'iret' - flags already loaded */
|
||||
|
||||
.globl _intercepted_int15
|
||||
_intercepted_int15: .word 0,0
|
||||
|
||||
/****************************************************************************
|
||||
* Exclude an address range from a potentially overlapping address range
|
||||
*
|
||||
* Note: this *can* be called even if the range doesn't overlap; it
|
||||
* will simply return the range unaltered. It copes with all the
|
||||
* possible cases of overlap, including total overlap (which will
|
||||
* modify the range to length zero). If the to-be-excluded range is
|
||||
* in the middle of the target range, then the larger remaining
|
||||
* portion will be returned. If %di is nonzero on entry then the
|
||||
* range will only be truncated from the high end, i.e. the base
|
||||
* address will never be altered. All this in less than 30
|
||||
* instructions. :)
|
||||
*
|
||||
* Parameters:
|
||||
* %eax Base address of memory range
|
||||
* %ecx Length of memory range
|
||||
* %ebx Base address of memory range to exclude
|
||||
* %edx Length of memory range to exclude
|
||||
* %di 0 => truncate either end, 1 => truncate high end only
|
||||
* Returns:
|
||||
* %eax Updated base address of range
|
||||
* %ecx Updated length of range
|
||||
* %ebx,%edx Undefined
|
||||
* All other registers (including %di) preserved
|
||||
*
|
||||
* Note: "ja" is used rather than "jg" because we are comparing
|
||||
* unsigned ints
|
||||
****************************************************************************
|
||||
*/
|
||||
#ifdef TEST_EXCLUDE_ALGORITHM
|
||||
.code32
|
||||
#endif /* TEST_EXCLUDE_ALGORITHM */
|
||||
exclude_memory_range:
|
||||
/* Convert (start,length) to (start,end) */
|
||||
addl %eax, %ecx
|
||||
addl %ebx, %edx
|
||||
/* Calculate "prefix" length */
|
||||
subl %eax, %ebx /* %ebx = "prefix" length */
|
||||
ja 1f
|
||||
xorl %ebx, %ebx /* Truncate to zero if negative */
|
||||
1: /* %di == 0 => truncate either end
|
||||
* %di != 0 => truncate only high end
|
||||
*/
|
||||
testw %di, %di
|
||||
je use_either
|
||||
cmpl %eax, %edx
|
||||
jbe 99f /* excl. range is below target range */
|
||||
use_prefix: /* Use prefix, discard suffix */
|
||||
addl %eax, %ebx /* %ebx = candidate end address */
|
||||
cmpl %ecx, %ebx /* %ecx = min ( %ebx, %ecx ) */
|
||||
ja 1f
|
||||
movl %ebx, %ecx
|
||||
1: jmp 99f
|
||||
use_either:
|
||||
/* Calculate "suffix" length */
|
||||
subl %ecx, %edx /* %edx = -( "suffix" length ) */
|
||||
jb 1f
|
||||
xorl %edx, %edx /* Truncate to zero if negative */
|
||||
1: negl %edx /* %edx = "suffix" length */
|
||||
/* Use whichever is longest of "prefix" and "suffix" */
|
||||
cmpl %ebx, %edx
|
||||
jbe use_prefix
|
||||
use_suffix: /* Use suffix, discard prefix */
|
||||
negl %edx
|
||||
addl %ecx, %edx /* %edx = candidate start address */
|
||||
cmpl %eax, %edx /* %eax = max ( %eax, %edx ) */
|
||||
jb 1f
|
||||
movl %edx, %eax
|
||||
1:
|
||||
99: subl %eax, %ecx /* Convert back to (start,length) */
|
||||
ret
|
||||
|
||||
#ifdef TEST_EXCLUDE_ALGORITHM
|
||||
.globl __test_exclude
|
||||
__test_exclude:
|
||||
pushl %ebx
|
||||
pushl %edi
|
||||
movl 12(%esp), %eax
|
||||
movl 16(%esp), %ecx
|
||||
movl 20(%esp), %ebx
|
||||
movl 24(%esp), %edx
|
||||
movl 28(%esp), %edi
|
||||
call exclude_memory_range
|
||||
shll $16, %eax
|
||||
orl %ecx, %eax
|
||||
popl %edi
|
||||
popl %ebx
|
||||
ret
|
||||
.code16
|
||||
#endif /* TEST_EXCLUDE_ALGORITHM */
|
||||
|
||||
/****************************************************************************
|
||||
* Exclude Etherboot-reserved address ranges from a potentially
|
||||
* overlapping address range
|
||||
*
|
||||
* Parameters:
|
||||
* %eax Base address of memory range
|
||||
* %ecx Length of memory range
|
||||
* %di 0 => truncate either end, 1 => truncate high end only
|
||||
* Returns:
|
||||
* %eax Updated base address of range
|
||||
* %ecx Updated length of range
|
||||
* All other registers (including %di) preserved
|
||||
****************************************************************************
|
||||
*/
|
||||
exclude_hidden_memory_ranges:
|
||||
pushw %si
|
||||
pushl %ebx
|
||||
pushl %edx
|
||||
movw $INSTALLED(_hide_memory), %si
|
||||
2: movl %cs:0(%si), %ebx
|
||||
movl %cs:4(%si), %edx
|
||||
call exclude_memory_range
|
||||
addw $8, %si
|
||||
cmpw $INSTALLED(_hide_memory_end), %si
|
||||
jl 2b
|
||||
popl %edx
|
||||
popl %ebx
|
||||
popw %si
|
||||
ret
|
||||
|
||||
.globl _hide_memory
|
||||
_hide_memory:
|
||||
.long 0,0 /* Etherboot text (base,length) */
|
||||
.long 0,0 /* Heap (base,length) */
|
||||
_hide_memory_end:
|
||||
|
||||
/****************************************************************************
|
||||
* Intercept INT 15,E820 calls and remove the hidden memory ranges
|
||||
* from the resulting memory map.
|
||||
****************************************************************************
|
||||
*/
|
||||
#define SMAP ( 0x534d4150 )
|
||||
intercept_e820:
|
||||
/* Check for valid E820 routine */
|
||||
cmpl $SMAP, %eax
|
||||
jne intercept_int15_exit
|
||||
/* If base address isn't in the low 4GB, return unaltered
|
||||
* (since we never claim memory above 4GB). WARNING: we cheat
|
||||
* by assuming that no E820 region will straddle the 4GB
|
||||
* boundary: if this is not a valid assumption then things
|
||||
* will probably break.
|
||||
*/
|
||||
cmpl $0, %es:4(%di)
|
||||
jne intercept_int15_exit
|
||||
/* Preserve registers */
|
||||
pushl %eax
|
||||
pushl %ecx
|
||||
/* Update returned memory range */
|
||||
movl %es:0(%di), %eax /* Base */
|
||||
movl %es:8(%di), %ecx /* Length */
|
||||
pushw %di
|
||||
xorw %di, %di /* "truncate either end" flag */
|
||||
call exclude_hidden_memory_ranges
|
||||
popw %di
|
||||
movl %eax, %es:0(%di) /* Store updated base */
|
||||
movl %ecx, %es:8(%di) /* Store updated length */
|
||||
/* Restore registers and return */
|
||||
popl %ecx
|
||||
popl %eax
|
||||
jmp intercept_int15_exit
|
||||
|
||||
/****************************************************************************
|
||||
* Intercept INT 15,E801 calls and remove the hidden memory ranges
|
||||
* from the resulting memory map.
|
||||
****************************************************************************
|
||||
*/
|
||||
intercept_e801:
|
||||
/* Adjust return values */
|
||||
call e801_adjust
|
||||
xchgw %ax, %cx
|
||||
xchgw %bx, %dx
|
||||
call e801_adjust
|
||||
xchgw %ax, %cx
|
||||
xchgw %bx, %dx
|
||||
jmp intercept_int15_exit
|
||||
|
||||
/* %ax = #KB from 1MB+, %bx = #64KB from 16MB+
|
||||
* Return with modified values in %ax, %bx. Preserver other regs.
|
||||
*/
|
||||
e801_adjust:
|
||||
pushw %di
|
||||
pushl %ecx
|
||||
pushl %eax
|
||||
movw $1, %di /* "truncate only high end" flag */
|
||||
|
||||
/* Truncate #64KB from 16MB+ as appropriate */
|
||||
movw %bx, %cx /* (no need to zero high word) */
|
||||
shll $16, %ecx /* %ecx = length in bytes */
|
||||
movl $(1<<24), %eax /* 16MB start address */
|
||||
call exclude_hidden_memory_ranges
|
||||
shrl $16, %ecx /* %cx = updated length in 64KB */
|
||||
movw %cx, %bx /* Return in %bx */
|
||||
|
||||
/* Truncate #KB from 1MB+ as appropriate */
|
||||
popw %cx /* Orig. %ax (high word already 0) */
|
||||
shll $10, %ecx /* %ecx = length in bytes */
|
||||
shrl $4, %eax /* 1MB start address */
|
||||
call exclude_hidden_memory_ranges
|
||||
shrl $10, %ecx /* %cx = updated length in KB */
|
||||
pushw %cx /* Will be picked up in %eax */
|
||||
|
||||
popl %eax
|
||||
popl %ecx
|
||||
popw %di
|
||||
ret
|
||||
|
||||
/****************************************************************************
|
||||
* Intercept INT 15,88 calls and remove the hidden memory ranges
|
||||
* from the resulting memory map.
|
||||
****************************************************************************
|
||||
*/
|
||||
intercept_88:
|
||||
pushw %bx /* E801 adjust, ignore %bx */
|
||||
call e801_adjust
|
||||
popw %bx
|
||||
jmp intercept_int15_exit
|
||||
|
||||
.globl e820mangler_end
|
||||
e820mangler_end:
|
||||
|
||||
.globl _e820mangler_size
|
||||
.equ _e820mangler_size, e820mangler_end - e820mangler
|
||||
.globl e820mangler_size
|
||||
e820mangler_size:
|
||||
.word _e820mangler_size
|
||||
|
||||
#else
|
||||
|
||||
.globl _e820mangler_size
|
||||
.equ _e820mangler_size, 0
|
||||
|
||||
#endif /* CODE16 */
|
||||
94
src/arch/i386/firmware/pcbios/hidemem.c
Normal file
94
src/arch/i386/firmware/pcbios/hidemem.c
Normal file
@@ -0,0 +1,94 @@
|
||||
/* Utility functions to hide Etherboot by manipulating the E820 memory
|
||||
* map. These could go in memsizes.c, but are placed here because not
|
||||
* all images will need them.
|
||||
*/
|
||||
|
||||
#include "etherboot.h"
|
||||
#include "hidemem.h"
|
||||
|
||||
#ifdef CODE16
|
||||
|
||||
static int mangling = 0;
|
||||
static void *mangler = NULL;
|
||||
|
||||
#define INSTALLED(x) ( (typeof(&x)) ( (void*)(&x) - (void*)e820mangler \
|
||||
+ mangler ) )
|
||||
#define intercept_int15 INSTALLED(_intercept_int15)
|
||||
#define intercepted_int15 INSTALLED(_intercepted_int15)
|
||||
#define hide_memory INSTALLED(_hide_memory)
|
||||
#define INT15_VECTOR ( (segoff_t*) ( phys_to_virt( 4 * 0x15 ) ) )
|
||||
|
||||
int install_e820mangler ( void *new_mangler ) {
|
||||
if ( mangling ) return 0;
|
||||
memcpy ( new_mangler, &e820mangler, e820mangler_size );
|
||||
mangler = new_mangler;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Intercept INT15 calls and pass them through the mangler. The
|
||||
* mangler must have been copied to base memory before making this
|
||||
* call, and "mangler" must point to the base memory copy, which must
|
||||
* be 16-byte aligned.
|
||||
*/
|
||||
int hide_etherboot ( void ) {
|
||||
if ( mangling ) return 1;
|
||||
if ( !mangler ) return 0;
|
||||
|
||||
/* Hook INT15 handler */
|
||||
*intercepted_int15 = *INT15_VECTOR;
|
||||
(*hide_memory)[0].start = virt_to_phys(_text);
|
||||
(*hide_memory)[0].length = _end - _text;
|
||||
/* IMPORTANT, possibly even FIXME:
|
||||
*
|
||||
* Etherboot has a tendency to claim a very large area of
|
||||
* memory as possible heap; enough to make it impossible to
|
||||
* load an OS if we hide all of it. We hide only the portion
|
||||
* that's currently in use. This means that we MUST NOT
|
||||
* perform further allocations from the heap while the mangler
|
||||
* is active.
|
||||
*/
|
||||
(*hide_memory)[1].start = heap_ptr;
|
||||
(*hide_memory)[1].length = heap_bot - heap_ptr;
|
||||
INT15_VECTOR->segment = SEGMENT(mangler);
|
||||
INT15_VECTOR->offset = 0;
|
||||
|
||||
mangling = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int unhide_etherboot ( void ) {
|
||||
if ( !mangling ) return 1;
|
||||
|
||||
/* Restore original INT15 handler
|
||||
*/
|
||||
if ( VIRTUAL(INT15_VECTOR->segment,INT15_VECTOR->offset) != mangler ) {
|
||||
/* Oh dear... */
|
||||
|
||||
#ifdef WORK_AROUND_BPBATCH_BUG
|
||||
/* BpBatch intercepts INT15, so can't unhook it, and
|
||||
* then proceeds to ignore our PXENV_KEEP_UNDI return
|
||||
* status, which means that it ends up zeroing out the
|
||||
* INT15 handler routine.
|
||||
*
|
||||
* This rather ugly hack involves poking into
|
||||
* BpBatch's code and changing it's stored value for
|
||||
* the "next handler" in the INT15 chain.
|
||||
*/
|
||||
segoff_t *bp_chain = VIRTUAL ( 0x0060, 0x8254 );
|
||||
|
||||
if ( ( bp_chain->segment == SEGMENT(mangler) ) &&
|
||||
( bp_chain->offset == 0 ) ) {
|
||||
printf ( "\nBPBATCH bug workaround enabled\n" );
|
||||
*bp_chain = *intercepted_int15;
|
||||
}
|
||||
#endif /* WORK_AROUND_BPBATCH_BUG */
|
||||
|
||||
return 0;
|
||||
}
|
||||
*INT15_VECTOR = *intercepted_int15;
|
||||
|
||||
mangling = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* CODE16 */
|
||||
201
src/arch/i386/firmware/pcbios/memsizes.c
Normal file
201
src/arch/i386/firmware/pcbios/memsizes.c
Normal file
@@ -0,0 +1,201 @@
|
||||
#ifdef PCBIOS
|
||||
|
||||
#include "etherboot.h"
|
||||
#include "realmode.h"
|
||||
|
||||
#define CF ( 1 << 0 )
|
||||
|
||||
#ifndef MEMSIZES_DEBUG
|
||||
#define MEMSIZES_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* by Eric Biederman */
|
||||
|
||||
struct meminfo meminfo;
|
||||
|
||||
/**************************************************************************
|
||||
BASEMEMSIZE - Get size of the conventional (base) memory
|
||||
**************************************************************************/
|
||||
unsigned short basememsize ( void )
|
||||
{
|
||||
RM_FRAGMENT(rm_basememsize,
|
||||
"int $0x12\n\t"
|
||||
);
|
||||
return real_call ( rm_basememsize, NULL, NULL );
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
MEMSIZE - Determine size of extended memory
|
||||
**************************************************************************/
|
||||
unsigned int memsize ( void )
|
||||
{
|
||||
struct {
|
||||
reg16_t ax;
|
||||
} PACKED in_stack;
|
||||
struct {
|
||||
reg16_t ax;
|
||||
reg16_t bx;
|
||||
reg16_t cx;
|
||||
reg16_t dx;
|
||||
reg16_t flags;
|
||||
} PACKED out_stack;
|
||||
int memsize;
|
||||
|
||||
RM_FRAGMENT(rm_memsize,
|
||||
/* Some buggy BIOSes don't clear/set carry on pass/error of
|
||||
* e801h memory size call or merely pass cx,dx through without
|
||||
* changing them, so we set carry and zero cx,dx before call.
|
||||
*/
|
||||
"stc\n\t"
|
||||
"xorw %cx,%cx\n\t"
|
||||
"xorw %dx,%dx\n\t"
|
||||
"popw %ax\n\t"
|
||||
"int $0x15\n\t"
|
||||
"pushfw\n\t"
|
||||
"pushw %dx\n\t"
|
||||
"pushw %cx\n\t"
|
||||
"pushw %bx\n\t"
|
||||
"pushw %ax\n\t"
|
||||
);
|
||||
|
||||
/* Try INT 15,e801 first */
|
||||
in_stack.ax.word = 0xe801;
|
||||
real_call ( rm_memsize, &in_stack, &out_stack );
|
||||
if ( out_stack.flags.word & CF ) {
|
||||
/* INT 15,e801 not supported: try INT 15,88 */
|
||||
in_stack.ax.word = 0x8800;
|
||||
memsize = real_call ( rm_memsize, &in_stack, &out_stack );
|
||||
} else {
|
||||
/* Some BIOSes report extended memory via ax,bx rather
|
||||
* than cx,dx
|
||||
*/
|
||||
if ( (out_stack.cx.word==0) && (out_stack.dx.word==0) ) {
|
||||
/* Use ax,bx */
|
||||
memsize = ( out_stack.bx.word<<6 ) + out_stack.ax.word;
|
||||
} else {
|
||||
/* Use cx,dx */
|
||||
memsize = ( out_stack.dx.word<<6 ) + out_stack.cx.word;
|
||||
}
|
||||
}
|
||||
return memsize;
|
||||
}
|
||||
|
||||
#define SMAP ( 0x534d4150 )
|
||||
int meme820 ( struct e820entry *buf, int count )
|
||||
{
|
||||
struct {
|
||||
reg16_t flags;
|
||||
reg32_t eax;
|
||||
reg32_t ebx;
|
||||
struct e820entry entry;
|
||||
} PACKED stack;
|
||||
int index = 0;
|
||||
|
||||
RM_FRAGMENT(rm_meme820,
|
||||
"addw $6, %sp\n\t" /* skip flags, eax */
|
||||
"popl %ebx\n\t"
|
||||
"pushw %ss\n\t" /* es:di = ss:sp */
|
||||
"popw %es\n\t"
|
||||
"movw %sp, %di\n\t"
|
||||
"movl $0xe820, %eax\n\t"
|
||||
"movl $" RM_STR(SMAP) ", %edx\n\t"
|
||||
"movl $" RM_STR(E820ENTRY_SIZE) ", %ecx\n\t"
|
||||
"int $0x15\n\t"
|
||||
"pushl %ebx\n\t"
|
||||
"pushl %eax\n\t"
|
||||
"pushfw\n\t"
|
||||
);
|
||||
|
||||
stack.ebx.dword = 0; /* 'EOF' marker */
|
||||
while ( ( index < count ) &&
|
||||
( ( index == 0 ) || ( stack.ebx.dword != 0 ) ) ) {
|
||||
real_call ( rm_meme820, &stack, &stack );
|
||||
if ( stack.eax.dword != SMAP ) return 0;
|
||||
if ( stack.flags.word & CF ) return 0;
|
||||
buf[index++] = stack.entry;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void get_memsizes(void)
|
||||
{
|
||||
/* Ensure we don't stomp bios data structutres.
|
||||
* the interrupt table: 0x000 - 0x3ff
|
||||
* the bios data area: 0x400 - 0x502
|
||||
* Dos variables: 0x502 - 0x5ff
|
||||
*/
|
||||
static const unsigned min_addr = 0x600;
|
||||
unsigned i;
|
||||
unsigned basemem;
|
||||
basemem = get_free_base_memory();
|
||||
meminfo.basememsize = basememsize();
|
||||
meminfo.memsize = memsize();
|
||||
#ifndef IGNORE_E820_MAP
|
||||
meminfo.map_count = meme820(meminfo.map, E820MAX);
|
||||
#else
|
||||
meminfo.map_count = 0;
|
||||
#endif
|
||||
if (meminfo.map_count == 0) {
|
||||
/* If we don't have an e820 memory map fake it */
|
||||
meminfo.map_count = 2;
|
||||
meminfo.map[0].addr = 0;
|
||||
meminfo.map[0].size = meminfo.basememsize << 10;
|
||||
meminfo.map[0].type = E820_RAM;
|
||||
meminfo.map[1].addr = 1024*1024;
|
||||
meminfo.map[1].size = meminfo.memsize << 10;
|
||||
meminfo.map[1].type = E820_RAM;
|
||||
}
|
||||
/* Scrub the e820 map */
|
||||
for(i = 0; i < meminfo.map_count; i++) {
|
||||
if (meminfo.map[i].type != E820_RAM) {
|
||||
continue;
|
||||
}
|
||||
/* Reserve the bios data structures */
|
||||
if (meminfo.map[i].addr < min_addr) {
|
||||
unsigned long delta;
|
||||
delta = min_addr - meminfo.map[i].addr;
|
||||
if (delta > meminfo.map[i].size) {
|
||||
delta = meminfo.map[i].size;
|
||||
}
|
||||
meminfo.map[i].addr = min_addr;
|
||||
meminfo.map[i].size -= delta;
|
||||
}
|
||||
/* Ensure the returned e820 map is in sync
|
||||
* with the actual memory state
|
||||
*/
|
||||
if ((meminfo.map[i].addr < 0xa0000) &&
|
||||
((meminfo.map[i].addr + meminfo.map[i].size) > basemem))
|
||||
{
|
||||
if (meminfo.map[i].addr <= basemem) {
|
||||
meminfo.map[i].size = basemem - meminfo.map[i].addr;
|
||||
} else {
|
||||
meminfo.map[i].addr = basemem;
|
||||
meminfo.map[i].size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if MEMSIZES_DEBUG
|
||||
{
|
||||
int i;
|
||||
printf("basememsize %d\n", meminfo.basememsize);
|
||||
printf("memsize %d\n", meminfo.memsize);
|
||||
printf("Memory regions(%d):\n", meminfo.map_count);
|
||||
for(i = 0; i < meminfo.map_count; i++) {
|
||||
unsigned long long r_start, r_end;
|
||||
r_start = meminfo.map[i].addr;
|
||||
r_end = r_start + meminfo.map[i].size;
|
||||
printf("[%X%X, %X%X) type %d\n",
|
||||
(unsigned long)(r_start >> 32),
|
||||
(unsigned long)r_start,
|
||||
(unsigned long)(r_end >> 32),
|
||||
(unsigned long)r_end,
|
||||
meminfo.map[i].type);
|
||||
#if defined(CONSOLE_FIRMWARE)
|
||||
sleep(1); /* No way to see 32 entries on a standard 80x25 screen... */
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* PCBIOS */
|
||||
Reference in New Issue
Block a user