Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
6.4 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/arch/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.
A "2.2x speedup" was claimed for this change on 2026-08-09. It was wrong,
and the retraction matters more than the claim did. The measurement sent a
command, slept a fixed 50 s on the host, and then read /proc/uptime — so it
timed the sleep, not the command, through a guest clock that drifts under load.
Correctly measured (see schos-bootcheck-trust), ls /bin | wc -l is 2.9 s
under TCG, 1.1 s under KVM, and the region-based allocator has no measurable
effect on it either way. The change is still right; it simply is not a
performance change.
UEFI reaches userspace — 2026-08-09
make iso produces a disc that boots under OVMF all the way to an
interactive shell, verified by driving it over serial: uname -a, 241
binaries in /bin, write-then-read a file, ps, /proc/schfs. The old
cpu0: starting ceiling is gone.
Four bugs stood between here and there. Three were pre-existing and invisible purely because this path had never got far enough to expose them.
- virtio-gpu's BAR lands above 4 GB. OVMF puts the 64-bit BAR at about
768 GB;
P2Vof that has no translation, so the first register write page faulted, then double, then triple, then a reboot loop. Every driver didP2V(bar.base). Fixed by a realioremap()— see schunix-ioremap-and-bars. EFER.SCEwas never set on the UEFI entry. The 32-bit entry sets it while climbing into long mode;entry64uefiskips that bootstrap because firmware already switched modes, and UEFI has no reason to set SCE. The firstsysretqraised #UD in pid 1 at zero ticks,ripinsyscall_trapret.- The RSDP is not where a BIOS leaves it.
find_rsdp()scanned the EBDA and0xE0000; OVMF publishes it in the EFI configuration table only. No RSDP means no MADT, no IOAPIC, no IRQ routing — and since serial output is polled, the machine reached a prompt and then ignored every keystroke, which looks like a broken console. The loader now passes it atSCHBOOT_RSDP_ADDR. Do not require 16-byte alignment when validating it: that rule is about scanning the BIOS area, and OVMF's sits at0x3f77e014. - The ramdisk was a v1 image the kernel could not mount — see schunix-iso-boot.
Still true: the loader prints could not reserve warnings for kernel segments
the firmware already owns, and writes there anyway. It has not bitten yet.
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.