diff options
Diffstat (limited to 'common')
| -rw-r--r-- | common/console.c | 29 | ||||
| -rw-r--r-- | common/dtb_find.c | 178 | ||||
| -rw-r--r-- | common/main.c | 58 |
3 files changed, 255 insertions, 10 deletions
diff --git a/common/console.c b/common/console.c index bdd24c1..64e5128 100644 --- a/common/console.c +++ b/common/console.c @@ -54,14 +54,23 @@ #define UART_IBRD_VAL 13 #define UART_FBRD_VAL 44 -#define PL011_BASE 0x09000000UL +/* the base comes from the devicetree walk, the qemu default + * only covers the dev path before the walk runs + */ +static uintptr_t pl011_base = 0x09000000UL; + +void tb_console_set_pl011(uintptr_t base) +{ + if (base) + pl011_base = base; +} static int console_uart_ok; static void uart_putc(char c) { - volatile uint32_t *fr = (volatile uint32_t *)(PL011_BASE + UART_FR); - volatile uint32_t *dr = (volatile uint32_t *)(PL011_BASE + UART_DR); + volatile uint32_t *fr = (volatile uint32_t *)(pl011_base + UART_FR); + volatile uint32_t *dr = (volatile uint32_t *)(pl011_base + UART_DR); /* TXFF can happen mid line on slow consoles, wait it out */ while (*fr & UART_FR_TXFF) @@ -77,12 +86,12 @@ static void uart_putc(char c) */ static int uart_init(void) { - volatile uint32_t *cr = (volatile uint32_t *)(PL011_BASE + UART_CR); - volatile uint32_t *ibrd = (volatile uint32_t *)(PL011_BASE + UART_IBRD); - volatile uint32_t *fbrd = (volatile uint32_t *)(PL011_BASE + UART_FBRD); - volatile uint32_t *lcrh = (volatile uint32_t *)(PL011_BASE + UART_LCRH); - volatile uint32_t *imsc = (volatile uint32_t *)(PL011_BASE + UART_IMSC); - volatile uint32_t *icr = (volatile uint32_t *)(PL011_BASE + UART_ICR); + volatile uint32_t *cr = (volatile uint32_t *)(pl011_base + UART_CR); + volatile uint32_t *ibrd = (volatile uint32_t *)(pl011_base + UART_IBRD); + volatile uint32_t *fbrd = (volatile uint32_t *)(pl011_base + UART_FBRD); + volatile uint32_t *lcrh = (volatile uint32_t *)(pl011_base + UART_LCRH); + volatile uint32_t *imsc = (volatile uint32_t *)(pl011_base + UART_IMSC); + volatile uint32_t *icr = (volatile uint32_t *)(pl011_base + UART_ICR); /* disable, mask irq, clear pending, divisors, fifo, enable tx */ *cr = 0; @@ -95,7 +104,7 @@ static int uart_init(void) /* self test write, TXFF clearing means the uart answers */ uart_putc('\0'); - while (*(volatile uint32_t *)(PL011_BASE + UART_FR) & UART_FR_BUSY) + while (*(volatile uint32_t *)(pl011_base + UART_FR) & UART_FR_BUSY) ; return 0; diff --git a/common/dtb_find.c b/common/dtb_find.c new file mode 100644 index 0000000..29a67f3 --- /dev/null +++ b/common/dtb_find.c @@ -0,0 +1,178 @@ +/* + * dtb_find.c - locate nodes and read reg by walking the flat + * devicetree. the machine tells the firmware where its devices + * live, a bootloader that hardcodes the gic address breaks on + * the first board with a different map. + * + * the walk is the standard token scan, FDT_BEGIN_NODE with a + * matching name at any depth, then the reg property inside, + * the first address/size pair decoded per the parent's cell + * counts, which the root carries in #address-cells and + * #size-cells. + * + * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org> + */ + +#include <string.h> +#include <stdint.h> +#include <boot.h> + +#define FDT_BEGIN_NODE 1 +#define FDT_END_NODE 2 +#define FDT_PROP 3 +#define FDT_NOP 4 +#define FDT_END 9 + +static uint32_t be32(const void *p) +{ + const uint8_t *b = p; + + return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | + ((uint32_t)b[2] << 8) | (uint32_t)b[3]; +} + +static int name_eq(const char *a, const char *b) +{ + while (*a && *a != '@') { + if (*a != *b) + return 0; + a++; + b++; + } + return *b == '\0' || *b == '@'; +} + +/* + * find the first node whose name matches, at any depth. returns + * the offset of its FDT_BEGIN_NODE token or 0 when absent. + */ +static uint32_t fdt_find_node(uintptr_t dtb, const char *name) +{ + uint8_t *basep = (uint8_t *)dtb; + uint32_t off_struct = be32(basep + 8); + uint32_t totalsize = be32(basep + 4); + uint8_t *p = basep + off_struct; + + if (be32(basep) != 0xd00dfeed) + return 0; + + while (p < basep + totalsize) { + uint32_t token = be32(p); + + if (token == FDT_BEGIN_NODE) { + char *n = (char *)(p + 4); + size_t nlen = strlen(n) + 1; + + if (name_eq(n, name)) + return (uint32_t)(p - basep); + p += 4 + ((nlen + 3) & ~3); + } else if (token == FDT_PROP) { + uint32_t plen = be32(p + 4); + + p += 12 + ((plen + 3) & ~3); + } else if (token == FDT_END_NODE || + token == FDT_NOP) { + p += 4; + } else if (token == FDT_END) { + break; + } else { + return 0; + } + } + + return 0; +} + +/* + * read the first reg pair of a node at the given token offset, + * honoring the root cell counts. pairs of 2 or 4 cells are the + * ones machines carry, anything else fails. the caller reads + * more pairs off the returned cursor if it needs them. + */ +int tb_dtb_reg0(uintptr_t dtb, uint32_t node_off, uintptr_t *addr, + size_t *size) +{ + uint8_t *basep = (uint8_t *)dtb; + uint32_t off_strings = be32(basep + 12); + uint8_t *p = basep + node_off; + uint32_t totalsize = be32(basep + 4); + uint32_t ac = 2; + uint32_t sc = 2; + /* + * zero, the node's own FDT_BEGIN_NODE below brings it to + * one and the props inside sit at depth one. starting at + * one instead skips every prop in the node. + */ + int depth_open = 0; + + while (p < basep + totalsize) { + uint32_t token = be32(p); + + if (token == FDT_BEGIN_NODE) { + char *n = (char *)(p + 4); + size_t nlen = strlen(n) + 1; + + depth_open++; + p += 4 + ((nlen + 3) & ~3); + } else if (token == FDT_END_NODE) { + depth_open--; + if (!depth_open) + return -1; + p += 4; + } else if (token == FDT_PROP) { + uint32_t plen = be32(p + 4); + const char *pname = + (char *)basep + off_strings + be32(p + 8); + uint8_t *val = p + 12; + + if (depth_open == 1 && + strcmp(pname, "#address-cells") == 0) + ac = be32(val); + if (depth_open == 1 && + strcmp(pname, "#size-cells") == 0) + sc = be32(val); + if (depth_open == 1 && strcmp(pname, "reg") == 0) { + if (plen >= (ac + sc) * 4) { + uint64_t a = 0; + uint64_t s = 0; + + for (uint32_t i = 0; i < ac; i++) + a = (a << 32) | + be32(val + i * 4); + for (uint32_t i = 0; i < sc; i++) + s = (s << 32) | + be32(val + (ac + i) * 4); + *addr = (uintptr_t)a; + if (size) + *size = (size_t)s; + return 0; + } + return -1; + } + p += 12 + ((plen + 3) & ~3); + } else if (token == FDT_NOP) { + p += 4; + } else if (token == FDT_END) { + return -1; + } else { + return -1; + } + } + + return -1; +} + +/* + * the whole lookup in one call: find the node, read its first + * reg pair. + */ +int tb_dtb_find_reg0(uintptr_t dtb, const char *name, uintptr_t *addr, + size_t *size) +{ + uint32_t off = fdt_find_node(dtb, name); + + if (!off) + return -1; + + return tb_dtb_reg0(dtb, off, addr, size); +} diff --git a/common/main.c b/common/main.c index a8fdf58..b53c4b0 100644 --- a/common/main.c +++ b/common/main.c @@ -57,6 +57,7 @@ extern char __image_copy_end[]; extern void __NO_RETURN tb_boot_linux(uintptr_t ep, uintptr_t fw_arg); extern uintptr_t tb_dtb_relocate(uintptr_t dtb, void *scratch, size_t scratch_size, size_t grow); +extern int tb_gic_init(uintptr_t gicd, uintptr_t gicc); extern int tb_dtb_add_chosen_prop(uintptr_t dtb, const char *name, const void *val, size_t len); static size_t initrd_size; @@ -66,6 +67,23 @@ void tashaboot_main(uintptr_t fw_arg) struct tb_image img; int ret; + /* + * the console uart the machine named, before the first + * print. the base is the first reg pair of the pl011 + * node, the same walk the gic used, no board hardcodes. + */ + { + extern int tb_dtb_find_reg0(uintptr_t dtb, + const char *name, + uintptr_t *addr, size_t *size); + extern void tb_console_set_pl011(uintptr_t base); + uintptr_t uart = 0; + size_t usz = 0; + + if (tb_dtb_find_reg0(fw_arg, "pl011", &uart, &usz) == 0) + tb_console_set_pl011(uart); + } + if (tb_console_init()) return; @@ -165,6 +183,31 @@ void tashaboot_main(uintptr_t fw_arg) } fw_arg = newdtb; + + /* + * the interrupt controller the machine told us + * about, found by name, the reg pair read with the + * root cell counts. the gic goes into the defined + * off state before the kernel brings its own irq + * handling up. + */ + { + extern int tb_dtb_find_reg0(uintptr_t dtb, + const char *name, + uintptr_t *addr, + size_t *size); + uintptr_t gicd = 0; + uintptr_t gicc = 0; + size_t sz = 0; + + if (tb_dtb_find_reg0(fw_arg, "intc", &gicd, &sz) == 0 && + sz >= 0x10000) { + gicc = gicd + 0x10000; + tb_gic_init(gicd, gicc); + dprintf(ALWAYS, "gic: %lx off\n", + (unsigned long)gicd); + } + } } /* @@ -210,6 +253,21 @@ void tashaboot_main(uintptr_t fw_arg) dprintf(ALWAYS, "loaded %llu bytes at %lx, entry %lx\n", (unsigned long long)img.size, img.load, img.ep); + +#ifdef TB_TEST_SMC + { + register uint64_t r0 asm("x0") = 0x84000000; + register uint64_t r1 asm("x1") = 0; + register uint64_t r2 asm("x2") = 0; + register uint64_t r3 asm("x3") = 0; + + asm volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3)); + dprintf(ALWAYS, "smc conduit: psci version %lx\n", + (unsigned long)r0); + } +#endif + dprintf(ALWAYS, "jumping\n"); tb_boot_linux(img.ep, fw_arg); |
