summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/dtb_patch.c142
-rw-r--r--common/load.c27
-rw-r--r--common/main.c46
3 files changed, 215 insertions, 0 deletions
diff --git a/common/dtb_patch.c b/common/dtb_patch.c
new file mode 100644
index 0000000..34d1a6c
--- /dev/null
+++ b/common/dtb_patch.c
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * dtb_patch.c - rewrite cpu-release-addr values in a flattened
+ * devicetree, in place, no libfdt, no structural change. the walk
+ * follows the devicetree specification structure, FDT_BEGIN_NODE
+ * then name then properties then children then FDT_END_NODE, all
+ * tokens and lengths big endian, everything 4 byte aligned.
+ *
+ * The bootloader owns the spin gates, the dtb names them, this
+ * writes the real addresses over the build time placeholders.
+ * The enable-method conversion and the placeholder properties are
+ * done at build time on the host, a firmware dtb is a fixed blob,
+ * only the gate addresses depend on where the image actually landed.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <endian.h>
+#include <dtb_patch.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 void put_be64(void *p, uint64_t v)
+{
+ uint8_t *b = p;
+ b[0] = (uint8_t)(v >> 56);
+ b[1] = (uint8_t)(v >> 48);
+ b[2] = (uint8_t)(v >> 40);
+ b[3] = (uint8_t)(v >> 32);
+ b[4] = (uint8_t)(v >> 24);
+ b[5] = (uint8_t)(v >> 16);
+ b[6] = (uint8_t)(v >> 8);
+ b[7] = (uint8_t)v;
+}
+
+static int name_eq(const char *node, const char *want)
+{
+ while (*node && *node != '@') {
+ if (*node != *want)
+ return 0;
+ node++;
+ want++;
+ }
+ return *want == '\0';
+}
+
+/*
+ * walk and rewrite. returns the number of cpu-release-addr values
+ * written, negative on a malformed blob.
+ */
+int tb_dtb_patch_spin_table(uintptr_t dtb, uintptr_t *gates, int ngates)
+{
+ uint8_t *base = (uint8_t *)dtb;
+ uint32_t off_struct = be32(base + 8);
+ uint32_t off_strings = be32(base + 12);
+ uint8_t *p = base + off_struct;
+ uint8_t *strings = base + off_strings;
+ const char *cur_cpu = NULL;
+ int in_cpus = 0;
+ int written = 0;
+ int depth = 0;
+
+ if (be32(base) != 0xd00dfeed)
+ return -1;
+
+ while (p < base + be32(base + 4)) {
+ uint32_t token = be32(p);
+
+ switch (token) {
+ case FDT_BEGIN_NODE: {
+ char *name = (char *)(p + 4);
+ size_t len = strlen(name) + 1;
+
+ p += 4 + ((len + 3) & ~3);
+ depth++;
+
+ if (depth == 2 && name_eq(name, "cpus")) {
+ in_cpus = 1;
+ } else if (depth == 2) {
+ in_cpus = 0;
+ } else if (in_cpus && depth == 3) {
+ cur_cpu = name;
+ }
+ break;
+ }
+ case FDT_END_NODE:
+ depth--;
+ p += 4;
+ break;
+ case FDT_PROP: {
+ uint32_t plen = be32(p + 4);
+ const char *pname = (char *)strings + be32(p + 8);
+ uint8_t *val = p + 12;
+
+ p += 12 + ((plen + 3) & ~3);
+
+ if (in_cpus && depth == 3 &&
+ strcmp(pname, "cpu-release-addr") == 0 &&
+ plen == 8 && cur_cpu) {
+ long idx = -1;
+ const char *at = strchr(cur_cpu, '@');
+
+ if (at) {
+ idx = 0;
+ while (*at >= '0' && *at <= '9') {
+ at++;
+ }
+ at = strchr(cur_cpu, '@') + 1;
+ while (*at >= '0' && *at <= '9') {
+ idx = idx * 10 + (*at - '0');
+ at++;
+ }
+ }
+ if (idx >= 0 && idx < ngates) {
+ put_be64(val, (uint64_t)gates[idx]);
+ written++;
+ }
+ }
+ break;
+ }
+ case FDT_NOP:
+ p += 4;
+ break;
+ case FDT_END:
+ return written;
+ }
+ }
+
+ return written;
+}
diff --git a/common/load.c b/common/load.c
index cb3b41d..56fc96a 100644
--- a/common/load.c
+++ b/common/load.c
@@ -55,3 +55,30 @@ int tb_load_semihosting(const char *fname, uintptr_t load_addr,
smh_close(fd);
return 0;
}
+
+/*
+ * the initrd path, no header, no placement math, bytes to the
+ * address the dtb /chosen already names.
+ */
+int tb_load_raw(const char *fname, uintptr_t load_addr)
+{
+ long fd, len, ret;
+
+ fd = smh_open(fname, MODE_READ | MODE_BINARY);
+ if (fd < 0)
+ return fd;
+
+ len = smh_flen(fd);
+ if (len < 0) {
+ smh_close(fd);
+ return len;
+ }
+
+ ret = smh_read(fd, (void *)load_addr, len);
+ smh_close(fd);
+
+ if (ret != len)
+ return -6;
+
+ return 0;
+}
diff --git a/common/main.c b/common/main.c
index 8d5ee63..0786027 100644
--- a/common/main.c
+++ b/common/main.c
@@ -37,6 +37,8 @@
#include <boot.h>
#define TB_VERSION "0.1"
+#define TB_INITRD_ADDR 0x46000000ULL
+#define TB_INITRD_FILE "initrd.cpio.gz"
extern int tb_console_init(void);
@@ -78,12 +80,56 @@ void tashaboot_main(uintptr_t fw_arg)
}
#endif
+ {
+ /* spin table gates into the dtb, one per cpu node */
+ extern unsigned long *tb_spin_gates_ptr;
+ extern int tb_dtb_patch_spin_table(uintptr_t dtb,
+ uintptr_t *gates,
+ int ngates);
+ int n;
+
+ if (tb_spin_gates_ptr) {
+ extern unsigned char tb_pen_stamps[8];
+ int c;
+
+ n = tb_dtb_patch_spin_table(fw_arg,
+ tb_spin_gates_ptr, 8);
+ dprintf(ALWAYS, "dtb: %d release addrs patched\n", n);
+
+ /* who made it to the pen */
+ for (c = 1; c < 8; c++) {
+ if (tb_pen_stamps[c])
+ break;
+ }
+ dprintf(ALWAYS, "pen: %s\n",
+ c < 8 ? "secondaries waiting" :
+ "no secondaries parked");
+ }
+ }
+
ret = tb_load_semihosting(TB_BOOTFILE, TB_LOAD_ADDR, &img);
if (ret) {
dprintf(ALWAYS, "load failed (%d), halting\n", ret);
platform_halt();
}
+ /*
+ * the initrd rides after the kernel, the dtb /chosen carries
+ * linux,initrd-start and -end, both already patched in place
+ * with this layout.
+ */
+ {
+ extern int tb_load_raw(const char *fname,
+ uintptr_t load_addr);
+ int r = tb_load_raw(TB_INITRD_FILE, TB_INITRD_ADDR);
+
+ if (r == 0)
+ dprintf(ALWAYS, "initrd at %lx\n",
+ (unsigned long)TB_INITRD_ADDR);
+ else
+ dprintf(ALWAYS, "no initrd (%d)\n", r);
+ }
+
dprintf(ALWAYS, "loaded %llu bytes at %lx, entry %lx\n",
(unsigned long long)img.size, img.load, img.ep);
dprintf(ALWAYS, "jumping\n");