BIOS INT 15h E820: Reading the Physical Memory Map Before DOS
Follow the E820 continuation protocol, validate firmware descriptors, and distinguish physical address ranges from memory DOS can allocate.
BIOS INT 15h, EAX=E820h is an x86 firmware query that enumerates physical-address ranges and classifies them for a boot loader. A boot manager or kernel can use the returned map before it decides which RAM to own and which ranges belong to firmware, devices, or ACPI. The call is not part of the DOS application memory API: FreeDOS programs should not use an E820 entry as though it were a DOS allocation handle, an XMS block, or an upper-memory block.
The interface is a continuation protocol, not a one-shot total-memory query. The caller initializes a record buffer and a continuation value, asks for one entry, validates the reply, saves it, and passes the returned continuation token back to obtain the next entry. On the first request, the continuation value is zero. The firmware returns the ASCII signature SMAP in EAX, a descriptor length in ECX, the next token in EBX, and an entry through ES:DI. Set up the CPU and buffer for real-mode BIOS access; a 16-bit real-mode caller needs a 386-class CPU and a wrapper that can supply and recover 32-bit registers correctly.
A guarded enumeration loop
This register-level pseudocode illustrates the checks. It is not directly compilable C; a boot loader must implement the interrupt wrapper, correct segment addressing, and stack discipline for its execution environment.
continuation = 0
first_call = true
repeat with a strict maximum entry count:
clear 24-byte descriptor buffer, including extension attributes
EAX = 0xE820
EDX = 0x534D4150 ; "SMAP"
EBX = continuation ; zero for first call
ECX = 24 ; room for the extended descriptor
ES:DI = descriptor_buffer ; accessible to BIOS in real mode
INT 15h
if carry set:
if first_call: reject E820 and choose a documented fallback
else: reject incomplete enumeration; do not treat partial entries as complete
if EAX != 0x534D4150 or ECX < 20: reject this response
read 64-bit base, 64-bit length, and 32-bit type from first 20 bytes
if ECX >= 24: inspect extended attributes only as specified
validate range arithmetic before recording it
first_call = false
if EBX == 0: finish
if EBX repeats without progress: reject to prevent an infinite loop
continuation = EBX
The first 20 bytes hold the base address, length, and type. Later firmware may return a longer descriptor; the ACPI-style extension includes an attributes field. A caller must honor the returned size: use an extension only when it is present and its validity rules say it is meaningful. Do not reject a valid 20-byte entry merely because the caller reserved a larger buffer, and do not read attributes from an uninitialized extension. The Intel BIOS Writer’s Guide describes the E820 calling convention; Linux’s x86 boot code shows how a real loader handles compatibility and bounds.
The continuation value is opaque to the caller. Pass the returned EBX back unchanged; do not increment it or treat it as an array index. Put a hard upper bound on the number of entries accepted, because corrupt or buggy firmware must not be able to spin the boot loader forever. A carry-set response on the first call means no valid E820 result was collected; an error after several entries is not automatically proof that the partial set is complete. Keep a raw record and the reason enumeration stopped so later stages can distinguish a valid terminator from a firmware failure.
Sanitize ranges conservatively
Each base and length is 64-bit. Before computing base + length, check for unsigned overflow. Zero-length entries are not usable ranges. Preserve the type field instead of reducing the map to a single “bytes of RAM” value. Type 1 traditionally denotes usable RAM; other values can represent reserved space, ACPI reclaim memory, ACPI NVS, unusable memory, persistent memory, or implementation-specific classes. The exact policy belongs to the boot protocol and the consumer, not to a guess based on a familiar numeric type.
Entries may be adjacent, unsorted, overlapping, or inconsistent. Normalize only after retaining the original sequence. When ranges conflict, prefer the more restrictive interpretation unless the platform specification says otherwise. Never turn reserved space into usable RAM because two entries overlap or because a legacy API reports a larger total. Track the fixed low-memory areas, video/firmware ranges, boot-loader image, temporary stack and E820 buffer as occupied by their owners even if a simplistic report labels surrounding memory usable.
Use checked arithmetic at every boundary. A safe consumer verifies that length is nonzero, that base + length does not wrap the 64-bit address space, that the end lies within the address range the kernel can represent, and that the descriptor is not merely stale data left in the buffer. Keep type and attributes attached to each range through sorting and merging; two adjacent ranges with different types must not be collapsed into one usable extent. If the boot protocol uses a narrower integer for the final map, reject or explicitly clip ranges that do not fit rather than silently truncating their high bits.
Do not assume a friendly-looking map is complete just because the first one or two entries contain conventional and extended RAM. A consumer should establish that enumeration reached its normal terminator, stayed under a defensive capacity limit, and returned structurally valid records. If the loader’s fixed buffer fills, record an explicit overflow failure; silently dropping the last reserved region can be more dangerous than refusing to boot. Preserve diagnostics in a format the next stage can inspect, including which call failed and how many entries were accepted.
Keep BIOS access in the correct execution environment
E820 is a BIOS interrupt service. A real-mode boot sector or loader can invoke it directly when it has the required CPU/register support and a BIOS-accessible buffer. A protected-mode kernel cannot simply issue the real-mode interrupt as if it were a normal function; it needs an established real-mode transition, firmware interface, or a map passed from an earlier boot stage. Follow the boot protocol used by the kernel rather than adding a second BIOS call after memory ownership has already changed.
The loader should reserve the memory used by its own code, stack, descriptor buffer, loaded kernel, modules, and boot data before handing the map to the next stage. “Usable RAM” is not synonymous with “free for this stage”: the same type-1 range may contain objects already placed there by firmware or the loader. The handoff contract needs both the firmware classification and reservations made by software. A later allocator can then subtract occupied ranges before making pages available.
Review output before trusting it
For each test machine or emulator, log the raw descriptors in the order returned and the normalized map separately. Include the firmware identity when available, entry size, base, length, type, attributes when valid, and termination status. Compare the results across a cold boot and any firmware setting that changes memory remapping; a map is a report of that boot environment, not a hardware invariant. If two sources disagree, preserve both outputs and identify which interface each used instead of averaging totals.
Test failure paths too: first-call unsupported signature, malformed size, zero-length range, arithmetic overflow, repeated continuation token, entry-capacity exhaustion, and a simulated incomplete response. The expected outcome for invalid input is a clear rejection or documented fallback, never a guessed allocation. This is especially important in boot code because a bad map can corrupt memory much later than the call that produced it, making the original cause hard to diagnose.
Compatibility fallback is separate from a valid map
Older BIOS interfaces such as INT 15h/AH=88h and AX=E801h return less detailed extended-memory summaries. A loader can implement them as explicit fallbacks when E820 is unavailable, but it must record which method produced the result and apply that method’s address limits. Do not add a legacy total to a valid E820 list; that can double-count regions or erase reserved holes. If neither interface supplies a trustworthy answer, use a conservative platform-specific minimum or stop with an actionable error rather than guessing that all installed memory is free.
Why DOS applications should use other interfaces
E820 reports a physical map at boot time. It does not reserve memory, enable the A20 line, map memory into a protected-mode address space, or notify DOS when another component takes ownership. Conventional memory is constrained by the real-mode address layout, while XMS, EMS, UMB providers, and DPMI use their own allocation and mapping contracts. The DOS kernel and memory managers decide what software can safely request. A DOS utility that bypasses them can corrupt the kernel, a driver, video memory, or firmware state even when the bytes appeared as RAM in an early map.
For low-level diagnostics, preserve both the firmware entries and the normalized result, report unknown types, and test in more than one BIOS or emulator. Include total entry count, range type, base, length, and rejection reason in the log. For a normal FreeDOS application, use the documented DOS, XMS, EMS, or DPMI API that matches the program model. E820 is valuable because it exposes platform layout to the boot stage; it is not a universal memory allocator.
Related:
- FreeDOS and BIOS INT 13h: CHS, LBA Extensions, and Safe Disk Access
- XMS, EMS, and the Many Kinds of DOS Memory Beyond 640K
Sources: