]> git.ipfire.org Git - thirdparty/systemd.git/commit
stub: auto-detect console device and append console= to kernel command line
authorDaan De Meyer <daan@amutable.com>
Fri, 27 Mar 2026 18:48:28 +0000 (18:48 +0000)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 1 Apr 2026 13:32:13 +0000 (15:32 +0200)
commit45e4df9a331208d20ecb9f5ead8110eb50a5b86d
tree46bcd2b8cb560891bd3c7bd54e89164f9e38e572
parente44f88f275ee99d8ed349f0cdfb37520aca3d8a8
stub: auto-detect console device and append console= to kernel command line

The Linux kernel does not reliably auto-detect serial consoles on
headless systems. While the docs claim serial is used as a fallback
when no VGA card is found, in practice CONFIG_VT's dummy console
(dummycon) registers early and satisfies the kernel's console
requirement, preventing the serial fallback from ever triggering. The
ACPI SPCR table can help on ARM/RISC-V where QEMU generates it, but
x86 QEMU does not produce SPCR, and SPCR cannot describe virtio
consoles at all. This means UKIs booted via sd-stub in headless VMs
produce no visible console output unless console= is explicitly
passed on the kernel command line.

Fix this by having sd-stub auto-detect the console type and append an
appropriate console= argument when one isn't already present.

Detection priority:

1. VirtIO console PCI device (vendor 0x1AF4, device 0x1003): if
   exactly one is found, append console=hvc0. This takes highest
   priority since a VirtIO console is explicitly configured by the
   VMM (e.g. systemd-vmspawn's virtconsole device). If multiple
   VirtIO console devices exist, we cannot determine which hvc index
   is correct, so we skip this path entirely.

2. EFI Graphics Output Protocol (GOP): if present, don't add any
   console= argument. The kernel will use the framebuffer console by
   default, and adding a serial console= would redirect the primary
   console away from the display.

3. Serial console: first, we count the total number of serial devices
   via EFI_SERIAL_IO_PROTOCOL. If there are zero or more than one,
   we bail out — with multiple UARTs, the kernel assigns ttyS indices
   based on its own enumeration order and we cannot determine which
   index the console UART will receive. Only when exactly one serial
   device exists (guaranteeing it will be ttyS0) do we proceed to
   verify it's actually used as a console by checking for UART device
   path nodes (MESSAGING_DEVICE_PATH + MSG_UART_DP). The firmware's
   ConOut handle is checked first; if it has no device path (common
   with OVMF's ConSplitter virtual handle when using -nographic
   -nodefaults), we fall back to enumerating all
   EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL handles and checking each one's
   device path. The architecture-specific console argument is then
   appended:
   - x86:     console=ttyS0
   - ARM:     console=ttyAMA0
   - Others:  console=ttyS0 (RISC-V, LoongArch, MIPS all use ttyS0)

Note on OVMF's VirtioSerialDxe: it exposes virtio serial ports with
the same UART device path nodes as real serial ports (ACPI PNP 0x0501
+ MSG_UART_DP), making them indistinguishable from real UARTs via
device path inspection alone. This is why we check for the VirtIO
console PCI device via EFI_PCI_IO_PROTOCOL before falling back to
device path analysis.

Also add a minimal EFI_PCI_IO_PROTOCOL definition (proto/pci-io.h)
with just enough to call Pci.Read for vendor/device ID enumeration,
and add the MSG_UART_DP subtype to the device path header.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
src/boot/proto/device-path.h
src/boot/proto/pci-io.h [new file with mode: 0644]
src/boot/proto/serial-io.h [new file with mode: 0644]
src/boot/stub.c