diff options
Diffstat (limited to 'common')
| -rw-r--r-- | common/dtb_patch.c | 142 | ||||
| -rw-r--r-- | common/load.c | 27 | ||||
| -rw-r--r-- | common/main.c | 46 |
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"); |
