/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "console.h"
+#include "device-path-util.h"
#include "efi-log.h"
+#include "efi-string.h"
#include "proto/graphics-output.h"
+#include "proto/pci-io.h"
+#include "string-util-fundamental.h"
+#include "util.h"
#define SYSTEM_FONT_WIDTH 8
#define SYSTEM_FONT_HEIGHT 19
return err;
}
+
+static bool has_virtio_console_pci_device(void) {
+ _cleanup_free_ EFI_HANDLE *handles = NULL;
+ size_t n_handles = 0;
+
+ EFI_STATUS err = BS->LocateHandleBuffer(
+ ByProtocol,
+ MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL),
+ NULL,
+ &n_handles,
+ &handles);
+ if (err != EFI_SUCCESS) {
+ log_debug_status(err, "Failed to locate PCI I/O protocol handles, assuming no VirtIO console: %m");
+ return false;
+ }
+
+ if (n_handles == 0) {
+ log_debug("No PCI devices found, not scanning for VirtIO console.");
+ return false;
+ }
+
+ log_debug("Found %zu PCI devices, scanning for VirtIO console...", n_handles);
+
+ size_t n_virtio_console = 0;
+
+ for (size_t i = 0; i < n_handles; i++) {
+ EFI_PCI_IO_PROTOCOL *pci_io = NULL;
+
+ if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), (void **) &pci_io) != EFI_SUCCESS)
+ continue;
+
+ /* Read PCI vendor ID and device ID (at offsets 0x00 and 0x02 in PCI config space) */
+ uint16_t pci_id[2] = {};
+ if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, /* offset= */ 0x00, /* count= */ 2, pci_id) != EFI_SUCCESS)
+ continue;
+
+ log_debug("PCI device %zu: vendor=%04x device=%04x", i, pci_id[0], pci_id[1]);
+
+ if (pci_id[0] == PCI_VENDOR_ID_REDHAT && pci_id[1] == PCI_DEVICE_ID_VIRTIO_CONSOLE)
+ n_virtio_console++;
+
+ if (n_virtio_console > 1) {
+ log_debug("There is more than one VirtIO console PCI device, cannot determine which one is the console.");
+ return false;
+ }
+ }
+
+ if (n_virtio_console == 0) {
+ log_debug("No VirtIO console PCI device found.");
+ return false;
+ }
+
+ log_debug("Found exactly one VirtIO console PCI device.");
+ return true;
+}
+
+static bool has_graphics_output(void) {
+ EFI_GRAPHICS_OUTPUT_PROTOCOL *gop = NULL;
+ EFI_STATUS err;
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &gop);
+ if (err != EFI_SUCCESS) {
+ log_debug_status(err, "No EFI Graphics Output Protocol found: %m");
+ return false;
+ }
+
+ log_debug("EFI Graphics Output Protocol found.");
+ return true;
+}
+
+#if defined(__i386__) || defined(__x86_64__)
+
+/* Walk the device path looking for a UART console and determine the COM port index from the
+ * ACPI device path node. On x86, the Linux kernel assigns fixed ttyS indices based on I/O port
+ * addresses (see arch/x86/include/asm/serial.h):
+ *
+ * ttyS0=0x3F8, ttyS1=0x2F8, ttyS2=0x3E8, ttyS3=0x2E8
+ *
+ * On standard PC firmware, the ACPI UID for PNP0501 (16550 UART) maps directly to the COM port
+ * index: UID 0 = COM1 (0x3F8) = ttyS0, UID 1 = COM2 (0x2F8) = ttyS1, etc.
+ *
+ * Returns EFI_SUCCESS and sets *ret_index on success, or EFI_NOT_FOUND if no PNP0501 UART
+ * was found. */
+static EFI_STATUS device_path_get_uart_index(const EFI_DEVICE_PATH *dp, uint32_t *ret_index) {
+ assert(ret_index);
+
+ for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node))
+ if (node->Type == ACPI_DEVICE_PATH &&
+ node->SubType == ACPI_DP &&
+ node->Length >= sizeof(ACPI_HID_DEVICE_PATH)) {
+ const ACPI_HID_DEVICE_PATH *acpi = (const ACPI_HID_DEVICE_PATH *) node;
+ if (acpi->HID == EISA_PNP_ID(0x0501)) {
+ *ret_index = acpi->UID;
+ return EFI_SUCCESS;
+ }
+ }
+
+ return EFI_NOT_FOUND;
+}
+
+/* Check if the console output is a serial UART. If so, determine the COM port index from the
+ * ACPI device path so we can pass the correct console= device to the kernel. */
+static EFI_STATUS find_serial_console_index(uint32_t *ret_index) {
+ assert(ret_index);
+
+ /* First try the ConOut handle directly. */
+ EFI_DEVICE_PATH *dp = NULL;
+ if (BS->HandleProtocol(ST->ConsoleOutHandle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) == EFI_SUCCESS) {
+ _cleanup_free_ char16_t *dp_str = NULL;
+ (void) device_path_to_str(dp, &dp_str);
+ log_debug("ConOut device path: %ls", strempty(dp_str));
+
+ if (device_path_get_uart_index(dp, ret_index) == EFI_SUCCESS) {
+ log_debug("ConOut is a serial console (port index %u).", *ret_index);
+ return EFI_SUCCESS;
+ }
+
+ log_debug("ConOut device path does not contain a PNP0501 UART node.");
+ return EFI_NOT_FOUND;
+ }
+
+ /* ConOut handle has no device path (e.g. ConSplitter virtual handle). Enumerate all
+ * text output handles and check if any of them is a serial console. */
+ log_debug("ConOut handle has no device path, enumerating text output handles...");
+
+ _cleanup_free_ EFI_HANDLE *handles = NULL;
+ size_t n_handles = 0;
+ if (BS->LocateHandleBuffer(
+ ByProtocol,
+ MAKE_GUID_PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL),
+ NULL,
+ &n_handles,
+ &handles) != EFI_SUCCESS) {
+ log_debug("Failed to enumerate text output handles.");
+ return EFI_NOT_FOUND;
+ }
+
+ bool found = false;
+
+ for (size_t i = 0; i < n_handles; i++) {
+ dp = NULL;
+ if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) != EFI_SUCCESS)
+ continue;
+
+ _cleanup_free_ char16_t *dp_str = NULL;
+ (void) device_path_to_str(dp, &dp_str);
+ log_debug("Text output handle %zu device path: %ls", i, strempty(dp_str));
+
+ uint32_t index;
+ if (device_path_get_uart_index(dp, &index) != EFI_SUCCESS)
+ continue;
+
+ log_debug("Text output handle %zu is a serial console (port index %u).", i, index);
+
+ if (found && *ret_index != index) {
+ log_debug("Multiple serial consoles with different port indices found, cannot determine which one to use.");
+ return EFI_NOT_FOUND;
+ }
+
+ *ret_index = index;
+ found = true;
+ }
+
+ if (!found) {
+ log_debug("No serial console found among text output handles.");
+ return EFI_NOT_FOUND;
+ }
+
+ return EFI_SUCCESS;
+}
+
+static const char16_t *serial_console_arg(uint32_t index) {
+ /* Use the uart I/O port address format (see Documentation/admin-guide/kernel-parameters.txt)
+ * instead of ttyS names. This addresses the 8250/16550 UART at the specified I/O port
+ * directly and switches to the matching ttyS device later. The I/O port addresses for
+ * the standard COM ports are fixed (see arch/x86/include/asm/serial.h), and the ACPI UID
+ * for PNP0501 maps directly to the COM port index. */
+ static const char16_t *const table[] = {
+ u"console=uart,io,0x3f8", /* COM1 */
+ u"console=uart,io,0x2f8", /* COM2 */
+ u"console=uart,io,0x3e8", /* COM3 */
+ u"console=uart,io,0x2e8", /* COM4 */
+ };
+
+ if (index >= ELEMENTSOF(table))
+ return NULL;
+
+ return table[index];
+}
+
+#endif /* __i386__ || __x86_64__ */
+
+/* If there's no console= in the command line yet, try to detect the appropriate console device.
+ *
+ * Detection order:
+ * 1. If exactly one VirtIO console PCI device exists -> console=hvc0
+ * 2. If there's graphical output (GOP) -> don't add console=, the kernel defaults are fine
+ * 3. On x86, if exactly one serial console exists -> console=uart,io,<addr>
+ * 4. Otherwise -> don't add console=, let the user handle it
+ *
+ * VirtIO console takes priority since it's explicitly configured by the VMM. Graphics is
+ * checked before serial to avoid accidentally redirecting output away from a graphical
+ * console by adding a serial console= argument.
+ *
+ * Serial console auto-detection is restricted to x86 where ACPI PNP0501 UIDs map to fixed
+ * I/O port addresses for 8250/16550 UARTs. On non-x86 (e.g. ARM), serial device indices are
+ * assigned dynamically, and the kernel has its own console auto-detection mechanisms
+ * (DT stdout-path, etc.).
+ *
+ * Not TPM-measured because the value is deterministically derived from firmware-reported
+ * hardware state (PCI device enumeration, GOP presence, serial device paths). */
+void cmdline_append_console(char16_t **cmdline) {
+ assert(cmdline);
+
+ if (*cmdline && (efi_fnmatch(u"console=*", *cmdline) || efi_fnmatch(u"* console=*", *cmdline))) {
+ log_debug("Kernel command line already contains console=, not adding one.");
+ return;
+ }
+
+ const char16_t *console_arg = NULL;
+
+ if (has_virtio_console_pci_device())
+ console_arg = u"console=hvc0";
+ else if (has_graphics_output()) {
+ log_debug("Graphical output available, not adding console= to kernel command line.");
+ return;
+ }
+#if defined(__i386__) || defined(__x86_64__)
+ else {
+ uint32_t serial_index;
+ if (find_serial_console_index(&serial_index) == EFI_SUCCESS)
+ console_arg = serial_console_arg(serial_index);
+ }
+#endif
+
+ if (!console_arg) {
+ log_debug("Cannot determine console type, not adding console= to kernel command line.");
+ return;
+ }
+
+ log_debug("Appending %ls to kernel command line.", console_arg);
+
+ _cleanup_free_ char16_t *old = TAKE_PTR(*cmdline);
+ if (isempty(old))
+ *cmdline = xstrdup16(console_arg);
+ else
+ *cmdline = xasprintf("%ls %ls", old, console_arg);
+}
EFI_STATUS console_set_mode(int64_t mode);
EFI_STATUS console_query_mode(size_t *x_max, size_t *y_max);
EFI_STATUS query_screen_resolution(uint32_t *ret_width, uint32_t *ret_height);
+void cmdline_append_console(char16_t **cmdline);
HW_MEMMAP_DP = 0x03,
+ ACPI_DP = 0x01,
+
MEDIA_HARDDRIVE_DP = 0x01,
MEDIA_VENDOR_DP = 0x03,
MEDIA_FILEPATH_DP = 0x04,
MEDIA_PIWG_FW_FILE_DP = 0x06,
MEDIA_PIWG_FW_VOL_DP = 0x07,
- MSG_UART_DP = 0x0e,
MSG_URI_DP = 24,
};
EFI_GUID Guid;
} _packed_ VENDOR_DEVICE_PATH;
+/* EISA PNP ID encoding: compressed 3-letter vendor + 16-bit product ID. */
+#define EISA_PNP_ID(Id) ((uint32_t) (((Id) << 16) | 0x41D0))
+
+typedef struct {
+ EFI_DEVICE_PATH Header;
+ uint32_t HID;
+ uint32_t UID;
+} _packed_ ACPI_HID_DEVICE_PATH;
+
typedef struct {
EFI_DEVICE_PATH Header;
uint32_t MemoryType;
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include "efi.h"
-
-#define EFI_SERIAL_IO_PROTOCOL_GUID \
- GUID_DEF(0xbb25cf6f, 0xf1d4, 0x11d2, 0x9a, 0x0c, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0xfd)
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "boot-secret.h"
+#include "console.h"
#include "cpio.h"
#include "device-path-util.h"
#include "devicetree.h"
#include "memory-util-fundamental.h"
#include "part-discovery.h"
#include "pe.h"
-#include "proto/graphics-output.h"
-#include "proto/pci-io.h"
-#include "proto/serial-io.h" /* IWYU pragma: keep */
#include "proto/shell-parameters.h"
#include "random-seed.h"
#include "sbat.h"
combine_measured_flag(parameters_measured, m);
}
-static bool has_virtio_console_pci_device(void) {
- _cleanup_free_ EFI_HANDLE *handles = NULL;
- size_t n_handles = 0;
-
- EFI_STATUS err = BS->LocateHandleBuffer(
- ByProtocol,
- MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL),
- NULL,
- &n_handles,
- &handles);
- if (err != EFI_SUCCESS) {
- log_debug_status(err, "Failed to locate PCI I/O protocol handles, assuming no VirtIO console: %m");
- return false;
- }
-
- if (n_handles == 0) {
- log_debug("No PCI devices found, not scanning for VirtIO console.");
- return false;
- }
-
- log_debug("Found %zu PCI devices, scanning for VirtIO console...", n_handles);
-
- size_t n_virtio_console = 0;
-
- for (size_t i = 0; i < n_handles; i++) {
- EFI_PCI_IO_PROTOCOL *pci_io = NULL;
-
- if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), (void **) &pci_io) != EFI_SUCCESS)
- continue;
-
- /* Read PCI vendor ID and device ID (at offsets 0x00 and 0x02 in PCI config space) */
- uint16_t pci_id[2] = {};
- if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, /* offset= */ 0x00, /* count= */ 2, pci_id) != EFI_SUCCESS)
- continue;
-
- log_debug("PCI device %zu: vendor=%04x device=%04x", i, pci_id[0], pci_id[1]);
-
- if (pci_id[0] == PCI_VENDOR_ID_REDHAT && pci_id[1] == PCI_DEVICE_ID_VIRTIO_CONSOLE)
- n_virtio_console++;
-
- if (n_virtio_console > 1) {
- log_debug("There is more than one VirtIO console PCI device, cannot determine which one is the console.");
- return false;
- }
- }
-
- if (n_virtio_console == 0) {
- log_debug("No VirtIO console PCI device found.");
- return false;
- }
-
- log_debug("Found exactly one VirtIO console PCI device.");
- return true;
-}
-
-static bool device_path_has_uart(const EFI_DEVICE_PATH *dp) {
- for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node))
- if (node->Type == MESSAGING_DEVICE_PATH && node->SubType == MSG_UART_DP)
- return true;
-
- return false;
-}
-
-static size_t count_serial_devices(void) {
- _cleanup_free_ EFI_HANDLE *handles = NULL;
- size_t n_handles = 0;
-
- if (BS->LocateHandleBuffer(
- ByProtocol,
- MAKE_GUID_PTR(EFI_SERIAL_IO_PROTOCOL),
- NULL,
- &n_handles,
- &handles) != EFI_SUCCESS)
- return 0;
-
- log_debug("Found %zu serial I/O devices in total.", n_handles);
- return n_handles;
-}
-
-static bool has_single_serial_console(void) {
- /* Even if we find exactly one serial console, we can only confidently map it to ttyS0
- * if there's only one serial device in the entire system. With multiple UARTs, the
- * kernel assigns ttyS indices based on its own discovery order, so the console UART
- * might end up as ttyS1 or higher. */
- size_t n_serial_devices = count_serial_devices();
- if (n_serial_devices == 0) {
- log_debug("No serial I/O devices found.");
- return false;
- }
- if (n_serial_devices > 1) {
- log_debug("Found %zu serial I/O devices, cannot determine ttyS index.", n_serial_devices);
- return false;
- }
-
- /* Exactly one serial device in the system. Verify it's actually used as a console
- * by checking if ConOut or any text output handle has a UART device path. */
-
- /* First try the ConOut handle directly */
- EFI_DEVICE_PATH *dp = NULL;
- if (BS->HandleProtocol(ST->ConsoleOutHandle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) == EFI_SUCCESS) {
- _cleanup_free_ char16_t *dp_str = NULL;
- (void) device_path_to_str(dp, &dp_str);
- log_debug("ConOut device path: %ls", strempty(dp_str));
-
- if (device_path_has_uart(dp)) {
- log_debug("ConOut device path contains UART node.");
- return true;
- }
-
- log_debug("ConOut device path does not contain UART node.");
- return false;
- }
-
- /* ConOut handle has no device path (e.g. ConSplitter virtual handle). Enumerate all
- * text output handles and check if any of them is a serial console. */
- log_debug("ConOut handle has no device path, enumerating text output handles...");
-
- _cleanup_free_ EFI_HANDLE *handles = NULL;
- size_t n_handles = 0;
- if (BS->LocateHandleBuffer(
- ByProtocol,
- MAKE_GUID_PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL),
- NULL,
- &n_handles,
- &handles) != EFI_SUCCESS) {
- log_debug("Failed to enumerate text output handles.");
- return false;
- }
-
- for (size_t i = 0; i < n_handles; i++) {
- dp = NULL;
- if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) != EFI_SUCCESS)
- continue;
-
- _cleanup_free_ char16_t *dp_str = NULL;
- (void) device_path_to_str(dp, &dp_str);
- log_debug("Text output handle %zu device path: %ls", i, strempty(dp_str));
-
- if (device_path_has_uart(dp)) {
- log_debug("Text output handle %zu is a serial console.", i);
- return true;
- }
- }
-
- log_debug("No serial console found among text output handles.");
- return false;
-}
-
-static bool has_graphics_output(void) {
- EFI_GRAPHICS_OUTPUT_PROTOCOL *gop = NULL;
- EFI_STATUS err;
-
- err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &gop);
- if (err != EFI_SUCCESS) {
- log_debug_status(err, "No EFI Graphics Output Protocol found: %m");
- return false;
- }
-
- log_debug("EFI Graphics Output Protocol found.");
- return true;
-}
-
-static const char16_t *serial_console_arg(void) {
-#if defined(__arm__) || defined(__aarch64__)
- return u"console=ttyAMA0";
-#else
- return u"console=ttyS0";
-#endif
-}
-
-/* If there's no console= in the command line yet, try to detect the appropriate console device.
- *
- * Detection order:
- * 1. If exactly one VirtIO console PCI device exists → console=hvc0
- * 2. If there's graphical output (GOP) → don't add console=, the kernel defaults are fine
- * 3. If exactly one serial console exists → arch-specific serial (ttyS0, ttyAMA0, etc.)
- * 4. Otherwise → don't add console=, let the user handle it
- *
- * VirtIO console takes priority since it's explicitly configured by the VMM. Graphics is
- * checked before serial to avoid accidentally redirecting output away from a graphical
- * console by adding a serial console= argument. */
-static void cmdline_append_console(char16_t **cmdline) {
- assert(cmdline);
-
- if (*cmdline && (efi_fnmatch(u"console=*", *cmdline) || efi_fnmatch(u"* console=*", *cmdline))) {
- log_debug("Kernel command line already contains console=, not adding one.");
- return;
- }
-
- const char16_t *console_arg = NULL;
-
- if (has_virtio_console_pci_device())
- console_arg = u"console=hvc0";
- else if (has_graphics_output()) {
- log_debug("Graphical output available, not adding console= to kernel command line.");
- return;
- } else if (has_single_serial_console())
- console_arg = serial_console_arg();
-
- if (!console_arg) {
- log_debug("Cannot determine console type, not adding console= to kernel command line.");
- return;
- }
-
- log_debug("Appending %ls to kernel command line.", console_arg);
-
- _cleanup_free_ char16_t *old = TAKE_PTR(*cmdline);
- if (isempty(old))
- *cmdline = xstrdup16(console_arg);
- else
- *cmdline = xasprintf("%ls %ls", old, console_arg);
-}
-
static EFI_STATUS run(EFI_HANDLE image) {
int sections_measured = -1, parameters_measured = -1, sysext_measured = -1, confext_measured = -1;
_cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
cmdline_append_and_measure_addons(cmdline_addons, &cmdline, ¶meters_measured);
cmdline_append_and_measure_smbios(&cmdline, ¶meters_measured);
- /* Console auto-detection is intentionally not TPM-measured. The value is deterministically
- * derived from firmware-reported hardware state (PCI device enumeration, GOP presence, serial
- * device paths), so it doesn't represent an independent input that could be manipulated
- * without also changing the firmware environment that TPM already captures. */
cmdline_append_console(&cmdline);
export_common_variables(loaded_image);