SchOS/.claude/memory/schunix-boot-splash-logo.md
Nexoniarz 08a1a9033a boot: separate BIOS from UEFI
boot/bios/ now holds the MBR, the El Torito stub and the real-mode stage
2; boot/uefi/ is empty and documented.

Separate directories rather than #ifdefs, because the two share almost
nothing. A BIOS loader lives in 16-bit real mode, reaches the firmware
through software interrupts, and walks itself up into long mode. A UEFI
loader is a 64-bit PE that the firmware has already placed in long mode
with paging on, calling services through a table of function pointers.
What they do share is the hand-off contract, which boot/README.md now
writes down: ELF segments placed, long mode, a 24-byte-entry memory map
at E820_MAP_ADDR, framebuffer geometry at VBE_INFO_ADDR.

That contract is the point of documenting it now. UEFI's GetMemoryMap
returns EFI_MEMORY_DESCRIPTORs, and the right move is to translate them
into the entries kernel/arch/x86_64/e820.c already parses rather than
teach the kernel a second format.

Build output paths under $(BUILD)/boot are unchanged; only sources moved.
2026-08-09 16:00:03 +02:00

3.6 KiB

name description metadata
schunix-boot-splash-logo The boot logo pipeline, why the loader draws it rather than the kernel, and the STAGE2_SECTORS trap that consolidating it exposed.
node_type type originSessionId modified
memory project 9d8a2aef-bcf0-47a0-96c6-f706efab4a95 2026-08-08T12:04:49.027Z

The boot splash shows the Assets/Schunix Small Logo.png mark (an SX monogram in a ring) above the S C H U N I X wordmark and its accent rule, all centred as one group. Verified rendering correctly on 2026-08-08.

The logo is drawn by the bootloader, not the kernel. This is the counter-intuitive part and it is a timing fact, not a preference: the kernel does not reach vga_init() until roughly the last 300ms of a ~2.8s boot, so a kernel-drawn logo appeared for about one frame before console_takeover() wiped it. Drawn from splash() in boot/bios/stage2main.c it is up for the whole boot. kernel/dev/vga.c keeps the same drawing code against the same mask (vga_draw_logo, vga_logo_centered) for things that want the mark after boot — the crash screen, eventually schinit. Don't "fix" the duplication by deleting the loader's copy.

Pipeline: tools/mklogo.py (host Python, has its own PNG inflate + un-filter) crops to the mark, box-downscales, normalises to full strength, packs to a 4-bit coverage mask, and writes include/logo.h. Run make logo — it is not a build dependency, and include/logo.h is committed, on the same reasoning as include/font.h. The mask is coverage, not colour, so the palette stays in schunix-console-tty's include/theme.h; the loader composites it in SCH_FG because the source art is a silver gradient and the accent is reserved for the one rule. 4 bits rather than 8 because the header is included by the bootloader, where size is a boot constraint.

STAGE2_SECTORS was encoded in five places, not the three docs/layout.md claimed. Raising it 24 → 48 to fit the 8 KiB mask proved it: the dd seek=25 offset stayed behind and the kernel got written straight over stage2's tail. The five were boot/bios/bootasm.S, boot/bios/stage2main.c, the dd seek=, and twice inside tools/mkiso.sh (its size assertion and -boot-load-size). It now lives only in the root Makefile, which passes -DSTAGE2_SECTORS and exports it to mkiso; the #defines left in the boot sources are #ifndef fallbacks. Hard ceiling is 65 sectors — stage2 loads at 0x7E00 and must not reach the multiboot kernel-search buffer at 0x10000 — and stage2main.c now static-asserts that. See also schunix-iso-boot.

tools/splashshot.ps1 captures a burst of QEMU screendumps across early boot and reports each frame's distinct-colour count, because the splash window is too short to hit with a single guessed delay. It uses -snapshot plus file.locking=off so it runs while an interactive QEMU has the images open — booting from copies also works but means duplicating all 153 MB of build/fs.img per run. Colour counts are the assertion: ~2 means text console or blank, 18 means the 4bpp logo is up. If it ever reads 156 again, something is drawing an 8-bit mask.

Two traps, both hit while doing this:

  • $(shell expr $(N) \* 512) in a Makefile does not work — make passes the backslash through, sh globs the *, and expr receives a directory listing. Make silently carries on with nonsense. Use $$(( )) in the recipe.
  • make iso was failing when this was written, for unrelated reasons since fixed — see schunix-ramdisk-root-sizing. -boot-load-size 49 is now verified: the ISO boots from optical media only.