--- name: schos-uefi-loader description: "boot/uefi works under OVMF; the SCH64 entry, the GDT trap, and the one open bug (e820 fragmentation)" metadata: node_type: memory originSessionId: d4821aa9-2e49-40da-91e7-90ef9ed9997e modified: 2026-08-09T19:05:15.325Z --- **`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): 1. **virtio-gpu's BAR lands above 4 GB.** OVMF assigns the 64-bit BAR high; the first MMIO write faults with `CR2` around `0xffff80c000000014`, which is `P2V` of physical **768 GB**, far outside the 4 GB direct map `kvmalloc` builds 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-pci` to boot UEFI past this.** 2. **Boot stalls after `cpu0: starting`**, before `schfs: 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/esp` serves a directory as FAT, so no mtools is needed. **Do not add `format=raw`** — it conflicts with the `fat:` 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 by `printf`: printf reads `\E` as 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.