Records the measured effect of the region-based allocator (2.2x on a cold ls, 6 s off the boot), the two pre-existing UEFI blockers found while verifying it, and the three confounds that each produced a confident wrong answer about guest speed in one session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.3 KiB
| name | description | metadata | ||||||
|---|---|---|---|---|---|---|---|---|
| schos-uefi-loader | boot/uefi works under OVMF; the SCH64 entry, the GDT trap, and the one open bug (e820 fragmentation) |
|
boot/uefi/ boots. make uefi, make esp, make qemu-uefi. Verified
under OVMF 2026-08-09: kernel loaded, PCI/VGA/e1000/RTC up, reaches
cpu0: starting.
Written from the UEFI specification, not gnu-efi or EDK II — a large dependency
for ~200 lines of use, and a licensing question (schunix-licensing) the
project does not need. clang emits PE/COFF directly
(--target=x86_64-unknown-windows, lld-link -subsystem:efi_application), so
no new toolchain. -mno-red-zone is mandatory: firmware can interrupt at
any point and would clobber the red zone.
The second kernel entry
The multiboot entry is 32-bit code that climbs into long mode itself. UEFI
hands over long mode already, and dropping back down to climb out again is a
mode switch whose failure mode is a triple fault with no console. So entry.S
publishes a 64-bit entry the same way it publishes the multiboot header: magic
SCH64BOO, 8-byte aligned in the first 16 KB, then a 64-bit physical address.
include/schboot.h owns the constants so loader and kernel cannot disagree —
the first attempt defined them twice and searched for SCH6 while the
assembler had written 6HCS.
Put that header after the multiboot header, never before. header_addr
claims the multiboot header sits at mboot_load_addr, and the BIOS loader
computes the kernel's file offset from (header_addr - load_addr). Bytes
inserted ahead of it load the kernel shifted, which fails as a triple fault.
Entry expects: long mode, interrupts off; CR3 mapping the low 4 GB and the
same 4 GB at 0xffff800000000000 (entry64low's first instruction is a movabs
to a high address — identity alone triple faults); RBX = framebuffer physical
address, never 1 (that marks an AP).
The kernel loads its own GDT there (entry64uefi), not the loader. A loader
arriving in long mode is still on the firmware's descriptors; the first thing
needing the kernel's own selectors faulted with unexpected trap 13 ... cs=38,
a long way from the cause.
The memory map — fixed 2026-08-09
e820_init used to take the usable run containing the kernel, which under
OVMF ends at 8 MB because the firmware puts an ACPI NVS region at 0x800000.
That failed the PHYSTOP_MIN check, so the kernel fell back to asserting
224 MB it had not measured and then freed straight through the reserved hole.
The kernel now keeps the usable regions as a list (mem_usable[] /
nmem_usable in memlayout.h) as well as a top address, and kinit1 frees each
one clipped to the window main() passes in. PHYSTOP still means exactly what
it did, so no call site changed. phystop comes from the largest region,
not the highest, because the DMA arena is carved off the top of it and must be
contiguous; a smaller region above it is deliberately left unfreed rather than
raising the bound past a hole.
Result: 1018 MB in 7 regions under OVMF, 1023 MB in 2 under BIOS.
It is also a large speedup, which was not the goal and is worth knowing.
Measured from inside the guest (/proc/uptime, fresh image each boot,
alternating rounds): boot to prompt 14.8 s vs 21.2 s, and a cold
ls /bin | wc -l 23.6 s vs 51.2 s — 2.2x. Both rounds agreed within
0.3 s. So if the allocator ever looks slow again, this is the baseline to
compare against.
What still stops a full UEFI boot
Two separate, pre-existing problems — neither is the memory map, and both predate the e820 work (the unfixed kernel triple faults in the same place):
- virtio-gpu's BAR lands above 4 GB. OVMF assigns the 64-bit BAR high;
the first MMIO write faults with
CR2around0xffff80c000000014, which isP2Vof physical 768 GB, far outside the 4 GB direct mapkvmallocbuilds from four 1 GB pages. Then a double fault, then a triple fault and a reboot loop. SeaBIOS assigns it low, so the BIOS path never sees it. Drop-device virtio-gpu-pcito boot UEFI past this. - Boot stalls after
cpu0: starting, beforeschfs: mounted. Under UEFI the ESP is hd0 and schfs.img is hd1; the root never mounts. Not diagnosed.
cpu0: starting remains the UEFI high-water mark, now reached with a truthful
memory map rather than a fabricated one.
Testing
QEMU ships the firmware: share/edk2-x86_64-code.fd plus
edk2-i386-vars.fd as the varstore, copied because it must be writable
(a copy on Windows inherits the read-only attribute — "Block node is
read-only").
- i440fx, not q35. The kernel's IDE driver speaks the legacy ports at 0x1f0; q35 has only AHCI, so the root volume would not be there at all.
-drive file=fat:rw:build/espserves a directory as FAT, so no mtools is needed. Do not addformat=raw— it conflicts with thefat:driver and fails with "Block node is read-only".- OVMF drops to its shell rather than auto-booting, so the ESP carries
startup.nsh. It is a file (boot/uefi/startup.nsh), not generated byprintf: printf reads\Eas ESC and silently corrupts the path.
Still to do: a real FAT ESP image for make iso, so the ISO carries an El
Torito EFI entry beside the BIOS one.