/* 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-/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 #include #include #include 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) { /* 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)); 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, 0, 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, 0, 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; }