254 lines
12 KiB
Markdown
254 lines
12 KiB
Markdown
|
The SeaBIOS code is required to support multiple x86 CPU memory
|
||
|
models. This requirement impacts the code layout and internal storage
|
||
|
of SeaBIOS.
|
||
|
|
||
|
x86 Memory Models
|
||
|
=================
|
||
|
|
||
|
The x86 line of CPUs has evolved over many years. The original 8086
|
||
|
chip used 16bit pointers and could only address 1 megabyte of memory.
|
||
|
The 80286 CPU still used 16bit pointers, but could address up to 16
|
||
|
megabytes of memory. The 80386 chips could process 32bit instructions
|
||
|
and could access up to 4 gigabyte of memory. The most recent x86 chips
|
||
|
can process 64bit instructions and access 16 exabytes of ram.
|
||
|
|
||
|
During the evolution of the x86 CPUs from the 8086 to the 80386 the
|
||
|
BIOS was extended to handle calls in the various modes that the CPU
|
||
|
implemented.
|
||
|
|
||
|
This section outlines the five different x86 CPU execution and memory
|
||
|
access models that SeaBIOS supports.
|
||
|
|
||
|
16bit real mode
|
||
|
---------------
|
||
|
|
||
|
This mode is a
|
||
|
[segmented](http://en.wikipedia.org/wiki/Memory_segmentation) memory
|
||
|
mode invoked by callers. The CPU defaults to executing 16bit
|
||
|
instructions. Callers typically invoke the BIOS by issuing an "int x"
|
||
|
instruction which causes a software
|
||
|
[interrupt](http://en.wikipedia.org/wiki/Interrupt) that is handled by
|
||
|
the BIOS. The SeaBIOS code also handles hardware interrupts in this
|
||
|
mode. SeaBIOS can only access the first 1 megabyte of memory in this
|
||
|
mode, but it can access any part of that first megabyte.
|
||
|
|
||
|
16bit bigreal mode
|
||
|
------------------
|
||
|
|
||
|
This mode is a segmented memory mode that is used for [option
|
||
|
roms](http://en.wikipedia.org/wiki/Option_ROM). The CPU defaults to
|
||
|
executing 16bit instructions and segmented memory accesses are still
|
||
|
used. However, the segment limits are increased so that the entire
|
||
|
first 4 gigabytes of memory is fully accessible. Callers can invoke
|
||
|
all the [16bit real mode](#16bit_real_mode) functions while in this
|
||
|
mode and can also invoke the Post Memory Manager (PMM) functions that
|
||
|
are available during option rom execution.
|
||
|
|
||
|
16bit protected mode
|
||
|
--------------------
|
||
|
|
||
|
CPU execution in this mode is similar to [16bit real
|
||
|
mode](#16bit_real_mode). The CPU defaults to executing 16bit
|
||
|
instructions. However, each segment register indexes a "descriptor
|
||
|
table", and it is difficult or impossible to know what the physical
|
||
|
address of each segment is. Generally speaking, the BIOS can only
|
||
|
access code and data in the f-segment. The PCIBIOS, APM BIOS, and PNP
|
||
|
BIOS all have documented 16bit protected mode entry points.
|
||
|
|
||
|
Some old code may attempt to invoke the standard [16bit real
|
||
|
mode](#16bit_real_mode) entry points while in 16bit protected
|
||
|
mode. The PCI BIOS specification explicitly requires that the legacy
|
||
|
"int 1a" real mode entry point support 16bit protected mode calls if
|
||
|
they are for the PCI BIOS. Callers of other legacy entry points in
|
||
|
protected mode have not been observed and SeaBIOS does not support
|
||
|
them.
|
||
|
|
||
|
32bit segmented mode
|
||
|
--------------------
|
||
|
|
||
|
In this mode the processor runs in 32bit mode, but the segment
|
||
|
registers may have a limit and may have a non-zero offset. In effect,
|
||
|
this mode has all of the limitations of [16bit protected
|
||
|
mode](#16bit_protected_mode) - the main difference between the modes
|
||
|
is that the processor defaults to executing 32bit instructions. In
|
||
|
addition to these limitations, callers may also run the SeaBIOS code
|
||
|
at varying virtual addresses and so the code must support code
|
||
|
relocation. The PCI BIOS specification and APM BIOS specification
|
||
|
define 32bit segmented mode interfaces.
|
||
|
|
||
|
32bit flat mode
|
||
|
---------------
|
||
|
|
||
|
In this mode the processor defaults to executing 32bit instructions,
|
||
|
and all segment registers have an offset of zero and allow access to
|
||
|
the entire first 4 gigabytes of memory. This is the only "sane" mode
|
||
|
for 32bit code - modern compilers and modern operating systems will
|
||
|
generally only support this mode (when running 32bit code).
|
||
|
Ironically, it's the only mode that is not strictly required for a
|
||
|
BIOS to support. SeaBIOS uses this mode internally to support the POST
|
||
|
and BOOT [phases of execution](Execution and code flow).
|
||
|
|
||
|
code16gcc
|
||
|
=========
|
||
|
|
||
|
In order to produce code that can run when the processor is in a 16bit
|
||
|
mode, SeaBIOS uses the
|
||
|
[binutils](http://en.wikipedia.org/wiki/GNU_Binutils) ".code16gcc"
|
||
|
assembler flag. This instructs the assembler to emit extra prefix
|
||
|
opcodes so that the 32bit code produced by
|
||
|
[gcc](http://en.wikipedia.org/wiki/GNU_Compiler_Collection) will run
|
||
|
even when the processor is in 16bit mode. Note that gcc always
|
||
|
produces 32bit code - it does not know about the ".code16gcc" flag and
|
||
|
does not know that the code will run in a 16bit mode.
|
||
|
|
||
|
SeaBIOS uses the same code for all of the 16bit modes ([16bit real
|
||
|
mode](#16bit_real_mode), [16bit bigreal mode](#16bit_bigreal_mode),
|
||
|
and [16bit protected mode](#16bit_protected_mode)) and that code is
|
||
|
assembled using ".code16gcc". SeaBIOS is careful to use segment
|
||
|
registers properly so that the same code can run in the different
|
||
|
16bit modes that it needs to support.
|
||
|
|
||
|
C code mode flags
|
||
|
=================
|
||
|
|
||
|
Two compile time flags are available to determine the memory model the
|
||
|
code is intended for: MODE16 and MODESEGMENT. When compiling for the
|
||
|
16 bit modes, MODE16 is true and MODESEGMENT is true. In 32bit
|
||
|
segmented mode, MODE16 is false and MODESEGMENT is true. In 32bit flat
|
||
|
mode both MODE16 and MODESEGMENT are false.
|
||
|
|
||
|
Common memory used at run-time
|
||
|
==============================
|
||
|
|
||
|
There are several memory areas that the SeaBIOS "runtime"
|
||
|
[phase](Execution and code flow) makes use of:
|
||
|
|
||
|
* 0x000000-0x000400: Interrupt descriptor table (IDT). This area
|
||
|
defines 256 interrupt vectors as defined by the Intel CPU
|
||
|
specification for 16bit irq handlers. This area is read/writable at
|
||
|
runtime and can be accessed from 16bit real mode and 16bit bigreal
|
||
|
mode calls. SeaBIOS only uses this area to maintain compatibility
|
||
|
with legacy systems.
|
||
|
|
||
|
* 0x000400-0x000500: BIOS Data Area (BDA). This area contains various
|
||
|
legacy flags and attributes. The area is read/writable at runtime
|
||
|
and can be accessed from 16bit real mode and 16bit bigreal mode
|
||
|
calls. SeaBIOS only uses this area to maintain compatibility with
|
||
|
legacy systems.
|
||
|
|
||
|
* 0x09FC00-0x0A0000 (typical): Extended BIOS Data Area (EBDA). This
|
||
|
area contains a few legacy flags and attributes. The area is
|
||
|
typically located at 0x9FC00, but it can be moved by option roms, by
|
||
|
legacy operating systems, and by SeaBIOS if
|
||
|
CONFIG_MALLOC_UPPERMEMORY is not set. Its actual location is
|
||
|
determined by a pointer in the BDA. The area is read/writable at
|
||
|
runtime and can be accessed from 16bit real mode and 16bit bigreal
|
||
|
mode calls. SeaBIOS only uses this area to maintain compatibility
|
||
|
with legacy systems.
|
||
|
|
||
|
* 0x0E0000-0x0F0000 (typical): "low" memory. This area is used for
|
||
|
custom read/writable storage internal to SeaBIOS. The area is
|
||
|
read/writable at runtime and can be accessed from 16bit real mode
|
||
|
and 16bit bigreal mode calls. The area is typically located at the
|
||
|
end of the e-segment, but the build may position it anywhere in the
|
||
|
0x0C0000-0x0F0000 region. However, if CONFIG_MALLOC_UPPERMEMORY is
|
||
|
not set, then this region is between 0x090000-0x0A0000. Space is
|
||
|
allocated in this region by either marking a global variable with
|
||
|
the "VARLOW" flag or by calling malloc_low() during
|
||
|
initialization. The area can be grown dynamically (via malloc_low),
|
||
|
but it will never exceed 64K.
|
||
|
|
||
|
* 0x0F0000-0x100000: The BIOS segment. This area is used for both
|
||
|
runtime code and static variables. Space is allocated in this region
|
||
|
by either marking a global variable with VAR16, one of the VARFSEG
|
||
|
flags, or by calling malloc_fseg() during initialization. The area
|
||
|
is read-only at runtime and can be accessed from 16bit real mode,
|
||
|
16bit bigreal mode, 16bit protected mode, and 32bit segmented mode
|
||
|
calls.
|
||
|
|
||
|
All of the above areas are also read/writable during the SeaBIOS
|
||
|
initialization phase and are accessible when in 32bit flat mode.
|
||
|
|
||
|
Segmented mode memory access
|
||
|
============================
|
||
|
|
||
|
The assembler entry functions for segmented mode calls (all modes
|
||
|
except [32bit flat mode](#32bit_flat_mode)) will arrange
|
||
|
to set the data segment (%ds) to be the same as the stack segment
|
||
|
(%ss) before calling any C code. This permits all C variables located
|
||
|
on the stack and C pointers to data located on the stack to work as
|
||
|
normal.
|
||
|
|
||
|
However, all code running in segmented mode must wrap non-stack memory
|
||
|
accesses in special macros. These macros ensure the correct segment
|
||
|
register is used. Failure to use the correct macro will result in an
|
||
|
incorrect memory access that will likely cause hard to find errors.
|
||
|
|
||
|
There are three low-level memory access macros:
|
||
|
|
||
|
* GET_VAR / SET_VAR : Accesses a variable using the specified segment
|
||
|
register. This isn't typically used directly by C code.
|
||
|
|
||
|
* GET_FARVAR / SET_FARVAR : Assigns the extra segment (%es) to the
|
||
|
given segment id and then performs the given memory access via %es.
|
||
|
|
||
|
* GET_FLATPTR / SET_FLATPTR : These macros take a 32bit pointer,
|
||
|
construct a segment/offset pair valid in real mode, and then perform
|
||
|
the given access. These macros must not be used in 16bit protected
|
||
|
mode or 32bit segmented mode.
|
||
|
|
||
|
Since most memory accesses are to [common memory used at
|
||
|
run-time](#Common_memory_used_at_run-time), several helper
|
||
|
macros are also available.
|
||
|
|
||
|
* GET_IDT / SET_IDT : Access the interrupt descriptor table (IDT).
|
||
|
|
||
|
* GET_BDA / SET_BDA : Access the BIOS Data Area (BDA).
|
||
|
|
||
|
* GET_EBDA / SET_EBDA : Access the Extended BIOS Data Area (EBDA).
|
||
|
|
||
|
* GET_LOW / SET_LOW : Access internal variables marked with
|
||
|
VARLOW. (There are also related macros GET_LOWFLAT / SET_LOWFLAT for
|
||
|
accessing storage allocated with malloc_low.)
|
||
|
|
||
|
* GET_GLOBAL : Access internal variables marked with the VAR16 or
|
||
|
VARFSEG flags. (There is also the related macro GET_GLOBALFLAT for
|
||
|
accessing storage allocated with malloc_fseg.)
|
||
|
|
||
|
Memory available during initialization
|
||
|
======================================
|
||
|
|
||
|
During the POST [phase](Execution and code flow) the code
|
||
|
can fully access the first 4 gigabytes of memory. However, memory
|
||
|
accesses are generally limited to the [common memory used at
|
||
|
run-time](#Common_memory_used_at_run-time) and areas
|
||
|
allocated at runtime via one of the malloc calls:
|
||
|
|
||
|
* malloc_high : Permanent high-memory zone. This area is used for
|
||
|
custom read/writable storage internal to SeaBIOS. The area is
|
||
|
located at the top of the first 4 gigabytes of ram. It is commonly
|
||
|
used for storing standard tables accessed by the operating system at
|
||
|
runtime (ACPI, SMBIOS, and MPTable) and for DMA buffers used by
|
||
|
hardware drivers. The area is read/writable at runtime and an entry
|
||
|
in the e820 memory map is used to reserve it. When running on an
|
||
|
emulator that has only 1 megabyte of ram this zone will be empty.
|
||
|
|
||
|
* malloc_tmphigh : Temporary high-memory zone. This area is used for
|
||
|
custom read/writable storage during the SeaBIOS initialization
|
||
|
phase. The area generally starts after the first 1 megabyte of ram
|
||
|
(0x100000) and ends prior to the Permanent high-memory zone. When
|
||
|
running on an emulator that has only 1 megabyte of ram this zone
|
||
|
will be empty. The area is not reserved from the operating system,
|
||
|
so it must not be accessed after the SeaBIOS initialization phase.
|
||
|
|
||
|
* malloc_tmplow : Temporary low-memory zone. This area is used for
|
||
|
custom read/writable storage during the SeaBIOS initialization
|
||
|
phase. The area resides between 0x07000-0x90000. The area is not
|
||
|
reserved from the operating system and by specification it is
|
||
|
required to be zero'd at the end of the initialization phase.
|
||
|
|
||
|
The "tmplow" and "tmphigh" regions are only available during the
|
||
|
initialization phase. Any access (either read or write) after
|
||
|
completion of the initialization phase can result in difficult to find
|
||
|
errors.
|