gfx: the console's typeface is replaceable, and the loader is what replaces it

Gate 0's last-but-one open item. Drop a TrueType file at \schos\console.ttf on
the ESP and the console is drawn with it; with no file, the face linked into
the kernel image, which is what an early panic needs and is not a degraded
mode.

**The loader reads it, and that is the decision worth writing down.** The
kernel opening the file once the root filesystem is mounted is the obvious
design. It is also wrong: the console has been drawing since the kernel's
first line, so a font arriving later changes the cell size, which changes the
column count, which re-lays out every terminal -- and blanks them. The kernel
log would vanish off the screen at the moment the root filesystem mounted,
which is exactly the log a machine being brought up for the first time is
being read for. Since the next thing on the list is real hardware, losing that
was not a trade worth making.

So the choice happens before anything is drawn. Three details make it cheap:

  - **EfiReservedMemoryType**, not EfiLoaderData. The E820 handoff turns
    loader data into usable memory, on the argument that the loader's own
    pages are the kernel's the moment it runs -- true of the loader, not true
    of a buffer it wants the kernel to keep reading from.
  - **Below 4 GB**, because the kernel reaches this through its direct map and
    that is how far the direct map goes. A font above it would need an ioremap
    the console cannot do before there is a console.
  - **Used in place.** No copy, which matters because the kernel has no
    contiguous allocator to copy 112 KB into -- the only one is the DMA arena,
    and that belongs to devices.

Sized without EFI_FILE_INFO and its GUID: seek past the end and ask where that
landed, which the specification requires to clamp.

Every failure falls back and keeps going -- no file, implausible size, no
memory, short read, unparseable file. The last says so in the log, because a
font that silently did not load looks exactly like a font that did.

Verified by booting with JetBrainsMono-ExtraBold.ttf on the ESP and
screendumping the console: visibly the bold face, against the Regular of a
boot with no file. Three boots each way, no traps and no panics, socktest and
memfdtest green in all six.

Nothing ships a console.ttf: the file would be the file already in the kernel,
and two copies of a font are two things that can disagree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-09-23 17:47:45 +02:00
commit d85274dbd9
5 changed files with 215 additions and 5 deletions

View file

@ -89,6 +89,29 @@
//
#define SCHBOOT_RSDP_ADDR (SCHBOOT_BASE + 0x600)
// A console typeface, read off the boot volume by the loader.
//
// Zero length means "there was none" and the kernel uses the one linked into
// its own image. That fallback is not a degraded mode: an early panic has to
// be readable, and the only font guaranteed to exist before any filesystem
// does is the one in the image.
//
// **Loaded by the loader rather than by the kernel**, which is the whole
// design decision here. The kernel could read the file itself once the root
// filesystem is mounted -- but the console is drawing before that, so
// switching then means re-measuring the cell and re-laying out every terminal
// mid-boot. That wipes the kernel log off the screen at exactly the moment a
// machine being brought up for the first time needs it. Choosing the font
// before the first line is drawn costs one file read and changes nothing
// afterwards.
//
// The pages are allocated as EfiReservedMemoryType, so the E820 handoff marks
// them reserved and the page allocator never hands them out -- the font is
// used *in place*, and nothing in the kernel has to find room for 100 KB of
// contiguous memory.
#define SCHBOOT_FONT_ADDR (SCHBOOT_BASE + 0x608) /* uint64, physical */
#define SCHBOOT_FONT_LEN (SCHBOOT_BASE + 0x610) /* uint32, bytes */
#ifndef __ASSEMBLER__
struct sch64_header {