From: Wadim Mueller <wafgo01@gmail.com>
To: qemu-devel@nongnu.org
Cc: qemu-arm@nongnu.org, "Peter Maydell" <peter.maydell@linaro.org>,
"Philippe Mathieu-Daudé" <philmd@mailo.com>,
"Bin Meng" <bmeng.cn@gmail.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Fabiano Rosas" <farosas@suse.de>,
"Wadim Mueller" <wafgo01@gmail.com>
Subject: [RFC PATCH v2 11/14] hw/arm: add TI K3 combined boot image parser
Date: Thu, 20 Aug 2026 14:48:11 +0200 [thread overview]
Message-ID: <20260820124824.618671-12-wafgo01@gmail.com> (raw)
In-Reply-To: <20260820124824.618671-1-wafgo01@gmail.com>
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 <wafgo01@gmail.com>
---
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 <wafgo01@gmail.com>
+ *
+ * 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 <wafgo01@gmail.com>
+ *
+ * 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
next prev parent reply other threads:[~2026-08-20 12:50 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 01/14] hw/i2c/omap_i2c: add a dedicated CONFIG_OMAP_I2C symbol Wadim Mueller
2026-08-24 15:39 ` Alex Bennée
2026-08-20 12:48 ` [RFC PATCH v2 02/14] hw/i2c/omap_i2c: implement soft reset and NACK reporting Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 03/14] hw/sd/sdhci: complete non-interrupt ADMA descriptor chains in one pass Wadim Mueller
2026-08-24 15:53 ` Alex Bennée
2026-08-20 12:48 ` [RFC PATCH v2 04/14] hw/char: add TI AM64x UART model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 05/14] hw/timer: add TI K3 DMTimer model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 06/14] hw/misc: add TI K3 CTRL_MMR, GTC, DDRSS, SDHCI PHY and TRNG models Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 07/14] hw/misc: add TI RAT (region address translation) model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 08/14] hw/misc: add TI mailbox (IPC) model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 09/14] hw/misc: add TI K3 secure proxy model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 10/14] hw/misc: add TI DMSC (TI-SCI system controller) model Wadim Mueller
2026-08-20 12:48 ` Wadim Mueller [this message]
2026-08-20 12:48 ` [RFC PATCH v2 12/14] hw/arm: add TI AM64x SoC model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 13/14] hw/arm: add the am64-virt machine Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 14/14] tests: add AM64x unit, qtest and functional tests Wadim Mueller
2026-08-20 16:25 ` Alex Bennée
2026-08-22 21:04 ` Wadim Mueller
2026-08-24 15:39 ` Alex Bennée
2026-08-21 10:26 ` [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Alex Bennée
2026-08-22 21:06 ` Wadim Mueller
2026-08-21 16:35 ` Nick Huang
2026-08-22 21:07 ` Wadim Mueller
2026-08-22 5:22 ` Bin Meng
2026-08-22 21:13 ` Wadim Mueller
2026-08-24 11:51 ` Nick Huang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260820124824.618671-12-wafgo01@gmail.com \
--to=wafgo01@gmail.com \
--cc=bmeng.cn@gmail.com \
--cc=farosas@suse.de \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@mailo.com \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox