// internals
Architecture
How ~20k lines of freestanding C become a boot menu.
Boot flow
firmware ─▶ efi_main (src/core/main.c)
│ arch_clock_init(), loader_mark_init() → LoaderTimeInitUSec
│ 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
├─ tpm_init() → measure boot.conf (PCR 5); publish Loader* vars
│ LoaderDevicePartUUID / LoaderImageIdentifier /
│ LoaderInfo / FirmwareInfo / LoaderFeatures /
│ LoaderEntries; apply LoaderEntryDefault/OneShot &
│ LoaderConfigTimeout overrides (consume OneShot)
├─ load background (PNG/BMP/GIF/MP4→VBG), decode icons,
│ accent_generate() from frame 0, arm hotplug watch
├─ gui_run(): event loop — input, animation inc. clock 1 Hz band,
│ mouse, hotplug poll, timeout, editor, snapshot panel,
│ screensaver (dim → blank), video frame scheduler
│ loader_mark_menu() → LoaderTimeMenuUSec
▼
entry selected
│ (password prompt if encrypted/LUKS)
├─ tpm_measure_cmdline() → PCR 12, loader_mark_selected()
│ → LoaderEntrySelected + LoaderTimeExecUSec
├─ visor_boot (src/boot/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/)
Sources are clustered by domain; modules that ship inside a feature live in
that feature's cluster and compile a no-op stub from src/stubs/
when the feature is off. Public types live in src/include/,
shared internals in *_internal.h headers next to their cluster.
| File | Role |
|---|---|
core/main.c | Entry point and orchestration: init, config, GUI loop, password capture, boot dispatch, recovery console; early loader_mark_init() and tpm_init(). |
config/config.c + config_*.c | boot.conf parser (unified colorSpec grammar, file-syntax from boot.conf.schema.json), theme loading, BLS parsing (config_bls.c), entry auto-detection (config_discover.c), cmdline derivation (UKI section / fstab / GPT, config_cmdline.c), snapshot sources, live hotplug scan/poll (config_hotplug.c), LUKS (config_luks.c), recovery (config_recovery.c), key schema (config_keys.c). |
gui/gui.c + 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 (gui_blur.c, gui_card.c, gui_draw.c, gui_fade.c, gui_image.c, gui_menu.c, gui_panels.c, gui_run.c, gui_text.c…). Live clock overlay (1 Hz band, frost/shadow, header-aware layout), screensaver state machine (dim → blank) with video-aware idle handling (gui_clock.c, gui_screensaver.c), and sound_start/sound_poll hooks so the boot sound starts with the fade-in. |
text/text_menu.c | Text-mode menu with the same feature set minus pixels (also respects Loader timeout overrides); config_gpt equivalents in text_gptcmd.c. A standalone text_recovery.c backs the text-mode recovery console. |
boot/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; plus tpm_measure_cmdline() before hand-off. File/initrd plumbing split into linux_file.c, linux_initrd.c, linux_rawboot.c. |
boot/windows_boot.c | bootmgfw.efi discovery across volumes. |
core/efi_*.c | Wrappers over UEFI protocols: file I/O (uuid-pinned volume resolution, efi_file.c), device paths (efi_path.c), handle/partition GUID matching, logging (efi_log.c), Secure Boot state, pool helpers, NVRAM vars (efi_var.c), driver loading (efi_fsdrv.c), and boot-entry self-heal (efi_selfheal.c) — plus Boot Loader Interface helpers. |
security/crypto.c | VISORENC v2: PBKDF2-HMAC-SHA256, ChaCha20, HMAC verification, secure wipe. |
security/sha256.c / hash_verify.c / luks_keyfile.c / tcg2.c | SHA-256, the sha256= image pin, LUKS keyfile injection, and TPM2 measured boot (EFI_TCG2_PROTOCOL: PCR 5 config / PCR 12 cmdline, LoaderTpm2ActivePcrBanks). |
decoders/png_decoder.c | Self-contained PNG (and BMP) decoder with decode budgets. |
decoders/gif_decoder.c | GIF89a decoder (LZW, disposal, loop handling, bounded memory) — supplies frames for animated backgrounds. |
decoders/mp4_decoder.c + vbg_decoder.c / anim.c / mjpeg_decoder.c | MJPEG MP4/MOV decoder plus VBG (tiled-quantized delta + motion compensation) — loop budget, bounded scratch, drops time on slow redraws; frame scheduling in anim.c. |
audio/hda.c | Built-in Intel HD Audio driver for the boot sound: HDA controller programming, widget walk, jack-presence detection, pin scoring (headphones before built-in speakers), muting of other output pins sharing a converter, and a split prepare/start/done playback flow. AArch64 builds compile to stubs. |
audio/menu_sound.c / rbd.c | Boot-sound PCM helpers (resample, trim, single-play prepared stream) and the RBD easter-egg audio pipeline; owns the bundled PCM audio blob. |
gpt/gpt.c + friends | GPT scan (gpt.c), diagnosis (gpt_diagnose.c), primary/backup repair (gpt_repair.c), and text-mode commands (text_gptcmd.c); core/gpt_disk.c maps whole-disk block devices. |
browse/filebrowse.c / text_browse.c | File browser (GUI / text): volume enumeration, directory merge-sort, pinned-volume loads, one-shot boot overrides. |
capture/capture.c + friends | Screenshot / F10 animation capture (capture_png.c for F6, capture_gif.c, capture_file.c). |
gui/accent.c (+ gui_accent.c) | Material You palette extraction: Celebi quantizer, HCT/CAM16 color space, scoring, palette variants — all Q16 fixed-point; includes the clock role. |
gui/font_jetbrains.c | Pre-baked JetBrains Mono glyph bitmaps (generated by tools/bake_font.py). |
arch/arch_x86_64.c / arch/arch_aarch64.c | Architecture layer behind include/arch.h: monotonic clock (calibrated TSC / generic timer, arch_clock_init / arch_now_us / efi_get_tick) and framebuffer write-combining (MTRR+PAT / EFI CPU protocol). |
stubs/ | No-op stub_*.c units swapped in for features that are off, so the build always links. |
include/ | Public headers; gui.h holds the central boot_entry_t and gui_state_t types (with clock_*, screensaver and sound_* fields); hda.h/menu_sound.h/rbd.h declare the audio API, *_internal.h the shared private headers. |
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.
The clock lives outside the scene cache as a live overlay: it repaints only its own band on a 1 Hz tick (or every second with clock_seconds=1), with header-aware placement, optional frost and shadow. The screensaver is a two-stage state machine (idle → dim → blank) that composes a saver frame (background + optional clock) and cross-fades via gui_crossfade; while blanked the decoder and scheduler are idle and the cursor backing store is dropped so no ghost remains. MJPEG/VBG video frames advance on a wall-clock budget that drops time on slow redraws; frost panels reuse the blur cache while the animation plays.
Audio
Audio runs through a small built-in Intel HD Audio driver (src/audio/hda.c). It is a two-phase job: hda_play_prepare() opens the controller and codec and begins streaming, and a separate hda_play_start() actually unmutes at the moment the boot sound should be heard, so the sound and the menu fade-in are simultaneous. hda_play_done() drains the stream and stops it once the audible end is reached; the buffer is padded with silence so DMA never wraps and replays the clip.
The driver walks the codec widget graph to find usable analog output pins. With jack-presence support it scores each pin — a plugged-in headphone outranks a fixed line-out, both outrank the built-in speaker, and a live HDMI/DP pin loses to all analog options. When two output pins share a converter, the non-selected one is muted for the duration (silence_other_outputs()) and restored in hda_cleanup(). On AArch64 the same symbols compile to no-op stubs. Visor plays before ExitBootServices, so it never touches the OS's audio state.
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/ firmware C sources, clustered by domain (arch/,
│ audio/ boot/ browse/ capture/ config/ core/,
│ decoders/ gpt/ gui/ security/ text/) + stubs/
│ + include/ + linker scripts
├── features/ feature registry (features.json) and Makefile hook
├── assets/ default icons and backgrounds (PNG)
├── tools/ host-side tools (encrypt, font baking, snapshot
│ sync, aarch64 gnu-efi setup, feature checks)
├── docs/ screenshots
├── Makefile x86_64 + aarch64 builds, profiles & features
├── install.sh · get.sh · visor installer, bootstrapper, host CLI
└── boot.conf.example fully-commented config reference