memory: UEFI reaches userspace, and the ISO grows a hierarchy

Records the four bugs between "boots to cpu0: starting" and "boots to an
interactive shell", the new ioremap rule for device registers, and what
the disc now contains -- including that it is still not a live CD, and
what that would take.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-09 22:04:12 +02:00
commit b428a50145
4 changed files with 121 additions and 21 deletions

View file

@ -7,7 +7,8 @@
- [Trusting bootcheck](schos-bootcheck-trust.md) - it silently stopped running; count the [ok] lines, never just grep FAIL. - [Trusting bootcheck](schos-bootcheck-trust.md) - it silently stopped running; count the [ok] lines, never just grep FAIL.
- [schinit](schos-schinit.md) - process 1 is launchd-shaped; job manifests, and why setsid() cost the shell its terminal. - [schinit](schos-schinit.md) - process 1 is launchd-shaped; job manifests, and why setsid() cost the shell its terminal.
- [BIOS/UEFI split](schos-boot-split.md) - boot/bios and boot/uefi, and the hand-off contract a second loader must satisfy. - [BIOS/UEFI split](schos-boot-split.md) - boot/bios and boot/uefi, and the hand-off contract a second loader must satisfy.
- [UEFI loader](schos-uefi-loader.md) - it boots under OVMF; the SCH64 entry, the GDT trap, and the open e820 fragmentation bug. - [UEFI loader](schos-uefi-loader.md) - it boots to an interactive shell now; the SCH64 entry, the GDT trap, and the four bugs that stood in the way.
- [ioremap and BARs](schunix-ioremap-and-bars.md) - never P2V a BAR; firmware puts 64-bit BARs far above the direct map.
- [Schunix build environment](schunix-build-environment.md) — builds natively on Windows with clang/lld + mingw + Git Bash; WSL is gone, and the five latent bugs the move exposed. - [Schunix build environment](schunix-build-environment.md) — builds natively on Windows with clang/lld + mingw + Git Bash; WSL is gone, and the five latent bugs the move exposed.
- [Syscalls: one table](schunix-syscall-single-table.md) — declare in kernel/sys/table.inc only; linux_abi.c is generated, never hand-edited. - [Syscalls: one table](schunix-syscall-single-table.md) — declare in kernel/sys/table.inc only; linux_abi.c is generated, never hand-edited.
- [Linux ABI decision](schunix-linux-abi-decision.md) — the native syscall ABI is being dropped; Linux numbering becomes the only ABI. - [Linux ABI decision](schunix-linux-abi-decision.md) — the native syscall ABI is being dropped; Linux numbering becomes the only ABI.

View file

@ -4,7 +4,7 @@ description: "boot/uefi works under OVMF; the SCH64 entry, the GDT trap, and the
metadata: metadata:
node_type: memory node_type: memory
originSessionId: d4821aa9-2e49-40da-91e7-90ef9ed9997e originSessionId: d4821aa9-2e49-40da-91e7-90ef9ed9997e
modified: 2026-08-09T19:05:15.325Z modified: 2026-08-09T20:03:43.364Z
--- ---
**`boot/uefi/` boots.** `make uefi`, `make esp`, `make qemu-uefi`. Verified **`boot/uefi/` boots.** `make uefi`, `make esp`, `make qemu-uefi`. Verified
@ -68,22 +68,37 @@ alternating rounds): boot to prompt **14.8 s vs 21.2 s**, and a cold
0.3 s. So if the allocator ever looks slow again, this is the baseline to 0.3 s. So if the allocator ever looks slow again, this is the baseline to
compare against. compare against.
## What still stops a full UEFI boot ## UEFI reaches userspace — 2026-08-09
Two separate, pre-existing problems — neither is the memory map, and both **`make iso` produces a disc that boots under OVMF all the way to an
predate the e820 work (the unfixed kernel triple faults in the same place): 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.
1. **virtio-gpu's BAR lands above 4 GB.** OVMF assigns the 64-bit BAR high; Four bugs stood between here and there. Three were pre-existing and invisible
the first MMIO write faults with `CR2` around `0xffff80c000000014`, which is purely because this path had never got far enough to expose them.
`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 1. **virtio-gpu's BAR lands above 4 GB.** OVMF puts the 64-bit BAR at about
memory map rather than a fabricated one. 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 ## Testing

View file

@ -0,0 +1,44 @@
---
name: schunix-ioremap-and-bars
description: "ioremap() maps device registers; never use P2V on a BAR, because firmware may place a 64-bit BAR far above the 4 GB direct map"
metadata:
node_type: memory
type: project
originSessionId: d4821aa9-2e49-40da-91e7-90ef9ed9997e
modified: 2026-08-09T20:03:56.063Z
---
**Never reach a device's registers with `P2V(bar.base)`.** Use
`ioremap(pa, size)` from `kernel/arch/x86_64/vm.c` (declared in `include/defs.h`).
Every driver used to do the `P2V` thing, which silently assumes the BAR lands
inside the 0-4 GB that `kvmalloc()` direct-maps with four 1 GB pages. Under
SeaBIOS it always does. Under OVMF it does not — virtio-gpu's 64-bit BAR came
back at about **768 GB**, `P2V` produced an address with no translation behind
it, and the first register write page faulted, then double faulted, then triple
faulted into a reboot loop. Coming out of a driver bind, that reads as broken
hardware rather than a missing mapping.
## How it works
PDPT entries **4-511 were unused**: the direct map occupies 0-3, and `kpml4[0]`,
`kpml4[KERNBASE]` and every process's PML4 (see `setupkvm`) all point at that
same table. So each MMIO region takes one 1 GB page there — no page table to
allocate, and the mapping appears in every address space at once. Entries are
`PTE_PCD | PTE_PWT` for the same reason the top GB of the direct map is: these
are registers, and a write sitting in a cache line is a write the device never
sees.
**A physical address below 4 GB is returned unchanged, as `P2V` would have.**
That is what makes the BIOS path — where every BAR is low — provably identical
rather than merely tested.
## Where this bites next
Converted: virtio-gpu, e1000, xhci. xHCI had already been *refusing* to bind
above 4 GB with a comment saying the mapping did not exist yet.
Any new driver that reads a BAR needs the same treatment. `grep -rn 'P2V(d->bar'
kernel/` should stay empty.
See [[schos-uefi-loader]] for the boot path this unblocked.

View file

@ -5,7 +5,7 @@ metadata:
node_type: memory node_type: memory
type: project type: project
originSessionId: a665e14b-86bd-4b41-a228-51377f3e9225 originSessionId: a665e14b-86bd-4b41-a228-51377f3e9225
modified: 2026-08-04T17:28:16.714Z modified: 2026-08-09T20:03:23.603Z
--- ---
**`make iso` produces a bootable `schunix.iso`** (added 2026-08-04). Always a **`make iso` produces a bootable `schunix.iso`** (added 2026-08-04). Always a
@ -45,5 +45,45 @@ message on a machine with no ATA disk: `waitdisk()` in stage2main.c and
empty channel, and anything that *probes* a disk must call empty channel, and anything that *probes* a disk must call
`ide_disk_present()` first — `iderw()` rightly panics on an absent drive. `ide_disk_present()` first — `iderw()` rightly panics on an absent drive.
Check it with `pwsh -File tools/bootcheck.ps1 -Iso -Fb`, which boots with no Check it with `pwsh -File tools/windows/bootcheck.ps1 -Iso -Fb`, which boots
hard disk attached at all. See [[schunix-verify-by-driving-shell]]. with no hard disk attached at all. See [[schunix-verify-by-driving-shell]].
## 2026-08-09: two boot entries, a real hierarchy, and a repair
The disc used to hold exactly one file (`boot/schos.bin`) with 8.3 uppercase
names and a single El Torito entry. Now:
| | |
|---|---|
| `/boot/schos.bin` | BIOS entry, unchanged |
| `/boot/efiboot.img` | UEFI entry: FAT image built with mtools |
| `/EFI/BOOT/BOOTX64.EFI` | the loader, also a visible file |
| `/bin /etc /usr ...` | the staged sysroot, as an ordinary tree |
- **Rock Ridge + Joliet** for real names, permissions and symlinks. **Keep
`-cache-inodes`**: the sysroot is a toybox multicall binary with ~240 hard
links to it, and without inode caching each link becomes its own copy —
28 MB becomes ~200 MB.
- **`-isohybrid-gpt-basdat`** so `dd` to a stick boots under UEFI. UEFI *only*,
and not an oversight: BIOS isohybrid needs syslinux's `isohdpfx.bin`, and even
with it this loader reads the kernel over ATAPI, which a stick presented as a
hard disk cannot serve. `usbstick.img` is the BIOS-from-USB answer.
- **The FAT image carries the kernel too.** `BOOTX64.EFI` opens `\schos\kernel`
on the volume it was loaded from, which for a UEFI boot is that FAT image and
not the ISO 9660 tree. Without it the loader halts with `EFI_NOT_FOUND`. The
kernel is therefore on the disc twice; the alternative is teaching the loader
to search other volumes, which is a feature, not a packaging tweak.
**`make iso` and `make ramroot` had been broken and nobody noticed.**
`build/fs-ram.img` was still built by the v1 `mkfs` while the kernel accepts
only SchFS as root, so the ramdisk was a format it could not mount:
`schfs root: no SchFS volume on the root device`, in forkret. It is an mkschfs
image now, which needed **`--exclude` added to mkschfs** — dropping the exclude
list was not an option, because the ramdisk is linked into the kernel binary and
what goes into it is a licensing question ([[schunix-ramdisk-root-sizing]]).
`RAMDISK_NINODES` is gone: SchFS has no preallocated inode table.
**Not a live CD.** The disc boots from the ramdisk embedded in the kernel, not
from itself. Mounting it would need an ATAPI block driver (`kernel/dev/ide.c`
speaks only ATA — 0x20/0x30/0xC4, no PACKET) plus an ISO 9660 filesystem behind
`struct fs_ops`. Only the *bootloader* does ATAPI, in real mode.