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
+163 -124
View File
File diff suppressed because it is too large Load Diff
+100
View File
@@ -506,6 +506,106 @@ const void* fdt_next_child_node(const void* fdt, const void* parent,
return (void*)0;
}
int fdt_complete(const void* fdt)
{
const fdt_header_t* h = (const fdt_header_t*)fdt;
uint32_t size_struct;
if (!fdt_valid(fdt)) return 0;
size_struct = be32(&h->size_dt_struct);
if (size_struct < 4 || (size_struct & 3u)) return 0;
return be32((const unsigned char*)fdt + be32(&h->off_dt_struct) + size_struct - 4) == FDT_END;
}
const void* fdt_root_node(const void* fdt)
{
const fdt_header_t* h = (const fdt_header_t*)fdt;
const unsigned char *p, *end;
if (!fdt_valid(fdt)) return (void*)0;
p = (const unsigned char*)fdt + be32(&h->off_dt_struct);
end = p + be32(&h->size_dt_struct);
while (p + 4 <= end)
{
uint32_t token = be32(p);
p += 4;
if (token == FDT_NOP) continue;
if (token != FDT_BEGIN_NODE) break;
{
const unsigned char* q = p;
while (q < end && *q) q++;
if (q >= end) break;
return (const void*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
}
}
return (void*)0;
}
const void* fdt_find_child(const void* fdt, const void* parent,
const char* name, uint32_t name_len)
{
const fdt_header_t* h = (const fdt_header_t*)fdt;
const unsigned char *end, *p;
if (!fdt_valid(fdt) || !parent || !name) return (void*)0;
end = (const unsigned char*)fdt + be32(&h->off_dt_struct) + be32(&h->size_dt_struct);
p = (const unsigned char*)parent;
/* parent's own properties first, then its children one after another:
* each child that is not the one wanted is skipped whole. */
while (p + 4 <= end)
{
uint32_t token = be32(p);
p += 4;
if (token == FDT_BEGIN_NODE)
{
const unsigned char* q = p;
const unsigned char* body;
uint32_t i;
while (q < end && *q) q++;
if (q >= end) return (void*)0;
body = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
/* the whole name, unit address and all, and nothing more */
if ((uint32_t)(q - p) == name_len)
{
for (i = 0; i < name_len && p[i] == (unsigned char)name[i]; i++) { }
if (i == name_len) return (const void*)body;
}
p = skip_node_body(body, end);
}
else if (token == FDT_PROP)
{
uint32_t len;
if (p + 8 > end) break;
len = be32(p);
p += 8;
if (len > (uint32_t)(end - p)) break;
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
}
else if (token == FDT_NOP)
{
/* no payload */
}
else
{
/* FDT_END_NODE (parent closing), FDT_END, or unknown */
return (void*)0;
}
}
return (void*)0;
}
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
const char* name, uint32_t* len_out)
{
+167
View File
@@ -0,0 +1,167 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* fdt_uart.c - the console's serial line, as a board's devicetree names it.
* See fdt_uart.h.
*
* The node is reached a component of its path at a time from the root, so
* that each bus on the way is in hand: a node's "reg" is an address on its
* parent's bus, and is carried up to the CPU's own addresses through the
* "ranges" of every bus above it (DT spec 2.3.8). A bus with an empty
* "ranges" passes addresses on unchanged; one with none at all has no
* mapping, and the line is not found.
*/
#include "starkernel/fdt_uart.h"
#include "starkernel/fdt.h"
#define PATH_DEPTH 8 /* nodes from the root to the UART, the root counted */
static uint32_t be32(const unsigned char* b)
{
return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3];
}
/* a property that is one cell, or `otherwise` */
static uint32_t cell_prop(const void* fdt, const void* node, const char* name, uint32_t otherwise)
{
uint32_t len;
const void* v = fdt_find_prop_in_node(fdt, node, name, &len);
return (v && len == 4) ? be32((const unsigned char*)v) : otherwise;
}
/* one or two cells as a number; 0 cells is 0 */
static uint64_t cells(const unsigned char* p, uint32_t n)
{
uint64_t v = 0;
while (n--) { v = (v << 32) | be32(p); p += 4; }
return v;
}
/* is `want` one of the strings of a "compatible" list? */
static int compatible_has(const char* list, uint32_t len, const char* want)
{
uint32_t i = 0;
while (i < len)
{
uint32_t j = 0;
while (i + j < len && list[i + j] && want[j] && list[i + j] == want[j]) j++;
if (i + j < len && !list[i + j] && !want[j]) return 1;
while (i < len && list[i]) i++;
i++;
}
return 0;
}
int fdt_console_uart(const void* fdt, fdt_uart_t* out)
{
const void* node[PATH_DEPTH];
const void* root;
const char *path, *v;
uint32_t len, path_len, depth, i, ac, sc;
uint64_t addr;
if (!out) return 0;
out->kind = FDT_UART_NONE; out->base = 0; out->reg_shift = 0; out->io_width = 1;
if (!fdt_complete(fdt)) return 0; /* an absent property means its default: not in half a tree */
root = fdt_root_node(fdt);
if (!root) return 0;
/* /chosen/stdout-path: a path, or an alias, and then perhaps ":115200n8" */
path = (const char*)fdt_find_prop_in_node(fdt, fdt_find_child(fdt, root, "chosen", 6), "stdout-path", &len);
if (!path || len == 0) return 0;
for (path_len = 0; path_len < len && path[path_len] && path[path_len] != ':'; path_len++) { }
if (path_len == 0) return 0;
if (path[0] != '/')
{
char alias[32];
if (path_len >= sizeof alias) return 0;
for (i = 0; i < path_len; i++) alias[i] = path[i];
alias[path_len] = '\0';
path = (const char*)fdt_find_prop_in_node(fdt, fdt_find_child(fdt, root, "aliases", 7), alias, &len);
if (!path || len == 0 || path[0] != '/') return 0;
for (path_len = 0; path_len < len && path[path_len]; path_len++) { }
}
/* down the path, keeping every node on the way */
node[0] = root;
depth = 1;
i = 0;
while (i < path_len)
{
uint32_t from;
while (i < path_len && path[i] == '/') i++;
from = i;
while (i < path_len && path[i] != '/') i++;
if (i == from) break;
if (depth == PATH_DEPTH) return 0;
node[depth] = fdt_find_child(fdt, node[depth - 1], path + from, i - from);
if (!node[depth]) return 0;
depth++;
}
if (depth < 2) return 0; /* the root is not a UART */
v = (const char*)fdt_find_prop_in_node(fdt, node[depth - 1], "status", &len);
if (v && !compatible_has(v, len, "okay") && !compatible_has(v, len, "ok")) return 0;
/* its address on its parent's bus */
ac = cell_prop(fdt, node[depth - 2], "#address-cells", 2);
sc = cell_prop(fdt, node[depth - 2], "#size-cells", 1);
if (ac < 1 || ac > 2 || sc > 2) return 0;
v = (const char*)fdt_find_prop_in_node(fdt, node[depth - 1], "reg", &len);
if (!v || len < 4 * (ac + sc)) return 0;
addr = cells((const unsigned char*)v, ac);
/* and up through each bus to the CPU's addresses */
for (i = depth - 2; i >= 1; i--)
{
uint32_t child_ac = cell_prop(fdt, node[i], "#address-cells", 2);
uint32_t child_sc = cell_prop(fdt, node[i], "#size-cells", 1);
uint32_t parent_ac = cell_prop(fdt, node[i - 1], "#address-cells", 2);
uint32_t entry, at;
int carried = 0;
v = (const char*)fdt_find_prop_in_node(fdt, node[i], "ranges", &len);
if (!v) return 0; /* no mapping to the bus above */
if (len == 0) continue; /* the same addresses above */
if (child_ac < 1 || child_ac > 2 || parent_ac < 1 || parent_ac > 2 || child_sc < 1 || child_sc > 2) return 0;
entry = 4 * (child_ac + parent_ac + child_sc);
for (at = 0; at + entry <= len; at += entry)
{
const unsigned char* e = (const unsigned char*)v + at;
uint64_t from = cells(e, child_ac);
uint64_t to = cells(e + 4 * child_ac, parent_ac);
uint64_t size = cells(e + 4 * (child_ac + parent_ac), child_sc);
if (addr >= from && addr - from < size) { addr = to + (addr - from); carried = 1; break; }
}
if (!carried) return 0;
}
v = (const char*)fdt_find_prop_in_node(fdt, node[depth - 1], "compatible", &len);
if (!v) return 0;
if (compatible_has(v, len, "arm,pl011") || compatible_has(v, len, "arm,pl011-axi"))
{
out->reg_shift = 0;
out->io_width = 4;
out->base = addr;
out->kind = FDT_UART_PL011;
return 1;
}
if (compatible_has(v, len, "ns16550a") || compatible_has(v, len, "ns16550") || compatible_has(v, len, "snps,dw-apb-uart"))
{
uint32_t shift = cell_prop(fdt, node[depth - 1], "reg-shift", 0);
uint32_t width = cell_prop(fdt, node[depth - 1], "reg-io-width", 1);
if (shift > 2 || (width != 1 && width != 4)) return 0;
out->reg_shift = shift;
out->io_width = width;
out->base = addr;
out->kind = FDT_UART_16550;
return 1;
}
return 0;
}