[RFC PATCH v2 11/14] hw/arm: add TI K3 combined boot image parser

Wadim Mueller <[email protected]>
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
The K3 boot ROM consumes a "combined boot image" (tiboot3.bin): an X.509
certificate carrying a TI-specific boot extension that describes the
individual components (SYSFW, board config blobs, the R5 SPL), followed by
the component data.

Add a standalone parser for that container. It is deliberately free of any
device or machine state so it can be unit tested; the loader, which acts
on the result, comes with the SoC model.

Signed-off-by: Wadim Mueller <[email protected]>
---
 hw/arm/k3-bootrom-parse.c   | 250 ++++++++++++++++++++++++++++++++++++
 hw/arm/meson.build          |   1 +
 include/hw/arm/k3-bootrom.h |  43 +++++++
 3 files changed, 294 insertions(+)
 create mode 100644 hw/arm/k3-bootrom-parse.c
 create mode 100644 include/hw/arm/k3-bootrom.h

diff --git a/hw/arm/k3-bootrom-parse.c b/hw/arm/k3-bootrom-parse.c
new file mode 100644
index 0000000000..e3e428b6b1
--- /dev/null
+++ b/hw/arm/k3-bootrom-parse.c
@@ -0,0 +1,250 @@
+/*
+ * TI K3 boot-ROM emulation: X.509 combined boot image parser
+ *
+ * Parses the DER wrapper and ext_boot_info extension
+ * (OID 1.3.6.1.4.1.294.1.9) for payload type, destination and size.
+ * No signature verification, since QEMU models a GP device.
+ *
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/arm/k3-bootrom.h"
+
+typedef struct DerSlice {
+    const uint8_t *p;
+    const uint8_t *end;
+} DerSlice;
+
+static bool der_read_tlv(DerSlice *s, uint8_t *tag, DerSlice *content,
+                         Error **errp)
+{
+    uint64_t len;
+
+    if (s->end - s->p < 2) {
+        error_setg(errp, "k3-bootrom: truncated DER structure");
+        return false;
+    }
+    *tag = *s->p++;
+    len = *s->p++;
+    if (len & 0x80) {
+        unsigned n = len & 0x7f;
+
+        if (n == 0 || n > 4 || (size_t)(s->end - s->p) < n) {
+            error_setg(errp, "k3-bootrom: bad DER length encoding");
+            return false;
+        }
+        len = 0;
+        while (n--) {
+            len = (len << 8) | *s->p++;
+        }
+    }
+    if ((uint64_t)(s->end - s->p) < len) {
+        error_setg(errp, "k3-bootrom: DER length exceeds buffer");
+        return false;
+    }
+    content->p = s->p;
+    content->end = s->p + len;
+    s->p += len;
+    return true;
+}
+
+static bool der_read_uint(DerSlice *s, uint64_t *out, Error **errp)
+{
+    DerSlice c;
+    uint8_t tag;
+    uint64_t v = 0;
+
+    if (!der_read_tlv(s, &tag, &c, errp)) {
+        return false;
+    }
+    if (tag != 0x02) {
+        error_setg(errp, "k3-bootrom: expected INTEGER, got tag 0x%02x",
+                   tag);
+        return false;
+    }
+    if (c.p == c.end) {
+        error_setg(errp, "k3-bootrom: empty INTEGER");
+        return false;
+    }
+    if (c.end - c.p > 9 || (c.end - c.p == 9 && c.p[0] != 0)) {
+        error_setg(errp, "k3-bootrom: INTEGER too large");
+        return false;
+    }
+    for (const uint8_t *q = c.p; q < c.end; q++) {
+        v = (v << 8) | *q;
+    }
+    *out = v;
+    return true;
+}
+
+static bool der_read_u32(DerSlice *s, uint32_t *out, Error **errp)
+{
+    uint64_t v;
+
+    if (!der_read_uint(s, &v, errp)) {
+        return false;
+    }
+    if (v > UINT32_MAX) {
+        error_setg(errp, "k3-bootrom: integer field %" PRIu64
+                   " exceeds 32 bits", v);
+        return false;
+    }
+    *out = v;
+    return true;
+}
+
+/* Big-endian OCTET STRING (<= 8 bytes), as uint64. */
+static bool der_read_addr(DerSlice *s, uint64_t *out, Error **errp)
+{
+    DerSlice c;
+    uint8_t tag;
+    uint64_t v = 0;
+
+    if (!der_read_tlv(s, &tag, &c, errp)) {
+        return false;
+    }
+    if (tag != 0x04 || c.end - c.p > 8) {
+        error_setg(errp, "k3-bootrom: bad destAddr field (tag 0x%02x)",
+                   tag);
+        return false;
+    }
+    for (const uint8_t *q = c.p; q < c.end; q++) {
+        v = (v << 8) | *q;
+    }
+    *out = v;
+    return true;
+}
+
+/* DER TLV for TI ext_boot_info OID 1.3.6.1.4.1.294.1.9. */
+static const uint8_t k3_ext_boot_oid[] = {
+    0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x26, 0x01, 0x09
+};
+
+static const uint8_t *find_bytes(const uint8_t *hay, size_t hay_len,
+                                 const uint8_t *needle, size_t needle_len)
+{
+    if (hay_len < needle_len) {
+        return NULL;
+    }
+    for (size_t i = 0; i + needle_len <= hay_len; i++) {
+        if (memcmp(hay + i, needle, needle_len) == 0) {
+            return hay + i;
+        }
+    }
+    return NULL;
+}
+
+bool k3_bootrom_parse(const uint8_t *buf, size_t len, K3BootImage *out,
+                      Error **errp)
+{
+    DerSlice top = { buf, buf + len };
+    DerSlice cert, rest, octets, info;
+    const uint8_t *oid;
+    uint8_t tag;
+    uint64_t v;
+    uint64_t payload_off;
+
+    memset(out, 0, sizeof(*out));
+
+    if (!der_read_tlv(&top, &tag, &cert, errp)) {
+        return false;
+    }
+    if (tag != 0x30) {
+        error_setg(errp,
+                   "k3-bootrom: not an X.509 boot image (tag 0x%02x)", tag);
+        return false;
+    }
+    out->cert_len = cert.end - buf;
+
+    oid = find_bytes(cert.p, cert.end - cert.p, k3_ext_boot_oid,
+                     sizeof(k3_ext_boot_oid));
+    if (!oid) {
+        error_setg(errp, "k3-bootrom: ext_boot_info extension "
+                   "(OID 1.3.6.1.4.1.294.1.9) not found");
+        return false;
+    }
+    rest.p = oid + sizeof(k3_ext_boot_oid);
+    rest.end = cert.end;
+
+    /* Optional BOOLEAN 'critical', between OID and extnValue. */
+    if (rest.p < rest.end && rest.p[0] == 0x01) {
+        DerSlice skip;
+
+        if (!der_read_tlv(&rest, &tag, &skip, errp)) {
+            return false;
+        }
+    }
+    if (!der_read_tlv(&rest, &tag, &octets, errp)) {
+        return false;
+    }
+    if (tag != 0x04) {
+        error_setg(errp, "k3-bootrom: extension value is not an "
+                   "OCTET STRING (tag 0x%02x)", tag);
+        return false;
+    }
+    if (!der_read_tlv(&octets, &tag, &info, errp)) {
+        return false;
+    }
+    if (tag != 0x30) {
+        error_setg(errp, "k3-bootrom: ext_boot_info is not a SEQUENCE");
+        return false;
+    }
+
+    if (!der_read_uint(&info, &out->ext_img_size, errp)) {
+        return false;
+    }
+    if (!der_read_uint(&info, &v, errp)) {
+        return false;
+    }
+    if (v == 0 || v > K3_BOOTROM_MAX_COMPS) {
+        error_setg(errp, "k3-bootrom: unsupported component count %"
+                   PRIu64, v);
+        return false;
+    }
+    out->num_comps = v;
+
+    payload_off = out->cert_len;
+    for (uint32_t i = 0; i < out->num_comps; i++) {
+        K3BootComponent *c = &out->comps[i];
+        DerSlice comp;
+
+        if (!der_read_tlv(&info, &tag, &comp, errp)) {
+            return false;
+        }
+        if (tag != 0x30) {
+            error_setg(errp, "k3-bootrom: component %u is not a SEQUENCE",
+                       i);
+            return false;
+        }
+        if (!der_read_u32(&comp, &c->comp_type, errp)) {
+            return false;
+        }
+        if (!der_read_u32(&comp, &c->boot_core, errp)) {
+            return false;
+        }
+        if (!der_read_u32(&comp, &c->comp_opts, errp)) {
+            return false;
+        }
+        if (!der_read_addr(&comp, &c->dest_addr, errp)) {
+            return false;
+        }
+        if (!der_read_u32(&comp, &c->comp_size, errp)) {
+            return false;
+        }
+        /* shaType / shaValue are not needed for the loading. */
+        /* payload_off is bounded by len below, so it fits into size_t. */
+        c->payload_offset = payload_off;
+        payload_off += c->comp_size;
+        if (payload_off > len) {
+            error_setg(errp, "k3-bootrom: image truncated (components "
+                       "need %" PRIu64 " bytes, file has %zu)",
+                       payload_off, len);
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 8ee5307a91..22691afdd8 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -109,6 +109,7 @@ arm_common_ss.add(when: 'CONFIG_STRONGARM', if_true: files('strongarm.c'))
 arm_common_ss.add(when: 'CONFIG_SX1', if_true: files('omap_sx1.c'))
 arm_common_ss.add(when: 'CONFIG_VERSATILE', if_true: files('versatilepb.c'))
 arm_common_ss.add(when: 'CONFIG_VEXPRESS', if_true: files('vexpress.c'))
+arm_common_ss.add(when: 'CONFIG_TI_AM64X', if_true: files('k3-bootrom-parse.c'))
 
 arm_common_ss.add(when: ['CONFIG_AXIADO_SOC', 'TARGET_AARCH64'], if_true: files(
   'ax3000-soc.c'))
diff --git a/include/hw/arm/k3-bootrom.h b/include/hw/arm/k3-bootrom.h
new file mode 100644
index 0000000000..cf60efc047
--- /dev/null
+++ b/include/hw/arm/k3-bootrom.h
@@ -0,0 +1,43 @@
+/*
+ * TI K3 boot-ROM (RBL) emulation - X.509 combined image loading
+ *
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_ARM_K3_BOOTROM_H
+#define HW_ARM_K3_BOOTROM_H
+
+#include "qapi/error.h"
+
+#define K3_BOOTROM_MAX_COMPS 8
+
+/* comp_type values from TI combined-image certificate. */
+#define K3_COMP_TYPE_SBL        1
+#define K3_COMP_TYPE_SYSFW      2
+#define K3_COMP_TYPE_SYSFW_DATA 18
+
+typedef struct K3BootComponent {
+    uint32_t comp_type;
+    uint32_t boot_core;
+    uint32_t comp_opts;
+    uint64_t dest_addr;
+    uint32_t comp_size;
+    size_t payload_offset;
+} K3BootComponent;
+
+typedef struct K3BootImage {
+    uint32_t num_comps;
+    uint64_t ext_img_size;
+    size_t cert_len;
+    K3BootComponent comps[K3_BOOTROM_MAX_COMPS];
+} K3BootImage;
+
+bool k3_bootrom_parse(const uint8_t *buf, size_t len, K3BootImage *out,
+                      Error **errp);
+
+typedef struct TIAM64xState TIAM64xState;
+void k3_bootrom_load(TIAM64xState *soc, const char *filename, Error **errp);
+
+#endif
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.