x86/early_printk: Add support for MMIO-based UARTs
authorDenis Mukhin <dmukhin@ford.com>
Tue, 25 Mar 2025 00:55:40 +0000 (17:55 -0700)
committerIngo Molnar <mingo@kernel.org>
Tue, 25 Mar 2025 07:35:38 +0000 (08:35 +0100)
During the bring-up of an x86 board, the kernel was crashing before
reaching the platform's console driver because of a bug in the firmware,
leaving no trace of the boot progress.

The only available method to debug the kernel boot process was via the
platform's MMIO-based UART, as the board lacked an I/O port-based UART,
PCI UART, or functional video output.

Then it turned out that earlyprintk= does not have a knob to configure
the MMIO-mapped UART.

Extend the early printk facility to support platform MMIO-based UARTs
on x86 systems, enabling debugging during the system bring-up phase.

The command line syntax to enable platform MMIO-based UART is:

  earlyprintk=mmio,membase[,{nocfg|baudrate}][,keep]

Note, the change does not integrate MMIO-based UART support to:

  arch/x86/boot/early_serial_console.c

Also, update kernel parameters documentation with the new syntax and
add the missing 'nocfg' setting to the PCI serial cards description.

Signed-off-by: Denis Mukhin <dmukhin@ford.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Link: https://lore.kernel.org/r/20250324-earlyprintk-v3-1-aee7421dc469@ford.com
Documentation/admin-guide/kernel-parameters.txt
arch/x86/kernel/early_printk.c

index 866427da6addbcaf40828a50c4f7f727153bd3fa..649261e9762852c1bfb93dcc8096605908dafb37 100644 (file)
                        earlyprintk=serial[,0x...[,baudrate]]
                        earlyprintk=ttySn[,baudrate]
                        earlyprintk=dbgp[debugController#]
-                       earlyprintk=pciserial[,force],bus:device.function[,baudrate]
+                       earlyprintk=pciserial[,force],bus:device.function[,{nocfg|baudrate}]
                        earlyprintk=xdbc[xhciController#]
                        earlyprintk=bios
+                       earlyprintk=mmio,membase[,{nocfg|baudrate}]
 
                        earlyprintk is useful when the kernel crashes before
                        the normal console is initialized. It is not enabled by
                        default because it has some cosmetic problems.
 
+                       Only 32-bit memory addresses are supported for "mmio"
+                       and "pciserial" devices.
+
+                       Use "nocfg" to skip UART configuration, assume
+                       BIOS/firmware has configured UART correctly.
+
                        Append ",keep" to not disable it when the real console
                        takes over.
 
index fc1714bad04588abc3468a0e72df26513250162b..611f27e3890c2852cece31a9e948e7212c4e10fe 100644 (file)
@@ -190,7 +190,6 @@ static __init void early_serial_init(char *s)
        early_serial_hw_init(divisor);
 }
 
-#ifdef CONFIG_PCI
 static __noendbr void mem32_serial_out(unsigned long addr, int offset, int value)
 {
        u32 __iomem *vaddr = (u32 __iomem *)addr;
@@ -207,6 +206,45 @@ static __noendbr unsigned int mem32_serial_in(unsigned long addr, int offset)
 }
 ANNOTATE_NOENDBR_SYM(mem32_serial_in);
 
+/*
+ * early_mmio_serial_init() - Initialize MMIO-based early serial console.
+ * @s: MMIO-based serial specification.
+ */
+static __init void early_mmio_serial_init(char *s)
+{
+       unsigned long baudrate;
+       unsigned long membase;
+       char *e;
+
+       if (*s == ',')
+               s++;
+
+       if (!strncmp(s, "0x", 2)) {
+               /* NB: only 32-bit addresses are supported. */
+               membase = simple_strtoul(s, &e, 16);
+               early_serial_base = (unsigned long)early_ioremap(membase, PAGE_SIZE);
+
+               static_call_update(serial_in, mem32_serial_in);
+               static_call_update(serial_out, mem32_serial_out);
+
+               s += strcspn(s, ",");
+               if (*s == ',')
+                       s++;
+       }
+
+       if (!strncmp(s, "nocfg", 5)) {
+               baudrate = 0;
+       } else {
+               baudrate = simple_strtoul(s, &e, 0);
+               if (baudrate == 0 || s == e)
+                       baudrate = DEFAULT_BAUD;
+       }
+
+       if (baudrate)
+               early_serial_hw_init(115200 / baudrate);
+}
+
+#ifdef CONFIG_PCI
 /*
  * early_pci_serial_init()
  *
@@ -351,6 +389,11 @@ static int __init setup_early_printk(char *buf)
        keep = (strstr(buf, "keep") != NULL);
 
        while (*buf != '\0') {
+               if (!strncmp(buf, "mmio", 4)) {
+                       early_mmio_serial_init(buf + 4);
+                       early_console_register(&early_serial_console, keep);
+                       buf += 4;
+               }
                if (!strncmp(buf, "serial", 6)) {
                        buf += 6;
                        early_serial_init(buf);