Files
LithosAnanake/tools/fdt_uart_test.c
T
rajamesandClaude Opus 5.5 0e2c60dac3 fix(raspi): the Pi 5's native path turns the MMU and caches on before any C; bounded mailbox; framebuffer address from dma-ranges
boards/BRINGUP.md R2/R3, FABRIC-3 SXXXIX.  The entry stub cleans the cache
over the image, builds an identity map (the image's GiB Normal, the rest
Device) and enables MMU and caches at EL2 (or EL1); rpi5_mmu.c makes the
devicetree's RAM Normal and the framebuffer uncached.  Mailbox waits give
up after one second; the buffer is cleaned around the exchange; the
framebuffer address goes through dma-ranges, not a Pi 4 mask.

New rpi5-rehearsal target runs the native image on QEMU virt at EL2.  It
found three more that would each have stopped the board: the PL011 shift
(mine, c9519173; the host test asserted it), the GIC address not carried
through /soc's ranges (new fdt_reg_address(), checked against the real
Pi 5 DTB), and no timer IRQ at EL2 (HCR_EL2.IMO/FMO/AMO).

Rehearsal: POST 1052 passed, same parity hash as UEFI.  Standard acceptance
on three ISAs: POST 1052, hash 0x82f4ae7cb2e2d424.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:58:35 -04:00

272 lines
13 KiB
C

/* fdt_uart_test.c -- host test of the console's serial-line lookup
* (kernel/src/hal/fdt_uart.c): which UART a board's devicetree names as its
* console, where it is, and how its registers are laid out.
*
* No board is needed. The test builds devicetrees shaped like the Milk-V
* Mars's (JH7110), the Raspberry Pi 5's (BCM2712) and QEMU's riscv64 virt
* machine's, with the values those boards' own devicetree sources give, and
* checks what the lookup finds in each; then ones that must find nothing.
* Given the path of a real blob and what it should give, it checks that too.
*
* Build and run, from the repository root:
*
* cc -std=c99 -Wall -Wextra -Werror -Ikernel/include \
* tools/fdt_uart_test.c kernel/src/hal/fdt.c kernel/src/hal/fdt_uart.c \
* -o build/fdt_uart_test
* build/fdt_uart_test
* build/fdt_uart_test build/cache/raspi-<tag>/bcm2712-rpi-5-b.dtb pl011 0x107d001000 0 1
*
* It is not part of any build; like tools/ttftest.c it is run by hand.
*/
#include "starkernel/fdt.h"
#include "starkernel/fdt_uart.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int failures = 0, checks = 0;
#define CHECK(c, ...) do { checks++; if (!(c)) { failures++; printf("FAIL %s:%d: ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } } while (0)
/* ---- a devicetree blob, written a token at a time ---- */
static unsigned char st[4096], strs[1024], blob[8192];
static unsigned st_len, strs_len;
static void put32(unsigned char *p, uint32_t v) { p[0] = (unsigned char)(v >> 24); p[1] = (unsigned char)(v >> 16); p[2] = (unsigned char)(v >> 8); p[3] = (unsigned char)v; }
static void tok(uint32_t v) { put32(st + st_len, v); st_len += 4; }
static void begin(const char *name)
{
size_t n = strlen(name) + 1;
tok(1);
memcpy(st + st_len, name, n);
st_len += (unsigned)n;
while (st_len & 3u) st[st_len++] = 0;
}
static void end(void) { tok(2); }
static uint32_t str_off(const char *name)
{
unsigned i = 0;
while (i < strs_len) { if (strcmp((const char *)strs + i, name) == 0) return i; i += (unsigned)strlen((const char *)strs + i) + 1; }
memcpy(strs + strs_len, name, strlen(name) + 1);
i = strs_len; strs_len += (unsigned)strlen(name) + 1;
return i;
}
static void prop(const char *name, const void *value, uint32_t len)
{
tok(3); tok(len); tok(str_off(name));
if (len) memcpy(st + st_len, value, len);
st_len += len;
while (st_len & 3u) st[st_len++] = 0;
}
static void prop_str(const char *name, const char *s) { prop(name, s, (uint32_t)strlen(s) + 1); }
static void prop_cells(const char *name, unsigned n, const uint32_t *c)
{
unsigned char b[64];
unsigned i;
for (i = 0; i < n; i++) put32(b + 4 * i, c[i]);
prop(name, b, 4 * n);
}
static void prop_u32(const char *name, uint32_t v) { prop_cells(name, 1, &v); }
static void start(void) { st_len = 0; strs_len = 0; begin(""); }
static const void *finish(void)
{
unsigned off_struct = 40 + 16, off_strs;
end(); tok(9);
off_strs = off_struct + st_len;
memset(blob, 0, sizeof blob);
put32(blob + 0, 0xd00dfeedu);
put32(blob + 4, off_strs + strs_len);
put32(blob + 8, off_struct);
put32(blob + 12, off_strs);
put32(blob + 16, 40);
put32(blob + 20, 17);
put32(blob + 24, 16);
put32(blob + 32, strs_len);
put32(blob + 36, st_len);
memcpy(blob + off_struct, st, st_len);
memcpy(blob + off_strs, strs, strs_len);
return blob;
}
static const char *kind_name(int k) { return k == FDT_UART_PL011 ? "pl011" : k == FDT_UART_16550 ? "16550" : "none"; }
static int is(const fdt_uart_t *u, int kind, uint64_t base, uint32_t shift, uint32_t width)
{
return u->kind == kind && u->base == base && u->reg_shift == shift && u->io_width == width;
}
#define SHOW(u) kind_name((u).kind), (unsigned long long)(u).base, (unsigned)(u).reg_shift, (unsigned)(u).io_width
/* the Mars: jh7110.dtsi's uart0 and jh7110-common.dtsi's aliases and chosen */
static const void *mars(const char *stdout_path, const char *status, int with_ranges)
{
static const uint32_t reg[4] = { 0, 0x10000000u, 0, 0x10000u };
start();
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
begin("aliases"); prop_str("serial0", "/soc/serial@10000000"); end();
begin("chosen"); if (stdout_path) prop_str("stdout-path", stdout_path); end();
begin("soc");
prop_str("compatible", "simple-bus");
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
if (with_ranges) prop("ranges", "", 0);
begin("serial@10000000");
prop("compatible", "starfive,jh7110-uart\0snps,dw-apb-uart", 38);
prop_cells("reg", 4, reg);
prop_u32("reg-io-width", 4); prop_u32("reg-shift", 2);
if (status) prop_str("status", status);
end();
end();
return finish();
}
/* the Pi 5: bcm2712.dtsi's soc bus and its debug UART, serial10 */
static const void *pi5(const char *compatible, unsigned compatible_len)
{
static const uint32_t ranges[4] = { 0x7c000000u, 0x10, 0x7c000000u, 0x80000000u };
static const uint32_t reg[2] = { 0x7d001000u, 0x200u };
static const uint32_t rp1[4] = { 0xc0, 0x40030000u, 0, 0x100 };
start();
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
begin("aliases");
prop_str("serial0", "/axi/pcie@1000120000/rp1/serial@30000");
prop_str("serial10", "/soc@107c000000/serial@7d001000");
end();
begin("chosen"); prop_str("stdout-path", "serial10:115200n8"); end();
begin("soc@107c000000");
prop_str("compatible", "simple-bus");
prop_u32("#address-cells", 1); prop_u32("#size-cells", 1);
prop_cells("ranges", 4, ranges);
begin("serial@7d001000");
prop("compatible", compatible, compatible_len);
prop_cells("reg", 2, reg);
prop_str("status", "okay");
end();
end();
begin("axi");
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
prop("ranges", "", 0);
begin("pcie@1000120000");
prop_u32("#address-cells", 3); prop_u32("#size-cells", 2);
begin("rp1");
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
begin("serial@30000"); prop("compatible", "arm,pl011-axi", 14); prop_cells("reg", 4, rp1); prop_str("status", "disabled"); end();
end();
end();
end();
return finish();
}
/* QEMU's riscv64 virt machine, as U-Boot hands it on: the console named by its path */
static const void *virt(void)
{
static const uint32_t reg[4] = { 0, 0x10000000u, 0, 0x100u };
start();
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
begin("chosen"); prop_str("stdout-path", "/soc/serial@10000000"); end();
begin("soc");
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
prop("ranges", "", 0);
begin("serial@10000000"); prop_str("compatible", "ns16550a"); prop_cells("reg", 4, reg); end();
end();
return finish();
}
int main(int argc, char **argv)
{
fdt_uart_t u;
const void *b;
if (argc == 6 || argc == 9) { /* a real blob, and what it should give */
static unsigned char real[2 * 1024 * 1024];
FILE *f = fopen(argv[1], "rb");
size_t n;
int kind = strcmp(argv[2], "pl011") == 0 ? FDT_UART_PL011 : strcmp(argv[2], "16550") == 0 ? FDT_UART_16550 : FDT_UART_NONE;
if (!f) { printf("cannot open %s\n", argv[1]); return 2; }
n = fread(real, 1, sizeof real, f);
fclose(f);
memset(&u, 0xAA, sizeof u);
CHECK(n > 40 && fdt_console_uart(real, &u) && is(&u, kind, strtoull(argv[3], 0, 0), (uint32_t)strtoul(argv[4], 0, 0), (uint32_t)strtoul(argv[5], 0, 0)),
"%s: %s at 0x%llx, registers %u apart (shift), %u wide", argv[1], SHOW(u));
printf(" %s: %s at 0x%llx, shift %u, width %u\n", argv[1], SHOW(u));
if (argc == 9) { /* and a node's reg, carried through its buses: argv[6] compatible, [7] [8] entries 0 and 1 */
uint64_t a0 = 0, a1 = 0;
CHECK(fdt_reg_address(real, argv[6], 0, &a0) && a0 == strtoull(argv[7], 0, 0) &&
fdt_reg_address(real, argv[6], 1, &a1) && a1 == strtoull(argv[8], 0, 0),
"%s: reg 0 at 0x%llx, reg 1 at 0x%llx", argv[6], (unsigned long long)a0, (unsigned long long)a1);
printf(" %s: reg 0 at 0x%llx, reg 1 at 0x%llx\n", argv[6], (unsigned long long)a0, (unsigned long long)a1);
}
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}
/* ---- the three boards ---- */
b = mars("serial0:115200n8", 0, 1);
CHECK(fdt_valid(b), "the blob the test builds is a devicetree");
memset(&u, 0xAA, sizeof u);
CHECK(fdt_console_uart(b, &u) && is(&u, FDT_UART_16550, 0x10000000ull, 2, 4),
"the Mars: a 16550 at 0x10000000 with its registers four bytes apart and four wide: %s 0x%llx %u %u", SHOW(u));
CHECK(fdt_console_uart(mars("serial0", 0, 1), &u) && is(&u, FDT_UART_16550, 0x10000000ull, 2, 4), "the same with no line settings after the alias");
CHECK(fdt_console_uart(mars("serial0:115200n8", "okay", 1), &u) && u.kind == FDT_UART_16550, "and with status okay");
memset(&u, 0xAA, sizeof u);
CHECK(fdt_console_uart(pi5("arm,pl011-axi\0arm,pl011\0arm,primecell", 37), &u) && is(&u, FDT_UART_PL011, 0x107d001000ull, 2, 4),
"the Pi 5: serial10, a PL011, its address carried through the bus's ranges to 0x107d001000: %s 0x%llx %u %u", SHOW(u));
CHECK(fdt_console_uart(pi5("arm,pl011-axi", 14), &u) && is(&u, FDT_UART_PL011, 0x107d001000ull, 2, 4), "a node that says only arm,pl011-axi is a PL011 too");
memset(&u, 0xAA, sizeof u);
CHECK(fdt_console_uart(virt(), &u) && is(&u, FDT_UART_16550, 0x10000000ull, 0, 1),
"QEMU's riscv64 machine: named by its path, a 16550 with registers a byte apart: %s 0x%llx %u %u", SHOW(u));
/* ---- what must find nothing, and leave *out as no line ---- */
memset(&u, 0xAA, sizeof u);
CHECK(!fdt_console_uart(mars(0, 0, 1), &u) && u.kind == FDT_UART_NONE, "no stdout-path: no line, and it is not guessed");
CHECK(!fdt_console_uart(mars("serial7:115200n8", 0, 1), &u) && u.kind == FDT_UART_NONE, "an alias that is not there");
CHECK(!fdt_console_uart(mars("/soc/serial@20000000", 0, 1), &u) && u.kind == FDT_UART_NONE, "a path to no node");
CHECK(!fdt_console_uart(mars("/soc/serial", 0, 1), &u) && u.kind == FDT_UART_NONE, "a node's name without its address is not that node");
CHECK(!fdt_console_uart(mars("serial0:115200n8", "disabled", 1), &u) && u.kind == FDT_UART_NONE, "a UART that is disabled");
CHECK(!fdt_console_uart(mars("serial0:115200n8", 0, 0), &u) && u.kind == FDT_UART_NONE, "a bus with no ranges: its addresses cannot be carried up");
CHECK(!fdt_console_uart(pi5("brcm,bcm2835-aux-uart", 22), &u) && u.kind == FDT_UART_NONE, "a UART of a kind the console has no driver for");
{
static const uint32_t far_ranges[4] = { 0x10000000u, 0x10, 0x10000000u, 0x1000u };
static const uint32_t reg[2] = { 0x7d001000u, 0x200u };
start();
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
begin("chosen"); prop_str("stdout-path", "/soc/serial@7d001000"); end();
begin("soc"); prop_u32("#address-cells", 1); prop_u32("#size-cells", 1); prop_cells("ranges", 4, far_ranges);
begin("serial@7d001000"); prop_str("compatible", "arm,pl011"); prop_cells("reg", 2, reg); end();
end();
CHECK(!fdt_console_uart(finish(), &u) && u.kind == FDT_UART_NONE, "an address none of the bus's ranges covers");
}
{
/* the console named is behind a bus with three address cells, as the Pi 5's RP1 UARTs are: not handled, and said so */
b = pi5("arm,pl011", 10);
start();
prop_u32("#address-cells", 2); prop_u32("#size-cells", 2);
begin("chosen"); prop_str("stdout-path", "/pcie/serial@30000"); end();
begin("pcie"); prop_u32("#address-cells", 3); prop_u32("#size-cells", 2); prop("ranges", "", 0);
{ static const uint32_t reg[5] = { 0, 0xc0, 0x40030000u, 0, 0x100 };
begin("serial@30000"); prop_str("compatible", "arm,pl011"); prop_cells("reg", 5, reg); end(); }
end();
CHECK(!fdt_console_uart(finish(), &u) && u.kind == FDT_UART_NONE, "a UART behind a bus with three address cells");
}
memset(&u, 0xAA, sizeof u);
CHECK(!fdt_console_uart(0, &u) && u.kind == FDT_UART_NONE, "no devicetree at all");
b = mars("serial0:115200n8", 0, 1);
blob[0] = 0;
CHECK(!fdt_console_uart(b, &u) && u.kind == FDT_UART_NONE, "what is not a devicetree");
{
/* cut short at every length: never read past the end, never find a line in half a tree */
unsigned n, total, found = 0;
static unsigned char cut[8192];
b = mars("serial0:115200n8", 0, 1);
total = (unsigned)((blob[4] << 24) | (blob[5] << 16) | (blob[6] << 8) | blob[7]);
for (n = 40; n < total; n += 4) {
memcpy(cut, blob, total);
put32(cut + 36, n > 56 ? (n - 56 < st_len ? n - 56 : st_len) : 0); /* the structure block ends early */
if (fdt_console_uart(cut, &u) && !is(&u, FDT_UART_16550, 0x10000000ull, 2, 4)) found++;
}
CHECK(found == 0, "a structure block cut short at any length gives the right line or none: %u wrong", found);
}
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}