`make ramroot` wiped the objects on the way in, and nothing wiped on the way out. So a plain `make` afterwards kept every RAMDISK_ROOT object and linked the 24 MB ramdisk back into a kernel that no longer used it: 25 MB where a clean build is 836 KB. It still booted -- from the ramdisk -- and the only outward symptom was stage2 having thirty times as much to load through PIO, one sector at a time. That is the "booting got slower" report this came from. A stamp file records which setting the objects were built with, and the tree is dropped when it changes in either direction. qemu-kvm is opt-in and the default stays TCG, because the suite's timings were established there. Measured with the guest timing itself, `ls /bin | wc -l` goes 2.9s -> 1.1s. Boot does not improve and gets slightly worse: every PIO port access in stage2 is a VM exit under KVM. Retracts a claim from earlier today. The "2.2x speedup" attributed to the region-based allocator was a measurement artifact -- the probe sent a command, slept a fixed 50s on the host, then read /proc/uptime, so it timed the sleep rather than the command, through a guest clock that drifts under load. Bracketing the command inside one guest command line shows no measurable difference. The e820 change is still correct; it is simply not a performance change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
122 lines
6.4 KiB
Markdown
122 lines
6.4 KiB
Markdown
---
|
|
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-09T20:44:02.893Z
|
|
---
|
|
|
|
**`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.
|
|
|
|
**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.
|
|
|
|
1. **virtio-gpu's BAR lands above 4 GB.** OVMF puts the 64-bit BAR at about
|
|
768 GB; `P2V` of that has no translation, so the first register write page
|
|
faulted, then double, then triple, then a reboot loop. Every driver did
|
|
`P2V(bar.base)`. Fixed by a real `ioremap()` — see
|
|
[[schunix-ioremap-and-bars]].
|
|
2. **`EFER.SCE` was never set on the UEFI entry.** The 32-bit entry sets it
|
|
while climbing into long mode; `entry64uefi` skips that bootstrap because
|
|
firmware already switched modes, and UEFI has no reason to set SCE. The first
|
|
`sysretq` raised **#UD in pid 1 at zero ticks**, `rip` in `syscall_trapret`.
|
|
3. **The RSDP is not where a BIOS leaves it.** `find_rsdp()` scanned the EBDA
|
|
and `0xE0000`; 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 at `SCHBOOT_RSDP_ADDR`.
|
|
**Do not require 16-byte alignment** when validating it: that rule is about
|
|
scanning the BIOS area, and OVMF's sits at `0x3f77e014`.
|
|
4. **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/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.
|