summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/console.c29
-rw-r--r--common/dtb_find.c178
-rw-r--r--common/main.c58
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);