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>
272 lines
13 KiB
C
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;
|
|
}
|