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:
parent
9735279970
commit
b428a50145
4 changed files with 121 additions and 21 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
44
.claude/memory/schunix-ioremap-and-bars.md
Normal file
44
.claude/memory/schunix-ioremap-and-bars.md
Normal 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.
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue