| Filename | Latest commit message | Latest commit date |
|---|---|---|
EFI status 0x5 is EFI_BUFFER_TOO_SMALL, and that is the whole bug: the loader
asked the firmware for its memory map with a 32 KB buffer, which is about 680
descriptors -- generous next to QEMU's map and not generous at all next to a
machine whose firmware has carved out every reserved range it has ever needed.
The descriptor count is a property of the vendor, not of the RAM, which is why
this was invisible in every test here and fatal on two real machines.
The fix is not a bigger guess. A call with a **zero size** fails on purpose and
fills in the size actually needed, and it allocates nothing -- so the map key
from the real call immediately after is still valid, which is the constraint
this whole sequence is written around and the reason the buffer has to be
static. 256 KB now, which is 5461 descriptors, and a machine that somehow
exceeds even that is told in numbers rather than in a status code:
schos: the firmware's memory map needs 5664 bytes and this loader has 512
schos: memory map larger than the loader's buffer (status 0x...0005)
That line is real: the failure path was provoked by shrinking the buffer to 512
and booting, which is also how the 5664 was measured -- QEMU needs 118
descriptors, so hardware needs more.
**A second limit is nearby, and it is only a warning.** SCHBOOT_E820_MAX is 40
entries, fixed by the hand-off block's layout; build_e820() merges adjacent
regions of the same type, so 40 survives a long map. If it ever does truncate,
what is dropped is the high end -- the map arrives sorted and phystop is capped
below 4 GB regardless -- so it stays a warning. But it now appears on screen as
well as on the serial line, because after the splash print() goes to a port a
laptop does not have, and "some RAM was quietly unused" is not a thing to
discover months later from a total that never looked right.
Both supported configurations: one boot each, no traps, no panics, socktest and
memfdtest green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
| .. | ||
| common | ||
| uefi | ||
| README.md | ||
Booting SchOS
One firmware path:
boot/
uefi/ the loader: a 64-bit PE application
common/ the user interface, shared with nothing else now
There were two. boot/bios/ held an MBR, an El Torito CD stub and a real-mode
stage 2, and it was a separate directory rather than #ifdefs because the two
shared almost nothing: a BIOS loader lives in 16-bit real mode and talks to the
firmware through software interrupts, while UEFI hands a loader long mode with
paging already on and a table of function pointers to call.
That asymmetry is what removed it. Everything since roughly 2012 boots UEFI,
CSM — the firmware-side BIOS emulation that let legacy loaders run at all — has
been going away since Intel dropped support for it in 2020, and the cost of the
second path was not the code but the tax on everything that touched it: stage 2
had a 65-sector ceiling that it silently overflowed, and the resolution menu
could not be shared because VBE mode setting is INT 10h and needs real mode.
The contract below survives it. It was written to let a second loader exist, and it is worth keeping in that shape: it is what the kernel expects, stated once, rather than a description of whoever happens to be calling.
What the loader must hand the kernel
Whichever path runs, the kernel expects the same things. This is the contract; a second loader is correct exactly when it satisfies it.
| Where | Notes | |
|---|---|---|
| Kernel image | loaded per its ELF program headers | entry at entry64 |
| CPU state | see below -- the two paths differ here | |
| Memory map | E820_MAP_ADDR, 24-byte entries, count at E820_COUNT_ADDR |
kernel/arch/x86_64/e820.c reads it |
| Framebuffer | VBE_INFO_ADDR |
address, pitch, width, height, depth |
| Verbose flag | BOOT_VERBOSE_FLAG_ADDR |
set by the F8 menu |
The addresses are defined once, in include/memlayout.h and include/schboot.h.
The kernel does not care which firmware produced them.
The entry point
UEFI hands a loader long mode with paging already on. Dropping back to
protected mode only to climb out again is a mode switch whose failure mode is a
triple fault with no console, so the kernel publishes an entry that takes the
machine as it already is.
kernel/arch/x86_64/entry.S carries a sch64_header — magic SCH64BOO,
8-byte aligned within the first 16 KB, followed by a 64-bit physical address —
found by scanning. include/schboot.h holds the magic so loader and kernel
cannot disagree.
The 32-bit multiboot entry is still in entry.S and now has no caller: the
BIOS loader was the only thing that used it. It is left in place rather than
removed in the same change that removed the loader, because deleting kernel
entry code is its own piece of work with its own way of failing silently.
What that entry expects:
- long mode, interrupts off
- CR3 pointing at tables that identity-map the low 4 GB and map the same
4 GB at
0xffff800000000000.entry64low's first instruction is amovabsto a high address, so identity alone triple faults immediately. - RBX = the framebuffer physical address, and never 1 — that value marks an
application processor in
entry64high.
The kernel loads its own GDT at that entry (entry64uefi), rather than the
loader doing it. A loader arriving in long mode is still on the firmware's
descriptors, and the first thing the kernel does that depends on its own
selectors takes a #GP against a GDT the firmware no longer owns — it
surfaced as unexpected trap 13 ... cs=38, a long way from the cause.
e820.c reads a BIOS memory map. UEFI hands out a different structure
(EFI_MEMORY_DESCRIPTOR, from GetMemoryMap), so the UEFI loader has to
translate it into the same 24-byte entries rather than inventing a second
format for the kernel to learn.
What booting looks like now
make esp stages EFI/BOOT/BOOTX64.EFI and schos/kernel, and QEMU is
pointed at OVMF with that directory as a virtual FAT volume — make qemu.
make iso builds a disc with a single El Torito entry pointing at a FAT image
holding the same two files.
Neither is an installable disk. A real GPT image with a real FAT partition is the installer's job, and until it exists there is nothing here that writes to a disk a machine could boot from unaided.
Secure Boot is not addressed either: the loader is unsigned, so a machine enforcing it will refuse to run this.