x86/early_printk: Add support for MMIO-based UARTs

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
This commit is contained in:
Denis Mukhin 2025-03-24 17:55:40 -07:00 committed by Ingo Molnar
parent 2c118f50d7
commit 3181424aea
2 changed files with 52 additions and 2 deletions

View file

@ -1407,14 +1407,21 @@
earlyprintk=serial[,0x...[,baudrate]] earlyprintk=serial[,0x...[,baudrate]]
earlyprintk=ttySn[,baudrate] earlyprintk=ttySn[,baudrate]
earlyprintk=dbgp[debugController#] earlyprintk=dbgp[debugController#]
earlyprintk=pciserial[,force],bus:device.function[,baudrate] earlyprintk=pciserial[,force],bus:device.function[,{nocfg|baudrate}]
earlyprintk=xdbc[xhciController#] earlyprintk=xdbc[xhciController#]
earlyprintk=bios earlyprintk=bios
earlyprintk=mmio,membase[,{nocfg|baudrate}]
earlyprintk is useful when the kernel crashes before earlyprintk is useful when the kernel crashes before
the normal console is initialized. It is not enabled by the normal console is initialized. It is not enabled by
default because it has some cosmetic problems. 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 Append ",keep" to not disable it when the real console
takes over. takes over.

View file

@ -190,7 +190,6 @@ static __init void early_serial_init(char *s)
early_serial_hw_init(divisor); early_serial_hw_init(divisor);
} }
#ifdef CONFIG_PCI
static __noendbr void mem32_serial_out(unsigned long addr, int offset, int value) static __noendbr void mem32_serial_out(unsigned long addr, int offset, int value)
{ {
u32 __iomem *vaddr = (u32 __iomem *)addr; 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); 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() * early_pci_serial_init()
* *
@ -351,6 +389,11 @@ static int __init setup_early_printk(char *buf)
keep = (strstr(buf, "keep") != NULL); keep = (strstr(buf, "keep") != NULL);
while (*buf != '\0') { 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)) { if (!strncmp(buf, "serial", 6)) {
buf += 6; buf += 6;
early_serial_init(buf); early_serial_init(buf);