diff options
| author | Bradley Morgan <brads@mainlining.org> | 2026-10-04 00:32:49 +0000 |
|---|---|---|
| committer | Bradley Morgan <brads@mainlining.org> | 2026-10-04 00:32:49 +0000 |
| commit | d72c2f898ed4c17aba0080c8bf6a0173cca940dc (patch) | |
| tree | 7ecfd6d2b7095e61e3ce7a5ee35a8772822ca6a8 /common/dtb_patch.c | |
| parent | 4f700c280b55c551047c00c71ef14aacf5830872 (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.c | 146 |
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; +} |
