Big Console makes the text of systemd-boot and other UEFI console applications, such as the UEFI shell, bigger on HiDPI displays without making it pixelated. It draws a high-resolution font at the panel's native resolution, so the large text stays as sharp as the panel can show it.
Big Console is a UEFI DXE driver: a firmware driver that systemd-boot, the UEFI shell, or the firmware itself loads before the operating system starts.
Firmware text consoles draw a hardcoded 8×19 pixel font. On a 4K panel that means unreadably tiny text in every pre-boot surface that uses the Simple Text Output protocol, most notably the systemd-boot menu. The usual way to enlarge that text is a low-resolution text mode, which switches the display to a lower resolution. The panel then scales every pixel up, so the text gets bigger but also blocky or blurred, and the display flickers through another resolution switch on the way into the OS. Big Console replaces the console's renderer instead. The framebuffer stays at native resolution, and each character is drawn from a font designed at the larger size, so the glyphs get bigger without getting coarser.
The systemd-boot menu on a 3840×2160 framebuffer, captured from the project's VM check. Each image is the same 1280×720 window at 1:1, so the glyphs compare at their real size.
| Stock console (8×19 cells) | Big Console (16×32 cells) | Big Console 2× (32×64 cells) |
|---|---|---|
![]() |
![]() |
![]() |
The whole frame, reduced to a third, shows how much of the panel each one uses; the stock menu is the speck in the middle.
| Stock console | Big Console | Big Console 2× |
|---|---|---|
![]() |
![]() |
![]() |
These come from the systemd-boot-menu check, which boots systemd-boot
from an ESP with the driver in its drivers directory, the way it is
deployed, and dumps the framebuffer once the menu is up.
A fork of TianoCore edk2's GraphicsConsoleDxe
(MdeModulePkg/Universal/Console/GraphicsConsoleDxe) with:
- An embedded high-resolution font. Glyph bitmaps are generated at build time from a PSF2 console font (Terminus 16×32 by default) and drawn directly by the driver. Characters the font lacks fall back to the platform's HII 8×19 glyphs, stretched to the cell.
- Integer magnification on top (
GLYPH_SCALE), for panels or viewing distances where even 16×32 is still too small. A 2× build (32×64 cells) is provided. On panels too small for an 80×25 grid of big cells the driver degrades to the stock 8×19 rendering path. - Console takeover. The driver binding registers at version
0xFFFFFFF0(the UEFI spec's platform-reserved band, above any IHV-range version a firmware console uses; edk2 binds at 0xa and AMI Aptio V binds at 0x10). At image start the driver evicts the incumbent console from every GOP handle, re-runs the binding contest it now wins, and re-syncs the system console's current mode. Loading the driver is sufficient; no platform modification and no explicit reconnect step is required.
The text-mode table keeps the UEFI-mandated 80×25 (and 80×50 where it fits), the stock driver's historical preset grids, and a computed full-screen mode, such as 240×67 on 3840×2160 with 16×32 cells.
From the moment the driver loads until the operating system takes over
the display, every pre-boot program that writes through the Simple Text
Output protocol renders in the big cells, whatever loaded it and from
wherever. That includes systemd-boot's menu, the UEFI shell, GRUB when
its output terminal is console (the usual state of a GRUB fetched
over the network), rEFInd in text mode, iPXE, shim and MokManager, the
boot menu and network boot messages of the firmware, and the few lines
the kernel's EFI stub prints. It does not include programs that draw
pixels directly through the graphics output protocol: GRUB's gfxterm
(what distribution configs select by default), rEFInd's graphical mode,
Limine, and Windows Boot Manager. The Linux console after boot is a
different renderer with a separate font setting.
How early the driver loads decides how much of that it reaches:
- Loaded by another UEFI application. systemd-boot loads drivers
from
\EFI\systemd\drivers\on the ESP (the file must end inx64.efi, oraa64.efion AArch64), rEFInd from itsdrivers_x64(ordrivers_aa64) directory, and the UEFI shell onload. The effect starts there and covers that program and everything it chainloads afterwards. - Loaded by the firmware itself. A
Driver####boot variable (e.g.Driver0000, created withefibootmgr --driver --create ...) or a firmware setup option makes the driver active before the screens of the firmware, so the boot menu and network boot messages render big too.
Under Secure Boot the driver must be signed with a db-trusted key in either case; an unsigned driver is skipped and boot proceeds with the stock console.
The framebuffer stays at native resolution throughout; what a text mode
chooses is the grid. By default the console keeps the firmware's current
mode, normally the spec-mandated 80×25, so the big text sits as a
centered block; selecting the full-screen mode (systemd-boot's
console-mode max in loader.conf, or mode 240 67 in the shell at
3840×2160) fills the panel instead. On a firmware whose console also
includes a serial terminal (servers, and edk2's virt firmware for
AArch64), the console splitter publishes only the modes every device
supports, so the largest grid is the terminal's 160×42, drawn in the
big cells as a centered block.
x86-64 and AArch64 UEFI. On x86-64 the driver is verified on every
change against TianoCore edk2 (OVMF in QEMU at 3840×2160, both variants,
loaded from the UEFI shell) and is in use on one AMI Aptio V firmware,
loaded by systemd-boot. On AArch64 it is verified the same way against
edk2's ArmVirtQemu on an emulated virt machine, and has not been run
on real hardware. Firmware early-loading (Driver####) has not received
much testing on either. Other vendors' firmware should work by the UEFI
driver-binding rules the console takeover relies on, but has not been
tried.
The driver is one .efi file. The most common installation puts that
file where systemd-boot looks for drivers. systemd-boot then loads it
every boot before drawing its menu, and everything systemd-boot starts
afterwards, the UEFI shell included, keeps the big font.
Each release has
four drivers and a SHA256SUMS file:
| File | Architecture | Cell size |
|---|---|---|
bigconsolex64.efi |
x86-64 | 16×32 |
bigconsole2xx64.efi |
x86-64 | 32×64 |
bigconsoleaa64.efi |
AArch64 | 16×32 |
bigconsole2xaa64.efi |
AArch64 | 32×64 |
Start with the 16×32 build. The 2× build suits a 4K panel viewed from a
distance. Each release file is the store path CI built for the tagged
commit, so it is byte-identical to what nix build produces at that
tag.
Copy the file into EFI/systemd/drivers/ on the EFI system partition
(ESP), keeping its name. systemd-boot loads only files whose names end
in x64.efi (or aa64.efi on AArch64), and the release names already
do. With the ESP mounted at /boot:
sha256sum --check --ignore-missing SHA256SUMS
sudo install -D -m0644 bigconsolex64.efi /boot/EFI/systemd/drivers/bigconsolex64.efiReboot. The menu appears in the big font as a centered 80×25 block. To
fill the screen instead, add this line to /boot/loader/loader.conf:
console-mode max
To uninstall, delete the file from EFI/systemd/drivers/.
Add this flake as an input and have systemd-boot install the driver onto the ESP:
# flake.nix
inputs.big-console.url = "github:clhodapp/big-console";# configuration.nix, with `inputs` passed to the module
boot.loader.systemd-boot = {
extraFiles."EFI/systemd/drivers/bigconsolex64.efi" =
"${inputs.big-console.packages.x86_64-linux.big-console-dxe}/BigGraphicsConsoleDxe.efi";
consoleMode = "max";
};The next nixos-rebuild boot or switch copies the driver onto the ESP.
The other builds are the packages big-console-dxe-2x,
big-console-dxe-aarch64 and big-console-dxe-2x-aarch64. All four
are under packages.x86_64-linux, because the AArch64 drivers are
cross-compiled, so an AArch64 machine also takes its driver from there
and names the file bigconsoleaa64.efi. The binary cache
section lists the caches that let Nix download the driver instead of
building the edk2 toolchain.
Under Secure Boot, the firmware loads only drivers signed with a key in
its db. Sign the file the same way you sign systemd-boot itself, for
example with sbsign or sbctl sign. The firmware skips an unsigned
driver, and boot continues with the stock console.
rEFInd loads the same file from its drivers_x64 (or drivers_aa64)
directory, and a UEFI shell started some other way loads it with
load. To make the boot menu of the firmware big too, register the
driver as a firmware driver, which runs before any boot manager:
sudo efibootmgr --driver --create --disk /dev/nvme0n1 --part 1 \
--loader '\EFI\big-console\bigconsolex64.efi' --label 'Big Console'Copy the file to that path on the ESP first, and change --disk and
--part to the disk and partition number that hold the ESP. This path
has had less testing than systemd-boot loading.
nix build .#big-console-dxe builds the 16×32 x86-64 driver, and
.#big-console-dxe-2x builds the 32×64 one. The AArch64 builds are
.#big-console-dxe-aarch64 and .#big-console-dxe-2x-aarch64, and
they cross-compile from any host. Each output is a directory holding
BigGraphicsConsoleDxe.efi.
nix flake check boots OVMF in QEMU at 3840×2160, loads the driver from a
UEFI shell, and asserts, from the mode table and framebuffer screendumps,
that the console was taken over, the mode grid matches the cell geometry,
and rendered text actually fills the tall cells (with a no-thin-stems check
for magnified builds). Both the 1× and 2× builds are exercised.
In CI the same nix flake check runs with the Nix store cached between
runs, since a cold runner builds the edk2 toolchain and both variants
before it can boot OVMF. A pull request that leaves .github/ alone is
checked by main's copy of the workflow, in main's context once the
request's check-pr run completes, and adds its build to the shared
cache; a pull request that changes the pipeline is checked by the copy
of the workflow it carries, under a cache only that request can see. The comments at the top of the two workflow files say why that
split is what makes the cache safe to write from a pull request.
What main builds, both driver variants included, is pushed to the
clhodapp cachix cache, signed with its key, so nix build at the same
pins downloads the driver instead of building the edk2 toolchain. That
cache skips paths its upstreams already hold, so using it means using
them too:
| Substituter | Public key |
|---|---|
https://clhodapp.cachix.org |
clhodapp.cachix.org-1:EW/0conxH0OQyo0o4ub/grdkFspholmQMSnQyj0vrZI= |
https://nix-community.cachix.org |
nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs= |
https://numtide.cachix.org |
numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE= |
The flake's nixConfig declares all three, so a direct nix build or
nix flake check here uses them once accepted: answer Nix's prompt, or
pass --accept-flake-config. A flake that consumes this one
as an input must add them to its extra-substituters and
extra-trusted-public-keys; Nix does not carry an input's settings
into the consumer.
Everything in this repository is BSD-2-Clause-Patent, matching upstream
edk2: the driver sources (pkg-overlays/default/packages/big-console-dxe/src, forked
from edk2, per the headers in each file) and the Nix build scaffolding
and tests alike. See LICENSE for the terms and NOTICE for what came
from where.
The one exception is the font: the glyph bitmaps embedded in the built
driver are generated from Terminus and carry the SIL Open Font License
1.1. See LICENSES/OFL-1.1.txt.





