| Filename | Latest commit message | Latest commit date |
|---|---|---|
"Leaving the firmware, and the bar is full" -- so on the laptop
ExitBootServices was reached, and everything after it was silent. die() draws
on screen, so a failed ExitBootServices would have said so; it did not, which
means the firmware call succeeded and the machine stopped somewhere in the
handful of instructions between there and the kernel's first.
That window had no diagnostics because of a claim in a comment: "nothing below
can report anything". Half true. ConOut is gone, but the *framebuffer* is not
-- it is memory, the firmware has stopped using it, and ui_progress_label() is
a write to it and nothing more. So the window is now split: "Starting the
kernel" is drawn after the page tables are built and immediately before the
jump. A machine that stops on "Leaving the firmware" stopped in
ExitBootServices or the page-table build; one that stops on "Starting the
kernel" stopped in the jump or in the kernel itself, before it clears the
screen.
**And one speculative change, reverted.** On the theory that hardcoded low
memory is a poor assumption under UEFI -- the firmware is entitled to have
anything at 0x1000 -- the page tables were moved to pages asked of
AllocatePages. It cost two triple faults: cr3 still loaded the constant, and
then the PML4 entries still pointed at the constant PDPT. Both were fixed, and
it still reboot-looped, which is when the actual objection turned up:
kernel/arch/x86_64/entry.S:85 # bootstrap page tables (0x1000 - 0x3000)
kernel/arch/x86_64/entry.S:111 mov $0x1000, %eax
mov %eax, %cr3
Those addresses are a *contract between the loader and the kernel's assembly*,
not this file's private choice. Moving them in one of the two places was never
going to work, and the theory that started it had no evidence behind it -- no
report says 0x1000 is the laptop's problem. So it is reverted, with the
contract written down where the constants are, and if it ever does move it
moves in both files at once.
Both supported configurations: one boot each, no traps, no panics, socktest
and memfdtest green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
| .. | ||
| common | ||
| uefi | ||
| README.md | ||
Booting SchOS
One firmware path:
boot/
uefi/ the loader: a 64-bit PE application
common/ the user interface, shared with nothing else now
There were two. boot/bios/ held an MBR, an El Torito CD stub and a real-mode
stage 2, and it was a separate directory rather than #ifdefs because the two
shared almost nothing: a BIOS loader lives in 16-bit real mode and talks to the
firmware through software interrupts, while UEFI hands a loader long mode with
paging already on and a table of function pointers to call.
That asymmetry is what removed it. Everything since roughly 2012 boots UEFI,
CSM — the firmware-side BIOS emulation that let legacy loaders run at all — has
been going away since Intel dropped support for it in 2020, and the cost of the
second path was not the code but the tax on everything that touched it: stage 2
had a 65-sector ceiling that it silently overflowed, and the resolution menu
could not be shared because VBE mode setting is INT 10h and needs real mode.
The contract below survives it. It was written to let a second loader exist, and it is worth keeping in that shape: it is what the kernel expects, stated once, rather than a description of whoever happens to be calling.
What the loader must hand the kernel
Whichever path runs, the kernel expects the same things. This is the contract; a second loader is correct exactly when it satisfies it.
| Where | Notes | |
|---|---|---|
| Kernel image | loaded per its ELF program headers | entry at entry64 |
| CPU state | see below -- the two paths differ here | |
| Memory map | E820_MAP_ADDR, 24-byte entries, count at E820_COUNT_ADDR |
kernel/arch/x86_64/e820.c reads it |
| Framebuffer | VBE_INFO_ADDR |
address, pitch, width, height, depth |
| Verbose flag | BOOT_VERBOSE_FLAG_ADDR |
set by the F8 menu |
The addresses are defined once, in include/memlayout.h and include/schboot.h.
The kernel does not care which firmware produced them.
The entry point
UEFI hands a loader long mode with paging already on. Dropping back to
protected mode only to climb out again is a mode switch whose failure mode is a
triple fault with no console, so the kernel publishes an entry that takes the
machine as it already is.
kernel/arch/x86_64/entry.S carries a sch64_header — magic SCH64BOO,
8-byte aligned within the first 16 KB, followed by a 64-bit physical address —
found by scanning. include/schboot.h holds the magic so loader and kernel
cannot disagree.
The 32-bit multiboot entry is still in entry.S and now has no caller: the
BIOS loader was the only thing that used it. It is left in place rather than
removed in the same change that removed the loader, because deleting kernel
entry code is its own piece of work with its own way of failing silently.
What that entry expects:
- long mode, interrupts off
- CR3 pointing at tables that identity-map the low 4 GB and map the same
4 GB at
0xffff800000000000.entry64low's first instruction is amovabsto a high address, so identity alone triple faults immediately. - RBX = the framebuffer physical address, and never 1 — that value marks an
application processor in
entry64high.
The kernel loads its own GDT at that entry (entry64uefi), rather than the
loader doing it. A loader arriving in long mode is still on the firmware's
descriptors, and the first thing the kernel does that depends on its own
selectors takes a #GP against a GDT the firmware no longer owns — it
surfaced as unexpected trap 13 ... cs=38, a long way from the cause.
e820.c reads a BIOS memory map. UEFI hands out a different structure
(EFI_MEMORY_DESCRIPTOR, from GetMemoryMap), so the UEFI loader has to
translate it into the same 24-byte entries rather than inventing a second
format for the kernel to learn.
What booting looks like now
make esp stages EFI/BOOT/BOOTX64.EFI and schos/kernel, and QEMU is
pointed at OVMF with that directory as a virtual FAT volume — make qemu.
make iso builds a disc with a single El Torito entry pointing at a FAT image
holding the same two files.
Neither is an installable disk. A real GPT image with a real FAT partition is the installer's job, and until it exists there is nothing here that writes to a disk a machine could boot from unaided.
Secure Boot is not addressed either: the loader is unsigned, so a machine enforcing it will refuse to run this.