* [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine
@ 2026-08-20 12:48 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
` (13 more replies)
0 siblings, 14 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
Add support for the TI AM64x (Sitara AM6442) SoC and a board around it,
``am64-virt``. The model is complete enough to run the stock TI boot chain
end to end:
ROM boot (tiboot3.bin) -> R5 SPL -> TF-A/OP-TEE -> U-Boot -> Linux SMP
This is a resend: I posted a first RFC back in May [1] but got no feedback
on it. Since then was the series rebased onto actual master, reworked for
style and split into reviewable pieces, so I send it out again in the hope
of getting some review.
RFC because I would like feedback on the overall approach before polishing
further; see the open questions at the end.
[1] https://lore.kernel.org/qemu-devel/20260528211210.74266-1-wafgo01@gmail.com/
What is modelled
================
The SoC is heterogeneous, and the model reflects that: a Cortex-A53
cluster behind a GICv3 (GIC-500) at the real addresses, the Cortex-R5F
MCU boot core, and the Cortex-M4F, each with their own RAT (region
address translation) view of the memory map.
The interesting part is the DMSC. On real silicon it is a hidden core
running TI's system firmware (SYSFW) which answers TI-SCI requests for
clocks, power, firewalls and processor boot control from every other core.
Rather than trying to execute the vendor blob, patch 10 models the DMSC
as a TI-SCI endpoint sitting on top of the secure proxy model (patch 9).
That is what actually starts the A53 and M4F cores when the R5 SPL asks
for it, and what TF-A, OP-TEE, U-Boot and Linux talk to for clock/power
management.
The remaining devices (patches 4-8) are the supporting cast: the UART,
the K3 DMTimer, mailboxes, and register-level stubs for CTRL_MMR
(including the DEVSTAT boot pins), GTC, DDRSS, the SDHCI PHY wrapper and
the TRNG. The
stubs implement the handshakes, which firmware polls for, not the
underlying hardware.
Patches 1-3 are prerequisites that touch existing code:
1-2 give the OMAP I2C model its own Kconfig symbol so it can be reused
outside the OMAP1 SoC, and implement soft reset and NACK reporting
3 fixes SDHCI ADMA chain processing; without it, transfer rate
depends from the virtual clock instead of on the guest's
programming. Patch 14 contains a qtest that fails without it.
To patch 3: it is only needed as long as Bin Meng's SDHCI series [2] (on
top of the SDMA boundary fix [3]) is not merged. I posted the same fix
standalone in August, Bin showed then, that his series already covers
the AM64x case, and I have withdrawn it there [4]. It is kept here only
so that this series works on actual master - please drop patch 3
together with the pacing qtest in patch 14 as soon as [2] lands. A
version of that qtest for the Icicle Kit was posted separately [5].
[2] https://patchwork.ozlabs.org/project/qemu-devel/list/?series=515264
[3] https://patchwork.ozlabs.org/project/qemu-devel/list/?series=513930
[4] https://lore.kernel.org/qemu-devel/20260810124519.34501-1-wafgo01@gmail.com/
[5] https://lore.kernel.org/qemu-devel/20260813123210.28987-1-wafgo01@gmail.com/
Testing
=======
Patch 14 adds:
* a unit test for the combined-boot-image parser
* qtests for the machine topology, GICv3 layout, SMP handling and the
DMSC TI-SCI request/response path
* an I2C qtest (soft reset, NACK, and a read from an at24c EEPROM which
the test attaches with -device)
* the SDHCI ADMA pacing reproducer
* a functional test that builds a synthetic tiboot3.bin containing a
bare-metal R5 payload and checks it runs from the certified entry
point. Setting QEMU_TEST_TIBOOT3 boots a real image instead.
Migration is not supported yet: none of the new devices has a VMState.
I left it out for the RFC, but can add it if you want it before merge.
The series was built and tested on top of ae4f344320. Every patch builds
on its own, and checkpatch reports no errors.
Open questions
==============
* pc-bios/dtb/am64-virt.dtb: patch 13 checks in a generated DTB (with
its source) for convenience. I am not sure a machine-specific blob
belongs in pc-bios; happy to drop it and have the functional test
generate it with dtc instead, or drop the Linux-boot part of the test.
* Splitting the DMSC: hw/misc/ti-dmsc.c is large because TI-SCI is
large. It could be split per message class (clocks / devices /
processor boot / security) if reviewers prefer, at the cost of a
non-functional intermediate state.
* Only one of the four R5F cores is modelled. mc->max_cpus reserves
room for the rest. Because the SoC realizes M4F/R5F vCPUs in addition
to the A53s and every vCPU needs a TCG context slot inside
smp.max_cpus, the machine defaults to the full vCPU budget. I would
appreciate a better idea than the actual "fail early with an
explanatory message" handling when the user passes a too-small -smp.
* The register-level stubs (DDRSS in particular) fake success rather
than modelling the hardware. That is deliberate, but tell me if you
would rather see them as plain unimplemented-device windows plus
firmware quirks elsewhere.
Wadim Mueller (14):
hw/i2c/omap_i2c: add a dedicated CONFIG_OMAP_I2C symbol
hw/i2c/omap_i2c: implement soft reset and NACK reporting
hw/sd/sdhci: complete non-interrupt ADMA descriptor chains in one pass
hw/char: add TI AM64x UART model
hw/timer: add TI K3 DMTimer model
hw/misc: add TI K3 CTRL_MMR, GTC, DDRSS, SDHCI PHY and TRNG models
hw/misc: add TI RAT (region address translation) model
hw/misc: add TI mailbox (IPC) model
hw/misc: add TI K3 secure proxy model
hw/misc: add TI DMSC (TI-SCI system controller) model
hw/arm: add TI K3 combined boot image parser
hw/arm: add TI AM64x SoC model
hw/arm: add the am64-virt machine
tests: add AM64x unit, qtest and functional tests
MAINTAINERS | 22 +
docs/system/arm/am64.rst | 74 +
docs/system/target-arm.rst | 1 +
hw/arm/Kconfig | 26 +
hw/arm/am64-virt.c | 347 ++++
hw/arm/k3-bootrom-parse.c | 250 +++
hw/arm/k3-bootrom.c | 125 ++
hw/arm/meson.build | 4 +
hw/arm/ti-am64x.c | 1223 ++++++++++++
hw/arm/trace-events | 4 +
hw/char/Kconfig | 4 +
hw/char/meson.build | 1 +
hw/char/ti-am64-uart.c | 109 ++
hw/i2c/Kconfig | 4 +
hw/i2c/meson.build | 2 +-
hw/i2c/omap_i2c.c | 234 ++-
hw/misc/Kconfig | 27 +
hw/misc/meson.build | 10 +
hw/misc/ti-dmsc.c | 1725 +++++++++++++++++
hw/misc/ti-k3-ctrlmmr.c | 103 +
hw/misc/ti-k3-ddrss.c | 98 +
hw/misc/ti-k3-gtc.c | 100 +
hw/misc/ti-k3-sdhci-phy.c | 91 +
hw/misc/ti-k3-trng.c | 139 ++
hw/misc/ti-mailbox.c | 566 ++++++
hw/misc/ti-rat.c | 290 +++
hw/misc/ti-sec-proxy.c | 446 +++++
hw/misc/trace-events | 62 +
hw/sd/sdhci-internal.h | 9 +
hw/sd/sdhci.c | 23 +-
hw/timer/Kconfig | 3 +
hw/timer/meson.build | 1 +
hw/timer/ti-k3-dmtimer.c | 177 ++
include/hw/arm/k3-bootrom.h | 43 +
include/hw/arm/ti-am64x.h | 86 +
include/hw/char/ti-am64-uart.h | 28 +
include/hw/misc/ti-dmsc.h | 661 +++++++
include/hw/misc/ti-k3-ctrlmmr.h | 26 +
include/hw/misc/ti-k3-ddrss.h | 26 +
include/hw/misc/ti-k3-gtc.h | 23 +
include/hw/misc/ti-k3-sdhci-phy.h | 26 +
include/hw/misc/ti-k3-trng.h | 35 +
include/hw/misc/ti-mailbox.h | 44 +
include/hw/misc/ti-rat.h | 51 +
include/hw/misc/ti-sec-proxy.h | 108 ++
include/hw/timer/ti-k3-dmtimer.h | 30 +
pc-bios/dtb/am64-virt.dtb | Bin 0 -> 6116 bytes
pc-bios/dtb/am64-virt.dts | 292 +++
tests/functional/aarch64/meson.build | 2 +
tests/functional/aarch64/test_am64_bootrom.py | 137 ++
tests/qtest/am64-adma-pacing-test.c | 243 +++
tests/qtest/am64-virt-test.c | 502 +++++
tests/qtest/meson.build | 1 +
tests/qtest/ti-am64-i2c-test.c | 137 ++
tests/unit/meson.build | 1 +
tests/unit/test-k3-bootrom.c | 263 +++
56 files changed, 9060 insertions(+), 5 deletions(-)
create mode 100644 docs/system/arm/am64.rst
create mode 100644 hw/arm/am64-virt.c
create mode 100644 hw/arm/k3-bootrom-parse.c
create mode 100644 hw/arm/k3-bootrom.c
create mode 100644 hw/arm/ti-am64x.c
create mode 100644 hw/char/ti-am64-uart.c
create mode 100644 hw/misc/ti-dmsc.c
create mode 100644 hw/misc/ti-k3-ctrlmmr.c
create mode 100644 hw/misc/ti-k3-ddrss.c
create mode 100644 hw/misc/ti-k3-gtc.c
create mode 100644 hw/misc/ti-k3-sdhci-phy.c
create mode 100644 hw/misc/ti-k3-trng.c
create mode 100644 hw/misc/ti-mailbox.c
create mode 100644 hw/misc/ti-rat.c
create mode 100644 hw/misc/ti-sec-proxy.c
create mode 100644 hw/timer/ti-k3-dmtimer.c
create mode 100644 include/hw/arm/k3-bootrom.h
create mode 100644 include/hw/arm/ti-am64x.h
create mode 100644 include/hw/char/ti-am64-uart.h
create mode 100644 include/hw/misc/ti-dmsc.h
create mode 100644 include/hw/misc/ti-k3-ctrlmmr.h
create mode 100644 include/hw/misc/ti-k3-ddrss.h
create mode 100644 include/hw/misc/ti-k3-gtc.h
create mode 100644 include/hw/misc/ti-k3-sdhci-phy.h
create mode 100644 include/hw/misc/ti-k3-trng.h
create mode 100644 include/hw/misc/ti-mailbox.h
create mode 100644 include/hw/misc/ti-rat.h
create mode 100644 include/hw/misc/ti-sec-proxy.h
create mode 100644 include/hw/timer/ti-k3-dmtimer.h
create mode 100644 pc-bios/dtb/am64-virt.dtb
create mode 100644 pc-bios/dtb/am64-virt.dts
create mode 100644 tests/functional/aarch64/test_am64_bootrom.py
create mode 100644 tests/qtest/am64-adma-pacing-test.c
create mode 100644 tests/qtest/am64-virt-test.c
create mode 100644 tests/qtest/ti-am64-i2c-test.c
create mode 100644 tests/unit/test-k3-bootrom.c
base-commit: ae4f3443209ab154b48b706a146e5f557ab147cb
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH v2 01/14] hw/i2c/omap_i2c: add a dedicated CONFIG_OMAP_I2C symbol
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 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 02/14] hw/i2c/omap_i2c: implement soft reset and NACK reporting Wadim Mueller
` (12 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
The OMAP I2C controller model was only buildable as part of the full OMAP1
SoC (CONFIG_OMAP). The same IP is used by the TI K3 family (AM64x and
friends), which shares nothing else with OMAP1.
Give the device its own Kconfig symbol and select it from CONFIG_OMAP, so
that other SoCs can reuse the model without pulling in OMAP1.
No functional change.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/arm/Kconfig | 1 +
hw/i2c/Kconfig | 4 ++++
hw/i2c/meson.build | 2 +-
3 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 260d2f0751..b2dc2714c0 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -137,6 +137,7 @@ config OMAP
bool
select FRAMEBUFFER
select I2C
+ select OMAP_I2C
select PFLASH_CFI01
select SD
select SERIAL_MM
diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
index 0766130b59..e7c0e2f8bf 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -54,3 +54,7 @@ config PMBUS
config BCM2835_I2C
bool
select I2C
+
+config OMAP_I2C
+ bool
+ select I2C
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index 88aea35662..760724e7bb 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -14,7 +14,7 @@ i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c'))
i2c_ss.add(when: 'CONFIG_DESIGNWARE_I2C', if_true: files('designware_i2c.c'))
i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c'))
i2c_ss.add(when: 'CONFIG_ARM_SBCON_I2C', if_true: files('arm_sbcon_i2c.c'))
-i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c'))
+i2c_ss.add(when: 'CONFIG_OMAP_I2C', if_true: files('omap_i2c.c'))
i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_i2c.c'))
i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c'))
i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c'))
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 02/14] hw/i2c/omap_i2c: implement soft reset and NACK reporting
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-20 12:48 ` 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
` (11 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
Add the pieces of the controller that the TI K3 ROM/SPL and the Linux
omap-i2c driver actually exercise:
- OMAP_I2C_SYSC.SRST triggers a soft reset and SYSS.RDONE reports its
completion, instead of the register being a plain scratch value.
- A transfer to an address that nobody acknowledges raises STAT.NACK and
ends the transfer, rather than being silently completed.
This allows to let a guest probe an I2C bus and correctly conclude that a
device is absent.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/i2c/omap_i2c.c | 234 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 232 insertions(+), 2 deletions(-)
diff --git a/hw/i2c/omap_i2c.c b/hw/i2c/omap_i2c.c
index d6e28a1a89..e56f75d6f5 100644
--- a/hw/i2c/omap_i2c.c
+++ b/hw/i2c/omap_i2c.c
@@ -37,6 +37,7 @@ struct OMAPI2CState {
I2CBus *bus;
uint8_t revision;
+ uint8_t mmio_version;
void *iclk;
void *fclk;
@@ -58,6 +59,61 @@ struct OMAPI2CState {
#define OMAP2_INTR_REV 0x34
#define OMAP2_GC_REV 0x34
+/*
+ * MMIO register layout selector. The classic OMAP1/OMAP2 "IP V1" map is the
+ * default and is what every existing OMAP board relies on. "IP V2" is the
+ * OMAP4-and-later layout ("ti,omap4-i2c" / "ti,am64-i2c"), which relocates the
+ * registers and adds the IRQSTATUS_RAW / IRQENABLE_SET / IRQENABLE_CLR set.
+ * The transfer/reset/NACK engine is shared; only the address decode differs.
+ */
+#define OMAP_I2C_MMIO_V1 0
+#define OMAP_I2C_MMIO_V2 2
+
+/* IP V2 register offsets, as used from OMAP4 onwards. */
+#define OMAP_I2C_V2_REVNB_LO 0x00
+#define OMAP_I2C_V2_REVNB_HI 0x04
+#define OMAP_I2C_V2_SYSC 0x10
+#define OMAP_I2C_V2_IRQSTATUS_RAW 0x24
+#define OMAP_I2C_V2_IRQSTATUS 0x28
+#define OMAP_I2C_V2_IRQENABLE_SET 0x2c
+#define OMAP_I2C_V2_IRQENABLE_CLR 0x30
+#define OMAP_I2C_V2_WE 0x34
+#define OMAP_I2C_V2_SYSS 0x90
+#define OMAP_I2C_V2_BUF 0x94
+#define OMAP_I2C_V2_CNT 0x98
+#define OMAP_I2C_V2_DATA 0x9c
+#define OMAP_I2C_V2_CON 0xa4
+#define OMAP_I2C_V2_OA 0xa8
+#define OMAP_I2C_V2_SA 0xac
+#define OMAP_I2C_V2_PSC 0xb0
+#define OMAP_I2C_V2_SCLL 0xb4
+#define OMAP_I2C_V2_SCLH 0xb8
+#define OMAP_I2C_V2_SYSTEST 0xbc
+#define OMAP_I2C_V2_BUFSTAT 0xc0
+
+/*
+ * Translate an IP-V2 offset for a register whose semantics are identical to
+ * the V1 model into the V1 offset the shared read/write switch decodes.
+ * Returns -1 for offsets that have no direct V1 equivalent (those are handled
+ * inline by the V2 front-end).
+ */
+static int omap_i2c_v2_to_v1(int offset)
+{
+ switch (offset) {
+ case OMAP_I2C_V2_BUF: return 0x14;
+ case OMAP_I2C_V2_CNT: return 0x18;
+ /* DATA (0x9c) is handled inline byte-wise by the V2 front-end. */
+ case OMAP_I2C_V2_CON: return 0x24;
+ case OMAP_I2C_V2_OA: return 0x28;
+ case OMAP_I2C_V2_SA: return 0x2c;
+ case OMAP_I2C_V2_PSC: return 0x30;
+ case OMAP_I2C_V2_SCLL: return 0x34;
+ case OMAP_I2C_V2_SCLH: return 0x38;
+ case OMAP_I2C_V2_SYSTEST: return 0x3c;
+ default: return -1;
+ }
+}
+
static void omap_i2c_interrupts_update(OMAPI2CState *s)
{
qemu_set_irq(s->irq, s->stat & s->mask);
@@ -162,6 +218,83 @@ static uint32_t omap_i2c_read(void *opaque, hwaddr addr)
int offset = addr & OMAP_MPUI_REG_MASK;
uint16_t ret;
+ if (s->mmio_version == OMAP_I2C_MMIO_V2) {
+ switch (offset) {
+ case OMAP_I2C_V2_REVNB_LO:
+ return s->revision;
+ case OMAP_I2C_V2_REVNB_HI:
+ return 0;
+ case OMAP_I2C_V2_SYSC:
+ return 0;
+ case OMAP_I2C_V2_IRQSTATUS_RAW:
+ case OMAP_I2C_V2_IRQSTATUS: /* STAT mirrors IRQSTATUS_RAW */
+ return s->stat | (i2c_bus_busy(s->bus) << 12);
+ case OMAP_I2C_V2_IRQENABLE_SET:
+ case OMAP_I2C_V2_IRQENABLE_CLR:
+ return s->mask;
+ case OMAP_I2C_V2_WE:
+ return 0;
+ case OMAP_I2C_V2_SYSS:
+ /* reset is instantaneous in the model: RDONE always reads set */
+ return 1;
+ case OMAP_I2C_V2_BUFSTAT:
+ return 0;
+ case OMAP_I2C_V2_DATA: {
+ /*
+ * The OMAP4/AM64x driver accesses the DATA register one byte per
+ * MMIO access (readw of a single byte), unlike the classic V1
+ * 16-bit FIFO convention. Pop exactly one byte (oldest first,
+ * FIFO is filled LSB-first by omap_i2c_fifo_run()).
+ */
+ uint8_t b = s->fifo & 0xff;
+ if (s->rxlen > 0) {
+ s->fifo >>= 8;
+ s->rxlen--;
+ }
+ /*
+ * Refill from the slave while the transfer is still live (this
+ * may complete count_cur and issue the STOP, leaving the last
+ * few prefetched bytes buffered in the FIFO with the bus idle).
+ */
+ omap_i2c_fifo_run(s);
+ /*
+ * Drive the RRDY/ARDY handshake directly off the FIFO drain
+ * state so it keeps working after the bus has gone idle: RRDY
+ * stays asserted while buffered bytes remain, and ARDY is raised
+ * once the last byte has been consumed (master-receive).
+ */
+ if (s->rxlen > 0) {
+ s->stat |= 1 << 3; /* RRDY */
+ } else {
+ s->stat &= ~(1 << 3); /* RRDY */
+ if (((s->control >> 10) & 1) && /* MST */
+ ((~s->control >> 9) & 1)) { /* TRX (receive) */
+ s->stat |= 1 << 2; /* ARDY */
+ s->control &= ~(1 << 10); /* MST */
+ /*
+ * DCOUNT decrements to 0 on real hardware and stays
+ * there; leave it at 0 so a following address-only probe
+ * (which programs no CNT) starts from 0 and completes
+ * with ARDY instead of waiting for phantom TX bytes.
+ */
+ s->count = 0;
+ s->count_cur = 0;
+ }
+ }
+ s->stat &= ~(1 << 11); /* ROVR */
+ omap_i2c_interrupts_update(s);
+ return b;
+ }
+ default:
+ offset = omap_i2c_v2_to_v1(offset);
+ if (offset < 0) {
+ OMAP_BAD_REG(addr);
+ return 0;
+ }
+ break;
+ }
+ }
+
switch (offset) {
case 0x00: /* I2C_REV */
return s->revision; /* REV */
@@ -266,6 +399,83 @@ static void omap_i2c_write(void *opaque, hwaddr addr,
int offset = addr & OMAP_MPUI_REG_MASK;
int nack;
+ if (s->mmio_version == OMAP_I2C_MMIO_V2) {
+ switch (offset) {
+ case OMAP_I2C_V2_SYSC:
+ if (value & 2) { /* SRST */
+ omap_i2c_reset(DEVICE(s));
+ }
+ return;
+ case OMAP_I2C_V2_DATA:
+ /*
+ * The OMAP4/AM64x driver writes the DATA register one byte per
+ * MMIO access (writew of a single byte). Push exactly one byte,
+ * mirroring the classic 8-bit FIFO path (omap_i2c_writeb()).
+ */
+ if (s->txlen <= 2) {
+ s->fifo <<= 8;
+ s->txlen += 1;
+ s->fifo |= value & 0xff;
+ s->stat &= ~(1 << 10); /* XUDF */
+ if (s->txlen > 2) {
+ s->stat &= ~(1 << 4); /* XRDY */
+ }
+ omap_i2c_fifo_run(s);
+ /*
+ * If the transmit finished and issued its STOP, leave DCOUNT
+ * at 0 (see the DATA read path) so the next probe/transfer
+ * that does not reprogram CNT is not tricked into expecting
+ * stale phantom bytes.
+ */
+ if (!i2c_bus_busy(s->bus)) {
+ s->count = 0;
+ s->count_cur = 0;
+ }
+ omap_i2c_interrupts_update(s);
+ }
+ return;
+ case OMAP_I2C_V2_IRQSTATUS_RAW:
+ case OMAP_I2C_V2_IRQSTATUS: /* write-1-to-clear */
+ s->stat &= ~(value & 0x7fff);
+ /*
+ * XRDY/RRDY are level events: after the driver clears them it
+ * expects them to re-assert while the transfer still has room /
+ * data. Re-run the FIFO engine for a live transfer, then
+ * re-assert RRDY if bytes remain buffered even after the bus has
+ * gone idle (the tail of a receive drains from the FIFO).
+ */
+ omap_i2c_fifo_run(s);
+ if (s->rxlen > 0) {
+ s->stat |= 1 << 3; /* RRDY */
+ }
+ omap_i2c_interrupts_update(s);
+ return;
+ case OMAP_I2C_V2_IRQENABLE_SET:
+ s->mask |= value & 0xff;
+ omap_i2c_interrupts_update(s);
+ return;
+ case OMAP_I2C_V2_IRQENABLE_CLR:
+ s->mask &= ~(value & 0xff);
+ omap_i2c_interrupts_update(s);
+ return;
+ case OMAP_I2C_V2_WE:
+ return; /* wakeup enable: ignored */
+ case OMAP_I2C_V2_REVNB_LO:
+ case OMAP_I2C_V2_REVNB_HI:
+ case OMAP_I2C_V2_SYSS:
+ case OMAP_I2C_V2_BUFSTAT:
+ OMAP_RO_REG(addr);
+ return;
+ default:
+ offset = omap_i2c_v2_to_v1(offset);
+ if (offset < 0) {
+ OMAP_BAD_REG(addr);
+ return;
+ }
+ break;
+ }
+ }
+
switch (offset) {
case 0x00: /* I2C_REV */
case 0x0c: /* I2C_IV */
@@ -412,6 +622,10 @@ static void omap_i2c_writeb(void *opaque, hwaddr addr,
OMAPI2CState *s = opaque;
int offset = addr & OMAP_MPUI_REG_MASK;
+ if (s->mmio_version == OMAP_I2C_MMIO_V2 && offset == OMAP_I2C_V2_DATA) {
+ offset = 0x1c; /* I2C_DATA */
+ }
+
switch (offset) {
case 0x1c: /* I2C_DATA */
if (s->txlen > 2) {
@@ -489,10 +703,24 @@ static void omap_i2c_init(Object *obj)
static void omap_i2c_realize(DeviceState *dev, Error **errp)
{
OMAPI2CState *s = OMAP_I2C(dev);
+ uint64_t size;
+ if (s->mmio_version == OMAP_I2C_MMIO_V2) {
+ size = 0x100; /* AM64x main_i2c reg length */
+ } else {
+ size = (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000;
+ }
memory_region_init_io(&s->iomem, OBJECT(dev), &omap_i2c_ops, s, "omap.i2c",
- (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000);
-
+ size);
+
+ /*
+ * The IP-V2 wiring (e.g. TI AM64x) drives the module from the SoC clock
+ * tree rather than the legacy omap_clk pointer stubs, so the fclk/iclk
+ * requirement only applies to the classic OMAP boards.
+ */
+ if (s->mmio_version == OMAP_I2C_MMIO_V2) {
+ return;
+ }
if (!s->fclk) {
error_setg(errp, "omap_i2c: fclk not connected");
return;
@@ -516,6 +744,8 @@ void omap_i2c_set_fclk(OMAPI2CState *i2c, omap_clk clk)
static const Property omap_i2c_properties[] = {
DEFINE_PROP_UINT8("revision", OMAPI2CState, revision, 0),
+ DEFINE_PROP_UINT8("mmio-version", OMAPI2CState, mmio_version,
+ OMAP_I2C_MMIO_V1),
};
static void omap_i2c_class_init(ObjectClass *klass, const void *data)
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 03/14] hw/sd/sdhci: complete non-interrupt ADMA descriptor chains in one pass
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-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 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 04/14] hw/char: add TI AM64x UART model Wadim Mueller
` (10 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
sdhci_do_adma() returns to the main loop after every descriptor and relies
on a timer re-entry to pick up the next one. For a descriptor chain whose
entries do not request an interrupt this makes the transfer rate depend
from the virtual clock rather than on the guest's programming, which
significantly slows down large transfers and makes guest-visible timing
depend on host timer behaviour.
Keep processing the chain in the same invocation while no descriptor asks
for an interrupt and the transfer has not finished, and only fall back to
the deferred path when the guest actually requested a notification.
A qtest reproducer is added later in this series.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
This patch can be dropped once Bin Meng's SDHCI series
https://patchwork.ozlabs.org/project/qemu-devel/list/?series=515264
(which needs series=513930 applied first) is merged - it covers the
same AM64x failure, see
https://lore.kernel.org/qemu-devel/20260810124519.34501-1-wafgo01@gmail.com/
It is included here only so that the series works on actual master.
hw/sd/sdhci-internal.h | 9 +++++++++
hw/sd/sdhci.c | 23 +++++++++++++++++++++--
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h
index 4aeed120bf..e6ce12617e 100644
--- a/hw/sd/sdhci-internal.h
+++ b/hw/sd/sdhci-internal.h
@@ -277,6 +277,15 @@ FIELD(SDHC_MAXCURR, V18_VDD2, 32, 8); /* since v4.20 */
#define SDHC_INSERTION_DELAY (NANOSECONDS_PER_SECOND)
#define SDHC_TRANSFER_DELAY 100
#define SDHC_ADMA_DESCS_PER_DELAY 5
+/*
+ * Upper bound on ADMA2 descriptors handled in a single sdhci_do_adma()
+ * call, as a safety valve against a malformed or circular descriptor
+ * list. A well-formed transfer terminates far below this via END or
+ * blkcnt == 0 (even a 4 GiB transfer built from 64 KiB TRAN descriptors
+ * is only ~64K descriptors); the bound merely guarantees the loop makes
+ * a decision instead of spinning forever.
+ */
+#define SDHC_ADMA_MAX_DESCRIPTORS (1 << 20)
#define SDHC_CMD_RESPONSE (3 << 0)
enum {
diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
index e58a610397..9a5dd1d93f 100644
--- a/hw/sd/sdhci.c
+++ b/hw/sd/sdhci.c
@@ -780,7 +780,6 @@ static void sdhci_do_adma(SDHCIState *s)
const MemTxAttrs attrs = { .memory = true };
ADMADescr dscr = {};
MemTxResult res = MEMTX_ERROR;
- int i;
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN && !s->blkcnt) {
/* Stop Multiple Transfer */
@@ -788,7 +787,27 @@ static void sdhci_do_adma(SDHCIState *s)
return;
}
- for (i = 0; i < SDHC_ADMA_DESCS_PER_DELAY; ++i) {
+ /*
+ * Process the descriptor chain to completion (END or blkcnt == 0),
+ * yielding to the guest only for a descriptor carrying the INT
+ * attribute (a DMA-boundary interrupt, handled at the end of the loop).
+ *
+ * Historically at most SDHC_ADMA_DESCS_PER_DELAY descriptors were
+ * handled per call before rescheduling SDHC_TRANSFER_DELAY ns later on
+ * QEMU_CLOCK_VIRTUAL. That pacing is only needed so a guest can observe
+ * the intermediate DMA-interrupt state; a bulk transfer that requests
+ * no interrupt does not need slicing, and throttling it across many
+ * virtual-clock round-trips can make it race a guest-side transfer
+ * timeout. Run such chains to completion in one call instead.
+ *
+ * SDHC_ADMA_MAX_DESCRIPTORS bounds the loop so a malformed or circular
+ * chain cannot spin here forever; on overflow, break to the reschedule
+ * path so the main loop stays responsive.
+ */
+ for (unsigned int adma_descs = 0; ; adma_descs++) {
+ if (adma_descs >= SDHC_ADMA_MAX_DESCRIPTORS) {
+ break;
+ }
s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH;
get_adma_description(s, &dscr);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 04/14] hw/char: add TI AM64x UART model
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (2 preceding siblings ...)
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-20 12:48 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 05/14] hw/timer: add TI K3 DMTimer model Wadim Mueller
` (9 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
The AM64x UART is an 8250-compatible core with a TI-specific mode-select
register (MDR1) that has to be taken out of "disabled" state before the
UART transmits, plus a power/emulation management register. Wrap the
generic 16550 model to provide these.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/char/Kconfig | 4 ++
hw/char/meson.build | 1 +
hw/char/ti-am64-uart.c | 109 +++++++++++++++++++++++++++++++++
include/hw/char/ti-am64-uart.h | 28 +++++++++
4 files changed, 142 insertions(+)
create mode 100644 hw/char/ti-am64-uart.c
create mode 100644 include/hw/char/ti-am64-uart.h
diff --git a/hw/char/Kconfig b/hw/char/Kconfig
index 020c0a84bb..c06811130a 100644
--- a/hw/char/Kconfig
+++ b/hw/char/Kconfig
@@ -31,6 +31,10 @@ config SERIAL_MM
bool
select SERIAL
+config AM64_UART
+ bool
+ select SERIAL
+
config SERIAL_PCI
bool
default y if PCI_DEVICES
diff --git a/hw/char/meson.build b/hw/char/meson.build
index fc3d7ee506..71c35af357 100644
--- a/hw/char/meson.build
+++ b/hw/char/meson.build
@@ -22,6 +22,7 @@ system_ss.add(when: 'CONFIG_VIRTIO_SERIAL', if_true: files('virtio-console.c'))
system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xen_console.c'))
system_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_uartlite.c'))
system_ss.add(when: 'CONFIG_DIVA_GSP', if_true: files('diva-gsp.c'))
+system_ss.add(when: 'CONFIG_AM64_UART', if_true: files('ti-am64-uart.c'))
system_ss.add(when: 'CONFIG_AVR_USART', if_true: files('avr_usart.c'))
system_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_uart.c'))
diff --git a/hw/char/ti-am64-uart.c b/hw/char/ti-am64-uart.c
new file mode 100644
index 0000000000..41d02060eb
--- /dev/null
+++ b/hw/char/ti-am64-uart.c
@@ -0,0 +1,109 @@
+/*
+ * TI AM64x UART emulation
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/char/ti-am64-uart.h"
+#include "migration/vmstate.h"
+#include "hw/core/qdev-properties.h"
+
+static uint64_t am64_uart_read(void *opaque, hwaddr addr, unsigned size)
+{
+ AM64Uart *au = AM64_UART(opaque);
+
+ if (addr >= (8 << au->regshift)) {
+ return 0;
+ }
+
+ return serial_io_ops.read(&au->serial, addr >> au->regshift, 1);
+}
+
+static void am64_uart_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ AM64Uart *au = AM64_UART(opaque);
+ value &= 255;
+ if (addr >= (8 << au->regshift)) {
+ return;
+ }
+
+ serial_io_ops.write(&au->serial, addr >> au->regshift, value, 1);
+}
+
+/*
+ * Registers are byte-wide and little-endian. TI-specific registers outside
+ * the 16550 window are RAZ/WI; MDR1 == 0 means the 16x mode we implement.
+ */
+static const MemoryRegionOps am64_uart_ops = {
+ .read = am64_uart_read,
+ .write = am64_uart_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.max_access_size = 8,
+ .impl.max_access_size = 8,
+};
+
+static void am64_uart_realize(DeviceState *dev, Error **errp)
+{
+ AM64Uart *au = AM64_UART(dev);
+ SerialState *s = &au->serial;
+
+ if (!qdev_realize(DEVICE(s), NULL, errp)) {
+ return;
+ }
+
+ memory_region_init_io(&s->io, OBJECT(dev),
+ &am64_uart_ops, au, "serial",
+ 64 << au->regshift);
+ sysbus_init_mmio(SYS_BUS_DEVICE(au), &s->io);
+ sysbus_init_irq(SYS_BUS_DEVICE(au), &au->serial.irq);
+}
+
+static const VMStateDescription vmstate_am64_uart = {
+ .name = "ti-am64-uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_STRUCT(serial, AM64Uart, 0, vmstate_serial, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void am64_uart_instance_init(Object *o)
+{
+ AM64Uart *au = AM64_UART(o);
+
+ object_initialize_child(o, "serial", &au->serial, TYPE_SERIAL);
+
+ qdev_alias_all_properties(DEVICE(&au->serial), o);
+}
+
+static const Property am64_uart_properties[] = {
+ /* AM64x has adjacent 16550 registers four bytes apart. */
+ DEFINE_PROP_UINT8("regshift", AM64Uart, regshift, 2),
+};
+
+static void am64_uart_class_init(ObjectClass *oc, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ device_class_set_props(dc, am64_uart_properties);
+ dc->realize = am64_uart_realize;
+ dc->vmsd = &vmstate_am64_uart;
+}
+
+static const TypeInfo types[] = {
+ {
+ .name = TYPE_AM64_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .class_init = am64_uart_class_init,
+ .instance_init = am64_uart_instance_init,
+ .instance_size = sizeof(AM64Uart),
+ },
+};
+
+DEFINE_TYPES(types)
diff --git a/include/hw/char/ti-am64-uart.h b/include/hw/char/ti-am64-uart.h
new file mode 100644
index 0000000000..5cf820dc20
--- /dev/null
+++ b/include/hw/char/ti-am64-uart.h
@@ -0,0 +1,28 @@
+/*
+ * TI AM64x UART emulation
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SERIAL_TI_AM64_H
+#define HW_SERIAL_TI_AM64_H
+
+#include "hw/char/serial.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_AM64_UART "ti-am64-uart"
+OBJECT_DECLARE_SIMPLE_TYPE(AM64Uart, AM64_UART)
+
+struct AM64Uart {
+ SysBusDevice parent;
+
+ SerialState serial;
+
+ uint8_t regshift;
+};
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 05/14] hw/timer: add TI K3 DMTimer model
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (3 preceding siblings ...)
2026-08-20 12:48 ` [RFC PATCH v2 04/14] hw/char: add TI AM64x UART model Wadim Mueller
@ 2026-08-20 12:48 ` 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
` (8 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
Add a model of the K3 DMTimer used, among others, by the R5 SPL for its
udelay() implementation. It is a free-running counter with load/reload and
overflow interrupt.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/timer/Kconfig | 3 +
hw/timer/meson.build | 1 +
hw/timer/ti-k3-dmtimer.c | 177 +++++++++++++++++++++++++++++++
include/hw/timer/ti-k3-dmtimer.h | 30 ++++++
4 files changed, 211 insertions(+)
create mode 100644 hw/timer/ti-k3-dmtimer.c
create mode 100644 include/hw/timer/ti-k3-dmtimer.h
diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
index e1b751a54a..fac5ba5a7e 100644
--- a/hw/timer/Kconfig
+++ b/hw/timer/Kconfig
@@ -68,3 +68,6 @@ config AVR_TIMER16
config HEX_QTIMER
bool
+
+config TI_K3_DMTIMER
+ bool
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 8323efaf46..4cf49ef6d6 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -32,6 +32,7 @@ system_ss.add(when: 'CONFIG_STM32F2XX_TIMER', if_true: files('stm32f2xx_timer.c'
system_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_timer.c'))
specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c'))
system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
+system_ss.add(when: 'CONFIG_TI_K3_DMTIMER', if_true: files('ti-k3-dmtimer.c'))
system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
diff --git a/hw/timer/ti-k3-dmtimer.c b/hw/timer/ti-k3-dmtimer.c
new file mode 100644
index 0000000000..e65098d101
--- /dev/null
+++ b/hw/timer/ti-k3-dmtimer.c
@@ -0,0 +1,177 @@
+/*
+ * TI K3 DM timer (dmtimer, am654 register layout)
+ *
+ * Minimal TCRR counter model. TCLR.PRE_EN and TCLR.PTV divide the clock
+ * by (2 << PTV); interrupts and PWM we omit.
+ *
+ * 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 "qemu/log.h"
+#include "qemu/timer.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/timer/ti-k3-dmtimer.h"
+
+#define R_TIDR 0x00
+#define R_TCLR 0x38
+#define R_TCRR 0x3c
+#define R_TLDR 0x40
+#define R_TTGR 0x44
+#define R_TWPS 0x48
+
+#define TCLR_ST (1u << 0)
+#define TCLR_AR (1u << 1)
+#define TCLR_PTV (7u << 2)
+#define TCLR_PRE_EN (1u << 5)
+
+static uint32_t dmtimer_effective_freq(TIK3DmTimerState *s)
+{
+ uint32_t freq = s->freq_hz;
+
+ if (s->tclr & TCLR_PRE_EN) {
+ uint32_t ptv = (s->tclr >> 2) & 7;
+ freq = s->freq_hz / (2u << ptv);
+ }
+ return freq;
+}
+
+static uint32_t dmtimer_tcrr(TIK3DmTimerState *s)
+{
+ int64_t now;
+ uint32_t eff_freq;
+
+ if (!s->running) {
+ return s->tcrr_base;
+ }
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ eff_freq = dmtimer_effective_freq(s);
+
+ return s->tcrr_base +
+ (uint32_t)muldiv64(now - s->base_ns, eff_freq,
+ NANOSECONDS_PER_SECOND);
+}
+
+static void dmtimer_set_tcrr(TIK3DmTimerState *s, uint32_t val)
+{
+ s->tcrr_base = val;
+ s->base_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+static uint64_t ti_k3_dmtimer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ TIK3DmTimerState *s = TI_K3_DMTIMER(opaque);
+
+ switch (addr) {
+ case R_TIDR:
+ return 0x0;
+ case R_TCLR:
+ return s->tclr;
+ case R_TCRR:
+ return dmtimer_tcrr(s);
+ case R_TLDR:
+ return s->tldr;
+ case R_TWPS:
+ return 0; /* no write is pending, ever */
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented read @0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ return 0;
+ }
+}
+
+static void ti_k3_dmtimer_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TIK3DmTimerState *s = TI_K3_DMTIMER(opaque);
+
+ switch (addr) {
+ case R_TCLR:
+ /*
+ * Rebase with the old TCLR first. Prescaler changes must only
+ * affect ticks after this write.
+ */
+ dmtimer_set_tcrr(s, dmtimer_tcrr(s));
+ s->tclr = val;
+ s->running = (val & TCLR_ST) != 0;
+ break;
+ case R_TCRR:
+ dmtimer_set_tcrr(s, val);
+ break;
+ case R_TLDR:
+ s->tldr = val;
+ break;
+ case R_TTGR:
+ dmtimer_set_tcrr(s, s->tldr);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented write @0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps ti_k3_dmtimer_ops = {
+ .read = ti_k3_dmtimer_read,
+ .write = ti_k3_dmtimer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void ti_k3_dmtimer_reset(DeviceState *dev)
+{
+ TIK3DmTimerState *s = TI_K3_DMTIMER(dev);
+
+ s->tclr = 0;
+ s->tldr = 0;
+ s->tcrr_base = 0;
+ s->base_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ /*
+ * This model exposes TCRR as free-running already after reset; TCLR.ST
+ * still reads back as reset value 0.
+ */
+ s->running = true;
+}
+
+static void ti_k3_dmtimer_init(Object *obj)
+{
+ TIK3DmTimerState *s = TI_K3_DMTIMER(obj);
+
+ memory_region_init_io(&s->iomem, obj, &ti_k3_dmtimer_ops, s,
+ TYPE_TI_K3_DMTIMER, 0x400);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const Property ti_k3_dmtimer_properties[] = {
+ /* AM64 main_timer0 default input clock is 20 MHz. */
+ DEFINE_PROP_UINT32("freq-hz", TIK3DmTimerState, freq_hz, 20000000),
+};
+
+static void ti_k3_dmtimer_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, ti_k3_dmtimer_properties);
+ device_class_set_legacy_reset(dc, ti_k3_dmtimer_reset);
+}
+
+static const TypeInfo ti_k3_dmtimer_info = {
+ .name = TYPE_TI_K3_DMTIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIK3DmTimerState),
+ .instance_init = ti_k3_dmtimer_init,
+ .class_init = ti_k3_dmtimer_class_init,
+};
+
+static void ti_k3_dmtimer_register_types(void)
+{
+ type_register_static(&ti_k3_dmtimer_info);
+}
+
+type_init(ti_k3_dmtimer_register_types)
diff --git a/include/hw/timer/ti-k3-dmtimer.h b/include/hw/timer/ti-k3-dmtimer.h
new file mode 100644
index 0000000000..04c3925e2d
--- /dev/null
+++ b/include/hw/timer/ti-k3-dmtimer.h
@@ -0,0 +1,30 @@
+/*
+ * TI K3 DM timer (am654 layout)
+ *
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_TIMER_TI_K3_DMTIMER_H
+#define HW_TIMER_TI_K3_DMTIMER_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_TI_K3_DMTIMER "ti-k3-dmtimer"
+OBJECT_DECLARE_SIMPLE_TYPE(TIK3DmTimerState, TI_K3_DMTIMER)
+
+struct TIK3DmTimerState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+ uint32_t freq_hz;
+ uint32_t tclr;
+ uint32_t tldr;
+ /* counter value latched at last write/start */
+ uint32_t tcrr_base;
+ int64_t base_ns;
+ bool running;
+};
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 06/14] hw/misc: add TI K3 CTRL_MMR, GTC, DDRSS, SDHCI PHY and TRNG models
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (4 preceding siblings ...)
2026-08-20 12:48 ` [RFC PATCH v2 05/14] hw/timer: add TI K3 DMTimer model Wadim Mueller
@ 2026-08-20 12:48 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 07/14] hw/misc: add TI RAT (region address translation) model Wadim Mueller
` (7 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
Add the small K3 support blocks the boot chain touches:
- ti-k3-ctrlmmr: control module registers, including the DEVSTAT boot
pins, which the ROM and SPL use to select the boot device.
- ti-k3-gtc: global timebase counter, read by TF-A.
- ti-k3-ddrss: DDR subsystem stub that reports a successful DRAM init
(including BIST/ECC priming status) to the R5 SPL.
- ti-k3-sdhci-phy: PHY control/calibration registers of the K3 SDHCI
wrapper, reporting calibration as done.
- ti-k3-trng: TRNG stub used by OP-TEE.
These are register-level stubs: they implement the handshakes that
firmware polls for, not the underlying hardware behaviour.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/misc/Kconfig | 15 ++++
hw/misc/meson.build | 6 ++
hw/misc/ti-k3-ctrlmmr.c | 103 ++++++++++++++++++++++
| 98 +++++++++++++++++++++
hw/misc/ti-k3-gtc.c | 100 +++++++++++++++++++++
hw/misc/ti-k3-sdhci-phy.c | 91 +++++++++++++++++++
hw/misc/ti-k3-trng.c | 139 ++++++++++++++++++++++++++++++
hw/misc/trace-events | 4 +
include/hw/misc/ti-k3-ctrlmmr.h | 26 ++++++
| 26 ++++++
include/hw/misc/ti-k3-gtc.h | 23 +++++
include/hw/misc/ti-k3-sdhci-phy.h | 26 ++++++
include/hw/misc/ti-k3-trng.h | 35 ++++++++
13 files changed, 692 insertions(+)
create mode 100644 hw/misc/ti-k3-ctrlmmr.c
create mode 100644 hw/misc/ti-k3-ddrss.c
create mode 100644 hw/misc/ti-k3-gtc.c
create mode 100644 hw/misc/ti-k3-sdhci-phy.c
create mode 100644 hw/misc/ti-k3-trng.c
create mode 100644 include/hw/misc/ti-k3-ctrlmmr.h
create mode 100644 include/hw/misc/ti-k3-ddrss.h
create mode 100644 include/hw/misc/ti-k3-gtc.h
create mode 100644 include/hw/misc/ti-k3-sdhci-phy.h
create mode 100644 include/hw/misc/ti-k3-trng.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index b8860dd3e7..3e499a902d 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -143,6 +143,21 @@ config MPS2_SCC
bool
select LED
+config TI_K3_CTRLMMR
+ bool
+
+config TI_K3_GTC
+ bool
+
+config TI_K3_DDRSS
+ bool
+
+config TI_K3_SDHCI_PHY
+ bool
+
+config TI_K3_TRNG
+ bool
+
config TZ_MPC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e86d9ad6b3..e973d7e8a4 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -96,6 +96,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_cprman.c',
'bcm2835_powermgt.c',
))
+
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
@@ -122,6 +123,11 @@ system_ss.add(when: 'CONFIG_STM32L4X5_RCC', if_true: files('stm32l4x5_rcc.c'))
system_ss.add(when: 'CONFIG_MPS2_FPGAIO', if_true: files('mps2-fpgaio.c'))
system_ss.add(when: 'CONFIG_MPS2_SCC', if_true: files('mps2-scc.c'))
+system_ss.add(when: 'CONFIG_TI_K3_CTRLMMR', if_true: files('ti-k3-ctrlmmr.c'))
+system_ss.add(when: 'CONFIG_TI_K3_GTC', if_true: files('ti-k3-gtc.c'))
+system_ss.add(when: 'CONFIG_TI_K3_DDRSS', if_true: files('ti-k3-ddrss.c'))
+system_ss.add(when: 'CONFIG_TI_K3_SDHCI_PHY', if_true: files('ti-k3-sdhci-phy.c'))
+system_ss.add(when: 'CONFIG_TI_K3_TRNG', if_true: files('ti-k3-trng.c'))
system_ss.add(when: 'CONFIG_TZ_MPC', if_true: files('tz-mpc.c'))
system_ss.add(when: 'CONFIG_TZ_MSC', if_true: files('tz-msc.c'))
system_ss.add(when: 'CONFIG_TZ_PPC', if_true: files('tz-ppc.c'))
diff --git a/hw/misc/ti-k3-ctrlmmr.c b/hw/misc/ti-k3-ctrlmmr.c
new file mode 100644
index 0000000000..5925573075
--- /dev/null
+++ b/hw/misc/ti-k3-ctrlmmr.c
@@ -0,0 +1,103 @@
+/*
+ * TI K3 CTRL_MMR stub
+ *
+ * Minimal AM64x control MMR model: selected boot, reset-source and
+ * security-status registers are configurable; other reads return zero,
+ * writes are ignored.
+ *
+ * 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 "qemu/log.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/ti-k3-ctrlmmr.h"
+
+#define CTRLMMR_MAIN_DEVSTAT 0x30
+#define CTRLMMR_MCU_RST_SRC 0x18178
+/*
+ * K3_SEC_MGR_SYS_STATUS lives at offset 0x100 in the sec-ctrlmmr window.
+ * It reports the device type, e.g. GP or HS.
+ */
+#define CTRLMMR_SEC_MGR_SYS_STATUS 0x100
+#define CTRLMMR_SIZE 0x20000 /* partitions 0-7 */
+
+static uint64_t ti_k3_ctrlmmr_read(void *opaque, hwaddr addr, unsigned size)
+{
+ TIK3CtrlMmrState *s = TI_K3_CTRLMMR(opaque);
+
+ if (addr == CTRLMMR_MAIN_DEVSTAT) {
+ return s->devstat;
+ }
+ if (addr == CTRLMMR_MCU_RST_SRC) {
+ return s->rst_src;
+ }
+ if (addr == CTRLMMR_SEC_MGR_SYS_STATUS) {
+ return s->sec_mgr_sys_status;
+ }
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented read @0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ return 0;
+}
+
+static void ti_k3_ctrlmmr_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ /* lock-kick and pinmux writes are accepted and ignored. */
+}
+
+static const MemoryRegionOps ti_k3_ctrlmmr_ops = {
+ .read = ti_k3_ctrlmmr_read,
+ .write = ti_k3_ctrlmmr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+};
+
+static void ti_k3_ctrlmmr_init(Object *obj)
+{
+ TIK3CtrlMmrState *s = TI_K3_CTRLMMR(obj);
+
+ memory_region_init_io(&s->iomem, obj, &ti_k3_ctrlmmr_ops, s,
+ TYPE_TI_K3_CTRLMMR, CTRLMMR_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const Property ti_k3_ctrlmmr_properties[] = {
+ /* Default primary bootmode is eMMC (0x9 << 3). */
+ DEFINE_PROP_UINT32("devstat", TIK3CtrlMmrState, devstat, 0x48),
+ /*
+ * Report warm reset in MCU_RST_SRC. Cold/POR cause bits are not
+ * modelled by this reset-source stub.
+ */
+ DEFINE_PROP_UINT32("rst-src", TIK3CtrlMmrState, rst_src, 0x1),
+ /*
+ * Default to SYS_STATUS_DEV_TYPE_GP (0x3), matching a model without
+ * security manager and certificate authentication.
+ */
+ DEFINE_PROP_UINT32("sec-mgr-sys-status", TIK3CtrlMmrState,
+ sec_mgr_sys_status, 0x3),
+};
+
+static void ti_k3_ctrlmmr_class_init(ObjectClass *klass, const void *data)
+{
+ device_class_set_props(DEVICE_CLASS(klass), ti_k3_ctrlmmr_properties);
+}
+
+static const TypeInfo ti_k3_ctrlmmr_info = {
+ .name = TYPE_TI_K3_CTRLMMR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIK3CtrlMmrState),
+ .instance_init = ti_k3_ctrlmmr_init,
+ .class_init = ti_k3_ctrlmmr_class_init,
+};
+
+static void ti_k3_ctrlmmr_register_types(void)
+{
+ type_register_static(&ti_k3_ctrlmmr_info);
+}
+
+type_init(ti_k3_ctrlmmr_register_types)
--git a/hw/misc/ti-k3-ddrss.c b/hw/misc/ti-k3-ddrss.c
new file mode 100644
index 0000000000..661b88b02c
--- /dev/null
+++ b/hw/misc/ti-k3-ddrss.c
@@ -0,0 +1,98 @@
+/*
+ * TI K3 DDRSS register-file stub (AM64x, DDR4 flavour)
+ *
+ * RAM-backed DENALI CTL/PI/PHY register file. Writes are stored and read
+ * back unchanged. Init-done and optional ECC BIST_DONE status bits are ORed
+ * into reads, so the DDR bring-up handshake completes immediately.
+ *
+ * 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 "qemu/log.h"
+#include "hw/misc/ti-k3-ddrss.h"
+
+/* Read-side OR masks: {offset, bits}. */
+static const struct {
+ hwaddr offset;
+ uint32_t bits;
+} ddrss_status_bits[] = {
+ { 0x214C, 1u << 0 }, /* DENALI_PI_83: PI init done */
+ { 0x0538, 1u << 13 }, /* DENALI_CTL_334: MASTER, MC_INIT grp */
+ { 0x0558, 1u << 25 }, /* DENALI_CTL_342: INT_STATUS_INIT bit1 */
+ /*
+ * ECC BIST_DONE is exposed in both places: INT_STATUS_MASTER bit 8 and
+ * INT_STATUS_BIST bit 0, resp. raw bit 16.
+ */
+ { 0x0538, 1u << 8 }, /* DENALI_CTL_334: MASTER, BIST grp */
+ { 0x0554, 1u << 16 }, /* DENALI_CTL_341: BIST fld, BIST_DONE */
+};
+
+static uint64_t ti_k3_ddrss_read(void *opaque, hwaddr addr, unsigned size)
+{
+ TIK3DdrssState *s = TI_K3_DDRSS(opaque);
+ uint32_t val = s->regs[addr >> 2];
+
+ for (size_t i = 0; i < ARRAY_SIZE(ddrss_status_bits); i++) {
+ if (addr == ddrss_status_bits[i].offset) {
+ val |= ddrss_status_bits[i].bits;
+ }
+ }
+ return val;
+}
+
+static void ti_k3_ddrss_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TIK3DdrssState *s = TI_K3_DDRSS(opaque);
+
+ s->regs[addr >> 2] = val;
+}
+
+static const MemoryRegionOps ti_k3_ddrss_ops = {
+ .read = ti_k3_ddrss_read,
+ .write = ti_k3_ddrss_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void ti_k3_ddrss_reset(DeviceState *dev)
+{
+ TIK3DdrssState *s = TI_K3_DDRSS(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+}
+
+static void ti_k3_ddrss_init(Object *obj)
+{
+ TIK3DdrssState *s = TI_K3_DDRSS(obj);
+
+ memory_region_init_io(&s->iomem, obj, &ti_k3_ddrss_ops, s,
+ TYPE_TI_K3_DDRSS, TI_K3_DDRSS_CFG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void ti_k3_ddrss_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, ti_k3_ddrss_reset);
+}
+
+static const TypeInfo ti_k3_ddrss_info = {
+ .name = TYPE_TI_K3_DDRSS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIK3DdrssState),
+ .instance_init = ti_k3_ddrss_init,
+ .class_init = ti_k3_ddrss_class_init,
+};
+
+static void ti_k3_ddrss_register_types(void)
+{
+ type_register_static(&ti_k3_ddrss_info);
+}
+
+type_init(ti_k3_ddrss_register_types)
diff --git a/hw/misc/ti-k3-gtc.c b/hw/misc/ti-k3-gtc.c
new file mode 100644
index 0000000000..b51baddc71
--- /dev/null
+++ b/hw/misc/ti-k3-gtc.c
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * TI K3 Global Timebase Counter (GTC) register stub.
+ *
+ * The GTC distributes a system counter and its nominal frequency to the ARM
+ * generic timers. Only CNTCR and CNTFID0 are implemented; the counter itself
+ * is not modelled. Reset reports an enabled 200 MHz counter, the AM64x
+ * default.
+ */
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/ti-k3-gtc.h"
+
+#define TI_K3_GTC_SIZE 0x1000
+
+#define GTC_CNTCR 0x000 /* control; bit0 = EN */
+#define GTC_CNTFID0 0x020 /* frequency id 0 (Hz) */
+
+static uint64_t ti_k3_gtc_read(void *opaque, hwaddr addr, unsigned size)
+{
+ TIK3GtcState *s = TI_K3_GTC(opaque);
+
+ switch (addr) {
+ case GTC_CNTCR:
+ return s->cntcr;
+ case GTC_CNTFID0:
+ return s->cntfid0;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented read @0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ return 0;
+ }
+}
+
+static void ti_k3_gtc_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TIK3GtcState *s = TI_K3_GTC(opaque);
+
+ switch (addr) {
+ case GTC_CNTCR:
+ s->cntcr = (uint32_t)val;
+ break;
+ case GTC_CNTFID0:
+ s->cntfid0 = (uint32_t)val;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented write @0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps ti_k3_gtc_ops = {
+ .read = ti_k3_gtc_read,
+ .write = ti_k3_gtc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+};
+
+static void ti_k3_gtc_init(Object *obj)
+{
+ TIK3GtcState *s = TI_K3_GTC(obj);
+
+ memory_region_init_io(&s->iomem, obj, &ti_k3_gtc_ops, s,
+ TYPE_TI_K3_GTC, TI_K3_GTC_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const Property ti_k3_gtc_properties[] = {
+ DEFINE_PROP_UINT32("cntcr", TIK3GtcState, cntcr, 0x1),
+ DEFINE_PROP_UINT32("cntfid0", TIK3GtcState, cntfid0, 200000000),
+};
+
+static void ti_k3_gtc_class_init(ObjectClass *klass, const void *data)
+{
+ device_class_set_props(DEVICE_CLASS(klass), ti_k3_gtc_properties);
+}
+
+static const TypeInfo ti_k3_gtc_info = {
+ .name = TYPE_TI_K3_GTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIK3GtcState),
+ .instance_init = ti_k3_gtc_init,
+ .class_init = ti_k3_gtc_class_init,
+};
+
+static void ti_k3_gtc_register_types(void)
+{
+ type_register_static(&ti_k3_gtc_info);
+}
+
+type_init(ti_k3_gtc_register_types)
diff --git a/hw/misc/ti-k3-sdhci-phy.c b/hw/misc/ti-k3-sdhci-phy.c
new file mode 100644
index 0000000000..10b1f56032
--- /dev/null
+++ b/hw/misc/ti-k3-sdhci-phy.c
@@ -0,0 +1,91 @@
+/*
+ * TI K3 SDHCI PHY register-file stub (AM64x)
+ *
+ * RAM-backed SDHCI companion PHY window. Drive and delay registers are stored
+ * as written. PHY_STAT1 reports CALDONE (bit 1) and DLLRDY (bit 0) set, so
+ * calibration resp. DLL lock complete immediately.
+ *
+ * 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 "qemu/log.h"
+#include "hw/misc/ti-k3-sdhci-phy.h"
+
+/* Read-side OR masks: {offset, bits}. */
+static const struct {
+ hwaddr offset;
+ uint32_t bits;
+} sdhci_phy_status_bits[] = {
+ /* PHY_STAT1: CALDONE (bit 1) | DLLRDY (bit 0) */
+ { 0x130, 0x3 },
+};
+
+static uint64_t ti_k3_sdhci_phy_read(void *opaque, hwaddr addr, unsigned size)
+{
+ TIK3SdhciPhyState *s = TI_K3_SDHCI_PHY(opaque);
+ uint32_t val = s->regs[addr >> 2];
+
+ for (size_t i = 0; i < ARRAY_SIZE(sdhci_phy_status_bits); i++) {
+ if (addr == sdhci_phy_status_bits[i].offset) {
+ val |= sdhci_phy_status_bits[i].bits;
+ }
+ }
+ return val;
+}
+
+static void ti_k3_sdhci_phy_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TIK3SdhciPhyState *s = TI_K3_SDHCI_PHY(opaque);
+
+ s->regs[addr >> 2] = val;
+}
+
+static const MemoryRegionOps ti_k3_sdhci_phy_ops = {
+ .read = ti_k3_sdhci_phy_read,
+ .write = ti_k3_sdhci_phy_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void ti_k3_sdhci_phy_reset(DeviceState *dev)
+{
+ TIK3SdhciPhyState *s = TI_K3_SDHCI_PHY(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+}
+
+static void ti_k3_sdhci_phy_init(Object *obj)
+{
+ TIK3SdhciPhyState *s = TI_K3_SDHCI_PHY(obj);
+
+ memory_region_init_io(&s->iomem, obj, &ti_k3_sdhci_phy_ops, s,
+ TYPE_TI_K3_SDHCI_PHY, TI_K3_SDHCI_PHY_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void ti_k3_sdhci_phy_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, ti_k3_sdhci_phy_reset);
+}
+
+static const TypeInfo ti_k3_sdhci_phy_info = {
+ .name = TYPE_TI_K3_SDHCI_PHY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIK3SdhciPhyState),
+ .instance_init = ti_k3_sdhci_phy_init,
+ .class_init = ti_k3_sdhci_phy_class_init,
+};
+
+static void ti_k3_sdhci_phy_register_types(void)
+{
+ type_register_static(&ti_k3_sdhci_phy_info);
+}
+
+type_init(ti_k3_sdhci_phy_register_types)
diff --git a/hw/misc/ti-k3-trng.c b/hw/misc/ti-k3-trng.c
new file mode 100644
index 0000000000..14fb2d077c
--- /dev/null
+++ b/hw/misc/ti-k3-trng.c
@@ -0,0 +1,139 @@
+/*
+ * TI K3 SA2UL TRNG (EIP-76) stub (AM64x)
+ *
+ * Minimal EIP-76 register subset. STATUS reports RNG_READY, INTACK writes
+ * are accepted, OUTPUT_0..3 return two cached 64-bit pairs, and configuration
+ * registers are RAM-backed.
+ *
+ * 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 "hw/misc/ti-k3-trng.h"
+#include "trace.h"
+
+#define RNG_OUTPUT_0 0x00
+#define RNG_OUTPUT_1 0x04
+#define RNG_OUTPUT_2 0x08
+#define RNG_OUTPUT_3 0x0C
+#define RNG_STATUS 0x10
+#define RNG_READY (1u << 0)
+#define RNG_CONTROL 0x14
+
+/* Nonzero xorshift64 state reaches never zero. */
+static uint64_t ti_k3_trng_next(TIK3TrngState *s)
+{
+ s->rng_state ^= s->rng_state << 13;
+ s->rng_state ^= s->rng_state >> 7;
+ s->rng_state ^= s->rng_state << 17;
+ return s->rng_state;
+}
+
+static uint64_t ti_k3_trng_read(void *opaque, hwaddr addr, unsigned size)
+{
+ TIK3TrngState *s = TI_K3_TRNG(opaque);
+ uint32_t val;
+
+ switch (addr) {
+ case RNG_OUTPUT_0:
+ s->pair_a = ti_k3_trng_next(s);
+ val = (uint32_t)s->pair_a;
+ break;
+ case RNG_OUTPUT_1:
+ val = (uint32_t)(s->pair_a >> 32);
+ break;
+ case RNG_OUTPUT_2:
+ s->pair_b = ti_k3_trng_next(s);
+ val = (uint32_t)s->pair_b;
+ break;
+ case RNG_OUTPUT_3:
+ val = (uint32_t)(s->pair_b >> 32);
+ break;
+ case RNG_STATUS:
+ /* Always ready, never in shutdown. */
+ val = RNG_READY;
+ break;
+ default:
+ val = s->regs[addr >> 2];
+ break;
+ }
+
+ trace_ti_k3_trng_read(addr, val);
+ return val;
+}
+
+static void ti_k3_trng_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ TIK3TrngState *s = TI_K3_TRNG(opaque);
+
+ trace_ti_k3_trng_write(addr, value);
+
+ switch (addr) {
+ case RNG_OUTPUT_0:
+ case RNG_OUTPUT_1:
+ case RNG_OUTPUT_2:
+ case RNG_OUTPUT_3:
+ /* Output registers are read-only; use RAZ/WI. */
+ break;
+ case RNG_STATUS:
+ /* STATUS is synthesized on each read, so INTACK has no state. */
+ break;
+ default:
+ s->regs[addr >> 2] = (uint32_t)value;
+ break;
+ }
+}
+
+static const MemoryRegionOps ti_k3_trng_ops = {
+ .read = ti_k3_trng_read,
+ .write = ti_k3_trng_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void ti_k3_trng_reset(DeviceState *dev)
+{
+ TIK3TrngState *s = TI_K3_TRNG(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+ s->rng_state = 0x9e3779b97f4a7c15ULL;
+ s->pair_a = 0;
+ s->pair_b = 0;
+}
+
+static void ti_k3_trng_init(Object *obj)
+{
+ TIK3TrngState *s = TI_K3_TRNG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &ti_k3_trng_ops, s,
+ TYPE_TI_K3_TRNG, TI_K3_TRNG_REGS_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void ti_k3_trng_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, ti_k3_trng_reset);
+}
+
+static const TypeInfo ti_k3_trng_info = {
+ .name = TYPE_TI_K3_TRNG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIK3TrngState),
+ .instance_init = ti_k3_trng_init,
+ .class_init = ti_k3_trng_class_init,
+};
+
+static void ti_k3_trng_register_types(void)
+{
+ type_register_static(&ti_k3_trng_info);
+}
+
+type_init(ti_k3_trng_register_types)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c9a868b3ef..b32ce80ea9 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -442,3 +442,7 @@ iommu_testdev_dma_read(uint64_t gva, uint32_t len) "gva=0x%" PRIx64 " len=%u"
iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actual=0x%x"
iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
iommu_testdev_dma_armed(bool armed) "armed=%d"
+
+# ti-k3-trng.c
+ti_k3_trng_read(uint64_t addr, uint32_t val) "offset 0x%" PRIx64 " -> 0x%08x"
+ti_k3_trng_write(uint64_t addr, uint64_t val) "offset 0x%" PRIx64 " <- 0x%" PRIx64
diff --git a/include/hw/misc/ti-k3-ctrlmmr.h b/include/hw/misc/ti-k3-ctrlmmr.h
new file mode 100644
index 0000000000..01fb71bc74
--- /dev/null
+++ b/include/hw/misc/ti-k3-ctrlmmr.h
@@ -0,0 +1,26 @@
+/*
+ * TI K3 CTRL_MMR stub
+ *
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_MISC_TI_K3_CTRLMMR_H
+#define HW_MISC_TI_K3_CTRLMMR_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_TI_K3_CTRLMMR "ti-k3-ctrlmmr"
+OBJECT_DECLARE_SIMPLE_TYPE(TIK3CtrlMmrState, TI_K3_CTRLMMR)
+
+struct TIK3CtrlMmrState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+ uint32_t devstat;
+ uint32_t rst_src;
+ uint32_t sec_mgr_sys_status;
+};
+
+#endif
--git a/include/hw/misc/ti-k3-ddrss.h b/include/hw/misc/ti-k3-ddrss.h
new file mode 100644
index 0000000000..ed0f54e5c4
--- /dev/null
+++ b/include/hw/misc/ti-k3-ddrss.h
@@ -0,0 +1,26 @@
+/*
+ * TI K3 DDRSS register-file stub (AM64x)
+ *
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_MISC_TI_K3_DDRSS_H
+#define HW_MISC_TI_K3_DDRSS_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_TI_K3_DDRSS "ti-k3-ddrss"
+OBJECT_DECLARE_SIMPLE_TYPE(TIK3DdrssState, TI_K3_DDRSS)
+
+#define TI_K3_DDRSS_CFG_SIZE 0x8000
+
+struct TIK3DdrssState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+ uint32_t regs[TI_K3_DDRSS_CFG_SIZE / 4];
+};
+
+#endif
diff --git a/include/hw/misc/ti-k3-gtc.h b/include/hw/misc/ti-k3-gtc.h
new file mode 100644
index 0000000000..a2a22afbf6
--- /dev/null
+++ b/include/hw/misc/ti-k3-gtc.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ */
+#ifndef HW_MISC_TI_K3_GTC_H
+#define HW_MISC_TI_K3_GTC_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_TI_K3_GTC "ti-k3-gtc"
+OBJECT_DECLARE_SIMPLE_TYPE(TIK3GtcState, TI_K3_GTC)
+
+struct TIK3GtcState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+
+ uint32_t cntcr; /* GTC control: bit0 EN. Read back enabled. */
+ uint32_t cntfid0; /* GTC frequency id 0, in Hz. */
+};
+
+#endif /* HW_MISC_TI_K3_GTC_H */
diff --git a/include/hw/misc/ti-k3-sdhci-phy.h b/include/hw/misc/ti-k3-sdhci-phy.h
new file mode 100644
index 0000000000..2a7296fc6f
--- /dev/null
+++ b/include/hw/misc/ti-k3-sdhci-phy.h
@@ -0,0 +1,26 @@
+/*
+ * TI K3 SDHCI PHY register-file stub (AM64x)
+ *
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_MISC_TI_K3_SDHCI_PHY_H
+#define HW_MISC_TI_K3_SDHCI_PHY_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_TI_K3_SDHCI_PHY "ti-k3-sdhci-phy"
+OBJECT_DECLARE_SIMPLE_TYPE(TIK3SdhciPhyState, TI_K3_SDHCI_PHY)
+
+#define TI_K3_SDHCI_PHY_SIZE 0x400
+
+struct TIK3SdhciPhyState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+ uint32_t regs[TI_K3_SDHCI_PHY_SIZE / 4];
+};
+
+#endif
diff --git a/include/hw/misc/ti-k3-trng.h b/include/hw/misc/ti-k3-trng.h
new file mode 100644
index 0000000000..b9c33b589f
--- /dev/null
+++ b/include/hw/misc/ti-k3-trng.h
@@ -0,0 +1,35 @@
+/*
+ * TI K3 SA2UL TRNG (EIP-76) stub (AM64x)
+ *
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_MISC_TI_K3_TRNG_H
+#define HW_MISC_TI_K3_TRNG_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_TI_K3_TRNG "ti-k3-trng"
+OBJECT_DECLARE_SIMPLE_TYPE(TIK3TrngState, TI_K3_TRNG)
+
+/* EIP-76 register window: offsets 0x00..0x7c. */
+#define TI_K3_TRNG_REGS_SIZE 0x80
+
+struct TIK3TrngState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+
+ /* Nonzero xorshift64 generator state. */
+ uint64_t rng_state;
+ /* Cached pairs for OUTPUT_0/1 and OUTPUT_2/3. */
+ uint64_t pair_a;
+ uint64_t pair_b;
+
+ /* RAM-backed registers except OUTPUT_x and STATUS. */
+ uint32_t regs[TI_K3_TRNG_REGS_SIZE / 4];
+};
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 07/14] hw/misc: add TI RAT (region address translation) model
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (5 preceding siblings ...)
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 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 08/14] hw/misc: add TI mailbox (IPC) model Wadim Mueller
` (6 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
The R5F and M4F subsystems of the K3 devices reach the 64-bit SoC address
space through a RAT, which maps windows of the core's 32-bit view onto
system addresses. Model it as a set of translation regions layered as
memory region aliases into the core's address space.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/ti-rat.c | 290 +++++++++++++++++++++++++++++++++++++++
hw/misc/trace-events | 5 +
include/hw/misc/ti-rat.h | 51 +++++++
5 files changed, 350 insertions(+)
create mode 100644 hw/misc/ti-rat.c
create mode 100644 include/hw/misc/ti-rat.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 3e499a902d..c176d6d6c7 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -143,6 +143,9 @@ config MPS2_SCC
bool
select LED
+config TI_RAT
+ bool
+
config TI_K3_CTRLMMR
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e973d7e8a4..952b7b7c2f 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -123,6 +123,7 @@ system_ss.add(when: 'CONFIG_STM32L4X5_RCC', if_true: files('stm32l4x5_rcc.c'))
system_ss.add(when: 'CONFIG_MPS2_FPGAIO', if_true: files('mps2-fpgaio.c'))
system_ss.add(when: 'CONFIG_MPS2_SCC', if_true: files('mps2-scc.c'))
+system_ss.add(when: 'CONFIG_TI_RAT', if_true: files('ti-rat.c'))
system_ss.add(when: 'CONFIG_TI_K3_CTRLMMR', if_true: files('ti-k3-ctrlmmr.c'))
system_ss.add(when: 'CONFIG_TI_K3_GTC', if_true: files('ti-k3-gtc.c'))
system_ss.add(when: 'CONFIG_TI_K3_DDRSS', if_true: files('ti-k3-ddrss.c'))
diff --git a/hw/misc/ti-rat.c b/hw/misc/ti-rat.c
new file mode 100644
index 0000000000..719fdffa0a
--- /dev/null
+++ b/hw/misc/ti-rat.c
@@ -0,0 +1,290 @@
+/*
+ * TI RAT (Region Address Translation) SysBus device
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * A RAT sits in front of a processor and translates accesses, which fall
+ * into its address window, into 64-bit system addresses. Each entry maps
+ * one aligned power-of-two region of the window onto a translated base;
+ * enabled entries are modelled as MemoryRegion aliases into the target
+ * address space.
+ *
+ * Where the window lies is not fixed. The "window-base" and "window-size"
+ * properties place it, and the "window-root" resp. "target-root" links
+ * select the address space the window is seen in and the one it
+ * translates into. TI_RAT_NUM_ENTRIES gives the number of entries.
+ */
+
+#include "qemu/osdep.h"
+#include "exec/hwaddr.h"
+#include "qemu/bitops.h"
+#include "hw/core/qdev.h"
+#include "hw/core/sysbus.h"
+#include "system/address-spaces.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "qemu/units.h"
+#include "hw/misc/ti-rat.h"
+#include "hw/core/qdev-properties.h"
+#include "trace.h"
+
+/* Property defaults: the AM64x MCU R5F RAT window. */
+#define TI_RAT_WINDOW_BASE 0x60000000ULL
+#define TI_RAT_WINDOW_SIZE (2ULL * GiB)
+
+#define RAT_PID 0x000
+#define RAT_CONFIG 0x004
+
+#define RAT_ENT_BASE 0x20
+#define RAT_ENT_STRIDE 0x10
+
+#define RAT_REG_CTRL 0x00
+#define RAT_REG_BASE 0x04
+#define RAT_REG_TRANS_L 0x08
+#define RAT_REG_TRANS_H 0x0C
+
+#define RAT_REGS_SIZE 0x1000
+
+static void ti_rat_apply_entry(TIRATState *s, TIRATEntry *e)
+{
+ bool en = (e->ctrl_reg & BIT(31)) != 0;
+
+ uint64_t shift = e->ctrl_reg & 0x3f;
+ uint64_t size = (shift >= 63) ? 0 : (1ULL << shift);
+
+ hwaddr source_addr = (hwaddr)e->base_reg;
+ hwaddr dest_addr = (hwaddr)e->transl_reg | ((hwaddr)e->transh_reg << 32);
+
+ /* Validate before aliases are changed. */
+ if (!en) {
+ trace_rat_disable_region(e->idx);
+ if (e->inserted) {
+ memory_region_transaction_begin();
+ memory_region_del_subregion(&s->window_container, &e->alias);
+ memory_region_set_enabled(&e->alias, false);
+ memory_region_transaction_commit();
+ e->inserted = false;
+ }
+ return;
+ }
+
+ if (size < 0x1000) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "RAT %u: size too small: 0x%" PRIx64 "\n", e->idx, size);
+ return;
+ }
+
+ if (source_addr < s->window_base ||
+ (source_addr - s->window_base) + size > s->window_size) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "RAT %u: source outside window: 0x%" HWADDR_PRIx "\n",
+ e->idx, source_addr);
+ return;
+ }
+
+ hwaddr woff = source_addr - s->window_base;
+ e->size = size;
+ trace_rat_enable_region(e->idx, e->size, source_addr, dest_addr);
+
+ memory_region_transaction_begin();
+
+ /* Create or move the alias at programmed source offset. */
+ if (!e->inserted) {
+ memory_region_add_subregion(&s->window_container, woff, &e->alias);
+ e->inserted = true;
+ } else {
+ /* Move existing alias, when the source offset changes. */
+ memory_region_del_subregion(&s->window_container, &e->alias);
+ memory_region_add_subregion(&s->window_container, woff, &e->alias);
+ }
+ /* Point the alias to the programmed translated address. */
+ memory_region_set_alias_offset(&e->alias, dest_addr);
+ memory_region_set_size(&e->alias, size);
+ memory_region_set_enabled(&e->alias, true);
+ memory_region_transaction_commit();
+}
+
+static uint64_t ti_rat_read(void *opaque, hwaddr off, unsigned size)
+{
+ TIRATState *s = opaque;
+ int entry;
+ int rel_offset;
+
+ if (off == RAT_PID) {
+ return 0x66804100;
+ }
+
+ if (off == RAT_CONFIG) {
+ return 0x00300110;
+ }
+
+ if (off <= 4 || off > 0x800) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TI-RAT: invalid read offset 0x%" PRIx64 "\n", off);
+ return 0;
+ }
+
+ entry = (off - RAT_ENT_BASE) / RAT_ENT_STRIDE;
+ rel_offset = (off - RAT_ENT_BASE) % RAT_ENT_STRIDE;
+ assert(entry < TI_RAT_NUM_ENTRIES);
+ trace_rat_read_entry(entry, rel_offset, off);
+
+ switch (rel_offset) {
+ case RAT_REG_CTRL:
+ return s->ent[entry].ctrl_reg;
+ case RAT_REG_BASE:
+ return s->ent[entry].base_reg;
+ case RAT_REG_TRANS_L:
+ return s->ent[entry].transl_reg;
+ case RAT_REG_TRANS_H:
+ return s->ent[entry].transh_reg;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TI-RAT: invalid entry read offset 0x%x\n", rel_offset);
+ break;
+ }
+
+ return 0;
+}
+
+static void ti_rat_write(void *opaque, hwaddr off, uint64_t val, unsigned size)
+{
+ TIRATState *s = opaque;
+ int entry;
+ int rel_offset;
+
+ if (off <= 4 || off > 0x800) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TI-RAT: invalid write offset 0x%" PRIx64 "\n", off);
+ return;
+ }
+
+ entry = (off - RAT_ENT_BASE) / RAT_ENT_STRIDE;
+ rel_offset = (off - RAT_ENT_BASE) % RAT_ENT_STRIDE;
+
+ assert(entry < TI_RAT_NUM_ENTRIES);
+ TIRATEntry *e = &s->ent[entry];
+
+ switch (rel_offset) {
+ case RAT_REG_CTRL:
+ e->ctrl_reg = val;
+ break;
+ case RAT_REG_BASE:
+ e->base_reg = val;
+ break;
+ case RAT_REG_TRANS_L:
+ e->transl_reg = val;
+ break;
+ case RAT_REG_TRANS_H:
+ e->transh_reg = val;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TI-RAT: invalid entry write offset 0x%x\n", rel_offset);
+ break;
+ }
+
+ ti_rat_apply_entry(s, e);
+}
+
+static const MemoryRegionOps ti_rat_ops = {
+ .read = ti_rat_read,
+ .write = ti_rat_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void ti_rat_reset(DeviceState *dev)
+{
+ TIRATState *s = TI_RAT(dev);
+
+ s->ctrl = 0;
+
+ memory_region_transaction_begin();
+ for (int i = 0; i < TI_RAT_NUM_ENTRIES; i++) {
+ TIRATEntry *e = &s->ent[i];
+ if (e->inserted) {
+ memory_region_del_subregion(&s->window_container, &e->alias);
+ e->inserted = false;
+ }
+ e->idx = i;
+ e->ctrl_reg = 0;
+ e->base_reg = 0;
+ e->trans_base = 0;
+ e->size = 0x0;
+ memory_region_set_enabled(&e->alias, false);
+ }
+ memory_region_transaction_commit();
+}
+
+static void ti_rat_realize(DeviceState *dev, Error **errp)
+{
+ TIRATState *s = TI_RAT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ if (!s->window_root) {
+ error_setg(errp, "ti-rat: property 'window-root' must be set");
+ return;
+ }
+ if (!s->target_root) {
+ error_setg(errp, "ti-rat: property 'target-root' must be set");
+ return;
+ }
+ /* Register block is separate from the translated window. */
+ memory_region_init_io(&s->regs_mmio, OBJECT(s), &ti_rat_ops, s,
+ "ti-rat-regs", RAT_REGS_SIZE);
+ sysbus_init_mmio(sbd, &s->regs_mmio);
+
+ memory_region_init(&s->window_container, OBJECT(s), "ti-rat-window",
+ s->window_size);
+ memory_region_add_subregion(s->window_root, s->window_base,
+ &s->window_container);
+
+ for (int i = 0; i < TI_RAT_NUM_ENTRIES; i++) {
+ g_autofree char *name = g_strdup_printf("ti-rat-alias[%d]", i);
+ memory_region_init_alias(&s->ent[i].alias, OBJECT(s), name,
+ s->target_root, 0, 0x1000);
+ memory_region_set_enabled(&s->ent[i].alias, false);
+ }
+
+ ti_rat_reset(dev);
+}
+
+static const Property ti_rat_props[] = {
+ DEFINE_PROP_UINT64("window-base", TIRATState, window_base,
+ TI_RAT_WINDOW_BASE),
+ DEFINE_PROP_UINT64("window-size", TIRATState, window_size,
+ TI_RAT_WINDOW_SIZE),
+ DEFINE_PROP_LINK("target-root", TIRATState, target_root, TYPE_MEMORY_REGION,
+ MemoryRegion *),
+ DEFINE_PROP_LINK("window-root", TIRATState, window_root, TYPE_MEMORY_REGION,
+ MemoryRegion *),
+};
+
+static void ti_rat_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = ti_rat_realize;
+ device_class_set_legacy_reset(dc, ti_rat_reset);
+ device_class_set_props(dc, ti_rat_props);
+}
+
+static const TypeInfo ti_rat_info = {
+ .name = TYPE_TI_RAT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIRATState),
+ .class_init = ti_rat_class_init,
+};
+
+static void ti_rat_register_types(void)
+{
+ type_register_static(&ti_rat_info);
+}
+
+type_init(ti_rat_register_types);
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index b32ce80ea9..fae1f90ee2 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -443,6 +443,11 @@ iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actu
iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
iommu_testdev_dma_armed(bool armed) "armed=%d"
+# ti-rat.c
+rat_enable_region(int idx, uint64_t size, uint64_t source, uint64_t dest) "Enabling RAT Region %u: size 0x%"PRIx64" map 0x%"PRIx64" -> 0x%"PRIx64
+rat_disable_region(int idx) "Disabling RAT Region %u"
+rat_read_entry(int entry, int rel_offset, uint64_t offset) "Reading Entry %i at offset %i: offset: 0x%"PRIx64
+
# ti-k3-trng.c
ti_k3_trng_read(uint64_t addr, uint32_t val) "offset 0x%" PRIx64 " -> 0x%08x"
ti_k3_trng_write(uint64_t addr, uint64_t val) "offset 0x%" PRIx64 " <- 0x%" PRIx64
diff --git a/include/hw/misc/ti-rat.h b/include/hw/misc/ti-rat.h
new file mode 100644
index 0000000000..9f40a4a256
--- /dev/null
+++ b/include/hw/misc/ti-rat.h
@@ -0,0 +1,51 @@
+/*
+ * TI RAT (Region Address Translation)
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef TI_RAT_H
+#define TI_RAT_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_TI_RAT "ti-rat"
+OBJECT_DECLARE_SIMPLE_TYPE(TIRATState, TI_RAT)
+
+#define TI_RAT_NUM_ENTRIES 16
+
+typedef struct TIRATEntry {
+ bool inserted;
+
+ int idx;
+ uint32_t ctrl_reg;
+ uint32_t base_reg;
+ uint32_t transl_reg;
+ uint32_t transh_reg;
+
+ uint64_t trans_base; /* translated base */
+ uint64_t size; /* bytes */
+
+ MemoryRegion alias;
+} TIRATEntry;
+
+typedef struct TIRATState {
+ SysBusDevice parent_obj;
+
+ uint32_t ctrl;
+ MemoryRegion *window_root;
+ MemoryRegion *target_root;
+ MemoryRegion regs_mmio;
+ MemoryRegion window_container;
+
+ uint64_t window_base;
+ uint64_t window_size;
+
+ TIRATEntry ent[TI_RAT_NUM_ENTRIES];
+} TIRATState;
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 08/14] hw/misc: add TI mailbox (IPC) model
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (6 preceding siblings ...)
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 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 09/14] hw/misc: add TI K3 secure proxy model Wadim Mueller
` (5 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
Add the K3 mailbox IP: per-mailbox message FIFOs and per-user interrupt
status/enable registers, used for inter-core IPC, for example between the
A53 cluster and the M4F.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/ti-mailbox.c | 566 +++++++++++++++++++++++++++++++++++
hw/misc/trace-events | 13 +
include/hw/misc/ti-mailbox.h | 44 +++
5 files changed, 627 insertions(+)
create mode 100644 hw/misc/ti-mailbox.c
create mode 100644 include/hw/misc/ti-mailbox.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index c176d6d6c7..e80fdc3214 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -161,6 +161,9 @@ config TI_K3_SDHCI_PHY
config TI_K3_TRNG
bool
+config TI_MAILBOX
+ bool
+
config TZ_MPC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 952b7b7c2f..d190d8608a 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -129,6 +129,7 @@ system_ss.add(when: 'CONFIG_TI_K3_GTC', if_true: files('ti-k3-gtc.c'))
system_ss.add(when: 'CONFIG_TI_K3_DDRSS', if_true: files('ti-k3-ddrss.c'))
system_ss.add(when: 'CONFIG_TI_K3_SDHCI_PHY', if_true: files('ti-k3-sdhci-phy.c'))
system_ss.add(when: 'CONFIG_TI_K3_TRNG', if_true: files('ti-k3-trng.c'))
+system_ss.add(when: 'CONFIG_TI_MAILBOX', if_true: files('ti-mailbox.c'))
system_ss.add(when: 'CONFIG_TZ_MPC', if_true: files('tz-mpc.c'))
system_ss.add(when: 'CONFIG_TZ_MSC', if_true: files('tz-msc.c'))
system_ss.add(when: 'CONFIG_TZ_PPC', if_true: files('tz-ppc.c'))
diff --git a/hw/misc/ti-mailbox.c b/hw/misc/ti-mailbox.c
new file mode 100644
index 0000000000..ba4858a226
--- /dev/null
+++ b/hw/misc/ti-mailbox.c
@@ -0,0 +1,566 @@
+/*
+ * TI AM64x/OMAP mailbox
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/core/sysbus.h"
+#include "qemu/module.h"
+#include "qemu/bitops.h"
+#include "hw/core/cpu.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/ti-mailbox.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "hw/core/irq.h"
+#include "qom/object.h"
+
+#define TI_MAILBOX_MMIO_SIZE 0x200
+
+#define MAILBOX_REVISION 0x00
+#define MAILBOX_SYSCONFIG 0x10
+#define MAILBOX_MESSAGE_BASE 0x40
+#define MAILBOX_FIFO_STATUS_BASE 0x80
+#define MAILBOX_MSG_STATUS_BASE 0xC0
+#define MAILBOX_IRQ_RAW_BASE 0x100
+#define MAILBOX_IRQ_CLR_BASE 0x104
+#define MAILBOX_IRQ_EN_SET_BASE 0x108
+#define MAILBOX_IRQ_EN_CLR_BASE 0x10C
+#define MAILBOX_IRQ_STRIDE 0x10
+#define MAILBOX_IRQ_EOI 0x140
+
+
+static uint32_t ti_mailbox_hw_raw_status(const TIMailboxState *s)
+{
+ uint32_t raw = 0;
+
+ for (int i = 0; i < TI_MAILBOX_NUM_MBOX; i++) {
+ const Fifo32 *f = &s->mbox[i];
+ uint32_t count = fifo32_num_used((Fifo32 *)f);
+
+ if (count > 0) {
+ raw |= BIT(i * 2);
+ }
+ if (count < s->fifo_depth) {
+ raw |= BIT(i * 2 + 1);
+ }
+ }
+
+ return raw;
+}
+
+static void ti_mailbox_update_irqs(TIMailboxState *s)
+{
+ uint32_t hw_raw = ti_mailbox_hw_raw_status(s);
+
+ for (int user = 0; user < s->num_users; user++) {
+ TIMailboxUser *u = &s->users[user];
+ uint32_t raw = (hw_raw & ~u->raw_clear_mask) | u->raw_set;
+ uint32_t masked = raw & u->irq_enable;
+ trace_ti_mailbox_irq_eval(s->mailbox_id, user, hw_raw, raw,
+ u->irq_enable, masked);
+ bool level = masked != 0;
+ if (level != u->irq_level) {
+ trace_ti_mailbox_irq(s->mailbox_id, user, level, raw, u->irq_enable,
+ masked);
+ u->irq_level = level;
+ }
+ qemu_set_irq(u->irq, level);
+ }
+}
+
+static bool ti_mailbox_fifo_push(TIMailboxState *s, int mb, uint32_t val)
+{
+ Fifo32 *f = &s->mbox[mb];
+ uint32_t used_before = fifo32_num_used(f);
+ bool ok = used_before < s->fifo_depth;
+
+ if (ok) {
+ fifo32_push(f, val);
+ }
+ trace_ti_mailbox_fifo_push(s->mailbox_id, mb, s->fifo_depth, used_before,
+ val, ok);
+ return ok;
+}
+
+static bool ti_mailbox_fifo_pop(TIMailboxState *s, int mb, uint32_t *val)
+{
+ Fifo32 *f = &s->mbox[mb];
+ uint32_t used_before = fifo32_num_used(f);
+ bool ok = !fifo32_is_empty(f);
+ uint32_t out = 0;
+
+ if (ok) {
+ out = fifo32_pop(f);
+ }
+ if (val) {
+ *val = out;
+ }
+ trace_ti_mailbox_fifo_pop(s->mailbox_id, mb, s->fifo_depth, used_before,
+ out, ok);
+ return ok;
+}
+
+static void ti_mailbox_fmt_opt_int(char *buf, size_t len, const char *key,
+ int value)
+{
+ if (value < 0) {
+ buf[0] = '\0';
+ return;
+ }
+
+ snprintf(buf, len, " %s=%d", key, value);
+}
+
+static void ti_mailbox_fmt_cpu(char *buf, size_t len)
+{
+ if (!current_cpu) {
+ snprintf(buf, len, " cpu=?");
+ return;
+ }
+
+ snprintf(buf, len, " cpu=%s:%d", object_get_typename(OBJECT(current_cpu)),
+ current_cpu->cpu_index);
+}
+
+static void ti_mailbox_irq_bits_to_str(uint32_t val, char *buf, size_t len)
+{
+ size_t pos = 0;
+ bool first = true;
+
+ for (int bit = 0; bit < TI_MAILBOX_NUM_MBOX * 2; bit++) {
+ if (!(val & BIT(bit))) {
+ continue;
+ }
+ if (pos < len) {
+ int mbox = bit / 2;
+ const char *event = (bit % 2 == 0) ? "newmsg" : "notfull";
+ int n = snprintf(buf + pos, len - pos, "%s%s:%d", first ? "" : ",",
+ event, mbox);
+ if (n < 0 || (size_t)n >= len - pos) {
+ pos = len - 1;
+ break;
+ }
+ pos += n;
+ first = false;
+ }
+ }
+
+ if (first) {
+ g_strlcpy(buf, "-", len);
+ }
+}
+
+static const char *ti_mailbox_reg_name(hwaddr off, int *mbox, int *user)
+{
+ *mbox = -1;
+ *user = -1;
+
+ switch (off) {
+ case MAILBOX_REVISION:
+ return "MAILBOX_REVISION";
+ case MAILBOX_SYSCONFIG:
+ return "MAILBOX_SYSCONFIG";
+ case MAILBOX_IRQ_EOI:
+ return "MAILBOX_IRQ_EOI";
+ default:
+ break;
+ }
+
+ if (off >= MAILBOX_MESSAGE_BASE &&
+ off < MAILBOX_MESSAGE_BASE + TI_MAILBOX_NUM_MBOX * 4) {
+ *mbox = (off - MAILBOX_MESSAGE_BASE) / 4;
+ return "MAILBOX_MESSAGE_y";
+ }
+
+ if (off >= MAILBOX_FIFO_STATUS_BASE &&
+ off < MAILBOX_FIFO_STATUS_BASE + TI_MAILBOX_NUM_MBOX * 4) {
+ *mbox = (off - MAILBOX_FIFO_STATUS_BASE) / 4;
+ return "MAILBOX_FIFO_STATUS_y";
+ }
+
+ if (off >= MAILBOX_MSG_STATUS_BASE &&
+ off < MAILBOX_MSG_STATUS_BASE + TI_MAILBOX_NUM_MBOX * 4) {
+ *mbox = (off - MAILBOX_MSG_STATUS_BASE) / 4;
+ return "MAILBOX_MSG_STATUS_y";
+ }
+
+ if (off >= MAILBOX_IRQ_RAW_BASE &&
+ off < MAILBOX_IRQ_RAW_BASE +
+ TI_MAILBOX_NUM_USERS_MAX * MAILBOX_IRQ_STRIDE) {
+ int rel;
+
+ *user = (off - MAILBOX_IRQ_RAW_BASE) / MAILBOX_IRQ_STRIDE;
+ rel = (off - MAILBOX_IRQ_RAW_BASE) % MAILBOX_IRQ_STRIDE;
+ switch (rel) {
+ case 0:
+ return "MAILBOX_IRQ_STATUS_RAW_j";
+ case 4:
+ return "MAILBOX_IRQ_STATUS_CLR_j";
+ case 8:
+ return "MAILBOX_IRQ_ENABLE_SET_j";
+ case 0x0C:
+ return "MAILBOX_IRQ_ENABLE_CLR_j";
+ default:
+ return "MAILBOX_IRQ_UNKNOWN";
+ }
+ }
+
+ return "UNKNOWN";
+}
+
+static uint64_t ti_mailbox_read(void *opaque, hwaddr off, unsigned size)
+{
+ TIMailboxState *s = opaque;
+ uint32_t hw_raw;
+ uint32_t raw;
+ uint32_t val;
+ int mb;
+ int user;
+
+ (void)size;
+
+#define TI_MAILBOX_TRACE_READ(_val) \
+ do { \
+ char irq_bits[64]; \
+ char trace_cpu_str[64]; \
+ char trace_mbox_str[24]; \
+ char trace_user_str[24]; \
+ char trace_bits_str[96]; \
+ int trace_mbox; \
+ int trace_user; \
+ const char *trace_reg = \
+ ti_mailbox_reg_name(off, &trace_mbox, &trace_user); \
+ const char *trace_bits = "-"; \
+ const char *trace_bits_strp = ""; \
+ if (!strcmp(trace_reg, "MAILBOX_IRQ_STATUS_RAW_j") || \
+ !strcmp(trace_reg, "MAILBOX_IRQ_STATUS_CLR_j") || \
+ !strcmp(trace_reg, "MAILBOX_IRQ_ENABLE_SET_j") || \
+ !strcmp(trace_reg, "MAILBOX_IRQ_ENABLE_CLR_j")) { \
+ ti_mailbox_irq_bits_to_str((uint32_t)(_val), irq_bits, \
+ sizeof(irq_bits)); \
+ trace_bits = irq_bits; \
+ snprintf(trace_bits_str, sizeof(trace_bits_str), " irq_bits=%s", \
+ trace_bits); \
+ trace_bits_strp = trace_bits_str; \
+ } \
+ ti_mailbox_fmt_cpu(trace_cpu_str, sizeof(trace_cpu_str)); \
+ ti_mailbox_fmt_opt_int(trace_mbox_str, sizeof(trace_mbox_str), "mbox", \
+ trace_mbox); \
+ ti_mailbox_fmt_opt_int(trace_user_str, sizeof(trace_user_str), "user", \
+ trace_user); \
+ trace_ti_mailbox_read( \
+ (uint64_t)off, (uint32_t)(_val), size, trace_reg, s->mailbox_id, \
+ trace_cpu_str, trace_mbox_str, trace_user_str, trace_bits_strp); \
+ return (_val); \
+ } while (0)
+
+ switch (off) {
+ case MAILBOX_REVISION:
+ TI_MAILBOX_TRACE_READ(0x66FC8900);
+ case MAILBOX_SYSCONFIG:
+ TI_MAILBOX_TRACE_READ(0);
+ case MAILBOX_IRQ_EOI:
+ TI_MAILBOX_TRACE_READ(0);
+ default:
+ break;
+ }
+
+ if (off >= MAILBOX_MESSAGE_BASE &&
+ off < MAILBOX_MESSAGE_BASE + TI_MAILBOX_NUM_MBOX * 4) {
+ mb = (off - MAILBOX_MESSAGE_BASE) / 4;
+ if (!ti_mailbox_fifo_pop(s, mb, &val)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read empty mailbox %d\n",
+ TYPE_TI_MAILBOX, mb);
+ TI_MAILBOX_TRACE_READ(0);
+ }
+ ti_mailbox_update_irqs(s);
+ TI_MAILBOX_TRACE_READ(val);
+ }
+
+ if (off >= MAILBOX_FIFO_STATUS_BASE &&
+ off < MAILBOX_FIFO_STATUS_BASE + TI_MAILBOX_NUM_MBOX * 4) {
+ mb = (off - MAILBOX_FIFO_STATUS_BASE) / 4;
+ TI_MAILBOX_TRACE_READ(
+ fifo32_num_used(&s->mbox[mb]) >= s->fifo_depth ? 1 : 0);
+ }
+
+ if (off >= MAILBOX_MSG_STATUS_BASE &&
+ off < MAILBOX_MSG_STATUS_BASE + TI_MAILBOX_NUM_MBOX * 4) {
+ mb = (off - MAILBOX_MSG_STATUS_BASE) / 4;
+ TI_MAILBOX_TRACE_READ(fifo32_num_used(&s->mbox[mb]) & 0x7);
+ }
+
+ if (off >= MAILBOX_IRQ_RAW_BASE &&
+ off < MAILBOX_IRQ_RAW_BASE +
+ TI_MAILBOX_NUM_USERS_MAX * MAILBOX_IRQ_STRIDE) {
+ user = (off - MAILBOX_IRQ_RAW_BASE) / MAILBOX_IRQ_STRIDE;
+ if (user >= s->num_users) {
+ TI_MAILBOX_TRACE_READ(0);
+ }
+ hw_raw = ti_mailbox_hw_raw_status(s);
+ raw =
+ (hw_raw & ~s->users[user].raw_clear_mask) | s->users[user].raw_set;
+ if ((off - MAILBOX_IRQ_RAW_BASE) % MAILBOX_IRQ_STRIDE == 0) {
+ TI_MAILBOX_TRACE_READ(raw);
+ }
+ if ((off - MAILBOX_IRQ_RAW_BASE) % MAILBOX_IRQ_STRIDE == 4) {
+ TI_MAILBOX_TRACE_READ(raw & s->users[user].irq_enable);
+ }
+ if ((off - MAILBOX_IRQ_RAW_BASE) % MAILBOX_IRQ_STRIDE == 8) {
+ TI_MAILBOX_TRACE_READ(s->users[user].irq_enable);
+ }
+ if ((off - MAILBOX_IRQ_RAW_BASE) % MAILBOX_IRQ_STRIDE == 0x0C) {
+ TI_MAILBOX_TRACE_READ(s->users[user].irq_enable);
+ }
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid read offset 0x%" HWADDR_PRIx "\n",
+ TYPE_TI_MAILBOX, off);
+ TI_MAILBOX_TRACE_READ(0);
+
+#undef TI_MAILBOX_TRACE_READ
+}
+
+static void ti_mailbox_write(void *opaque, hwaddr off, uint64_t val,
+ unsigned size)
+{
+ TIMailboxState *s = opaque;
+ int mb;
+ int user;
+ int rel;
+
+ (void)size;
+
+ {
+ char irq_bits[64];
+ char trace_cpu_str[64];
+ char trace_mbox_str[24];
+ char trace_user_str[24];
+ char trace_bits_str[96];
+ int trace_mbox;
+ int trace_user;
+ const char *trace_reg =
+ ti_mailbox_reg_name(off, &trace_mbox, &trace_user);
+ const char *trace_bits = "-";
+ const char *trace_bits_strp = "";
+
+ if (!strcmp(trace_reg, "MAILBOX_IRQ_STATUS_RAW_j") ||
+ !strcmp(trace_reg, "MAILBOX_IRQ_STATUS_CLR_j") ||
+ !strcmp(trace_reg, "MAILBOX_IRQ_ENABLE_SET_j") ||
+ !strcmp(trace_reg, "MAILBOX_IRQ_ENABLE_CLR_j")) {
+ ti_mailbox_irq_bits_to_str((uint32_t)val, irq_bits,
+ sizeof(irq_bits));
+ trace_bits = irq_bits;
+ snprintf(trace_bits_str, sizeof(trace_bits_str), " irq_bits=%s",
+ trace_bits);
+ trace_bits_strp = trace_bits_str;
+ }
+
+ ti_mailbox_fmt_cpu(trace_cpu_str, sizeof(trace_cpu_str));
+ ti_mailbox_fmt_opt_int(trace_mbox_str, sizeof(trace_mbox_str), "mbox",
+ trace_mbox);
+ ti_mailbox_fmt_opt_int(trace_user_str, sizeof(trace_user_str), "user",
+ trace_user);
+ trace_ti_mailbox_write((uint64_t)off, (uint32_t)val, size, trace_reg,
+ s->mailbox_id, trace_cpu_str, trace_mbox_str,
+ trace_user_str, trace_bits_strp);
+ }
+
+ switch (off) {
+ case MAILBOX_SYSCONFIG:
+ if (val & 0x1) {
+ for (int i = 0; i < TI_MAILBOX_NUM_MBOX; i++) {
+ fifo32_reset(&s->mbox[i]);
+ }
+ for (int i = 0; i < s->num_users; i++) {
+ TIMailboxUser *u = &s->users[i];
+
+ u->irq_enable = 0;
+ u->raw_set = 0;
+ u->raw_clear_mask = 0;
+ }
+ ti_mailbox_update_irqs(s);
+ }
+ return;
+ case MAILBOX_IRQ_EOI:
+ return;
+ default:
+ break;
+ }
+
+ if (off >= MAILBOX_MESSAGE_BASE &&
+ off < MAILBOX_MESSAGE_BASE + TI_MAILBOX_NUM_MBOX * 4) {
+ mb = (off - MAILBOX_MESSAGE_BASE) / 4;
+ {
+ char trace_cpu_str[64];
+ uint64_t pc = current_cpu
+ ? (uint64_t)current_cpu->cc->get_pc(current_cpu)
+ : 0;
+
+ ti_mailbox_fmt_cpu(trace_cpu_str, sizeof(trace_cpu_str));
+ trace_ti_mailbox_msg_write_pc(s->mailbox_id, mb, pc, trace_cpu_str);
+ }
+ if (!ti_mailbox_fifo_push(s, mb, (uint32_t)val)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: mailbox %d full\n",
+ TYPE_TI_MAILBOX, mb);
+ return;
+ }
+ ti_mailbox_update_irqs(s);
+ return;
+ }
+
+ if (off >= MAILBOX_IRQ_RAW_BASE &&
+ off < MAILBOX_IRQ_RAW_BASE +
+ TI_MAILBOX_NUM_USERS_MAX * MAILBOX_IRQ_STRIDE) {
+ user = (off - MAILBOX_IRQ_RAW_BASE) / MAILBOX_IRQ_STRIDE;
+ if (user >= s->num_users) {
+ return;
+ }
+ rel = (off - MAILBOX_IRQ_RAW_BASE) % MAILBOX_IRQ_STRIDE;
+ uint32_t mask = (uint32_t)val;
+
+ if (rel == 0) {
+ if (mask == 0) {
+ return;
+ }
+ s->users[user].raw_set |= mask;
+ trace_ti_mailbox_raw_set(s->mailbox_id, user, mask,
+ s->users[user].raw_set);
+ ti_mailbox_update_irqs(s);
+ return;
+ }
+ if (rel == 4) {
+ if (mask == 0) {
+ return;
+ }
+ s->users[user].raw_set &= ~mask;
+ s->users[user].raw_clear_mask &= ~mask;
+ trace_ti_mailbox_raw_clear(s->mailbox_id, user, mask,
+ s->users[user].raw_set,
+ s->users[user].raw_clear_mask);
+ ti_mailbox_update_irqs(s);
+ return;
+ }
+ if (rel == 8) {
+ s->users[user].irq_enable |= mask;
+ trace_ti_mailbox_irq_enable(s->mailbox_id, user, 1, mask,
+ s->users[user].irq_enable);
+ ti_mailbox_update_irqs(s);
+ return;
+ }
+ if (rel == 0x0C) {
+ s->users[user].irq_enable &= ~mask;
+ trace_ti_mailbox_irq_enable(s->mailbox_id, user, 0, mask,
+ s->users[user].irq_enable);
+ ti_mailbox_update_irqs(s);
+ return;
+ }
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid write offset 0x%" HWADDR_PRIx "\n",
+ TYPE_TI_MAILBOX, off);
+}
+
+static const MemoryRegionOps ti_mailbox_ops = {
+ .read = ti_mailbox_read,
+ .write = ti_mailbox_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void ti_mailbox_reset(DeviceState *dev)
+{
+ TIMailboxState *s = TI_MAILBOX(dev);
+
+ trace_ti_mailbox_reset_state(s->mailbox_id);
+ for (int i = 0; i < TI_MAILBOX_NUM_MBOX; i++) {
+ fifo32_reset(&s->mbox[i]);
+ }
+ for (int i = 0; i < s->num_users; i++) {
+ TIMailboxUser *u = &s->users[i];
+
+ u->irq_enable = 0;
+ u->raw_set = 0;
+ u->raw_clear_mask = 0;
+ u->irq_level = false;
+ }
+
+ ti_mailbox_update_irqs(s);
+}
+
+static void ti_mailbox_realize(DeviceState *dev, Error **errp)
+{
+ TIMailboxState *s = TI_MAILBOX(dev);
+
+ if (s->num_users == 0 || s->num_users > TI_MAILBOX_NUM_USERS_MAX) {
+ error_setg(errp, "num-users must be between 1 and %u",
+ TI_MAILBOX_NUM_USERS_MAX);
+ return;
+ }
+
+ if (s->fifo_depth == 0 || s->fifo_depth > TI_MAILBOX_FIFO_DEPTH_MAX) {
+ error_setg(errp, "fifo-depth must be between 1 and %u",
+ TI_MAILBOX_FIFO_DEPTH_MAX);
+ return;
+ }
+
+ for (int i = 0; i < TI_MAILBOX_NUM_MBOX; i++) {
+ fifo32_create(&s->mbox[i], s->fifo_depth);
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &ti_mailbox_ops, s,
+ TYPE_TI_MAILBOX, TI_MAILBOX_MMIO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+
+ for (int i = 0; i < s->num_users; i++) {
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->users[i].irq);
+ }
+}
+
+static void ti_mailbox_finalize(Object *obj)
+{
+ TIMailboxState *s = TI_MAILBOX(obj);
+
+ for (int i = 0; i < TI_MAILBOX_NUM_MBOX; i++) {
+ fifo32_destroy(&s->mbox[i]);
+ }
+}
+
+static const Property ti_mailbox_properties[] = {
+ DEFINE_PROP_UINT8("num-users", TIMailboxState, num_users,
+ TI_MAILBOX_NUM_USERS_DEFAULT),
+ DEFINE_PROP_UINT8("fifo-depth", TIMailboxState, fifo_depth,
+ TI_MAILBOX_FIFO_DEPTH_DEFAULT),
+ DEFINE_PROP_UINT8("mailbox-id", TIMailboxState, mailbox_id, 0),
+};
+
+static void ti_mailbox_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, ti_mailbox_reset);
+ dc->realize = ti_mailbox_realize;
+ dc->user_creatable = false;
+ device_class_set_props(dc, ti_mailbox_properties);
+}
+
+static const TypeInfo ti_mailbox_types[] = {
+ {
+ .name = TYPE_TI_MAILBOX,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIMailboxState),
+ .class_init = ti_mailbox_class_init,
+ .instance_finalize = ti_mailbox_finalize,
+ }
+};
+
+DEFINE_TYPES(ti_mailbox_types)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index fae1f90ee2..ea7cde021e 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -69,6 +69,19 @@ ecc_diag_mem_readb(uint64_t addr, uint32_t ret) "Read diagnostic %"PRId64"= 0x%0
# empty_slot.c
empty_slot_write(uint64_t addr, unsigned width, uint64_t value, unsigned size, const char *name) "wr addr:0x%04"PRIx64" data:0x%0*"PRIx64" size %u [%s]"
+# ti-mailbox.c
+ti_mailbox_read(uint64_t offset, uint32_t value, unsigned size, const char *reg, uint8_t mailbox_id, const char *cpu_str, const char *mbox_str, const char *user_str, const char *irq_bits_str) "off=0x%" PRIx64 " value=0x%08x size=%u reg=%s mailbox_id=%u%s%s%s%s"
+ti_mailbox_write(uint64_t offset, uint32_t value, unsigned size, const char *reg, uint8_t mailbox_id, const char *cpu_str, const char *mbox_str, const char *user_str, const char *irq_bits_str) "off=0x%" PRIx64 " value=0x%08x size=%u reg=%s mailbox_id=%u%s%s%s%s"
+ti_mailbox_irq(uint8_t mailbox_id, uint8_t user, int level, uint32_t raw, uint32_t enable, uint32_t masked) "mailbox_id=%u user=%u level=%d raw=0x%08x enable=0x%08x masked=0x%08x"
+ti_mailbox_irq_eval(uint8_t mailbox_id, uint8_t user, uint32_t hw_raw, uint32_t raw, uint32_t enable, uint32_t masked) "mailbox_id=%u user=%u hw_raw=0x%08x raw=0x%08x enable=0x%08x masked=0x%08x"
+ti_mailbox_fifo_push(uint8_t mailbox_id, int mbox, uint8_t depth, uint32_t used_before, uint32_t val, int ok) "mailbox_id=%u mbox=%d depth=%u used_before=%u val=0x%08x ok=%d"
+ti_mailbox_fifo_pop(uint8_t mailbox_id, int mbox, uint8_t depth, uint32_t used_before, uint32_t val, int ok) "mailbox_id=%u mbox=%d depth=%u used_before=%u val=0x%08x ok=%d"
+ti_mailbox_reset_state(uint8_t mailbox_id) "mailbox_id=%u reset"
+ti_mailbox_raw_set(uint8_t mailbox_id, uint8_t user, uint32_t mask, uint32_t raw_set) "mailbox_id=%u user=%u mask=0x%08x raw_set=0x%08x"
+ti_mailbox_raw_clear(uint8_t mailbox_id, uint8_t user, uint32_t mask, uint32_t raw_set, uint32_t raw_clear_mask) "mailbox_id=%u user=%u mask=0x%08x raw_set=0x%08x raw_clear_mask=0x%08x"
+ti_mailbox_irq_enable(uint8_t mailbox_id, uint8_t user, int set, uint32_t mask, uint32_t irq_enable) "mailbox_id=%u user=%u set=%d mask=0x%08x irq_enable=0x%08x"
+ti_mailbox_msg_write_pc(uint8_t mailbox_id, int mbox, uint64_t pc, const char *cpu_str) "mailbox_id=%u mbox=%d pc=0x%" PRIx64 "%s"
+
# slavio_misc.c
slavio_misc_update_irq_raise(void) "Raise IRQ"
slavio_misc_update_irq_lower(void) "Lower IRQ"
diff --git a/include/hw/misc/ti-mailbox.h b/include/hw/misc/ti-mailbox.h
new file mode 100644
index 0000000000..9cfc3b8fc2
--- /dev/null
+++ b/include/hw/misc/ti-mailbox.h
@@ -0,0 +1,44 @@
+/*
+ * TI mailbox (IPC)
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef TI_MAILBOX_H
+#define TI_MAILBOX_H
+
+#include "qemu/fifo32.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_TI_MAILBOX "ti-mailbox"
+OBJECT_DECLARE_SIMPLE_TYPE(TIMailboxState, TI_MAILBOX)
+
+#define TI_MAILBOX_NUM_MBOX 16
+#define TI_MAILBOX_NUM_USERS_MAX 4
+#define TI_MAILBOX_NUM_USERS_DEFAULT 4
+#define TI_MAILBOX_FIFO_DEPTH_MAX 4
+#define TI_MAILBOX_FIFO_DEPTH_DEFAULT 4
+
+typedef struct TIMailboxUser {
+ uint32_t irq_enable;
+ uint32_t raw_set;
+ uint32_t raw_clear_mask;
+ bool irq_level;
+ qemu_irq irq;
+} TIMailboxUser;
+
+struct TIMailboxState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+ Fifo32 mbox[TI_MAILBOX_NUM_MBOX];
+ TIMailboxUser users[TI_MAILBOX_NUM_USERS_MAX];
+ uint8_t num_users;
+ uint8_t fifo_depth;
+ uint8_t mailbox_id;
+};
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 09/14] hw/misc: add TI K3 secure proxy model
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (7 preceding siblings ...)
2026-08-20 12:48 ` [RFC PATCH v2 08/14] hw/misc: add TI mailbox (IPC) model Wadim Mueller
@ 2026-08-20 12:48 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 10/14] hw/misc: add TI DMSC (TI-SCI system controller) model Wadim Mueller
` (4 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
The secure proxy is the message transport between the K3 host cores and
the device management and security controller (DMSC). Model the per-thread
RT/SCFG register banks and the message buffers, and provide a callback
interface, which the DMSC model plugs into.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/ti-sec-proxy.c | 446 +++++++++++++++++++++++++++++++++
hw/misc/trace-events | 8 +
include/hw/misc/ti-sec-proxy.h | 108 ++++++++
5 files changed, 566 insertions(+)
create mode 100644 hw/misc/ti-sec-proxy.c
create mode 100644 include/hw/misc/ti-sec-proxy.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index e80fdc3214..bea8e9341f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -161,6 +161,9 @@ config TI_K3_SDHCI_PHY
config TI_K3_TRNG
bool
+config TI_SEC_PROXY
+ bool
+
config TI_MAILBOX
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index d190d8608a..a8a40ea2a7 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -129,6 +129,7 @@ system_ss.add(when: 'CONFIG_TI_K3_GTC', if_true: files('ti-k3-gtc.c'))
system_ss.add(when: 'CONFIG_TI_K3_DDRSS', if_true: files('ti-k3-ddrss.c'))
system_ss.add(when: 'CONFIG_TI_K3_SDHCI_PHY', if_true: files('ti-k3-sdhci-phy.c'))
system_ss.add(when: 'CONFIG_TI_K3_TRNG', if_true: files('ti-k3-trng.c'))
+system_ss.add(when: 'CONFIG_TI_SEC_PROXY', if_true: files('ti-sec-proxy.c'))
system_ss.add(when: 'CONFIG_TI_MAILBOX', if_true: files('ti-mailbox.c'))
system_ss.add(when: 'CONFIG_TZ_MPC', if_true: files('tz-mpc.c'))
system_ss.add(when: 'CONFIG_TZ_MSC', if_true: files('tz-msc.c'))
diff --git a/hw/misc/ti-sec-proxy.c b/hw/misc/ti-sec-proxy.c
new file mode 100644
index 0000000000..524bd1353c
--- /dev/null
+++ b/hw/misc/ti-sec-proxy.c
@@ -0,0 +1,446 @@
+/*
+ * TI SEC PROXY SysBus device
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * TI K3 SoCs use it as mailbox transport to the DMSC/SYSFW endpoint.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "hw/misc/ti-sec-proxy.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/register.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/core/irq.h"
+#include "trace.h"
+
+#define SEC_PROXY_MAX_MSG (16)
+
+REG32(SEC_PROXY_0_thread_status, 0x0)
+FIELD(SEC_PROXY_0_thread_status, ERROR, 31, 1)
+FIELD(SEC_PROXY_0_thread_status, DIR, 30, 1)
+FIELD(SEC_PROXY_0_thread_status, MAX_CNT, 16, 8)
+FIELD(SEC_PROXY_0_thread_status, CUR_CNT, 0, 8)
+
+REG32(SEC_PROXY_0_thread_threshold, 0x4)
+FIELD(SEC_PROXY_0_thread_threshold, THR_CNT, 0, 8)
+
+REG32(SEC_PROXY_0_thread_private, 0x0)
+FIELD(SEC_PROXY_0_thread_private, SRC_THR, 0, 10)
+
+REG32(SEC_PROXY_0_thread_message, 0x4)
+
+REG32(SEC_PROXY_0_buffer_l, 0x0)
+REG32(SEC_PROXY_0_buffer_h, 0x4)
+FIELD(SEC_PROXY_0_buffer_h, BASE_H, 0, 16)
+
+REG32(SEC_PROXY_0_target_l, 0x8)
+REG32(SEC_PROXY_0_target_h, 0xc)
+FIELD(SEC_PROXY_0_target_h, TARGET_H, 0, 16)
+
+REG32(SEC_PROXY_0_orderid, 0x10)
+FIELD(SEC_PROXY_0_orderid, ORDERID, 0, 4)
+FIELD(SEC_PROXY_0_orderid, REPLACE, 4, 1)
+
+REG32(SEC_PROXY_0_thread_ctl, 0x0)
+FIELD(SEC_PROXY_0_thread_ctl, QUEUE, 0, 16)
+FIELD(SEC_PROXY_0_thread_ctl, MAX_CNT, 16, 8)
+FIELD(SEC_PROXY_0_thread_ctl, DIR, 31, 1)
+
+REG32(SEC_PROXY_0_thread_evt_map, 0x4)
+FIELD(SEC_PROXY_0_thread_evt_map, THR_EVT, 0, 16)
+FIELD(SEC_PROXY_0_thread_evt_map, ERR_EVT, 16, 16)
+
+REG32(SEC_PROXY_0_thread_dst, 0x8)
+FIELD(SEC_PROXY_0_thread_dst, THREAD, 0, 16)
+
+REG32(SEC_PROXY_0_pid, 0x0)
+FIELD(SEC_PROXY_0_pid, SCHEME, 30, 2)
+FIELD(SEC_PROXY_0_pid, BU, 28, 2)
+FIELD(SEC_PROXY_0_pid, FUNC, 16, 12)
+FIELD(SEC_PROXY_0_pid, RTL, 11, 5)
+FIELD(SEC_PROXY_0_pid, MAJOR, 8, 3)
+FIELD(SEC_PROXY_0_pid, CUSTOM, 6, 2)
+FIELD(SEC_PROXY_0_pid, MINOR, 0, 6)
+
+REG32(SEC_PROXY_0_config, 0x4)
+FIELD(SEC_PROXY_0_config, MSG_SIZE, 16, 16)
+FIELD(SEC_PROXY_0_config, THREADS, 0, 16)
+
+REG32(SEC_PROXY_0_glb_evt, 0x14)
+FIELD(SEC_PROXY_0_glb_evt, ERR_EVENT, 0, 16)
+
+static RegisterAccessInfo sec_proxy_mmrs_regs_info[] = {
+ {
+ .name = "SEC_PROXY_0_pid",
+ .addr = A_SEC_PROXY_0_pid,
+ .ro = 0xffffffff,
+ .reset = 1714843904,
+ },
+ {
+ .name = "SEC_PROXY_0_config",
+ .addr = A_SEC_PROXY_0_config,
+ .ro = 0xffffffff,
+ .reset = 4194380,
+ },
+ {
+ .name = "SEC_PROXY_0_glb_evt",
+ .addr = A_SEC_PROXY_0_glb_evt,
+ .rsvd = 0xffff0000,
+ .reset = 65535,
+ }
+};
+
+static void ti_sec_proxy_realize(DeviceState *dev_soc, Error **errp)
+{
+ ERRP_GUARD();
+ TISecProxyState *s = TI_SEC_PROXY(dev_soc);
+
+ for (int i = 0; i < SEC_PROXY_THREAD_ID_MAX; ++i) {
+ struct TISecProxyThreadInfo *ti = &s->thread_info[i];
+ ti->thread_id = i;
+ /* K3 secure proxy alternates inbound and outbound threads. */
+ ti->is_outbound = (i % 2) ? true : false;
+ ti->num_messages = ti->is_outbound ? SEC_PROXY_MSG_MAX_WORDS : 0;
+ memset(ti->current_message, 0, sizeof(ti->current_message));
+ }
+}
+
+/* --- Backend API exported to DMSC -------------------------------------- */
+void ti_sec_proxy_register_msg_cb(TISecProxyState *sp, uint16_t thread_id,
+ TISecProxyMsgCb cb, void *opaque)
+{
+ if (thread_id >= ARRAY_SIZE(sp->thread_info)) {
+ return;
+ }
+
+ sp->thread_info[thread_id].cb = cb;
+ sp->thread_info[thread_id].cb_opaque = opaque;
+}
+
+size_t ti_sec_proxy_push_msg(TISecProxyState *sp, uint16_t thread_id,
+ const uint32_t *words, size_t nbytes)
+{
+ struct TISecProxyThreadInfo *ti;
+
+ if (thread_id >= ARRAY_SIZE(sp->thread_info)) {
+ return 0;
+ }
+
+ ti = &sp->thread_info[thread_id];
+ if (nbytes > sizeof(ti->current_message) - sizeof(uint32_t)) {
+ return 0;
+ }
+
+ memcpy(&ti->current_message[1], words, nbytes);
+ qemu_irq_raise(sp->irq_evt);
+
+ ti->num_messages++;
+ return ti->num_messages;
+}
+
+uint32_t ti_sec_proxy_get_msg_words(TISecProxyState *sp)
+{
+ return sp->msg_words;
+}
+
+void ti_sec_proxy_reset_thread_count(TISecProxyState *sp, uint16_t thread_id)
+{
+ if (thread_id >= ARRAY_SIZE(sp->thread_info)) {
+ return;
+ }
+
+ sp->thread_info[thread_id].num_messages = 0;
+}
+
+static const char *
+ti_sec_proxy_get_thread_channel_name(enum TISciThreadIds thread_id)
+{
+ const char *thread_names[] = {
+ "MAIN_0_R5_0_READ_RESPONSE_THREAD", "MAIN_0_R5_0_WRITE_THREAD",
+ "MAIN_0_R5_1_READ_RESPONSE_THREAD", "MAIN_0_R5_1_WRITE_THREAD",
+ "MAIN_0_R5_2_READ_RESPONSE_THREAD", "MAIN_0_R5_2_WRITE_THREAD",
+ "MAIN_0_R5_3_READ_RESPONSE_THREAD", "MAIN_0_R5_3_WRITE_THREAD",
+ "A53_0_READ_RESPONSE_THREAD", "A53_0_WRITE_THREAD",
+ "A53_1_READ_RESPONSE_THREAD", "A53_1_WRITE_THREAD",
+ "A53_2_READ_RESPONSE_THREAD", "A53_2_WRITE_THREAD",
+ "A53_3_READ_RESPONSE_THREAD", "A53_3_WRITE_THREAD",
+ "M4_0_READ_RESPONSE_THREAD", "M4_0_WRITE_THREAD",
+ "MAIN_1_R5_0_READ_RESPONSE_THREAD", "MAIN_1_R5_0_WRITE_THREAD",
+ "MAIN_1_R5_1_READ_RESPONSE_THREAD", "MAIN_1_R5_1_WRITE_THREAD",
+ "MAIN_1_R5_2_READ_RESPONSE_THREAD", "MAIN_1_R5_2_WRITE_THREAD",
+ "MAIN_1_R5_3_READ_RESPONSE_THREAD", "MAIN_1_R5_3_WRITE_THREAD",
+ "A53_4_READ_RESPONSE_THREAD", "A53_4_WRITE_THREAD",
+ "ICSSG_0_READ_RESPONSE_THREAD", "ICSSG_0_WRITE_THREAD",
+ "ICSSG_1_READ_RESPONSE_THREAD", "ICSSG_1_WRITE_THREAD"};
+
+ if (thread_id < ARRAY_SIZE(thread_names)) {
+ return thread_names[thread_id];
+ }
+ return "UNKNOWN_THREAD";
+}
+
+static void ti_sec_proxy_reset_hold(Object *obj, ResetType type)
+{
+ TISecProxyState *s = TI_SEC_PROXY(obj);
+
+ for (int i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
+ register_reset(&s->regs_info[i]);
+ }
+}
+
+static void ti_sec_proxy_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = ti_sec_proxy_realize;
+ rc->phases.hold = ti_sec_proxy_reset_hold;
+}
+
+static const MemoryRegionOps ti_sec_proxy_mmr_ops = {
+ .read = register_read_memory,
+ .write = register_write_memory,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t ti_sec_proxy_read_scfg(void *opaque, hwaddr addr, unsigned size)
+{
+ TISecProxyState *s = opaque;
+ if (addr <= 0x10) {
+ return 0;
+ }
+
+ hwaddr thread_rel_addr = addr - 0x10;
+ int thread_num = thread_rel_addr / 0x1000;
+ int reg = (thread_rel_addr % 0x1000) / 4;
+ struct TISecProxyThreadInfo *tinfo = &s->thread_info[thread_num];
+
+ trace_ti_sec_proxy_read_scfg(
+ ti_sec_proxy_get_thread_channel_name(thread_num), reg, addr);
+ return tinfo->is_outbound ? 0x0 : 0x80000000;
+}
+
+static void ti_sec_proxy_write_scfg(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ if (addr <= 0x10) {
+ return;
+ }
+
+ int thread_num = addr / 0x1000;
+ int reg = (addr % 0x1000) / 4;
+
+ trace_ti_sec_proxy_write_scfg(
+ ti_sec_proxy_get_thread_channel_name(thread_num), reg, addr, value);
+}
+
+static const MemoryRegionOps ti_sec_proxy_scfg_ops = {
+ .read = ti_sec_proxy_read_scfg,
+ .write = ti_sec_proxy_write_scfg,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t ti_sec_proxy_read_rt(void *opaque, hwaddr addr, unsigned size)
+{
+ TISecProxyState *s = opaque;
+ int thread_num = addr / 0x1000;
+ hwaddr off = addr % 0x1000;
+
+ int byte = off & 0x3;
+
+ struct TISecProxyThreadInfo *tinfo = &s->thread_info[thread_num];
+
+ uint32_t reg_val = (tinfo->num_messages) |
+ (tinfo->is_outbound ? 0x0 : 0x40000000) |
+ (tinfo->is_outbound ? (tinfo->num_messages << 16) : 0x0);
+
+ uint64_t ret;
+
+ if (size == 1) {
+ ret = (reg_val >> (8 * byte)) & 0xff;
+ } else if (size == 2) {
+ ret = (reg_val >> (8 * (byte & ~1))) & 0xffff;
+ } else if (size == 4) {
+ ret = reg_val;
+ } else {
+ ret = 0;
+ }
+ trace_ti_sec_proxy_read_rt(ti_sec_proxy_get_thread_channel_name(thread_num),
+ ret, addr);
+ return ret;
+}
+
+static void ti_sec_proxy_write_rt(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+}
+
+static const MemoryRegionOps ti_sec_proxy_rt_ops = {
+ .read = ti_sec_proxy_read_rt,
+ .write = ti_sec_proxy_write_rt,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t ti_sec_proxy_read_target(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TISecProxyState *s = opaque;
+ int thread_num = addr / 0x1000;
+ hwaddr off = addr % 0x1000;
+
+ int reg = off >> 2; /* 32-bit register index */
+ int byte = off & 0x3; /* byte offset within the 32-bit register */
+
+ struct TISecProxyThreadInfo *tinfo = &s->thread_info[thread_num];
+
+ /* First fetch the full little-endian 32-bit register. */
+ uint32_t reg_val = (uint32_t)tinfo->current_message[reg];
+ uint64_t ret = 0;
+
+ if (size == 1) {
+ ret = (reg_val >> (8 * byte)) & 0xffu;
+ } else if (size == 2) {
+ /*
+ * Odd halfword reads are rounded down. Guest drivers use aligned
+ * accesses, but this keeps byte-lane handling tolerant.
+ */
+ ret = (reg_val >> (8 * (byte & ~1))) & 0xffffu;
+ } else if (size == 4) {
+ ret = reg_val;
+ } else {
+ /* Shouldn't happen given .valid, but be defensive. */
+ ret = 0;
+ }
+
+ /* For inbound threads reset the message counter */
+ if (!tinfo->is_outbound && reg == SEC_PROXY_MAX_MSG - 1) {
+ trace_ti_sec_proxy_complete_read(
+ ti_sec_proxy_get_thread_channel_name(thread_num),
+ tinfo->num_messages);
+ tinfo->num_messages = 0;
+ qemu_irq_lower(s->irq_evt);
+ }
+ return ret;
+}
+
+static void ti_sec_proxy_write_target(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ TISecProxyState *s = opaque;
+ int thread_num = addr / 0x1000;
+ hwaddr off = addr % 0x1000;
+
+ int reg = off >> 2; /* 32-bit register index */
+ int byte = off & 0x3; /* byte offset within the 32-bit register */
+
+ struct TISecProxyThreadInfo *tinfo = &s->thread_info[thread_num];
+
+ if (reg >= 16) {
+ return;
+ }
+
+ uint32_t cur = (uint32_t)tinfo->current_message[reg];
+ uint32_t v32 = (uint32_t)value;
+
+ if (size == 1) {
+ uint32_t mask = 0xffu << (8 * byte);
+ cur = (cur & ~mask) | ((v32 & 0xffu) << (8 * byte));
+ } else if (size == 2) {
+ /* Round odd halfword writes down, same as in the read path. */
+ int hbyte = (byte & ~1);
+ uint32_t mask = 0xffffu << (8 * hbyte);
+ cur = (cur & ~mask) | ((v32 & 0xffffu) << (8 * hbyte));
+ } else if (size == 4) {
+ cur = v32;
+ } else {
+ return;
+ }
+
+ tinfo->current_message[reg] = cur;
+
+ /*
+ * The data-window commit point is the last register. Byte writes can
+ * hit it more than once while the last word is assembled.
+ */
+ if (reg == SEC_PROXY_MAX_MSG - 1) {
+ trace_ti_sec_proxy_complete_write(
+ ti_sec_proxy_get_thread_channel_name(thread_num),
+ tinfo->num_messages);
+
+ if (tinfo->cb) {
+ trace_ti_sec_proxy_announce_callback(
+ ti_sec_proxy_get_thread_channel_name(thread_num));
+ tinfo->cb(tinfo->cb_opaque, thread_num, &tinfo->current_message[1],
+ SEC_PROXY_MAX_MSG);
+ }
+ }
+}
+
+static const MemoryRegionOps ti_sec_proxy_target_ops = {
+ .read = ti_sec_proxy_read_target,
+ .write = ti_sec_proxy_write_target,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static void ti_sec_proxy_init(Object *obj)
+{
+ TISecProxyState *s = TI_SEC_PROXY(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ s->reg_array = register_init_block32(DEVICE(obj), sec_proxy_mmrs_regs_info,
+ ARRAY_SIZE(sec_proxy_mmrs_regs_info),
+ s->regs_info, s->regs,
+ &ti_sec_proxy_mmr_ops, true, 0x100);
+ sysbus_init_mmio(sbd, &s->reg_array->mem);
+
+ memory_region_init_io(&s->iomem_scfg, OBJECT(s), &ti_sec_proxy_scfg_ops, s,
+ "ti-sec-proxy-scfg", 0x80000);
+ sysbus_init_mmio(sbd, &s->iomem_scfg);
+
+ memory_region_init_io(&s->iomem_rt, OBJECT(s), &ti_sec_proxy_rt_ops, s,
+ "ti-sec-proxy-rt", 0x80000);
+ sysbus_init_mmio(sbd, &s->iomem_rt);
+
+ memory_region_init_io(&s->iomem_target_data, OBJECT(s),
+ &ti_sec_proxy_target_ops, s, "ti-sec-proxy-target",
+ 0x80000);
+ sysbus_init_mmio(sbd, &s->iomem_target_data);
+
+ sysbus_init_irq(sbd, &s->irq_evt);
+}
+
+static const TypeInfo ti_sec_proxy_info = {
+ .name = TYPE_TI_SEC_PROXY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TISecProxyState),
+ .class_init = ti_sec_proxy_class_init,
+ .instance_init = ti_sec_proxy_init,
+};
+
+static void ti_sec_proxy_types(void)
+{
+ type_register_static(&ti_sec_proxy_info);
+}
+
+type_init(ti_sec_proxy_types)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index ea7cde021e..dfa2d04117 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -461,6 +461,14 @@ rat_enable_region(int idx, uint64_t size, uint64_t source, uint64_t dest) "Enabl
rat_disable_region(int idx) "Disabling RAT Region %u"
rat_read_entry(int entry, int rel_offset, uint64_t offset) "Reading Entry %i at offset %i: offset: 0x%"PRIx64
+# ti-sec-proxy.c
+ti_sec_proxy_read_scfg(const char *name, int reg, uint64_t addr) "thread: %s, reg: %i. read from SCFG addr 0x%" PRIx64
+ti_sec_proxy_write_scfg(const char *name, int reg, uint64_t addr, uint64_t value) "thread: %s, reg: %i. write 0x%" PRIx64 "0x%" PRIx64
+ti_sec_proxy_complete_read(const char *name, int num) "thread: %s completed read message %i. Resetting number of messages"
+ti_sec_proxy_complete_write(const char *name, int num) "thread: %s completed write message %i. Resetting number of messages"
+ti_sec_proxy_announce_callback(const char *name) "invoce callback for thread: %s"
+ti_sec_proxy_read_rt(const char *name, int reg, uint64_t addr) "thread: %s, reg: 0x%x, read from RT addr 0x%" PRIx64
+
# ti-k3-trng.c
ti_k3_trng_read(uint64_t addr, uint32_t val) "offset 0x%" PRIx64 " -> 0x%08x"
ti_k3_trng_write(uint64_t addr, uint64_t val) "offset 0x%" PRIx64 " <- 0x%" PRIx64
diff --git a/include/hw/misc/ti-sec-proxy.h b/include/hw/misc/ti-sec-proxy.h
new file mode 100644
index 0000000000..d028242eff
--- /dev/null
+++ b/include/hw/misc/ti-sec-proxy.h
@@ -0,0 +1,108 @@
+/*
+ * TI K3 secure proxy
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef TI_SEC_PROXY_H
+#define TI_SEC_PROXY_H
+
+#include "hw/core/sysbus.h"
+#include "hw/core/register.h"
+#include "system/dma.h"
+#include "qom/object.h"
+
+#define TYPE_TI_SEC_PROXY "ti.sec-proxy"
+
+#define RMAX_TI_SEC_PROXY (3)
+
+OBJECT_DECLARE_SIMPLE_TYPE(TISecProxyState, TI_SEC_PROXY)
+
+typedef void (*TISecProxyMsgCb)(void *opaque,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords);
+
+enum TISciThreadIds {
+ MAIN_0_R5_0_READ_RESPONSE_THREAD_ID = 0,
+ MAIN_0_R5_0_WRITE_THREAD_ID = 1,
+ MAIN_0_R5_1_READ_RESPONSE_THREAD_ID = 2,
+ MAIN_0_R5_1_WRITE_THREAD_ID = 3,
+ MAIN_0_R5_2_READ_RESPONSE_THREAD_ID = 4,
+ MAIN_0_R5_2_WRITE_THREAD_ID = 5,
+ MAIN_0_R5_3_READ_RESPONSE_THREAD_ID = 6,
+ MAIN_0_R5_3_WRITE_THREAD_ID = 7,
+ A53_0_READ_RESPONSE_THREAD_ID = 8,
+ A53_0_WRITE_THREAD_ID = 9,
+ A53_1_READ_RESPONSE_THREAD_ID = 10,
+ A53_1_WRITE_THREAD_ID = 11,
+ A53_2_READ_RESPONSE_THREAD_ID = 12,
+ A53_2_WRITE_THREAD_ID = 13,
+ A53_3_READ_RESPONSE_THREAD_ID = 14,
+ A53_3_WRITE_THREAD_ID = 15,
+ M4_0_READ_RESPONSE_THREAD_ID = 16,
+ M4_0_WRITE_THREAD_ID = 17,
+ MAIN_1_R5_0_READ_RESPONSE_THREAD_ID = 18,
+ MAIN_1_R5_0_WRITE_THREAD_ID = 19,
+ MAIN_1_R5_1_READ_RESPONSE_THREAD_ID = 20,
+ MAIN_1_R5_1_WRITE_THREAD_ID = 21,
+ MAIN_1_R5_2_READ_RESPONSE_THREAD_ID = 22,
+ MAIN_1_R5_2_WRITE_THREAD_ID = 23,
+ MAIN_1_R5_3_READ_RESPONSE_THREAD_ID = 24,
+ MAIN_1_R5_3_WRITE_THREAD_ID = 25,
+ A53_4_READ_RESPONSE_THREAD_ID = 26,
+ A53_4_WRITE_THREAD_ID = 27,
+ ICSSG_0_READ_RESPONSE_THREAD_ID = 28,
+ ICSSG_0_WRITE_THREAD_ID = 29,
+ ICSSG_1_READ_RESPONSE_THREAD_ID = 30,
+ ICSSG_1_WRITE_THREAD_ID = 31,
+ SEC_PROXY_THREAD_ID_MAX,
+};
+
+#define SEC_PROXY_MSG_MAX_WORDS (16)
+#define SEC_PROXY_MSG_FIFO_DEPTH (16)
+
+struct TISecProxyThreadInfo {
+ uint8_t thread_id;
+ uint8_t num_messages;
+ uint32_t current_message[SEC_PROXY_MSG_MAX_WORDS];
+ bool is_outbound;
+ /* Callback invoked on commit (outbound only) */
+ TISecProxyMsgCb cb;
+ void *cb_opaque;
+};
+
+struct TISecProxyState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem_scfg;
+ MemoryRegion iomem_rt;
+ MemoryRegion iomem_target_data;
+ RegisterInfoArray *reg_array;
+ qemu_irq irq_evt;
+ uint32_t regs[RMAX_TI_SEC_PROXY];
+ RegisterInfo regs_info[RMAX_TI_SEC_PROXY];
+ struct TISecProxyThreadInfo thread_info[SEC_PROXY_THREAD_ID_MAX];
+ uint32_t msg_words;
+};
+
+
+/* Backend API used by ti-dmsc, resp. other consumers */
+void ti_sec_proxy_register_msg_cb(TISecProxyState *sp,
+ uint16_t thread_id,
+ TISecProxyMsgCb cb,
+ void *opaque);
+
+size_t ti_sec_proxy_push_msg(TISecProxyState *sp, uint16_t thread_id,
+ const uint32_t *words, size_t nbytes);
+
+uint32_t ti_sec_proxy_get_msg_words(TISecProxyState *sp);
+
+/*
+ * Clear an outbound thread counter before requeueing unsolicited messages.
+ * push_msg() only increments it; reads clear only inbound threads.
+ */
+void ti_sec_proxy_reset_thread_count(TISecProxyState *sp, uint16_t thread_id);
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 10/14] hw/misc: add TI DMSC (TI-SCI system controller) model
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (8 preceding siblings ...)
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 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 11/14] hw/arm: add TI K3 combined boot image parser Wadim Mueller
` (3 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
On real K3 silicon the device management and security controller runs the
TI system firmware (SYSFW) and answers TI-SCI requests from all other
cores. Rather than executing the vendor firmware blob, model the DMSC as a
TI-SCI endpoint on top of the secure proxy.
Implemented message classes:
- version and firmware capability queries
- device state, device reset and clock management (set/get state,
get/set frequency, query frequency, clock parents)
- processor boot control: request/release, set config, set control,
boot vector capture, wait_proc_boot_status and handover, which is what
actually starts the A53 and M4F cores
- firewall configuration and SA2UL/secure-revision queries needed by
TF-A and OP-TEE
- system reset
Unknown messages are NAKed and traced, which keeps guests that probe for
optional features working.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/ti-dmsc.c | 1725 +++++++++++++++++++++++++++++++++++++
hw/misc/trace-events | 32 +
include/hw/misc/ti-dmsc.h | 661 ++++++++++++++
5 files changed, 2422 insertions(+)
create mode 100644 hw/misc/ti-dmsc.c
create mode 100644 include/hw/misc/ti-dmsc.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index bea8e9341f..ebdbda1962 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -143,6 +143,9 @@ config MPS2_SCC
bool
select LED
+config TI_DMSC
+ bool
+
config TI_RAT
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index a8a40ea2a7..124d55502d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -123,6 +123,7 @@ system_ss.add(when: 'CONFIG_STM32L4X5_RCC', if_true: files('stm32l4x5_rcc.c'))
system_ss.add(when: 'CONFIG_MPS2_FPGAIO', if_true: files('mps2-fpgaio.c'))
system_ss.add(when: 'CONFIG_MPS2_SCC', if_true: files('mps2-scc.c'))
+system_ss.add(when: 'CONFIG_TI_DMSC', if_true: files('ti-dmsc.c'))
system_ss.add(when: 'CONFIG_TI_RAT', if_true: files('ti-rat.c'))
system_ss.add(when: 'CONFIG_TI_K3_CTRLMMR', if_true: files('ti-k3-ctrlmmr.c'))
system_ss.add(when: 'CONFIG_TI_K3_GTC', if_true: files('ti-k3-gtc.c'))
diff --git a/hw/misc/ti-dmsc.c b/hw/misc/ti-dmsc.c
new file mode 100644
index 0000000000..3b5b32830e
--- /dev/null
+++ b/hw/misc/ti-dmsc.c
@@ -0,0 +1,1725 @@
+/*
+ * TI DMSC emulator (minimal TISCI service) as QOM device
+ *
+ * This is used together with a TI SEC_PROXY model. The SEC_PROXY stays the
+ * transport/queue/data-window part, this device handles SYSFW/TISCI logic.
+ *
+ * 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 "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev.h"
+#include "hw/core/resettable.h"
+#include "system/reset.h"
+#include "system/runstate.h"
+#include "target/arm/arm-powerctl.h"
+#include "qemu/main-loop.h"
+#include "hw/misc/ti-dmsc.h"
+#include "trace.h"
+
+/*
+ * Responses, which go through sec-proxy, have one word less payload than the
+ * slot size: word 0 is reserved by ti_sec_proxy_push_msg().
+ * TI_DMSC_MAX_WORDS is the TISCI request limit, not transport capacity.
+ */
+#define TI_DMSC_SEC_PROXY_PAYLOAD_MAX \
+ ((SEC_PROXY_MSG_MAX_WORDS - 1) * sizeof(uint32_t))
+
+static const char *ti_dmsc_proc_name_from_id(uint32_t proc_id)
+{
+ switch (proc_id) {
+ case SCICLIENT_PROCID_A53_CL0_C0:
+ return "A53_CL0_C0";
+ case SCICLIENT_PROCID_A53_CL0_C1:
+ return "A53_CL0_C1";
+
+ case SCICLIENT_PROCID_R5_CL0_C0:
+ return "R5_CL0_C0";
+ case SCICLIENT_PROCID_R5_CL0_C1:
+ return "R5_CL0_C1";
+ case SCICLIENT_PROCID_R5_CL1_C0:
+ return "R5_CL1_C0";
+ case SCICLIENT_PROCID_R5_CL1_C1:
+ return "R5_CL1_C1";
+
+ case SCICLIENT_PROCID_MCU_M4FSS0_C0:
+ return "MCU_M4FSS0_C0";
+
+ default:
+ return "UNKNOWN_PROC";
+ }
+}
+
+static const char *ti_dmsc_host_name_from_id(uint32_t host_id)
+{
+ switch (host_id) {
+ case TISCI_HOST_ID_DMSC:
+ return "DMSC";
+
+ case TISCI_HOST_ID_MAIN_0_R5_0:
+ return "MAIN_0_R5_0";
+ case TISCI_HOST_ID_MAIN_0_R5_1:
+ return "MAIN_0_R5_1";
+ case TISCI_HOST_ID_MAIN_0_R5_2:
+ return "MAIN_0_R5_2";
+ case TISCI_HOST_ID_MAIN_0_R5_3:
+ return "MAIN_0_R5_3";
+
+ case TISCI_HOST_ID_A53_0:
+ return "A53_0";
+ case TISCI_HOST_ID_A53_1:
+ return "A53_1";
+ case TISCI_HOST_ID_A53_2:
+ return "A53_2";
+ case TISCI_HOST_ID_A53_3:
+ return "A53_3";
+ case TISCI_HOST_ID_A53_4:
+ return "A53_4";
+
+ case TISCI_HOST_ID_M4_0:
+ return "M4_0";
+
+ case TISCI_HOST_ID_MAIN_1_R5_0:
+ return "MAIN_1_R5_0";
+ case TISCI_HOST_ID_MAIN_1_R5_1:
+ return "MAIN_1_R5_1";
+ case TISCI_HOST_ID_MAIN_1_R5_2:
+ return "MAIN_1_R5_2";
+ case TISCI_HOST_ID_MAIN_1_R5_3:
+ return "MAIN_1_R5_3";
+
+ case TISCI_HOST_ID_ICSSG_0:
+ return "ICSSG_0";
+ case TISCI_HOST_ID_ICSSG_1:
+ return "ICSSG_1";
+
+ default:
+ return "UNKNOWN_HOST";
+ }
+}
+
+static const char *ti_dmsc_device_state_to_str(uint8_t state)
+{
+ switch (state) {
+ case TISCI_MSG_VALUE_DEVICE_SW_STATE_AUTO_OFF:
+ return "AUTO";
+ case TISCI_MSG_VALUE_DEVICE_SW_STATE_RETENTION:
+ return "RETENTION";
+ case TISCI_MSG_VALUE_DEVICE_SW_STATE_ON:
+ return "ON";
+ default:
+ return "UNKNOWN_STATE";
+ }
+}
+
+static const char *ti_dmsc_device_name_from_id(uint32_t dev_id)
+{
+ switch (dev_id) {
+ case TISCI_DEV_ADC0:
+ return "ADC0";
+ case TISCI_DEV_CMP_EVENT_INTROUTER0:
+ return "CMP_EVENT_INTROUTER0";
+ case TISCI_DEV_DBGSUSPENDROUTER0:
+ return "DBGSUSPENDROUTER0";
+ case TISCI_DEV_MAIN_GPIOMUX_INTROUTER0:
+ return "MAIN_GPIOMUX_INTROUTER0";
+ case TISCI_DEV_MCU_MCU_GPIOMUX_INTROUTER0:
+ return "MCU_MCU_GPIOMUX_INTROUTER0";
+ case TISCI_DEV_TIMESYNC_EVENT_INTROUTER0:
+ return "TIMESYNC_EVENT_INTROUTER0";
+ case TISCI_DEV_MCU_M4FSS0:
+ return "MCU_M4FSS0";
+ case TISCI_DEV_MCU_M4FSS0_CBASS_0:
+ return "MCU_M4FSS0_CBASS_0";
+ case TISCI_DEV_MCU_M4FSS0_CORE0:
+ return "MCU_M4FSS0_CORE0";
+ case TISCI_DEV_CPSW0:
+ return "CPSW0";
+ case TISCI_DEV_CPT2_AGGR0:
+ return "CPT2_AGGR0";
+ case TISCI_DEV_STM0:
+ return "STM0";
+ case TISCI_DEV_DCC0:
+ return "DCC0";
+ case TISCI_DEV_DCC1:
+ return "DCC1";
+ case TISCI_DEV_DCC2:
+ return "DCC2";
+ case TISCI_DEV_DCC3:
+ return "DCC3";
+ case TISCI_DEV_DCC4:
+ return "DCC4";
+ case TISCI_DEV_DCC5:
+ return "DCC5";
+ case TISCI_DEV_DMSC0:
+ return "DMSC0";
+ case TISCI_DEV_MCU_DCC0:
+ return "MCU_DCC0";
+ case TISCI_DEV_DEBUGSS_WRAP0:
+ return "DEBUGSS_WRAP0";
+ case TISCI_DEV_DMASS0:
+ return "DMASS0";
+ case TISCI_DEV_DMASS0_BCDMA_0:
+ return "DMASS0_BCDMA_0";
+ case TISCI_DEV_DMASS0_CBASS_0:
+ return "DMASS0_CBASS_0";
+ case TISCI_DEV_DMASS0_INTAGGR_0:
+ return "DMASS0_INTAGGR_0";
+ case TISCI_DEV_DMASS0_IPCSS_0:
+ return "DMASS0_IPCSS_0";
+ case TISCI_DEV_DMASS0_PKTDMA_0:
+ return "DMASS0_PKTDMA_0";
+ case TISCI_DEV_DMASS0_RINGACC_0:
+ return "DMASS0_RINGACC_0";
+ case TISCI_DEV_MCU_TIMER0:
+ return "MCU_TIMER0";
+ case TISCI_DEV_TIMER0:
+ return "TIMER0";
+ case TISCI_DEV_TIMER1:
+ return "TIMER1";
+ case TISCI_DEV_TIMER2:
+ return "TIMER2";
+ case TISCI_DEV_TIMER3:
+ return "TIMER3";
+ case TISCI_DEV_TIMER4:
+ return "TIMER4";
+ case TISCI_DEV_TIMER5:
+ return "TIMER5";
+ case TISCI_DEV_TIMER6:
+ return "TIMER6";
+ case TISCI_DEV_TIMER7:
+ return "TIMER7";
+ case TISCI_DEV_TIMER8:
+ return "TIMER8";
+ case TISCI_DEV_TIMER9:
+ return "TIMER9";
+ case TISCI_DEV_TIMER10:
+ return "TIMER10";
+ case TISCI_DEV_TIMER11:
+ return "TIMER11";
+ case TISCI_DEV_MCU_TIMER1:
+ return "MCU_TIMER1";
+ case TISCI_DEV_MCU_TIMER2:
+ return "MCU_TIMER2";
+ case TISCI_DEV_MCU_TIMER3:
+ return "MCU_TIMER3";
+ case TISCI_DEV_ECAP0:
+ return "ECAP0";
+ case TISCI_DEV_ECAP1:
+ return "ECAP1";
+ case TISCI_DEV_ECAP2:
+ return "ECAP2";
+ case TISCI_DEV_ELM0:
+ return "ELM0";
+ case TISCI_DEV_EMIF_DATA_0_VD:
+ return "EMIF_DATA_0_VD";
+ case TISCI_DEV_MMCSD0:
+ return "MMCSD0";
+ case TISCI_DEV_MMCSD1:
+ return "MMCSD1";
+ case TISCI_DEV_EQEP0:
+ return "EQEP0";
+ case TISCI_DEV_EQEP1:
+ return "EQEP1";
+ case TISCI_DEV_GTC0:
+ return "GTC0";
+ case TISCI_DEV_EQEP2:
+ return "EQEP2";
+ case TISCI_DEV_ESM0:
+ return "ESM0";
+ case TISCI_DEV_MCU_ESM0:
+ return "MCU_ESM0";
+ case TISCI_DEV_FSIRX0:
+ return "FSIRX0";
+ case TISCI_DEV_FSIRX1:
+ return "FSIRX1";
+ case TISCI_DEV_FSIRX2:
+ return "FSIRX2";
+ case TISCI_DEV_FSIRX3:
+ return "FSIRX3";
+ case TISCI_DEV_FSIRX4:
+ return "FSIRX4";
+ case TISCI_DEV_FSIRX5:
+ return "FSIRX5";
+ case TISCI_DEV_FSITX0:
+ return "FSITX0";
+ case TISCI_DEV_FSITX1:
+ return "FSITX1";
+ case TISCI_DEV_FSS0:
+ return "FSS0";
+ case TISCI_DEV_FSS0_FSAS_0:
+ return "FSS0_FSAS_0";
+ case TISCI_DEV_FSS0_OSPI_0:
+ return "FSS0_OSPI_0";
+ case TISCI_DEV_GICSS0:
+ return "GICSS0";
+ case TISCI_DEV_GPIO0:
+ return "GPIO0";
+ case TISCI_DEV_GPIO1:
+ return "GPIO1";
+ case TISCI_DEV_MCU_GPIO0:
+ return "MCU_GPIO0";
+ case TISCI_DEV_GPMC0:
+ return "GPMC0";
+ case TISCI_DEV_PRU_ICSSG0:
+ return "PRU_ICSSG0";
+ case TISCI_DEV_PRU_ICSSG1:
+ return "PRU_ICSSG1";
+ case TISCI_DEV_LED0:
+ return "LED0";
+ case TISCI_DEV_CPTS0:
+ return "CPTS0";
+ case TISCI_DEV_DDPA0:
+ return "DDPA0";
+ case TISCI_DEV_EPWM0:
+ return "EPWM0";
+ case TISCI_DEV_EPWM1:
+ return "EPWM1";
+ case TISCI_DEV_EPWM2:
+ return "EPWM2";
+ case TISCI_DEV_EPWM3:
+ return "EPWM3";
+ case TISCI_DEV_EPWM4:
+ return "EPWM4";
+ case TISCI_DEV_EPWM5:
+ return "EPWM5";
+ case TISCI_DEV_EPWM6:
+ return "EPWM6";
+ case TISCI_DEV_EPWM7:
+ return "EPWM7";
+ case TISCI_DEV_EPWM8:
+ return "EPWM8";
+ case TISCI_DEV_VTM0:
+ return "VTM0";
+ case TISCI_DEV_MAILBOX0:
+ return "MAILBOX0";
+ case TISCI_DEV_MAIN2MCU_VD:
+ return "MAIN2MCU_VD";
+ case TISCI_DEV_MCAN0:
+ return "MCAN0";
+ case TISCI_DEV_MCAN1:
+ return "MCAN1";
+ case TISCI_DEV_MCU_MCRC64_0:
+ return "MCU_MCRC64_0";
+ case TISCI_DEV_MCU2MAIN_VD:
+ return "MCU2MAIN_VD";
+ case TISCI_DEV_I2C0:
+ return "I2C0";
+ case TISCI_DEV_I2C1:
+ return "I2C1";
+ case TISCI_DEV_I2C2:
+ return "I2C2";
+ case TISCI_DEV_I2C3:
+ return "I2C3";
+ case TISCI_DEV_MCU_I2C0:
+ return "MCU_I2C0";
+ case TISCI_DEV_MCU_I2C1:
+ return "MCU_I2C1";
+ case TISCI_DEV_PCIE0:
+ return "PCIE0";
+ case TISCI_DEV_R5FSS0:
+ return "R5FSS0";
+ case TISCI_DEV_R5FSS1:
+ return "R5FSS1";
+ case TISCI_DEV_R5FSS0_CORE0:
+ return "R5FSS0_CORE0";
+ case TISCI_DEV_R5FSS0_CORE1:
+ return "R5FSS0_CORE1";
+ case TISCI_DEV_R5FSS1_CORE0:
+ return "R5FSS1_CORE0";
+ case TISCI_DEV_R5FSS1_CORE1:
+ return "R5FSS1_CORE1";
+ case TISCI_DEV_RTI0:
+ return "RTI0";
+ case TISCI_DEV_RTI1:
+ return "RTI1";
+ case TISCI_DEV_RTI8:
+ return "RTI8";
+ case TISCI_DEV_RTI9:
+ return "RTI9";
+ case TISCI_DEV_RTI10:
+ return "RTI10";
+ case TISCI_DEV_RTI11:
+ return "RTI11";
+ case TISCI_DEV_MCU_RTI0:
+ return "MCU_RTI0";
+ case TISCI_DEV_SA2_UL0:
+ return "SA2_UL0";
+ case TISCI_DEV_COMPUTE_CLUSTER0:
+ return "COMPUTE_CLUSTER0";
+ case TISCI_DEV_A53SS0_CORE_0:
+ return "A53SS0_CORE_0";
+ case TISCI_DEV_A53SS0_CORE_1:
+ return "A53SS0_CORE_1";
+ case TISCI_DEV_A53SS0:
+ return "A53SS0";
+ case TISCI_DEV_DDR16SS0:
+ return "DDR16SS0";
+ case TISCI_DEV_PSC0:
+ return "PSC0";
+ case TISCI_DEV_MCU_PSC0:
+ return "MCU_PSC0";
+ case TISCI_DEV_MCSPI0:
+ return "MCSPI0";
+ case TISCI_DEV_MCSPI1:
+ return "MCSPI1";
+ case TISCI_DEV_MCSPI2:
+ return "MCSPI2";
+ case TISCI_DEV_MCSPI3:
+ return "MCSPI3";
+ case TISCI_DEV_MCSPI4:
+ return "MCSPI4";
+ case TISCI_DEV_UART0:
+ return "UART0";
+ case TISCI_DEV_MCU_MCSPI0:
+ return "MCU_MCSPI0";
+ case TISCI_DEV_MCU_MCSPI1:
+ return "MCU_MCSPI1";
+ case TISCI_DEV_MCU_UART0:
+ return "MCU_UART0";
+ case TISCI_DEV_SPINLOCK0:
+ return "SPINLOCK0";
+ case TISCI_DEV_TIMERMGR0:
+ return "TIMERMGR0";
+ case TISCI_DEV_UART1:
+ return "UART1";
+ case TISCI_DEV_UART2:
+ return "UART2";
+ case TISCI_DEV_UART3:
+ return "UART3";
+ case TISCI_DEV_UART4:
+ return "UART4";
+ case TISCI_DEV_UART5:
+ return "UART5";
+ case TISCI_DEV_BOARD0:
+ return "BOARD0";
+ case TISCI_DEV_UART6:
+ return "UART6";
+ case TISCI_DEV_MCU_UART1:
+ return "MCU_UART1";
+ case TISCI_DEV_USB0:
+ return "USB0";
+ case TISCI_DEV_SERDES_10G0:
+ return "SERDES_10G0";
+ case TISCI_DEV_PBIST0:
+ return "PBIST0";
+ case TISCI_DEV_PBIST1:
+ return "PBIST1";
+ case TISCI_DEV_PBIST2:
+ return "PBIST2";
+ case TISCI_DEV_PBIST3:
+ return "PBIST3";
+ case TISCI_DEV_COMPUTE_CLUSTER0_PBIST_0:
+ return "COMPUTE_CLUSTER0_PBIST_0";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void ti_dmsc_init_device_states(TIDmscState *s)
+{
+ for (size_t i = 0; i < TISCI_DEV_ID_MAX; i++) {
+ s->dev_hw_state[i] = TISCI_MSG_VALUE_DEVICE_HW_STATE_ON;
+ s->dev_prog_state[i] = TISCI_MSG_VALUE_DEVICE_HW_STATE_ON;
+ }
+
+ s->dev_hw_state[TISCI_DEV_MCU_M4FSS0_CORE0] =
+ TISCI_MSG_VALUE_DEVICE_HW_STATE_OFF;
+ s->dev_prog_state[TISCI_DEV_MCU_M4FSS0_CORE0] =
+ TISCI_MSG_VALUE_DEVICE_HW_STATE_OFF;
+ s->m4_running = false;
+ memset(s->proc_bootvector, 0, sizeof(s->proc_bootvector));
+}
+
+static const char *ti_dmsc_message_name_from_id(uint16_t msg_id)
+{
+ switch (msg_id) {
+ case TISCI_MSG_GET_DEVICE:
+ return "GET_DEVICE";
+ case TISCI_MSG_SET_DEVICE:
+ return "SET_DEVICE";
+ case TISCI_MSG_SET_DEVICE_RESETS:
+ return "SET_DEVICE_RESETS";
+ case TISCI_MSG_DEVICE_DROP_POWERUP_REF:
+ return "DEVICE_DROP_POWERUP_REF";
+ case TISCI_MSG_PREPARE_SLEEP:
+ return "PREPARE_SLEEP";
+ case TISCI_MSG_ENTER_SLEEP:
+ return "ENTER_SLEEP";
+ case TISCI_MSG_VERSION:
+ return "VERSION";
+ case TISCI_MSG_BOOT_NOTIFICATION:
+ return "BOOT_NOTIFICATION";
+ case TISCI_MSG_BOARD_CONFIG:
+ return "BOARD_CONFIG";
+ case TISCI_MSG_BOARD_CONFIG_RM:
+ return "BOARD_CONFIG_RM";
+ case TISCI_MSG_BOARD_CONFIG_SECURITY:
+ return "BOARD_CONFIG_SECURITY";
+ case TISCI_MSG_BOARD_CONFIG_PM:
+ return "BOARD_CONFIG_PM";
+ case TISCI_MSG_ENABLE_WDT:
+ return "ENABLE_WDT";
+ case TISCI_MSG_WAKE_RESET:
+ return "WAKE_RESET";
+ case TISCI_MSG_WAKE_REASON:
+ return "WAKE_REASON";
+ case TISCI_MSG_GOODBYE:
+ return "GOODBYE";
+ case TISCI_MSG_SYS_RESET:
+ return "SYS_RESET";
+ case TISCI_MSG_QUERY_MSMC:
+ return "QUERY_MSMC";
+ case TISCI_MSG_GET_TRACE_CONFIG:
+ return "GET_TRACE_CONFIG";
+ case TISCI_MSG_QUERY_FW_CAPS:
+ return "QUERY_FW_CAPS";
+ case TISCI_MSG_SET_CLOCK:
+ return "SET_CLOCK";
+ case TISCI_MSG_GET_CLOCK:
+ return "GET_CLOCK";
+ case TISCI_MSG_SET_CLOCK_PARENT:
+ return "SET_CLOCK_PARENT";
+ case TISCI_MSG_GET_CLOCK_PARENT:
+ return "GET_CLOCK_PARENT";
+ case TISCI_MSG_GET_NUM_CLOCK_PARENTS:
+ return "GET_NUM_CLOCK_PARENTS";
+ case TISCI_MSG_SET_FREQ:
+ return "SET_FREQ";
+ case TISCI_MSG_QUERY_FREQ:
+ return "QUERY_FREQ";
+ case TISCI_MSG_GET_FREQ:
+ return "GET_FREQ";
+ case TISCI_MSG_PROC_REQUEST:
+ return "PROC_REQUEST";
+ case TISCI_MSG_PROC_RELEASE:
+ return "PROC_RELEASE";
+ case TISCI_MSG_PROC_HANDOVER:
+ return "PROC_HANDOVER";
+ case TISCI_MSG_SET_CONFIG:
+ return "SET_CONFIG";
+ case TISCI_MSG_SET_CTRL:
+ return "SET_CTRL";
+ case TISCI_MSG_GET_STATUS:
+ return "GET_STATUS";
+ case TISCI_MSG_WAIT_PROC_BOOT_STATUS:
+ return "WAIT_PROC_BOOT_STATUS";
+ case TISCI_MSG_FWL_SET:
+ return "FWL_SET";
+ case TISCI_MSG_FWL_GET:
+ return "FWL_GET";
+ case TISCI_MSG_FWL_CHANGE_OWNER:
+ return "FWL_CHANGE_OWNER";
+ case TISCI_MSG_SA2UL_GET_DKEK:
+ return "SA2UL_GET_DKEK";
+ case TISCI_MSG_READ_SWREV:
+ return "READ_SWREV";
+ case TISCI_MSG_READ_KEYCNT_KEYREV:
+ return "READ_KEYCNT_KEYREV";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+/*
+ * The DMSC has no MMIO. Sec-proxy calls us, when a client commits the last
+ * word of a request data window.
+ */
+static void ti_dmsc_handle_one(TIDmscClient *client, uint16_t thread_id,
+ const uint32_t *words, size_t nwords);
+
+/*
+ * Push a response to the client's TX thread. Secure hosts carry a 4-byte
+ * {u16 checksum; u16 reserved} prefix before TISciMsgHdr. The checksum word
+ * is present in the transport, but not validated by this model.
+ *
+ * TI_SCI_FLAG_REQ_GENERIC_NORESPONSE is handled here too. The handlers run
+ * still for side effects, but replies are suppressed unless AOP was set.
+ */
+static size_t ti_dmsc_client_respond(TIDmscClient *client, const void *msg,
+ size_t nbytes)
+{
+ TIDmscState *s = client->dmsc;
+ const uint32_t *words = msg;
+
+ if (!client->cur_req_wants_resp) {
+ return nbytes;
+ }
+
+ if (client->secure) {
+ uint32_t buf[TI_DMSC_MAX_WORDS + 1] = {0};
+
+ /*
+ * Control the sec-proxy payload limit, not only the local scratch
+ * buffer. The scratch buffer has the extra secure-prefix word.
+ */
+ if (nbytes + sizeof(uint32_t) > TI_DMSC_SEC_PROXY_PAYLOAD_MAX) {
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "ti-dmsc: secure response too large (%zu bytes), dropping\n",
+ nbytes);
+ return 0;
+ }
+
+ memcpy((uint8_t *)buf + sizeof(uint32_t), words, nbytes);
+ return ti_sec_proxy_push_msg(s->sec_proxy, client->tx_thread_id, buf,
+ nbytes + sizeof(uint32_t));
+ }
+
+ if (nbytes > TI_DMSC_SEC_PROXY_PAYLOAD_MAX) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: response too large (%zu bytes), dropping\n",
+ nbytes);
+ return 0;
+ }
+
+ return ti_sec_proxy_push_msg(s->sec_proxy, client->tx_thread_id, words,
+ nbytes);
+}
+
+/*
+ * Queue TISCI_MSG_BOOT_NOTIFICATION for secure boot hosts. SYSFW sends this
+ * unsolicited message once it is ready on the host response thread.
+ *
+ * Re-arm it on every reset: sec-proxy reset leaves thread slots alone. Reset
+ * the outbound counter first, since pushes only increment it.
+ */
+static void ti_dmsc_send_boot_notification(TIDmscClient *client)
+{
+ TISciMsgHdr notif = {0};
+
+ notif.type = TISCI_MSG_BOOT_NOTIFICATION;
+ notif.host = TISCI_HOST_ID_DMSC;
+ notif.seq = 0;
+ notif.flags = 0;
+
+ ti_sec_proxy_reset_thread_count(client->dmsc->sec_proxy,
+ client->tx_thread_id);
+
+ /*
+ * This is unsolicited, so ignore no-response state of the previous
+ * request and force delivery.
+ */
+ client->cur_req_wants_resp = true;
+
+ if (!ti_dmsc_client_respond(client, ¬if, sizeof(notif))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push BOOT_NOTIFICATION into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/* Bottom half: handle pending message outside of MMIO context */
+static void ti_dmsc_bh(void *opaque)
+{
+ TIDmscState *s = opaque;
+ uint32_t local_words[TI_DMSC_MAX_WORDS];
+
+ while (true) {
+ TIDmscClient *client = NULL;
+ size_t local_nwords = 0;
+ uint16_t tid = 0;
+
+ qemu_mutex_lock(&s->lock);
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ if (s->clients[i].pending) {
+ client = &s->clients[i];
+ break;
+ }
+ }
+ if (!client) {
+ qemu_mutex_unlock(&s->lock);
+ return;
+ }
+
+ tid = client->rx_thread_id;
+ local_nwords = client->pending_nwords;
+ if (local_nwords > TI_DMSC_MAX_WORDS) {
+ local_nwords = TI_DMSC_MAX_WORDS;
+ }
+ memcpy(local_words, client->pending_words,
+ local_nwords * sizeof(uint32_t));
+ client->pending = false;
+ client->pending_nwords = 0;
+ qemu_mutex_unlock(&s->lock);
+
+ ti_dmsc_handle_one(client, tid, local_words, local_nwords);
+ }
+}
+
+/*
+ * Sec-proxy calls this, when a client commits a message on our RX thread.
+ */
+static void ti_dmsc_sec_proxy_cb(void *opaque, uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TIDmscClient *client = opaque;
+ TIDmscState *s = client->dmsc;
+
+ if (thread_id != client->rx_thread_id) {
+ return;
+ }
+
+ qemu_mutex_lock(&s->lock);
+ client->pending = true;
+
+ if (nwords > TI_DMSC_MAX_WORDS) {
+ nwords = TI_DMSC_MAX_WORDS;
+ }
+ memcpy(client->pending_words, words, nwords * sizeof(uint32_t));
+ client->pending_nwords = nwords;
+ qemu_mutex_unlock(&s->lock);
+
+ qemu_bh_schedule(s->bh);
+}
+
+/* Dispatch one committed TISCI message. */
+static void ti_dmsc_handle_one(TIDmscClient *client, uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+
+ if (!s->sec_proxy) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: No sec-proxy linked, dropping message\n");
+ return;
+ }
+
+ size_t hdr_words = sizeof(TISciMsgHdr) / sizeof(uint32_t);
+ size_t min_words = hdr_words + (client->secure ? 1 : 0);
+
+ if (nwords < min_words) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Short message (words=%zu), dropping\n", nwords);
+ return;
+ }
+
+ if (client->secure) {
+ /*
+ * Secure R5 clients prepend a checksum/reserved word before
+ * TISciMsgHdr. Drop it before we cast payloads.
+ */
+ words += 1;
+ nwords -= 1;
+ }
+
+ TISciMsgHdr hdr = {0};
+ memcpy(&hdr, words, MIN(sizeof(hdr), nwords * sizeof(uint32_t)));
+
+ /*
+ * Remember AOP before dispatch. ti_dmsc_client_respond() uses it to
+ * suppress replies for requests with TI_SCI_FLAG_REQ_GENERIC_NORESPONSE.
+ */
+ client->cur_req_wants_resp = (hdr.flags & TISCI_MSG_FLAG_AOP) != 0;
+
+ if (hdr.type < ARRAY_SIZE(s->msg_handler) && s->msg_handler[hdr.type]) {
+ trace_dmsc_new_message_received(
+ hdr.type, ti_dmsc_message_name_from_id(hdr.type),
+ ti_dmsc_host_name_from_id(hdr.host), thread_id);
+ s->msg_handler[hdr.type](client, &hdr, thread_id, words, nwords);
+ return;
+ } else {
+ TISciMsgHdr resp = hdr;
+
+ trace_dmsc_unsupported_message(
+ ti_dmsc_message_name_from_id(hdr.type), hdr.type,
+ ti_dmsc_host_name_from_id(hdr.host), thread_id);
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "ti-dmsc: No handler for message type=0x%04x (%s), dropping\n",
+ hdr.type, ti_dmsc_message_name_from_id(hdr.type));
+
+ /*
+ * Header-only NAKs unblock callers, which wait for a response,
+ * instead of leaving them to time out.
+ */
+ resp.flags = 0;
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push NAK response into sec-proxy "
+ "thread=%u\n",
+ client->tx_thread_id);
+ }
+ }
+}
+
+static void ti_dmsc_reset_hold(Object *obj, ResetType type)
+{
+ TIDmscState *s = TI_DMSC(obj);
+
+ qemu_mutex_lock(&s->lock);
+ ti_dmsc_init_device_states(s);
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ s->clients[i].pending = false;
+ s->clients[i].pending_nwords = 0;
+ memset(s->clients[i].pending_words, 0,
+ sizeof(s->clients[i].pending_words));
+ }
+ /*
+ * msg_handler contains realize-time function pointers, not guest state.
+ * Clearing it during reset disables all TISCI handlers after the first
+ * cold reset.
+ */
+ qemu_mutex_unlock(&s->lock);
+
+ /*
+ * Secure clients get a fresh boot notification after every reset.
+ */
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ if (s->clients[i].secure) {
+ ti_dmsc_send_boot_notification(&s->clients[i]);
+ }
+ }
+}
+
+static TISciMsgHdr ti_dmsc_set_resp_flags(TISciMsgHdr *req_hdr, int add_flags)
+{
+ TISciMsgHdr resp = *req_hdr;
+ resp.flags =
+ ((req_hdr->flags & TISCI_MSG_FLAG_AOP) ? TISCI_MSG_FLAG_ACK : 0) |
+ add_flags;
+ return resp;
+}
+
+static void ti_dmsc_handle_set_clock(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgSetClockReq *req = (struct TisciMsgSetClockReq *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_set_clock(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device),
+ req->clk);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_CLOCK response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SET_CLOCK_PARENT stores no clock tree state. The model only returns the
+ * generic TISCI ACK/NAK header.
+ */
+static void ti_dmsc_handle_set_clock_parent(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgSetClockParentReq *req =
+ (struct TisciMsgSetClockParentReq *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_set_clock_parent(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->dev_id),
+ req->clk_id, req->parent_id);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_CLOCK_PARENT response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_set_freq(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_FREQ response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_query_freq(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgQueryFreqReq *req = (struct TisciMsgQueryFreqReq *)words;
+ struct TisciMsgQueryFreqResp resp = {0};
+
+ trace_dmsc_handle_query_freq(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device),
+ req->clk, req->clk32, req->target_freq_hz);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.freq_hz = req->target_freq_hz;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push QUERY_FREQ response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_freq(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgGetFreqReq *req = (struct TisciMsgGetFreqReq *)words;
+ struct TisciMsgQueryFreqResp resp = {0};
+
+ trace_dmsc_handle_get_freq(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device),
+ req->clk);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ /*
+ * Clock rates are not modelled per device. Return the fixed 200 MHz
+ * rate used by the AM64x board model for generic clocks.
+ */
+ resp.freq_hz = 200000000ULL;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_FREQ response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_clock_parents(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgGetNumClockParentsReq *req =
+ (struct TisciMsgGetNumClockParentsReq *)words;
+ struct TisciMsgGetNumClockParentsResp resp = {0};
+
+ trace_dmsc_handle_get_clock_parents(
+ ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device), req->clk, req->clk32);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ /*
+ * Clock parent topology is not modelled. Report two possible parents,
+ * which is the minimum topology where SET_CLOCK_PARENT has a meaning.
+ */
+ resp.num_parents = 2;
+ resp.num_parentint32_t = UINT_MAX;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_CLOCK_PARENTS response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * Clock muxes are not modelled. Report parent 0 as actual parent for all
+ * clocks.
+ */
+static void ti_dmsc_handle_get_clock_parent(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgGetClockParentResp resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.parent = 0;
+ resp.parent32 = 0;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_CLOCK_PARENT response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_clock(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgGetClockReq *req = (struct TisciMsgGetClockReq *)words;
+ struct TisciMsgGetClockResp resp = {0};
+
+ trace_dmsc_handle_get_clock(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device),
+ req->clk, req->clk32);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.current_state = resp.programmed_state =
+ TISCI_MSG_VALUE_DEVICE_HW_STATE_ON;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_CLOCK response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_stop_proc(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TiSciMsgReqProcRelease *req = (struct TiSciMsgReqProcRelease *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+ trace_dmsc_stop_proc(ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id,
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (req->processor_id == SCICLIENT_PROCID_MCU_M4FSS0_C0) {
+ s->m4_running = false;
+ }
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push PROC_RELEASE response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_start_proc(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TiSciMsgReqProcRequest *req = (struct TiSciMsgReqProcRequest *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_start_proc(ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id,
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push PROC_REQUEST response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SET_CTRL carries processor control flags, e.g. halt and reset-vector
+ * latch. Boot vectors are captured by SET_CONFIG and release happens via
+ * SET_DEVICE, so here is no processor-control state modeled.
+ */
+static void ti_dmsc_handle_proc_set_ctrl(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_CTRL response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * PROC_HANDOVER transfers a processor to another TISCI host. Ownership is
+ * not modeled; the request gets only the generic ACK/NAK header.
+ */
+static void ti_dmsc_handover_proc(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TiSciMsgReqProcHandover *req =
+ (struct TiSciMsgReqProcHandover *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handover_proc(ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id,
+ ti_dmsc_host_name_from_id(req->host_id),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push PROC_HANDOVER response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SYS_RESET is a TISCI system-reset command and is normally sent without
+ * AOP. Request a QEMU system reset; an AOP caller gets the bare ACK first.
+ */
+static void ti_dmsc_handle_sys_reset(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_sys_reset(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ ti_dmsc_client_respond(client, &resp, sizeof(resp));
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+static void ti_dmsc_query_hw_caps(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TiSciMsgQueryFwCapsResp resp = {0};
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.fw_caps = MSG_FLAG_CAPS_GENERIC;
+ trace_dmsc_get_fw_caps(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push FW CAPABILITIES response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_get_version(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TiSciMsgVersionResp resp = {0};
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.firmware_revision = 0x000a;
+ resp.abi_major = 4;
+ resp.abi_minor = 0;
+ snprintf(resp.firmware_description, sizeof(resp.firmware_description),
+ "QEMU_TI_DMSC (Wadims DMSC)");
+ trace_dmsc_get_version(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ resp.firmware_description);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push VERSION response into sec-proxy "
+ "thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_device(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TisciMsgGetDeviceReq *req = (struct TisciMsgGetDeviceReq *)words;
+ struct TisciMsgGetDeviceResp resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ if (req->id < TISCI_DEV_ID_MAX) {
+ resp.current_state = s->dev_hw_state[req->id];
+ resp.programmed_state = s->dev_prog_state[req->id];
+ } else {
+ resp.current_state = resp.programmed_state =
+ TISCI_MSG_VALUE_DEVICE_HW_STATE_ON;
+ }
+
+ trace_dmsc_handle_get_device(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->id),
+ resp.programmed_state, resp.current_state);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_DEVICE response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_status(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TisciMsgProcGetStatusReq *req =
+ (struct TisciMsgProcGetStatusReq *)words;
+ struct TisciMsgProcGetStatusResp resp = {0};
+
+ trace_dmsc_handle_get_status(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.processor_id = req->processor_id;
+ resp.bootvector_lo = 0;
+ resp.bootvector_hi = 0;
+ resp.config_flags_1 = 0;
+ resp.control_flags_1 = 0;
+ resp.status_flags_1 = 0;
+
+ if (req->processor_id == SCICLIENT_PROCID_A53_CL0_C0 ||
+ req->processor_id == SCICLIENT_PROCID_A53_CL0_C1) {
+ uint64_t bv =
+ s->proc_bootvector[req->processor_id - SCICLIENT_PROCID_A53_CL0_C0];
+
+ resp.bootvector_lo = (uint32_t)bv;
+ resp.bootvector_hi = (uint32_t)(bv >> 32);
+ }
+
+ if (req->processor_id == SCICLIENT_PROCID_MCU_M4FSS0_C0) {
+ resp.status_flags_1 |= TISCI_MSG_VAL_PROC_BOOT_STATUS_FLAG_M4F_WFI;
+ }
+
+ trace_dmsc_get_status_resp(ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id, resp.status_flags_1,
+ s->m4_running);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_STATUS response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * WAIT_PROC_BOOT_STATUS waits for processor WFE/WFI status bits. Per-core
+ * polling is not modeled; no-response requests complete without a reply.
+ */
+static void ti_dmsc_handle_wait_proc_boot_status(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgReqWaitProcBootStatus *req =
+ (struct TisciMsgReqWaitProcBootStatus *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_wait_proc_boot_status(
+ ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_proc_name_from_id(req->processor_id), req->processor_id);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push WAIT_PROC_BOOT_STATUS response "
+ "into sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_set_device_state(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TisciMsgSetDeviceReq *req = (struct TisciMsgSetDeviceReq *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_set_device_state(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->id),
+ ti_dmsc_device_state_to_str(req->state));
+
+ if (req->id < TISCI_DEV_ID_MAX) {
+ s->dev_hw_state[req->id] = s->dev_prog_state[req->id] = req->state;
+ }
+
+ if (req->id == TISCI_DEV_MCU_M4FSS0_CORE0 &&
+ req->state != TISCI_MSG_VALUE_DEVICE_SW_STATE_ON) {
+ s->m4_running = false;
+ }
+
+ /*
+ * SET_DEVICE ON releases an A53 core at the vector captured by
+ * SET_CONFIG. Start the vCPU in EL3/AArch64; ALREADY_ON is harmless on
+ * repeated handoffs.
+ */
+ if (req->id == TISCI_DEV_A53SS0_CORE_0 ||
+ req->id == TISCI_DEV_A53SS0_CORE_1) {
+ int core = req->id - TISCI_DEV_A53SS0_CORE_0;
+ uint64_t cpuid = s->a53_cpu_id_base + core;
+
+ if (req->state == TISCI_MSG_VALUE_DEVICE_SW_STATE_ON) {
+ uint64_t entry = s->proc_bootvector[core];
+
+ trace_dmsc_a53_start(core, entry);
+ arm_set_cpu_on(cpuid, entry, 0, /* target_el */ 3,
+ /* target_aa64 */ true);
+ } else if (req->state == TISCI_MSG_VALUE_DEVICE_SW_STATE_AUTO_OFF) {
+ trace_dmsc_a53_stop(core);
+ arm_set_cpu_off(cpuid);
+ }
+ }
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_DEVICE response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_set_device_resets(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TisciMsgSetDeviceResetsReq *req =
+ (struct TisciMsgSetDeviceResetsReq *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_set_device_resets(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->id),
+ req->resets);
+
+ if (req->id == TISCI_DEV_MCU_M4FSS0_CORE0) {
+ s->m4_running = !(req->resets);
+ if (req->resets == 1) {
+ arm_set_cpu_off(s->m4_cpu_id);
+ } else {
+ arm_set_cpu_on_and_reset(s->m4_cpu_id);
+ }
+ }
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_DEVICE_RESETS response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * BOARD_CONFIG messages carry SYSFW board-configuration blobs. This model
+ * accepts them, but does not consume or store the blobs.
+ */
+static void ti_dmsc_handle_board_config(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push BOARD_CONFIG response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SET_CONFIG carries the A53 boot vector used later by SET_DEVICE. The
+ * packed TISCI request puts bootvector_low at byte offset 9, so read it
+ * with byte loads, not with a struct cast.
+ */
+static void ti_dmsc_handle_proc_set_config(TIDmscClient *client,
+ TISciMsgHdr *hdr, uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_proc_set_config(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (nwords * sizeof(uint32_t) >= sizeof(TISciMsgHdr) + 9) {
+ /* Packed payload right after the 8-byte header. */
+ const uint8_t *p = (const uint8_t *)words + sizeof(TISciMsgHdr);
+ uint8_t proc_id = p[0];
+ uint64_t bv = (uint64_t)(uint32_t)ldl_le_p(p + 1) |
+ ((uint64_t)(uint32_t)ldl_le_p(p + 5) << 32);
+
+ if (proc_id == SCICLIENT_PROCID_A53_CL0_C0 ||
+ proc_id == SCICLIENT_PROCID_A53_CL0_C1) {
+ s->proc_bootvector[proc_id - SCICLIENT_PROCID_A53_CL0_C0] = bv;
+ trace_dmsc_a53_bootvector(proc_id, bv);
+ }
+ }
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_CONFIG response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * FWL_SET programs a firewall region. Firewall state is not modelled, so the
+ * request is accepted and only a generic ACK/NAK is returned.
+ */
+static void ti_dmsc_handle_fwl_set(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgReqFwlSetFirewallRegion *req =
+ (struct TisciMsgReqFwlSetFirewallRegion *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_fwl_set(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host), req->fwl_id,
+ req->region);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push FWL_SET response into sec-proxy "
+ "thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * FWL_GET returns one firewall region descriptor. The model echoes fwl_id
+ * and region, and leaves control/permission fields zeroed.
+ */
+static void ti_dmsc_handle_fwl_get(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgReqFwlGetFirewallRegion *req =
+ (struct TisciMsgReqFwlGetFirewallRegion *)words;
+ struct TisciMsgRespFwlGetFirewallRegion resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.fwl_id = req->fwl_id;
+ resp.region = req->region;
+
+ trace_dmsc_handle_fwl_get(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host), req->fwl_id,
+ req->region);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push FWL_GET response into sec-proxy "
+ "thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * FWL_CHANGE_OWNER changes the owner of a firewall region. Ownership is not
+ * tracked; echo the IDs and owner index back.
+ */
+static void ti_dmsc_handle_fwl_change_owner(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgReqFwlChangeOwnerInfo *req =
+ (struct TisciMsgReqFwlChangeOwnerInfo *)words;
+ struct TisciMsgRespFwlChangeOwnerInfo resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.fwl_id = req->fwl_id;
+ resp.region = req->region;
+ resp.owner_index = req->owner_index;
+
+ trace_dmsc_handle_fwl_change_owner(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ req->fwl_id, req->region,
+ req->owner_index);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push FWL_CHANGE_OWNER response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SA2UL_GET_DKEK returns derived key data from SA2UL. No key ladder is
+ * modelled; the response contains zero bytes and is no secret material.
+ */
+static void ti_dmsc_handle_sa2ul_get_dkek(TIDmscClient *client,
+ TISciMsgHdr *hdr, uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ struct TisciMsgReqSa2ulGetDkek *req =
+ (struct TisciMsgReqSa2ulGetDkek *)words;
+ struct TisciMsgRespSa2ulGetDkek resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_sa2ul_get_dkek(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ req->sa2ul_instance);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SA2UL_GET_DKEK response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * READ_SWREV and READ_KEYCNT_KEYREV are security status queries. The status
+ * words are not modelled and read back as zero.
+ */
+static void ti_dmsc_handle_read_swrev(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgRespReadSwrev resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_read_swrev(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push READ_SWREV response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_read_keycnt_keyrev(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgRespReadKeycntKeyrev resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_read_keycnt_keyrev(
+ ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push READ_KEYCNT_KEYREV response "
+ "into sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_realize(DeviceState *dev, Error **errp)
+{
+ ERRP_GUARD();
+ TIDmscState *s = TI_DMSC(dev);
+
+ if (!s->sec_proxy) {
+ error_setg(errp, "ti-dmsc: 'sec-proxy' link not set");
+ return;
+ }
+
+ s->msg_words = ti_sec_proxy_get_msg_words(s->sec_proxy);
+ if (s->msg_words == 0) {
+ /* Fallback if sec-proxy does not provide it yet. */
+ s->msg_words = TI_DMSC_MAX_WORDS;
+ }
+
+ if (s->msg_words > TI_DMSC_MAX_WORDS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: msg_words=%u too large, clamping to %u\n",
+ s->msg_words, TI_DMSC_MAX_WORDS);
+ s->msg_words = TI_DMSC_MAX_WORDS;
+ }
+
+ if (s->num_rx_threads || s->num_tx_threads) {
+ if (s->num_rx_threads != s->num_tx_threads) {
+ error_setg(
+ errp,
+ "ti-dmsc: rx-threads and tx-threads must have the same length");
+ return;
+ }
+ if (s->num_rx_threads == 0) {
+ error_setg(errp, "ti-dmsc: rx-threads list is empty");
+ return;
+ }
+ s->num_clients = s->num_rx_threads;
+ s->clients = g_new0(TIDmscClient, s->num_clients);
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ s->clients[i].dmsc = s;
+ s->clients[i].rx_thread_id = s->rx_thread_ids[i];
+ s->clients[i].tx_thread_id = s->tx_thread_ids[i];
+ }
+ } else {
+ s->num_clients = 1;
+ s->clients = g_new0(TIDmscClient, s->num_clients);
+ s->clients[0].dmsc = s;
+ s->clients[0].rx_thread_id = s->rx_thread_id;
+ s->clients[0].tx_thread_id = s->tx_thread_id;
+ }
+
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ for (uint32_t j = 0; j < s->num_secure_rx_threads; j++) {
+ if (s->clients[i].rx_thread_id == s->secure_rx_threads[j]) {
+ s->clients[i].secure = true;
+ break;
+ }
+ }
+ }
+
+ s->msg_handler[TISCI_MSG_PROC_RELEASE] = ti_dmsc_stop_proc;
+ s->msg_handler[TISCI_MSG_PROC_REQUEST] = ti_dmsc_start_proc;
+ s->msg_handler[TISCI_MSG_PROC_HANDOVER] = ti_dmsc_handover_proc;
+ s->msg_handler[TISCI_MSG_SET_CTRL] = ti_dmsc_handle_proc_set_ctrl;
+ s->msg_handler[TISCI_MSG_SYS_RESET] = ti_dmsc_handle_sys_reset;
+ s->msg_handler[TISCI_MSG_QUERY_FW_CAPS] = ti_dmsc_query_hw_caps;
+ s->msg_handler[TISCI_MSG_VERSION] = ti_dmsc_get_version;
+ s->msg_handler[TISCI_MSG_GET_DEVICE] = ti_dmsc_handle_get_device;
+ s->msg_handler[TISCI_MSG_SET_DEVICE] = ti_dmsc_handle_set_device_state;
+ s->msg_handler[TISCI_MSG_GET_STATUS] = ti_dmsc_handle_get_status;
+ s->msg_handler[TISCI_MSG_WAIT_PROC_BOOT_STATUS] =
+ ti_dmsc_handle_wait_proc_boot_status;
+ s->msg_handler[TISCI_MSG_SET_DEVICE_RESETS] =
+ ti_dmsc_handle_set_device_resets;
+ s->msg_handler[TISCI_MSG_GET_CLOCK] = ti_dmsc_handle_get_clock;
+ s->msg_handler[TISCI_MSG_SET_CLOCK] = ti_dmsc_handle_set_clock;
+ s->msg_handler[TISCI_MSG_GET_CLOCK_PARENT] =
+ ti_dmsc_handle_get_clock_parent;
+ s->msg_handler[TISCI_MSG_GET_NUM_CLOCK_PARENTS] =
+ ti_dmsc_handle_get_clock_parents;
+ s->msg_handler[TISCI_MSG_SET_CLOCK_PARENT] =
+ ti_dmsc_handle_set_clock_parent;
+ s->msg_handler[TISCI_MSG_QUERY_FREQ] = ti_dmsc_handle_query_freq;
+ s->msg_handler[TISCI_MSG_GET_FREQ] = ti_dmsc_handle_get_freq;
+ s->msg_handler[TISCI_MSG_SET_FREQ] = ti_dmsc_handle_set_freq;
+ s->msg_handler[TISCI_MSG_SET_CONFIG] = ti_dmsc_handle_proc_set_config;
+ s->msg_handler[TISCI_MSG_BOARD_CONFIG] = ti_dmsc_handle_board_config;
+ s->msg_handler[TISCI_MSG_BOARD_CONFIG_RM] = ti_dmsc_handle_board_config;
+ s->msg_handler[TISCI_MSG_BOARD_CONFIG_SECURITY] =
+ ti_dmsc_handle_board_config;
+ s->msg_handler[TISCI_MSG_BOARD_CONFIG_PM] = ti_dmsc_handle_board_config;
+ s->msg_handler[TISCI_MSG_FWL_SET] = ti_dmsc_handle_fwl_set;
+ s->msg_handler[TISCI_MSG_FWL_GET] = ti_dmsc_handle_fwl_get;
+ s->msg_handler[TISCI_MSG_FWL_CHANGE_OWNER] =
+ ti_dmsc_handle_fwl_change_owner;
+ s->msg_handler[TISCI_MSG_SA2UL_GET_DKEK] = ti_dmsc_handle_sa2ul_get_dkek;
+ s->msg_handler[TISCI_MSG_READ_SWREV] = ti_dmsc_handle_read_swrev;
+ s->msg_handler[TISCI_MSG_READ_KEYCNT_KEYREV] =
+ ti_dmsc_handle_read_keycnt_keyrev;
+
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ ti_sec_proxy_register_msg_cb(s->sec_proxy, s->clients[i].rx_thread_id,
+ ti_dmsc_sec_proxy_cb, &s->clients[i]);
+ }
+
+ ti_dmsc_init_device_states(s);
+
+ /*
+ * ti-dmsc is a pure QOM child with no bus/MMIO reset path. Register it
+ * with the global reset container, so cold boot and system_reset both
+ * requeue the DMSC boot notification.
+ */
+ qemu_register_resettable(OBJECT(dev));
+}
+
+static void ti_dmsc_init(Object *obj)
+{
+ TIDmscState *s = TI_DMSC(obj);
+
+ qemu_mutex_init(&s->lock);
+ s->bh = qemu_bh_new(ti_dmsc_bh, s);
+ s->num_rx_threads = 0;
+ s->rx_thread_ids = NULL;
+ s->num_tx_threads = 0;
+ s->tx_thread_ids = NULL;
+ s->num_secure_rx_threads = 0;
+ s->secure_rx_threads = NULL;
+ s->num_clients = 0;
+ s->clients = NULL;
+
+ /*
+ * Legacy single-client defaults; machines pass normally thread arrays.
+ */
+ /* Legacy M4 response thread default: 16. */
+
+ s->msg_words = TI_DMSC_MAX_WORDS;
+ object_property_add_link(
+ obj, "sec-proxy", TYPE_TI_SEC_PROXY, (Object **)&s->sec_proxy,
+ qdev_prop_allow_set_link_before_realize, OBJ_PROP_LINK_STRONG);
+}
+
+static void ti_dmsc_finalize(Object *obj)
+{
+ TIDmscState *s = TI_DMSC(obj);
+
+ if (s->bh) {
+ qemu_bh_delete(s->bh);
+ s->bh = NULL;
+ }
+ g_free(s->clients);
+ s->clients = NULL;
+ s->num_clients = 0;
+ g_free(s->rx_thread_ids);
+ s->rx_thread_ids = NULL;
+ s->num_rx_threads = 0;
+ g_free(s->tx_thread_ids);
+ s->tx_thread_ids = NULL;
+ s->num_tx_threads = 0;
+ g_free(s->secure_rx_threads);
+ s->secure_rx_threads = NULL;
+ s->num_secure_rx_threads = 0;
+ qemu_mutex_destroy(&s->lock);
+}
+
+static const Property ti_dmsc_props[] = {
+ DEFINE_PROP_UINT16("rx-thread", TIDmscState, rx_thread_id, 17),
+ DEFINE_PROP_UINT16("tx-thread", TIDmscState, tx_thread_id, 16),
+ DEFINE_PROP_ARRAY("rx-threads", TIDmscState, num_rx_threads, rx_thread_ids,
+ qdev_prop_uint16, uint16_t),
+ DEFINE_PROP_ARRAY("tx-threads", TIDmscState, num_tx_threads, tx_thread_ids,
+ qdev_prop_uint16, uint16_t),
+ DEFINE_PROP_ARRAY("secure-rx-threads", TIDmscState, num_secure_rx_threads,
+ secure_rx_threads, qdev_prop_uint16, uint16_t),
+ DEFINE_PROP_UINT64("m4-cpu-id", TIDmscState, m4_cpu_id, 0),
+ DEFINE_PROP_UINT64("a53-cpu-id-base", TIDmscState, a53_cpu_id_base, 0),
+};
+
+static void ti_dmsc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = ti_dmsc_realize;
+ rc->phases.hold = ti_dmsc_reset_hold;
+ device_class_set_props(dc, ti_dmsc_props);
+}
+
+static const TypeInfo ti_dmsc_info = {
+ .name = TYPE_TI_DMSC,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(TIDmscState),
+ .instance_init = ti_dmsc_init,
+ .instance_finalize = ti_dmsc_finalize,
+ .class_init = ti_dmsc_class_init,
+};
+
+static void ti_dmsc_types(void)
+{
+ type_register_static(&ti_dmsc_info);
+}
+
+type_init(ti_dmsc_types)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index dfa2d04117..d6fac538b4 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -456,6 +456,38 @@ iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actu
iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
iommu_testdev_dma_armed(bool armed) "armed=%d"
+# ti-dmsc.c
+dmsc_handle_set_clock(const char *type, const char *host_name, const char *device_name, int clk) "%s received from host %s, device %s, clk: %i"
+dmsc_handle_get_device(const char *type, const char *host_name, const char *device_name, uint8_t prog_state, uint8_t current_state) "%s received from host %s, device %s, response: prog_state: %i, current_state: %i"
+dmsc_handle_get_clock(const char *type, const char *host_name, const char *device_name, uint8_t clk, uint32_t clk32) "%s received from host %s, device %s, clk %u, clk32 %u"
+dmsc_handle_get_clock_parents(const char *type, const char *host_name, const char *device_name, uint8_t clk, uint32_t clk32) "%s received from host %s, device %s, clk %u, clk32 %u"
+dmsc_handle_set_clock_parent(const char *type, const char *host_name, const char *device_name, uint8_t clk_id, uint8_t parent_id) "%s received from host %s, device %s, clk_id %u, parent_id %u"
+dmsc_handle_query_freq(const char *type, const char *host_name, const char *device_name, uint8_t clk, uint32_t clk32, uint64_t target_freq) "%s received from host %s, device %s, clk %u, clk32 %u, target_frequency %"PRIu64
+dmsc_handle_get_freq(const char *type, const char *host_name, const char *device_name, uint8_t clk) "%s received from host %s, device %s, clk %u"
+dmsc_handle_proc_set_config(const char *type, const char *host_name) "%s received from host %s"
+dmsc_handle_set_device_state(const char *type, const char *host_name, const char *device_name, const char *state) "%s received from host %s, device %s, state %s"
+dmsc_handle_set_device_resets(const char *type, const char *host_name, const char *device_name, uint32_t resets) "%s received from host %s, device %s, resets 0x%08x"
+dmsc_handle_get_status(const char *type, const char *host_name, const char *proc_name, uint8_t proc_id) "%s received from host %s, proc %s (%u)"
+dmsc_handle_wait_proc_boot_status(const char *type, const char *host_name, const char *proc_name, uint8_t proc_id) "%s received from host %s, proc %s (%u)"
+dmsc_get_status_resp(const char *proc_name, uint8_t proc_id, uint32_t status_flags, int m4_running) "GET_STATUS response for %s (%u): status_flags=0x%08x, m4_running=%d"
+dmsc_unsupported_message(const char *msg, int type, const char *host, int thread_id) "No handler for message type=%s (%i) from host %s ... (thread id: %i) dropping"
+dmsc_new_message_received(int type, const char *msg, const char *host, int thread_id) "Handling message type=0x%04x (%s) from host=%s, thread=%u"
+dmsc_get_version(const char* msg, const char *host, const char *description) "Get Version received %s from host %s. Description returned %s"
+dmsc_get_fw_caps(const char* msg, const char *host) "Get FW Caps Request received %s from host %s"
+dmsc_start_proc(const char* proc_name, int proc_id, const char *host) "Requested to start Processor %s (%i) from host %s"
+dmsc_stop_proc(const char* proc_name, int proc_id, const char *host) "Requested to stop Processor %s (%i) from host %s"
+dmsc_handover_proc(const char* proc_name, int proc_id, const char *new_host, const char *host) "Requested to hand over Processor %s (%i) to host %s, from host %s"
+dmsc_a53_bootvector(uint8_t proc_id, uint64_t bootvector) "Captured boot vector for A53 proc %u: 0x%" PRIx64
+dmsc_a53_start(int core, uint64_t entry) "Cold-starting A53 core %d at entry 0x%" PRIx64 " (EL3/AArch64)"
+dmsc_a53_stop(int core) "Powering off A53 core %d"
+dmsc_handle_sys_reset(const char *type, const char *host_name) "%s received from host %s, requesting machine reset"
+dmsc_handle_fwl_set(const char *type, const char *host_name, uint16_t fwl_id, uint16_t region) "%s received from host %s, fwl_id %u, region %u"
+dmsc_handle_fwl_get(const char *type, const char *host_name, uint16_t fwl_id, uint16_t region) "%s received from host %s, fwl_id %u, region %u"
+dmsc_handle_fwl_change_owner(const char *type, const char *host_name, uint16_t fwl_id, uint16_t region, uint8_t owner_index) "%s received from host %s, fwl_id %u, region %u, owner_index %u"
+dmsc_handle_sa2ul_get_dkek(const char *type, const char *host_name, uint8_t sa2ul_instance) "%s received from host %s, sa2ul_instance %u (returning zeroed DKEK)"
+dmsc_handle_read_swrev(const char *type, const char *host_name) "%s received from host %s (returning swrev=0)"
+dmsc_handle_read_keycnt_keyrev(const char *type, const char *host_name) "%s received from host %s (returning keycnt=keyrev=0)"
+
# ti-rat.c
rat_enable_region(int idx, uint64_t size, uint64_t source, uint64_t dest) "Enabling RAT Region %u: size 0x%"PRIx64" map 0x%"PRIx64" -> 0x%"PRIx64
rat_disable_region(int idx) "Disabling RAT Region %u"
diff --git a/include/hw/misc/ti-dmsc.h b/include/hw/misc/ti-dmsc.h
new file mode 100644
index 0000000000..f918890180
--- /dev/null
+++ b/include/hw/misc/ti-dmsc.h
@@ -0,0 +1,661 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * TI DMSC emulator device (minimal TISCI service)
+ *
+ * This device links to a TI SEC_PROXY instance and provides the minimal
+ * DMSC/TISCI request/response handler.
+ */
+
+#ifndef HW_MISC_TI_DMSC_H
+#define HW_MISC_TI_DMSC_H
+
+#include "qemu/compiler.h"
+#include "hw/core/qdev.h"
+#include "hw/misc/ti-sec-proxy.h"
+
+#define TISCI_MSG_VALUE_DEVICE_SW_STATE_AUTO_OFF 0
+
+/** Used by TISCI_MSG_SET_DEVICE to disable device but keep in retention. */
+#define TISCI_MSG_VALUE_DEVICE_SW_STATE_RETENTION 1
+
+/** Used by TISCI_MSG_SET_DEVICE to turn device on for usage. */
+#define TISCI_MSG_VALUE_DEVICE_SW_STATE_ON 2
+
+/* Device is off in TISCI_MSG_GET_DEVICE response. */
+#define TISCI_MSG_VALUE_DEVICE_HW_STATE_OFF 0
+
+/* Device is on in TISCI_MSG_GET_DEVICE response. */
+#define TISCI_MSG_VALUE_DEVICE_HW_STATE_ON 1
+
+/*
+ * Device is changing state. The state may remain until dependent hardware,
+ * e.g. pending IRQ handshakes, allows to complete the transition.
+ */
+#define TISCI_MSG_VALUE_DEVICE_HW_STATE_TRANS 2
+
+/** DMSC(Secure): Device Management and Security Control */
+#define TISCI_HOST_ID_DMSC (0U)
+/** MAIN_0_R5_0(Secure): Cortex R5_0 context 0 on Main island(BOOT) */
+#define TISCI_HOST_ID_MAIN_0_R5_0 (35U)
+/** MAIN_0_R5_1(Non Secure): Cortex R5_0 context 1 on Main island */
+#define TISCI_HOST_ID_MAIN_0_R5_1 (36U)
+/** MAIN_0_R5_2(Secure): Cortex R5_0 context 2 on Main island */
+#define TISCI_HOST_ID_MAIN_0_R5_2 (37U)
+/** MAIN_0_R5_3(Non Secure): Cortex R5_0 context 3 on Main island */
+#define TISCI_HOST_ID_MAIN_0_R5_3 (38U)
+/** A53_0(Secure): Cortex a53 context 0 on Main island */
+#define TISCI_HOST_ID_A53_0 (10U)
+/** A53_1(Secure): Cortex A53 context 1 on Main island */
+#define TISCI_HOST_ID_A53_1 (11U)
+/** A53_2(Non Secure): Cortex A53 context 2 on Main island */
+#define TISCI_HOST_ID_A53_2 (12U)
+/** A53_3(Non Secure): Cortex A53 context 3 on Main island */
+#define TISCI_HOST_ID_A53_3 (13U)
+/** M4_0(Non Secure): M4 */
+#define TISCI_HOST_ID_M4_0 (30U)
+/** MAIN_1_R5_0(Secure): Cortex R5_1 context 0 on Main island */
+#define TISCI_HOST_ID_MAIN_1_R5_0 (40U)
+/** MAIN_1_R5_1(Non Secure): Cortex R5_1 context 1 on Main island */
+#define TISCI_HOST_ID_MAIN_1_R5_1 (41U)
+/** MAIN_1_R5_2(Secure): Cortex R5_1 context 2 on Main island */
+#define TISCI_HOST_ID_MAIN_1_R5_2 (42U)
+/** MAIN_1_R5_3(Non Secure): Cortex R5_1 context 3 on Main island */
+#define TISCI_HOST_ID_MAIN_1_R5_3 (43U)
+/** A53_4(Non Secure): Cortex A53 context 1 on Main island */
+#define TISCI_HOST_ID_A53_4 (14U)
+/** ICSSG_0(Non Secure): ICSSG context 0 on Main island */
+#define TISCI_HOST_ID_ICSSG_0 (50U)
+/** ICSSG_1(Non Secure): ICSSG context 1 on Main island */
+#define TISCI_HOST_ID_ICSSG_1 (51U)
+
+/* Catch-all host for board-config resource assignments. */
+#define TISCI_HOST_ID_ALL (128U)
+
+/** Number of unique hosts on the SoC */
+#define TISCI_HOST_ID_CNT (17U)
+
+#define TISCI_DEV_ADC0 0U
+#define TISCI_DEV_CMP_EVENT_INTROUTER0 1U
+#define TISCI_DEV_DBGSUSPENDROUTER0 2U
+#define TISCI_DEV_MAIN_GPIOMUX_INTROUTER0 3U
+#define TISCI_DEV_MCU_MCU_GPIOMUX_INTROUTER0 5U
+#define TISCI_DEV_TIMESYNC_EVENT_INTROUTER0 6U
+#define TISCI_DEV_MCU_M4FSS0 7U
+#define TISCI_DEV_MCU_M4FSS0_CBASS_0 8U
+#define TISCI_DEV_MCU_M4FSS0_CORE0 9U
+#define TISCI_DEV_CPSW0 13U
+#define TISCI_DEV_CPT2_AGGR0 14U
+#define TISCI_DEV_STM0 15U
+#define TISCI_DEV_DCC0 16U
+#define TISCI_DEV_DCC1 17U
+#define TISCI_DEV_DCC2 18U
+#define TISCI_DEV_DCC3 19U
+#define TISCI_DEV_DCC4 20U
+#define TISCI_DEV_DCC5 21U
+#define TISCI_DEV_DMSC0 22U
+#define TISCI_DEV_MCU_DCC0 23U
+#define TISCI_DEV_DEBUGSS_WRAP0 24U
+#define TISCI_DEV_DMASS0 25U
+#define TISCI_DEV_DMASS0_BCDMA_0 26U
+#define TISCI_DEV_DMASS0_CBASS_0 27U
+#define TISCI_DEV_DMASS0_INTAGGR_0 28U
+#define TISCI_DEV_DMASS0_IPCSS_0 29U
+#define TISCI_DEV_DMASS0_PKTDMA_0 30U
+#define TISCI_DEV_DMASS0_RINGACC_0 33U
+#define TISCI_DEV_MCU_TIMER0 35U
+#define TISCI_DEV_TIMER0 36U
+#define TISCI_DEV_TIMER1 37U
+#define TISCI_DEV_TIMER2 38U
+#define TISCI_DEV_TIMER3 39U
+#define TISCI_DEV_TIMER4 40U
+#define TISCI_DEV_TIMER5 41U
+#define TISCI_DEV_TIMER6 42U
+#define TISCI_DEV_TIMER7 43U
+#define TISCI_DEV_TIMER8 44U
+#define TISCI_DEV_TIMER9 45U
+#define TISCI_DEV_TIMER10 46U
+#define TISCI_DEV_TIMER11 47U
+#define TISCI_DEV_MCU_TIMER1 48U
+#define TISCI_DEV_MCU_TIMER2 49U
+#define TISCI_DEV_MCU_TIMER3 50U
+#define TISCI_DEV_ECAP0 51U
+#define TISCI_DEV_ECAP1 52U
+#define TISCI_DEV_ECAP2 53U
+#define TISCI_DEV_ELM0 54U
+#define TISCI_DEV_EMIF_DATA_0_VD 55U
+#define TISCI_DEV_MMCSD0 57U
+#define TISCI_DEV_MMCSD1 58U
+#define TISCI_DEV_EQEP0 59U
+#define TISCI_DEV_EQEP1 60U
+#define TISCI_DEV_GTC0 61U
+#define TISCI_DEV_EQEP2 62U
+#define TISCI_DEV_ESM0 63U
+#define TISCI_DEV_MCU_ESM0 64U
+#define TISCI_DEV_FSIRX0 65U
+#define TISCI_DEV_FSIRX1 66U
+#define TISCI_DEV_FSIRX2 67U
+#define TISCI_DEV_FSIRX3 68U
+#define TISCI_DEV_FSIRX4 69U
+#define TISCI_DEV_FSIRX5 70U
+#define TISCI_DEV_FSITX0 71U
+#define TISCI_DEV_FSITX1 72U
+#define TISCI_DEV_FSS0 73U
+#define TISCI_DEV_FSS0_FSAS_0 74U
+#define TISCI_DEV_FSS0_OSPI_0 75U
+#define TISCI_DEV_GICSS0 76U
+#define TISCI_DEV_GPIO0 77U
+#define TISCI_DEV_GPIO1 78U
+#define TISCI_DEV_MCU_GPIO0 79U
+#define TISCI_DEV_GPMC0 80U
+#define TISCI_DEV_PRU_ICSSG0 81U
+#define TISCI_DEV_PRU_ICSSG1 82U
+#define TISCI_DEV_LED0 83U
+#define TISCI_DEV_CPTS0 84U
+#define TISCI_DEV_DDPA0 85U
+#define TISCI_DEV_EPWM0 86U
+#define TISCI_DEV_EPWM1 87U
+#define TISCI_DEV_EPWM2 88U
+#define TISCI_DEV_EPWM3 89U
+#define TISCI_DEV_EPWM4 90U
+#define TISCI_DEV_EPWM5 91U
+#define TISCI_DEV_EPWM6 92U
+#define TISCI_DEV_EPWM7 93U
+#define TISCI_DEV_EPWM8 94U
+#define TISCI_DEV_VTM0 95U
+#define TISCI_DEV_MAILBOX0 96U
+#define TISCI_DEV_MAIN2MCU_VD 97U
+#define TISCI_DEV_MCAN0 98U
+#define TISCI_DEV_MCAN1 99U
+#define TISCI_DEV_MCU_MCRC64_0 100U
+#define TISCI_DEV_MCU2MAIN_VD 101U
+#define TISCI_DEV_I2C0 102U
+#define TISCI_DEV_I2C1 103U
+#define TISCI_DEV_I2C2 104U
+#define TISCI_DEV_I2C3 105U
+#define TISCI_DEV_MCU_I2C0 106U
+#define TISCI_DEV_MCU_I2C1 107U
+#define TISCI_DEV_PCIE0 114U
+#define TISCI_DEV_R5FSS0 119U
+#define TISCI_DEV_R5FSS1 120U
+#define TISCI_DEV_R5FSS0_CORE0 121U
+#define TISCI_DEV_R5FSS0_CORE1 122U
+#define TISCI_DEV_R5FSS1_CORE0 123U
+#define TISCI_DEV_R5FSS1_CORE1 124U
+#define TISCI_DEV_RTI0 125U
+#define TISCI_DEV_RTI1 126U
+#define TISCI_DEV_RTI8 127U
+#define TISCI_DEV_RTI9 128U
+#define TISCI_DEV_RTI10 130U
+#define TISCI_DEV_RTI11 131U
+#define TISCI_DEV_MCU_RTI0 132U
+#define TISCI_DEV_SA2_UL0 133U
+#define TISCI_DEV_COMPUTE_CLUSTER0 134U
+#define TISCI_DEV_A53SS0_CORE_0 135U
+#define TISCI_DEV_A53SS0_CORE_1 136U
+#define TISCI_DEV_A53SS0 137U
+#define TISCI_DEV_DDR16SS0 138U
+#define TISCI_DEV_PSC0 139U
+#define TISCI_DEV_MCU_PSC0 140U
+#define TISCI_DEV_MCSPI0 141U
+#define TISCI_DEV_MCSPI1 142U
+#define TISCI_DEV_MCSPI2 143U
+#define TISCI_DEV_MCSPI3 144U
+#define TISCI_DEV_MCSPI4 145U
+#define TISCI_DEV_UART0 146U
+#define TISCI_DEV_MCU_MCSPI0 147U
+#define TISCI_DEV_MCU_MCSPI1 148U
+#define TISCI_DEV_MCU_UART0 149U
+#define TISCI_DEV_SPINLOCK0 150U
+#define TISCI_DEV_TIMERMGR0 151U
+#define TISCI_DEV_UART1 152U
+#define TISCI_DEV_UART2 153U
+#define TISCI_DEV_UART3 154U
+#define TISCI_DEV_UART4 155U
+#define TISCI_DEV_UART5 156U
+#define TISCI_DEV_BOARD0 157U
+#define TISCI_DEV_UART6 158U
+#define TISCI_DEV_MCU_UART1 160U
+#define TISCI_DEV_USB0 161U
+#define TISCI_DEV_SERDES_10G0 162U
+#define TISCI_DEV_PBIST0 163U
+#define TISCI_DEV_PBIST1 164U
+#define TISCI_DEV_PBIST2 165U
+#define TISCI_DEV_PBIST3 166U
+#define TISCI_DEV_COMPUTE_CLUSTER0_PBIST_0 167U
+#define TISCI_DEV_ID_MAX 168U
+
+#define TISCI_MSG_FLAG_RESERVED0 BIT(0)
+/*
+ * ACK-on-processed: request a response after handling, ACK on success and
+ * NAK otherwise.
+ */
+#define TISCI_MSG_FLAG_AOP BIT(1)
+
+/** Indicate that this message is marked secure */
+#define TISCI_MSG_FLAG_SEC BIT(2)
+
+/* Response success flag; missing one means NAK. */
+#define TISCI_MSG_FLAG_ACK BIT(1)
+
+/* TISCI Message IDs */
+#define TISCI_MSG_VERSION (0x0002U)
+#define TISCI_MSG_BOOT_NOTIFICATION (0x000AU)
+#define TISCI_MSG_BOARD_CONFIG (0x000BU)
+#define TISCI_MSG_BOARD_CONFIG_RM (0x000CU)
+#define TISCI_MSG_BOARD_CONFIG_SECURITY (0x000DU)
+#define TISCI_MSG_BOARD_CONFIG_PM (0x000EU)
+
+#define TISCI_MSG_ENABLE_WDT (0x0000U)
+#define TISCI_MSG_WAKE_RESET (0x0001U)
+#define TISCI_MSG_WAKE_REASON (0x0003U)
+#define TISCI_MSG_GOODBYE (0x0004U)
+#define TISCI_MSG_SYS_RESET (0x0005U)
+
+#define TISCI_MSG_QUERY_MSMC (0x0020U)
+#define TISCI_MSG_GET_TRACE_CONFIG (0x0021U)
+#define TISCI_MSG_QUERY_FW_CAPS (0x0022U)
+
+#define TISCI_MSG_SET_CLOCK (0x0100U)
+#define TISCI_MSG_GET_CLOCK (0x0101U)
+#define TISCI_MSG_SET_CLOCK_PARENT (0x0102U)
+#define TISCI_MSG_GET_CLOCK_PARENT (0x0103U)
+#define TISCI_MSG_GET_NUM_CLOCK_PARENTS (0x0104U)
+#define TISCI_MSG_SET_FREQ (0x010cU)
+#define TISCI_MSG_QUERY_FREQ (0x010dU)
+#define TISCI_MSG_GET_FREQ (0x010eU)
+
+#define TISCI_MSG_SET_DEVICE (0x0200U)
+#define TISCI_MSG_GET_DEVICE (0x0201U)
+
+#define TISCI_MSG_SET_DEVICE_RESETS (0x0202U)
+#define TISCI_MSG_DEVICE_DROP_POWERUP_REF (0x0203U)
+
+#define TISCI_MSG_PREPARE_SLEEP (0x0300U)
+#define TISCI_MSG_ENTER_SLEEP (0x0301U)
+
+#define TISCI_MSG_PROC_REQUEST (0xc000U)
+#define TISCI_MSG_PROC_RELEASE (0xc001U)
+#define TISCI_MSG_PROC_HANDOVER (0xc005U)
+#define TISCI_MSG_SET_CONFIG (0xc100U)
+#define TISCI_MSG_SET_CTRL (0xc101U)
+#define TISCI_MSG_GET_STATUS (0xc400U)
+#define TISCI_MSG_WAIT_PROC_BOOT_STATUS (0xc401U)
+
+/*
+ * Security message IDs for K3 SA2UL, OTP and secure-boot TI-SCI services.
+ * Layouts match the U-Boot/Zephyr TI-SCI protocol headers.
+ */
+#define TISCI_MSG_FWL_SET (0x9000U)
+#define TISCI_MSG_FWL_GET (0x9001U)
+#define TISCI_MSG_FWL_CHANGE_OWNER (0x9002U)
+#define TISCI_MSG_SA2UL_GET_DKEK (0x9029U)
+#define TISCI_MSG_READ_SWREV (0x9033U)
+#define TISCI_MSG_READ_KEYCNT_KEYREV (0x9034U)
+
+#define TISCI_MSG_MAX_ID (0xc500U)
+
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 9 Processor 0) */
+#define SCICLIENT_PROCID_A53_CL0_C0 (0x20U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 9 Processor 1) */
+#define SCICLIENT_PROCID_A53_CL0_C1 (0x21U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 0 Processor 0) */
+#define SCICLIENT_PROCID_R5_CL0_C0 (0x01U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 0 Processor 1) */
+#define SCICLIENT_PROCID_R5_CL0_C1 (0x02U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 1 Processor 0) */
+#define SCICLIENT_PROCID_R5_CL1_C0 (0x06U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 1 Processor 1) */
+#define SCICLIENT_PROCID_R5_CL1_C1 (0x07U)
+/*** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 16 Processor 0) */
+#define SCICLIENT_PROCID_MCU_M4FSS0_C0 (0x18U)
+
+#define TYPE_TI_DMSC "ti-dmsc"
+
+OBJECT_DECLARE_SIMPLE_TYPE(TIDmscState, TI_DMSC)
+
+/* Default: 64 bytes -> 16 words */
+#define TI_DMSC_MAX_WORDS 16
+
+/*
+ * Minimal TISCI wire structs. Keep them packed and model only fields this
+ * device actually consumes or returns.
+ */
+typedef struct TISciMsgHdr {
+ uint16_t type;
+ uint8_t host;
+ uint8_t seq;
+ uint32_t flags;
+} QEMU_PACKED TISciMsgHdr;
+
+struct TiSciMsgReqProcRequest {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+} QEMU_PACKED;
+
+struct TiSciMsgReqProcRelease {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+} QEMU_PACKED;
+
+/*
+ * TISCI_MSG_PROC_HANDOVER request. Response is only TISciMsgHdr ACK/NAK.
+ */
+struct TiSciMsgReqProcHandover {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+ uint8_t host_id;
+} QEMU_PACKED;
+
+#define TISCI_MSG_VAL_PROC_BOOT_STATUS_FLAG_M4F_WFI (0x00000002U)
+
+struct TisciMsgProcGetStatusReq {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+} QEMU_PACKED;
+
+struct TisciMsgProcGetStatusResp {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+ uint32_t bootvector_lo;
+ uint32_t bootvector_hi;
+ uint32_t config_flags_1;
+ uint32_t control_flags_1;
+ uint32_t status_flags_1;
+} QEMU_PACKED;
+
+/*
+ * WAIT_PROC_BOOT_STATUS request. Only processor_id is consumed by the no-op
+ * handler, so the trailing wait/status fields are left out.
+ */
+struct TisciMsgReqWaitProcBootStatus {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+} QEMU_PACKED;
+
+/*
+ * SET_DEVICE matches the TISCI ABI layout: the reserved u32 before state is
+ * on the wire and keeps the state byte aligned to SYSFW.
+ */
+struct TisciMsgSetDeviceReq {
+ TISciMsgHdr hdr;
+ uint32_t id;
+ uint32_t reserved;
+ uint8_t state;
+} QEMU_PACKED;
+
+struct TisciMsgSetDeviceResetsReq {
+ TISciMsgHdr hdr;
+ uint32_t id;
+ uint32_t resets;
+} QEMU_PACKED;
+
+struct TiSciMsgQueryFwCapsResp {
+ TISciMsgHdr hdr;
+#define MSG_FLAG_CAPS_GENERIC BIT(0)
+#define MSG_FLAG_CAPS_LPM_DEEP_SLEEP BIT(1)
+#define MSG_FLAG_CAPS_LPM_MCU_ONLY BIT(2)
+#define MSG_FLAG_CAPS_LPM_STANDBY BIT(3)
+#define MSG_FLAG_CAPS_LPM_PARTIAL_IO BIT(4)
+#define MSG_FLAG_CAPS_LPM_DM_MANAGED BIT(5)
+ uint64_t fw_caps;
+} QEMU_PACKED;
+
+struct TiSciMsgVersionResp {
+ TISciMsgHdr hdr;
+ char firmware_description[32];
+ uint16_t firmware_revision;
+ uint8_t abi_major;
+ uint8_t abi_minor;
+} QEMU_PACKED;
+
+struct TisciMsgSetFreqReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint64_t min_freq_hz;
+ uint64_t target_freq_hz;
+ uint64_t max_freq_hz;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgQueryFreqReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint64_t min_freq_hz;
+ uint64_t target_freq_hz;
+ uint64_t max_freq_hz;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgQueryFreqResp {
+ TISciMsgHdr hdr;
+ uint64_t freq_hz;
+} QEMU_PACKED;
+
+/*
+ * GET_FREQ has only device/clock in the request. The response is hdr plus
+ * freq_hz, same payload as QUERY_FREQ.
+ */
+struct TisciMsgGetFreqReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+} QEMU_PACKED;
+
+struct TisciMsgSetClockReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+ uint8_t state;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgGetNumClockParentsReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgGetNumClockParentsResp {
+ TISciMsgHdr hdr;
+ uint8_t num_parents;
+ uint32_t num_parentint32_t;
+} QEMU_PACKED;
+
+struct TisciMsgGetClockParentReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgGetClockParentResp {
+ TISciMsgHdr hdr;
+ uint8_t parent;
+ uint32_t parent32;
+} QEMU_PACKED;
+
+/*
+ * TISCI_MSG_SET_CLOCK_PARENT request. Response is bare TISciMsgHdr ACK/NAK.
+ */
+struct TisciMsgSetClockParentReq {
+ TISciMsgHdr hdr;
+ uint32_t dev_id;
+ uint8_t clk_id;
+ uint8_t parent_id;
+} QEMU_PACKED;
+
+struct TisciMsgGetClockReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgGetClockResp {
+ TISciMsgHdr hdr;
+ uint8_t programmed_state;
+ uint8_t current_state;
+} QEMU_PACKED;
+
+struct TisciMsgGetDeviceReq {
+ TISciMsgHdr hdr;
+ uint32_t id;
+} QEMU_PACKED;
+
+struct TisciMsgGetDeviceResp {
+ TISciMsgHdr hdr;
+ uint32_t context_loss_count;
+ uint32_t resets;
+ uint8_t programmed_state;
+ uint8_t current_state;
+} QEMU_PACKED;
+
+/*
+ * Security message layouts for K3 SA2UL, OTP and secure-boot services. They
+ * match the U-Boot and Zephyr TI-SCI protocol headers.
+ */
+#define FWL_MAX_PRIVID_SLOTS 3U
+
+struct TisciMsgReqFwlSetFirewallRegion {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint32_t n_permission_regs;
+ uint32_t control;
+ uint32_t permissions[FWL_MAX_PRIVID_SLOTS];
+ uint64_t start_address;
+ uint64_t end_address;
+} QEMU_PACKED;
+
+/* TISCI_MSG_FWL_SET response is bare generic ACK/NACK (TISciMsgHdr). */
+
+struct TisciMsgReqFwlGetFirewallRegion {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint32_t n_permission_regs;
+} QEMU_PACKED;
+
+struct TisciMsgRespFwlGetFirewallRegion {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint32_t n_permission_regs;
+ uint32_t control;
+ uint32_t permissions[FWL_MAX_PRIVID_SLOTS];
+ uint64_t start_address;
+ uint64_t end_address;
+} QEMU_PACKED;
+
+struct TisciMsgReqFwlChangeOwnerInfo {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint8_t owner_index;
+} QEMU_PACKED;
+
+struct TisciMsgRespFwlChangeOwnerInfo {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint8_t owner_index;
+ uint8_t owner_privid;
+ uint16_t owner_permission_bits;
+} QEMU_PACKED;
+
+#define SA2UL_DKEK_KEY_LEN 32
+#define KDF_LABEL_AND_CONTEXT_LEN_MAX 41
+
+struct TisciMsgReqSa2ulGetDkek {
+ TISciMsgHdr hdr;
+ uint8_t sa2ul_instance;
+ uint8_t kdf_label_len;
+ uint8_t kdf_context_len;
+ uint8_t kdf_label_and_context[KDF_LABEL_AND_CONTEXT_LEN_MAX];
+} QEMU_PACKED;
+
+struct TisciMsgRespSa2ulGetDkek {
+ TISciMsgHdr hdr;
+ uint8_t dkek[SA2UL_DKEK_KEY_LEN];
+} QEMU_PACKED;
+
+struct TisciMsgRespReadSwrev {
+ TISciMsgHdr hdr;
+ uint32_t swrev;
+} QEMU_PACKED;
+
+struct TisciMsgRespReadKeycntKeyrev {
+ TISciMsgHdr hdr;
+ uint32_t keycnt;
+ uint32_t keyrev;
+} QEMU_PACKED;
+
+typedef struct TIDmscClient TIDmscClient;
+
+typedef void (*TiDmscMsgHandler)(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords);
+
+struct TIDmscClient {
+ TIDmscState *dmsc;
+ uint16_t rx_thread_id;
+ uint16_t tx_thread_id;
+ bool pending;
+ /*
+ * Secure R5 clients add a 4-byte checksum/reserved word before the
+ * normal TISciMsgHdr on requests and responses.
+ */
+ bool secure;
+ uint32_t pending_words[TI_DMSC_MAX_WORDS];
+ size_t pending_nwords;
+
+ /*
+ * Set from the actual request's AOP bit before dispatch. The bottom
+ * half handles one message at a time, so no extra locking is needed.
+ */
+ bool cur_req_wants_resp;
+};
+
+struct TIDmscState {
+ DeviceState parent_obj;
+
+ /* QOM link to SEC_PROXY */
+ TISecProxyState *sec_proxy;
+
+ /* Config */
+ uint16_t rx_thread_id; /* e.g. M4_0_WRITE_THREAD */
+ uint16_t tx_thread_id; /* e.g. M4_0_READ_RESPONSE_THREAD */
+ uint32_t num_rx_threads;
+ uint16_t *rx_thread_ids;
+ uint32_t num_tx_threads;
+ uint16_t *tx_thread_ids;
+ /* rx threads of clients, which use secure R5 transport framing */
+ uint32_t num_secure_rx_threads;
+ uint16_t *secure_rx_threads;
+ uint64_t m4_cpu_id; /* QEMU CPU index used for MCU M4 */
+ uint64_t a53_cpu_id_base; /* MP affinity of A53 core 0 (core 1 = +1) */
+
+ uint32_t msg_words; /* usually 16 */
+
+ /* Optional async handling */
+ QEMUBH *bh;
+ QemuMutex lock;
+
+ TiDmscMsgHandler msg_handler[TISCI_MSG_MAX_ID];
+ uint32_t num_clients;
+ TIDmscClient *clients;
+
+ uint8_t dev_hw_state[TISCI_DEV_ID_MAX];
+ uint8_t dev_prog_state[TISCI_DEV_ID_MAX];
+ bool m4_running;
+ /* A53 boot vectors captured from TISCI_MSG_SET_CONFIG. */
+ uint64_t proc_bootvector[2];
+};
+
+#endif /* HW_MISC_TI_DMSC_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 11/14] hw/arm: add TI K3 combined boot image parser
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (9 preceding siblings ...)
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
2026-08-20 12:48 ` [RFC PATCH v2 12/14] hw/arm: add TI AM64x SoC model Wadim Mueller
` (2 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
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
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 12/14] hw/arm: add TI AM64x SoC model
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (10 preceding siblings ...)
2026-08-20 12:48 ` [RFC PATCH v2 11/14] hw/arm: add TI K3 combined boot image parser Wadim Mueller
@ 2026-08-20 12:48 ` 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
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
Add a model of the TI AM64x (AM6442) Sitara SoC tying the previous patches
together:
- Cortex-A53 cluster with a GICv3 (GIC-500) at the real addresses
- Cortex-R5F MCU boot core and a Cortex-M4F, each behind their own RAT
- MCU and MAIN domain UARTs, mailboxes, secure proxy instances and the
DMSC endpoint
- CTRL_MMR, GTC, DDRSS, DMTimer, TRNG, SDHCI controllers with the K3 PHY
wrapper and the MAIN domain I2C0
- OCSRAM and DDR windows
Also add the ROM boot loader, which parses a combined boot image, places
the components and starts the R5F boot core at the certified entry point,
mirroring what the on-chip boot ROM does.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/arm/Kconfig | 16 +
hw/arm/k3-bootrom.c | 125 ++++
hw/arm/meson.build | 2 +
hw/arm/ti-am64x.c | 1223 +++++++++++++++++++++++++++++++++++++
hw/arm/trace-events | 4 +
include/hw/arm/ti-am64x.h | 86 +++
6 files changed, 1456 insertions(+)
create mode 100644 hw/arm/k3-bootrom.c
create mode 100644 hw/arm/ti-am64x.c
create mode 100644 include/hw/arm/ti-am64x.h
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index b2dc2714c0..2172fc803b 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -415,6 +415,22 @@ config STM32L4X5_SOC
select STM32L4X5_GPIO
select STM32L4X5_USART
+config TI_AM64X
+ bool
+ select ARM_V7M
+ select ARM_GIC
+ select OMAP_I2C
+ select TI_MAILBOX
+ select TI_RAT
+ select TI_K3_CTRLMMR
+ select TI_K3_GTC
+ select TI_K3_DDRSS
+ select TI_K3_DMTIMER
+ select TI_K3_SDHCI_PHY
+ select TI_K3_TRNG
+ select SDHCI
+ select AM64_UART
+
config XLNX_ZYNQMP_ARM
bool
default y if PIXMAN
diff --git a/hw/arm/k3-bootrom.c b/hw/arm/k3-bootrom.c
new file mode 100644
index 0000000000..172dbe99a3
--- /dev/null
+++ b/hw/arm/k3-bootrom.c
@@ -0,0 +1,125 @@
+/*
+ * TI K3 boot-ROM (RBL) emulation for the AM64x
+ *
+ * Loads a TI combined boot image, places the R5 SPL in OCSRAM, and
+ * starts R5F0_0 at the ROM-certified entry point. Since ti-dmsc emulates
+ * TIFS, SYSFW payloads are ignored.
+ *
+ * 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 "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/core/loader.h"
+#include "hw/core/cpu.h"
+#include "system/address-spaces.h"
+#include "system/reset.h"
+#include "hw/arm/ti-am64x.h"
+#include "hw/arm/k3-bootrom.h"
+#include "trace.h"
+
+#define K3_OCSRAM_BASE 0x70000000ULL
+#define K3_OCSRAM_SIZE (2 * MiB)
+/* u-boot: CONFIG_SYS_K3_BOOT_PARAM_TABLE_INDEX (AM642) */
+#define K3_BOOT_PARAM_TABLE_INDEX 0x701bebfcULL
+#define K3_PRIMARY_BOOTMODE 0x0
+/* u-boot: ROM_EXTENDED_BOOT_DATA_INFO (AM64) */
+#define K3_ROM_EXTENDED_BOOT_DATA 0x701beb00ULL
+
+typedef struct K3BootRomReset {
+ ARMCPU *cpu;
+ uint64_t entry;
+ TIAM64xState *soc;
+} K3BootRomReset;
+
+static void k3_bootrom_cpu_reset(void *opaque)
+{
+ K3BootRomReset *r = opaque;
+ CPUState *cs = CPU(r->cpu);
+
+ /*
+ * On warm reset only the R5 boot core runs from ROM. Reset A53s back to
+ * PSCI_OFF, matching the SoC reset state.
+ */
+ if (r->soc) {
+ for (unsigned i = 0; i < r->soc->a53_cpus; i++) {
+ cpu_reset(CPU(&r->soc->a53[i]));
+ }
+
+ /*
+ * ROM boot does not install armv7m_load_kernel's reset hook.
+ * Therefore reset the M4 too, back to its powered-off reset state.
+ */
+ if (r->soc->armv7m.cpu) {
+ cpu_reset(CPU(r->soc->armv7m.cpu));
+ }
+ }
+
+ cpu_reset(cs);
+ cpu_set_pc(cs, r->entry);
+}
+
+void k3_bootrom_load(TIAM64xState *soc, const char *filename, Error **errp)
+{
+ g_autofree uint8_t *buf = NULL;
+ gsize len;
+ GError *gerr = NULL;
+ K3BootImage img;
+ const K3BootComponent *sbl = NULL;
+ K3BootRomReset *r;
+ struct {
+ char magic[8];
+ uint32_t num_components;
+ } QEMU_PACKED extboot = { "EXTBOOT", 0 };
+ uint32_t bootindex = cpu_to_le32(K3_PRIMARY_BOOTMODE);
+
+ if (!g_file_get_contents(filename, (char **)&buf, &len, &gerr)) {
+ error_setg(errp, "k3-bootrom: cannot read '%s': %s", filename,
+ gerr->message);
+ g_error_free(gerr);
+ return;
+ }
+ if (!k3_bootrom_parse(buf, len, &img, errp)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < img.num_comps; i++) {
+ const K3BootComponent *c = &img.comps[i];
+
+ trace_k3_bootrom_component(c->comp_type, c->dest_addr,
+ c->comp_size);
+ if (c->comp_type == K3_COMP_TYPE_SBL && !sbl) {
+ sbl = c;
+ }
+ /* SYSFW/SYSFW-DATA are DMSC payloads; TIFS is in ti-dmsc. */
+ }
+ if (!sbl) {
+ error_setg(errp, "k3-bootrom: image has no SBL component");
+ return;
+ }
+ if (sbl->dest_addr < K3_OCSRAM_BASE ||
+ sbl->dest_addr + sbl->comp_size > K3_OCSRAM_BASE + K3_OCSRAM_SIZE) {
+ error_setg(errp, "k3-bootrom: SBL 0x%" PRIx64 "+0x%x outside "
+ "OCSRAM", sbl->dest_addr, sbl->comp_size);
+ return;
+ }
+
+ rom_add_blob_fixed_as("k3.sbl", buf + sbl->payload_offset,
+ sbl->comp_size, sbl->dest_addr,
+ &address_space_memory);
+ rom_add_blob_fixed_as("k3.bootindex", &bootindex, sizeof(bootindex),
+ K3_BOOT_PARAM_TABLE_INDEX, &address_space_memory);
+ extboot.num_components = cpu_to_le32(img.num_comps);
+ rom_add_blob_fixed_as("k3.extboot", &extboot, sizeof(extboot),
+ K3_ROM_EXTENDED_BOOT_DATA, &address_space_memory);
+
+ r = g_new0(K3BootRomReset, 1);
+ r->cpu = &soc->r5[0];
+ r->entry = sbl->dest_addr;
+ r->soc = soc;
+ qemu_register_reset(k3_bootrom_cpu_reset, r);
+ trace_k3_bootrom_boot(sbl->dest_addr);
+}
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 22691afdd8..139692dd11 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -109,7 +109,9 @@ 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('ti-am64x.c'))
arm_common_ss.add(when: 'CONFIG_TI_AM64X', if_true: files('k3-bootrom-parse.c'))
+arm_common_ss.add(when: 'CONFIG_TI_AM64X', if_true: files('k3-bootrom.c'))
arm_common_ss.add(when: ['CONFIG_AXIADO_SOC', 'TARGET_AARCH64'], if_true: files(
'ax3000-soc.c'))
diff --git a/hw/arm/ti-am64x.c b/hw/arm/ti-am64x.c
new file mode 100644
index 0000000000..551e743476
--- /dev/null
+++ b/hw/arm/ti-am64x.c
@@ -0,0 +1,1223 @@
+/*
+ * TI AM64x SoC family model
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "hw/core/boards.h"
+#include "hw/arm/ti-am64x.h"
+#include "hw/misc/unimp.h"
+#include "hw/core/or-irq.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/arm/bsa.h"
+#include "qapi/error.h"
+#include "hw/char/ti-am64-uart.h"
+#include "hw/misc/ti-sec-proxy.h"
+#include "hw/intc/arm_gicv3.h"
+#include "hw/arm/omap.h"
+
+#include "qobject/qlist.h"
+#include "qemu/units.h"
+#include "system/address-spaces.h"
+#include "system/system.h"
+
+/* MCU MMIO */
+#define MCU_RAT_MMIO_ADDRESS 0x44200000
+
+/* MAIN MMIO */
+#define MAIN_SEC_PROXY_MMRS_ADDRESS 0x48250000
+#define MAIN_SEC_PROXY_SCFG_ADDRESS 0x4A400000
+#define MAIN_SEC_PROXY_RT_ADDRESS 0x4A600000
+#define MAIN_SEC_PROXY_TARGET_ADDRESS 0x4D000000
+#define MAIN_MAILBOX_BASE_ADDRESS 0x029000000ULL
+#define MAIN_MAILBOX_STRIDE 0x00010000ULL
+#define MAIN_RAM_BASE_ADDRESS 0x80000000ULL
+/* AM64 TRM: GICSS0_GIC at 0x001800000 (1 MiB). */
+#define MAIN_GIC_DIST_ADDRESS 0x01800000ULL
+#define MAIN_GIC_REDIST_ADDRESS 0x01840000ULL /* GIC-500 GICR */
+
+#define MCU_IRAM_SIZE (192 * 1024)
+#define MCU_IRAM_BASE_ADDRESS 0x00000000
+
+#define MCU_DRAM_SIZE (64 * 1024)
+#define MCU_DRAM_BASE_ADDRESS 0x00030000
+
+#define MCU_DDR_SIZE (512 * 1024 * 1024)
+#define MCU_DDR_BASE_ADDRESS 0xa4100000
+
+#define MCU_RAT_SIZE (2 * 1024 * 1024)
+#define MCU_RAT_BASE_ADDRESS 0x60000000
+
+static void ti_am64x_initfn(Object *obj)
+{
+ TIAM64xState *s = TI_AM64X(obj);
+
+ object_initialize_child(obj, "a53-cluster", &s->a53_cluster,
+ TYPE_CPU_CLUSTER);
+ qdev_prop_set_uint32(DEVICE(&s->a53_cluster), "cluster-id", 0);
+
+ object_initialize_child(obj, "m4-cluster", &s->m4_cluster,
+ TYPE_CPU_CLUSTER);
+ qdev_prop_set_uint32(DEVICE(&s->m4_cluster), "cluster-id", 1);
+
+ object_initialize_child(OBJECT(&s->m4_cluster), "armv7m", &s->armv7m,
+ TYPE_ARMV7M);
+
+ object_initialize_child(obj, "r5-cluster", &s->r5_cluster,
+ TYPE_CPU_CLUSTER);
+ qdev_prop_set_uint32(DEVICE(&s->r5_cluster), "cluster-id", 2);
+ object_initialize_child(OBJECT(&s->r5_cluster), "r5-cpu[*]", &s->r5[0],
+ ARM_CPU_TYPE_NAME("cortex-r5f"));
+
+ object_initialize_child(obj, "gic", &s->gic, TYPE_ARM_GICV3);
+ object_initialize_child(obj, "rat", &s->rat, TYPE_TI_RAT);
+ object_initialize_child(obj, "sec-proxy", &s->sec_proxy, TYPE_TI_SEC_PROXY);
+ object_initialize_child(obj, "dmsc", &s->dmsc, TYPE_TI_DMSC);
+ object_initialize_child(obj, "ctrlmmr", &s->ctrlmmr, TYPE_TI_K3_CTRLMMR);
+ object_initialize_child(obj, "mcu-ctrlmmr", &s->mcu_ctrlmmr,
+ TYPE_TI_K3_CTRLMMR);
+ object_initialize_child(obj, "sec-ctrlmmr", &s->sec_ctrlmmr,
+ TYPE_TI_K3_CTRLMMR);
+ object_initialize_child(obj, "ddrss", &s->ddrss, TYPE_TI_K3_DDRSS);
+ object_initialize_child(obj, "trng", &s->trng, TYPE_TI_K3_TRNG);
+ object_initialize_child(obj, "main-timer0", &s->main_timer0,
+ TYPE_TI_K3_DMTIMER);
+ object_initialize_child(obj, "gtc", &s->gtc, TYPE_TI_K3_GTC);
+
+ for (int i = 0; i < TI_AM64X_SDHCI_NUM; i++) {
+ object_initialize_child(obj, "sdhci[*]", &s->sdhci[i],
+ TYPE_SYSBUS_SDHCI);
+ object_initialize_child(obj, "sdhci-phy[*]", &s->sdhci_phy[i],
+ TYPE_TI_K3_SDHCI_PHY);
+ }
+
+ for (int i = 0; i < TI_AM64X_A53_NUM; i++) {
+ object_initialize_child(OBJECT(&s->a53_cluster), "a53[*]", &s->a53[i],
+ ARM_CPU_TYPE_NAME("cortex-a53"));
+ }
+
+ for (int i = 0; i < TI_AM64X_MAILBOX_NUM; i++) {
+ object_initialize_child(obj, "mailbox[*]", &s->mailbox[i],
+ TYPE_TI_MAILBOX);
+ }
+
+ for (int i = 0; i < TI_AM64X_MCU_UART_NUM; i++) {
+ object_initialize_child(obj, "mcu-uart[*]", &s->mcu_uart[i],
+ TYPE_AM64_UART);
+ }
+
+ object_initialize_child(obj, "main-uart0", &s->main_uart0, TYPE_AM64_UART);
+
+ s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
+ s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
+ s->main_ram_base = MAIN_RAM_BASE_ADDRESS;
+ s->main_ram_size = 0;
+ s->a53_cpus = TI_AM64X_A53_NUM;
+}
+
+static void create_unimplemented_device_in_root(MemoryRegion *root,
+ const char *name, hwaddr base,
+ hwaddr size)
+{
+ DeviceState *dev = qdev_new(TYPE_UNIMPLEMENTED_DEVICE);
+
+ qdev_prop_set_string(dev, "name", name);
+ qdev_prop_set_uint64(dev, "size", size);
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ /* Allows to map the unimplemented MMIO window into this root. */
+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
+ memory_region_add_subregion(root, base, mr);
+}
+
+static void ti_am64_create_main_unimplemented(MemoryRegion *root)
+{
+/* === MAIN MMIO / register blocks (<= 256 KiB) === */
+#define ADD_MAIN_UNIMP(_name, _base, _size) \
+ create_unimplemented_device_in_root(root, (_name), (hwaddr)(_base), \
+ (hwaddr)(_size))
+
+ /* 0x0000_xxxx */
+ ADD_MAIN_UNIMP("PSRAMECC0_RAM", 0x000000000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("PADCFG_CTRL0_CFG0", 0x0000F0000ULL, 0x00008000ULL);
+ ADD_MAIN_UNIMP("PADCFG_CTRL0_CFG1", 0x0000F8000ULL, 0x00008000ULL);
+ ADD_MAIN_UNIMP("CBASS_DBG0_ERR", 0x000200000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("CBASS_INFRA1_ERR", 0x000210000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("CBASS_FW0_ERR", 0x000220000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("EFUSE0", 0x000300000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PBIST0", 0x000310000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("COMPUTE_CLUSTER0_PBIST", 0x000330000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("PSC0", 0x000400000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PLLCTRL0", 0x000410000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("ESM0_CFG", 0x000420000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DFTSS0", 0x000500000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("DDPA0", 0x000580000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("GPIO0", 0x000600000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("GPIO1", 0x000601000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PLL0_CFG", 0x000680000ULL, 0x00010000ULL);
+
+ /* ECC aggregators */
+ ADD_MAIN_UNIMP("PSRAMECC0_ECC_AGGR0", 0x000700000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("ECC_AGGR1_ECC_AGGR0", 0x000701000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("USB0_ECC_AGGR0", 0x000703000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("CPSW0_ECC0", 0x000704000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MMCSD0_ECC_AGGR_RXMEM", 0x000706000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MMCSD0_ECC_AGGR_TXMEM", 0x000707000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MMCSD1_ECC_AGGR_RXMEM", 0x000708000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MMCSD1_ECC_AGGR_TXMEM", 0x000709000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("SA2_UL0_ECC_AGGR0", 0x000712000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("FSS0_OSPI0_ECC_AGGR0", 0x000716000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("COMPUTE_CLUSTER0_SS_ECC_AGGR", 0x000717000ULL,
+ 0x00000400ULL);
+ ADD_MAIN_UNIMP("COMPUTE_CLUSTER0_CORE0_ECC_AGGR", 0x000717400ULL,
+ 0x00000400ULL);
+ ADD_MAIN_UNIMP("COMPUTE_CLUSTER0_CORE1_ECC_AGGR", 0x000717800ULL,
+ 0x00000400ULL);
+ ADD_MAIN_UNIMP("PCIE0_CORE_ECC_AGGR0", 0x000718000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("PCIE0_CORE_ECC_AGGR1", 0x000719000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("ADC0_ECC_REGS", 0x00071A000ULL, 0x00000400ULL);
+
+ /* DCC */
+ ADD_MAIN_UNIMP("DCC0", 0x000800000ULL, 0x00000040ULL);
+ ADD_MAIN_UNIMP("DCC1", 0x000804000ULL, 0x00000040ULL);
+ ADD_MAIN_UNIMP("DCC2", 0x000808000ULL, 0x00000040ULL);
+ ADD_MAIN_UNIMP("DCC3", 0x00080C000ULL, 0x00000040ULL);
+ ADD_MAIN_UNIMP("DCC4", 0x000810000ULL, 0x00000040ULL);
+ ADD_MAIN_UNIMP("DCC5", 0x000814000ULL, 0x00000040ULL);
+
+ /* Routers, GTC and VTM */
+ ADD_MAIN_UNIMP("MAIN_GPIOMUX_INTROUTER0_CFG", 0x000A00000ULL,
+ 0x00000800ULL);
+ ADD_MAIN_UNIMP("CMP_EVENT_INTROUTER0_CFG", 0x000A30000ULL, 0x00000800ULL);
+ ADD_MAIN_UNIMP("TIMESYNC_EVENT_INTROUTER0_CFG", 0x000A40000ULL,
+ 0x00000800ULL);
+ ADD_MAIN_UNIMP("GTC0_GTC_CFG0", 0x000A80000ULL, 0x00000400ULL);
+ /* GTC0_GTC_CFG1 gets modeled by TIK3GtcState at 0x000a90000. */
+ ADD_MAIN_UNIMP("GTC0_GTC_CFG2", 0x000AA0000ULL, 0x00004000ULL);
+ ADD_MAIN_UNIMP("GTC0_GTC_CFG3", 0x000AB0000ULL, 0x00004000ULL);
+ ADD_MAIN_UNIMP("VTM0_MMR_VBUSP_CFG1", 0x000B00000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("VTM0_MMR_VBUSP_CFG2", 0x000B01000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("VTM0_ECCAGGR_CFG0", 0x000B02000ULL, 0x00000400ULL);
+
+ /* PDMA */
+ ADD_MAIN_UNIMP("PDMA0_REGS", 0x000C00000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("PDMA1_REGS", 0x000C01000ULL, 0x00000400ULL);
+
+ /* Timers */
+ /* TIMER0_CFG (main_timer0) is realized as a device at 0x02400000 */
+ ADD_MAIN_UNIMP("TIMER1_CFG", 0x002410000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER2_CFG", 0x002420000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER3_CFG", 0x002430000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER4_CFG", 0x002440000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER5_CFG", 0x002450000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER6_CFG", 0x002460000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER7_CFG", 0x002470000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER8_CFG", 0x002480000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER9_CFG", 0x002490000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER10_CFG", 0x0024A0000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("TIMER11_CFG", 0x0024B0000ULL, 0x00000400ULL);
+
+ /* UARTs */
+ ADD_MAIN_UNIMP("UART1", 0x002810000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("UART2", 0x002820000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("UART3", 0x002830000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("UART4", 0x002840000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("UART5", 0x002850000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("UART6", 0x002860000ULL, 0x00000200ULL);
+
+ /* PBIST */
+ ADD_MAIN_UNIMP("PBIST3", 0x003310000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("PBIST2", 0x003330000ULL, 0x00000400ULL);
+
+ /* RTI */
+ ADD_MAIN_UNIMP("RTI0_CFG", 0x00E000000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("RTI1_CFG", 0x00E010000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("RTI8_CFG", 0x00E080000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("RTI9_CFG", 0x00E090000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("RTI10_CFG", 0x00E0A0000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("RTI11_CFG", 0x00E0B0000ULL, 0x00000100ULL);
+
+ /* SERDES, PCIe, DDR, USB, MMC and FSS */
+ ADD_MAIN_UNIMP("SERDES_10G0", 0x00F000000ULL, 0x00010000ULL);
+ /* PCIe core config space is used for ECAM; avoid shadowing it here. */
+ ADD_MAIN_UNIMP("DDR16SS0_SS_CFG", 0x00F300000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("USB0_MMR_MMRVBP_USBSS_CMN0", 0x00F900000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("USB0_RAMS_INJ_CFG", 0x00F901000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("USB0_PHY2", 0x00F908000ULL, 0x00000400ULL);
+ /*
+ * MMCSD0/1 use real SDHCI devices for the first 0x100 bytes and
+ * ti-k3-sdhci-phy stubs for SS_CFG; only the vendor tail stays here.
+ */
+ ADD_MAIN_UNIMP("MMCSD1_CTL_VENDOR", 0x00FA00100ULL, 0x00000F00ULL);
+ ADD_MAIN_UNIMP("MMCSD0_CTL_VENDOR", 0x00FA10100ULL, 0x00000F00ULL);
+ ADD_MAIN_UNIMP("FSS0_CFG", 0x00FC00000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSS0_FSAS_CFG", 0x00FC10000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSS0_OTFA_CFG", 0x00FC20000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("FSS0_OSPI0_CTRL0", 0x00FC40000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSS0_OSPI0_SS_CFG", 0x00FC44000ULL, 0x00000200ULL);
+
+ /* I2C */
+ /* I2C0_CFG is a real OMAP-I2C V2 device; only I2C1/2/3 stay as stubs. */
+ ADD_MAIN_UNIMP("I2C1_CFG", 0x020010000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("I2C2_CFG", 0x020020000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("I2C3_CFG", 0x020030000ULL, 0x00000100ULL);
+
+ /* MCSPI */
+ ADD_MAIN_UNIMP("MCSPI0_CFG", 0x020100000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MCSPI1_CFG", 0x020110000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MCSPI2_CFG", 0x020120000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MCSPI3_CFG", 0x020130000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MCSPI4_CFG", 0x020140000ULL, 0x00000400ULL);
+
+ /* MCAN */
+ ADD_MAIN_UNIMP("MCAN0_SS", 0x020700000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("MCAN0_CFG", 0x020701000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("MCAN0_MSGMEM_RAM", 0x020708000ULL, 0x00008000ULL);
+ ADD_MAIN_UNIMP("MCAN1_SS", 0x020710000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("MCAN1_CFG", 0x020711000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("MCAN1_MSGMEM_RAM", 0x020718000ULL, 0x00008000ULL);
+ ADD_MAIN_UNIMP("MCAN0_ECC_AGGR", 0x024018000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MCAN1_ECC_AGGR", 0x024019000ULL, 0x00000400ULL);
+
+ /* PWM / QEP / FSI */
+ ADD_MAIN_UNIMP("EPWM0", 0x023000000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EPWM1", 0x023010000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EPWM2", 0x023020000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EPWM3", 0x023030000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EPWM4", 0x023040000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EPWM5", 0x023050000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EPWM6", 0x023060000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EPWM7", 0x023070000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EPWM8", 0x023080000ULL, 0x00000100ULL);
+
+ ADD_MAIN_UNIMP("ECAP0_CTL_STS", 0x023100000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("ECAP1_CTL_STS", 0x023110000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("ECAP2_CTL_STS", 0x023120000ULL, 0x00000100ULL);
+
+ ADD_MAIN_UNIMP("EQEP0_REG", 0x023200000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EQEP1_REG", 0x023210000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("EQEP2_REG", 0x023220000ULL, 0x00000100ULL);
+
+ ADD_MAIN_UNIMP("FSIRX0_CFG", 0x023500000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSIRX1_CFG", 0x023510000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSIRX2_CFG", 0x023520000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSIRX3_CFG", 0x023530000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSIRX4_CFG", 0x023540000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSIRX5_CFG", 0x023550000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSITX0_CFG", 0x023600000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("FSITX1_CFG", 0x023610000ULL, 0x00000100ULL);
+
+ /* ELM/ADC/SPINLOCK */
+ ADD_MAIN_UNIMP("ELM0", 0x025010000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("ADC0_FIFO", 0x028000000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("ADC0", 0x028001000ULL, 0x00000400ULL);
+
+ ADD_MAIN_UNIMP("SPINLOCK0", 0x02A000000ULL, 0x00008000ULL);
+ /* === PRU_ICSSG0 / PRU_ICSSG1 blocks === */
+
+ /* PRU_ICSSG0 */
+ ADD_MAIN_UNIMP("PRU_ICSSG0_DRAM0_SLV_RAM", 0x030000000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_DRAM1_SLV_RAM", 0x030002000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_RTU0_IRAM_RAM", 0x030004000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_RTU1_IRAM_RAM", 0x030006000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_RAT_SLICE0_CFG", 0x030008000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_RAT_SLICE1_CFG", 0x030009000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP_TX0_IRAM_RAM", 0x03000A000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP_TX1_IRAM_RAM", 0x03000C000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_RAM_SLV_RAM", 0x030010000ULL, 0x00010000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_ICSS_INTC_SLV", 0x030020000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP0_IRAM", 0x030022000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP0_IRAM_DEBUG", 0x030022400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_RTU0_IRAM", 0x030023000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_RTU0_IRAM_DEBUG", 0x030023400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_RTU1_IRAM", 0x030023800ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_RTU1_IRAM_DEBUG", 0x030023C00ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP1_IRAM", 0x030024000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP1_IRAM_DEBUG", 0x030024400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PROT_SLV", 0x030024C00ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP_TX0_IRAM", 0x030025000ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP_TX0_IRAM_DEBUG", 0x030025400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP_TX1_IRAM", 0x030025800ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP_TX1_IRAM_DEBUG", 0x030025C00ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_CFG_SLV", 0x030026000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PA_STAT_WRAP_QSTAT", 0x030027000ULL,
+ 0x00000800ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_ICSS_UART_SLV", 0x030028000ULL,
+ 0x00000040ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_TASKS_MGR_PRU0_MMR0", 0x03002A000ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_TASKS_MGR_RTU0_MMR0", 0x03002A100ULL,
+ 0x00000200ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_TASKS_MGR_PRU1_MMR0", 0x03002A200ULL,
+ 0x00000300ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_TASKS_MGR_RTU1_MMR0", 0x03002A300ULL,
+ 0x00000400ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_TASKS_MGR_PRU_TX0_MMR0", 0x03002A400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_TASKS_MGR_PRU_TX1_MMR0", 0x03002A500ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PA_STAT_WRAP_CSTAT", 0x03002C000ULL,
+ 0x00000800ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_IEP0", 0x03002E000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_IEP1", 0x03002F000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_ICSS_ECAP0_SLV", 0x030030000ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_MII_RT_CFG", 0x030032000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_MII_RT_SGMII0_CFG", 0x030032100ULL,
+ 0x00000200ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_MII_RT_SGMII1_CFG", 0x030032200ULL,
+ 0x00000300ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_MDIO", 0x030032400ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_MII_RT_G_CFG_REGS_G0", 0x030033000ULL,
+ 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP0_IRAM_RAM", 0x030034000ULL,
+ 0x00004000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PR1_PDSP1_IRAM_RAM", 0x030038000ULL,
+ 0x00004000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_PA_STAT_WRAP_PA_SLV", 0x03003C000ULL,
+ 0x00000100ULL);
+
+ /* PRU_ICSSG1 */
+ ADD_MAIN_UNIMP("PRU_ICSSG1_DRAM0_SLV_RAM", 0x030080000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_DRAM1_SLV_RAM", 0x030082000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_RTU0_IRAM_RAM", 0x030084000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_RTU1_IRAM_RAM", 0x030086000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_RAT_SLICE0_CFG", 0x030088000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_RAT_SLICE1_CFG", 0x030089000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP_TX0_IRAM_RAM", 0x03008A000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP_TX1_IRAM_RAM", 0x03008C000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_RAM_SLV_RAM", 0x030090000ULL, 0x00010000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_ICSS_INTC_SLV", 0x0300A0000ULL,
+ 0x00002000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP0_IRAM", 0x0300A2000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP0_IRAM_DEBUG", 0x0300A2400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_RTU0_IRAM", 0x0300A3000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_RTU0_IRAM_DEBUG", 0x0300A3400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_RTU1_IRAM", 0x0300A3800ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_RTU1_IRAM_DEBUG", 0x0300A3C00ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP1_IRAM", 0x0300A4000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP1_IRAM_DEBUG", 0x0300A4400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PROT_SLV", 0x0300A4C00ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP_TX0_IRAM", 0x0300A5000ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP_TX0_IRAM_DEBUG", 0x0300A5400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP_TX1_IRAM", 0x0300A5800ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP_TX1_IRAM_DEBUG", 0x0300A5C00ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_CFG_SLV", 0x0300A6000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PA_STAT_WRAP_QSTAT", 0x0300A7000ULL,
+ 0x00000800ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_ICSS_UART_SLV", 0x0300A8000ULL,
+ 0x00000040ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_TASKS_MGR_PRU0_MMR0", 0x0300AA000ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_TASKS_MGR_RTU0_MMR0", 0x0300AA100ULL,
+ 0x00000200ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_TASKS_MGR_PRU1_MMR0", 0x0300AA200ULL,
+ 0x00000300ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_TASKS_MGR_RTU1_MMR0", 0x0300AA300ULL,
+ 0x00000400ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_TASKS_MGR_PRU_TX0_MMR0", 0x0300AA400ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_TASKS_MGR_PRU_TX1_MMR0", 0x0300AA500ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PA_STAT_WRAP_CSTAT", 0x0300AC000ULL,
+ 0x00000800ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_IEP0", 0x0300AE000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_IEP1", 0x0300AF000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_ICSS_ECAP0_SLV", 0x0300B0000ULL,
+ 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_MII_RT_CFG", 0x0300B2000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_MII_RT_SGMII0_CFG", 0x0300B2100ULL,
+ 0x00000200ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_MII_RT_SGMII1_CFG", 0x0300B2200ULL,
+ 0x00000300ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_MDIO", 0x0300B2400ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_MII_RT_G_CFG_REGS_G0", 0x0300B3000ULL,
+ 0x00001000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP0_IRAM_RAM", 0x0300B4000ULL,
+ 0x00004000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PR1_PDSP1_IRAM_RAM", 0x0300B8000ULL,
+ 0x00004000ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_PA_STAT_WRAP_PA_SLV", 0x0300BC000ULL,
+ 0x00000100ULL);
+
+ /* === Large MAIN regions and windows (MiB..GiB) === */
+
+ /* The GIC device maps these regions, so do not shadow them here. */
+
+ /* CPSW / PCIe DBN */
+ ADD_MAIN_UNIMP("CPSW0_NUSS0", 0x008000000ULL, 0x00200000ULL);
+ /*
+ * Since PCIE0_CORE_DBN_CFG_PCIE_CORE0 overlaps ECAM, do not map it as
+ * unimplemented.
+ */
+
+ /* USB VBP core address map */
+ ADD_MAIN_UNIMP("USB0_VBP2APB_WRAP_CONTROLLER_VBP_CORE_ADDR_MAP",
+ 0x00F400000ULL, 0x00040000ULL); /* 256 KB */
+
+ /* TIMERMGR, CPTS, CBASS0, GPMC and R5F */
+ ADD_MAIN_UNIMP("TIMERMGR0_TIMERS", 0x037000000ULL, 0x00040000ULL);
+ ADD_MAIN_UNIMP("CPTS0", 0x039000000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("CBASS0_ERR0", 0x03A000000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("GPMC0_CFG", 0x03B000000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("R5FSS0_ECC_AGGR0", 0x03C010000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("R5FSS0_EVNT_BUS_VBUSP_MMRS", 0x03C018000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("R5FSS1_ECC_AGGR0", 0x03C030000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("R5FSS1_EVNT_BUS_VBUSP_MMRS", 0x03C038000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("TIMERMGR0_CONFIG", 0x03CD00000ULL, 0x00000200ULL);
+
+ /* MSRAM ECC aggregators / misc at 0x03F0_xxxx */
+ ADD_MAIN_UNIMP("MSRAM_256K0_ECC_AGGR_REGS", 0x03F001000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MSRAM_256K1_ECC_AGGR_REGS", 0x03F002000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MSRAM_256K2_ECC_AGGR_REGS", 0x03F003000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("GICSS0_REGS", 0x03F004000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("DMASS0_ECCAGGR0", 0x03F005000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MSRAM_256K5_ECC_AGGR_REGS", 0x03F006000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MSRAM_256K4_ECC_AGGR_REGS", 0x03F007000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MSRAM_256K3_ECC_AGGR_REGS", 0x03F008000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG0_ECC_AGGR0", 0x03F00A000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("PRU_ICSSG1_ECC_AGGR0", 0x03F00B000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("R5FSS0_CORE0_ECC_AGGR0", 0x03F00D000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("R5FSS1_CORE0_ECC_AGGR0", 0x03F00E000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("ECC_AGGR0_ECC_AGGR0", 0x03F00F000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MSRAM_256K6_ECC_AGGR_REGS", 0x03F010000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("MSRAM_256K7_ECC_AGGR_REGS", 0x03F011000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("PBIST1", 0x03F100000ULL, 0x00000400ULL);
+
+ /* SA2/DEBUGSS/ROM/STM/CTRL/FW/SEC MMR/GLB */
+ ADD_MAIN_UNIMP("SA2_UL0", 0x040900000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("SA2_UL0_MMRA", 0x040901000ULL, 0x00000200ULL);
+ /* SA2_UL0_EIP_76 (0x040910000, 128 B) is now the real TIK3Trng device. */
+ ADD_MAIN_UNIMP("SA2_UL0_EIP_29T2", 0x040920000ULL, 0x00010000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS0_SYS", 0x041000000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("ROM0", 0x041800000ULL, 0x00040000ULL);
+ ADD_MAIN_UNIMP("STM0_STIMULUS", 0x042000000ULL, 0x01000000ULL);
+ ADD_MAIN_UNIMP("CBASS0_FW0", 0x045000000ULL, 0x00008000ULL);
+ ADD_MAIN_UNIMP("CBASS_INFRA1_FW0", 0x045008000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("MAIN_SEC_MMR0_CFG2", 0x045900000ULL, 0x00020000ULL);
+ ADD_MAIN_UNIMP("MAIN_SEC_MMR0_CFG0", 0x045A00000ULL, 0x00020000ULL);
+ ADD_MAIN_UNIMP("CBASS_INFRA1_GLB0", 0x045B01000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("DMASS0_CBASS_DMSC_GLB0", 0x045B03000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("CBASS0_GLB0", 0x045B08000ULL, 0x00000400ULL);
+
+ /* DMASS0 regions */
+ ADD_MAIN_UNIMP("DMASS0_INTAGGR_INTR0", 0x048000000ULL, 0x00100000ULL);
+ ADD_MAIN_UNIMP("DMASS0_INTAGGR_IMAP0", 0x048100000ULL, 0x00004000ULL);
+ ADD_MAIN_UNIMP("DMASS0_INTAGGR_CFG0", 0x048110000ULL, 0x00000020ULL);
+ ADD_MAIN_UNIMP("DMASS0_INTAGGR_L2G0", 0x048120000ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("DMASS0_PSILCFG_PROXY0", 0x048130000ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("DMASS0_PSILSS_MMRS0", 0x048140000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DMASS0_INTAGGR_UNMAP0", 0x048180000ULL, 0x00020000ULL);
+ ADD_MAIN_UNIMP("DMASS0_INTAGGR_MCAST0", 0x048210000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DMASS0_INTAGGR_GCNTCFG0", 0x048220000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("DMASS0_ETLSW_MMRS0", 0x048230000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DMASS0_RINGACC_GCFG0", 0x048240000ULL, 0x00000400ULL);
+
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_CRED", 0x048400000ULL, 0x00000800ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_CRED", 0x048410000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_BCHAN", 0x048420000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_RFLOW", 0x048430000ULL, 0x00004000ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_TCHAN", 0x0484A0000ULL, 0x00004000ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_TCHAN", 0x0484A4000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_RCHAN", 0x0484C0000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_RCHAN", 0x0484C2000ULL, 0x00002000ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_GCFG", 0x0485C0000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_GCFG", 0x0485C0100ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_RING", 0x0485E0000ULL, 0x00020000ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_RING", 0x048600000ULL, 0x00008000ULL);
+ ADD_MAIN_UNIMP("DMASS0_RINGACC_RT0", 0x049000000ULL, 0x00400000ULL);
+ ADD_MAIN_UNIMP("DMASS0_RINGACC_CFG0", 0x049800000ULL, 0x00040000ULL);
+ ADD_MAIN_UNIMP("DMASS0_INTAGGR_GCNTRTI0", 0x04A000000ULL, 0x00100000ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_RCHANRT0", 0x04A800000ULL, 0x00020000ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_RCHANRT0", 0x04A820000ULL, 0x00020000ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_TCHANRT0", 0x04AA00000ULL, 0x00040000ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_TCHANRT0", 0x04AA40000ULL, 0x00020000ULL);
+ ADD_MAIN_UNIMP("DMASS0_PKTDMA_RINGRT0", 0x04B800000ULL, 0x00400000ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_RINGRT0", 0x04BC00000ULL, 0x00100000ULL);
+ ADD_MAIN_UNIMP("DMASS0_BCDMA_BCHANRT0", 0x04C000000ULL, 0x00020000ULL);
+ ADD_MAIN_UNIMP("DMASS0_RINGACC_SRC_FIFOS0", 0x04E000000ULL, 0x00400000ULL);
+
+ /* DEBUGSS_WRAP0 region set */
+
+ ADD_MAIN_UNIMP("STM0_CXSTM0", 0x73D200000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("STM0_CTI_CSCTI0", 0x73D201000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DBGSUSPENDROUTER0_CFG", 0x73D300000ULL, 0x00000800ULL);
+
+ /* CPT2 blocks */
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MMR0", 0x73E100000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM0", 0x73E120000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM1", 0x73E121000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM2", 0x73E122000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM3", 0x73E123000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM4", 0x73E124000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM5", 0x73E125000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM6", 0x73E126000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM7", 0x73E127000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM8", 0x73E128000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM9", 0x73E129000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM10", 0x73E12A000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM11", 0x73E12B000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM12", 0x73E12C000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM13", 0x73E12D000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM14", 0x73E12E000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM15", 0x73E12F000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM16", 0x73E130000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM17", 0x73E131000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM18", 0x73E132000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM19", 0x73E133000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM20", 0x73E134000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM21", 0x73E135000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM22", 0x73E136000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM23", 0x73E137000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM24", 0x73E138000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM25", 0x73E139000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM26", 0x73E13A000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM27", 0x73E13B000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM28", 0x73E13C000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM29", 0x73E13D000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM30", 0x73E13E000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("CPT2_AGGR0_MEM31", 0x73E13F000ULL, 0x00001000ULL);
+
+ /* DEBUGSS_WRAP0 set 0_1, 1_1 and EXT_APB1 */
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_ROM_TABLE_0_1", 0x740000000ULL,
+ 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_RESV0_1", 0x740001000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CFGAP1", 0x740002000ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_APBAP1", 0x740002100ULL, 0x00000200ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_AXIAP1", 0x740002200ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_PWRAP1", 0x740002300ULL, 0x00000400ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_PVIEW1", 0x740002400ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_JTAGAP1", 0x740002500ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_SECAP1", 0x740002600ULL, 0x00000100ULL);
+
+ /* Cortex config blocks 0..8, 256 bytes each */
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX0_CFG1", 0x740002700ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX1_CFG1", 0x740002800ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX2_CFG1", 0x740002900ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX3_CFG1", 0x740002A00ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX4_CFG1", 0x740002B00ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX5_CFG1", 0x740002C00ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX6_CFG1", 0x740002D00ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX7_CFG1", 0x740002E00ULL, 0x00000100ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CORTEX8_CFG1", 0x740002F00ULL, 0x00000100ULL);
+
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_RESV1_1", 0x740003000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_RESV2_1", 0x740004000ULL, 0x02000000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_ROM_TABLE_1_1", 0x760000000ULL,
+ 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CSCTI1", 0x760001000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_DRM1", 0x760002000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_RESV3_1", 0x760003000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CSTPIU1", 0x760004000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_CTF1", 0x760005000ULL, 0x00001000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_RESV4_1", 0x760006000ULL, 0x01000000ULL);
+ ADD_MAIN_UNIMP("DEBUGSS_WRAP0_EXT_APB1", 0x770000000ULL, 0x10000000ULL);
+}
+
+static void ti_am64_create_mcu_unimplemented(MemoryRegion *root)
+{
+ create_unimplemented_device_in_root(root, "MCU_PSC0", 0x04000000, 0x1000);
+ create_unimplemented_device_in_root(root, "MCU_PLLCTRL0", 0x04020000,
+ 0x200);
+ create_unimplemented_device_in_root(root, "MCU_PLL0_CFG", 0x04040000,
+ 0x1000);
+ create_unimplemented_device_in_root(root, "MCU_PADCFG_CTRL0_CFG0",
+ 0x04080000, 0x8000);
+ create_unimplemented_device_in_root(root, "MCU_ESM0_CFG", 0x04100000,
+ 0x1000);
+ create_unimplemented_device_in_root(root, "MCU_GPIO0", 0x04201000, 0x100);
+ create_unimplemented_device_in_root(root, "MCU_GPIOMUX_INTROUTER0_CFG",
+ 0x04210000, 0x200);
+ create_unimplemented_device_in_root(root, "MCU_TIMEOUT0_CFG", 0x04300000,
+ 0x400);
+ /*
+ * MCU_CTRL_MMR0 is a ctrlmmr stub, so MCU_RST_SRC reads as warm reset.
+ */
+ create_unimplemented_device_in_root(root, "MCU_ECC_AGGR0_ECC_AGGR0",
+ 0x04700000, 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_CBASS0_ERR0", 0x04720000,
+ 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_TIMER0_CFG", 0x04800000,
+ 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_TIMER1_CFG", 0x04810000,
+ 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_TIMER2_CFG", 0x04820000,
+ 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_TIMER3_CFG", 0x04830000,
+ 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_RTI0_CFG", 0x04880000,
+ 0x0100);
+ create_unimplemented_device_in_root(root, "MCU_I2C0_CFG", 0x04900000,
+ 0x0100);
+ create_unimplemented_device_in_root(root, "MCU_I2C1_CFG", 0x04910000,
+ 0x0100);
+ create_unimplemented_device_in_root(root, "MCU_MCSPI0_CFG", 0x04B00000,
+ 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_MCSPI1_CFG", 0x04B10000,
+ 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_DCC0", 0x04C00000, 0x0040);
+ create_unimplemented_device_in_root(root, "MCU_MCRC64_0_REGS", 0x04D00000,
+ 0x1000);
+ create_unimplemented_device_in_root(root, "MCU_M4FSS0_RAT", 0x05FF0000,
+ 0x1000);
+ create_unimplemented_device_in_root(root, "MCU_M4FSS0_ECC_AGGR0",
+ 0x05FF1000, 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_CBASS0_GLB0", 0x45B02000,
+ 0x0400);
+ create_unimplemented_device_in_root(root, "MCU_ECC_AGGR", 0x44201000,
+ 0x400);
+}
+
+static bool ti_am64x_uart_realize(TIAM64xState *s, MemoryRegion *memory,
+ AM64Uart *au, const hwaddr addr, Error **errp)
+{
+ qdev_prop_set_uint8(DEVICE(au), "regshift", 2);
+ if (!sysbus_realize(SYS_BUS_DEVICE(au), errp)) {
+ return false;
+ }
+
+ memory_region_add_subregion(memory, addr,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(au), 0));
+ return true;
+}
+
+static void ti_am64x_realize(DeviceState *dev_soc, Error **errp)
+{
+ ERRP_GUARD();
+
+ TIAM64xState *s = TI_AM64X(dev_soc);
+ Error *err = NULL;
+ DeviceState *armv7m;
+ MachineState *ms = MACHINE(qdev_get_machine());
+ MemoryRegion *sysmem = get_system_memory();
+
+ if (clock_has_source(s->refclk)) {
+ error_setg(errp, "refclk clock must not be wired up by the board code");
+ return;
+ }
+
+ if (!clock_has_source(s->sysclk)) {
+ error_setg(errp, "sysclk clock must be wired up by the board code");
+ return;
+ }
+
+ memory_region_init_ram(&s->mcu_iram, OBJECT(dev_soc), "am64x.mcu.iram",
+ MCU_IRAM_SIZE, &err);
+
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ memory_region_init_ram(&s->mcu_dram, OBJECT(dev_soc), "am64x.mcu.dram",
+ MCU_DRAM_SIZE, &err);
+
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ memory_region_init_ram(&s->mcu_ddr, OBJECT(dev_soc), "am64x.mcu.ddr",
+ MCU_DDR_SIZE, &err);
+
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ if (s->main_ram_size == 0) {
+ s->main_ram_size = ms->ram_size;
+ }
+ if (s->main_ram_base == 0) {
+ s->main_ram_base = MAIN_RAM_BASE_ADDRESS;
+ }
+ if (s->a53_cpus == 0) {
+ s->a53_cpus = TI_AM64X_A53_NUM;
+ }
+ if (s->a53_cpus > TI_AM64X_A53_NUM) {
+ error_setg(errp, "a53-cpus must be between 1 and %u", TI_AM64X_A53_NUM);
+ return;
+ }
+
+ memory_region_init(&s->mcu_root, OBJECT(s), "am64x.mcu-root", UINT64_MAX);
+
+ memory_region_add_subregion(&s->mcu_root, MCU_IRAM_BASE_ADDRESS,
+ &s->mcu_iram);
+ memory_region_add_subregion(&s->mcu_root, MCU_DRAM_BASE_ADDRESS,
+ &s->mcu_dram);
+ memory_region_init_alias(&s->mcu_iram_sysmem, OBJECT(s),
+ "am64x.mcu.iram.sysmem", &s->mcu_iram, 0,
+ MCU_IRAM_SIZE);
+ memory_region_init_alias(&s->mcu_dram_sysmem, OBJECT(s),
+ "am64x.mcu.dram.sysmem", &s->mcu_dram, 0,
+ MCU_DRAM_SIZE);
+ memory_region_add_subregion(sysmem, 0x05000000, &s->mcu_iram_sysmem);
+ memory_region_add_subregion(sysmem, 0x05040000, &s->mcu_dram_sysmem);
+
+ /* Main-domain SRAM (OCSRAM/MSRAM), where ROM places the boot image. */
+ memory_region_init_ram(&s->ocsram, OBJECT(dev_soc), "am64x.ocsram", 2 * MiB,
+ &err);
+
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ memory_region_add_subregion(sysmem, 0x70000000, &s->ocsram);
+
+ /* DDR window of the MCU domain, the M4F fetches and runs from here. */
+ memory_region_add_subregion(&s->mcu_root, MCU_DDR_BASE_ADDRESS,
+ &s->mcu_ddr);
+
+ for (int i = 0; i < s->a53_cpus; i++) {
+ CPUState *cs = CPU(&s->a53[i]);
+ if (!object_property_set_int(OBJECT(&s->a53[i]), "mp-affinity", i,
+ errp)) {
+ return;
+ }
+ cs->cpu_index = i;
+ qdev_prop_set_bit(DEVICE(&s->a53[i]), "start-powered-off",
+ s->a53_start_powered_off || i > 0);
+ qdev_prop_set_bit(DEVICE(&s->a53[i]), "has_el3", true);
+ qdev_prop_set_bit(DEVICE(&s->a53[i]), "has_el2", true);
+ /*
+ * AM64x uses a 200 MHz system counter rate. Override QEMU's
+ * Cortex-A53 default of 62.5 MHz.
+ */
+ object_property_set_int(OBJECT(&s->a53[i]), "cntfrq", 200000000,
+ &error_abort);
+ if (!qdev_realize(DEVICE(&s->a53[i]), NULL, errp)) {
+ return;
+ }
+ }
+ if (!qdev_realize(DEVICE(&s->a53_cluster), NULL, errp)) {
+ return;
+ }
+
+ {
+ DeviceState *gicdev = DEVICE(&s->gic);
+ SysBusDevice *gicsbd = SYS_BUS_DEVICE(&s->gic);
+ QList *redist_region_count;
+
+ qdev_prop_set_uint32(gicdev, "revision", 3);
+ qdev_prop_set_uint32(gicdev, "num-cpu", s->a53_cpus);
+ qdev_prop_set_uint32(gicdev, "num-irq",
+ TI_AM64X_GIC_NUM_SPI + GIC_INTERNAL);
+ qdev_prop_set_bit(gicdev, "has-security-extensions", true);
+ redist_region_count = qlist_new();
+ qlist_append_int(redist_region_count, s->a53_cpus);
+ qdev_prop_set_array(gicdev, "redist-region-count", redist_region_count);
+
+ if (!sysbus_realize(gicsbd, errp)) {
+ return;
+ }
+ sysbus_mmio_map(gicsbd, 0, MAIN_GIC_DIST_ADDRESS);
+ sysbus_mmio_map(gicsbd, 1, MAIN_GIC_REDIST_ADDRESS);
+
+ for (int i = 0; i < s->a53_cpus; i++) {
+ DeviceState *cpudev = DEVICE(&s->a53[i]);
+ int ppibase = TI_AM64X_GIC_NUM_SPI + i * GIC_INTERNAL + GIC_NR_SGIS;
+ const int timer_irq[] = {
+ [GTIMER_PHYS] = INTID_TO_PPI(ARCH_TIMER_NS_EL1_IRQ),
+ [GTIMER_VIRT] = INTID_TO_PPI(ARCH_TIMER_VIRT_IRQ),
+ [GTIMER_HYP] = INTID_TO_PPI(ARCH_TIMER_NS_EL2_IRQ),
+ [GTIMER_SEC] = INTID_TO_PPI(ARCH_TIMER_S_EL1_IRQ),
+ };
+
+ for (int j = 0; j < ARRAY_SIZE(timer_irq); j++) {
+ qdev_connect_gpio_out(
+ cpudev, j,
+ qdev_get_gpio_in(gicdev, ppibase + timer_irq[j]));
+ }
+ qdev_connect_gpio_out_named(
+ cpudev, "gicv3-maintenance-interrupt", 0,
+ qdev_get_gpio_in(gicdev,
+ ppibase + INTID_TO_PPI(ARCH_GIC_MAINT_IRQ)));
+
+ sysbus_connect_irq(gicsbd, i,
+ qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
+ sysbus_connect_irq(gicsbd, i + s->a53_cpus,
+ qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
+ sysbus_connect_irq(gicsbd, i + (2 * s->a53_cpus),
+ qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ));
+ sysbus_connect_irq(gicsbd, i + (3 * s->a53_cpus),
+ qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ));
+ }
+ }
+
+ armv7m = DEVICE(&s->armv7m);
+ qdev_prop_set_uint32(armv7m, "num-irq", 64);
+ qdev_prop_set_uint8(armv7m, "num-prio-bits", 3);
+ qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m4"));
+ qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
+ qdev_prop_set_bit(armv7m, "start-powered-off", s->m4_start_powered_off);
+ object_property_set_link(OBJECT(&s->armv7m), "memory", OBJECT(&s->mcu_root),
+ &error_abort);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
+ return;
+ }
+ CPU(s->armv7m.cpu)->cpu_index = s->a53_cpus;
+ if (!qdev_realize(DEVICE(&s->m4_cluster), NULL, errp)) {
+ return;
+ }
+
+ /* Cortex-R5F0_0 - the AM64x boot core released by ROM. */
+ object_property_set_bool(OBJECT(&s->r5[0]), "start-powered-off",
+ s->r5_start_powered_off, &error_abort);
+ /*
+ * R5F0_0 has MPIDR.Aff0 == 0 on the SoC. Pin the affinity here,
+ * since QEMU would derive it from the nonzero cpu_index.
+ */
+ object_property_set_int(OBJECT(&s->r5[0]), "mp-affinity", 0, &error_abort);
+ /* R5 reset vectors live low at 0x70000000, not in hivecs. */
+ object_property_set_bool(OBJECT(&s->r5[0]), "reset-hivecs", false,
+ &error_abort);
+ if (!qdev_realize(DEVICE(&s->r5[0]), NULL, errp)) {
+ return;
+ }
+ CPU(&s->r5[0])->cpu_index = s->a53_cpus + 1;
+ qdev_realize(DEVICE(&s->r5_cluster), NULL, &error_abort);
+
+ object_property_set_link(OBJECT(&s->rat), "window-root",
+ OBJECT(&s->mcu_root), &error_abort);
+
+ object_property_set_link(OBJECT(&s->rat), "target-root", OBJECT(sysmem),
+ &error_abort);
+
+ /* Map RAT config registers. */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->rat), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ &s->mcu_root, MCU_RAT_MMIO_ADDRESS,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->rat), 0));
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->sec_proxy), errp)) {
+ return;
+ }
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->sec_proxy), 0,
+ qdev_get_gpio_in(DEVICE(&s->gic), 34));
+
+ object_property_set_link(OBJECT(&s->dmsc), "sec-proxy",
+ OBJECT(&s->sec_proxy), &error_abort);
+
+ {
+ static const uint16_t a53_rx_threads[] = {
+ A53_0_WRITE_THREAD_ID,
+ A53_1_WRITE_THREAD_ID,
+ };
+ static const uint16_t a53_tx_threads[] = {
+ A53_0_READ_RESPONSE_THREAD_ID,
+ A53_1_READ_RESPONSE_THREAD_ID,
+ };
+ QList *rx_threads = qlist_new();
+ QList *tx_threads = qlist_new();
+
+ for (int i = 0; i < s->a53_cpus && i < ARRAY_SIZE(a53_rx_threads);
+ i++) {
+ qlist_append_int(rx_threads, a53_rx_threads[i]);
+ qlist_append_int(tx_threads, a53_tx_threads[i]);
+ }
+ /* Keep the old A53_2 threads for existing guest. */
+ qlist_append_int(rx_threads, A53_2_WRITE_THREAD_ID);
+ qlist_append_int(tx_threads, A53_2_READ_RESPONSE_THREAD_ID);
+ qlist_append_int(rx_threads, M4_0_WRITE_THREAD_ID);
+ qlist_append_int(tx_threads, M4_0_READ_RESPONSE_THREAD_ID);
+
+ /* R5F0_0 secure host 35: writes on thread 1, reads on 0. */
+ qlist_append_int(rx_threads, MAIN_0_R5_0_WRITE_THREAD_ID);
+ qlist_append_int(tx_threads, MAIN_0_R5_0_READ_RESPONSE_THREAD_ID);
+
+ qdev_prop_set_array(DEVICE(&s->dmsc), "rx-threads", rx_threads);
+ qdev_prop_set_array(DEVICE(&s->dmsc), "tx-threads", tx_threads);
+
+ /*
+ * Secure TISCI hosts use a 4-byte {u16 checksum; u16 reserved}
+ * prefix on the TX thread. We mark those RX threads secure, so
+ * ti-dmsc strips and restores the prefix around message handling.
+ */
+ QList *secure_rx = qlist_new();
+
+ qlist_append_int(secure_rx, MAIN_0_R5_0_WRITE_THREAD_ID);
+ qlist_append_int(secure_rx, A53_0_WRITE_THREAD_ID);
+ qlist_append_int(secure_rx, A53_1_WRITE_THREAD_ID);
+ qdev_prop_set_array(DEVICE(&s->dmsc), "secure-rx-threads", secure_rx);
+ }
+ qdev_prop_set_uint64(DEVICE(&s->dmsc), "m4-cpu-id", s->a53_cpus);
+ /* arm_set_cpu_on() uses MP affinity; the A53 core 0 has affinity 0. */
+ qdev_prop_set_uint64(DEVICE(&s->dmsc), "a53-cpu-id-base", 0);
+
+ if (!qdev_realize(DEVICE(&s->dmsc), NULL, errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, MAIN_SEC_PROXY_MMRS_ADDRESS,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->sec_proxy), 0));
+
+ memory_region_add_subregion(
+ sysmem, MAIN_SEC_PROXY_SCFG_ADDRESS,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->sec_proxy), 1));
+
+ memory_region_add_subregion(
+ sysmem, MAIN_SEC_PROXY_RT_ADDRESS,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->sec_proxy), 2));
+
+ memory_region_add_subregion(
+ sysmem, MAIN_SEC_PROXY_TARGET_ADDRESS,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->sec_proxy), 3));
+
+ for (int i = 0; i < TI_AM64X_MAILBOX_NUM; i++) {
+ hwaddr base = MAIN_MAILBOX_BASE_ADDRESS + (i * MAIN_MAILBOX_STRIDE);
+ qdev_prop_set_uint8(DEVICE(&s->mailbox[i]), "mailbox-id", i);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->mailbox[i]), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, base,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->mailbox[i]), 0));
+ }
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->mailbox[6]), 3,
+ qdev_get_gpio_in(DEVICE(&s->armv7m), 56));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->mailbox[7]), 3,
+ qdev_get_gpio_in(DEVICE(&s->armv7m), 57));
+ /* GIC SPI 108..: mailbox cluster pending intr2/3 (UM 7.1.2.1). */
+ {
+ struct {
+ uint8_t mbox;
+ uint8_t user;
+ uint16_t spi;
+ } gic_map[] = {
+ {6, 2, 108}, /* CLUSTER6_PEND_INTR2 */
+ {7, 2, 109}, /* CLUSTER7_PEND_INTR2 */
+ {2, 2, 110}, /* CLUSTER2_PEND_INTR2 */
+ {2, 3, 111}, /* CLUSTER2_PEND_INTR3 */
+ {3, 2, 112}, /* CLUSTER3_PEND_INTR2 */
+ {3, 3, 113}, /* CLUSTER3_PEND_INTR3 */
+ {4, 2, 114}, /* CLUSTER4_PEND_INTR2 */
+ {4, 3, 115}, /* CLUSTER4_PEND_INTR3 */
+ {5, 2, 116}, /* CLUSTER5_PEND_INTR2 */
+ {5, 3, 117}, /* CLUSTER5_PEND_INTR3 */
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(gic_map); i++) {
+ uint16_t spi_index = gic_map[i].spi - GIC_INTERNAL;
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->mailbox[gic_map[i].mbox]),
+ gic_map[i].user,
+ qdev_get_gpio_in(DEVICE(&s->gic), spi_index));
+ }
+ }
+
+ struct ti_am64_uart_config {
+ hwaddr base_addr;
+ int irq_num;
+ } mcu_uart_configs[TI_AM64X_MCU_UART_NUM] = {
+ {.base_addr = 0x04A00000, .irq_num = 24}, /* MCU_UART0 */
+ {.base_addr = 0x04A10000, .irq_num = 25}, /* MCU_UART1 */
+ };
+
+ /* UARTs */
+ for (int i = 0; i < TI_AM64X_MCU_UART_NUM; i++) {
+ struct ti_am64_uart_config *cfg = &mcu_uart_configs[i];
+ if (!ti_am64x_uart_realize(s, sysmem, &s->mcu_uart[i], cfg->base_addr,
+ errp)) {
+ return;
+ }
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->mcu_uart[i]), 0,
+ qdev_get_gpio_in(DEVICE(&s->armv7m), cfg->irq_num));
+ }
+
+ /* Main-domain UART0. */
+ if (!ti_am64x_uart_realize(s, sysmem, &s->main_uart0, 0x02800000, errp)) {
+ return;
+ }
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->main_uart0), 0,
+ qdev_get_gpio_in(DEVICE(&s->gic), 178));
+
+ /* Control MMR: DEVSTAT and lock-kick sink. */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->ctrlmmr), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, 0x43000000,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->ctrlmmr), 0));
+
+ /*
+ * MCU_CTRL_MMR0 only implements MCU_RST_SRC (0x18178). The reset value
+ * reports a warm reset source.
+ */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->mcu_ctrlmmr), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, 0x04500000,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->mcu_ctrlmmr), 0));
+
+ /*
+ * Security Manager MMR only implements K3_SEC_MGR_SYS_STATUS at
+ * 0x44234100. The reset value reports SYS_STATUS_DEV_TYPE_GP.
+ */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->sec_ctrlmmr), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, 0x44234000,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->sec_ctrlmmr), 0));
+
+ /* DDRSS config stub: RAM-backed, with status bits ORed into it. */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->ddrss), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, 0x0f308000,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->ddrss), 0));
+
+ /* SA2UL TRNG (EIP-76) register window. */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->trng), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, 0x40910000,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->trng), 0));
+
+ /* DM Timer0: 20 MHz free-running main_timer0. */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->main_timer0), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, 0x02400000,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->main_timer0), 0));
+
+ /*
+ * GTC0_GTC_CFG1 exposes CNTCR/CNTFID0. Reset reports the counter enabled
+ * at 200 MHz, matching the AM64x default system counter.
+ */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->gtc), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, 0x00A90000, sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->gtc), 0));
+
+ /*
+ * MAIN domain I2C0, an OMAP-I2C V2 controller. The other three MAIN
+ * instances are not modelled yet.
+ */
+ s->i2c0 = qdev_new(TYPE_OMAP_I2C);
+ /* revision >= OMAP2 selects OMAP2+ reset semantics; MMIO stays V2. */
+ qdev_prop_set_uint8(s->i2c0, "revision", 0x40);
+ qdev_prop_set_uint8(s->i2c0, "mmio-version", 2);
+ if (!sysbus_realize_and_unref(SYS_BUS_DEVICE(s->i2c0), errp)) {
+ return;
+ }
+ memory_region_add_subregion(
+ sysmem, 0x20000000, sysbus_mmio_get_region(SYS_BUS_DEVICE(s->i2c0), 0));
+
+ /*
+ * MMCSD0/1 SDHCI controllers and am654-style PHY windows. IRQs are raw
+ * GIC_SPI values: sdhci0 = 133, sdhci1 = 134.
+ */
+ {
+ static const struct {
+ hwaddr ctl;
+ hwaddr phy;
+ int irq;
+ } sdhci_cfg[] = {
+ {0x0fa10000, 0x0fa18000, 133}, /* MMCSD0 / sdhci0, eMMC */
+ {0x0fa00000, 0x0fa08000, 134}, /* MMCSD1 / sdhci1, SD */
+ };
+
+ for (int i = 0; i < TI_AM64X_SDHCI_NUM; i++) {
+ SysBusDevice *sbd = SYS_BUS_DEVICE(&s->sdhci[i]);
+
+ object_property_set_uint(OBJECT(&s->sdhci[i]), "sd-spec-version", 3,
+ &error_abort);
+ /*
+ * capareg 0x157c34b4 is the am654 SDHCI 3.0 capability set plus
+ * SDHC_CAPAB_BUS64BIT. Bit 28 advertises 64-bit ADMA2 support.
+ */
+ object_property_set_uint(OBJECT(&s->sdhci[i]), "capareg",
+ 0x157c34b4, &error_abort);
+ if (!sysbus_realize(sbd, errp)) {
+ return;
+ }
+ memory_region_add_subregion(sysmem, sdhci_cfg[i].ctl,
+ sysbus_mmio_get_region(sbd, 0));
+ sysbus_connect_irq(
+ sbd, 0, qdev_get_gpio_in(DEVICE(&s->gic), sdhci_cfg[i].irq));
+
+ sbd = SYS_BUS_DEVICE(&s->sdhci_phy[i]);
+ if (!sysbus_realize(sbd, errp)) {
+ return;
+ }
+ memory_region_add_subregion(sysmem, sdhci_cfg[i].phy,
+ sysbus_mmio_get_region(sbd, 0));
+ }
+ }
+
+ ti_am64_create_mcu_unimplemented(sysmem);
+ ti_am64_create_main_unimplemented(sysmem);
+}
+
+static const Property ti_am64x_properties[] = {
+ DEFINE_PROP_UINT64("ram-base", TIAM64xState, main_ram_base,
+ MAIN_RAM_BASE_ADDRESS),
+ DEFINE_PROP_UINT64("ram-size", TIAM64xState, main_ram_size, 0),
+ DEFINE_PROP_UINT8("a53-cpus", TIAM64xState, a53_cpus, TI_AM64X_A53_NUM),
+ DEFINE_PROP_BOOL("a53-start-powered-off", TIAM64xState,
+ a53_start_powered_off, false),
+ DEFINE_PROP_BOOL("m4-start-powered-off", TIAM64xState, m4_start_powered_off,
+ true),
+ DEFINE_PROP_BOOL("r5-start-powered-off", TIAM64xState, r5_start_powered_off,
+ true),
+};
+
+static void ti_am64x_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = ti_am64x_realize;
+ /* Fixed system-bus mapping. */
+ dc->user_creatable = false;
+ device_class_set_props(dc, ti_am64x_properties);
+}
+
+static const TypeInfo am64_mcu_types[] = {
+ {
+ .name = TYPE_TI_AM64X,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TIAM64xState),
+ .instance_init = ti_am64x_initfn,
+ .class_init = ti_am64x_class_init,
+ }
+};
+
+DEFINE_TYPES(am64_mcu_types)
diff --git a/hw/arm/trace-events b/hw/arm/trace-events
index 1b16f710fe..605b71dd62 100644
--- a/hw/arm/trace-events
+++ b/hw/arm/trace-events
@@ -96,3 +96,7 @@ z2_aer915_event(int8_t event, int8_t len) "i2c event =0x%x len=%d bytes"
# bcm2838.c
bcm2838_gic_set_irq(int irq, int level) "gic irq:%d lvl:%d"
+
+# k3-bootrom.c
+k3_bootrom_component(uint32_t comp_type, uint64_t dest, uint32_t size) "comp_type %u dest 0x%" PRIx64 " size 0x%x"
+k3_bootrom_boot(uint64_t entry) "starting R5F0_0 at 0x%" PRIx64
diff --git a/include/hw/arm/ti-am64x.h b/include/hw/arm/ti-am64x.h
new file mode 100644
index 0000000000..a362569f36
--- /dev/null
+++ b/include/hw/arm/ti-am64x.h
@@ -0,0 +1,86 @@
+/*
+ * TI AM64x SoC family model
+ *
+ * Copyright (c) 2025 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_ARM_TI_AM64X_H
+#define HW_ARM_TI_AM64X_H
+
+#include "system/memory.h"
+#include "hw/arm/armv7m.h"
+#include "cpu.h"
+#include "hw/cpu/cluster.h"
+#include "hw/intc/arm_gicv3.h"
+#include "hw/core/clock.h"
+#include "qom/object.h"
+#include "hw/misc/ti-rat.h"
+#include "hw/misc/ti-sec-proxy.h"
+#include "hw/misc/ti-dmsc.h"
+#include "hw/misc/ti-mailbox.h"
+#include "hw/misc/ti-k3-ctrlmmr.h"
+#include "hw/misc/ti-k3-gtc.h"
+#include "hw/misc/ti-k3-ddrss.h"
+#include "hw/misc/ti-k3-sdhci-phy.h"
+#include "hw/misc/ti-k3-trng.h"
+#include "hw/sd/sdhci.h"
+#include "hw/char/ti-am64-uart.h"
+#include "hw/timer/ti-k3-dmtimer.h"
+
+#define TYPE_TI_AM64X "ti-am64x"
+OBJECT_DECLARE_SIMPLE_TYPE(TIAM64xState, TI_AM64X)
+
+#define TI_AM64X_MCU_UART_NUM 2
+#define TI_AM64X_MAILBOX_NUM 8
+#define TI_AM64X_A53_NUM 2
+#define TI_AM64X_GIC_NUM_SPI 256
+#define TI_AM64X_R5_NUM 1
+#define TI_AM64X_SDHCI_NUM 2
+
+struct TIAM64xState {
+ SysBusDevice parent_obj;
+ CPUClusterState a53_cluster;
+ CPUClusterState m4_cluster;
+ CPUClusterState r5_cluster;
+ ARMv7MState armv7m;
+ ARMCPU a53[TI_AM64X_A53_NUM];
+ ARMCPU r5[TI_AM64X_R5_NUM];
+ GICv3State gic;
+ MemoryRegion mcu_iram;
+ MemoryRegion mcu_dram;
+ MemoryRegion mcu_ddr;
+ MemoryRegion mcu_iram_sysmem;
+ MemoryRegion mcu_dram_sysmem;
+ MemoryRegion mcu_root;
+ MemoryRegion ocsram;
+ Clock *sysclk;
+ Clock *refclk;
+ TIRATState rat;
+ TISecProxyState sec_proxy;
+ TIDmscState dmsc;
+ TIMailboxState mailbox[TI_AM64X_MAILBOX_NUM];
+ AM64Uart mcu_uart[TI_AM64X_MCU_UART_NUM];
+ AM64Uart main_uart0;
+ TIK3CtrlMmrState ctrlmmr;
+ TIK3CtrlMmrState mcu_ctrlmmr;
+ TIK3CtrlMmrState sec_ctrlmmr;
+ TIK3DdrssState ddrss;
+ TIK3TrngState trng;
+ TIK3DmTimerState main_timer0;
+ TIK3GtcState gtc;
+ SDHCIState sdhci[TI_AM64X_SDHCI_NUM];
+ TIK3SdhciPhyState sdhci_phy[TI_AM64X_SDHCI_NUM];
+ /* MAIN domain I2C0, an OMAP-I2C V2 controller. */
+ DeviceState *i2c0;
+ uint64_t main_ram_base;
+ uint64_t main_ram_size;
+ uint8_t a53_cpus;
+ bool a53_start_powered_off;
+ bool m4_start_powered_off;
+ bool r5_start_powered_off;
+};
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 13/14] hw/arm: add the am64-virt machine
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (11 preceding siblings ...)
2026-08-20 12:48 ` [RFC PATCH v2 12/14] hw/arm: add TI AM64x SoC model Wadim Mueller
@ 2026-08-20 12:48 ` Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 14/14] tests: add AM64x unit, qtest and functional tests Wadim Mueller
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
Add a board around the AM64x SoC model. Besides the SoC it wires up a
PL011 pair, a PL031 RTC, PL061 GPIO, CFI flash, a GPEX PCIe host bridge
and an SD card, so that it is usable both for direct kernel boot and for
running the full TI boot chain from a .wic image via -bios.
A generated device tree matching the machine is added under pc-bios/dtb
for convenience, together with documentation and a MAINTAINERS entry.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
MAINTAINERS | 22 +++
docs/system/arm/am64.rst | 74 ++++++++
docs/system/target-arm.rst | 1 +
hw/arm/Kconfig | 9 +
hw/arm/am64-virt.c | 347 +++++++++++++++++++++++++++++++++++++
hw/arm/meson.build | 1 +
pc-bios/dtb/am64-virt.dtb | Bin 0 -> 6116 bytes
pc-bios/dtb/am64-virt.dts | 292 +++++++++++++++++++++++++++++++
8 files changed, 746 insertions(+)
create mode 100644 docs/system/arm/am64.rst
create mode 100644 hw/arm/am64-virt.c
create mode 100644 pc-bios/dtb/am64-virt.dtb
create mode 100644 pc-bios/dtb/am64-virt.dts
diff --git a/MAINTAINERS b/MAINTAINERS
index b51f5c3e60..dd0a3ee66a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1266,6 +1266,28 @@ F: hw/arm/msf2-som.c
F: docs/system/arm/emcraft-sf2.rst
F: tests/functional/arm/test_emcraft_sf2.py
+TI AM64x / K3
+M: Wadim Mueller <wafgo01@gmail.com>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/arm/am64-virt.c
+F: hw/arm/ti-am64x.c
+F: hw/arm/k3-bootrom*.c
+F: hw/char/ti-am64-uart.c
+F: hw/misc/ti-*.c
+F: hw/timer/ti-k3-dmtimer.c
+F: include/hw/arm/ti-am64x.h
+F: include/hw/arm/k3-bootrom.h
+F: include/hw/char/ti-am64-uart.h
+F: include/hw/misc/ti-*.h
+F: include/hw/timer/ti-k3-dmtimer.h
+F: pc-bios/dtb/am64-virt.dt[sb]
+F: tests/functional/aarch64/test_am64_bootrom.py
+F: tests/qtest/am64-*.c
+F: tests/qtest/ti-am64-i2c-test.c
+F: tests/unit/test-k3-bootrom.c
+F: docs/system/arm/am64.rst
+
ASPEED BMCs
M: Cédric Le Goater <clg@kaod.org>
M: Peter Maydell <peter.maydell@linaro.org>
diff --git a/docs/system/arm/am64.rst b/docs/system/arm/am64.rst
new file mode 100644
index 0000000000..cc2483b839
--- /dev/null
+++ b/docs/system/arm/am64.rst
@@ -0,0 +1,74 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+.. Copyright (c) 2026 CMBLU Energy AG
+
+Texas Instruments AM64x (``am64-virt``)
+======================================
+
+The ``am64-virt`` machine models a board built around the Texas Instruments
+AM64x (AM6442) Sitara SoC. The AM64x is a heterogeneous multi-core device;
+the model implements enough of it to run the stock TI boot chain
+(ROM boot -> R5 SPL -> TF-A/OP-TEE -> U-Boot -> Linux).
+
+Implemented CPU cores
+---------------------
+
+* Cortex-A53 cluster (2 cores, GICv3)
+* Cortex-R5F ``MCU_R5FSS0_CORE0`` boot core
+* Cortex-M4F ``MCU_M4FSS0_CORE0``
+
+Only one R5F core is modelled today; ``max_cpus`` reserves room for the
+remaining R5F cores of both clusters.
+
+Implemented devices
+-------------------
+
+* GICv3 (GIC-500) at the real AM64x addresses
+* MCU and MAIN domain UARTs (8250-compatible)
+* TI mailbox (IPC) blocks
+* TI secure proxy and the DMSC (TI-SCI system controller firmware) model
+* RAT (region address translation) for the R5F/M4F views of the memory map
+* CTRL_MMR (including the DEVSTAT boot pins), GTC, DDRSS, SDHCI PHY and TRNG
+* K3 DMTimer
+* SDHCI controllers (MMC1/eMMC and MMC2/SD)
+* OMAP-style MAIN I2C0 (no slaves; attach them with -device)
+* OCSRAM, DDR and a small set of virtio/PCIe/PL011/PL031/PL061 conveniences
+
+Boot modes
+----------
+
+Direct kernel boot::
+
+ qemu-system-aarch64 -M am64-virt -nographic \
+ -kernel Image -append "console=ttyAMA0" \
+ -dtb pc-bios/dtb/am64-virt.dtb
+
+ROM boot from a TI combined boot image (``tiboot3.bin``). The image is parsed
+the way the on-chip boot ROM does (X.509 certificate with the TI boot
+extension, followed by the individual components), the SYSFW component is
+handed to the DMSC model and the R5 SPL is started at the certified entry
+point::
+
+ qemu-system-aarch64 -M am64-virt -nographic \
+ -bios tiboot3.bin \
+ -drive if=sd,format=raw,file=core-image.wic
+
+Boot only the M4F core::
+
+ qemu-system-aarch64 -M am64-virt -M m4boot-cpu=0 -kernel m4-firmware.elf
+
+Machine options
+---------------
+
+``m4boot-cpu``
+ Set to ``0`` to hold the A53 and R5F cores in reset and boot only the M4F
+ core. Mutually exclusive with ``-bios``.
+
+Caveats
+-------
+
+Migration is not supported: the new device models have no VMState yet.
+
+Because the SoC realizes M4F and R5F vCPUs in addition to the A53s, every
+core needs a TCG context slot inside ``smp.max_cpus``. The machine therefore
+defaults to the full vCPU budget; if you pass ``-smp`` explicitly you must
+size ``maxcpus`` accordingly, for example ``-smp cpus=2,maxcpus=7``.
diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst
index e34492402f..2fd41111e4 100644
--- a/docs/system/target-arm.rst
+++ b/docs/system/target-arm.rst
@@ -71,6 +71,7 @@ Board-specific documentation
.. toctree::
:maxdepth: 1
+ arm/am64
arm/max78000
arm/integratorcp
arm/mps2
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 2172fc803b..3802170287 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -38,6 +38,15 @@ config ARM_VIRT
select ACPI_HMAT
select WDT_SBSA
+config AM64_VIRT
+ bool
+ default y
+ depends on TCG && ARM
+ select AT24C # I2C EEPROMs can be attached with -device
+ select TI_AM64X
+ select TI_DMSC
+ select TI_SEC_PROXY
+
config CUBIEBOARD
bool
default y
diff --git a/hw/arm/am64-virt.c b/hw/arm/am64-virt.c
new file mode 100644
index 0000000000..e78c73a347
--- /dev/null
+++ b/hw/arm/am64-virt.c
@@ -0,0 +1,347 @@
+/*
+ * AM64 virt machine model
+ *
+ * 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/core/boards.h"
+#include "hw/core/sysbus.h"
+#include "hw/arm/boot.h"
+#include "hw/arm/machines-qom.h"
+#include "hw/char/pl011.h"
+#include "hw/block/flash.h"
+#include "hw/rtc/pl031.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/pci-host/gpex.h"
+#include "hw/pci/pci.h"
+#include "hw/arm/ti-am64x.h"
+#include "hw/arm/k3-bootrom.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/sd/sd.h"
+#include "system/address-spaces.h"
+#include "system/system.h"
+#include "system/blockdev.h"
+#include "qemu/error-report.h"
+#include "chardev/char.h"
+#include "qemu/units.h"
+#include "qemu/datadir.h"
+#include "qapi/visitor.h"
+
+#define AM64_VIRT_DRAM_BASE 0x80000000ULL
+#define AM64_VIRT_UART0_BASE 0x09000000ULL
+#define AM64_VIRT_UART1_BASE 0x09040000ULL
+#define AM64_VIRT_UART0_IRQ 1
+#define AM64_VIRT_UART1_IRQ 8
+#define AM64_VIRT_RTC_BASE 0x09010000ULL
+#define AM64_VIRT_RTC_IRQ 2
+#define AM64_VIRT_GPIO_BASE 0x09030000ULL
+#define AM64_VIRT_GPIO_IRQ 7
+#define AM64_VIRT_FLASH_BASE 0x050000000ULL
+#define AM64_VIRT_FLASH_SIZE 0x08000000ULL
+#define AM64_VIRT_FLASH_SECTOR_SIZE (256 * KiB)
+#define AM64_VIRT_PCIE_MMIO_BASE 0x68000000ULL
+#define AM64_VIRT_PCIE_MMIO_SIZE 0x08000000ULL
+#define AM64_VIRT_PCIE_PIO_BASE 0x3EFF0000ULL
+#define AM64_VIRT_PCIE_PIO_SIZE 0x00010000ULL
+#define AM64_VIRT_PCIE_ECAM_BASE 0x0D000000ULL
+#define AM64_VIRT_PCIE_ECAM_SIZE 0x02000000ULL
+#define AM64_VIRT_PCIE_IRQ_BASE 3
+
+#define SYSCLK_FRQ 168000000ULL
+
+typedef struct AM64VirtMachineState {
+ MachineState parent_obj;
+ TIAM64xState *soc;
+ struct arm_boot_info bootinfo;
+ int32_t m4boot_cpu;
+} AM64VirtMachineState;
+
+#define TYPE_AM64_VIRT_MACHINE MACHINE_TYPE_NAME("am64-virt")
+OBJECT_DECLARE_SIMPLE_TYPE(AM64VirtMachineState, AM64_VIRT_MACHINE)
+
+static void am64_virt_get_m4boot_cpu(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ AM64VirtMachineState *ams = AM64_VIRT_MACHINE(obj);
+ int32_t value = ams->m4boot_cpu;
+
+ visit_type_int32(v, name, &value, errp);
+}
+
+static void am64_virt_set_m4boot_cpu(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ AM64VirtMachineState *ams = AM64_VIRT_MACHINE(obj);
+ int32_t value;
+
+ if (!visit_type_int32(v, name, &value, errp)) {
+ return;
+ }
+ if (value != -1 && value != 0) {
+ error_setg(errp, "am64-virt: m4boot_cpu must be -1 or 0");
+ return;
+ }
+ ams->m4boot_cpu = value;
+}
+
+static void am64_virt_machine_instance_init(Object *obj)
+{
+ AM64VirtMachineState *ams = AM64_VIRT_MACHINE(obj);
+
+ ams->m4boot_cpu = -1;
+ object_property_add_alias(obj, "m4boot_cpu", obj, "m4boot-cpu");
+}
+
+static void am64_virt_create_uart(hwaddr base, int irq, Chardev *chr,
+ DeviceState *gic)
+{
+ DeviceState *dev = qdev_new(TYPE_PL011);
+ SysBusDevice *s = SYS_BUS_DEVICE(dev);
+ MemoryRegion *sysmem = get_system_memory();
+
+ qdev_prop_set_chr(dev, "chardev", chr);
+ sysbus_realize_and_unref(s, &error_fatal);
+ memory_region_add_subregion(sysmem, base, sysbus_mmio_get_region(s, 0));
+ sysbus_connect_irq(s, 0, qdev_get_gpio_in(gic, irq));
+}
+
+static void am64_virt_create_pcie(const AM64VirtMachineState *ams,
+ DeviceState *gic)
+{
+ DeviceState *dev = qdev_new(TYPE_GPEX_HOST);
+ PCIHostState *pci;
+ MemoryRegion *mmio_alias;
+ MemoryRegion *ecam_alias;
+ MemoryRegion *mmio_reg;
+ MemoryRegion *ecam_reg;
+ int i;
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ ecam_alias = g_new0(MemoryRegion, 1);
+ ecam_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
+ memory_region_init_alias(ecam_alias, OBJECT(dev), "pcie-ecam",
+ ecam_reg, 0, AM64_VIRT_PCIE_ECAM_SIZE);
+ memory_region_add_subregion(get_system_memory(),
+ AM64_VIRT_PCIE_ECAM_BASE, ecam_alias);
+
+ mmio_alias = g_new0(MemoryRegion, 1);
+ mmio_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1);
+ memory_region_init_alias(mmio_alias, OBJECT(dev), "pcie-mmio",
+ mmio_reg, AM64_VIRT_PCIE_MMIO_BASE,
+ AM64_VIRT_PCIE_MMIO_SIZE);
+ memory_region_add_subregion(get_system_memory(),
+ AM64_VIRT_PCIE_MMIO_BASE, mmio_alias);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, AM64_VIRT_PCIE_PIO_BASE);
+
+ for (i = 0; i < PCI_NUM_PINS; i++) {
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), i,
+ qdev_get_gpio_in(gic,
+ AM64_VIRT_PCIE_IRQ_BASE + i));
+ gpex_set_irq_num(GPEX_HOST(dev), i, AM64_VIRT_PCIE_IRQ_BASE + i);
+ }
+
+ pci = PCI_HOST_BRIDGE(dev);
+ pci_init_nic_devices(pci->bus, MACHINE_GET_CLASS(ams)->default_nic);
+}
+
+static void am64_virt_create_rtc(hwaddr base, int irq, DeviceState *gic)
+{
+ sysbus_create_simple(TYPE_PL031, base, qdev_get_gpio_in(gic, irq));
+}
+
+static void am64_virt_create_gpio(hwaddr base, int irq, DeviceState *gic)
+{
+ sysbus_create_simple("pl061", base, qdev_get_gpio_in(gic, irq));
+}
+
+static void am64_virt_create_flash(void)
+{
+ DeviceState *dev = qdev_new(TYPE_PFLASH_CFI01);
+
+ qdev_prop_set_uint32(dev, "num-blocks",
+ AM64_VIRT_FLASH_SIZE / AM64_VIRT_FLASH_SECTOR_SIZE);
+ qdev_prop_set_uint64(dev, "sector-length", AM64_VIRT_FLASH_SECTOR_SIZE);
+ qdev_prop_set_uint8(dev, "width", 4);
+ qdev_prop_set_uint8(dev, "device-width", 2);
+ qdev_prop_set_bit(dev, "big-endian", false);
+ qdev_prop_set_uint16(dev, "id0", 0x89);
+ qdev_prop_set_uint16(dev, "id1", 0x18);
+ qdev_prop_set_uint16(dev, "id2", 0x00);
+ qdev_prop_set_uint16(dev, "id3", 0x00);
+ qdev_prop_set_string(dev, "name", "am64-virt.flash0");
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, AM64_VIRT_FLASH_BASE);
+}
+
+static void am64_virt_init(MachineState *machine)
+{
+ AM64VirtMachineState *ams = AM64_VIRT_MACHINE(machine);
+ DeviceState *soc = qdev_new(TYPE_TI_AM64X);
+ DeviceState *gic;
+ Clock *sysclk = clock_new(OBJECT(machine), "SYSCLK");
+ Chardev *mcu_chardev;
+ uint8_t a53_cpus = MIN(machine->smp.cpus, TI_AM64X_A53_NUM);
+ DriveInfo *sd_di = drive_get(IF_SD, 0, 0);
+
+ /*
+ * The SoC has M4 and R5F vCPUs beyond the A53s. Since TCG allocates
+ * context slots from smp.max_cpus at accelerator init, fail before realize.
+ */
+ if (machine->smp.max_cpus < a53_cpus + 2) {
+ error_report("am64-virt: -smp maxcpus=%d is too small for %d A53 "
+ "+ M4 + R5F vCPUs; raise maxcpus (e.g. "
+ "-smp cpus=2,maxcpus=7) or omit -smp",
+ machine->smp.max_cpus, a53_cpus);
+ exit(1);
+ }
+
+ clock_set_hz(sysclk, SYSCLK_FRQ);
+ qdev_prop_set_uint8(soc, "a53-cpus", a53_cpus);
+ qdev_prop_set_uint64(soc, "ram-base", AM64_VIRT_DRAM_BASE);
+ qdev_prop_set_uint64(soc, "ram-size", machine->ram_size);
+ qdev_connect_clock_in(soc, "sysclk", sysclk);
+ mcu_chardev = qemu_chr_find("uart0");
+ if (mcu_chardev) {
+ qdev_prop_set_chr(DEVICE(&TI_AM64X(soc)->mcu_uart[0]),
+ "chardev", mcu_chardev);
+ }
+ if (ams->m4boot_cpu >= 0) {
+ if (ams->m4boot_cpu != 0) {
+ error_report("am64-virt: m4boot_cpu only supports value 0");
+ exit(1);
+ }
+ qdev_prop_set_bit(soc, "a53-start-powered-off", true);
+ qdev_prop_set_bit(soc, "m4-start-powered-off", false);
+ }
+
+ if (machine->firmware && ams->m4boot_cpu >= 0) {
+ error_report("am64-virt: -bios and m4boot-cpu are mutually "
+ "exclusive");
+ exit(1);
+ }
+ if (machine->firmware && machine->kernel_filename) {
+ /*
+ * ROM boot consumes -bios via k3_bootrom_load() and never calls
+ * arm_load_kernel(), therefore reject a silently ignored -kernel.
+ */
+ error_report("am64-virt: -bios and -kernel are mutually exclusive");
+ exit(1);
+ }
+ if (machine->firmware) {
+ /* ROM boot starts only R5F boot core. */
+ qdev_prop_set_bit(soc, "a53-start-powered-off", true);
+ qdev_prop_set_bit(soc, "m4-start-powered-off", true);
+ qdev_prop_set_bit(soc, "r5-start-powered-off", false);
+ qdev_prop_set_chr(DEVICE(&TI_AM64X(soc)->main_uart0),
+ "chardev", serial_hd(0));
+ if (sd_di) {
+ /*
+ * DEVSTAT primary bootmode MMC (0x8), SD port: describe MMC2
+ * (sdhci[1]) FS-mode boot, not MMC1 raw mode.
+ */
+ qdev_prop_set_uint32(DEVICE(&TI_AM64X(soc)->ctrlmmr), "devstat",
+ 0x240);
+ }
+ }
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(soc), &error_fatal);
+ ams->soc = TI_AM64X(soc);
+ gic = DEVICE(&ams->soc->gic);
+
+ if (sd_di) {
+ DeviceState *card = qdev_new(TYPE_SD_CARD);
+
+ qdev_prop_set_drive_err(card, "drive", blk_by_legacy_dinfo(sd_di),
+ &error_fatal);
+ qdev_realize_and_unref(card,
+ qdev_get_child_bus(DEVICE(&ams->soc->sdhci[1]),
+ "sd-bus"),
+ &error_fatal);
+ }
+
+ memory_region_add_subregion(get_system_memory(), AM64_VIRT_DRAM_BASE,
+ machine->ram);
+
+ am64_virt_create_uart(AM64_VIRT_UART0_BASE, AM64_VIRT_UART0_IRQ,
+ machine->firmware ? serial_hd(1) : serial_hd(0),
+ gic);
+ am64_virt_create_uart(AM64_VIRT_UART1_BASE, AM64_VIRT_UART1_IRQ,
+ machine->firmware ? serial_hd(2) : serial_hd(1),
+ gic);
+ am64_virt_create_rtc(AM64_VIRT_RTC_BASE, AM64_VIRT_RTC_IRQ, gic);
+ am64_virt_create_gpio(AM64_VIRT_GPIO_BASE, AM64_VIRT_GPIO_IRQ, gic);
+ am64_virt_create_flash();
+ am64_virt_create_pcie(ams, gic);
+
+ memset(&ams->bootinfo, 0, sizeof(ams->bootinfo));
+ ams->bootinfo.ram_size = machine->ram_size;
+ ams->bootinfo.loader_start = AM64_VIRT_DRAM_BASE;
+ ams->bootinfo.board_id = -1;
+ ams->bootinfo.psci_conduit = QEMU_PSCI_CONDUIT_SMC;
+ g_assert(qemu_get_cpu(0));
+ if (machine->firmware) {
+ g_autofree char *fn =
+ qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware);
+
+ k3_bootrom_load(ams->soc, fn ? fn : machine->firmware,
+ &error_fatal);
+ } else if (ams->m4boot_cpu < 0) {
+ arm_load_kernel(ARM_CPU(qemu_get_cpu(0)), machine, &ams->bootinfo);
+ }
+}
+
+static void am64_virt_machine_class_init(ObjectClass *oc, const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->desc = "AM64 virt machine";
+ mc->init = am64_virt_init;
+ mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a53");
+ mc->default_nic = "virtio-net-pci";
+ mc->default_ram_id = "am64-virt.ram";
+ /*
+ * Real AM64x has four R5F cores plus one M4 beyond the A53 cluster.
+ * Reserve max_cpus headroom for the not yet modeled R5F cores.
+ */
+ mc->max_cpus = TI_AM64X_A53_NUM + 4 + 1; /* 4 R5F cores, 1 M4 */
+ /*
+ * Default to the full heterogeneous vCPU budget, so plain invocations
+ * have enough TCG slots for A53, R5F and M4 cores.
+ */
+ mc->default_cpus = mc->max_cpus;
+ mc->default_ram_size = 2 * GiB;
+
+ object_class_property_add(oc, "m4boot-cpu", "int32",
+ am64_virt_get_m4boot_cpu,
+ am64_virt_set_m4boot_cpu,
+ NULL, NULL);
+ object_class_property_set_description(oc, "m4boot-cpu",
+ "Set to 0 to boot only the M4 core");
+}
+
+static const TypeInfo am64_virt_machine_info = {
+ .name = TYPE_AM64_VIRT_MACHINE,
+ .parent = TYPE_MACHINE,
+ .instance_size = sizeof(AM64VirtMachineState),
+ .instance_init = am64_virt_machine_instance_init,
+ .class_init = am64_virt_machine_class_init,
+ .interfaces = arm_aarch64_machine_interfaces,
+};
+
+static void am64_virt_machine_init_register_types(void)
+{
+ type_register_static(&am64_virt_machine_info);
+}
+
+type_init(am64_virt_machine_init_register_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 139692dd11..cb6e881d01 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -112,6 +112,7 @@ arm_common_ss.add(when: 'CONFIG_VEXPRESS', if_true: files('vexpress.c'))
arm_common_ss.add(when: 'CONFIG_TI_AM64X', if_true: files('ti-am64x.c'))
arm_common_ss.add(when: 'CONFIG_TI_AM64X', if_true: files('k3-bootrom-parse.c'))
arm_common_ss.add(when: 'CONFIG_TI_AM64X', if_true: files('k3-bootrom.c'))
+arm_common_ss.add(when: 'CONFIG_AM64_VIRT', if_true: files('am64-virt.c'))
arm_common_ss.add(when: ['CONFIG_AXIADO_SOC', 'TARGET_AARCH64'], if_true: files(
'ax3000-soc.c'))
diff --git a/pc-bios/dtb/am64-virt.dtb b/pc-bios/dtb/am64-virt.dtb
new file mode 100644
index 0000000000000000000000000000000000000000..b9cf2dd0b494ade59cd8cd4e4dc7b4d1434f7724
GIT binary patch
literal 6116
zcmb_gO^h5z6|SB*4hBLT9Kw$fj93F9xZ~ON*xnpAJCTS8jx8KT+=7<g?wXm__MiJ_
ze*%X+Sc=5K5?nxV1>qaHAPz_okq|<b4;+HT0fY}GHw1SC;rm`y_4NFJMc_#_RqxgN
ztM{tjRP}Ek{pVkl`sjV7)TUA&If4Euw8zm-qXByNlzQT>4CjA5i#~*&a)4uw*$C9Z
zG5b#?an>B3i<&e&^7rGi294lk&tlx`;DZiM`R#ur@0GgJ<-U&mRHu1)1lCq(R|oy;
z+92N*G%p>F{U{Br_$U|-1UOIM7=!I<8kDd=wj$4y?%c~ne#9SwiI`je*FbUWdlln{
z(YQvjY21aq_4i=U*iq_l&}M9~U$l1;G};rJMHR+m0yv+dUm2eqo&e|A;nmIgoO~1W
z%(<I?eYzhyeQOwO2W5JWcKr2c2C78{&up{^*=-CYU87AC?g8o4%aut?k1-&O<X7e%
z!SL2B%|_2|Q;mjJi(wUB4u60~ogYBkuHv*vv=4S*vo+8#<3qj^XlFY(hU&*?ockCW
z_ltUwKLFRC90}^=-vIV;G%pR}WH&z?4WrqH0-byW=i8GQZ`bj;AiZ>P=vO*yO6?bA
zekeP6=GU9xbFqH{<2!XwPISE!1$CfsnyPR-0a^5OO%lUvq!8X`x+~u>eT$FgaQem=
zzg-^g4AwVfj<gS=-4{%$&!A_V+<LjMDZ?1Dc|{V`<Gf56=~1|}cFF>dDbGe=T%ErE
zln=&r`-V=NbIyob$Z99<8_y0d(K-zPsQ)yYjrGvC`RUF{%)Y==Bf#Z|TzCCfLF=x+
zn-|jmIzR6*XS7TEIRD<6d@r!6ZP&~ga^6d^Vf=LHTc=5bZSNE49ndL9|18F?20J=^
zpg#7bv9aD%)#ze38PxG1v{^jJ0|J0^eKd&&i<SVXfQPA>U@VjRezZGr8Kf%Wj{xcP
zol7C-Dch5N2Bgc_@JK?~V7=qP&cGiZV8+;n1@8rnyK8y_ICW4SyV+wNcqZTN&|Dnp
zq2miN<`S4y(lDOOfyAdes%oA3VV>1xo+P>)4Tpo_1-E-BM{s9W>Iu&5-p0gtRh2yS
z`v!WfTWrCm_pS0!)rTNh8uo(c&J6i@b{@5JkD<R4>D^|slj^DpCVEz@AZPONr4tM(
z;jw%8v34crvX&x0&}C;S<OD3P;c?KIdsgcr^`ji;ArsD4r}sK=>J+_Ul81W_*2^_e
zuZ@i#CVQgQ#pj1zo&4{vAFPw{alVR(Xq@?Z+v@Dbf_u*SF8+@Xe3Z!1vxAMcD!7<1
z796)RK1B%w++&=RcuDOsiu^Q<bIT+8Cq<n5d-|wzhIlTv7wDekCm)ZkEOqmo{0udb
z`_%cwp8M$+=y_Kn{$b$1{_^#&e6|0QKI&g*qFY67t*F0)OY2xBcdy+kRu}h3YXfJ=
zyBid{JB7=Tu|b=^2OrFdP43bAL;&2Yn0G#Uf3joBu<p?Z+G5PiU4Xur|Nb&gb*PgB
zQ^GTgH6;W{GJCBEV?A2_-vopAccb1&;Fmr@L+==k^{fln1IMj)J3*Xl=mPv+;v>%@
zA7q#Kn6pJb;7fe0KcL$`Vq<-kf7G?ex5__jcTV3b|5n*u<)4j<<iL1`V6Rw*wL8bR
z%0Fv&j&GHJtL(1w&&GvYtA$nmS-W$5tNgQe=lE9nx61A+|J**XdV8OQm+(&!-$c7Y
zqzljSpNoJAI4Ce(=xuhXgMoaR(=3JrE5kX0t6>o7`G%xEjo$fl+0raNchBV-2bR~(
z-@+NB2ywZi0{5qRRr@*&Qh%Z|UB)3fj4P}=7g%@XIb-OQeJ^!}OQTPeZ`XaQ4WR5P
z&M|qe&RkegXQ+pMt<x_75CD`#yu7<jP}VGeE9xNP=<49Hlj+t%db8{!IR#ewxc(p?
z<?U${{6Bo;_F49k6oLQJ$F!jo{c>?&3{Za`>e`Te;OV!&iEBl(e}OWRhd8Ul(Ykr4
zkqDqJ%JP4SW41n;>~rrJBZAGlchjIEVR+>p#%Jav4t--vbda(8rZ@|nJ~(@W4$k3=
zvpG4p<P)FANl;DQy_r(2j^5WX?yg&WzAh|nm8TxL1BBz)7hPsvbccm~iMBfBOp>GP
zCs*%*7w?sVb6$mOn6AYcFH9O-Tub8_`SMa*#Omlx5nI|bHZi|WEv*firwzyBdfFV#
z>ykWi-uWkMwm9lvFIo7;Aa_CL2Y%K1&<`%kc4*~q!3M;wZ?HG-o7kgoU3(7yU+qae
zYFtW6dSGmDx%bg%17%kBHOIJDv}5eKc8uJ%dmn7Q*YHDM`)HkWV5~3>JQtV!)~fh`
z+T$<Z!DQ`FBl=K7dpA+1HiqexHV7`i-}ACd9o*yn+TlE>+vE5UpRVNgPP(y~#6e{q
z7u171^?q`Gxlp>fpSE+e0wsDv{2^%bY9Q}b==ph>=k?b4NbjHDkAi-A(C_1m&DO?X
zxOuIQ2Ue9QdaJIFUfJkk&tH3f(60{SyiRtv2K}Ik;(AMVpY!3Dtq-$8&bdBp-u>d*
znRnu+zEeN<^vV4{|KY9MZ{554<oCaQyq|sU_HUm4{Prv0BQNS<?{}yF@xmY9{O%QQ
zJg$N56l#+$%ciLP0w4OaTJcL?m`}AJX&&iBtyS@B+UFIvQfomJ;UQ94G|bZ?sN>y4
zt1`&=YAf|bWx1T$Vj5%-X3}_42DMh$^E-KFI=ljiR!vptEK;eir+K6hyExQ4^--bK
zE}~gX{Qwtd`efDHcN-Y}I8tl!*}!-P!+sXv^{8fRTEHs>aS_Ry)Nk<hbe=Mf$9biy
zI;c^g94lY3Rtm3Kd4yGJ9GB?<0wxwY&&ROo_z5e7bCsecxMM!s`S35s>cJ#;33zWj
z5trA@+ts{9U8wE5)5P6A7zN?Os#4~&aEKJCB-lls48K{kj4LHiu-W5Ytr>!7nS}`j
z7745NI+0_|U7K;35aYX+$%D1ci;;akcUap`32HaU_WXl5s;5l0(pi9wg3ry8>mWG@
zjw+uULf+STQx{EbQ)o!zQr~EF79J_2Dh`r??bl^<;42`L6+sZ27dFZaA0@Po)Lu{}
Hr5XMU#<@8L
literal 0
HcmV?d00001
diff --git a/pc-bios/dtb/am64-virt.dts b/pc-bios/dtb/am64-virt.dts
new file mode 100644
index 0000000000..09ab555c2e
--- /dev/null
+++ b/pc-bios/dtb/am64-virt.dts
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ */
+/dts-v1/;
+
+/ {
+ interrupt-parent = <0x8003>;
+ dma-coherent;
+ model = "linux,dummy-virt";
+ #size-cells = <0x02>;
+ #address-cells = <0x02>;
+ compatible = "linux,dummy-virt";
+
+ reserved-memory {
+ #address-cells = <0x02>;
+ #size-cells = <0x02>;
+ ranges;
+
+ m4f-dma-memory@a4000000 {
+ compatible = "shared-dma-pool";
+ reg = <0x00 0xa4000000 0x00 0x100000>;
+ no-map;
+ phandle = <0x05>;
+ };
+
+ m4f-memory@a4100000 {
+ compatible = "shared-dma-pool";
+ reg = <0x00 0xa4100000 0x00 0xf00000>;
+ no-map;
+ phandle = <0x06>;
+ };
+ };
+
+ psci {
+ migrate = <0xc4000005>;
+ cpu_on = <0xc4000003>;
+ cpu_off = <0x84000002>;
+ cpu_suspend = <0xc4000001>;
+ method = "hvc";
+ compatible = "arm,psci-1.0", "arm,psci-0.2", "arm,psci";
+ };
+
+ memory@80000000 {
+ reg = <0x00 0x80000000 0x00 0x40000000>;
+ device_type = "memory";
+ };
+
+ bus@48000000 {
+ bootph-all;
+ compatible = "simple-bus";
+ #address-cells = <0x02>;
+ #size-cells = <0x02>;
+ dma-ranges;
+ ranges = <0x00 0x48000000 0x00 0x48000000 0x00 0x6400000>;
+ ti,sci-dev-id = <0x19>;
+
+ mailbox@4d000000 {
+ bootph-all;
+ compatible = "ti,am654-secure-proxy";
+ #mbox-cells = <0x01>;
+ reg-names = "target_data", "rt", "scfg";
+ reg = <0x00 0x4d000000 0x00 0x80000 0x00 0x4a600000 0x00 0x80000 0x00 0x4a400000 0x00 0x80000>;
+ interrupt-names = "rx_012";
+ interrupts = <0x00 0x22 0x04>;
+ phandle = <0x07>;
+ };
+ };
+
+ platform-bus@c000000 {
+ interrupt-parent = <0x8003>;
+ ranges;
+ #address-cells = <0x02>;
+ #size-cells = <0x02>;
+ compatible = "qemu,platform", "simple-bus";
+
+ mailbox@29060000 {
+ compatible = "ti,am64-mailbox";
+ reg = <0x00 0x29060000 0x00 0x200>;
+ interrupts = <0x00 0x4c 0x04>;
+ #mbox-cells = <0x01>;
+ ti,mbox-num-users = <0x04>;
+ ti,mbox-num-fifos = <0x10>;
+ status = "okay";
+ phandle = <0x03>;
+
+ mbox-m4-0 {
+ ti,mbox-rx = <0x00 0x00 0x02>;
+ ti,mbox-tx = <0x01 0x00 0x02>;
+ phandle = <0x04>;
+ };
+ };
+
+ m4fss@5000000 {
+ compatible = "ti,am64-m4fss";
+ reg = <0x00 0x5000000 0x00 0x30000 0x00 0x5040000 0x00 0x10000>;
+ reg-names = "iram", "dram";
+ resets = <0x01 0x09 0x01>;
+ firmware-name = "am64-mcu-m4f0_0-fw";
+ ti,sci = <0x02>;
+ ti,sci-dev-id = <0x09>;
+ ti,sci-proc-ids = <0x18 0xff>;
+ mboxes = <0x03 0x04>;
+ memory-region = <0x05 0x06>;
+ status = "okay";
+ };
+
+ system-controller@44043000 {
+ bootph-all;
+ compatible = "ti,k2g-sci";
+ ti,host-id = <0x0c>;
+ mbox-names = "rx", "tx";
+ mboxes = <0x07 0x0c 0x07 0x0d>;
+ reg-names = "debug_messages";
+ reg = <0x00 0x44043000 0x00 0xfe0>;
+ phandle = <0x02>;
+
+ power-controller {
+ bootph-all;
+ compatible = "ti,sci-pm-domain";
+ #power-domain-cells = <0x02>;
+ };
+
+ clock-controller {
+ bootph-all;
+ compatible = "ti,k2g-sci-clk";
+ #clock-cells = <0x02>;
+ };
+
+ reset-controller {
+ bootph-all;
+ compatible = "ti,sci-reset";
+ #reset-cells = <0x02>;
+ phandle = <0x01>;
+ };
+ };
+ };
+
+ fw-cfg@9020000 {
+ dma-coherent;
+ reg = <0x00 0x9020000 0x00 0x18>;
+ compatible = "qemu,fw-cfg-mmio";
+ };
+
+ gpio-keys {
+ compatible = "gpio-keys";
+
+ poweroff {
+ gpios = <0x8005 0x03 0x00>;
+ linux,code = <0x74>;
+ label = "GPIO Key Poweroff";
+ };
+ };
+
+ pl061@9030000 {
+ phandle = <0x8005>;
+ clock-names = "apb_pclk";
+ clocks = <0x8000>;
+ interrupts = <0x00 0x07 0x04>;
+ gpio-controller;
+ #gpio-cells = <0x02>;
+ compatible = "arm,pl061", "arm,primecell";
+ reg = <0x00 0x9030000 0x00 0x1000>;
+ };
+
+ pcie@d000000 {
+ interrupt-map-mask = <0x1800 0x00 0x00 0x07>;
+ interrupt-map = <0x00 0x00 0x00 0x01 0x8003 0x00 0x00 0x00 0x03 0x04 0x00 0x00 0x00 0x02 0x8003 0x00 0x00 0x00 0x04 0x04 0x00 0x00 0x00 0x03 0x8003 0x00 0x00 0x00 0x05 0x04 0x00 0x00 0x00 0x04 0x8003 0x00 0x00 0x00 0x06 0x04 0x800 0x00 0x00 0x01 0x8003 0x00 0x00 0x00 0x04 0x04 0x800 0x00 0x00 0x02 0x8003 0x00 0x00 0x00 0x05 0x04 0x800 0x00 0x00 0x03 0x8003 0x00 0x00 0x00 0x06 0x04 0x800 0x00 0x00 0x04 0x8003 0x00 0x00 0x00 0x03 0x04 0x1000 0x00 0x00 0x01 0x8003 0x00 0x00 0x00 0x05 0x04 0x1000 0x00 0x00 0x02 0x8003 0x00 0x00 0x00 0x06 0x04 0x1000 0x00 0x00 0x03 0x8003 0x00 0x00 0x00 0x03 0x04 0x1000 0x00 0x00 0x04 0x8003 0x00 0x00 0x00 0x04 0x04 0x1800 0x00 0x00 0x01 0x8003 0x00 0x00 0x00 0x06 0x04 0x1800 0x00 0x00 0x02 0x8003 0x00 0x00 0x00 0x03 0x04 0x1800 0x00 0x00 0x03 0x8003 0x00 0x00 0x00 0x04 0x04 0x1800 0x00 0x00 0x04 0x8003 0x00 0x00 0x00 0x05 0x04>;
+ #interrupt-cells = <0x01>;
+ ranges = <0x1000000 0x00 0x00 0x00 0x3eff0000 0x00 0x10000 0x2000000 0x00 0x68000000 0x00 0x68000000 0x00 0x08000000>;
+ reg = <0x00 0x0d000000 0x00 0x02000000>;
+ dma-coherent;
+ bus-range = <0x00 0x1f>;
+ linux,pci-domain = <0x00>;
+ #size-cells = <0x02>;
+ #address-cells = <0x03>;
+ device_type = "pci";
+ compatible = "pci-host-ecam-generic";
+ };
+
+ pl031@9010000 {
+ clock-names = "apb_pclk";
+ clocks = <0x8000>;
+ interrupts = <0x00 0x02 0x04>;
+ reg = <0x00 0x9010000 0x00 0x1000>;
+ compatible = "arm,pl031", "arm,primecell";
+ };
+
+ pl011@9000000 {
+ clock-names = "uartclk", "apb_pclk";
+ clocks = <0x8000 0x8000>;
+ interrupts = <0x00 0x01 0x04>;
+ reg = <0x00 0x9000000 0x00 0x1000>;
+ compatible = "arm,pl011", "arm,primecell";
+ };
+
+ pl011@9040000 {
+ clock-names = "uartclk", "apb_pclk";
+ clocks = <0x8000 0x8000>;
+ interrupts = <0x00 0x08 0x04>;
+ reg = <0x00 0x9040000 0x00 0x1000>;
+ compatible = "arm,pl011", "arm,primecell";
+ };
+
+ pmu {
+ interrupts = <0x01 0x07 0x304>;
+ compatible = "arm,armv8-pmuv3";
+ };
+
+ intc@1800000 {
+ phandle = <0x8003>;
+ reg = <0x00 0x1800000 0x00 0x10000
+ 0x00 0x1840000 0x00 0x40000>;
+ compatible = "arm,gic-v3";
+ ranges;
+ #size-cells = <0x02>;
+ #address-cells = <0x02>;
+ interrupt-controller;
+ #interrupt-cells = <0x03>;
+ };
+
+
+ flash@50000000 {
+ bank-width = <0x04>;
+ reg = <0x00 0x50000000 0x00 0x8000000>;
+ compatible = "cfi-flash";
+ };
+ cpus {
+ #size-cells = <0x00>;
+ #address-cells = <0x01>;
+
+ cpu-map {
+
+ socket0 {
+
+ cluster0 {
+
+ core0 {
+ cpu = <0x8002>;
+ };
+
+ core1 {
+ cpu = <0x8001>;
+ };
+ };
+ };
+ };
+
+ cpu@0 {
+ phandle = <0x8002>;
+ reg = <0x00>;
+ enable-method = "psci";
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ };
+
+ cpu@1 {
+ phandle = <0x8001>;
+ reg = <0x01>;
+ enable-method = "psci";
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ };
+ };
+
+ timer {
+ interrupts = <0x01 0x0d 0x304 0x01 0x0e 0x304 0x01 0x0b 0x304 0x01 0x0a 0x304>;
+ always-on;
+ compatible = "arm,armv8-timer", "arm,armv7-timer";
+ };
+
+ apb-pclk {
+ phandle = <0x8000>;
+ clock-output-names = "clk24mhz";
+ clock-frequency = <0x16e3600>;
+ #clock-cells = <0x00>;
+ compatible = "fixed-clock";
+ };
+
+ aliases {
+ serial0 = "/pl011@9000000";
+ serial1 = "/pl011@9040000";
+ };
+
+ chosen {
+ bootargs = "root=/dev/vda rw mem=2048M console=ttyS2 console=ttyAMA0 swiotlb=0 audit=0";
+ stdout-path = "/pl011@9000000";
+ rng-seed = <0x2b2313e6 0x69289174 0x3e2a0676 0xe5dcc1a8 0xc1e04c16 0x988a8076 0x6e26a8c8 0x2a273f53>;
+ kaslr-seed = <0x10fd45dd 0xbe924a75>;
+ };
+};
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 14/14] tests: add AM64x unit, qtest and functional tests
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
` (12 preceding siblings ...)
2026-08-20 12:48 ` [RFC PATCH v2 13/14] hw/arm: add the am64-virt machine Wadim Mueller
@ 2026-08-20 12:48 ` Wadim Mueller
13 siblings, 0 replies; 15+ messages in thread
From: Wadim Mueller @ 2026-08-20 12:48 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé, Bin Meng,
Paolo Bonzini, Fabiano Rosas, Wadim Mueller
Cover the new code:
- tests/unit/test-k3-bootrom: combined boot image parser, including
malformed input.
- tests/qtest/am64-virt-test: machine topology, GICv3 redistributor
layout, SMP handling, DMSC TI-SCI request/response handling.
- tests/qtest/ti-am64-i2c-test: I2C soft reset, NACK on an absent
address and a transfer against the on-SoM EEPROM.
- tests/qtest/am64-adma-pacing-test: reproducer for the SDHCI ADMA
pacing fix earlier in this series; without the fix it fails.
- tests/functional/aarch64/test_am64_bootrom: builds a synthetic
combined boot image with a bare-metal R5 payload and checks that the
ROM boot path runs it. If QEMU_TEST_TIBOOT3 points at a real
tiboot3.bin, that is booted as well.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
tests/functional/aarch64/meson.build | 2 +
tests/functional/aarch64/test_am64_bootrom.py | 137 +++++
tests/qtest/am64-adma-pacing-test.c | 243 +++++++++
tests/qtest/am64-virt-test.c | 502 ++++++++++++++++++
tests/qtest/meson.build | 1 +
tests/qtest/ti-am64-i2c-test.c | 137 +++++
tests/unit/meson.build | 1 +
tests/unit/test-k3-bootrom.c | 263 +++++++++
8 files changed, 1286 insertions(+)
create mode 100644 tests/functional/aarch64/test_am64_bootrom.py
create mode 100644 tests/qtest/am64-adma-pacing-test.c
create mode 100644 tests/qtest/am64-virt-test.c
create mode 100644 tests/qtest/ti-am64-i2c-test.c
create mode 100644 tests/unit/test-k3-bootrom.c
diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build
index e81afd6c39..6962ae1da1 100644
--- a/tests/functional/aarch64/meson.build
+++ b/tests/functional/aarch64/meson.build
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
test_aarch64_timeouts = {
+ 'am64_bootrom' : 300,
'aspeed_ast2700a1' : 600,
'aspeed_ast2700a2' : 600,
'aspeed_ast2700fc' : 600,
@@ -26,6 +27,7 @@ tests_aarch64_system_quick = [
]
tests_aarch64_system_thorough = [
+ 'am64_bootrom',
'aspeed_ast2700a1',
'aspeed_ast2700a2',
'aspeed_ast2700fc',
diff --git a/tests/functional/aarch64/test_am64_bootrom.py b/tests/functional/aarch64/test_am64_bootrom.py
new file mode 100644
index 0000000000..2203ec7585
--- /dev/null
+++ b/tests/functional/aarch64/test_am64_bootrom.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+#
+# Boot-ROM emulation test for am64-virt machine: build a synthetic TI
+# combined boot image and check the R5 payload runs from the certified
+# entry. Optionally boots a vendor tiboot3.bin via QEMU_TEST_TIBOOT3.
+#
+# Copyright (c) 2026 CMBLU Energy AG
+# Author: Wadim Mueller <wafgo01@gmail.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+import struct
+
+from qemu_test import Asset, QemuSystemTest, wait_for_console_pattern
+from unittest import skipUnless
+
+# Checked-in DTB path; pc-bios/dtb/am64-virt.dtb does not get copied into
+# the build directory.
+SOURCE_DIR = os.path.normpath(
+ os.path.join(os.path.dirname(__file__), '..', '..', '..'))
+
+
+def der(tag, payload):
+ n = len(payload)
+ if n < 0x80:
+ hdr = bytes([tag, n])
+ else:
+ hdr = bytes([tag, 0x82, n >> 8, n & 0xff])
+ return hdr + payload
+
+
+def der_int(v):
+ out = v.to_bytes((v.bit_length() + 7) // 8 or 1, 'big')
+ if out[0] & 0x80:
+ out = b'\x00' + out
+ return der(0x02, out)
+
+
+EXT_BOOT_OID = bytes.fromhex('06092b0601040182260109')
+SHA512_OID = bytes.fromhex('0609608648016503040203')
+
+
+def component(ctype, core, opts, dest, size):
+ return der(0x30,
+ der_int(ctype) + der_int(core) + der_int(opts) +
+ der(0x04, dest.to_bytes(4, 'big')) + der_int(size) +
+ SHA512_OID + der(0x04, bytes(64)))
+
+
+# Bare-metal A32 stub, linked at 0x70000000: prints a magic string on
+# main UART0 (0x02800000, 16550 THR at offset 0), then parks.
+SBL_STUB = struct.pack(
+ '<10I',
+ 0xe59f001c, # ldr r0, [pc, #0x1c] ; r0 = 0x02800000
+ 0xe28f101c, # add r1, pc, #0x1c ; r1 = msg
+ 0xe4d12001, # loop: ldrb r2, [r1], #1
+ 0xe3520000, # cmp r2, #0
+ 0x0a000001, # beq hang
+ 0xe5802000, # str r2, [r0]
+ 0xeafffffa, # b loop
+ 0xeafffffe, # hang: b hang
+ 0x00000000, # (pad)
+ 0x02800000, # UART0 literal
+) + b'K3BOOTROM-OK\r\n\x00'
+
+
+def make_tiboot3():
+ sbl = SBL_STUB
+ sysfw = b'FAKE-SYSFW-PAYLOAD'
+ cfg = b'FAKE-CFG'
+ info = der(0x30,
+ der_int(len(sbl) + len(sysfw) + len(cfg)) + der_int(3) +
+ component(1, 16, 0, 0x70000000, len(sbl)) +
+ component(2, 0, 0, 0x44000, len(sysfw)) +
+ component(18, 0, 0, 0x7b000, len(cfg)))
+ ext = der(0x30, EXT_BOOT_OID + der(0x04, info))
+ cert = der(0x30, ext)
+ return cert + sbl + sysfw + cfg
+
+
+class Am64BootRom(QemuSystemTest):
+
+ # The gated Linux boot subtest can need about 90 s under TCG on a
+ # development host. 300 s leaves room for slower CI machines.
+ timeout = 300
+
+ def boot_bios(self, path):
+ self.set_machine('am64-virt')
+ self.vm.set_console()
+ self.vm.add_args('-bios', path)
+ self.vm.launch()
+
+ def test_synthetic_image(self):
+ path = os.path.join(self.workdir, 'tiboot3-synth.bin')
+ with open(path, 'wb') as f:
+ f.write(make_tiboot3())
+ self.boot_bios(path)
+ wait_for_console_pattern(self, 'K3BOOTROM-OK')
+
+ @skipUnless(os.getenv('QEMU_TEST_TIBOOT3'),
+ 'set QEMU_TEST_TIBOOT3=<path to tiboot3.bin>')
+ def test_vendor_tiboot3(self):
+ self.boot_bios(os.getenv('QEMU_TEST_TIBOOT3'))
+ # The SYSFW ABI line also proves boot notification plus TISCI
+ # VERSION before execution reaches the unmodelled DDR init.
+ wait_for_console_pattern(self, 'U-Boot SPL')
+ wait_for_console_pattern(self, 'SYSFW ABI:')
+
+ # Standalone arm64 netboot kernel. It has PL011 and GICv3 drivers but
+ # no initramfs, so expected rootfs panic comes after the milestones,
+ # which this test cares about.
+ ASSET_KERNEL = Asset(
+ ('http://ports.ubuntu.com/ubuntu-ports/dists/bionic-updates/main/'
+ 'installer-arm64/20101020ubuntu543.19/images/netboot/'
+ 'ubuntu-installer/arm64/linux'),
+ 'ce54f74ab0b15cfd13d1a293f2d27ffd79d8a85b7bb9bf21093ae9513864ac79')
+
+ def test_linux_gicv3(self):
+ kernel_path = self.ASSET_KERNEL.fetch()
+ dtb = os.path.join(SOURCE_DIR, 'pc-bios', 'dtb', 'am64-virt.dtb')
+ self.set_machine('am64-virt')
+ self.vm.set_console()
+ self.vm.add_args('-kernel', kernel_path,
+ '-dtb', dtb,
+ '-append', 'console=ttyAMA0 earlycon')
+ self.vm.launch()
+ wait_for_console_pattern(
+ self,
+ 'GICv3: CPU0: found redistributor 0 region '
+ '0:0x0000000001840000')
+ wait_for_console_pattern(self, 'CPU1: Booted secondary processor')
+ wait_for_console_pattern(self, 'ttyAMA0')
+
+
+if __name__ == '__main__':
+ QemuSystemTest.main()
diff --git a/tests/qtest/am64-adma-pacing-test.c b/tests/qtest/am64-adma-pacing-test.c
new file mode 100644
index 0000000000..7b503d86ed
--- /dev/null
+++ b/tests/qtest/am64-adma-pacing-test.c
@@ -0,0 +1,243 @@
+/*
+ * QTest for AM64x SDHCI ADMA2 virtual-clock pacing
+ *
+ * CMD18 over a 12-descriptor ADMA2 chain has to complete synchronously
+ * when no DMA-boundary interrupt is requested. A sliced transfer needs
+ * at least one SDHC_TRANSFER_DELAY virtual-clock step.
+ *
+ * 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 "libqtest.h"
+
+/*
+ * Keep the small SDHCI subset local; sdhci-internal.h is not usable from
+ * qtests, since it exposes SDHCIState internals.
+ */
+#define SDHC_ARGUMENT 0x08
+#define SDHC_TRNMOD 0x0C
+#define SDHC_TRNS_DMA 0x0001
+#define SDHC_TRNS_BLK_CNT_EN 0x0002
+#define SDHC_TRNS_READ 0x0010
+#define SDHC_TRNS_MULTI 0x0020
+#define SDHC_CMDREG 0x0E
+#define SDHC_CMD_DATA_PRESENT (1 << 5)
+#define SDHC_RSPREG0 0x10
+#define SDHC_PRNSTS 0x24
+#define SDHC_CARD_PRESENT 0x00010000
+#define SDHC_HOSTCTL 0x28
+#define SDHC_CTRL_ADMA2_32 0x10
+#define SDHC_BLKSIZE 0x04
+#define SDHC_CLKCON 0x2C
+#define SDHC_CLOCK_INT_EN 0x0001
+#define SDHC_CLOCK_SDCLK_EN (1 << 2)
+#define SDHC_SWRST 0x2F
+#define SDHC_RESET_ALL 0x01
+#define SDHC_NORINTSTS 0x30
+#define SDHC_NIS_ERR 0x8000
+#define SDHC_NIS_CMDCMP 0x0001
+#define SDHC_NIS_TRSCMP 0x0002
+#define SDHC_NORINTSTSEN 0x34
+#define SDHC_ERRINTSTSEN 0x36
+#define SDHC_ADMASYSADDR 0x58
+#define SDHC_ADMA_ATTR_ACT_TRAN (1 << 5)
+#define SDHC_ADMA_ATTR_END (1 << 1)
+#define SDHC_ADMA_ATTR_VALID (1 << 0)
+#define SDHC_TRANSFER_DELAY 100
+/* NDESC stays above SDHC_ADMA_DESCS_PER_DELAY (5). */
+
+/*
+ * Probe both AM64x SDHCI instances. The first free sd-bus gets the
+ * test card, so the base with CARD_PRESENT depends from realize order.
+ */
+#define SDHCI_SD_BASE 0x0fa00000ULL
+#define SDHCI_EMMC_BASE 0x0fa10000ULL
+
+/* Scratch DDR: ADMA descriptor table plus target buffer. */
+#define ADMA_TABLE_ADDR 0x82000000ULL
+#define ADMA_BUF_ADDR 0x82100000ULL
+
+#define BLK_LEN 512
+#define NDESC 12 /* > SDHC_ADMA_DESCS_PER_DELAY (5) */
+
+/* SDHCI command-register response-type encodings (CMDREG bits [1:0]). */
+#define RESP_NONE 0x0000
+#define RESP_R2 0x0001 /* 136-bit (CID/CSD) */
+#define RESP_R48 0x0002 /* 48-bit (R1/R3/R6/R7) */
+#define RESP_R1B 0x0003 /* 48-bit with busy (R1b) */
+
+/* ACMD41 argument: HCS (bit30) + a 3.3-3.6V voltage window (non-enquiry). */
+#define ACMD41_ARG 0x40FF8000u
+/* CMD8 argument: VHS = 2.7-3.6V + recommended check pattern 0xAA. */
+#define CMD8_ARG 0x000001AAu
+
+/* Bounded command-complete poll; returns actual latched NORINTSTS. */
+static uint16_t sd_cmd(QTestState *qts, uint64_t base, uint8_t cmd,
+ uint32_t arg, uint16_t flags)
+{
+ uint16_t sts = 0;
+ int i;
+
+ qtest_writel(qts, base + SDHC_ARGUMENT, arg);
+ qtest_writew(qts, base + SDHC_CMDREG, ((uint16_t)cmd << 8) | flags);
+
+ /* CMDREG write runs synchronously sdhci_send_command(). */
+ for (i = 0; i < 1000; i++) {
+ sts = qtest_readw(qts, base + SDHC_NORINTSTS);
+ if (sts & SDHC_NIS_CMDCMP) {
+ break;
+ }
+ }
+ g_assert_cmphex(sts & SDHC_NIS_CMDCMP, ==, SDHC_NIS_CMDCMP);
+
+ /* Clear all latched normal-interrupt status bits (write-1-to-clear). */
+ qtest_writew(qts, base + SDHC_NORINTSTS, 0xffff);
+ return sts;
+}
+
+/* Return the controller base whose PRNSTS reports a card inserted. */
+static uint64_t find_card_base(QTestState *qts)
+{
+ uint32_t sd_sts = qtest_readl(qts, SDHCI_SD_BASE + SDHC_PRNSTS);
+ uint32_t emmc_sts = qtest_readl(qts, SDHCI_EMMC_BASE + SDHC_PRNSTS);
+
+ if (sd_sts & SDHC_CARD_PRESENT) {
+ return SDHCI_SD_BASE;
+ }
+ if (emmc_sts & SDHC_CARD_PRESENT) {
+ return SDHCI_EMMC_BASE;
+ }
+ g_assert_not_reached();
+}
+
+/* Bring SD card to transfer state. */
+static void sd_bring_up_card(QTestState *qts, uint64_t base)
+{
+ uint32_t rca;
+
+ /* Host reset, clock on (INT_EN sets INT_STABLE), latch all status. */
+ qtest_writeb(qts, base + SDHC_SWRST, SDHC_RESET_ALL);
+ qtest_writew(qts, base + SDHC_CLKCON,
+ SDHC_CLOCK_INT_EN | SDHC_CLOCK_SDCLK_EN);
+ qtest_writew(qts, base + SDHC_NORINTSTSEN, 0xffff);
+ qtest_writew(qts, base + SDHC_ERRINTSTSEN, 0xffff);
+
+ sd_cmd(qts, base, 0, 0x00000000, RESP_NONE); /* CMD0 GO_IDLE_STATE */
+ sd_cmd(qts, base, 8, CMD8_ARG, RESP_R48); /* CMD8 SEND_IF_COND */
+
+ /* ACMD41: CMD55 (APP_CMD) then CMD41 (SEND_OP_COND), until powered up. */
+ for (int i = 0; i < 100; i++) {
+ sd_cmd(qts, base, 55, 0x00000000, RESP_R48);
+ sd_cmd(qts, base, 41, ACMD41_ARG, RESP_R48);
+ /* Non-enquiry ACMD41 powers up the model; the loop is a guard. */
+ break;
+ }
+
+ sd_cmd(qts, base, 2, 0x00000000, RESP_R2); /* CMD2 ALL_SEND_CID */
+ sd_cmd(qts, base, 3, 0x00000000, RESP_R48); /* CMD3 SEND_RELATIVE */
+
+ /* R6 packs the assigned RCA in the upper 16 bits of RSPREG0. */
+ rca = qtest_readl(qts, base + SDHC_RSPREG0) >> 16;
+
+ sd_cmd(qts, base, 7, rca << 16, RESP_R1B); /* CMD7 SELECT_CARD */
+ sd_cmd(qts, base, 16, BLK_LEN, RESP_R48); /* CMD16 SET_BLOCKLEN */
+}
+
+/*
+ * 32-bit ADMA2 entries are little-endian 64-bit words: attr in bits
+ * [6:0], length in [31:16], address in [63:32]. No INT bit is set,
+ * so we expect the whole chain in one call.
+ */
+static void build_adma_table(QTestState *qts)
+{
+ for (int i = 0; i < NDESC; i++) {
+ uint8_t attr = SDHC_ADMA_ATTR_VALID | SDHC_ADMA_ATTR_ACT_TRAN;
+ uint32_t addr = (uint32_t)(ADMA_BUF_ADDR + (uint64_t)i * BLK_LEN);
+
+ if (i == NDESC - 1) {
+ attr |= SDHC_ADMA_ATTR_END;
+ }
+ qtest_writel(qts, ADMA_TABLE_ADDR + (uint64_t)i * 8,
+ ((uint32_t)BLK_LEN << 16) | attr);
+ qtest_writel(qts, ADMA_TABLE_ADDR + (uint64_t)i * 8 + 4, addr);
+ }
+}
+
+static void test_adma_pacing(void)
+{
+ char *tmp = NULL;
+ int fd;
+ GError *err = NULL;
+ uint64_t base;
+ QTestState *qts;
+ uint16_t sts;
+ int steps = 0;
+
+ /* 1 MiB raw backing file, filled with zero. */
+ fd = g_file_open_tmp("am64-adma-XXXXXX.raw", &tmp, &err);
+ g_assert_no_error(err);
+ g_assert_cmpint(fd, >=, 0);
+ g_assert_cmpint(ftruncate(fd, 1 * 1024 * 1024), ==, 0);
+ close(fd);
+
+ qts = qtest_initf("-machine am64-virt -accel qtest -display none "
+ "-drive if=none,id=sd0,file=%s,format=raw "
+ "-device sd-card,drive=sd0", tmp);
+
+ base = find_card_base(qts);
+ sd_bring_up_card(qts, base);
+
+ /* Select ADMA2 32-bit as DMA engine (Host Control 1 bits [4:3]). */
+ qtest_writeb(qts, base + SDHC_HOSTCTL, SDHC_CTRL_ADMA2_32);
+
+ build_adma_table(qts);
+
+ /* Program descriptor/geometry and clear old status. */
+ qtest_writel(qts, base + SDHC_ADMASYSADDR, (uint32_t)ADMA_TABLE_ADDR);
+ qtest_writel(qts, base + SDHC_ADMASYSADDR + 4,
+ (uint32_t)(ADMA_TABLE_ADDR >> 32));
+ qtest_writel(qts, base + SDHC_BLKSIZE, BLK_LEN | (NDESC << 16));
+ qtest_writew(qts, base + SDHC_TRNMOD,
+ SDHC_TRNS_DMA | SDHC_TRNS_BLK_CNT_EN |
+ SDHC_TRNS_READ | SDHC_TRNS_MULTI);
+ qtest_writel(qts, base + SDHC_ARGUMENT, 0); /* start block 0 */
+ qtest_writew(qts, base + SDHC_NORINTSTS, 0xffff);
+
+ /* CMD18 READ_MULTIPLE_BLOCK kicks off ADMA. */
+ qtest_writew(qts, base + SDHC_CMDREG,
+ (18 << 8) | SDHC_CMD_DATA_PRESENT | RESP_R48);
+
+ /*
+ * Transfer Complete has to be already set. A batched 12-descriptor
+ * chain needs at least one SDHC_TRANSFER_DELAY step.
+ */
+ while (!((sts = qtest_readw(qts, base + SDHC_NORINTSTS)) &
+ SDHC_NIS_TRSCMP)) {
+ qtest_clock_step(qts, SDHC_TRANSFER_DELAY);
+ steps++;
+ g_assert_cmpint(steps, <, 1000);
+ }
+
+ /* Completion has to be Transfer Complete with no error interrupt. */
+ g_assert_cmphex(sts & SDHC_NIS_TRSCMP, ==, SDHC_NIS_TRSCMP);
+ g_assert_cmphex(sts & SDHC_NIS_ERR, ==, 0);
+
+ g_test_message("ADMA %d descriptors: %d clock steps", NDESC, steps);
+
+ /* The fixed path completes whole chain in one pass: no clock steps. */
+ g_assert_cmpint(steps, ==, 0);
+
+ qtest_quit(qts);
+ unlink(tmp);
+ g_free(tmp);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/am64/sdhci/adma-pacing", test_adma_pacing);
+ return g_test_run();
+}
diff --git a/tests/qtest/am64-virt-test.c b/tests/qtest/am64-virt-test.c
new file mode 100644
index 0000000000..c0a9a26275
--- /dev/null
+++ b/tests/qtest/am64-virt-test.c
@@ -0,0 +1,502 @@
+/*
+ * QTests for AM64 virt machine
+ *
+ * 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 "libqtest.h"
+#include "qobject/qdict.h"
+#include "qobject/qlist.h"
+
+#define OCSRAM_BASE 0x70000000ULL
+#define OCSRAM_SIZE (2 * 1024 * 1024)
+#define MAIN_UART0_BASE 0x02800000ULL
+
+static void test_ocsram_rw(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ qtest_writel(qts, OCSRAM_BASE, 0xdeadbeef);
+ g_assert_cmphex(qtest_readl(qts, OCSRAM_BASE), ==, 0xdeadbeef);
+ qtest_writel(qts, OCSRAM_BASE + OCSRAM_SIZE - 4, 0x12345678);
+ g_assert_cmphex(qtest_readl(qts, OCSRAM_BASE + OCSRAM_SIZE - 4), ==,
+ 0x12345678);
+ /* Boot parameters must stay in OCSRAM. */
+ qtest_writel(qts, 0x701bebfc, 0x0);
+ g_assert_cmphex(qtest_readl(qts, 0x701bebfc), ==, 0x0);
+ qtest_quit(qts);
+}
+
+static void test_main_uart0_present(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /* Idle 16550: transmitter empty bits are set. */
+ g_assert_cmphex(qtest_readl(qts, MAIN_UART0_BASE + (5 << 2)) & 0x60,
+ ==, 0x60);
+ qtest_quit(qts);
+}
+
+static void test_r5f_cpu_present(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+ QDict *resp = qtest_qmp(qts, "{'execute': 'query-cpus-fast'}");
+ QList *cpus = qdict_get_qlist(resp, "return");
+
+ /* 2x A53 + 1x M4 + 1x R5F */
+ g_assert_cmpint(qlist_size(cpus), ==, 4);
+ qobject_unref(resp);
+ qtest_quit(qts);
+}
+
+static void test_devstat(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /* CTRLMMR_MAIN_DEVSTAT: primary bootmode = eMMC (0x9 << 3). */
+ g_assert_cmphex(qtest_readl(qts, 0x43000030), ==, 0x48);
+ /* mmr_unlock() kick writes have to be accepted. */
+ qtest_writel(qts, 0x43008008, 0x68ef3490);
+ qtest_writel(qts, 0x4300800c, 0xd172bc5a);
+ qtest_quit(qts);
+}
+
+#define DDRSS_CFG_BASE 0x0f308000ULL
+#define SP_TARGET(thread) (0x4D000000ULL + (thread) * 0x1000)
+#define SP_RT(thread) (0x4A600000ULL + (thread) * 0x1000)
+
+static void test_dmsc_r5_version(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+ /*
+ * R5 secure-host TISCI VERSION request. The zero secure prefix
+ * has to be skipped before parsing the TISCI header.
+ */
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0a230002);
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000002);
+ /* Commit with the last data word. */
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+
+ /* Response has to land on RX thread 0. */
+ for (int i = 0; i < 100; i++) {
+ if (qtest_readl(qts, SP_RT(0)) & 0xff) {
+ break;
+ }
+ g_usleep(10 * 1000);
+ }
+ g_assert_cmpuint(qtest_readl(qts, SP_RT(0)) & 0xff, >, 0);
+
+ /* Secure prefix, echoed VERSION type, ACK bit is set. */
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(0) + 0x08) & 0xffff,
+ ==, 0x0002);
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(0) + 0x0c) & 0x2, ==, 0x2);
+ qtest_quit(qts);
+}
+
+static void test_dmsc_r5_get_freq(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /*
+ * R5 secure-host GET_FREQ (0x010e) for MMCSD0 clock 1. This
+ * controls packed request layout and nonzero frequency response.
+ */
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0a23010e);
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000002);
+ qtest_writel(qts, SP_TARGET(1) + 0x10, 57);
+ qtest_writel(qts, SP_TARGET(1) + 0x14, 1);
+ /* commit by writing the last data word */
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+
+ /* response has to land on RX thread 0 (message count > 0) */
+ for (int i = 0; i < 100; i++) {
+ if (qtest_readl(qts, SP_RT(0)) & 0xff) {
+ break;
+ }
+ g_usleep(10 * 1000);
+ }
+ g_assert_cmpuint(qtest_readl(qts, SP_RT(0)) & 0xff, >, 0);
+
+ /* Secure prefix, echoed GET_FREQ type, ACK bit is set. */
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(0) + 0x08) & 0xffff,
+ ==, 0x010e);
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(0) + 0x0c) & 0x2, ==, 0x2);
+ /* freq_hz follows the 8-byte TISCI header and has to be nonzero. */
+ g_assert_cmpuint(qtest_readl(qts, SP_TARGET(0) + 0x10), !=, 0);
+ qtest_quit(qts);
+}
+
+/*
+ * Requests without TISCI_MSG_FLAG_AOP must get no reply. Else a stale
+ * message in the single-slot RX thread breaks later request/response
+ * pairing, so keep RX count at zero for both no-response messages.
+ */
+static void test_dmsc_r5_no_response_flag(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /*
+ * Drain the reset-time boot notification. Reading register 15 clears
+ * the inbound thread message count again.
+ */
+ if (qtest_readl(qts, SP_RT(0)) & 0xff) {
+ qtest_readl(qts, SP_TARGET(0) + 0x3c);
+ }
+
+ /* WAIT_PROC_BOOT_STATUS (0xc401), hdr.flags = 0: no response. */
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0a23c401);
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+ g_usleep(50 * 1000);
+ g_assert_cmphex(qtest_readl(qts, SP_RT(0)) & 0xff, ==, 0);
+
+ /* SET_DEVICE (0x0200), hdr.flags = 0: no response. */
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0a230200);
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x10, 121); /* device id */
+ qtest_writel(qts, SP_TARGET(1) + 0x14, 0); /* state off */
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+ g_usleep(50 * 1000);
+ g_assert_cmphex(qtest_readl(qts, SP_RT(0)) & 0xff, ==, 0);
+
+ /* AOP messages still get a response. */
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0a230002); /* VERSION */
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000002);
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+ for (int i = 0; i < 100; i++) {
+ if (qtest_readl(qts, SP_RT(0)) & 0xff) {
+ break;
+ }
+ g_usleep(10 * 1000);
+ }
+ g_assert_cmphex(qtest_readl(qts, SP_RT(0)) & 0xff, >, 0);
+ qtest_quit(qts);
+}
+
+/*
+ * TISCI_MSG_SYS_RESET (0x0005) is a no-response request, but it still
+ * triggers a full machine reset.
+ */
+static void test_dmsc_r5_sys_reset(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /* Drain the reset-time boot notification. */
+ if (qtest_readl(qts, SP_RT(0)) & 0xff) {
+ qtest_readl(qts, SP_TARGET(0) + 0x3c);
+ }
+
+ /* SYS_RESET from host 35, with secure prefix and hdr.flags = 0. */
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0a230005);
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000000);
+ /* commit: write the last data word */
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+
+ /* The DMSC reset request appears as QEMU RESET event. */
+ qtest_qmp_eventwait(qts, "RESET");
+ qtest_quit(qts);
+}
+
+static void test_dmtimer_counts(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+ uint32_t t0, t1;
+
+ /* TCLR.ST is safe also when the model free-runs. */
+ qtest_writel(qts, 0x02400038, 1);
+ t0 = qtest_readl(qts, 0x0240003c);
+ qtest_clock_step(qts, 1000000); /* +1 ms */
+ t1 = qtest_readl(qts, 0x0240003c);
+ /* 20 MHz -> 1 ms = 20000 ticks */
+ g_assert_cmpuint(t1 - t0, ==, 20000);
+ qtest_quit(qts);
+}
+
+static void test_dmtimer_prescaler(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+ uint32_t t0, t1;
+
+ /*
+ * PTV=2 with PRE_EN gives 20 MHz / (2 << 2), i.e. 1 ms is
+ * 2500 ticks.
+ */
+ qtest_writel(qts, 0x02400038, 0x2b);
+ t0 = qtest_readl(qts, 0x0240003c);
+ qtest_clock_step(qts, 1000000); /* +1 ms virtual time */
+ t1 = qtest_readl(qts, 0x0240003c);
+ g_assert_cmpuint(t1 - t0, ==, 2500);
+ qtest_quit(qts);
+}
+
+static void test_dmtimer_reconfigure(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+ uint32_t t0, t1;
+
+ /*
+ * Changing the prescaler while the timer runs must only affect time
+ * after the TCLR write, never rescale already-elapsed ticks.
+ */
+ qtest_writel(qts, 0x02400038, 1); /* ST, no prescaler */
+ t0 = qtest_readl(qts, 0x0240003c);
+ qtest_clock_step(qts, 1000000); /* +1 ms @ 20 MHz */
+ qtest_writel(qts, 0x02400038, 0x2b); /* PTV=2, PRE_EN, AR, ST */
+ qtest_clock_step(qts, 1000000); /* +1 ms @ 2.5 MHz */
+ t1 = qtest_readl(qts, 0x0240003c);
+ g_assert_cmpuint(t1 - t0, ==, 20000 + 2500);
+ qtest_quit(qts);
+}
+
+#define GICD_BASE 0x01800000ULL
+#define GICR_BASE 0x01840000ULL
+#define GIC_PIDR2 0xffe8
+
+static void test_gicv3_present(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /* GICD_PIDR2.ArchRev must report a GICv3 distributor. */
+ g_assert_cmphex((qtest_readl(qts, GICD_BASE + GIC_PIDR2) >> 4) & 0xf,
+ ==, 3);
+ /* first redistributor frame at actual AM64x GICR base */
+ g_assert_cmphex((qtest_readl(qts, GICR_BASE + GIC_PIDR2) >> 4) & 0xf,
+ ==, 3);
+ qtest_quit(qts);
+}
+
+static void test_ddrss_stub(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /* DENALI_CTL_0 writes must persist, including dram_class DDR4. */
+ qtest_writel(qts, DDRSS_CFG_BASE + 0x0, 0x00000A00);
+ g_assert_cmphex(qtest_readl(qts, DDRSS_CFG_BASE + 0x0), ==, 0x00000A00);
+
+ /* Done bits are ORed into status reads, also after writes. */
+ qtest_writel(qts, DDRSS_CFG_BASE + 0x214C, 0x0);
+ g_assert_cmphex(qtest_readl(qts, DDRSS_CFG_BASE + 0x214C) & 0x1, ==, 0x1);
+ qtest_writel(qts, DDRSS_CFG_BASE + 0x538, 0x0);
+ g_assert_cmphex(qtest_readl(qts, DDRSS_CFG_BASE + 0x538) & (1u << 13),
+ ==, 1u << 13);
+ qtest_writel(qts, DDRSS_CFG_BASE + 0x558, 0x0);
+ g_assert_cmphex(qtest_readl(qts, DDRSS_CFG_BASE + 0x558) & (1u << 25),
+ ==, 1u << 25);
+
+ /*
+ * ECC priming needs both BIST_DONE latches: INT_STATUS_MASTER bit 8
+ * and INT_STATUS_BIST bit 0, resp. CTL_341 raw bit 16.
+ */
+ qtest_writel(qts, DDRSS_CFG_BASE + 0x538, 0x0);
+ g_assert_cmphex(qtest_readl(qts, DDRSS_CFG_BASE + 0x538) & (1u << 8),
+ ==, 1u << 8);
+ qtest_writel(qts, DDRSS_CFG_BASE + 0x554, 0x0);
+ g_assert_cmphex(qtest_readl(qts, DDRSS_CFG_BASE + 0x554) & (1u << 16),
+ ==, 1u << 16);
+ qtest_quit(qts);
+}
+
+/*
+ * SET_CONFIG (0xc100) records the A53 bootvector, and PROC_GET_STATUS
+ * (0xc400) must echo it. The packed payload has processor_id at byte 0
+ * and bootvector_low at bytes 1..4, so 0x701c0000 is written as
+ * 0x1c000020 / 0x00000070.
+ */
+static void test_dmsc_r5_bootvector_capture(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+ uint32_t reg4, reg5, bootvector_lo;
+
+ /* drain boot notification pre-queued on thread 0 at reset */
+ if (qtest_readl(qts, SP_RT(0)) & 0xff) {
+ qtest_readl(qts, SP_TARGET(0) + 0x3c);
+ }
+
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000); /* sec hdr */
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0a23c100); /* hdr */
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000002); /* AOP */
+ qtest_writel(qts, SP_TARGET(1) + 0x10, 0x1c000020); /* id+bv */
+ qtest_writel(qts, SP_TARGET(1) + 0x14, 0x00000070);
+ qtest_writel(qts, SP_TARGET(1) + 0x18, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x1c, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+ for (int i = 0; i < 100 && !(qtest_readl(qts, SP_RT(0)) & 0xff); i++) {
+ g_usleep(10 * 1000);
+ }
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(0) + 0x0c) & 0x2, ==, 0x2);
+ qtest_readl(qts, SP_TARGET(0) + 0x3c); /* drain */
+
+ /* PROC_GET_STATUS (0xc400), proc 32: bootvector_low has to echo. */
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0a23c400);
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000002);
+ qtest_writel(qts, SP_TARGET(1) + 0x10, 32); /* proc_id */
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+ for (int i = 0; i < 100 && !(qtest_readl(qts, SP_RT(0)) & 0xff); i++) {
+ g_usleep(10 * 1000);
+ }
+
+ /*
+ * Secure responses carry a zero prefix at +0x04. processor_id is
+ * response byte 8, so bootvector_lo starts one byte into register 4
+ * and has to be rebuilt from registers 4 and 5.
+ */
+ reg4 = qtest_readl(qts, SP_TARGET(0) + 0x10);
+ reg5 = qtest_readl(qts, SP_TARGET(0) + 0x14);
+ bootvector_lo = (reg4 >> 8) | ((reg5 & 0xff) << 24);
+ g_assert_cmphex(bootvector_lo, ==, 0x701c0000);
+ qtest_quit(qts);
+}
+
+#define SDHCI_SD_BASE 0x0fa00000ULL
+#define SDHCI_EMMC_BASE 0x0fa10000ULL
+
+static void test_sdhci_present(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /*
+ * CAPAB bit 28 advertises 64-bit system-bus support, which is needed
+ * for A53 SPL ADMA2-64 transfers.
+ */
+ g_assert_cmphex(qtest_readl(qts, SDHCI_SD_BASE + 0x40), ==, 0x157c34b4);
+ g_assert_cmphex(qtest_readl(qts, SDHCI_EMMC_BASE + 0x40), ==, 0x157c34b4);
+ /* Host controller version (0xFE): SDHCI spec 3.00. */
+ g_assert_cmphex(qtest_readw(qts, SDHCI_SD_BASE + 0xFE) & 0xff, ==, 2);
+ /* PHY window: PHY_STAT1 reads CALDONE|DLLRDY */
+ g_assert_cmphex(qtest_readl(qts, 0x0fa08000ULL + 0x130) & 0x3, ==, 0x3);
+ g_assert_cmphex(qtest_readl(qts, 0x0fa18000ULL + 0x130) & 0x3, ==, 0x3);
+ qtest_quit(qts);
+}
+
+#define TRNG_BASE 0x40910000ULL
+
+static void test_trng_stub(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ /* readiness bit is permanently set */
+ g_assert_cmphex(qtest_readl(qts, TRNG_BASE + 0x10) & 0x1, ==, 0x1);
+ /* Output words must be nonzero and change between reads. */
+ uint32_t a = qtest_readl(qts, TRNG_BASE + 0x00);
+ uint32_t b = qtest_readl(qts, TRNG_BASE + 0x00);
+
+ g_assert_cmpuint(a, !=, 0);
+ g_assert_true(a != b || qtest_readl(qts, TRNG_BASE + 0x04) != a);
+ /* INTACK writes are accepted. */
+ qtest_writel(qts, TRNG_BASE + 0x10, 0x1);
+ /* CONTROL is RAM-backed: read back actual written value */
+ qtest_writel(qts, TRNG_BASE + 0x14, 0x400);
+ g_assert_cmphex(qtest_readl(qts, TRNG_BASE + 0x14), ==, 0x400);
+ qtest_quit(qts);
+}
+
+/*
+ * A53_0 uses threads 9/8 with the same secure prefix as the R5 secure
+ * pair. If it is classified non-secure, the request header shifts and
+ * response prefix is missing, leaving BL31 waiting forever.
+ */
+static void test_dmsc_a53_secure_version(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ if (qtest_readl(qts, SP_RT(8)) & 0xff) {
+ qtest_readl(qts, SP_TARGET(8) + 0x3c);
+ }
+
+ /*
+ * A53_0 secure-host VERSION request: zero secure prefix, host 10,
+ * and AOP is set.
+ */
+ qtest_writel(qts, SP_TARGET(9) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(9) + 0x08, 0x010a0002);
+ qtest_writel(qts, SP_TARGET(9) + 0x0c, 0x00000002);
+ qtest_writel(qts, SP_TARGET(9) + 0x3c, 0x00000000);
+
+ for (int i = 0; i < 100; i++) {
+ if (qtest_readl(qts, SP_RT(8)) & 0xff) {
+ break;
+ }
+ g_usleep(10 * 1000);
+ }
+ g_assert_cmphex(qtest_readl(qts, SP_RT(8)) & 0xff, >, 0);
+
+ /* VERSION type echoed and ACK bit is set. */
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(8) + 0x08) & 0xffff,
+ ==, 0x0002);
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(8) + 0x0c) & 0x2, ==, 0x2);
+ qtest_quit(qts);
+}
+
+/*
+ * TISCI_MSG_FWL_SET (0x9000) is a bare-ACK request. Without handler
+ * it falls through to the unknown-message NAK path.
+ */
+static void test_dmsc_fwl_set_ack(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+
+ if (qtest_readl(qts, SP_RT(0)) & 0xff) {
+ qtest_readl(qts, SP_TARGET(0) + 0x3c);
+ }
+
+ /*
+ * R5 secure FWL_SET: type 0x9000, host 35, AOP set, fwl_id 0x23,
+ * region 3, one permission register.
+ */
+ qtest_writel(qts, SP_TARGET(1) + 0x04, 0x00000000);
+ qtest_writel(qts, SP_TARGET(1) + 0x08, 0x0b239000);
+ qtest_writel(qts, SP_TARGET(1) + 0x0c, 0x00000002);
+ qtest_writel(qts, SP_TARGET(1) + 0x10, 0x00030023);
+ qtest_writel(qts, SP_TARGET(1) + 0x14, 0x00000001);
+ qtest_writel(qts, SP_TARGET(1) + 0x3c, 0x00000000);
+
+ for (int i = 0; i < 100; i++) {
+ if (qtest_readl(qts, SP_RT(0)) & 0xff) {
+ break;
+ }
+ g_usleep(10 * 1000);
+ }
+ g_assert_cmphex(qtest_readl(qts, SP_RT(0)) & 0xff, >, 0);
+
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(0) + 0x08) & 0xffff,
+ ==, 0x9000);
+ g_assert_cmphex(qtest_readl(qts, SP_TARGET(0) + 0x0c) & 0x2, ==, 0x2);
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/am64-virt/ocsram", test_ocsram_rw);
+ qtest_add_func("/am64-virt/main-uart0", test_main_uart0_present);
+ qtest_add_func("/am64-virt/r5f-present", test_r5f_cpu_present);
+ qtest_add_func("/am64-virt/devstat", test_devstat);
+ qtest_add_func("/am64-virt/dmsc-r5-version", test_dmsc_r5_version);
+ qtest_add_func("/am64-virt/dmsc-r5-get-freq", test_dmsc_r5_get_freq);
+ qtest_add_func("/am64-virt/dmsc-no-response",
+ test_dmsc_r5_no_response_flag);
+ qtest_add_func("/am64-virt/dmsc-sys-reset", test_dmsc_r5_sys_reset);
+ qtest_add_func("/am64-virt/dmsc-bootvector",
+ test_dmsc_r5_bootvector_capture);
+ qtest_add_func("/am64-virt/dmtimer", test_dmtimer_counts);
+ qtest_add_func("/am64-virt/dmtimer-prescaler", test_dmtimer_prescaler);
+ qtest_add_func("/am64-virt/dmtimer-reconfigure", test_dmtimer_reconfigure);
+ qtest_add_func("/am64-virt/gicv3", test_gicv3_present);
+ qtest_add_func("/am64-virt/ddrss-stub", test_ddrss_stub);
+ qtest_add_func("/am64-virt/sdhci", test_sdhci_present);
+ qtest_add_func("/am64-virt/trng", test_trng_stub);
+ qtest_add_func("/am64-virt/dmsc-a53-secure-version",
+ test_dmsc_a53_secure_version);
+ qtest_add_func("/am64-virt/dmsc-fwl-set", test_dmsc_fwl_set_ack);
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index f7c7d06620..d915fb4199 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -274,6 +274,7 @@ qtests_aarch64 = \
(config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
config_all_devices.has_key('CONFIG_ARM_VIRT') ?
['iommu-smmuv3-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_AM64_VIRT') ? ['am64-virt-test', 'ti-am64-i2c-test', 'am64-adma-pacing-test'] : []) + \
qtests_cxl + \
['arm-cpu-features',
'numa-test',
diff --git a/tests/qtest/ti-am64-i2c-test.c b/tests/qtest/ti-am64-i2c-test.c
new file mode 100644
index 0000000000..eda8d8032f
--- /dev/null
+++ b/tests/qtest/ti-am64-i2c-test.c
@@ -0,0 +1,137 @@
+/*
+ * QTests for AM64x main_i2c0 controller
+ *
+ * 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 "libqtest.h"
+
+/* main_i2c0 window: i2c@20000000, len 0x100. */
+#define I2C0_BASE 0x20000000ULL
+
+/* OMAP I2C V2 register offsets. */
+#define I2C_V2_SYSC 0x10
+#define I2C_V2_IRQSTATUS_RAW 0x24
+#define I2C_V2_IRQSTATUS 0x28
+#define I2C_V2_SYSS 0x90
+#define I2C_V2_CNT 0x98
+#define I2C_V2_DATA 0x9c
+#define I2C_V2_CON 0xa4
+#define I2C_V2_SA 0xac
+
+/* Register bits. */
+#define I2C_SYSC_SRST (1 << 1) /* SYSCONFIG soft reset */
+#define I2C_SYSS_RDONE (1 << 0) /* reset done */
+#define I2C_STAT_NACK (1 << 1) /* no acknowledgement */
+#define I2C_STAT_ARDY (1 << 2) /* register access ready */
+#define I2C_STAT_RRDY (1 << 3) /* receive data ready */
+
+#define I2C_CON_EN (1 << 15) /* module enable */
+#define I2C_CON_MST (1 << 10) /* master mode */
+#define I2C_CON_STP (1 << 1) /* stop condition */
+#define I2C_CON_STT (1 << 0) /* start condition */
+
+/*
+ * SYSCONFIG.SRST must make SYSS.RDONE assert, or the firmware spins in
+ * the soft-reset poll.
+ */
+static void test_soft_reset_completes(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+ uint32_t syss = 0;
+
+ qtest_writew(qts, I2C0_BASE + I2C_V2_SYSC, I2C_SYSC_SRST);
+
+ for (int i = 0; i < 16; i++) {
+ syss = qtest_readw(qts, I2C0_BASE + I2C_V2_SYSS);
+ if (syss & I2C_SYSS_RDONE) {
+ break;
+ }
+ }
+ g_assert_cmphex(syss & I2C_SYSS_RDONE, ==, I2C_SYSS_RDONE);
+ qtest_quit(qts);
+}
+
+/*
+ * Reads from an address without a slave must raise NACK promptly, since a
+ * driver would otherwise wait forever for an I2C event. No slave is
+ * attached here, so any address does.
+ */
+static void test_nack_on_absent_slave(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt");
+ uint32_t stat;
+
+ /* Bring the controller out of reset as firmware does. */
+ qtest_writew(qts, I2C0_BASE + I2C_V2_SYSC, I2C_SYSC_SRST);
+ (void)qtest_readw(qts, I2C0_BASE + I2C_V2_SYSS);
+
+ /* Address 0x51 has no slave: one-byte master read. */
+ qtest_writew(qts, I2C0_BASE + I2C_V2_SA, 0x51);
+ qtest_writew(qts, I2C0_BASE + I2C_V2_CNT, 1);
+ qtest_writew(qts, I2C0_BASE + I2C_V2_CON,
+ I2C_CON_EN | I2C_CON_MST | I2C_CON_STT | I2C_CON_STP);
+
+ stat = qtest_readw(qts, I2C0_BASE + I2C_V2_IRQSTATUS_RAW);
+ g_assert_cmphex(stat & I2C_STAT_NACK, ==, I2C_STAT_NACK);
+
+ /* IRQSTATUS mirrors NACK and is write-1-to-clear. */
+ stat = qtest_readw(qts, I2C0_BASE + I2C_V2_IRQSTATUS);
+ g_assert_cmphex(stat & I2C_STAT_NACK, ==, I2C_STAT_NACK);
+ qtest_writew(qts, I2C0_BASE + I2C_V2_IRQSTATUS, I2C_STAT_NACK);
+ stat = qtest_readw(qts, I2C0_BASE + I2C_V2_IRQSTATUS);
+ g_assert_cmphex(stat & I2C_STAT_NACK, ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * With a slave on the bus a one-byte master read has to ACK, return DATA
+ * and then finish with ARDY. An at24c EEPROM at 0x50 serves as the slave;
+ * it comes up erased, so offset 0 reads back as 0x00.
+ */
+static void test_eeprom_read_first_byte(void)
+{
+ QTestState *qts = qtest_init("-machine am64-virt "
+ "-device at24c-eeprom,address=0x50,"
+ "rom-size=4096");
+ uint32_t stat;
+ uint8_t b;
+
+ qtest_writew(qts, I2C0_BASE + I2C_V2_SYSC, I2C_SYSC_SRST);
+ (void)qtest_readw(qts, I2C0_BASE + I2C_V2_SYSS);
+
+ /* One-byte read from 0x50; address pointer starts by 0. */
+ qtest_writew(qts, I2C0_BASE + I2C_V2_SA, 0x50);
+ qtest_writew(qts, I2C0_BASE + I2C_V2_CNT, 1);
+ qtest_writew(qts, I2C0_BASE + I2C_V2_CON,
+ I2C_CON_EN | I2C_CON_MST | I2C_CON_STT | I2C_CON_STP);
+
+ /* Slave ACKed and data are ready. */
+ stat = qtest_readw(qts, I2C0_BASE + I2C_V2_IRQSTATUS_RAW);
+ g_assert_cmphex(stat & I2C_STAT_NACK, ==, 0);
+ g_assert_cmphex(stat & I2C_STAT_RRDY, ==, I2C_STAT_RRDY);
+
+ /* Offset 0 of the erased EEPROM reads as 0x00. */
+ b = qtest_readw(qts, I2C0_BASE + I2C_V2_DATA) & 0xff;
+ g_assert_cmphex(b, ==, 0x00);
+
+ /* Single-byte transfer complete: ARDY, no more RRDY. */
+ stat = qtest_readw(qts, I2C0_BASE + I2C_V2_IRQSTATUS_RAW);
+ g_assert_cmphex(stat & I2C_STAT_ARDY, ==, I2C_STAT_ARDY);
+ g_assert_cmphex(stat & I2C_STAT_RRDY, ==, 0);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/am64/i2c/soft-reset", test_soft_reset_completes);
+ qtest_add_func("/am64/i2c/nack", test_nack_on_absent_slave);
+ qtest_add_func("/am64/i2c/eeprom-read", test_eeprom_read_first_byte);
+ return g_test_run();
+}
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index dc3fb954c0..be2aab4274 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -41,6 +41,7 @@ tests = {
'test-bitops': [],
'test-bitcnt': [],
'test-qgraph': ['../qtest/libqos/qgraph.c'],
+ 'test-k3-bootrom': ['../../hw/arm/k3-bootrom-parse.c'],
'check-qom-interface': [qom],
'check-qom-proplist': [qom],
'test-qemu-opts': [],
diff --git a/tests/unit/test-k3-bootrom.c b/tests/unit/test-k3-bootrom.c
new file mode 100644
index 0000000000..b030d7df9b
--- /dev/null
+++ b/tests/unit/test-k3-bootrom.c
@@ -0,0 +1,263 @@
+/*
+ * Unit tests for K3 boot-ROM combined-image parser
+ *
+ * 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"
+
+/* Minimal DER emitters. */
+
+static GByteArray *der_tlv(uint8_t tag, const uint8_t *data, size_t len)
+{
+ GByteArray *a = g_byte_array_new();
+
+ g_byte_array_append(a, &tag, 1);
+ if (len < 0x80) {
+ uint8_t l = len;
+ g_byte_array_append(a, &l, 1);
+ } else if (len <= 0xffff) {
+ uint8_t l[3] = { 0x82, len >> 8, len & 0xff };
+ g_byte_array_append(a, l, 3);
+ } else {
+ g_assert_not_reached();
+ }
+ if (data) {
+ g_byte_array_append(a, data, len);
+ }
+ return a;
+}
+
+static GByteArray *der_wrap(uint8_t tag, GByteArray *inner)
+{
+ GByteArray *a = der_tlv(tag, inner->data, inner->len);
+ g_byte_array_unref(inner);
+ return a;
+}
+
+static void der_append(GByteArray *dst, GByteArray *src)
+{
+ g_byte_array_append(dst, src->data, src->len);
+ g_byte_array_unref(src);
+}
+
+static GByteArray *der_uint(uint64_t v)
+{
+ uint8_t buf[9];
+ int n = 0;
+ uint64_t t = v;
+
+ do {
+ n++;
+ t >>= 8;
+ } while (t);
+ if (v >> (n * 8 - 1) & 1) {
+ n++; /* leading zero keeps it positive */
+ }
+ for (int i = 0; i < n; i++) {
+ buf[i] = v >> ((n - 1 - i) * 8);
+ }
+ return der_tlv(0x02, buf, n);
+}
+
+/* OID 1.3.6.1.4.1.294.1.9 (ext_boot_info), pre-encoded TLV */
+static const uint8_t ext_boot_oid[] = {
+ 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x26, 0x01, 0x09
+};
+/* OID 2.16.840.1.101.3.4.2.3 (sha512), pre-encoded TLV */
+static const uint8_t sha512_oid[] = {
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03
+};
+
+static GByteArray *der_component(uint32_t ctype, uint32_t core,
+ uint32_t opts, uint32_t dest, uint64_t size)
+{
+ GByteArray *seq = g_byte_array_new();
+ uint8_t dest_be[4] = { dest >> 24, dest >> 16, dest >> 8, dest };
+ uint8_t sha[64] = { 0 };
+
+ der_append(seq, der_uint(ctype));
+ der_append(seq, der_uint(core));
+ der_append(seq, der_uint(opts));
+ der_append(seq, der_tlv(0x04, dest_be, sizeof(dest_be)));
+ der_append(seq, der_uint(size));
+ g_byte_array_append(seq, sha512_oid, sizeof(sha512_oid));
+ der_append(seq, der_tlv(0x04, sha, sizeof(sha)));
+ return der_wrap(0x30, seq);
+}
+
+/*
+ * Layout follows u-boot tools/binman/btool/openssl.py
+ * x509_cert_rom_combined(): SBL, SYSFW, SYSFW-DATA payloads are after
+ * the certificate.
+ */
+static GByteArray *make_image(const uint8_t *sbl, size_t sbl_len)
+{
+ static const uint8_t sysfw_blob[16] = "SYSFW-payload";
+ static const uint8_t cfg_blob[8] = "BCFG";
+ GByteArray *info = g_byte_array_new();
+ GByteArray *ext, *cert, *img;
+
+ der_append(info, der_uint(sbl_len + sizeof(sysfw_blob)
+ + sizeof(cfg_blob))); /* extImgSize */
+ der_append(info, der_uint(3)); /* numComp */
+ der_append(info, der_component(K3_COMP_TYPE_SBL, 16, 0,
+ 0x70000000, sbl_len));
+ der_append(info, der_component(K3_COMP_TYPE_SYSFW, 0, 0,
+ 0x44000, sizeof(sysfw_blob)));
+ der_append(info, der_component(K3_COMP_TYPE_SYSFW_DATA, 0, 0,
+ 0x7b000, sizeof(cfg_blob)));
+ info = der_wrap(0x30, info);
+
+ /* extension is SEQ { OID, OCTETSTRING { info } } */
+ ext = g_byte_array_new();
+ g_byte_array_append(ext, ext_boot_oid, sizeof(ext_boot_oid));
+ der_append(ext, der_wrap(0x04, info));
+ ext = der_wrap(0x30, ext);
+
+ /* fake cert: top-level SEQUENCE around the extension */
+ cert = der_wrap(0x30, ext);
+
+ img = g_byte_array_new();
+ g_byte_array_append(img, cert->data, cert->len);
+ g_byte_array_unref(cert);
+ g_byte_array_append(img, sbl, sbl_len);
+ g_byte_array_append(img, sysfw_blob, sizeof(sysfw_blob));
+ g_byte_array_append(img, cfg_blob, sizeof(cfg_blob));
+ return img;
+}
+
+/* Certificate-only image with caller given component sizes. */
+static GByteArray *make_cert_with_sizes(const uint64_t *sizes, size_t n)
+{
+ GByteArray *info = g_byte_array_new();
+ GByteArray *ext;
+ uint64_t total = 0;
+
+ for (size_t i = 0; i < n; i++) {
+ total += sizes[i];
+ }
+ der_append(info, der_uint(total)); /* extImgSize */
+ der_append(info, der_uint(n)); /* numComp */
+ for (size_t i = 0; i < n; i++) {
+ der_append(info, der_component(K3_COMP_TYPE_SBL, 16, 0,
+ 0x70000000, sizes[i]));
+ }
+ info = der_wrap(0x30, info);
+
+ ext = g_byte_array_new();
+ g_byte_array_append(ext, ext_boot_oid, sizeof(ext_boot_oid));
+ der_append(ext, der_wrap(0x04, info));
+ ext = der_wrap(0x30, ext);
+ return der_wrap(0x30, ext);
+}
+
+static void test_parse_ok(void)
+{
+ static const uint8_t sbl[32] = "SBL-payload";
+ GByteArray *img = make_image(sbl, sizeof(sbl));
+ K3BootImage out;
+ Error *err = NULL;
+
+ g_assert_true(k3_bootrom_parse(img->data, img->len, &out, &err));
+ g_assert_null(err);
+ g_assert_cmpuint(out.num_comps, ==, 3);
+ g_assert_cmpuint(out.comps[0].comp_type, ==, K3_COMP_TYPE_SBL);
+ g_assert_cmphex(out.comps[0].dest_addr, ==, 0x70000000);
+ g_assert_cmpuint(out.comps[0].comp_size, ==, sizeof(sbl));
+ g_assert_cmpuint(out.comps[0].payload_offset, ==, out.cert_len);
+ g_assert_cmphex(out.comps[1].dest_addr, ==, 0x44000);
+ g_assert_cmpuint(out.comps[2].payload_offset, ==,
+ out.cert_len + sizeof(sbl) + 16);
+ g_assert_cmpint(memcmp(img->data + out.comps[0].payload_offset,
+ sbl, sizeof(sbl)), ==, 0);
+ g_byte_array_unref(img);
+}
+
+static void test_parse_not_der(void)
+{
+ static const uint8_t junk[64] = { 0xff, 0x00, 0x41 };
+ K3BootImage out;
+ Error *err = NULL;
+
+ g_assert_false(k3_bootrom_parse(junk, sizeof(junk), &out, &err));
+ g_assert_nonnull(err);
+ error_free(err);
+}
+
+static void test_parse_no_extension(void)
+{
+ /* Valid DER SEQUENCE, but no ext_boot_info OID. */
+ static const uint8_t seq[] = { 0x30, 0x03, 0x02, 0x01, 0x05 };
+ K3BootImage out;
+ Error *err = NULL;
+
+ g_assert_false(k3_bootrom_parse(seq, sizeof(seq), &out, &err));
+ g_assert_nonnull(err);
+ error_free(err);
+}
+
+static void test_parse_truncated_payload(void)
+{
+ static const uint8_t sbl[32] = "SBL-payload";
+ GByteArray *img = make_image(sbl, sizeof(sbl));
+ K3BootImage out;
+ Error *err = NULL;
+
+ /* Truncated payload: comp_size claims are beyond file. */
+ g_assert_false(k3_bootrom_parse(img->data, img->len - 20, &out, &err));
+ g_assert_nonnull(err);
+ error_free(err);
+ g_byte_array_unref(img);
+}
+
+static void test_parse_size_exceeds_u32(void)
+{
+ /* comp_size wider than 32 bits must not truncate silently. */
+ static const uint64_t sizes[1] = { UINT32_MAX + 1ull };
+ GByteArray *img = make_cert_with_sizes(sizes, G_N_ELEMENTS(sizes));
+ K3BootImage out;
+ Error *err = NULL;
+
+ g_assert_false(k3_bootrom_parse(img->data, img->len, &out, &err));
+ g_assert_nonnull(err);
+ error_free(err);
+ g_byte_array_unref(img);
+}
+
+static void test_parse_size_sum_wraps_32bit(void)
+{
+ /*
+ * Each single size fits in 32 bits, but their sum must not wrap the
+ * running total.
+ */
+ static const uint64_t sizes[3] = {
+ 0xf0000000, 0xf0000000, 0xf0000000
+ };
+ GByteArray *img = make_cert_with_sizes(sizes, G_N_ELEMENTS(sizes));
+ K3BootImage out;
+ Error *err = NULL;
+
+ g_assert_false(k3_bootrom_parse(img->data, img->len, &out, &err));
+ g_assert_nonnull(err);
+ error_free(err);
+ g_byte_array_unref(img);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ g_test_add_func("/k3-bootrom/parse-ok", test_parse_ok);
+ g_test_add_func("/k3-bootrom/not-der", test_parse_not_der);
+ g_test_add_func("/k3-bootrom/no-extension", test_parse_no_extension);
+ g_test_add_func("/k3-bootrom/truncated", test_parse_truncated_payload);
+ g_test_add_func("/k3-bootrom/size-exceeds-u32",
+ test_parse_size_exceeds_u32);
+ g_test_add_func("/k3-bootrom/size-sum-wraps-32bit",
+ test_parse_size_sum_wraps_32bit);
+ return g_test_run();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-08-20 12:51 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-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-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 ` [RFC PATCH v2 11/14] hw/arm: add TI K3 combined boot image parser Wadim Mueller
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
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.