74 lines
3.6 KiB
Markdown
74 lines
3.6 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: project
|
|
---
|
|
|
|
**`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.
|
|
|
|
## Open bug: the memory map
|
|
|
|
`e820_init` takes the usable run **containing the kernel**. Under OVMF that run
|
|
ends at 8 MB — the firmware places an ACPI NVS region at 0x800000, so it is
|
|
genuinely fragmented. The loader's map is truthful and untruncated (21 entries);
|
|
the one-contiguous-run assumption is BIOS-shaped.
|
|
|
|
**Fix: have the kernel free each usable region rather than one range.** That
|
|
touches `kalloc`/`freerange` and `kinit1`, which is the most safety-critical
|
|
code in the tree — it deserves its own change with both boot paths verified,
|
|
not the tail of a session.
|
|
|
|
## 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.
|