| Filename | Latest commit message | Latest commit date |
|---|---|---|
Two reports from an Asus TUF FA607NU, which now gets as far as the splash: the progress bar does not move, and after a resolution change the screen keeps the background colour with no mark and no bar. Both point at the same code, and it is code this loader did not have a week ago. **Modes were being matched on every frame.** mirrors_match() ran from inside mirrors_show(), so every menu redraw and every segment of the kernel load asked another display adapter to change mode. On QEMU that is instant and idempotent. On real hardware a mode set can take the best part of a second, can blank a panel while it happens, and -- on a laptop -- is quite often being asked of the *same* panel reached through a second GOP. A loader that does that in the middle of drawing a progress bar is a loader that shows a background colour and then waits on a firmware call that is not coming back. It now happens exactly once, at discovery, before anything is drawn. The second head is centred and cropped afterwards rather than re-moded, and the booted system matches resolutions anyway because the kernel driver and vgpud both follow the console. **And mirroring no longer runs per segment of the kernel load.** Copying a megabyte-and-a-half framebuffer to every other adapter, three times, to animate a bar on a screen nobody is looking at. **One refusal now turns it off for the rest of the boot**, and `\schos\nomirror` -- an empty file whose only job is to exist -- turns it off before it starts. There is no console to ask a question on at that point and no menu to reach if the menu is what is broken, so the answer has to already be on the volume. That also makes it a one-file experiment on hardware we cannot instrument. **The loader names its stages on screen.** After the splash, print() goes to the serial port and nowhere else, so on a machine with no serial port a stall leaves a progress bar and no information at all. Every stage after the kernel load now labels itself under the bar -- Reading the console font, Reading the memory map, Leaving the firmware -- so the last thing on the screen names the call that did not return. Past ExitBootServices there is nothing left to report with, which is itself worth knowing: a machine stopped on the last label stopped in the jump. Opening the boot volume moved ahead of the display, because what is on the volume now decides some of what the display does. Both supported configurations: one boot each, no traps, no panics, socktest and memfdtest green. Mirroring still verified end to end -- F8, a resolution change, boot, and both heads byte-identical at the new mode. 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.