// internals
Architecture
How ~20k lines of freestanding C become a boot menu.
Boot flow
firmware ─▶ efi_main (src/main.c)
│ init gnu-efi, open boot.log
├─ GUI init: locate GOP, pick mode, allocate back buffer,
│ make framebuffer write-combining (arch_*)
├─ load EfiFs drivers from \EFI\visor\drivers (deferred start)
├─ config_parse(\EFI\visor\boot.conf)
│ └─ missing? detect_entries(): BLS ▸ Windows ▸ UKIs ▸
│ raw kernels (+ cmdline derivation) ▸ vendor loaders
├─ load background, decode icons, arm hotplug watch
├─ gui_run(): event loop — input, animation, mouse,
│ hotplug poll, timeout, editor, snapshot panel
▼
entry selected
│ (password prompt if encrypted/LUKS)
├─ visor_boot (src/linux_boot.c)
│ ├─ read image (uuid-pinned volume first)
│ ├─ VISORENC decrypt + HMAC verify, sha256= pin check,
│ │ SHIM_LOCK verify under Secure Boot
│ ├─ PE image ─▶ LoadImage(device path) ─▶ cmdline via
│ │ LoadOptions, initrd via LINUX_EFI_INITRD_MEDIA
│ │ (LoadFile2), LUKS keyfile cpio appended ─▶ StartImage
│ └─ raw kernel ─▶ x86-64 EFI handover protocol
▼
control transfers — or on failure: recovery console
Module map (src/)
| File | Role |
|---|---|
main.c | Entry point and orchestration: init, config, GUI loop, password capture, boot dispatch, recovery console. |
config.c | boot.conf parser, theme loading, BLS parsing, entry auto-detection, cmdline derivation (UKI section / fstab / GPT), snapshot sources, live hotplug scan/poll. |
gui.c | The renderer: back buffer, partial-redraw bands, text, PNG icons, blur, animations (selection, page flip, fades, hotplug pop-in/out), mouse, editor overlay, snapshot/version panels, scene cache. |
text_menu.c | Text-mode menu with the same feature set minus pixels. |
linux_boot.c | Boot engine: file loading, decryption hooks, verification, PE LoadImage/StartImage path, LoadFile2 initrd serving, LUKS keyfile injection, x86-64 EFI handover, Windows chainload. |
windows_boot.c | bootmgfw.efi discovery across volumes. |
efi_helpers.c | Wrappers over UEFI protocols: file I/O (uuid-pinned volume resolution), device paths, handle/partition GUID matching, logging, Secure Boot state, pool helpers. |
crypto.c | VISORENC v2: PBKDF2-HMAC-SHA256, ChaCha20, HMAC verification, secure wipe. |
sha256.c / hash_verify.c | SHA-256 and the sha256= image pin. |
png_decoder.c | Self-contained PNG (and BMP) decoder with decode budgets. |
accent.c | Material You palette extraction: Celebi quantizer, HCT/CAM16 color space, scoring, palette variants — all Q16 fixed-point. |
font_jetbrains.c | Pre-baked JetBrains Mono glyph bitmaps (generated by tools/bake_font.py). |
arch_x86_64.c / arch_aarch64.c | Architecture layer behind include/arch.h: monotonic clock (calibrated TSC / generic timer) and framebuffer write-combining (MTRR+PAT / EFI CPU protocol). |
include/ | Public headers; gui.h holds the central boot_entry_t and gui_state_t types. |
The rendering pipeline
Everything draws into a full-screen 32-bit back buffer, then blits to the framebuffer. On x86_64 the framebuffer is mapped write-combining via MTRR and a PAT slot; on AArch64 via the EFI CPU Architecture Protocol — this is the difference between a 2 ms and a 200 ms present on real hardware. Redraws are banded: only the horizontal strips that changed (icon row, title, info line) are recomposed and blitted each frame. Static pixels (background, title, power actions) live in a scene cache. Animations interpolate a small set of scalar targets (card position, underline, page cross-fade, hotplug offsets) with a smoothstep easing, at integer millisecond timing from the arch clock.
Firmware constraints the code lives under
- No floating point, no libm. Firmware may not save FP state; everything (easing, color science, scaling) is integer or Q16 fixed-point.
- Only RELATIVE relocations. The PE conversion only survives
R_X86_64_RELATIVE/R_AARCH64_RELATIVE; anything else (e.g. from hidden float literals or certain initializers) breaks the binary. The build checks this. - ASCII-only font — the baked glyph set covers ASCII; all UI text stays inside it.
- Strict alignment on AArch64 (
-mstrict-align): packed structures like device-path nodes are read viaCopyMem, never cast-and-dereferenced. - Freestanding: no libc; gnu-efi provides the runtime; allocations come from EFI pool memory.
- 0-warning policy:
-Wall -Wextramust be clean on both architectures.
Key design decisions
- PE first. Any PE image boots via
LoadImage/StartImagewith a full device path — maximum firmware compatibility, Secure Boot integration for free, and chainloaded loaders can find their own volume. The Linux EFI handover path exists only for genuinely raw kernels. - Initrd via LoadFile2. The
LINUX_EFI_INITRD_MEDIAvendor device path serves the initrd from memory — noinitrd=path games, and it composes with decryption and LUKS keyfile injection (both operate on the in-memory buffer). - Entries pin their partition.
uuid=(and, for hotplug, the volume handle) decide where files load from; the all-volume search is only a logged fallback. - Deferred drivers. EfiFs drivers load at startup but connect lazily, so the common case never pays btrfs's tree-walk cost.
- Everything logs. Every fallible step writes one descriptive line to
boot.log; failure lands in a recovery console, not a black screen.
Repository layout
bootloader/
├── src/ all firmware C sources + include/ + linker script
├── assets/ default icons and backgrounds (PNG)
├── tools/ host-side tools (encrypt, font baking, snapshot
│ sync, aarch64 gnu-efi setup)
├── docs/ release notes, changelog, screenshots
├── site/ this wiki
├── Makefile x86_64 + aarch64 builds
├── install.sh · get.sh · visor installer, bootstrapper, host CLI
└── boot.conf.example fully-commented config reference