From: Tao Tang <tangtao1634@phytium.com.cn>
Add a tiny, test-only DMA source dedicated to exercising the SMMUv3 model.
The device purposefully avoids a realistic PCIe/platform implementation and
instead routes DMA requests straight into the SMMU, so that qtests can
populate STE/CD/PTE with known values and observe translation and data
movement deterministically, without booting any firmware or guest kernel.
Motivation
----------
Bringing up and regression-testing the SMMU in emulation often depends on a
large and flaky software stack (enumeration, drivers, PCIe fabric). For the
class of tests that only need to (1) program translation structures and (2)
trigger DMA at a precise time, that stack adds noise, slows CI, and makes
failures harder to attribute to the SMMU itself. A hermetic DMA source
keeps the surface area small and the results reproducible.
What this device is (and is not)
--------------------------------
* It is a minimal DMA producer solely for SMMU tests.
* It is NOT a faithful PCIe Endpoint nor a platform device.
* It is NOT added to any machine by default and remains test-only.
Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
---
hw/misc/Kconfig | 5 +
hw/misc/meson.build | 1 +
hw/misc/smmu-testdev.c | 943 +++++++++++++++++++++++++++++++++
include/hw/misc/smmu-testdev.h | 402 ++++++++++++++
4 files changed, 1351 insertions(+)
create mode 100644 hw/misc/smmu-testdev.c
create mode 100644 include/hw/misc/smmu-testdev.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 4e35657468..c83a0872ef 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -25,6 +25,11 @@ config PCI_TESTDEV
default y if TEST_DEVICES
depends on PCI
+config SMMU_TESTDEV
+ bool
+ default y if TEST_DEVICES
+ depends on PCI && ARM_SMMUV3
+
config EDU
bool
default y if TEST_DEVICES
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index b1d8d8e5d2..862a9895c3 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -4,6 +4,7 @@ system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
+system_ss.add(when: 'CONFIG_SMMU_TESTDEV', if_true: files('smmu-testdev.c'))
system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
system_ss.add(when: 'CONFIG_LED', if_true: files('led.c'))
diff --git a/hw/misc/smmu-testdev.c b/hw/misc/smmu-testdev.c
new file mode 100644
index 0000000000..156f4cd714
--- /dev/null
+++ b/hw/misc/smmu-testdev.c
@@ -0,0 +1,943 @@
+/*
+ * A test device for the SMMU
+ *
+ * This test device is a minimal SMMU-aware device used to test the SMMU.
+ *
+ * Copyright (c) 2025 Phytium Technology
+ *
+ * Author:
+ * Tao Tang <tangtao1634@phytium.com.cn>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "system/address-spaces.h"
+#include "exec/memattrs.h"
+#include "system/dma.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_device.h"
+#include "hw/arm/smmu-common.h"
+#include "hw/qdev-properties.h"
+#include "qom/object.h"
+#include "hw/misc/smmu-testdev.h"
+
+#define TYPE_SMMU_TESTDEV "smmu-testdev"
+OBJECT_DECLARE_SIMPLE_TYPE(SMMUTestDevState, SMMU_TESTDEV)
+
+struct SMMUTestDevState {
+ PCIDevice parent_obj;
+ MemoryRegion bar0;
+ uint32_t attr_ns; /* Track Non-Secure for now; reserve room for more. */
+
+ uint64_t smmu_base;
+ uint64_t dma_iova;
+ uint32_t dma_len;
+ uint32_t dma_dir;
+ uint32_t dma_result;
+ bool dma_pending;
+
+ /* Future-proof DMA config */
+ AddressSpace *dma_as; /* IOMMU-mediated DMA AS for this device */
+ uint32_t dma_mode; /* 0=legacy pci_dma, 1=attrs via dma_memory_* */
+ uint32_t dma_attrs_cfg; /* bit0 secure, bits[2:1] space, bit3 unspecified */
+
+ /* Translation build configuration */
+ uint32_t trans_mode; /* 0=S1, 1=S2, 2=Nested */
+ SMMUTestDevSpace s1_space;
+ SMMUTestDevSpace s2_space;
+ uint32_t trans_status; /* 0=ok; non-zero=error */
+
+ /* User-configurable BDF (device/function) */
+ uint32_t cfg_dev; /* PCI device/slot number (0..31) */
+ uint32_t cfg_fn; /* PCI function number (0..7) */
+
+ bool debug_log; /* Enable verbose debug output */
+};
+
+/* BAR0 layout */
+enum {
+ REG_ID = 0x00,
+ REG_ATTR_NS = 0x04,
+ REG_SMMU_BASE_LO = 0x20,
+ REG_SMMU_BASE_HI = 0x24,
+ REG_DMA_IOVA_LO = 0x28,
+ REG_DMA_IOVA_HI = 0x2C,
+ REG_DMA_LEN = 0x30,
+ REG_DMA_DIR = 0x34,
+ REG_DMA_RESULT = 0x38,
+ REG_DMA_DOORBELL = 0x3C,
+ /* Extended controls for DMA attributes/mode (kept after legacy regs) */
+ REG_DMA_MODE = 0x40, /* 0: legacy; 1: attrs path */
+ REG_DMA_ATTRS = 0x44, /* [0] secure, [2:1] space, [3] unspecified */
+ /* Translation config & builder */
+ REG_TRANS_MODE = 0x48, /* 0=S1 only, 1=S2 only, 2=Nested */
+ REG_S1_SPACE = 0x4C, /* SMMUTestDevSpace for stage-1 path */
+ REG_S2_SPACE = 0x50, /* SMMUTestDevSpace for stage-2 path */
+ REG_TRANS_DBELL = 0x54, /* bit0=build, bit1=clear status */
+ REG_TRANS_STATUS = 0x58, /* 0=ok else error */
+ REG_TRANS_CLEAR = 0x5C, /* write-any: clear helper-built CD/STE/PTE */
+ BAR0_SIZE = 0x1000,
+};
+
+#define DMA_DIR_DEV2HOST 0u
+#define DMA_DIR_HOST2DEV 1u
+#define DMA_RESULT_IDLE 0xffffffffu
+#define DMA_RESULT_BUSY 0xfffffffeu
+#define DMA_ERR_BAD_LEN 0xdead0001u
+#define DMA_ERR_TX_FAIL 0xdead0002u
+#define DMA_MAX_LEN (64 * KiB)
+
+/*
+ * Compile-time calculated STE field setting macros.
+ */
+
+ #define STD_STE_OR_CD_ENTRY_BYTES 64 /* 64 bytes per STE entry */
+ #define STD_STE_S2T0SZ_VAL 0x14
+
+#define STD_STE_SET_VALID(ste, val) \
+ ((ste)->word[0] = ((ste)->word[0] & ~(0x1 << 0)) | (((val) & 0x1) << 0))
+#define STD_STE_SET_CONFIG(ste, val) \
+ ((ste)->word[0] = ((ste)->word[0] & ~(0x7 << 1)) | (((val) & 0x7) << 1))
+#define STD_STE_SET_S1FMT(ste, val) \
+ ((ste)->word[0] = ((ste)->word[0] & ~(0x3 << 4)) | (((val) & 0x3) << 4))
+
+#define STD_STE_SET_CTXPTR(ste, val) \
+do { \
+ /* Lower address bits (31:6) occupy the upper 26 bits of word[0]. */ \
+ (ste)->word[0] = ((ste)->word[0] & 0x0000003FU) | \
+ ((uint32_t)(val) & 0xFFFFFFC0U); \
+ \
+ /* Upper address bits (47:32) occupy the low 16 bits of word[1]. */ \
+ (ste)->word[1] = ((ste)->word[1] & 0xFFFF0000U) | \
+ ((uint32_t)(((uint64_t)(val)) >> 32) & 0x0000FFFFU); \
+} while (0)
+
+#define STD_STE_SET_S1CDMAX(ste, val) \
+ ((ste)->word[1] = ((ste)->word[1] & ~(0x1f << 27)) | (((val) & 0x1f) << 27))
+#define STD_STE_SET_S1STALLD(ste, val) \
+ ((ste)->word[2] = ((ste)->word[2] & ~(0x1 << 27)) | (((val) & 0x1) << 27))
+#define STD_STE_SET_EATS(ste, val) \
+ ((ste)->word[2] = ((ste)->word[2] & ~(0x3 << 28)) | (((val) & 0x3) << 28))
+#define STD_STE_SET_STRW(ste, val) \
+ ((ste)->word[2] = ((ste)->word[2] & ~(0x3 << 30)) | (((val) & 0x3) << 30))
+#define STD_STE_SET_NSCFG(ste, val) \
+ ((ste)->word[2] = ((ste)->word[2] & ~(0x3 << 14)) | (((val) & 0x3) << 14))
+#define STD_STE_SET_S2VMID(ste, val) \
+ ((ste)->word[4] = ((ste)->word[4] & ~0xffff) | ((val) & 0xffff))
+#define STD_STE_SET_S2T0SZ(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~0x3f) | ((val) & 0x3f))
+#define STD_STE_SET_S2SL0(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x3 << 6)) | (((val) & 0x3) << 6))
+#define STD_STE_SET_S2TG(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x3 << 14)) | (((val) & 0x3) << 14))
+#define STD_STE_SET_S2PS(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x7 << 16)) | (((val) & 0x7) << 16))
+#define STD_STE_SET_S2AA64(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 19)) | (((val) & 0x1) << 19))
+#define STD_STE_SET_S2ENDI(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 20)) | (((val) & 0x1) << 20))
+#define STD_STE_SET_S2AFFD(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 21)) | (((val) & 0x1) << 21))
+#define STD_STE_SET_S2HD(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 23)) | (((val) & 0x1) << 23))
+#define STD_STE_SET_S2HA(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 24)) | (((val) & 0x1) << 24))
+#define STD_STE_SET_S2S(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 25)) | (((val) & 0x1) << 25))
+#define STD_STE_SET_S2R(ste, val) \
+ ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 26)) | (((val) & 0x1) << 26))
+
+#define STD_STE_SET_S2TTB(ste, val) \
+do { \
+ /* Lower address bits (31:4) occupy the upper 28 bits of word[6]. */ \
+ (ste)->word[6] = ((ste)->word[6] & 0x0000000FU) | \
+ ((uint32_t)(val) & 0xFFFFFFF0U); \
+ \
+ /* Upper address bits (51:32) occupy the low 20 bits of word[7]. */ \
+ (ste)->word[7] = ((ste)->word[7] & 0xFFF00000U) | \
+ ((uint32_t)(((uint64_t)(val)) >> 32) & \
+ 0x000FFFFFU); \
+} while (0)
+
+#define STE_S2TTB(x) \
+ ((extract64((x)->word[7], 0, 16) << 32) | \
+ ((x)->word[6] & 0xfffffff0))
+
+/*
+ * Compile-time calculated CD field setting macros.
+ */
+#define STD_CD_SET_VALID(cd, val) \
+ ((cd)->word[0] = ((cd)->word[0] & ~(0x1 << 31)) | \
+ (((val) & 0x1) << 31))
+#define STD_CD_SET_TSZ(cd, sel, val) \
+ ((cd)->word[0] = ((cd)->word[0] & \
+ ~(0x3F << ((sel) * 16 + 0))) | \
+ (((val) & 0x3F) << ((sel) * 16 + 0)))
+#define STD_CD_SET_TG(cd, sel, val) \
+ ((cd)->word[0] = ((cd)->word[0] & \
+ ~(0x3 << ((sel) * 16 + 6))) | \
+ (((val) & 0x3) << ((sel) * 16 + 6)))
+#define STD_CD_SET_EPD(cd, sel, val) \
+ ((cd)->word[0] = ((cd)->word[0] & \
+ ~(0x1 << ((sel) * 16 + 14))) | \
+ (((val) & 0x1) << ((sel) * 16 + 14)))
+#define STD_CD_SET_ENDI(cd, val) \
+ ((cd)->word[0] = ((cd)->word[0] & ~(0x1 << 15)) | \
+ (((val) & 0x1) << 15))
+#define STD_CD_SET_IPS(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x7 << 0)) | \
+ (((val) & 0x7) << 0))
+#define STD_CD_SET_AFFD(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 3)) | \
+ (((val) & 0x1) << 3))
+#define STD_CD_SET_HD(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 10)) | \
+ (((val) & 0x1) << 10))
+#define STD_CD_SET_HA(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 11)) | \
+ (((val) & 0x1) << 11))
+#define STD_CD_SET_TTB(cd, sel, val) do { \
+ (cd)->word[(sel) * 2 + 2] = ((cd)->word[(sel) * 2 + 2] & 0x0000000F) | \
+ ((val) & 0xFFFFFFF0); \
+ (cd)->word[(sel) * 2 + 3] = ((cd)->word[(sel) * 2 + 3] & 0xFFF80000) | \
+ ((((uint64_t)(val)) >> 32) & 0x0007FFFF); \
+} while (0)
+#define STD_CD_SET_HAD(cd, sel, val) \
+ ((cd)->word[(sel) * 2 + 2] = ((cd)->word[(sel) * 2 + 2] & \
+ ~(0x1 << 1)) | \
+ (((val) & 0x1) << 1))
+#define STD_CD_SET_MAIR0(cd, val) ((cd)->word[6] = (val))
+#define STD_CD_SET_MAIR1(cd, val) ((cd)->word[7] = (val))
+#define STD_CD_SET_TCR_T0SZ(cd, val) \
+ ((cd)->word[4] = ((cd)->word[4] & ~0x3F) | ((val) & 0x3F))
+#define STD_CD_SET_ASID(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0xFFFF << 16)) | \
+ (((val) & 0xFFFF) << 16))
+#define STD_CD_SET_S(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 12)) | \
+ (((val) & 0x1) << 12))
+#define STD_CD_SET_R(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 13)) | \
+ (((val) & 0x1) << 13))
+#define STD_CD_SET_A(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 14)) | \
+ (((val) & 0x1) << 14))
+#define STD_CD_SET_AARCH64(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 9)) | \
+ (((val) & 0x1) << 9))
+#define STD_CD_SET_TBI(cd, val) \
+ ((cd)->word[1] = ((cd)->word[1] & ~(0x3 << 6)) | \
+ (((val) & 0x3) << 6))
+#define STD_CD_SET_NSCFG0(cd, val) \
+ ((cd)->word[2] = ((cd)->word[2] & ~(0x1 << 0)) | (((val) & 0x1) << 0))
+#define STD_CD_SET_NSCFG1(cd, val) \
+ ((cd)->word[4] = ((cd)->word[4] & ~(0x1 << 0)) | (((val) & 0x1) << 0))
+
+typedef enum TransMode {
+ TM_S1_ONLY = 0,
+ TM_S2_ONLY = 1,
+ TM_NESTED = 2,
+} TransMode;
+
+/* Minimal STE/CD images (bit layout derived from test helpers) */
+typedef struct {
+ uint32_t word[8];
+} STEImg;
+
+typedef struct {
+ uint32_t word[8];
+} CDImg;
+
+/* ---- Debug helpers for printing current translation configuration ---- */
+static void G_GNUC_PRINTF(2, 3)
+smmu_testdev_debug(const SMMUTestDevState *s, const char *fmt, ...)
+{
+ va_list ap;
+ g_autofree char *msg = NULL;
+
+ if (!s->debug_log) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ msg = g_strdup_vprintf(fmt, ap);
+ va_end(ap);
+
+ if (qemu_log_enabled()) {
+ qemu_log("%s", msg);
+ } else {
+ fprintf(stderr, "%s", msg);
+ }
+}
+
+/* Only support Non-Secure space for now. */
+static bool smmu_testdev_space_supported(SMMUTestDevSpace sp)
+{
+ return sp == STD_SPACE_NONSECURE;
+}
+
+static MemTxAttrs mk_attrs_from_space(SMMUTestDevSpace space)
+{
+ MemTxAttrs a = {0};
+ if (!smmu_testdev_space_supported(space)) {
+ g_assert_not_reached();
+ } else {
+ a.space = space;
+ }
+ a.secure = 0;
+ return a;
+}
+
+/* Convert SMMUTestDevSpace to AddressSpace */
+static inline AddressSpace *space_to_as(SMMUTestDevSpace sp)
+{
+ /* Future work can dispatch Secure/Realm/Root address spaces here. */
+ if (!smmu_testdev_space_supported(sp)) {
+ g_assert_not_reached();
+ }
+ return &address_space_memory;
+}
+
+/* Apply per-space offset for addresses or values that encode addresses. */
+static inline uint64_t std_apply_space_offs(SMMUTestDevSpace sp, uint64_t x)
+{
+ return x + std_space_offset(sp);
+}
+
+/* Direct write helpers (no mirroring) */
+static void std_write64(SMMUTestDevSpace sp, uint64_t pa, uint64_t val,
+ uint32_t *status)
+{
+ MemTxAttrs a = mk_attrs_from_space(sp);
+ AddressSpace *as = space_to_as(sp);
+ if (!as) {
+ *status = 0xdead2011u;
+ return;
+ }
+ MemTxResult r = address_space_write(as, pa, a, &val, sizeof(val));
+ if (r != MEMTX_OK && status) {
+ *status = 0xdead2011u;
+ return;
+ }
+ *status = 0;
+}
+
+static void std_write32(SMMUTestDevSpace sp, uint64_t pa, uint32_t val,
+ uint32_t *status)
+{
+ MemTxAttrs a = mk_attrs_from_space(sp);
+ AddressSpace *as = space_to_as(sp);
+ if (!as) {
+ *status = 0xdead2012u;
+ return;
+ }
+ MemTxResult r = address_space_write(as, pa, a, &val, sizeof(val));
+ if (r != MEMTX_OK && status) {
+ *status = 0xdead2012u;
+ return;
+ }
+ *status = 0;
+}
+
+/* Build the translation tables with specified stage and security spaces. */
+static void smmu_testdev_build_translation(SMMUTestDevState *s)
+{
+ smmu_testdev_debug(s, "smmu_testdev_build_translation: stage=%s s1_space=%s"
+ " s2_space=%s\n", std_mode_to_str(s->trans_mode),
+ std_space_to_str(s->s1_space),
+ std_space_to_str(s->s2_space));
+ uint32_t st = 0;
+ SMMUTestDevSpace build_space =
+ (s->trans_mode == TM_S1_ONLY) ? s->s1_space : s->s2_space;
+
+ if (!smmu_testdev_space_supported(build_space) ||
+ (s->trans_mode != TM_S2_ONLY &&
+ !smmu_testdev_space_supported(s->s1_space))) {
+ /* Only the Non-Secure space is supported until more domains land. */
+ s->trans_status = 0xdead3001u;
+ return;
+ }
+
+ /*
+ * Build base page tables (L0..L3) in the chosen space.
+ * For Non-Secure, place tables at Secure-base + space offset and
+ * update descriptor values by the same offset to keep internal
+ * relationships identical across spaces.
+ */
+ uint64_t L0_pa = std_apply_space_offs(build_space, STD_L0_ADDR);
+ uint64_t L1_pa = std_apply_space_offs(build_space, STD_L1_ADDR);
+ uint64_t L2_pa = std_apply_space_offs(build_space, STD_L2_ADDR);
+ uint64_t L3_pa = std_apply_space_offs(build_space, STD_L3_ADDR);
+ uint64_t L0_val = std_apply_space_offs(build_space, STD_L0_VAL);
+ uint64_t L1_val = std_apply_space_offs(build_space, STD_L1_VAL);
+ uint64_t L2_val = std_apply_space_offs(build_space, STD_L2_VAL);
+ uint64_t L3_val = std_apply_space_offs(build_space, STD_L3_VAL);
+ std_write64(build_space, L0_pa, L0_val, &st);
+ std_write64(build_space, L1_pa, L1_val, &st);
+ std_write64(build_space, L2_pa, L2_val, &st);
+ std_write64(build_space, L3_pa, L3_val, &st);
+
+ /* Build STE image */
+ STEImg ste = {0};
+ switch (s->trans_mode) {
+ case TM_S1_ONLY:
+ STD_STE_SET_CONFIG(&ste, 0x5);
+ break;
+ case TM_S2_ONLY:
+ STD_STE_SET_CONFIG(&ste, 0x6);
+ break;
+ case TM_NESTED:
+ default:
+ STD_STE_SET_CONFIG(&ste, 0x7);
+ break;
+ }
+
+ uint64_t vttb = STD_VTTB;
+ STD_STE_SET_VALID(&ste, 1);
+ STD_STE_SET_S2T0SZ(&ste, STD_STE_S2T0SZ_VAL);
+ STD_STE_SET_S2SL0(&ste, 0x2); /* Start level 0*/
+ STD_STE_SET_S2TG(&ste, 0); /* 4KB */
+ STD_STE_SET_S2PS(&ste, 0x5); /* 48 bits */
+ STD_STE_SET_S2AA64(&ste, 1); /* Enable S2AA64 (64-bit address format). */
+ STD_STE_SET_S2ENDI(&ste, 0); /* Little Endian */
+ STD_STE_SET_S2AFFD(&ste, 0); /* AF Fault Disable */
+
+ STD_STE_SET_S2T0SZ(&ste, STD_STE_S2T0SZ_VAL);
+ STD_STE_SET_S2SL0(&ste, 0x2); /* Start level */
+ STD_STE_SET_S2TG(&ste, 0); /* 4KB */
+ STD_STE_SET_S2PS(&ste, 0x5); /* 48 bits ?*/
+ /* Set Context Pointer (S1ContextPtr) */
+ STD_STE_SET_CTXPTR(&ste, std_apply_space_offs(build_space, STD_CD_GPA));
+
+ STD_STE_SET_S2TTB(&ste, std_apply_space_offs(build_space, vttb));
+
+ /*
+ * Start assembling the STE, which is 64 bytes in total.
+ */
+ for (int i = 0; i < 8; i++) {
+ std_write32(build_space,
+ std_apply_space_offs(build_space, STD_STE_GPA) + i * 4,
+ ste.word[i], &st);
+ if (st != 0) {
+ printf("Writing STE error! status: %x\n", st);
+ return;
+ }
+ }
+
+ /* Build CD image for S1 path if needed */
+ if (s->trans_mode != TM_S2_ONLY) {
+ CDImg cd = {0};
+
+ STD_CD_SET_ASID(&cd, 0x1e20); /* ASID */
+ STD_CD_SET_AARCH64(&cd, 1); /* AA64 */
+ STD_CD_SET_VALID(&cd, 1);
+ STD_CD_SET_A(&cd, 1);
+ STD_CD_SET_S(&cd, 0);
+ STD_CD_SET_HD(&cd, 0);
+ STD_CD_SET_HA(&cd, 0);
+ STD_CD_SET_IPS(&cd, 0x4);
+ STD_CD_SET_TBI(&cd, 0x0);
+ STD_CD_SET_AFFD(&cd, 0x0);
+ /* Disable TTB0 translation table walk */
+ STD_CD_SET_EPD(&cd, 0, 0x0);
+ /* Enable TTB1 translation table walk */
+ STD_CD_SET_EPD(&cd, 1, 0x1);
+ STD_CD_SET_TSZ(&cd, 0, 0x10);
+ STD_CD_SET_TG(&cd, 0, 0x0);
+ STD_CD_SET_ENDI(&cd, 0x0);
+ STD_CD_SET_NSCFG0(&cd, 0x0);
+ STD_CD_SET_NSCFG1(&cd, 0x0);
+ STD_CD_SET_R(&cd, 0x1);
+
+ /*
+ * CD belongs to S1 path: compute offsets using s1_space so the
+ * GPA and embedded addresses are consistent with that space.
+ */
+ uint64_t cd_ttb = std_apply_space_offs(build_space, vttb);
+ smmu_testdev_debug(s, "STD_CD_SET_TTB: 0x%llx\n",
+ (unsigned long long)cd_ttb);
+ STD_CD_SET_TTB(&cd, 0, cd_ttb);
+
+ for (int i = 0; i < 8; i++) {
+ std_write32(s->s1_space,
+ std_apply_space_offs(s->s1_space, STD_CD_GPA) + i * 4,
+ cd.word[i], &st);
+ }
+
+ L3_val = std_apply_space_offs(build_space, STD_L3_S1_VAL);
+ std_write64(build_space, L3_pa, L3_val, &st);
+ }
+
+ /* Nested extras: CD S2 tables, CD.TTB S2 tables, shared entries. */
+ if (s->trans_mode == TM_NESTED) {
+ /* CD.S2 tables */
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_CD_S2_L0_ADDR),
+ std_apply_space_offs(build_space, STD_L0_VAL), &st);
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_CD_S2_L1_ADDR),
+ std_apply_space_offs(build_space, STD_L1_VAL), &st);
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_CD_S2_L2_ADDR),
+ std_apply_space_offs(build_space, STD_L2_VAL), &st);
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_CD_S2_L3_ADDR),
+ std_apply_space_offs(build_space,
+ STD_CD_S2_L3_VAL), &st);
+
+ /* CD.TTB S2 tables */
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_CDTTB_S2_L2_ADDR),
+ std_apply_space_offs(build_space, STD_L2_VAL), &st);
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_CDTTB_S2_L3_ADDR),
+ std_apply_space_offs(build_space,
+ STD_CDTTB_S2_L3_VAL), &st);
+
+ /* Shared mappings between S1 and S2 page tables */
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_S1L0_IN_S2L3_ADDR),
+ std_apply_space_offs(build_space,
+ STD_S1L0_IN_S2L3_VAL), &st);
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_S1L1_IN_S2L3_ADDR),
+ std_apply_space_offs(build_space,
+ STD_S1L1_IN_S2L3_VAL), &st);
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_S1L2_IN_S2L3_ADDR),
+ std_apply_space_offs(build_space,
+ STD_S1L2_IN_S2L3_VAL), &st);
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_S1L3_IN_S2L2_ADDR),
+ std_apply_space_offs(build_space,
+ STD_S1L3_IN_S2L2_VAL), &st);
+ std_write64(build_space,
+ std_apply_space_offs(build_space, STD_S1L3_IN_S2L3_ADDR),
+ std_apply_space_offs(build_space,
+ STD_S1L3_IN_S2L3_VAL), &st);
+ }
+
+ s->trans_status = st;
+}
+
+/* Manipulate SMMU command to invalidate caches */
+static void push_cfgi_cmd(SMMUTestDevState *s,
+ SMMUTestDevSpace bank_sp,
+ uint32_t type,
+ uint32_t sid,
+ bool ssec)
+{
+ MemTxResult res = 0;
+ g_assert(smmu_testdev_space_supported(bank_sp));
+ g_assert(!ssec);
+ hwaddr bank_off = 0;
+ uint32_t base_lo = address_space_ldl_le(&address_space_memory,
+ s->smmu_base + bank_off + 0x90,
+ MEMTXATTRS_UNSPECIFIED, &res);
+ uint32_t base_hi = address_space_ldl_le(&address_space_memory,
+ s->smmu_base + bank_off + 0x94,
+ MEMTXATTRS_UNSPECIFIED, &res);
+ uint64_t base = ((uint64_t)base_hi << 32) | base_lo;
+ uint32_t log2size = base & 0x1f;
+ uint64_t qbase = base & 0xfffffffffffc0ULL;
+ uint32_t prod = address_space_ldl_le(&address_space_memory,
+ s->smmu_base + bank_off + 0x98,
+ MEMTXATTRS_UNSPECIFIED, &res);
+ uint32_t index_mask = (1u << log2size) - 1u;
+ uint32_t slot = prod & index_mask;
+ uint64_t entry_pa = qbase + (uint64_t)slot * 16u;
+
+ uint32_t words[4] = {0};
+ words[0] = (type & 0xff) | (ssec ? (1u << 10) : 0u);
+ words[1] = sid;
+
+ /* push command to the command queue */
+ MemTxAttrs a = mk_attrs_from_space(bank_sp);
+ AddressSpace *as = space_to_as(bank_sp);
+ if (!as) {
+ printf("push_cfgi_cmd: space %d not supported\n", bank_sp);
+ return;
+ }
+ int ret = address_space_write(as, entry_pa, a,
+ words, sizeof(words));
+ smmu_testdev_debug(s, "push_cfgi_cmd ret %d\n", ret);
+
+ /* update PROD to trigger command handler */
+ uint32_t new_prod = (prod + 1) & ((1u << (log2size + 1)) - 1u);
+ address_space_stl_le(&address_space_memory,
+ s->smmu_base + bank_off + 0x98,
+ new_prod, MEMTXATTRS_UNSPECIFIED, &res);
+ smmu_testdev_debug(s, "last res %d\n", res);
+}
+
+/* Clear all the cache to avoid the incorrect cache hits using SMMU commands */
+static void smmu_testdev_clear_caches(SMMUTestDevState *s)
+{
+ uint32_t st = 0;
+ static const SMMUTestDevSpace spaces[] = { STD_SPACE_NONSECURE };
+
+ for (size_t idx = 0; idx < ARRAY_SIZE(spaces); idx++) {
+ SMMUTestDevSpace build_space = spaces[idx];
+ if (!smmu_testdev_space_supported(build_space)) {
+ continue;
+ }
+ /* Clear L0..L3 entries written by the builder. */
+ uint64_t L0_pa = std_apply_space_offs(build_space, STD_L0_ADDR);
+ uint64_t L1_pa = std_apply_space_offs(build_space, STD_L1_ADDR);
+ uint64_t L2_pa = std_apply_space_offs(build_space, STD_L2_ADDR);
+ uint64_t L3_pa = std_apply_space_offs(build_space, STD_L3_ADDR);
+ uint64_t S2_L0_pa = std_apply_space_offs(build_space,
+ STD_CD_S2_L0_ADDR);
+ uint64_t S2_L1_pa = std_apply_space_offs(build_space,
+ STD_CD_S2_L1_ADDR);
+ uint64_t S2_L2_pa = std_apply_space_offs(build_space,
+ STD_CD_S2_L2_ADDR);
+ uint64_t S2_L3_pa = std_apply_space_offs(build_space,
+ STD_CD_S2_L3_ADDR);
+
+ std_write64(build_space, L0_pa, 0ull, &st);
+ std_write64(build_space, L1_pa, 0ull, &st);
+ std_write64(build_space, L2_pa, 0ull, &st);
+ std_write64(build_space, L3_pa, 0ull, &st);
+ std_write64(build_space, S2_L0_pa, 0ull, &st);
+ std_write64(build_space, S2_L1_pa, 0ull, &st);
+ std_write64(build_space, S2_L2_pa, 0ull, &st);
+ std_write64(build_space, S2_L3_pa, 0ull, &st);
+
+ /* Clear STE image where it was placed. */
+ for (int i = 0; i < 8; i++) {
+ std_write32(build_space,
+ std_apply_space_offs(build_space, STD_STE_GPA) + i * 4,
+ 0u, &st);
+ }
+
+ /* Clear CD image in S1 space (matches builder placement). */
+ for (int i = 0; i < 8; i++) {
+ std_write32(build_space,
+ std_apply_space_offs(build_space, STD_CD_GPA) + i * 4,
+ 0u, &st);
+ }
+ }
+
+ /* Invalidate configuration cache via CFGI_STE and CFGI_CD commands */
+ if (s->smmu_base) {
+ /* Compute this PCI function's StreamID: bus 0, current devfn. */
+ uint8_t devfn = PCI_DEVICE(&s->parent_obj)->devfn;
+ uint32_t sid = PCI_BUILD_BDF(0, devfn);
+
+ /* Non-secure bank invalidations (SSEC=0). */
+ push_cfgi_cmd(s, STD_SPACE_NONSECURE, STD_CMD_CFGI_STE, sid, false);
+ push_cfgi_cmd(s, STD_SPACE_NONSECURE, STD_CMD_CFGI_CD, sid, false);
+ push_cfgi_cmd(s, STD_SPACE_NONSECURE, STD_CMD_TLBI_NSNH_ALL,
+ sid, false);
+
+ /* Add Secure/Realm/Root invalidations here once those domains exist. */
+ }
+}
+
+static void smmu_testdev_refresh_attrs(SMMUTestDevState *s)
+{
+ /* Report the baked-in Non-Secure attributes until more exist. */
+ s->attr_ns = (STD_SPACE_NONSECURE << 1);
+}
+
+/* Trigger a DMA operation */
+static void smmu_testdev_maybe_run_dma(SMMUTestDevState *s)
+{
+ if (!s->dma_pending) {
+ return;
+ }
+ smmu_testdev_debug(s, "smmu_testdev_maybe_run_dma: dma_pending: %d\n",
+ s->dma_pending);
+
+ s->dma_pending = false;
+
+ if (!s->dma_len || s->dma_len > DMA_MAX_LEN) {
+ s->dma_result = DMA_ERR_BAD_LEN;
+ return;
+ }
+
+ g_autofree uint8_t *buf = g_malloc(s->dma_len);
+ MemTxResult res;
+
+ if (s->dma_mode == 0) {
+ if (s->dma_dir == DMA_DIR_HOST2DEV) {
+ res = pci_dma_read(PCI_DEVICE(&s->parent_obj), s->dma_iova,
+ buf, s->dma_len);
+ } else {
+ for (uint32_t i = 0; i < s->dma_len; i++) {
+ buf[i] = 0xA0u + (i & 0x1fu);
+ }
+ res = pci_dma_write(PCI_DEVICE(&s->parent_obj), s->dma_iova,
+ buf, s->dma_len);
+ }
+ } else {
+ SMMUTestDevSpace dma_space =
+ (SMMUTestDevSpace)((s->dma_attrs_cfg >> 1) & 0x3);
+ if (!smmu_testdev_space_supported(dma_space)) {
+ /* Default to Non-Secure until other spaces are modeled. */
+ dma_space = STD_SPACE_NONSECURE;
+ }
+ MemTxAttrs attrs = {
+ .secure = 0,
+ .space = dma_space,
+ .unspecified = (s->dma_attrs_cfg & (1u << 3)) ? 1 : 0,
+ };
+ /*
+ * If 'unspecified' is set, bypass IOMMU AS and use system memory.
+ * This helps tests that want deterministic success without full
+ * IOMMU programming.
+ */
+ AddressSpace *as = (s->dma_as && !attrs.unspecified)
+ ? s->dma_as
+ : &address_space_memory;
+ if (s->dma_dir == DMA_DIR_HOST2DEV) {
+ res = dma_memory_read(as, s->dma_iova, buf, s->dma_len, attrs);
+ } else {
+ for (uint32_t i = 0; i < s->dma_len; i++) {
+ buf[i] = 0xA0u + (i & 0x1fu);
+ }
+ res = dma_memory_write(as, s->dma_iova, buf, s->dma_len, attrs);
+ }
+ }
+ s->dma_result = (res == MEMTX_OK) ? 0 : DMA_ERR_TX_FAIL;
+ smmu_testdev_debug(s, "iommu ret %d , dma_result: 0x%x\n",
+ res, s->dma_result);
+}
+
+static uint64_t smmu_testdev_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ SMMUTestDevState *s = opaque;
+ switch (addr) {
+ case REG_ID:
+ /*
+ * Only reads of REG_ID intentionally trigger the side effects
+ * (SMMU CR0 write and pending DMA). This lets tests poll
+ * REG_DMA_RESULT to observe BUSY before consuming the DMA.
+ */
+ smmu_testdev_maybe_run_dma(s);
+ return 0x53544d4du; /* 'STMM' */
+ case REG_ATTR_NS:
+ return s->attr_ns;
+ case REG_SMMU_BASE_LO:
+ return (uint32_t)(s->smmu_base & 0xffffffffu);
+ case REG_SMMU_BASE_HI:
+ return (uint32_t)(s->smmu_base >> 32);
+ case REG_DMA_IOVA_LO:
+ return (uint32_t)(s->dma_iova & 0xffffffffu);
+ case REG_DMA_IOVA_HI:
+ return (uint32_t)(s->dma_iova >> 32);
+ case REG_DMA_LEN:
+ return s->dma_len;
+ case REG_DMA_DIR:
+ return s->dma_dir;
+ case REG_DMA_RESULT:
+ return s->dma_result;
+ case REG_DMA_MODE:
+ return s->dma_mode;
+ case REG_DMA_ATTRS:
+ return s->dma_attrs_cfg;
+ case REG_TRANS_MODE:
+ return s->trans_mode;
+ case REG_S1_SPACE:
+ return s->s1_space;
+ case REG_S2_SPACE:
+ return s->s2_space;
+ case REG_TRANS_STATUS:
+ return s->trans_status;
+ default:
+ return 0;
+ }
+}
+
+static void smmu_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ SMMUTestDevState *s = opaque;
+ uint32_t data = val;
+
+ switch (addr) {
+ case REG_ID:
+ if (data == 0x1) {
+ smmu_testdev_refresh_attrs(s);
+ }
+ break;
+ case REG_SMMU_BASE_LO:
+ s->smmu_base = (s->smmu_base & ~0xffffffffull) | data;
+ break;
+ case REG_SMMU_BASE_HI:
+ s->smmu_base = (s->smmu_base & 0xffffffffull) |
+ ((uint64_t)data << 32);
+ break;
+ case REG_DMA_IOVA_LO:
+ s->dma_iova = (s->dma_iova & ~0xffffffffull) | data;
+ break;
+ case REG_DMA_IOVA_HI:
+ s->dma_iova = (s->dma_iova & 0xffffffffull) |
+ ((uint64_t)data << 32);
+ break;
+ case REG_DMA_LEN:
+ s->dma_len = data;
+ break;
+ case REG_DMA_DIR:
+ s->dma_dir = data ? DMA_DIR_HOST2DEV : DMA_DIR_DEV2HOST;
+ break;
+ case REG_DMA_RESULT:
+ s->dma_result = data;
+ break;
+ case REG_DMA_DOORBELL:
+ if (data & 0x1) {
+ s->dma_pending = true;
+ s->dma_result = DMA_RESULT_BUSY;
+ } else {
+ s->dma_pending = false;
+ s->dma_result = DMA_RESULT_IDLE;
+ }
+ break;
+ case REG_DMA_MODE:
+ s->dma_mode = data & 0x1;
+ break;
+ case REG_DMA_ATTRS:
+ s->dma_attrs_cfg = data;
+ break;
+ case REG_TRANS_MODE:
+ s->trans_mode = data & 0x3;
+ break;
+ case REG_S1_SPACE:
+ s->s1_space = (SMMUTestDevSpace)(data & 0x3);
+ break;
+ case REG_S2_SPACE:
+ s->s2_space = (SMMUTestDevSpace)(data & 0x3);
+ break;
+ case REG_TRANS_DBELL:
+ if (data & 0x2) {
+ s->trans_status = 0;
+ }
+ if (data & 0x1) {
+ smmu_testdev_build_translation(s);
+ }
+ break;
+ case REG_TRANS_CLEAR:
+ /* Clear helper caches so the next iteration rebuilds cleanly. */
+ smmu_testdev_clear_caches(s);
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps smmu_testdev_mmio_ops = {
+ .read = smmu_testdev_mmio_read,
+ .write = smmu_testdev_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void smmu_testdev_realize(PCIDevice *pdev, Error **errp)
+{
+ SMMUTestDevState *s = SMMU_TESTDEV(pdev);
+
+ /* Apply user-configurable BDF (default 0:1). */
+ uint8_t dev = s->cfg_dev & 0x1f;
+ uint8_t fn = s->cfg_fn & 0x7;
+ pdev->devfn = (dev << 3) | fn;
+
+ smmu_testdev_refresh_attrs(s);
+ s->smmu_base = 0;
+ s->dma_iova = 0;
+ s->dma_len = 0;
+ s->dma_dir = DMA_DIR_DEV2HOST;
+ s->dma_result = DMA_RESULT_IDLE;
+ s->dma_pending = false;
+ s->dma_mode = 0;
+ s->dma_attrs_cfg = 0;
+ s->dma_as = pci_device_iommu_address_space(pdev);
+ s->trans_mode = TM_S2_ONLY;
+ s->s1_space = STD_SPACE_NONSECURE;
+ s->s2_space = STD_SPACE_NONSECURE;
+ s->trans_status = 0;
+
+ if (s->debug_log) {
+ smmu_testdev_debug(s, "[smmu-testdev] debug logging enabled\n");
+ }
+
+ memory_region_init_io(&s->bar0, OBJECT(pdev), &smmu_testdev_mmio_ops, s,
+ TYPE_SMMU_TESTDEV ".bar0", BAR0_SIZE);
+ pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
+}
+
+static void smmu_testdev_reset(DeviceState *dev)
+{
+ SMMUTestDevState *s = SMMU_TESTDEV(dev);
+
+ smmu_testdev_refresh_attrs(s);
+ s->smmu_base = 0;
+ s->dma_iova = 0;
+ s->dma_len = 0;
+ s->dma_dir = DMA_DIR_DEV2HOST;
+ s->dma_result = DMA_RESULT_IDLE;
+ s->dma_pending = false;
+ s->dma_mode = 0;
+ s->dma_attrs_cfg = 0;
+ s->trans_mode = TM_S2_ONLY;
+ s->s1_space = STD_SPACE_NONSECURE;
+ s->s2_space = STD_SPACE_NONSECURE;
+ s->trans_status = 0;
+ /* Keep cfg_dev/cfg_fn as-is across reset */
+}
+
+static const Property smmu_testdev_properties[] = {
+ DEFINE_PROP_UINT32("device", SMMUTestDevState, cfg_dev, 0),
+ DEFINE_PROP_UINT32("function", SMMUTestDevState, cfg_fn, 1),
+ DEFINE_PROP_BOOL("debug-log", SMMUTestDevState, debug_log, false),
+};
+
+static void smmu_testdev_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+
+ pc->realize = smmu_testdev_realize;
+ pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+ pc->device_id = PCI_DEVICE_ID_REDHAT_TEST;
+ pc->revision = 0;
+ pc->class_id = PCI_CLASS_OTHERS;
+ dc->desc = "A test device for the SMMU";
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ device_class_set_legacy_reset(dc, smmu_testdev_reset);
+ device_class_set_props(dc, smmu_testdev_properties);
+}
+
+static void smmu_testdev_instance_init(Object *obj)
+{
+ SMMUTestDevState *s = SMMU_TESTDEV(obj);
+ s->cfg_dev = 0;
+ s->cfg_fn = 1; /* default StreamID = 1 (slot 0, fn 1) */
+ s->debug_log = false;
+}
+
+static const TypeInfo smmu_testdev_info = {
+ .name = TYPE_SMMU_TESTDEV,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(SMMUTestDevState),
+ .instance_init = smmu_testdev_instance_init,
+ .class_init = smmu_testdev_class_init,
+ .interfaces = (const InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { }
+ },
+};
+
+static void smmu_testdev_register_types(void)
+{
+ type_register_static(&smmu_testdev_info);
+}
+
+type_init(smmu_testdev_register_types);
diff --git a/include/hw/misc/smmu-testdev.h b/include/hw/misc/smmu-testdev.h
new file mode 100644
index 0000000000..6d97f6c704
--- /dev/null
+++ b/include/hw/misc/smmu-testdev.h
@@ -0,0 +1,402 @@
+/*
+ * A test device for the SMMU
+ *
+ * This test device is a minimal SMMU-aware device used to test the SMMU.
+ *
+ * Copyright (c) 2025 Phytium Technology
+ *
+ * Author:
+ * Tao Tang <tangtao1634@phytium.com.cn>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_SMMU_TESTDEV_H
+#define HW_MISC_SMMU_TESTDEV_H
+
+#include "qemu/osdep.h"
+typedef enum SMMUTestDevSpace {
+ STD_SPACE_SECURE = 0,
+ STD_SPACE_NONSECURE = 1,
+ STD_SPACE_ROOT = 2,
+ STD_SPACE_REALM = 3,
+} SMMUTestDevSpace;
+
+/* Only the Non-Secure space is implemented; leave room for future domains. */
+#define STD_SUPPORTED_SPACES 1
+
+/* BAR0 registers (offsets) */
+enum {
+ STD_REG_ID = 0x00,
+ STD_REG_ATTR_NS = 0x04,
+ STD_REG_SMMU_BASE_LO = 0x20,
+ STD_REG_SMMU_BASE_HI = 0x24,
+ STD_REG_DMA_IOVA_LO = 0x28,
+ STD_REG_DMA_IOVA_HI = 0x2C,
+ STD_REG_DMA_LEN = 0x30,
+ STD_REG_DMA_DIR = 0x34,
+ STD_REG_DMA_RESULT = 0x38,
+ STD_REG_DMA_DBELL = 0x3C,
+ /* Extended controls for DMA attributes/mode */
+ STD_REG_DMA_MODE = 0x40,
+ STD_REG_DMA_ATTRS = 0x44,
+ /* Translation controls */
+ STD_REG_TRANS_MODE = 0x48,
+ STD_REG_S1_SPACE = 0x4C,
+ STD_REG_S2_SPACE = 0x50,
+ STD_REG_TRANS_DBELL = 0x54,
+ STD_REG_TRANS_STATUS = 0x58,
+ /* Clear helper-built tables/descriptors (write-any to trigger) */
+ STD_REG_TRANS_CLEAR = 0x5C,
+};
+
+/* DMA result/status values shared with tests */
+#define STD_DMA_RESULT_IDLE 0xffffffffu
+#define STD_DMA_RESULT_BUSY 0xfffffffeu
+#define STD_DMA_ERR_BAD_LEN 0xdead0001u
+#define STD_DMA_ERR_TX_FAIL 0xdead0002u
+
+/* DMA attributes layout (for STD_REG_DMA_ATTRS) */
+#define STD_DMA_ATTR_SECURE (1u << 0)
+#define STD_DMA_ATTR_SPACE_SHIFT 1
+#define STD_DMA_ATTR_SPACE_MASK (0x3u << STD_DMA_ATTR_SPACE_SHIFT)
+#define STD_DMA_ATTR_UNSPECIFIED (1u << 3)
+
+/* Command type */
+#define STD_CMD_CFGI_STE 0x03
+#define STD_CMD_CFGI_CD 0x05
+#define STD_CMD_TLBI_NSNH_ALL 0x30
+
+/*
+ * Translation tables and descriptors for a mapping of IOVA to GPA.
+ *
+ * This file defines a set of constants used to construct a static page table
+ * for an smmu-testdev device. The goal is to translate a specific STD_IOVA
+ * into a final GPA.
+ * The translation is based on the Arm architecture with the following
+ * prerequisites:
+ * - Granule size: 4KB pages.
+ * - Page table levels: 4 levels (L0, L1, L2, L3), starting at level 0.
+ * - IOVA size: The walk resolves a 39-bit IOVA (0x8080604567).
+ * - Address space: The 4-level lookup with 4KB granules supports up to a
+ * 48-bit (256TB) virtual address space. Each level uses a 9-bit index
+ * (512 entries per table). The breakdown is:
+ * - L0 index: IOVA bits [47:39]
+ * - L1 index: IOVA bits [38:30]
+ * - L2 index: IOVA bits [29:21]
+ * - L3 index: IOVA bits [20:12]
+ * - Page offset: IOVA bits [11:0]
+ *
+ * NOTE: All physical addresses defined here (STD_VTTB, table addresses, etc.)
+ * appear to be within a secure RAM region. In practice, an offset is added
+ * to these values to place them in non-secure RAM. For example, when running
+ * in a virt machine type, the RAM base address (e.g., 0x40000000) is added to
+ * these constants.
+ *
+ * The page table walk for STD_IOVA (0x8080604567) proceeds as follows:
+ *
+ * The Translation Table Base (for both Stage 1 CD_TTB and Stage 2 STE_S2TTB)
+ * is set to STD_VTTB (0xe4d0000).
+ *
+ * 1. Level 0 (L0) Table Walk:
+ * l0_index = (0x8080604567 >> 39) & 0x1ff = 1
+ * STD_L0_ADDR = STD_VTTB + (l0_index * 8) = 0xe4d0000 + 8 = 0xe4d0008
+ * STD_L0_VAL = 0xe4d1003
+ * The next level table base address = STD_L0_VAL & ~0xfff = 0xe4d1000
+ *
+ * 2. Level 1 (L1) Table Walk:
+ * l1_index = (0x8080604567 >> 30) & 0x1ff = 2
+ * STD_L1_ADDR = 0xe4d1000 + (l1_index * 8) = 0xe4d1000 + 16 = 0xe4d1010
+ * STD_L1_VAL = 0xe4d2003
+ * The next level table base address = STD_L1_VAL & ~0xfff = 0xe4d2000
+ *
+ * 3. Level 2 (L2) Table Walk:
+ * l2_index = (0x8080604567 >> 21) & 0x1ff = 3
+ * STD_L2_ADDR = 0xe4d2000 + (l2_index * 8) = 0xe4d2000 + 24 = 0xe4d2018
+ * STD_L2_VAL = 0xe4d3003
+ * The next level table base address = STD_L2_VAL & ~0xfff = 0xe4d3000
+ *
+ * 4. Level 3 (L3) Table Walk (Leaf):
+ * l3_index = (0x8080604567 >> 12) & 0x1ff = 4
+ * STD_L3_ADDR = 0xe4d3000 + (l3_index * 8) = 0xe4d3000 + 32 = 0xe4d3020
+ * STD_L3_VAL = 0x040000000ECBA7C3
+ * The next level table base address = STD_L3_VAL & ~0xfff = 0xecba000
+ *
+ * 5. Final GPA Calculation:
+ * - The final output physical address is formed by combining the address from
+ * the leaf descriptor with the original IOVA's page offset.
+ * - Output Page Base Address = (STD_L3_VAL & ~0xFFFULL) = 0xECBA000
+ * - Page Offset = (STD_IOVA & 0xFFFULL) = 0x567.
+ * - Final GPA = Output Page Base Address + Page Offset
+ * = 0xECBA000 + 0x567 = 0xECBA567
+ */
+
+#define STD_IOVA 0x0000008080604567ULL
+
+#define STD_VTTB 0xe4d0000
+
+#define STD_STR_TAB_BASE 0x000000000E179000ULL
+#define STD_STE_GPA (STD_STR_TAB_BASE + 0x40ULL)
+#define STD_CD_GPA (STD_STR_TAB_BASE + 0x80ULL)
+
+/* Page table structures */
+#define STD_L0_ADDR 0x000000000E4D0008ULL
+#define STD_L1_ADDR 0x000000000E4D1010ULL
+#define STD_L2_ADDR 0x000000000E4D2018ULL
+#define STD_L3_ADDR 0x000000000E4D3020ULL
+#define STD_L0_VAL 0x000000000E4D1003ULL
+#define STD_L1_VAL 0x000000000E4D2003ULL
+#define STD_L2_VAL 0x000000000E4D3003ULL
+#define STD_L3_VAL 0x040000000ECBA7C3ULL
+
+/*
+ * Nested stage PTW maybe a bit more complex. We share the page tables in
+ * nested stage 2 to avoid complicated definitions here. That is to say:
+ *
+ * At each level of the Stage 1 page table walk, a corresponding 4-level Stage 2
+ * page table walk is performed. The intermediate Stage 2 page tables are shared
+ * across these walks, with the key connecting PTE values being:
+ * - l0_pte_val=0x4e4d1003
+ * - l1_pte_val=0x4e4d2003
+ * - l2_pte_val=0x4e4d3003
+ *
+ *
+ * ======================================================================
+ * Nested Page Table Walk (Stage 1 + Stage 2) Example
+ * ======================================================================
+ *
+ * Goal: Translate IOVA 0x8080604567 to a final Physical Address (PA).
+ *
+ * Prerequisites:
+ * - Stage 1 VTTB (as IPA): 0x4e4d0000
+ * - Stage 2 VTTB (as PA): 0x4e4d0000
+ *
+ * ----------------------------------------------------------------------
+ * 1. Stage 1 Page Table Walk (IOVA -> IPA)
+ * ----------------------------------------------------------------------
+ *
+ * Level 0 (L0) Walk (IPA as PA)
+ * =============================
+ * iova = 0x8080604567
+ * l0_index = (0x8080604567 >> 39) & 0x1ff = 1
+ * s1_l0_pte_addr (IPA) = 0x4e4d0000 + (1 * 8) = 0x4e4d0008
+ *
+ * --> Nested Stage 2 Walk for S1 L0 Table (IPA 0x4e4d0000 -> PA 0x4e4d0000)
+ * -------------------------------------------------------------------------
+ * ipa_to_translate = 0x4e4d0000
+ * s2_vttb = 0x4e4d0000
+ * s2_l0_index = (0x4e4d0000 >> 39) & 0x1ff = 0
+ * s2_l0_pte_addr (PA) = 0x4e4d0000 + (0 * 8) = 0x4e4d0000
+ * s2_l0_pte_val = 0x4e4d1003
+ * s2_l1_table_base (PA) = 0x4e4d1003 & ~0xfff = 0x4e4d1000
+ * s2_l1_index = (0x4e4d0000 >> 30) & 0x1ff = 1
+ * s2_l1_pte_addr (PA) = 0x4e4d1000 + (1 * 8) = 0x4e4d1008
+ * s2_l1_pte_val = 0x4e4d2003
+ * s2_l2_table_base (PA) = 0x4e4d2003 & ~0xfff = 0x4e4d2000
+ * s2_l2_index = (0x4e4d0000 >> 21) & 0x1ff = 114 (0x72)
+ * s2_l2_pte_addr (PA) = 0x4e4d2000 + (114 * 8) = 0x4e4d2390
+ * s2_l2_pte_val = 0x4e4d3003
+ * s2_l3_table_base (PA) = 0x4e4d3003 & ~0xfff = 0x4e4d3000
+ * s2_l3_index = (0x4e4d0000 >> 12) & 0x1ff = 208 (0xd0)
+ * s2_l3_pte_addr (PA) = 0x4e4d3000 + (208 * 8) = 0x4e4d3680
+ * s2_l3_pte_val = 0x040000000E4D0743ULL
+ * output_page_base (PA) = 0x040000000E4D0743ULL & ~0xfff = 0x4e4d0000
+ * Final PA for table = 0x4e4d0000 + (0x0000 & 0xfff) = 0x4e4d0000
+ *
+ * s1_l0_pte_val (read from PA 0x4e4d0008) = 0x4e4d1003
+ * s1_l1_table_base (IPA) = 0x4e4d1003 & ~0xfff = 0x4e4d1000
+ *
+ * Level 1 (L1) Walk (IPA as PA)
+ * =============================
+ * iova = 0x8080604567
+ * l1_index = (0x8080604567 >> 30) & 0x1ff = 2
+ * s1_l1_pte_addr (IPA) = 0x4e4d1000 + (2 * 8) = 0x4e4d1010
+ *
+ * --> Nested Stage 2 Walk for S1 L1 Table (IPA 0x4e4d1000 -> PA 0x4e4d1000)
+ * -------------------------------------------------------------------------
+ * ipa_to_translate = 0x4e4d1000
+ * s2_vttb = 0x4e4d0000
+ * s2_l0_index = (0x4e4d1000 >> 39) & 0x1ff = 0
+ * s2_l1_table_base (PA) = 0x4e4d1000
+ * s2_l1_index = (0x4e4d1000 >> 30) & 0x1ff = 1
+ * s2_l2_table_base (PA) = 0x4e4d2000
+ * s2_l2_index = (0x4e4d1000 >> 21) & 0x1ff = 114 (0x72)
+ * s2_l3_table_base (PA) = 0x4e4d3000
+ * s2_l3_index = (0x4e4d1000 >> 12) & 0x1ff = 209 (0xd1)
+ * s2_l3_pte_addr (PA) = 0x4e4d3000 + (209 * 8) = 0x4e4d3688
+ * s2_l3_pte_val = 0x40000004e4d1743
+ * output_page_base (PA) = 0x40000004e4d1743 & ~0xfff = 0x4e4d1000
+ * Final PA for table = 0x4e4d1000 + (0x1000 & 0xfff) = 0x4e4d1000
+ *
+ * s1_l1_pte_val (read from PA 0x4e4d1010) = 0x4e4d2003
+ * s1_l2_table_base (IPA) = 0x4e4d2003 & ~0xfff = 0x4e4d2000
+ *
+ * Level 2 (L2) Walk (IPA as PA)
+ * =============================
+ * l2_index = (0x8080604567 >> 21) & 0x1ff = 3
+ * s1_l2_pte_addr (IPA) = 0x4e4d2000 + (3 * 8) = 0x4e4d2018
+ *
+ * --> Nested Stage 2 Walk for S1 L2 Table (IPA 0x4e4d2000 -> PA 0x4e4d2000)
+ * -------------------------------------------------------------------------
+ * ipa_to_translate = 0x4e4d2000
+ * s2_l0_index = (0x4e4d2000 >> 39) & 0x1ff = 0
+ * s2_l1_table_base (PA) = 0x4e4d1000
+ * s2_l1_index = (0x4e4d2000 >> 30) & 0x1ff = 1
+ * s2_l2_table_base (PA) = 0x4e4d2000
+ * s2_l2_index = (0x4e4d2000 >> 21) & 0x1ff = 114 (0x72)
+ * s2_l3_table_base (PA) = 0x4e4d3000
+ * s2_l3_index = (0x4e4d2000 >> 12) & 0x1ff = 210 (0xd2)
+ * s2_l3_pte_addr (PA) = 0x4e4d3000 + (210 * 8) = 0x4e4d3690
+ * s2_l3_pte_val = 0x40000004e4d2743
+ * output_page_base (PA) = 0x40000004e4d2743 & ~0xfff = 0x4e4d2000
+ * Final PA for table = 0x4e4d2000 + (0x2000 & 0xfff) = 0x4e4d2000
+ *
+ * s1_l2_pte_val (read from PA 0x4e4d2018) = 0x4e4d3003
+ * s1_l3_table_base (IPA) = 0x4e4d3003 & ~0xfff = 0x4e4d3000
+ *
+ * Level 3 (L3) Walk (Leaf, IPA as PA)
+ * ===================================
+ * l3_index = (0x8080604567 >> 12) & 0x1ff = 4
+ * s1_l3_pte_addr (IPA) = 0x4e4d3000 + (4 * 8) = 0x4e4d3020
+ *
+ * --> Nested Stage 2 Walk for S1 L3 Table (IPA 0x4e4d3000 -> PA 0x4e4d3000)
+ * -------------------------------------------------------------------------
+ * ipa_to_translate = 0x4e4d3000
+ * s2_l0_index = (0x4e4d3000 >> 39) & 0x1ff = 0
+ * s2_l1_table_base (PA) = 0x4e4d1000
+ * s2_l1_index = (0x4e4d3000 >> 30) & 0x1ff = 1
+ * s2_l2_table_base (PA) = 0x4e4d2000
+ * s2_l2_index = (0x4e4d3000 >> 21) & 0x1ff = 114 (0x72)
+ * s2_l3_table_base (PA) = 0x4e4d3000
+ * s2_l3_index = (0x4e4d3000 >> 12) & 0x1ff = 211 (0xd3)
+ * s2_l3_pte_addr (PA) = 0x4e4d3000 + (211 * 8) = 0x4e4d3698
+ * s2_l3_pte_val = 0x40000004e4d3743
+ * output_page_base (PA) = 0x40000004e4d3743 & ~0xfff = 0x4e4d3000
+ * Final PA for table = 0x4e4d3000 + (0x3000 & 0xfff) = 0x4e4d3000
+ *
+ * s1_l3_pte_val (read from PA 0x4e4d3020) = 0x40000004ecba743
+ * output_page_base (IPA) = 0x40000004ecba743 & ~0xfff = 0x4ecba000
+ * page_offset = 0x8080604567 & 0xfff = 0x567
+ * Final IPA = 0x4ecba000 + 0x567 = 0x4ecba567
+ *
+ * ----------------------------------------------------------------------
+ * 2. Final Stage 2 Page Table Walk (Final IPA -> PA)
+ * ----------------------------------------------------------------------
+ *
+ * ipa = 0x4ecba567
+ *
+ * s2_l0_index = (0x4ecba567 >> 39) & 0x1ff = 0
+ * s2_l1_table_base (PA) = 0x4e4d1000
+ *
+ * s2_l1_index = (0x4ecba567 >> 30) & 0x1ff = 1
+ * s2_l2_table_base (PA) = 0x4e4d2000
+ *
+ * s2_l2_index = (0x4ecba567 >> 21) & 0x1ff = 118 (0x76)
+ * s2_l3_table_base (PA) = 0x4e4d3000
+ *
+ * s2_l3_index = (0x4ecba567 >> 12) & 0x1ff = 186 (0xba)
+ * s2_l3_pte_addr (PA) = 0x4e4d3000 + (186 * 8) = 0x4e4d35d0
+ * s2_l3_pte_val = 0x40000004ecba7c3
+ * output_page_base (PA) = 0x40000004ecba7c3 & ~0xfff = 0x4ecba000
+ * page_offset = 0x4ecba567 & 0xfff = 0x567
+ *
+ * ----------------------------------------------------------------------
+ * 3. Final Result
+ * ----------------------------------------------------------------------
+ * Final PA = 0x4ecba000 + 0x567 = 0x4ecba567
+ *
+ * ----------------------------------------------------------------------
+ * 4. Appendix: Context Descriptor (CD) Fetch Walk
+ * ----------------------------------------------------------------------
+ * Before any S1 walk can begin, the SMMU must fetch the Context Descriptor.
+ * The CD's address is an IPA, so it also requires a full S2 walk. This
+ * walk RE-USES the exact same S2 page tables shown above.
+ *
+ * ipa = 0x4e179080 (Address of the CD)
+ *
+ * s2_l0_index = (0x4e179080 >> 39) & 0x1ff = 0
+ * s2_l0_pte_val = 0x4e4d1003
+ * s2_l1_table_base (PA) = 0x4e4d1000 (*RE-USED*)
+ *
+ * s2_l1_index = (0x4e179080 >> 30) & 0x1ff = 1
+ * s2_l1_pte_val = 0x4e4d2003
+ * s2_l2_table_base (PA) = 0x4e4d2000 (*RE-USED*)
+ *
+ * s2_l2_index = (0x4e179080 >> 21) & 0x1ff = 112 (0x70)
+ * s2_l2_pte_val = 0x4e4d3003
+ * s2_l3_table_base (PA) = 0x4e4d3000 (*RE-USED*)
+ *
+ * s2_l3_index = (0x4e179080 >> 12) & 0x1ff = 377 (0x179)
+ * s2_l3_pte_addr (PA) = 0x4e4d3000 + (377 * 8) = 0x4e4d3bc8
+ * s2_l3_pte_val = 0x40000004e179743
+ * output_page_base (PA) = 0x40000004e179743 & ~0xfff = 0x4e179000
+ * page_offset = 0x4e179080 & 0xfff = 0x080
+ *
+ * Final PA for CD = 0x4e179000 + 0x080 = 0x4e179080
+ *
+ */
+#define STD_CD_S2_L0_ADDR 0x000000000E4D0000ULL
+#define STD_CD_S2_L1_ADDR 0x000000000E4D1008ULL
+#define STD_CD_S2_L2_ADDR 0x000000000E4D2380ULL
+#define STD_CD_S2_L3_ADDR 0x000000000E4D3BC8ULL
+#define STD_CD_S2_L3_VAL 0x040000000E179743ULL
+
+#define STD_CDTTB_S2_L2_ADDR 0x000000000E4D2390ULL
+#define STD_CDTTB_S2_L3_ADDR 0x000000000E4D3680ULL
+#define STD_CDTTB_S2_L3_VAL 0x040000000E4D0743ULL
+
+#define STD_L3_S1_VAL 0x040000000ECBA743ULL
+
+#define STD_S1L0_IN_S2L3_ADDR 0x000000000E4D3688ULL
+#define STD_S1L0_IN_S2L3_VAL 0x040000000E4D1743ULL
+#define STD_S1L1_IN_S2L3_ADDR 0x000000000E4D3690ULL
+#define STD_S1L1_IN_S2L3_VAL 0x040000000E4D2743ULL
+#define STD_S1L2_IN_S2L3_ADDR 0x000000000E4D3698ULL
+#define STD_S1L2_IN_S2L3_VAL 0x040000000E4D3743ULL
+#define STD_S1L3_IN_S2L2_ADDR 0x000000000E4D23B0ULL
+#define STD_S1L3_IN_S2L2_VAL 0x000000000E4D3003ULL
+#define STD_S1L3_IN_S2L3_ADDR 0x000000000E4D35D0ULL
+#define STD_S1L3_IN_S2L3_VAL 0x040000000ECBA7C3ULL
+
+/*
+ * Address-space base offsets for test tables.
+ * - Non-Secure uses a fixed offset, keeping internal layout identical.
+ *
+ * Note: Future spaces (e.g. Secure/Realm/Root) are not implemented here.
+ * When needed, introduce new offsets and reuse the helpers below so
+ * relative layout stays identical across spaces.
+ */
+#define STD_SPACE_OFFS_NS 0x40000000ULL
+
+static inline uint64_t std_space_offset(SMMUTestDevSpace sp)
+{
+ switch (sp) {
+ case STD_SPACE_NONSECURE:
+ return STD_SPACE_OFFS_NS;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static const char *std_space_to_str(SMMUTestDevSpace sp)
+{
+ switch (sp) {
+ case STD_SPACE_NONSECURE:
+ return "Non-Secure";
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static const char *std_mode_to_str(uint32_t m)
+{
+ switch (m & 0x3) {
+ case 0: return "S1-only";
+ case 1: return "S2-only";
+ case 2: return "Nested";
+ default:
+ g_assert_not_reached();
+ }
+}
+
+#endif /* HW_MISC_SMMU_TESTDEV_H */
--
2.49.0
tangtao1634 <tangtao1634@phytium.com.cn> writes:
> From: Tao Tang <tangtao1634@phytium.com.cn>
>
> Add a tiny, test-only DMA source dedicated to exercising the SMMUv3 model.
> The device purposefully avoids a realistic PCIe/platform implementation and
> instead routes DMA requests straight into the SMMU, so that qtests can
> populate STE/CD/PTE with known values and observe translation and data
> movement deterministically, without booting any firmware or guest kernel.
>
> Motivation
> ----------
> Bringing up and regression-testing the SMMU in emulation often depends on a
> large and flaky software stack (enumeration, drivers, PCIe fabric). For the
> class of tests that only need to (1) program translation structures and (2)
> trigger DMA at a precise time, that stack adds noise, slows CI, and makes
> failures harder to attribute to the SMMU itself. A hermetic DMA source
> keeps the surface area small and the results reproducible.
>
> What this device is (and is not)
> --------------------------------
> * It is a minimal DMA producer solely for SMMU tests.
> * It is NOT a faithful PCIe Endpoint nor a platform device.
> * It is NOT added to any machine by default and remains test-only.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> ---
> hw/misc/Kconfig | 5 +
> hw/misc/meson.build | 1 +
> hw/misc/smmu-testdev.c | 943 +++++++++++++++++++++++++++++++++
> include/hw/misc/smmu-testdev.h | 402 ++++++++++++++
> 4 files changed, 1351 insertions(+)
> create mode 100644 hw/misc/smmu-testdev.c
> create mode 100644 include/hw/misc/smmu-testdev.h
>
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 4e35657468..c83a0872ef 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -25,6 +25,11 @@ config PCI_TESTDEV
> default y if TEST_DEVICES
> depends on PCI
>
> +config SMMU_TESTDEV
> + bool
> + default y if TEST_DEVICES
> + depends on PCI && ARM_SMMUV3
> +
> config EDU
> bool
> default y if TEST_DEVICES
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index b1d8d8e5d2..862a9895c3 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -4,6 +4,7 @@ system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
> system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
> system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
> system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
> +system_ss.add(when: 'CONFIG_SMMU_TESTDEV', if_true: files('smmu-testdev.c'))
> system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
> system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
> system_ss.add(when: 'CONFIG_LED', if_true: files('led.c'))
> diff --git a/hw/misc/smmu-testdev.c b/hw/misc/smmu-testdev.c
> new file mode 100644
> index 0000000000..156f4cd714
> --- /dev/null
> +++ b/hw/misc/smmu-testdev.c
> @@ -0,0 +1,943 @@
> +/*
> + * A test device for the SMMU
> + *
> + * This test device is a minimal SMMU-aware device used to test the SMMU.
> + *
> + * Copyright (c) 2025 Phytium Technology
> + *
> + * Author:
> + * Tao Tang <tangtao1634@phytium.com.cn>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "qemu/units.h"
> +#include "system/address-spaces.h"
> +#include "exec/memattrs.h"
> +#include "system/dma.h"
> +#include "hw/pci/pci.h"
> +#include "hw/pci/pci_device.h"
> +#include "hw/arm/smmu-common.h"
> +#include "hw/qdev-properties.h"
> +#include "qom/object.h"
> +#include "hw/misc/smmu-testdev.h"
> +
> +#define TYPE_SMMU_TESTDEV "smmu-testdev"
> +OBJECT_DECLARE_SIMPLE_TYPE(SMMUTestDevState, SMMU_TESTDEV)
> +
> +struct SMMUTestDevState {
> + PCIDevice parent_obj;
> + MemoryRegion bar0;
> + uint32_t attr_ns; /* Track Non-Secure for now; reserve room for more. */
> +
> + uint64_t smmu_base;
> + uint64_t dma_iova;
> + uint32_t dma_len;
> + uint32_t dma_dir;
> + uint32_t dma_result;
> + bool dma_pending;
> +
> + /* Future-proof DMA config */
> + AddressSpace *dma_as; /* IOMMU-mediated DMA AS for this device */
> + uint32_t dma_mode; /* 0=legacy pci_dma, 1=attrs via dma_memory_* */
> + uint32_t dma_attrs_cfg; /* bit0 secure, bits[2:1] space, bit3 unspecified */
> +
> + /* Translation build configuration */
> + uint32_t trans_mode; /* 0=S1, 1=S2, 2=Nested */
> + SMMUTestDevSpace s1_space;
> + SMMUTestDevSpace s2_space;
> + uint32_t trans_status; /* 0=ok; non-zero=error */
> +
> + /* User-configurable BDF (device/function) */
> + uint32_t cfg_dev; /* PCI device/slot number (0..31) */
> + uint32_t cfg_fn; /* PCI function number (0..7) */
> +
> + bool debug_log; /* Enable verbose debug output */
> +};
> +
> +/* BAR0 layout */
> +enum {
> + REG_ID = 0x00,
> + REG_ATTR_NS = 0x04,
> + REG_SMMU_BASE_LO = 0x20,
> + REG_SMMU_BASE_HI = 0x24,
> + REG_DMA_IOVA_LO = 0x28,
> + REG_DMA_IOVA_HI = 0x2C,
> + REG_DMA_LEN = 0x30,
> + REG_DMA_DIR = 0x34,
> + REG_DMA_RESULT = 0x38,
> + REG_DMA_DOORBELL = 0x3C,
> + /* Extended controls for DMA attributes/mode (kept after legacy regs) */
> + REG_DMA_MODE = 0x40, /* 0: legacy; 1: attrs path */
> + REG_DMA_ATTRS = 0x44, /* [0] secure, [2:1] space, [3] unspecified */
> + /* Translation config & builder */
> + REG_TRANS_MODE = 0x48, /* 0=S1 only, 1=S2 only, 2=Nested */
> + REG_S1_SPACE = 0x4C, /* SMMUTestDevSpace for stage-1 path */
> + REG_S2_SPACE = 0x50, /* SMMUTestDevSpace for stage-2 path */
> + REG_TRANS_DBELL = 0x54, /* bit0=build, bit1=clear status */
> + REG_TRANS_STATUS = 0x58, /* 0=ok else error */
> + REG_TRANS_CLEAR = 0x5C, /* write-any: clear helper-built CD/STE/PTE */
> + BAR0_SIZE = 0x1000,
> +};
> +
> +#define DMA_DIR_DEV2HOST 0u
> +#define DMA_DIR_HOST2DEV 1u
> +#define DMA_RESULT_IDLE 0xffffffffu
> +#define DMA_RESULT_BUSY 0xfffffffeu
> +#define DMA_ERR_BAD_LEN 0xdead0001u
> +#define DMA_ERR_TX_FAIL 0xdead0002u
> +#define DMA_MAX_LEN (64 * KiB)
> +
> +/*
> + * Compile-time calculated STE field setting macros.
> + */
> +
> + #define STD_STE_OR_CD_ENTRY_BYTES 64 /* 64 bytes per STE entry */
> + #define STD_STE_S2T0SZ_VAL 0x14
> +
> +#define STD_STE_SET_VALID(ste, val) \
> + ((ste)->word[0] = ((ste)->word[0] & ~(0x1 << 0)) | (((val) & 0x1) << 0))
> +#define STD_STE_SET_CONFIG(ste, val) \
> + ((ste)->word[0] = ((ste)->word[0] & ~(0x7 << 1)) | (((val) & 0x7) << 1))
> +#define STD_STE_SET_S1FMT(ste, val) \
> + ((ste)->word[0] = ((ste)->word[0] & ~(0x3 << 4)) | (((val) & 0x3) << 4))
> +
> +#define STD_STE_SET_CTXPTR(ste, val) \
> +do { \
> + /* Lower address bits (31:6) occupy the upper 26 bits of word[0]. */ \
> + (ste)->word[0] = ((ste)->word[0] & 0x0000003FU) | \
> + ((uint32_t)(val) & 0xFFFFFFC0U); \
> + \
> + /* Upper address bits (47:32) occupy the low 16 bits of word[1]. */ \
> + (ste)->word[1] = ((ste)->word[1] & 0xFFFF0000U) | \
> + ((uint32_t)(((uint64_t)(val)) >> 32) & 0x0000FFFFU); \
> +} while (0)
> +
> +#define STD_STE_SET_S1CDMAX(ste, val) \
> + ((ste)->word[1] = ((ste)->word[1] & ~(0x1f << 27)) | (((val) & 0x1f) << 27))
> +#define STD_STE_SET_S1STALLD(ste, val) \
> + ((ste)->word[2] = ((ste)->word[2] & ~(0x1 << 27)) | (((val) & 0x1) << 27))
> +#define STD_STE_SET_EATS(ste, val) \
> + ((ste)->word[2] = ((ste)->word[2] & ~(0x3 << 28)) | (((val) & 0x3) << 28))
> +#define STD_STE_SET_STRW(ste, val) \
> + ((ste)->word[2] = ((ste)->word[2] & ~(0x3 << 30)) | (((val) & 0x3) << 30))
> +#define STD_STE_SET_NSCFG(ste, val) \
> + ((ste)->word[2] = ((ste)->word[2] & ~(0x3 << 14)) | (((val) & 0x3) << 14))
> +#define STD_STE_SET_S2VMID(ste, val) \
> + ((ste)->word[4] = ((ste)->word[4] & ~0xffff) | ((val) & 0xffff))
> +#define STD_STE_SET_S2T0SZ(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~0x3f) | ((val) & 0x3f))
> +#define STD_STE_SET_S2SL0(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x3 << 6)) | (((val) & 0x3) << 6))
> +#define STD_STE_SET_S2TG(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x3 << 14)) | (((val) & 0x3) << 14))
> +#define STD_STE_SET_S2PS(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x7 << 16)) | (((val) & 0x7) << 16))
> +#define STD_STE_SET_S2AA64(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 19)) | (((val) & 0x1) << 19))
> +#define STD_STE_SET_S2ENDI(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 20)) | (((val) & 0x1) << 20))
> +#define STD_STE_SET_S2AFFD(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 21)) | (((val) & 0x1) << 21))
> +#define STD_STE_SET_S2HD(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 23)) | (((val) & 0x1) << 23))
> +#define STD_STE_SET_S2HA(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 24)) | (((val) & 0x1) << 24))
> +#define STD_STE_SET_S2S(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 25)) | (((val) & 0x1) << 25))
> +#define STD_STE_SET_S2R(ste, val) \
> + ((ste)->word[5] = ((ste)->word[5] & ~(0x1 << 26)) | (((val) & 0x1) << 26))
> +
> +#define STD_STE_SET_S2TTB(ste, val) \
> +do { \
> + /* Lower address bits (31:4) occupy the upper 28 bits of word[6]. */ \
> + (ste)->word[6] = ((ste)->word[6] & 0x0000000FU) | \
> + ((uint32_t)(val) & 0xFFFFFFF0U); \
> + \
> + /* Upper address bits (51:32) occupy the low 20 bits of word[7]. */ \
> + (ste)->word[7] = ((ste)->word[7] & 0xFFF00000U) | \
> + ((uint32_t)(((uint64_t)(val)) >> 32) & \
> + 0x000FFFFFU); \
> +} while (0)
> +
> +#define STE_S2TTB(x) \
> + ((extract64((x)->word[7], 0, 16) << 32) | \
> + ((x)->word[6] & 0xfffffff0))
> +
> +/*
> + * Compile-time calculated CD field setting macros.
> + */
> +#define STD_CD_SET_VALID(cd, val) \
> + ((cd)->word[0] = ((cd)->word[0] & ~(0x1 << 31)) | \
> + (((val) & 0x1) << 31))
> +#define STD_CD_SET_TSZ(cd, sel, val) \
> + ((cd)->word[0] = ((cd)->word[0] & \
> + ~(0x3F << ((sel) * 16 + 0))) | \
> + (((val) & 0x3F) << ((sel) * 16 + 0)))
> +#define STD_CD_SET_TG(cd, sel, val) \
> + ((cd)->word[0] = ((cd)->word[0] & \
> + ~(0x3 << ((sel) * 16 + 6))) | \
> + (((val) & 0x3) << ((sel) * 16 + 6)))
> +#define STD_CD_SET_EPD(cd, sel, val) \
> + ((cd)->word[0] = ((cd)->word[0] & \
> + ~(0x1 << ((sel) * 16 + 14))) | \
> + (((val) & 0x1) << ((sel) * 16 + 14)))
> +#define STD_CD_SET_ENDI(cd, val) \
> + ((cd)->word[0] = ((cd)->word[0] & ~(0x1 << 15)) | \
> + (((val) & 0x1) << 15))
> +#define STD_CD_SET_IPS(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x7 << 0)) | \
> + (((val) & 0x7) << 0))
> +#define STD_CD_SET_AFFD(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 3)) | \
> + (((val) & 0x1) << 3))
> +#define STD_CD_SET_HD(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 10)) | \
> + (((val) & 0x1) << 10))
> +#define STD_CD_SET_HA(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 11)) | \
> + (((val) & 0x1) << 11))
> +#define STD_CD_SET_TTB(cd, sel, val) do { \
> + (cd)->word[(sel) * 2 + 2] = ((cd)->word[(sel) * 2 + 2] & 0x0000000F) | \
> + ((val) & 0xFFFFFFF0); \
> + (cd)->word[(sel) * 2 + 3] = ((cd)->word[(sel) * 2 + 3] & 0xFFF80000) | \
> + ((((uint64_t)(val)) >> 32) & 0x0007FFFF); \
> +} while (0)
> +#define STD_CD_SET_HAD(cd, sel, val) \
> + ((cd)->word[(sel) * 2 + 2] = ((cd)->word[(sel) * 2 + 2] & \
> + ~(0x1 << 1)) | \
> + (((val) & 0x1) << 1))
> +#define STD_CD_SET_MAIR0(cd, val) ((cd)->word[6] = (val))
> +#define STD_CD_SET_MAIR1(cd, val) ((cd)->word[7] = (val))
> +#define STD_CD_SET_TCR_T0SZ(cd, val) \
> + ((cd)->word[4] = ((cd)->word[4] & ~0x3F) | ((val) & 0x3F))
> +#define STD_CD_SET_ASID(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0xFFFF << 16)) | \
> + (((val) & 0xFFFF) << 16))
> +#define STD_CD_SET_S(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 12)) | \
> + (((val) & 0x1) << 12))
> +#define STD_CD_SET_R(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 13)) | \
> + (((val) & 0x1) << 13))
> +#define STD_CD_SET_A(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 14)) | \
> + (((val) & 0x1) << 14))
> +#define STD_CD_SET_AARCH64(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x1 << 9)) | \
> + (((val) & 0x1) << 9))
> +#define STD_CD_SET_TBI(cd, val) \
> + ((cd)->word[1] = ((cd)->word[1] & ~(0x3 << 6)) | \
> + (((val) & 0x3) << 6))
> +#define STD_CD_SET_NSCFG0(cd, val) \
> + ((cd)->word[2] = ((cd)->word[2] & ~(0x1 << 0)) | (((val) & 0x1) << 0))
> +#define STD_CD_SET_NSCFG1(cd, val) \
> + ((cd)->word[4] = ((cd)->word[4] & ~(0x1 << 0)) | (((val) & 0x1) << 0))
> +
> +typedef enum TransMode {
> + TM_S1_ONLY = 0,
> + TM_S2_ONLY = 1,
> + TM_NESTED = 2,
> +} TransMode;
> +
> +/* Minimal STE/CD images (bit layout derived from test helpers) */
> +typedef struct {
> + uint32_t word[8];
> +} STEImg;
> +
> +typedef struct {
> + uint32_t word[8];
> +} CDImg;
> +
> +/* ---- Debug helpers for printing current translation configuration ---- */
> +static void G_GNUC_PRINTF(2, 3)
> +smmu_testdev_debug(const SMMUTestDevState *s, const char *fmt, ...)
> +{
> + va_list ap;
> + g_autofree char *msg = NULL;
> +
> + if (!s->debug_log) {
> + return;
> + }
> +
> + va_start(ap, fmt);
> + msg = g_strdup_vprintf(fmt, ap);
> + va_end(ap);
> +
> + if (qemu_log_enabled()) {
> + qemu_log("%s", msg);
> + } else {
> + fprintf(stderr, "%s", msg);
> + }
> +}
Why are we re-inventing logging here? Either use qemu_log or probably
better define tracepoints.
> +
> +/* Only support Non-Secure space for now. */
> +static bool smmu_testdev_space_supported(SMMUTestDevSpace sp)
> +{
> + return sp == STD_SPACE_NONSECURE;
> +}
> +
> +static MemTxAttrs mk_attrs_from_space(SMMUTestDevSpace space)
> +{
> + MemTxAttrs a = {0};
> + if (!smmu_testdev_space_supported(space)) {
> + g_assert_not_reached();
Isn't this the same as just saying:
g_assert(smmu_testdev_space_supported(space));
?
> + } else {
> + a.space = space;
> + }
> + a.secure = 0;
> + return a;
> +}
> +
> +/* Convert SMMUTestDevSpace to AddressSpace */
> +static inline AddressSpace *space_to_as(SMMUTestDevSpace sp)
> +{
> + /* Future work can dispatch Secure/Realm/Root address spaces here. */
> + if (!smmu_testdev_space_supported(sp)) {
> + g_assert_not_reached();
> + }
> + return &address_space_memory;
> +}
> +
> +/* Apply per-space offset for addresses or values that encode addresses. */
> +static inline uint64_t std_apply_space_offs(SMMUTestDevSpace sp, uint64_t x)
> +{
> + return x + std_space_offset(sp);
> +}
> +
> +/* Direct write helpers (no mirroring) */
> +static void std_write64(SMMUTestDevSpace sp, uint64_t pa, uint64_t val,
> + uint32_t *status)
> +{
> + MemTxAttrs a = mk_attrs_from_space(sp);
> + AddressSpace *as = space_to_as(sp);
> + if (!as) {
> + *status = 0xdead2011u;
> + return;
> + }
> + MemTxResult r = address_space_write(as, pa, a, &val, sizeof(val));
> + if (r != MEMTX_OK && status) {
> + *status = 0xdead2011u;
> + return;
> + }
> + *status = 0;
> +}
> +
> +static void std_write32(SMMUTestDevSpace sp, uint64_t pa, uint32_t val,
> + uint32_t *status)
> +{
> + MemTxAttrs a = mk_attrs_from_space(sp);
> + AddressSpace *as = space_to_as(sp);
> + if (!as) {
> + *status = 0xdead2012u;
> + return;
> + }
> + MemTxResult r = address_space_write(as, pa, a, &val, sizeof(val));
> + if (r != MEMTX_OK && status) {
> + *status = 0xdead2012u;
> + return;
> + }
> + *status = 0;
> +}
> +
> +/* Build the translation tables with specified stage and security spaces. */
> +static void smmu_testdev_build_translation(SMMUTestDevState *s)
> +{
> + smmu_testdev_debug(s, "smmu_testdev_build_translation: stage=%s s1_space=%s"
> + " s2_space=%s\n", std_mode_to_str(s->trans_mode),
> + std_space_to_str(s->s1_space),
> + std_space_to_str(s->s2_space));
tracepoint
> + uint32_t st = 0;
> + SMMUTestDevSpace build_space =
> + (s->trans_mode == TM_S1_ONLY) ? s->s1_space : s->s2_space;
> +
> + if (!smmu_testdev_space_supported(build_space) ||
> + (s->trans_mode != TM_S2_ONLY &&
> + !smmu_testdev_space_supported(s->s1_space))) {
> + /* Only the Non-Secure space is supported until more domains land. */
> + s->trans_status = 0xdead3001u;
> + return;
> + }
> +
> + /*
> + * Build base page tables (L0..L3) in the chosen space.
> + * For Non-Secure, place tables at Secure-base + space offset and
> + * update descriptor values by the same offset to keep internal
> + * relationships identical across spaces.
> + */
> + uint64_t L0_pa = std_apply_space_offs(build_space, STD_L0_ADDR);
> + uint64_t L1_pa = std_apply_space_offs(build_space, STD_L1_ADDR);
> + uint64_t L2_pa = std_apply_space_offs(build_space, STD_L2_ADDR);
> + uint64_t L3_pa = std_apply_space_offs(build_space, STD_L3_ADDR);
> + uint64_t L0_val = std_apply_space_offs(build_space, STD_L0_VAL);
> + uint64_t L1_val = std_apply_space_offs(build_space, STD_L1_VAL);
> + uint64_t L2_val = std_apply_space_offs(build_space, STD_L2_VAL);
> + uint64_t L3_val = std_apply_space_offs(build_space, STD_L3_VAL);
> + std_write64(build_space, L0_pa, L0_val, &st);
> + std_write64(build_space, L1_pa, L1_val, &st);
> + std_write64(build_space, L2_pa, L2_val, &st);
> + std_write64(build_space, L3_pa, L3_val, &st);
> +
> + /* Build STE image */
> + STEImg ste = {0};
> + switch (s->trans_mode) {
> + case TM_S1_ONLY:
> + STD_STE_SET_CONFIG(&ste, 0x5);
> + break;
> + case TM_S2_ONLY:
> + STD_STE_SET_CONFIG(&ste, 0x6);
> + break;
> + case TM_NESTED:
> + default:
> + STD_STE_SET_CONFIG(&ste, 0x7);
> + break;
> + }
> +
> + uint64_t vttb = STD_VTTB;
> + STD_STE_SET_VALID(&ste, 1);
> + STD_STE_SET_S2T0SZ(&ste, STD_STE_S2T0SZ_VAL);
> + STD_STE_SET_S2SL0(&ste, 0x2); /* Start level 0*/
> + STD_STE_SET_S2TG(&ste, 0); /* 4KB */
> + STD_STE_SET_S2PS(&ste, 0x5); /* 48 bits */
> + STD_STE_SET_S2AA64(&ste, 1); /* Enable S2AA64 (64-bit address format). */
> + STD_STE_SET_S2ENDI(&ste, 0); /* Little Endian */
> + STD_STE_SET_S2AFFD(&ste, 0); /* AF Fault Disable */
> +
> + STD_STE_SET_S2T0SZ(&ste, STD_STE_S2T0SZ_VAL);
> + STD_STE_SET_S2SL0(&ste, 0x2); /* Start level */
> + STD_STE_SET_S2TG(&ste, 0); /* 4KB */
> + STD_STE_SET_S2PS(&ste, 0x5); /* 48 bits ?*/
> + /* Set Context Pointer (S1ContextPtr) */
> + STD_STE_SET_CTXPTR(&ste, std_apply_space_offs(build_space, STD_CD_GPA));
> +
> + STD_STE_SET_S2TTB(&ste, std_apply_space_offs(build_space, vttb));
> +
> + /*
> + * Start assembling the STE, which is 64 bytes in total.
> + */
> + for (int i = 0; i < 8; i++) {
> + std_write32(build_space,
> + std_apply_space_offs(build_space, STD_STE_GPA) + i * 4,
> + ste.word[i], &st);
> + if (st != 0) {
> + printf("Writing STE error! status: %x\n", st);
qemu_log_mask(LOG_GUEST_ERROR, ...
> + return;
> + }
> + }
> +
> + /* Build CD image for S1 path if needed */
> + if (s->trans_mode != TM_S2_ONLY) {
> + CDImg cd = {0};
> +
> + STD_CD_SET_ASID(&cd, 0x1e20); /* ASID */
> + STD_CD_SET_AARCH64(&cd, 1); /* AA64 */
> + STD_CD_SET_VALID(&cd, 1);
> + STD_CD_SET_A(&cd, 1);
> + STD_CD_SET_S(&cd, 0);
> + STD_CD_SET_HD(&cd, 0);
> + STD_CD_SET_HA(&cd, 0);
> + STD_CD_SET_IPS(&cd, 0x4);
> + STD_CD_SET_TBI(&cd, 0x0);
> + STD_CD_SET_AFFD(&cd, 0x0);
> + /* Disable TTB0 translation table walk */
> + STD_CD_SET_EPD(&cd, 0, 0x0);
> + /* Enable TTB1 translation table walk */
> + STD_CD_SET_EPD(&cd, 1, 0x1);
> + STD_CD_SET_TSZ(&cd, 0, 0x10);
> + STD_CD_SET_TG(&cd, 0, 0x0);
> + STD_CD_SET_ENDI(&cd, 0x0);
> + STD_CD_SET_NSCFG0(&cd, 0x0);
> + STD_CD_SET_NSCFG1(&cd, 0x0);
> + STD_CD_SET_R(&cd, 0x1);
> +
> + /*
> + * CD belongs to S1 path: compute offsets using s1_space so the
> + * GPA and embedded addresses are consistent with that space.
> + */
> + uint64_t cd_ttb = std_apply_space_offs(build_space, vttb);
> + smmu_testdev_debug(s, "STD_CD_SET_TTB: 0x%llx\n",
> + (unsigned long long)cd_ttb);
> + STD_CD_SET_TTB(&cd, 0, cd_ttb);
> +
> + for (int i = 0; i < 8; i++) {
> + std_write32(s->s1_space,
> + std_apply_space_offs(s->s1_space, STD_CD_GPA) + i * 4,
> + cd.word[i], &st);
> + }
> +
> + L3_val = std_apply_space_offs(build_space, STD_L3_S1_VAL);
> + std_write64(build_space, L3_pa, L3_val, &st);
> + }
> +
> + /* Nested extras: CD S2 tables, CD.TTB S2 tables, shared entries. */
> + if (s->trans_mode == TM_NESTED) {
> + /* CD.S2 tables */
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_CD_S2_L0_ADDR),
> + std_apply_space_offs(build_space, STD_L0_VAL), &st);
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_CD_S2_L1_ADDR),
> + std_apply_space_offs(build_space, STD_L1_VAL), &st);
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_CD_S2_L2_ADDR),
> + std_apply_space_offs(build_space, STD_L2_VAL), &st);
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_CD_S2_L3_ADDR),
> + std_apply_space_offs(build_space,
> + STD_CD_S2_L3_VAL), &st);
> +
> + /* CD.TTB S2 tables */
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_CDTTB_S2_L2_ADDR),
> + std_apply_space_offs(build_space, STD_L2_VAL), &st);
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_CDTTB_S2_L3_ADDR),
> + std_apply_space_offs(build_space,
> + STD_CDTTB_S2_L3_VAL), &st);
> +
> + /* Shared mappings between S1 and S2 page tables */
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_S1L0_IN_S2L3_ADDR),
> + std_apply_space_offs(build_space,
> + STD_S1L0_IN_S2L3_VAL), &st);
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_S1L1_IN_S2L3_ADDR),
> + std_apply_space_offs(build_space,
> + STD_S1L1_IN_S2L3_VAL), &st);
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_S1L2_IN_S2L3_ADDR),
> + std_apply_space_offs(build_space,
> + STD_S1L2_IN_S2L3_VAL), &st);
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_S1L3_IN_S2L2_ADDR),
> + std_apply_space_offs(build_space,
> + STD_S1L3_IN_S2L2_VAL), &st);
> + std_write64(build_space,
> + std_apply_space_offs(build_space, STD_S1L3_IN_S2L3_ADDR),
> + std_apply_space_offs(build_space,
> + STD_S1L3_IN_S2L3_VAL), &st);
> + }
> +
> + s->trans_status = st;
> +}
> +
> +/* Manipulate SMMU command to invalidate caches */
> +static void push_cfgi_cmd(SMMUTestDevState *s,
> + SMMUTestDevSpace bank_sp,
> + uint32_t type,
> + uint32_t sid,
> + bool ssec)
> +{
> + MemTxResult res = 0;
> + g_assert(smmu_testdev_space_supported(bank_sp));
> + g_assert(!ssec);
> + hwaddr bank_off = 0;
> + uint32_t base_lo = address_space_ldl_le(&address_space_memory,
> + s->smmu_base + bank_off + 0x90,
> + MEMTXATTRS_UNSPECIFIED, &res);
> + uint32_t base_hi = address_space_ldl_le(&address_space_memory,
> + s->smmu_base + bank_off + 0x94,
> + MEMTXATTRS_UNSPECIFIED, &res);
> + uint64_t base = ((uint64_t)base_hi << 32) | base_lo;
> + uint32_t log2size = base & 0x1f;
> + uint64_t qbase = base & 0xfffffffffffc0ULL;
> + uint32_t prod = address_space_ldl_le(&address_space_memory,
> + s->smmu_base + bank_off + 0x98,
> + MEMTXATTRS_UNSPECIFIED, &res);
> + uint32_t index_mask = (1u << log2size) - 1u;
> + uint32_t slot = prod & index_mask;
> + uint64_t entry_pa = qbase + (uint64_t)slot * 16u;
> +
> + uint32_t words[4] = {0};
> + words[0] = (type & 0xff) | (ssec ? (1u << 10) : 0u);
> + words[1] = sid;
> +
> + /* push command to the command queue */
> + MemTxAttrs a = mk_attrs_from_space(bank_sp);
> + AddressSpace *as = space_to_as(bank_sp);
> + if (!as) {
> + printf("push_cfgi_cmd: space %d not supported\n", bank_sp);
qemu_log_mask(LOG_UNIMP?
> + return;
> + }
> + int ret = address_space_write(as, entry_pa, a,
> + words, sizeof(words));
> + smmu_testdev_debug(s, "push_cfgi_cmd ret %d\n", ret);
tracepoint
> +
> + /* update PROD to trigger command handler */
> + uint32_t new_prod = (prod + 1) & ((1u << (log2size + 1)) - 1u);
> + address_space_stl_le(&address_space_memory,
> + s->smmu_base + bank_off + 0x98,
> + new_prod, MEMTXATTRS_UNSPECIFIED, &res);
> + smmu_testdev_debug(s, "last res %d\n", res);
tracepoint but really see if it can be merged with above.
> +}
> +
> +/* Clear all the cache to avoid the incorrect cache hits using SMMU commands */
> +static void smmu_testdev_clear_caches(SMMUTestDevState *s)
> +{
> + uint32_t st = 0;
> + static const SMMUTestDevSpace spaces[] = { STD_SPACE_NONSECURE };
> +
> + for (size_t idx = 0; idx < ARRAY_SIZE(spaces); idx++) {
> + SMMUTestDevSpace build_space = spaces[idx];
> + if (!smmu_testdev_space_supported(build_space)) {
> + continue;
> + }
> + /* Clear L0..L3 entries written by the builder. */
> + uint64_t L0_pa = std_apply_space_offs(build_space, STD_L0_ADDR);
> + uint64_t L1_pa = std_apply_space_offs(build_space, STD_L1_ADDR);
> + uint64_t L2_pa = std_apply_space_offs(build_space, STD_L2_ADDR);
> + uint64_t L3_pa = std_apply_space_offs(build_space, STD_L3_ADDR);
> + uint64_t S2_L0_pa = std_apply_space_offs(build_space,
> + STD_CD_S2_L0_ADDR);
> + uint64_t S2_L1_pa = std_apply_space_offs(build_space,
> + STD_CD_S2_L1_ADDR);
> + uint64_t S2_L2_pa = std_apply_space_offs(build_space,
> + STD_CD_S2_L2_ADDR);
> + uint64_t S2_L3_pa = std_apply_space_offs(build_space,
> + STD_CD_S2_L3_ADDR);
> +
> + std_write64(build_space, L0_pa, 0ull, &st);
> + std_write64(build_space, L1_pa, 0ull, &st);
> + std_write64(build_space, L2_pa, 0ull, &st);
> + std_write64(build_space, L3_pa, 0ull, &st);
> + std_write64(build_space, S2_L0_pa, 0ull, &st);
> + std_write64(build_space, S2_L1_pa, 0ull, &st);
> + std_write64(build_space, S2_L2_pa, 0ull, &st);
> + std_write64(build_space, S2_L3_pa, 0ull, &st);
> +
> + /* Clear STE image where it was placed. */
> + for (int i = 0; i < 8; i++) {
> + std_write32(build_space,
> + std_apply_space_offs(build_space, STD_STE_GPA) + i * 4,
> + 0u, &st);
> + }
> +
> + /* Clear CD image in S1 space (matches builder placement). */
> + for (int i = 0; i < 8; i++) {
> + std_write32(build_space,
> + std_apply_space_offs(build_space, STD_CD_GPA) + i * 4,
> + 0u, &st);
> + }
> + }
> +
> + /* Invalidate configuration cache via CFGI_STE and CFGI_CD commands */
> + if (s->smmu_base) {
> + /* Compute this PCI function's StreamID: bus 0, current devfn. */
> + uint8_t devfn = PCI_DEVICE(&s->parent_obj)->devfn;
> + uint32_t sid = PCI_BUILD_BDF(0, devfn);
> +
> + /* Non-secure bank invalidations (SSEC=0). */
> + push_cfgi_cmd(s, STD_SPACE_NONSECURE, STD_CMD_CFGI_STE, sid, false);
> + push_cfgi_cmd(s, STD_SPACE_NONSECURE, STD_CMD_CFGI_CD, sid, false);
> + push_cfgi_cmd(s, STD_SPACE_NONSECURE, STD_CMD_TLBI_NSNH_ALL,
> + sid, false);
> +
> + /* Add Secure/Realm/Root invalidations here once those domains exist. */
> + }
> +}
> +
> +static void smmu_testdev_refresh_attrs(SMMUTestDevState *s)
> +{
> + /* Report the baked-in Non-Secure attributes until more exist. */
> + s->attr_ns = (STD_SPACE_NONSECURE << 1);
> +}
> +
> +/* Trigger a DMA operation */
> +static void smmu_testdev_maybe_run_dma(SMMUTestDevState *s)
> +{
> + if (!s->dma_pending) {
> + return;
> + }
> + smmu_testdev_debug(s, "smmu_testdev_maybe_run_dma: dma_pending: %d\n",
> + s->dma_pending);
tracepoint
> +
> + s->dma_pending = false;
> +
> + if (!s->dma_len || s->dma_len > DMA_MAX_LEN) {
> + s->dma_result = DMA_ERR_BAD_LEN;
> + return;
> + }
> +
> + g_autofree uint8_t *buf = g_malloc(s->dma_len);
> + MemTxResult res;
Keep local vars at the top of the block.
> +
> + if (s->dma_mode == 0) {
> + if (s->dma_dir == DMA_DIR_HOST2DEV) {
> + res = pci_dma_read(PCI_DEVICE(&s->parent_obj), s->dma_iova,
> + buf, s->dma_len);
> + } else {
> + for (uint32_t i = 0; i < s->dma_len; i++) {
> + buf[i] = 0xA0u + (i & 0x1fu);
> + }
> + res = pci_dma_write(PCI_DEVICE(&s->parent_obj), s->dma_iova,
> + buf, s->dma_len);
> + }
> + } else {
> + SMMUTestDevSpace dma_space =
> + (SMMUTestDevSpace)((s->dma_attrs_cfg >> 1) & 0x3);
> + if (!smmu_testdev_space_supported(dma_space)) {
> + /* Default to Non-Secure until other spaces are modeled. */
> + dma_space = STD_SPACE_NONSECURE;
> + }
> + MemTxAttrs attrs = {
> + .secure = 0,
> + .space = dma_space,
> + .unspecified = (s->dma_attrs_cfg & (1u << 3)) ? 1 : 0,
> + };
> + /*
> + * If 'unspecified' is set, bypass IOMMU AS and use system memory.
> + * This helps tests that want deterministic success without full
> + * IOMMU programming.
> + */
> + AddressSpace *as = (s->dma_as && !attrs.unspecified)
> + ? s->dma_as
> + : &address_space_memory;
> + if (s->dma_dir == DMA_DIR_HOST2DEV) {
> + res = dma_memory_read(as, s->dma_iova, buf, s->dma_len, attrs);
> + } else {
> + for (uint32_t i = 0; i < s->dma_len; i++) {
> + buf[i] = 0xA0u + (i & 0x1fu);
> + }
> + res = dma_memory_write(as, s->dma_iova, buf, s->dma_len, attrs);
> + }
> + }
> + s->dma_result = (res == MEMTX_OK) ? 0 : DMA_ERR_TX_FAIL;
> + smmu_testdev_debug(s, "iommu ret %d , dma_result: 0x%x\n",
> + res, s->dma_result);
> +}
> +
> +static uint64_t smmu_testdev_mmio_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + SMMUTestDevState *s = opaque;
> + switch (addr) {
> + case REG_ID:
> + /*
> + * Only reads of REG_ID intentionally trigger the side effects
> + * (SMMU CR0 write and pending DMA). This lets tests poll
> + * REG_DMA_RESULT to observe BUSY before consuming the DMA.
> + */
> + smmu_testdev_maybe_run_dma(s);
> + return 0x53544d4du; /* 'STMM' */
> + case REG_ATTR_NS:
> + return s->attr_ns;
> + case REG_SMMU_BASE_LO:
> + return (uint32_t)(s->smmu_base & 0xffffffffu);
> + case REG_SMMU_BASE_HI:
> + return (uint32_t)(s->smmu_base >> 32);
> + case REG_DMA_IOVA_LO:
> + return (uint32_t)(s->dma_iova & 0xffffffffu);
> + case REG_DMA_IOVA_HI:
> + return (uint32_t)(s->dma_iova >> 32);
> + case REG_DMA_LEN:
> + return s->dma_len;
> + case REG_DMA_DIR:
> + return s->dma_dir;
> + case REG_DMA_RESULT:
> + return s->dma_result;
> + case REG_DMA_MODE:
> + return s->dma_mode;
> + case REG_DMA_ATTRS:
> + return s->dma_attrs_cfg;
> + case REG_TRANS_MODE:
> + return s->trans_mode;
> + case REG_S1_SPACE:
> + return s->s1_space;
> + case REG_S2_SPACE:
> + return s->s2_space;
> + case REG_TRANS_STATUS:
> + return s->trans_status;
> + default:
> + return 0;
> + }
> +}
> +
> +static void smmu_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
> + unsigned size)
> +{
> + SMMUTestDevState *s = opaque;
> + uint32_t data = val;
> +
> + switch (addr) {
> + case REG_ID:
> + if (data == 0x1) {
> + smmu_testdev_refresh_attrs(s);
> + }
> + break;
> + case REG_SMMU_BASE_LO:
> + s->smmu_base = (s->smmu_base & ~0xffffffffull) | data;
> + break;
> + case REG_SMMU_BASE_HI:
> + s->smmu_base = (s->smmu_base & 0xffffffffull) |
> + ((uint64_t)data << 32);
> + break;
> + case REG_DMA_IOVA_LO:
> + s->dma_iova = (s->dma_iova & ~0xffffffffull) | data;
> + break;
> + case REG_DMA_IOVA_HI:
> + s->dma_iova = (s->dma_iova & 0xffffffffull) |
> + ((uint64_t)data << 32);
> + break;
> + case REG_DMA_LEN:
> + s->dma_len = data;
> + break;
> + case REG_DMA_DIR:
> + s->dma_dir = data ? DMA_DIR_HOST2DEV : DMA_DIR_DEV2HOST;
> + break;
> + case REG_DMA_RESULT:
> + s->dma_result = data;
> + break;
> + case REG_DMA_DOORBELL:
> + if (data & 0x1) {
> + s->dma_pending = true;
> + s->dma_result = DMA_RESULT_BUSY;
> + } else {
> + s->dma_pending = false;
> + s->dma_result = DMA_RESULT_IDLE;
> + }
> + break;
> + case REG_DMA_MODE:
> + s->dma_mode = data & 0x1;
> + break;
> + case REG_DMA_ATTRS:
> + s->dma_attrs_cfg = data;
> + break;
> + case REG_TRANS_MODE:
> + s->trans_mode = data & 0x3;
> + break;
> + case REG_S1_SPACE:
> + s->s1_space = (SMMUTestDevSpace)(data & 0x3);
> + break;
> + case REG_S2_SPACE:
> + s->s2_space = (SMMUTestDevSpace)(data & 0x3);
> + break;
> + case REG_TRANS_DBELL:
> + if (data & 0x2) {
> + s->trans_status = 0;
> + }
> + if (data & 0x1) {
> + smmu_testdev_build_translation(s);
> + }
> + break;
> + case REG_TRANS_CLEAR:
> + /* Clear helper caches so the next iteration rebuilds cleanly. */
> + smmu_testdev_clear_caches(s);
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps smmu_testdev_mmio_ops = {
> + .read = smmu_testdev_mmio_read,
> + .write = smmu_testdev_mmio_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void smmu_testdev_realize(PCIDevice *pdev, Error **errp)
> +{
> + SMMUTestDevState *s = SMMU_TESTDEV(pdev);
> +
> + /* Apply user-configurable BDF (default 0:1). */
> + uint8_t dev = s->cfg_dev & 0x1f;
> + uint8_t fn = s->cfg_fn & 0x7;
> + pdev->devfn = (dev << 3) | fn;
> +
> + smmu_testdev_refresh_attrs(s);
> + s->smmu_base = 0;
> + s->dma_iova = 0;
> + s->dma_len = 0;
> + s->dma_dir = DMA_DIR_DEV2HOST;
> + s->dma_result = DMA_RESULT_IDLE;
> + s->dma_pending = false;
> + s->dma_mode = 0;
> + s->dma_attrs_cfg = 0;
> + s->dma_as = pci_device_iommu_address_space(pdev);
> + s->trans_mode = TM_S2_ONLY;
> + s->s1_space = STD_SPACE_NONSECURE;
> + s->s2_space = STD_SPACE_NONSECURE;
> + s->trans_status = 0;
> +
> + if (s->debug_log) {
> + smmu_testdev_debug(s, "[smmu-testdev] debug logging enabled\n");
> + }
> +
> + memory_region_init_io(&s->bar0, OBJECT(pdev), &smmu_testdev_mmio_ops, s,
> + TYPE_SMMU_TESTDEV ".bar0", BAR0_SIZE);
> + pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
> +}
> +
> +static void smmu_testdev_reset(DeviceState *dev)
> +{
> + SMMUTestDevState *s = SMMU_TESTDEV(dev);
> +
> + smmu_testdev_refresh_attrs(s);
> + s->smmu_base = 0;
> + s->dma_iova = 0;
> + s->dma_len = 0;
> + s->dma_dir = DMA_DIR_DEV2HOST;
> + s->dma_result = DMA_RESULT_IDLE;
> + s->dma_pending = false;
> + s->dma_mode = 0;
> + s->dma_attrs_cfg = 0;
> + s->trans_mode = TM_S2_ONLY;
> + s->s1_space = STD_SPACE_NONSECURE;
> + s->s2_space = STD_SPACE_NONSECURE;
> + s->trans_status = 0;
> + /* Keep cfg_dev/cfg_fn as-is across reset */
> +}
> +
> +static const Property smmu_testdev_properties[] = {
> + DEFINE_PROP_UINT32("device", SMMUTestDevState, cfg_dev, 0),
> + DEFINE_PROP_UINT32("function", SMMUTestDevState, cfg_fn, 1),
> + DEFINE_PROP_BOOL("debug-log", SMMUTestDevState, debug_log,
> false),
We have tracepoints so we don't need this flag.
> +};
> +
> +static void smmu_testdev_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
> +
> + pc->realize = smmu_testdev_realize;
> + pc->vendor_id = PCI_VENDOR_ID_REDHAT;
> + pc->device_id = PCI_DEVICE_ID_REDHAT_TEST;
> + pc->revision = 0;
> + pc->class_id = PCI_CLASS_OTHERS;
> + dc->desc = "A test device for the SMMU";
> + set_bit(DEVICE_CATEGORY_MISC, dc->categories);
> + device_class_set_legacy_reset(dc, smmu_testdev_reset);
> + device_class_set_props(dc, smmu_testdev_properties);
> +}
> +
> +static void smmu_testdev_instance_init(Object *obj)
> +{
> + SMMUTestDevState *s = SMMU_TESTDEV(obj);
> + s->cfg_dev = 0;
> + s->cfg_fn = 1; /* default StreamID = 1 (slot 0, fn 1) */
> + s->debug_log = false;
> +}
> +
> +static const TypeInfo smmu_testdev_info = {
> + .name = TYPE_SMMU_TESTDEV,
> + .parent = TYPE_PCI_DEVICE,
> + .instance_size = sizeof(SMMUTestDevState),
> + .instance_init = smmu_testdev_instance_init,
> + .class_init = smmu_testdev_class_init,
> + .interfaces = (const InterfaceInfo[]) {
> + { INTERFACE_CONVENTIONAL_PCI_DEVICE },
> + { }
> + },
> +};
> +
> +static void smmu_testdev_register_types(void)
> +{
> + type_register_static(&smmu_testdev_info);
> +}
> +
> +type_init(smmu_testdev_register_types);
> diff --git a/include/hw/misc/smmu-testdev.h b/include/hw/misc/smmu-testdev.h
> new file mode 100644
> index 0000000000..6d97f6c704
> --- /dev/null
> +++ b/include/hw/misc/smmu-testdev.h
> @@ -0,0 +1,402 @@
> +/*
> + * A test device for the SMMU
> + *
> + * This test device is a minimal SMMU-aware device used to test the SMMU.
> + *
> + * Copyright (c) 2025 Phytium Technology
> + *
> + * Author:
> + * Tao Tang <tangtao1634@phytium.com.cn>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_SMMU_TESTDEV_H
> +#define HW_MISC_SMMU_TESTDEV_H
> +
> +#include "qemu/osdep.h"
> +typedef enum SMMUTestDevSpace {
> + STD_SPACE_SECURE = 0,
> + STD_SPACE_NONSECURE = 1,
> + STD_SPACE_ROOT = 2,
> + STD_SPACE_REALM = 3,
> +} SMMUTestDevSpace;
> +
> +/* Only the Non-Secure space is implemented; leave room for future domains. */
> +#define STD_SUPPORTED_SPACES 1
> +
> +/* BAR0 registers (offsets) */
> +enum {
> + STD_REG_ID = 0x00,
> + STD_REG_ATTR_NS = 0x04,
> + STD_REG_SMMU_BASE_LO = 0x20,
> + STD_REG_SMMU_BASE_HI = 0x24,
> + STD_REG_DMA_IOVA_LO = 0x28,
> + STD_REG_DMA_IOVA_HI = 0x2C,
> + STD_REG_DMA_LEN = 0x30,
> + STD_REG_DMA_DIR = 0x34,
> + STD_REG_DMA_RESULT = 0x38,
> + STD_REG_DMA_DBELL = 0x3C,
> + /* Extended controls for DMA attributes/mode */
> + STD_REG_DMA_MODE = 0x40,
> + STD_REG_DMA_ATTRS = 0x44,
> + /* Translation controls */
> + STD_REG_TRANS_MODE = 0x48,
> + STD_REG_S1_SPACE = 0x4C,
> + STD_REG_S2_SPACE = 0x50,
> + STD_REG_TRANS_DBELL = 0x54,
> + STD_REG_TRANS_STATUS = 0x58,
> + /* Clear helper-built tables/descriptors (write-any to trigger) */
> + STD_REG_TRANS_CLEAR = 0x5C,
> +};
Aren't we just duplicating the anonymous enum in smmu-testdev.c here? I
think qtest is allowed to include qemu headers so lets just have a
common set please.
> +
> +/* DMA result/status values shared with tests */
> +#define STD_DMA_RESULT_IDLE 0xffffffffu
> +#define STD_DMA_RESULT_BUSY 0xfffffffeu
> +#define STD_DMA_ERR_BAD_LEN 0xdead0001u
> +#define STD_DMA_ERR_TX_FAIL 0xdead0002u
> +
> +/* DMA attributes layout (for STD_REG_DMA_ATTRS) */
> +#define STD_DMA_ATTR_SECURE (1u << 0)
> +#define STD_DMA_ATTR_SPACE_SHIFT 1
> +#define STD_DMA_ATTR_SPACE_MASK (0x3u << STD_DMA_ATTR_SPACE_SHIFT)
> +#define STD_DMA_ATTR_UNSPECIFIED (1u << 3)
> +
> +/* Command type */
> +#define STD_CMD_CFGI_STE 0x03
> +#define STD_CMD_CFGI_CD 0x05
> +#define STD_CMD_TLBI_NSNH_ALL 0x30
> +
> +/*
> + * Translation tables and descriptors for a mapping of IOVA to GPA.
> + *
> + * This file defines a set of constants used to construct a static page table
> + * for an smmu-testdev device. The goal is to translate a specific STD_IOVA
> + * into a final GPA.
> + * The translation is based on the Arm architecture with the following
> + * prerequisites:
> + * - Granule size: 4KB pages.
> + * - Page table levels: 4 levels (L0, L1, L2, L3), starting at level 0.
> + * - IOVA size: The walk resolves a 39-bit IOVA (0x8080604567).
> + * - Address space: The 4-level lookup with 4KB granules supports up to a
> + * 48-bit (256TB) virtual address space. Each level uses a 9-bit index
> + * (512 entries per table). The breakdown is:
> + * - L0 index: IOVA bits [47:39]
> + * - L1 index: IOVA bits [38:30]
> + * - L2 index: IOVA bits [29:21]
> + * - L3 index: IOVA bits [20:12]
> + * - Page offset: IOVA bits [11:0]
> + *
> + * NOTE: All physical addresses defined here (STD_VTTB, table addresses, etc.)
> + * appear to be within a secure RAM region. In practice, an offset is added
> + * to these values to place them in non-secure RAM. For example, when running
> + * in a virt machine type, the RAM base address (e.g., 0x40000000) is added to
> + * these constants.
> + *
> + * The page table walk for STD_IOVA (0x8080604567) proceeds as follows:
> + *
> + * The Translation Table Base (for both Stage 1 CD_TTB and Stage 2 STE_S2TTB)
> + * is set to STD_VTTB (0xe4d0000).
> + *
> + * 1. Level 0 (L0) Table Walk:
> + * l0_index = (0x8080604567 >> 39) & 0x1ff = 1
> + * STD_L0_ADDR = STD_VTTB + (l0_index * 8) = 0xe4d0000 + 8 = 0xe4d0008
> + * STD_L0_VAL = 0xe4d1003
> + * The next level table base address = STD_L0_VAL & ~0xfff = 0xe4d1000
> + *
> + * 2. Level 1 (L1) Table Walk:
> + * l1_index = (0x8080604567 >> 30) & 0x1ff = 2
> + * STD_L1_ADDR = 0xe4d1000 + (l1_index * 8) = 0xe4d1000 + 16 = 0xe4d1010
> + * STD_L1_VAL = 0xe4d2003
> + * The next level table base address = STD_L1_VAL & ~0xfff = 0xe4d2000
> + *
> + * 3. Level 2 (L2) Table Walk:
> + * l2_index = (0x8080604567 >> 21) & 0x1ff = 3
> + * STD_L2_ADDR = 0xe4d2000 + (l2_index * 8) = 0xe4d2000 + 24 = 0xe4d2018
> + * STD_L2_VAL = 0xe4d3003
> + * The next level table base address = STD_L2_VAL & ~0xfff = 0xe4d3000
> + *
> + * 4. Level 3 (L3) Table Walk (Leaf):
> + * l3_index = (0x8080604567 >> 12) & 0x1ff = 4
> + * STD_L3_ADDR = 0xe4d3000 + (l3_index * 8) = 0xe4d3000 + 32 = 0xe4d3020
> + * STD_L3_VAL = 0x040000000ECBA7C3
> + * The next level table base address = STD_L3_VAL & ~0xfff = 0xecba000
> + *
> + * 5. Final GPA Calculation:
> + * - The final output physical address is formed by combining the address from
> + * the leaf descriptor with the original IOVA's page offset.
> + * - Output Page Base Address = (STD_L3_VAL & ~0xFFFULL) = 0xECBA000
> + * - Page Offset = (STD_IOVA & 0xFFFULL) = 0x567.
> + * - Final GPA = Output Page Base Address + Page Offset
> + * = 0xECBA000 + 0x567 = 0xECBA567
> + */
> +
> +#define STD_IOVA 0x0000008080604567ULL
> +
> +#define STD_VTTB 0xe4d0000
> +
> +#define STD_STR_TAB_BASE 0x000000000E179000ULL
> +#define STD_STE_GPA (STD_STR_TAB_BASE + 0x40ULL)
> +#define STD_CD_GPA (STD_STR_TAB_BASE + 0x80ULL)
> +
> +/* Page table structures */
> +#define STD_L0_ADDR 0x000000000E4D0008ULL
> +#define STD_L1_ADDR 0x000000000E4D1010ULL
> +#define STD_L2_ADDR 0x000000000E4D2018ULL
> +#define STD_L3_ADDR 0x000000000E4D3020ULL
> +#define STD_L0_VAL 0x000000000E4D1003ULL
> +#define STD_L1_VAL 0x000000000E4D2003ULL
> +#define STD_L2_VAL 0x000000000E4D3003ULL
> +#define STD_L3_VAL 0x040000000ECBA7C3ULL
> +
> +/*
> + * Nested stage PTW maybe a bit more complex. We share the page tables in
> + * nested stage 2 to avoid complicated definitions here. That is to say:
> + *
> + * At each level of the Stage 1 page table walk, a corresponding 4-level Stage 2
> + * page table walk is performed. The intermediate Stage 2 page tables are shared
> + * across these walks, with the key connecting PTE values being:
> + * - l0_pte_val=0x4e4d1003
> + * - l1_pte_val=0x4e4d2003
> + * - l2_pte_val=0x4e4d3003
> + *
> + *
> + * ======================================================================
> + * Nested Page Table Walk (Stage 1 + Stage 2) Example
> + * ======================================================================
> + *
> + * Goal: Translate IOVA 0x8080604567 to a final Physical Address (PA).
> + *
> + * Prerequisites:
> + * - Stage 1 VTTB (as IPA): 0x4e4d0000
> + * - Stage 2 VTTB (as PA): 0x4e4d0000
> + *
> + * ----------------------------------------------------------------------
> + * 1. Stage 1 Page Table Walk (IOVA -> IPA)
> + * ----------------------------------------------------------------------
> + *
> + * Level 0 (L0) Walk (IPA as PA)
> + * =============================
> + * iova = 0x8080604567
> + * l0_index = (0x8080604567 >> 39) & 0x1ff = 1
> + * s1_l0_pte_addr (IPA) = 0x4e4d0000 + (1 * 8) = 0x4e4d0008
> + *
> + * --> Nested Stage 2 Walk for S1 L0 Table (IPA 0x4e4d0000 -> PA 0x4e4d0000)
> + * -------------------------------------------------------------------------
> + * ipa_to_translate = 0x4e4d0000
> + * s2_vttb = 0x4e4d0000
> + * s2_l0_index = (0x4e4d0000 >> 39) & 0x1ff = 0
> + * s2_l0_pte_addr (PA) = 0x4e4d0000 + (0 * 8) = 0x4e4d0000
> + * s2_l0_pte_val = 0x4e4d1003
> + * s2_l1_table_base (PA) = 0x4e4d1003 & ~0xfff = 0x4e4d1000
> + * s2_l1_index = (0x4e4d0000 >> 30) & 0x1ff = 1
> + * s2_l1_pte_addr (PA) = 0x4e4d1000 + (1 * 8) = 0x4e4d1008
> + * s2_l1_pte_val = 0x4e4d2003
> + * s2_l2_table_base (PA) = 0x4e4d2003 & ~0xfff = 0x4e4d2000
> + * s2_l2_index = (0x4e4d0000 >> 21) & 0x1ff = 114 (0x72)
> + * s2_l2_pte_addr (PA) = 0x4e4d2000 + (114 * 8) = 0x4e4d2390
> + * s2_l2_pte_val = 0x4e4d3003
> + * s2_l3_table_base (PA) = 0x4e4d3003 & ~0xfff = 0x4e4d3000
> + * s2_l3_index = (0x4e4d0000 >> 12) & 0x1ff = 208 (0xd0)
> + * s2_l3_pte_addr (PA) = 0x4e4d3000 + (208 * 8) = 0x4e4d3680
> + * s2_l3_pte_val = 0x040000000E4D0743ULL
> + * output_page_base (PA) = 0x040000000E4D0743ULL & ~0xfff = 0x4e4d0000
> + * Final PA for table = 0x4e4d0000 + (0x0000 & 0xfff) = 0x4e4d0000
> + *
> + * s1_l0_pte_val (read from PA 0x4e4d0008) = 0x4e4d1003
> + * s1_l1_table_base (IPA) = 0x4e4d1003 & ~0xfff = 0x4e4d1000
> + *
> + * Level 1 (L1) Walk (IPA as PA)
> + * =============================
> + * iova = 0x8080604567
> + * l1_index = (0x8080604567 >> 30) & 0x1ff = 2
> + * s1_l1_pte_addr (IPA) = 0x4e4d1000 + (2 * 8) = 0x4e4d1010
> + *
> + * --> Nested Stage 2 Walk for S1 L1 Table (IPA 0x4e4d1000 -> PA 0x4e4d1000)
> + * -------------------------------------------------------------------------
> + * ipa_to_translate = 0x4e4d1000
> + * s2_vttb = 0x4e4d0000
> + * s2_l0_index = (0x4e4d1000 >> 39) & 0x1ff = 0
> + * s2_l1_table_base (PA) = 0x4e4d1000
> + * s2_l1_index = (0x4e4d1000 >> 30) & 0x1ff = 1
> + * s2_l2_table_base (PA) = 0x4e4d2000
> + * s2_l2_index = (0x4e4d1000 >> 21) & 0x1ff = 114 (0x72)
> + * s2_l3_table_base (PA) = 0x4e4d3000
> + * s2_l3_index = (0x4e4d1000 >> 12) & 0x1ff = 209 (0xd1)
> + * s2_l3_pte_addr (PA) = 0x4e4d3000 + (209 * 8) = 0x4e4d3688
> + * s2_l3_pte_val = 0x40000004e4d1743
> + * output_page_base (PA) = 0x40000004e4d1743 & ~0xfff = 0x4e4d1000
> + * Final PA for table = 0x4e4d1000 + (0x1000 & 0xfff) = 0x4e4d1000
> + *
> + * s1_l1_pte_val (read from PA 0x4e4d1010) = 0x4e4d2003
> + * s1_l2_table_base (IPA) = 0x4e4d2003 & ~0xfff = 0x4e4d2000
> + *
> + * Level 2 (L2) Walk (IPA as PA)
> + * =============================
> + * l2_index = (0x8080604567 >> 21) & 0x1ff = 3
> + * s1_l2_pte_addr (IPA) = 0x4e4d2000 + (3 * 8) = 0x4e4d2018
> + *
> + * --> Nested Stage 2 Walk for S1 L2 Table (IPA 0x4e4d2000 -> PA 0x4e4d2000)
> + * -------------------------------------------------------------------------
> + * ipa_to_translate = 0x4e4d2000
> + * s2_l0_index = (0x4e4d2000 >> 39) & 0x1ff = 0
> + * s2_l1_table_base (PA) = 0x4e4d1000
> + * s2_l1_index = (0x4e4d2000 >> 30) & 0x1ff = 1
> + * s2_l2_table_base (PA) = 0x4e4d2000
> + * s2_l2_index = (0x4e4d2000 >> 21) & 0x1ff = 114 (0x72)
> + * s2_l3_table_base (PA) = 0x4e4d3000
> + * s2_l3_index = (0x4e4d2000 >> 12) & 0x1ff = 210 (0xd2)
> + * s2_l3_pte_addr (PA) = 0x4e4d3000 + (210 * 8) = 0x4e4d3690
> + * s2_l3_pte_val = 0x40000004e4d2743
> + * output_page_base (PA) = 0x40000004e4d2743 & ~0xfff = 0x4e4d2000
> + * Final PA for table = 0x4e4d2000 + (0x2000 & 0xfff) = 0x4e4d2000
> + *
> + * s1_l2_pte_val (read from PA 0x4e4d2018) = 0x4e4d3003
> + * s1_l3_table_base (IPA) = 0x4e4d3003 & ~0xfff = 0x4e4d3000
> + *
> + * Level 3 (L3) Walk (Leaf, IPA as PA)
> + * ===================================
> + * l3_index = (0x8080604567 >> 12) & 0x1ff = 4
> + * s1_l3_pte_addr (IPA) = 0x4e4d3000 + (4 * 8) = 0x4e4d3020
> + *
> + * --> Nested Stage 2 Walk for S1 L3 Table (IPA 0x4e4d3000 -> PA 0x4e4d3000)
> + * -------------------------------------------------------------------------
> + * ipa_to_translate = 0x4e4d3000
> + * s2_l0_index = (0x4e4d3000 >> 39) & 0x1ff = 0
> + * s2_l1_table_base (PA) = 0x4e4d1000
> + * s2_l1_index = (0x4e4d3000 >> 30) & 0x1ff = 1
> + * s2_l2_table_base (PA) = 0x4e4d2000
> + * s2_l2_index = (0x4e4d3000 >> 21) & 0x1ff = 114 (0x72)
> + * s2_l3_table_base (PA) = 0x4e4d3000
> + * s2_l3_index = (0x4e4d3000 >> 12) & 0x1ff = 211 (0xd3)
> + * s2_l3_pte_addr (PA) = 0x4e4d3000 + (211 * 8) = 0x4e4d3698
> + * s2_l3_pte_val = 0x40000004e4d3743
> + * output_page_base (PA) = 0x40000004e4d3743 & ~0xfff = 0x4e4d3000
> + * Final PA for table = 0x4e4d3000 + (0x3000 & 0xfff) = 0x4e4d3000
> + *
> + * s1_l3_pte_val (read from PA 0x4e4d3020) = 0x40000004ecba743
> + * output_page_base (IPA) = 0x40000004ecba743 & ~0xfff = 0x4ecba000
> + * page_offset = 0x8080604567 & 0xfff = 0x567
> + * Final IPA = 0x4ecba000 + 0x567 = 0x4ecba567
> + *
> + * ----------------------------------------------------------------------
> + * 2. Final Stage 2 Page Table Walk (Final IPA -> PA)
> + * ----------------------------------------------------------------------
> + *
> + * ipa = 0x4ecba567
> + *
> + * s2_l0_index = (0x4ecba567 >> 39) & 0x1ff = 0
> + * s2_l1_table_base (PA) = 0x4e4d1000
> + *
> + * s2_l1_index = (0x4ecba567 >> 30) & 0x1ff = 1
> + * s2_l2_table_base (PA) = 0x4e4d2000
> + *
> + * s2_l2_index = (0x4ecba567 >> 21) & 0x1ff = 118 (0x76)
> + * s2_l3_table_base (PA) = 0x4e4d3000
> + *
> + * s2_l3_index = (0x4ecba567 >> 12) & 0x1ff = 186 (0xba)
> + * s2_l3_pte_addr (PA) = 0x4e4d3000 + (186 * 8) = 0x4e4d35d0
> + * s2_l3_pte_val = 0x40000004ecba7c3
> + * output_page_base (PA) = 0x40000004ecba7c3 & ~0xfff = 0x4ecba000
> + * page_offset = 0x4ecba567 & 0xfff = 0x567
> + *
> + * ----------------------------------------------------------------------
> + * 3. Final Result
> + * ----------------------------------------------------------------------
> + * Final PA = 0x4ecba000 + 0x567 = 0x4ecba567
> + *
> + * ----------------------------------------------------------------------
> + * 4. Appendix: Context Descriptor (CD) Fetch Walk
> + * ----------------------------------------------------------------------
> + * Before any S1 walk can begin, the SMMU must fetch the Context Descriptor.
> + * The CD's address is an IPA, so it also requires a full S2 walk. This
> + * walk RE-USES the exact same S2 page tables shown above.
> + *
> + * ipa = 0x4e179080 (Address of the CD)
> + *
> + * s2_l0_index = (0x4e179080 >> 39) & 0x1ff = 0
> + * s2_l0_pte_val = 0x4e4d1003
> + * s2_l1_table_base (PA) = 0x4e4d1000 (*RE-USED*)
> + *
> + * s2_l1_index = (0x4e179080 >> 30) & 0x1ff = 1
> + * s2_l1_pte_val = 0x4e4d2003
> + * s2_l2_table_base (PA) = 0x4e4d2000 (*RE-USED*)
> + *
> + * s2_l2_index = (0x4e179080 >> 21) & 0x1ff = 112 (0x70)
> + * s2_l2_pte_val = 0x4e4d3003
> + * s2_l3_table_base (PA) = 0x4e4d3000 (*RE-USED*)
> + *
> + * s2_l3_index = (0x4e179080 >> 12) & 0x1ff = 377 (0x179)
> + * s2_l3_pte_addr (PA) = 0x4e4d3000 + (377 * 8) = 0x4e4d3bc8
> + * s2_l3_pte_val = 0x40000004e179743
> + * output_page_base (PA) = 0x40000004e179743 & ~0xfff = 0x4e179000
> + * page_offset = 0x4e179080 & 0xfff = 0x080
> + *
> + * Final PA for CD = 0x4e179000 + 0x080 = 0x4e179080
> + *
> + */
> +#define STD_CD_S2_L0_ADDR 0x000000000E4D0000ULL
> +#define STD_CD_S2_L1_ADDR 0x000000000E4D1008ULL
> +#define STD_CD_S2_L2_ADDR 0x000000000E4D2380ULL
> +#define STD_CD_S2_L3_ADDR 0x000000000E4D3BC8ULL
> +#define STD_CD_S2_L3_VAL 0x040000000E179743ULL
> +
> +#define STD_CDTTB_S2_L2_ADDR 0x000000000E4D2390ULL
> +#define STD_CDTTB_S2_L3_ADDR 0x000000000E4D3680ULL
> +#define STD_CDTTB_S2_L3_VAL 0x040000000E4D0743ULL
> +
> +#define STD_L3_S1_VAL 0x040000000ECBA743ULL
> +
> +#define STD_S1L0_IN_S2L3_ADDR 0x000000000E4D3688ULL
> +#define STD_S1L0_IN_S2L3_VAL 0x040000000E4D1743ULL
> +#define STD_S1L1_IN_S2L3_ADDR 0x000000000E4D3690ULL
> +#define STD_S1L1_IN_S2L3_VAL 0x040000000E4D2743ULL
> +#define STD_S1L2_IN_S2L3_ADDR 0x000000000E4D3698ULL
> +#define STD_S1L2_IN_S2L3_VAL 0x040000000E4D3743ULL
> +#define STD_S1L3_IN_S2L2_ADDR 0x000000000E4D23B0ULL
> +#define STD_S1L3_IN_S2L2_VAL 0x000000000E4D3003ULL
> +#define STD_S1L3_IN_S2L3_ADDR 0x000000000E4D35D0ULL
> +#define STD_S1L3_IN_S2L3_VAL 0x040000000ECBA7C3ULL
> +
> +/*
> + * Address-space base offsets for test tables.
> + * - Non-Secure uses a fixed offset, keeping internal layout identical.
> + *
> + * Note: Future spaces (e.g. Secure/Realm/Root) are not implemented here.
> + * When needed, introduce new offsets and reuse the helpers below so
> + * relative layout stays identical across spaces.
> + */
> +#define STD_SPACE_OFFS_NS 0x40000000ULL
> +
> +static inline uint64_t std_space_offset(SMMUTestDevSpace sp)
> +{
> + switch (sp) {
> + case STD_SPACE_NONSECURE:
> + return STD_SPACE_OFFS_NS;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +static const char *std_space_to_str(SMMUTestDevSpace sp)
> +{
> + switch (sp) {
> + case STD_SPACE_NONSECURE:
> + return "Non-Secure";
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +static const char *std_mode_to_str(uint32_t m)
> +{
> + switch (m & 0x3) {
> + case 0: return "S1-only";
> + case 1: return "S2-only";
> + case 2: return "Nested";
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +#endif /* HW_MISC_SMMU_TESTDEV_H */
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
Hi Alex,
On 2025/10/23 18:31, Alex Bennée wrote:
> tangtao1634 <tangtao1634@phytium.com.cn> writes:
>
>> From: Tao Tang <tangtao1634@phytium.com.cn>
>>
>> Add a tiny, test-only DMA source dedicated to exercising the SMMUv3 model.
>> The device purposefully avoids a realistic PCIe/platform implementation and
>> instead routes DMA requests straight into the SMMU, so that qtests can
>> populate STE/CD/PTE with known values and observe translation and data
>> movement deterministically, without booting any firmware or guest kernel.
>>
>> Motivation
>> ----------
>> Bringing up and regression-testing the SMMU in emulation often depends on a
>> large and flaky software stack (enumeration, drivers, PCIe fabric). For the
>> class of tests that only need to (1) program translation structures and (2)
>> trigger DMA at a precise time, that stack adds noise, slows CI, and makes
>> failures harder to attribute to the SMMU itself. A hermetic DMA source
>> keeps the surface area small and the results reproducible.
>>
>> What this device is (and is not)
>> --------------------------------
>> * It is a minimal DMA producer solely for SMMU tests.
>> * It is NOT a faithful PCIe Endpoint nor a platform device.
>> * It is NOT added to any machine by default and remains test-only.
>>
>> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
>> ---
>> hw/misc/Kconfig | 5 +
>> hw/misc/meson.build | 1 +
>> hw/misc/smmu-testdev.c | 943 +++++++++++++++++++++++++++++++++
>> include/hw/misc/smmu-testdev.h | 402 ++++++++++++++
>> 4 files changed, 1351 insertions(+)
>> create mode 100644 hw/misc/smmu-testdev.c
>> create mode 100644 include/hw/misc/smmu-testdev.h
>>
>> ------------------------------<snip>------------------------------
>>
>>
>>
>> ------------------------------<snip>------------------------------
>> +/* ---- Debug helpers for printing current translation configuration ---- */
>> +static void G_GNUC_PRINTF(2, 3)
>> +smmu_testdev_debug(const SMMUTestDevState *s, const char *fmt, ...)
>> +{
>> + va_list ap;
>> + g_autofree char *msg = NULL;
>> +
>> + if (!s->debug_log) {
>> + return;
>> + }
>> +
>> + va_start(ap, fmt);
>> + msg = g_strdup_vprintf(fmt, ap);
>> + va_end(ap);
>> +
>> + if (qemu_log_enabled()) {
>> + qemu_log("%s", msg);
>> + } else {
>> + fprintf(stderr, "%s", msg);
>> + }
>> +}
> Why are we re-inventing logging here? Either use qemu_log or probably
> better define tracepoints.
>
>> +
>> +static MemTxAttrs mk_attrs_from_space(SMMUTestDevSpace space)
>> +{
>> + MemTxAttrs a = {0};
>> + if (!smmu_testdev_space_supported(space)) {
>> + g_assert_not_reached();
> Isn't this the same as just saying:
>
> g_assert(smmu_testdev_space_supported(space));
>
> ?
>
>> +static void smmu_testdev_build_translation(SMMUTestDevState *s)
>> +{
>> + smmu_testdev_debug(s, "smmu_testdev_build_translation: stage=%s s1_space=%s"
>> + " s2_space=%s\n", std_mode_to_str(s->trans_mode),
>> + std_space_to_str(s->s1_space),
>> + std_space_to_str(s->s2_space));
> tracepoint
>
>> + if (st != 0) {
>> + printf("Writing STE error! status: %x\n", st);
>
> qemu_log_mask(LOG_GUEST_ERROR, ...
>
>> + AddressSpace *as = space_to_as(bank_sp);
>> + if (!as) {
>> + printf("push_cfgi_cmd: space %d not supported\n", bank_sp);
> qemu_log_mask(LOG_UNIMP?
>
>> + return;
>> + }
>> + int ret = address_space_write(as, entry_pa, a,
>> + words, sizeof(words));
>> + smmu_testdev_debug(s, "push_cfgi_cmd ret %d\n", ret);
> tracepoint
>
>> +
>> + /* update PROD to trigger command handler */
>> + uint32_t new_prod = (prod + 1) & ((1u << (log2size + 1)) - 1u);
>> + address_space_stl_le(&address_space_memory,
>> + s->smmu_base + bank_off + 0x98,
>> + new_prod, MEMTXATTRS_UNSPECIFIED, &res);
>> + smmu_testdev_debug(s, "last res %d\n", res);
> tracepoint but really see if it can be merged with above.
>
>> + smmu_testdev_debug(s, "smmu_testdev_maybe_run_dma: dma_pending: %d\n",
>> + s->dma_pending);
> tracepoint
>
>> +
>> + s->dma_pending = false;
>> +
>> + if (!s->dma_len || s->dma_len > DMA_MAX_LEN) {
>> + s->dma_result = DMA_ERR_BAD_LEN;
>> + return;
>> + }
>> +
>> + g_autofree uint8_t *buf = g_malloc(s->dma_len);
>> + MemTxResult res;
> Keep local vars at the top of the block.
>
>> +static const Property smmu_testdev_properties[] = {
>> + DEFINE_PROP_UINT32("device", SMMUTestDevState, cfg_dev, 0),
>> + DEFINE_PROP_UINT32("function", SMMUTestDevState, cfg_fn, 1),
>> + DEFINE_PROP_BOOL("debug-log", SMMUTestDevState, debug_log,
>> false),
> We have tracepoints so we don't need this flag.
>
>> +/* BAR0 registers (offsets) */
>> +enum {
>> + STD_REG_ID = 0x00,
>> + STD_REG_ATTR_NS = 0x04,
>> + STD_REG_SMMU_BASE_LO = 0x20,
>> + STD_REG_SMMU_BASE_HI = 0x24,
>> + STD_REG_DMA_IOVA_LO = 0x28,
>> + STD_REG_DMA_IOVA_HI = 0x2C,
>> + STD_REG_DMA_LEN = 0x30,
>> + STD_REG_DMA_DIR = 0x34,
>> + STD_REG_DMA_RESULT = 0x38,
>> + STD_REG_DMA_DBELL = 0x3C,
>> + /* Extended controls for DMA attributes/mode */
>> + STD_REG_DMA_MODE = 0x40,
>> + STD_REG_DMA_ATTRS = 0x44,
>> + /* Translation controls */
>> + STD_REG_TRANS_MODE = 0x48,
>> + STD_REG_S1_SPACE = 0x4C,
>> + STD_REG_S2_SPACE = 0x50,
>> + STD_REG_TRANS_DBELL = 0x54,
>> + STD_REG_TRANS_STATUS = 0x58,
>> + /* Clear helper-built tables/descriptors (write-any to trigger) */
>> + STD_REG_TRANS_CLEAR = 0x5C,
>> +};
> Aren't we just duplicating the anonymous enum in smmu-testdev.c here? I
> think qtest is allowed to include qemu headers so lets just have a
> common set please.
Thank you very much for the thorough and highly constructive review of
this patch. I've gone through all your comments, and I agree with points
you've raised.
I will incorporate all your feedback into the next version of the
series. To be clear, even as the broader discussion about potentially
refactoring smmu-testdev into a more generic iommu-testdev continues,
your review points on code quality are fundamental. I will ensure they
are implemented regardless of which final architecture we decide upon.
To summarize the planned changes:
- Logging mechanism: I will replace the custom smmu_testdev_debug
function and other printf calls with the standard tracepoint mechanism.
Consequently, the debug-log device property will be removed as it will
no longer be necessary.
- Removing duplicated codes: Move the duplicated enum into
smmu-testdev.h which will then be included by both the device
implementation and the qtest file.
- Code style and simplification: Simplify the g_assert checks and
ensuring local variables are declared at the top of their respective blocks.
Thanks again for taking the time to provide such valuable and detailed
feedback.
Best regards,
Tao
© 2016 - 2025 Red Hat, Inc.