summaryrefslogtreecommitdiff
path: root/common/dtb_patch.c
diff options
context:
space:
mode:
authorBradley Morgan <brads@mainlining.org>2026-10-04 00:32:49 +0000
committerBradley Morgan <brads@mainlining.org>2026-10-04 00:32:49 +0000
commitd72c2f898ed4c17aba0080c8bf6a0173cca940dc (patch)
tree7ecfd6d2b7095e61e3ce7a5ee35a8772822ca6a8 /common/dtb_patch.c
parent4f700c280b55c551047c00c71ef14aacf5830872 (diff)
tashaboot: image header, EL split, self located load address
qemu -kernel parses a raw arm64 blob as a linux Image and enters at RAMBASE plus whatever text_offset it guesses out of the garbage, 0x80000 in our case. every wild PC at image+0x80000 in the debug logs was our own code running from the wrong address. the binary now carries a real Image header: code0 branches over it, magic ARM\x64 at 0x38, text_offset 0, image_size stamped after objcopy by tools/fillsize.py. the runtime also split by exception level. the C body runs at EL1, the semihosting hlt is answered by qemu only from EL2, so the EL2 vector replays the trap there and erets home with the result. the kernel handoff hvc raises back to EL2 where booting.rst wants it, the same vector slot dispatches PSCI hvc from the kernel, boot handoff and semihosting by EC and function id. the payload load address was hardcoded 0x40200000, which is where qemu placed our image, so the load overwrote the running bootloader with kernel bytes mid flight. the load address is now __image_copy_end plus 16MB, wherever the image actually runs. receipt: run /init, tashaboot linux userspace reached, cores: 4, busybox shell on a 4 cpu virt machine with initrd.
Diffstat (limited to 'common/dtb_patch.c')
-rw-r--r--common/dtb_patch.c146
1 files changed, 146 insertions, 0 deletions
diff --git a/common/dtb_patch.c b/common/dtb_patch.c
index 34d1a6c..cd9a1f3 100644
--- a/common/dtb_patch.c
+++ b/common/dtb_patch.c
@@ -32,6 +32,15 @@ static uint32_t be32(const void *p)
((uint32_t)b[2] << 8) | (uint32_t)b[3];
}
+static void put_be32(void *p, uint32_t v)
+{
+ uint8_t *b = p;
+ b[0] = (uint8_t)(v >> 24);
+ b[1] = (uint8_t)(v >> 16);
+ b[2] = (uint8_t)(v >> 8);
+ b[3] = (uint8_t)v;
+}
+
static void put_be64(void *p, uint64_t v)
{
uint8_t *b = p;
@@ -57,6 +66,76 @@ static int name_eq(const char *node, const char *want)
}
/*
+ * rewrite /memory reg with the RAM the bootloader actually sees.
+ * the value is two u32 cells, base and size, addresses above 4GB
+ * need the parent #address-cells respected, virt is below 4GB and
+ * 2 cells for size. returns 0 on success.
+ */
+int tb_dtb_patch_memory(uintptr_t dtb, uint64_t base, uint64_t size)
+{
+ uint8_t *basep = (uint8_t *)dtb;
+ uint32_t off_struct = be32(basep + 8);
+ uint32_t off_strings = be32(basep + 12);
+ uint8_t *p = basep + off_struct;
+ uint8_t *strings = basep + off_strings;
+ const char *cur_node = NULL;
+ int depth = 0;
+
+ if (be32(basep) != 0xd00dfeed)
+ return -1;
+
+ while (p < basep + be32(basep + 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++;
+ cur_node = 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 (depth == 2 && name_eq(cur_node, "memory") &&
+ strcmp(pname, "reg") == 0 && plen >= 16) {
+ /*
+ * #address-cells 2, #size-cells 2, the
+ * reg is four cells, base hi lo and
+ * size hi lo, below 4GB the hi cells
+ * are zero.
+ */
+ put_be32(val, (uint32_t)(base >> 32));
+ put_be32(val + 4, (uint32_t)base);
+ put_be32(val + 8, (uint32_t)(size >> 32));
+ put_be32(val + 12, (uint32_t)size);
+ return 0;
+ }
+ break;
+ }
+ case FDT_NOP:
+ p += 4;
+ break;
+ case FDT_END:
+ return -2;
+ }
+ }
+
+ return -3;
+}
+
+/*
* walk and rewrite. returns the number of cpu-release-addr values
* written, negative on a malformed blob.
*/
@@ -140,3 +219,70 @@ int tb_dtb_patch_spin_table(uintptr_t dtb, uintptr_t *gates, int ngates)
return written;
}
+
+/*
+ * tell the kernel where the initrd landed. /chosen is created by
+ * the machine firmware, the two cells exist when an initrd was
+ * already staged, we overwrite them in place. depth 2 under the
+ * root, node name "chosen".
+ */
+int tb_dtb_patch_initrd(uintptr_t dtb, uint64_t start, uint64_t end)
+{
+ uint8_t *basep = (uint8_t *)dtb;
+ uint32_t off_struct = be32(basep + 8);
+ uint32_t off_strings = be32(basep + 12);
+ uint8_t *p = basep + off_struct;
+ uint8_t *strings = basep + off_strings;
+ const char *cur_node = NULL;
+ int depth = 0;
+
+ if (be32(basep) != 0xd00dfeed)
+ return -1;
+
+ while (p < basep + be32(basep + 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++;
+ cur_node = 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 (depth == 2 && name_eq(cur_node, "chosen") &&
+ strcmp(pname, "linux,initrd-start") == 0 &&
+ plen >= 8) {
+ put_be64(val, start);
+ }
+ if (depth == 2 && name_eq(cur_node, "chosen") &&
+ strcmp(pname, "linux,initrd-end") == 0 &&
+ plen >= 8) {
+ put_be64(val, end);
+ return 0;
+ }
+ break;
+ }
+ case FDT_NOP:
+ p += 4;
+ break;
+ case FDT_END:
+ return -2;
+ }
+ }
+
+ return -2;
+}