fix(console): the serial line is found, not assumed -- from the devicetree on a board, COM1 by probe on a PC

Each board has one serial line that is its console, the lowest common
denominator beside the screen and keyboard.  Its address was QEMU's,
fixed: on a Raspberry Pi 5 the aarch64 one is ordinary RAM, written to
with every character printed; on a Milk-V Mars the riscv64 one is the
board's UART with registers four bytes apart, and the UEFI loader would
have waited for ever on its first character; on a PC with no COM1 the
prompt read 0xFF for ever.

console_attach_serial(), called before anything prints: COM1 if a UART
answers there; elsewhere the UART that /chosen/stdout-path names
(fdt_uart.c: alias, path, the address carried up through each bus's
ranges, reg-shift and reg-io-width); with no devicetree, QEMU's.  A
devicetree that names no usable console gives no line, not a guess.  A
line that does not come ready is given up, not waited on.  The riscv64
loader's early serial does the same.

tools/fdt_uart_test.c, run by hand, 19 checks plain and under the
sanitizers: devicetrees shaped like the Mars's, the Pi 5's and QEMU's,
what must find nothing, and a block cut short at every length; and the
Pi 5's real devicetree gives a PL011 at 0x107d001000.

Acceptance, one QEMU at a time: v3 on amd64 (logs/20261013-204436),
aarch64 (-204803) and riscv64 (-205001) to the zuse prompt, no unknown
word, hash 0x08873e0f44b7cb2a on all three as before.  -204600 is an
aarch64 run during which the disk images were restored under it by a
script that had been refused a second QEMU; it passed and is kept, and
was run again.  v4 on the three ISAs (-211149, -211444, -211857): POST
538 of 538, dict_hash 0xe5a57f3eb72ff94a, the typed session unchanged.
The Mars image through OpenSBI and U-Boot in QEMU took its line and its
interrupt controller from the devicetree and passed POST.

Found on the way and not mended (boards/BRINGUP.md 1.6): built with
EMERGENCY_CONSOLE_ENABLED=1 the prompt floods with empty lines.  -210442
shows it; the commit before this does the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-13 21:21:32 -04:00
co-authored by Claude Opus 5.5
parent f4e359859a
commit c95191736e
26 changed files with 48560 additions and 141 deletions
+54 -6
View File
@@ -53,6 +53,8 @@
#include "elf64.h"
#include "starkernel/cmdline.h"
#include "starkernel/boot_info_offsets.h"
#include "starkernel/fdt.h"
#include "starkernel/fdt_uart.h"
#include <stddef.h>
/* Verify BootInfo field offsets match boot_info_offsets.h (BOOT_INFO_OFFSETS) */
@@ -242,20 +244,56 @@ static void raw_serial_puts(const char *s)
*/
#define UART_MMIO_BASE 0x10000000UL
static inline void raw_mmio_outb(uint64_t addr, uint8_t val)
/* That is QEMU's. A real board's console UART is the one its devicetree
* names, and may be laid out otherwise: the Milk-V Mars's is at the same
* address with its registers four bytes apart, and the byte-spaced write
* below would wait for ever on what is not its status register
* (boards/BRINGUP.md). So the line is found first, raw_serial_find(), by
* the lookup the kernel's console uses (starkernel/fdt_uart.h); until then,
* and on a board whose devicetree names no line this can use, RAW_LOG()
* writes nothing. A line that does not come ready is given up. */
#define RAW_SERIAL_THRE_MAX_SPIN 4000000u
static int raw_serial_ready = 0;
static uint64_t raw_uart_base = UART_MMIO_BASE;
static unsigned raw_uart_shift = 0;
static unsigned raw_uart_width = 1;
static inline uint32_t raw_uart_read(unsigned reg)
{
*(volatile uint8_t *)addr = val;
uint64_t addr = raw_uart_base + ((uint64_t)reg << raw_uart_shift);
return raw_uart_width == 4 ? *(volatile uint32_t *)addr : *(volatile uint8_t *)addr;
}
static inline uint8_t raw_mmio_inb(uint64_t addr)
static inline void raw_uart_write(unsigned reg, uint8_t val)
{
return *(volatile uint8_t *)addr;
uint64_t addr = raw_uart_base + ((uint64_t)reg << raw_uart_shift);
if (raw_uart_width == 4) *(volatile uint32_t *)addr = val;
else *(volatile uint8_t *)addr = val;
}
/* `dtb`: the devicetree the firmware handed over, or NULL if it gave none. */
static void raw_serial_setup(const void *dtb)
{
fdt_uart_t u;
raw_serial_ready = 0;
if (fdt_console_uart(dtb, &u)) {
if (u.kind != FDT_UART_16550) return;
raw_uart_base = u.base; raw_uart_shift = u.reg_shift; raw_uart_width = u.io_width;
raw_serial_ready = 1;
} else if (!fdt_valid(dtb)) {
raw_uart_base = UART_MMIO_BASE; raw_uart_shift = 0; raw_uart_width = 1; /* no devicetree: QEMU's */
raw_serial_ready = 1;
}
}
static void raw_serial_putc(char c)
{
while ((raw_mmio_inb(UART_MMIO_BASE + 5) & 0x20) == 0) { }
raw_mmio_outb(UART_MMIO_BASE + 0, (uint8_t)c);
unsigned int spin = 0;
if (!raw_serial_ready) return;
while ((raw_uart_read(5) & 0x20) == 0) {
if (++spin >= RAW_SERIAL_THRE_MAX_SPIN) { raw_serial_ready = 0; return; }
}
raw_uart_write(0, (uint8_t)c);
}
static void raw_serial_puts(const char *s)
@@ -691,6 +729,16 @@ EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable
raw_serial_init();
RAW_LOG("RAW SERIAL UP\n");
debug_checkpoint(SystemTable, 2, L"Serial (COM1) initialized");
#elif defined(__riscv) || defined(ARCH_RISCV64)
{
/* the serial line, before the first RAW_LOG(): from the devicetree, if the firmware gave one */
const void *early_dtb = NULL;
EFI_CONFIGURATION_TABLE *tables = (EFI_CONFIGURATION_TABLE *)SystemTable->ConfigurationTable;
UINTN t;
for (t = 0; tables && t < SystemTable->NumberOfTableEntries; ++t)
if (guid_equals(&tables[t].VendorGuid, &EFI_DTB_TABLE_GUID)) { early_dtb = tables[t].VendorTable; break; }
raw_serial_setup(early_dtb);
}
#endif
#ifndef MONOLITHIC_BUILD