* [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
@ 2025-12-24 3:46 ` Tao Tang
2025-12-26 22:45 ` Philippe Mathieu-Daudé
2026-01-15 8:43 ` Eric Auger
2025-12-24 3:46 ` [RFC v8 2/7] hw/arm/smmuv3-common: Define STE/CD fields via registerfields Tao Tang
` (7 subsequent siblings)
8 siblings, 2 replies; 35+ messages in thread
From: Tao Tang @ 2025-12-24 3:46 UTC (permalink / raw)
To: Paolo Bonzini, Fabiano Rosas, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF, Tao Tang
Move register definitions, command enums, and Stream Table Entry (STE) /
Context Descriptor (CD) structure definitions from the internal header
hw/arm/smmuv3-internal.h to a new common header
include/hw/arm/smmuv3-common.h.
This allows other components, such as generic SMMUv3 tests or test devices,
to utilize these definitions without including the specific SMMUv3 device
internal state.
Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
hw/arm/smmuv3-internal.h | 255 +------------------------------
include/hw/arm/smmuv3-common.h | 268 +++++++++++++++++++++++++++++++++
2 files changed, 269 insertions(+), 254 deletions(-)
create mode 100644 include/hw/arm/smmuv3-common.h
diff --git a/hw/arm/smmuv3-internal.h b/hw/arm/smmuv3-internal.h
index b6b7399347..8679ab6d09 100644
--- a/hw/arm/smmuv3-internal.h
+++ b/hw/arm/smmuv3-internal.h
@@ -23,6 +23,7 @@
#include "hw/registerfields.h"
#include "hw/arm/smmu-common.h"
+#include "hw/arm/smmuv3-common.h"
typedef enum SMMUTranslationStatus {
SMMU_TRANS_DISABLE,
@@ -38,147 +39,6 @@ typedef enum SMMUTranslationClass {
SMMU_CLASS_IN,
} SMMUTranslationClass;
-/* MMIO Registers */
-
-REG32(IDR0, 0x0)
- FIELD(IDR0, S2P, 0 , 1)
- FIELD(IDR0, S1P, 1 , 1)
- FIELD(IDR0, TTF, 2 , 2)
- FIELD(IDR0, COHACC, 4 , 1)
- FIELD(IDR0, BTM, 5 , 1)
- FIELD(IDR0, HTTU, 6 , 2)
- FIELD(IDR0, DORMHINT, 8 , 1)
- FIELD(IDR0, HYP, 9 , 1)
- FIELD(IDR0, ATS, 10, 1)
- FIELD(IDR0, NS1ATS, 11, 1)
- FIELD(IDR0, ASID16, 12, 1)
- FIELD(IDR0, MSI, 13, 1)
- FIELD(IDR0, SEV, 14, 1)
- FIELD(IDR0, ATOS, 15, 1)
- FIELD(IDR0, PRI, 16, 1)
- FIELD(IDR0, VMW, 17, 1)
- FIELD(IDR0, VMID16, 18, 1)
- FIELD(IDR0, CD2L, 19, 1)
- FIELD(IDR0, VATOS, 20, 1)
- FIELD(IDR0, TTENDIAN, 21, 2)
- FIELD(IDR0, ATSRECERR, 23, 1)
- FIELD(IDR0, STALL_MODEL, 24, 2)
- FIELD(IDR0, TERM_MODEL, 26, 1)
- FIELD(IDR0, STLEVEL, 27, 2)
- FIELD(IDR0, RME_IMPL, 30, 1)
-
-REG32(IDR1, 0x4)
- FIELD(IDR1, SIDSIZE, 0 , 6)
- FIELD(IDR1, SSIDSIZE, 6 , 5)
- FIELD(IDR1, PRIQS, 11, 5)
- FIELD(IDR1, EVENTQS, 16, 5)
- FIELD(IDR1, CMDQS, 21, 5)
- FIELD(IDR1, ATTR_PERMS_OVR, 26, 1)
- FIELD(IDR1, ATTR_TYPES_OVR, 27, 1)
- FIELD(IDR1, REL, 28, 1)
- FIELD(IDR1, QUEUES_PRESET, 29, 1)
- FIELD(IDR1, TABLES_PRESET, 30, 1)
- FIELD(IDR1, ECMDQ, 31, 1)
-
-#define SMMU_IDR1_SIDSIZE 16
-#define SMMU_CMDQS 19
-#define SMMU_EVENTQS 19
-
-REG32(IDR2, 0x8)
- FIELD(IDR2, BA_VATOS, 0, 10)
-
-REG32(IDR3, 0xc)
- FIELD(IDR3, HAD, 2, 1);
- FIELD(IDR3, PBHA, 3, 1);
- FIELD(IDR3, XNX, 4, 1);
- FIELD(IDR3, PPS, 5, 1);
- FIELD(IDR3, MPAM, 7, 1);
- FIELD(IDR3, FWB, 8, 1);
- FIELD(IDR3, STT, 9, 1);
- FIELD(IDR3, RIL, 10, 1);
- FIELD(IDR3, BBML, 11, 2);
- FIELD(IDR3, E0PD, 13, 1);
- FIELD(IDR3, PTWNNC, 14, 1);
- FIELD(IDR3, DPT, 15, 1);
-
-REG32(IDR4, 0x10)
-
-REG32(IDR5, 0x14)
- FIELD(IDR5, OAS, 0, 3);
- FIELD(IDR5, GRAN4K, 4, 1);
- FIELD(IDR5, GRAN16K, 5, 1);
- FIELD(IDR5, GRAN64K, 6, 1);
- FIELD(IDR5, VAX, 10, 2);
- FIELD(IDR5, STALL_MAX, 16, 16);
-
-#define SMMU_IDR5_OAS 4
-
-REG32(IIDR, 0x18)
-REG32(AIDR, 0x1c)
-REG32(CR0, 0x20)
- FIELD(CR0, SMMU_ENABLE, 0, 1)
- FIELD(CR0, EVENTQEN, 2, 1)
- FIELD(CR0, CMDQEN, 3, 1)
-
-#define SMMU_CR0_RESERVED 0xFFFFFC20
-
-REG32(CR0ACK, 0x24)
-REG32(CR1, 0x28)
-REG32(CR2, 0x2c)
-REG32(STATUSR, 0x40)
-REG32(GBPA, 0x44)
- FIELD(GBPA, ABORT, 20, 1)
- FIELD(GBPA, UPDATE, 31, 1)
-
-/* Use incoming. */
-#define SMMU_GBPA_RESET_VAL 0x1000
-
-REG32(IRQ_CTRL, 0x50)
- FIELD(IRQ_CTRL, GERROR_IRQEN, 0, 1)
- FIELD(IRQ_CTRL, PRI_IRQEN, 1, 1)
- FIELD(IRQ_CTRL, EVENTQ_IRQEN, 2, 1)
-
-REG32(IRQ_CTRL_ACK, 0x54)
-REG32(GERROR, 0x60)
- FIELD(GERROR, CMDQ_ERR, 0, 1)
- FIELD(GERROR, EVENTQ_ABT_ERR, 2, 1)
- FIELD(GERROR, PRIQ_ABT_ERR, 3, 1)
- FIELD(GERROR, MSI_CMDQ_ABT_ERR, 4, 1)
- FIELD(GERROR, MSI_EVENTQ_ABT_ERR, 5, 1)
- FIELD(GERROR, MSI_PRIQ_ABT_ERR, 6, 1)
- FIELD(GERROR, MSI_GERROR_ABT_ERR, 7, 1)
- FIELD(GERROR, MSI_SFM_ERR, 8, 1)
-
-REG32(GERRORN, 0x64)
-
-#define A_GERROR_IRQ_CFG0 0x68 /* 64b */
-REG32(GERROR_IRQ_CFG1, 0x70)
-REG32(GERROR_IRQ_CFG2, 0x74)
-
-#define A_STRTAB_BASE 0x80 /* 64b */
-
-#define SMMU_BASE_ADDR_MASK 0xfffffffffffc0
-
-REG32(STRTAB_BASE_CFG, 0x88)
- FIELD(STRTAB_BASE_CFG, FMT, 16, 2)
- FIELD(STRTAB_BASE_CFG, SPLIT, 6 , 5)
- FIELD(STRTAB_BASE_CFG, LOG2SIZE, 0 , 6)
-
-#define A_CMDQ_BASE 0x90 /* 64b */
-REG32(CMDQ_PROD, 0x98)
-REG32(CMDQ_CONS, 0x9c)
- FIELD(CMDQ_CONS, ERR, 24, 7)
-
-#define A_EVENTQ_BASE 0xa0 /* 64b */
-REG32(EVENTQ_PROD, 0xa8)
-REG32(EVENTQ_CONS, 0xac)
-
-#define A_EVENTQ_IRQ_CFG0 0xb0 /* 64b */
-REG32(EVENTQ_IRQ_CFG1, 0xb8)
-REG32(EVENTQ_IRQ_CFG2, 0xbc)
-
-#define A_IDREGS 0xfd0
-
static inline int smmu_enabled(SMMUv3State *s)
{
return FIELD_EX32(s->cr[0], CR0, SMMU_ENABLE);
@@ -272,37 +132,6 @@ static inline void smmu_write_cmdq_err(SMMUv3State *s, uint32_t err_type)
s->cmdq.cons = FIELD_DP32(s->cmdq.cons, CMDQ_CONS, ERR, err_type);
}
-/* Commands */
-
-typedef enum SMMUCommandType {
- SMMU_CMD_NONE = 0x00,
- SMMU_CMD_PREFETCH_CONFIG ,
- SMMU_CMD_PREFETCH_ADDR,
- SMMU_CMD_CFGI_STE,
- SMMU_CMD_CFGI_STE_RANGE,
- SMMU_CMD_CFGI_CD,
- SMMU_CMD_CFGI_CD_ALL,
- SMMU_CMD_CFGI_ALL,
- SMMU_CMD_TLBI_NH_ALL = 0x10,
- SMMU_CMD_TLBI_NH_ASID,
- SMMU_CMD_TLBI_NH_VA,
- SMMU_CMD_TLBI_NH_VAA,
- SMMU_CMD_TLBI_EL3_ALL = 0x18,
- SMMU_CMD_TLBI_EL3_VA = 0x1a,
- SMMU_CMD_TLBI_EL2_ALL = 0x20,
- SMMU_CMD_TLBI_EL2_ASID,
- SMMU_CMD_TLBI_EL2_VA,
- SMMU_CMD_TLBI_EL2_VAA,
- SMMU_CMD_TLBI_S12_VMALL = 0x28,
- SMMU_CMD_TLBI_S2_IPA = 0x2a,
- SMMU_CMD_TLBI_NSNH_ALL = 0x30,
- SMMU_CMD_ATC_INV = 0x40,
- SMMU_CMD_PRI_RESP,
- SMMU_CMD_RESUME = 0x44,
- SMMU_CMD_STALL_TERM,
- SMMU_CMD_SYNC,
-} SMMUCommandType;
-
static const char *cmd_stringify[] = {
[SMMU_CMD_PREFETCH_CONFIG] = "SMMU_CMD_PREFETCH_CONFIG",
[SMMU_CMD_PREFETCH_ADDR] = "SMMU_CMD_PREFETCH_ADDR",
@@ -525,64 +354,6 @@ typedef struct SMMUEventInfo {
void smmuv3_record_event(SMMUv3State *s, SMMUEventInfo *event);
-/* Configuration Data */
-
-/* STE Level 1 Descriptor */
-typedef struct STEDesc {
- uint32_t word[2];
-} STEDesc;
-
-/* CD Level 1 Descriptor */
-typedef struct CDDesc {
- uint32_t word[2];
-} CDDesc;
-
-/* Stream Table Entry(STE) */
-typedef struct STE {
- uint32_t word[16];
-} STE;
-
-/* Context Descriptor(CD) */
-typedef struct CD {
- uint32_t word[16];
-} CD;
-
-/* STE fields */
-
-#define STE_VALID(x) extract32((x)->word[0], 0, 1)
-
-#define STE_CONFIG(x) extract32((x)->word[0], 1, 3)
-#define STE_CFG_S1_ENABLED(config) (config & 0x1)
-#define STE_CFG_S2_ENABLED(config) (config & 0x2)
-#define STE_CFG_ABORT(config) (!(config & 0x4))
-#define STE_CFG_BYPASS(config) (config == 0x4)
-
-#define STE_S1FMT(x) extract32((x)->word[0], 4 , 2)
-#define STE_S1CDMAX(x) extract32((x)->word[1], 27, 5)
-#define STE_S1STALLD(x) extract32((x)->word[2], 27, 1)
-#define STE_EATS(x) extract32((x)->word[2], 28, 2)
-#define STE_STRW(x) extract32((x)->word[2], 30, 2)
-#define STE_S2VMID(x) extract32((x)->word[4], 0 , 16)
-#define STE_S2T0SZ(x) extract32((x)->word[5], 0 , 6)
-#define STE_S2SL0(x) extract32((x)->word[5], 6 , 2)
-#define STE_S2TG(x) extract32((x)->word[5], 14, 2)
-#define STE_S2PS(x) extract32((x)->word[5], 16, 3)
-#define STE_S2AA64(x) extract32((x)->word[5], 19, 1)
-#define STE_S2ENDI(x) extract32((x)->word[5], 20, 1)
-#define STE_S2AFFD(x) extract32((x)->word[5], 21, 1)
-#define STE_S2HD(x) extract32((x)->word[5], 23, 1)
-#define STE_S2HA(x) extract32((x)->word[5], 24, 1)
-#define STE_S2S(x) extract32((x)->word[5], 25, 1)
-#define STE_S2R(x) extract32((x)->word[5], 26, 1)
-
-#define STE_CTXPTR(x) \
- ((extract64((x)->word[1], 0, 16) << 32) | \
- ((x)->word[0] & 0xffffffc0))
-
-#define STE_S2TTB(x) \
- ((extract64((x)->word[7], 0, 16) << 32) | \
- ((x)->word[6] & 0xfffffff0))
-
static inline int oas2bits(int oas_field)
{
switch (oas_field) {
@@ -603,30 +374,6 @@ static inline int oas2bits(int oas_field)
g_assert_not_reached();
}
-/* CD fields */
-
-#define CD_VALID(x) extract32((x)->word[0], 31, 1)
-#define CD_ASID(x) extract32((x)->word[1], 16, 16)
-#define CD_TTB(x, sel) \
- ((extract64((x)->word[(sel) * 2 + 3], 0, 19) << 32) | \
- ((x)->word[(sel) * 2 + 2] & ~0xfULL))
-
-#define CD_HAD(x, sel) extract32((x)->word[(sel) * 2 + 2], 1, 1)
-
-#define CD_TSZ(x, sel) extract32((x)->word[0], (16 * (sel)) + 0, 6)
-#define CD_TG(x, sel) extract32((x)->word[0], (16 * (sel)) + 6, 2)
-#define CD_EPD(x, sel) extract32((x)->word[0], (16 * (sel)) + 14, 1)
-#define CD_ENDI(x) extract32((x)->word[0], 15, 1)
-#define CD_IPS(x) extract32((x)->word[1], 0 , 3)
-#define CD_AFFD(x) extract32((x)->word[1], 3 , 1)
-#define CD_TBI(x) extract32((x)->word[1], 6 , 2)
-#define CD_HD(x) extract32((x)->word[1], 10 , 1)
-#define CD_HA(x) extract32((x)->word[1], 11 , 1)
-#define CD_S(x) extract32((x)->word[1], 12, 1)
-#define CD_R(x) extract32((x)->word[1], 13, 1)
-#define CD_A(x) extract32((x)->word[1], 14, 1)
-#define CD_AARCH64(x) extract32((x)->word[1], 9 , 1)
-
/**
* tg2granule - Decodes the CD translation granule size field according
* to the ttbr in use
diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
new file mode 100644
index 0000000000..9da817f41a
--- /dev/null
+++ b/include/hw/arm/smmuv3-common.h
@@ -0,0 +1,268 @@
+/*
+ * ARM SMMUv3 support - Common API
+ *
+ * Copyright (C) 2014-2016 Broadcom Corporation
+ * Copyright (c) 2017 Red Hat, Inc.
+ * Written by Prem Mallappa, Eric Auger
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_ARM_SMMUV3_COMMON_H
+#define HW_ARM_SMMUV3_COMMON_H
+
+/* Configuration Data */
+
+/* STE Level 1 Descriptor */
+typedef struct STEDesc {
+ uint32_t word[2];
+} STEDesc;
+
+/* CD Level 1 Descriptor */
+typedef struct CDDesc {
+ uint32_t word[2];
+} CDDesc;
+
+/* Stream Table Entry(STE) */
+typedef struct STE {
+ uint32_t word[16];
+} STE;
+
+/* Context Descriptor(CD) */
+typedef struct CD {
+ uint32_t word[16];
+} CD;
+
+/* STE fields */
+
+#define STE_VALID(x) extract32((x)->word[0], 0, 1)
+
+#define STE_CONFIG(x) extract32((x)->word[0], 1, 3)
+#define STE_CFG_S1_ENABLED(config) (config & 0x1)
+#define STE_CFG_S2_ENABLED(config) (config & 0x2)
+#define STE_CFG_ABORT(config) (!(config & 0x4))
+#define STE_CFG_BYPASS(config) (config == 0x4)
+
+#define STE_S1FMT(x) extract32((x)->word[0], 4 , 2)
+#define STE_S1CDMAX(x) extract32((x)->word[1], 27, 5)
+#define STE_S1STALLD(x) extract32((x)->word[2], 27, 1)
+#define STE_EATS(x) extract32((x)->word[2], 28, 2)
+#define STE_STRW(x) extract32((x)->word[2], 30, 2)
+#define STE_S2VMID(x) extract32((x)->word[4], 0 , 16)
+#define STE_S2T0SZ(x) extract32((x)->word[5], 0 , 6)
+#define STE_S2SL0(x) extract32((x)->word[5], 6 , 2)
+#define STE_S2TG(x) extract32((x)->word[5], 14, 2)
+#define STE_S2PS(x) extract32((x)->word[5], 16, 3)
+#define STE_S2AA64(x) extract32((x)->word[5], 19, 1)
+#define STE_S2ENDI(x) extract32((x)->word[5], 20, 1)
+#define STE_S2AFFD(x) extract32((x)->word[5], 21, 1)
+#define STE_S2HD(x) extract32((x)->word[5], 23, 1)
+#define STE_S2HA(x) extract32((x)->word[5], 24, 1)
+#define STE_S2S(x) extract32((x)->word[5], 25, 1)
+#define STE_S2R(x) extract32((x)->word[5], 26, 1)
+
+#define STE_CTXPTR(x) \
+ ((extract64((x)->word[1], 0, 16) << 32) | \
+ ((x)->word[0] & 0xffffffc0))
+
+#define STE_S2TTB(x) \
+ ((extract64((x)->word[7], 0, 16) << 32) | \
+ ((x)->word[6] & 0xfffffff0))
+
+/* CD fields */
+
+#define CD_VALID(x) extract32((x)->word[0], 31, 1)
+#define CD_ASID(x) extract32((x)->word[1], 16, 16)
+#define CD_TTB(x, sel) \
+ ((extract64((x)->word[(sel) * 2 + 3], 0, 19) << 32) | \
+ ((x)->word[(sel) * 2 + 2] & ~0xfULL))
+
+#define CD_HAD(x, sel) extract32((x)->word[(sel) * 2 + 2], 1, 1)
+
+#define CD_TSZ(x, sel) extract32((x)->word[0], (16 * (sel)) + 0, 6)
+#define CD_TG(x, sel) extract32((x)->word[0], (16 * (sel)) + 6, 2)
+#define CD_EPD(x, sel) extract32((x)->word[0], (16 * (sel)) + 14, 1)
+#define CD_ENDI(x) extract32((x)->word[0], 15, 1)
+#define CD_IPS(x) extract32((x)->word[1], 0 , 3)
+#define CD_AFFD(x) extract32((x)->word[1], 3 , 1)
+#define CD_TBI(x) extract32((x)->word[1], 6 , 2)
+#define CD_HD(x) extract32((x)->word[1], 10 , 1)
+#define CD_HA(x) extract32((x)->word[1], 11 , 1)
+#define CD_S(x) extract32((x)->word[1], 12, 1)
+#define CD_R(x) extract32((x)->word[1], 13, 1)
+#define CD_A(x) extract32((x)->word[1], 14, 1)
+#define CD_AARCH64(x) extract32((x)->word[1], 9 , 1)
+
+/* MMIO Registers */
+
+REG32(IDR0, 0x0)
+ FIELD(IDR0, S2P, 0 , 1)
+ FIELD(IDR0, S1P, 1 , 1)
+ FIELD(IDR0, TTF, 2 , 2)
+ FIELD(IDR0, COHACC, 4 , 1)
+ FIELD(IDR0, BTM, 5 , 1)
+ FIELD(IDR0, HTTU, 6 , 2)
+ FIELD(IDR0, DORMHINT, 8 , 1)
+ FIELD(IDR0, HYP, 9 , 1)
+ FIELD(IDR0, ATS, 10, 1)
+ FIELD(IDR0, NS1ATS, 11, 1)
+ FIELD(IDR0, ASID16, 12, 1)
+ FIELD(IDR0, MSI, 13, 1)
+ FIELD(IDR0, SEV, 14, 1)
+ FIELD(IDR0, ATOS, 15, 1)
+ FIELD(IDR0, PRI, 16, 1)
+ FIELD(IDR0, VMW, 17, 1)
+ FIELD(IDR0, VMID16, 18, 1)
+ FIELD(IDR0, CD2L, 19, 1)
+ FIELD(IDR0, VATOS, 20, 1)
+ FIELD(IDR0, TTENDIAN, 21, 2)
+ FIELD(IDR0, ATSRECERR, 23, 1)
+ FIELD(IDR0, STALL_MODEL, 24, 2)
+ FIELD(IDR0, TERM_MODEL, 26, 1)
+ FIELD(IDR0, STLEVEL, 27, 2)
+ FIELD(IDR0, RME_IMPL, 30, 1)
+
+REG32(IDR1, 0x4)
+ FIELD(IDR1, SIDSIZE, 0 , 6)
+ FIELD(IDR1, SSIDSIZE, 6 , 5)
+ FIELD(IDR1, PRIQS, 11, 5)
+ FIELD(IDR1, EVENTQS, 16, 5)
+ FIELD(IDR1, CMDQS, 21, 5)
+ FIELD(IDR1, ATTR_PERMS_OVR, 26, 1)
+ FIELD(IDR1, ATTR_TYPES_OVR, 27, 1)
+ FIELD(IDR1, REL, 28, 1)
+ FIELD(IDR1, QUEUES_PRESET, 29, 1)
+ FIELD(IDR1, TABLES_PRESET, 30, 1)
+ FIELD(IDR1, ECMDQ, 31, 1)
+
+#define SMMU_IDR1_SIDSIZE 16
+#define SMMU_CMDQS 19
+#define SMMU_EVENTQS 19
+
+REG32(IDR2, 0x8)
+ FIELD(IDR2, BA_VATOS, 0, 10)
+
+REG32(IDR3, 0xc)
+ FIELD(IDR3, HAD, 2, 1);
+ FIELD(IDR3, PBHA, 3, 1);
+ FIELD(IDR3, XNX, 4, 1);
+ FIELD(IDR3, PPS, 5, 1);
+ FIELD(IDR3, MPAM, 7, 1);
+ FIELD(IDR3, FWB, 8, 1);
+ FIELD(IDR3, STT, 9, 1);
+ FIELD(IDR3, RIL, 10, 1);
+ FIELD(IDR3, BBML, 11, 2);
+ FIELD(IDR3, E0PD, 13, 1);
+ FIELD(IDR3, PTWNNC, 14, 1);
+ FIELD(IDR3, DPT, 15, 1);
+
+REG32(IDR4, 0x10)
+
+REG32(IDR5, 0x14)
+ FIELD(IDR5, OAS, 0, 3);
+ FIELD(IDR5, GRAN4K, 4, 1);
+ FIELD(IDR5, GRAN16K, 5, 1);
+ FIELD(IDR5, GRAN64K, 6, 1);
+ FIELD(IDR5, VAX, 10, 2);
+ FIELD(IDR5, STALL_MAX, 16, 16);
+
+#define SMMU_IDR5_OAS 4
+
+REG32(IIDR, 0x18)
+REG32(AIDR, 0x1c)
+REG32(CR0, 0x20)
+ FIELD(CR0, SMMU_ENABLE, 0, 1)
+ FIELD(CR0, EVENTQEN, 2, 1)
+ FIELD(CR0, CMDQEN, 3, 1)
+
+#define SMMU_CR0_RESERVED 0xFFFFFC20
+
+REG32(CR0ACK, 0x24)
+REG32(CR1, 0x28)
+REG32(CR2, 0x2c)
+REG32(STATUSR, 0x40)
+REG32(GBPA, 0x44)
+ FIELD(GBPA, ABORT, 20, 1)
+ FIELD(GBPA, UPDATE, 31, 1)
+
+/* Use incoming. */
+#define SMMU_GBPA_RESET_VAL 0x1000
+
+REG32(IRQ_CTRL, 0x50)
+ FIELD(IRQ_CTRL, GERROR_IRQEN, 0, 1)
+ FIELD(IRQ_CTRL, PRI_IRQEN, 1, 1)
+ FIELD(IRQ_CTRL, EVENTQ_IRQEN, 2, 1)
+
+REG32(IRQ_CTRL_ACK, 0x54)
+REG32(GERROR, 0x60)
+ FIELD(GERROR, CMDQ_ERR, 0, 1)
+ FIELD(GERROR, EVENTQ_ABT_ERR, 2, 1)
+ FIELD(GERROR, PRIQ_ABT_ERR, 3, 1)
+ FIELD(GERROR, MSI_CMDQ_ABT_ERR, 4, 1)
+ FIELD(GERROR, MSI_EVENTQ_ABT_ERR, 5, 1)
+ FIELD(GERROR, MSI_PRIQ_ABT_ERR, 6, 1)
+ FIELD(GERROR, MSI_GERROR_ABT_ERR, 7, 1)
+ FIELD(GERROR, MSI_SFM_ERR, 8, 1)
+
+REG32(GERRORN, 0x64)
+
+#define A_GERROR_IRQ_CFG0 0x68 /* 64b */
+REG32(GERROR_IRQ_CFG1, 0x70)
+REG32(GERROR_IRQ_CFG2, 0x74)
+
+#define A_STRTAB_BASE 0x80 /* 64b */
+
+#define SMMU_BASE_ADDR_MASK 0xfffffffffffc0
+
+REG32(STRTAB_BASE_CFG, 0x88)
+ FIELD(STRTAB_BASE_CFG, FMT, 16, 2)
+ FIELD(STRTAB_BASE_CFG, SPLIT, 6 , 5)
+ FIELD(STRTAB_BASE_CFG, LOG2SIZE, 0 , 6)
+
+#define A_CMDQ_BASE 0x90 /* 64b */
+REG32(CMDQ_PROD, 0x98)
+REG32(CMDQ_CONS, 0x9c)
+ FIELD(CMDQ_CONS, ERR, 24, 7)
+
+#define A_EVENTQ_BASE 0xa0 /* 64b */
+REG32(EVENTQ_PROD, 0xa8)
+REG32(EVENTQ_CONS, 0xac)
+
+#define A_EVENTQ_IRQ_CFG0 0xb0 /* 64b */
+REG32(EVENTQ_IRQ_CFG1, 0xb8)
+REG32(EVENTQ_IRQ_CFG2, 0xbc)
+
+#define A_IDREGS 0xfd0
+
+/* Commands */
+
+typedef enum SMMUCommandType {
+ SMMU_CMD_NONE = 0x00,
+ SMMU_CMD_PREFETCH_CONFIG ,
+ SMMU_CMD_PREFETCH_ADDR,
+ SMMU_CMD_CFGI_STE,
+ SMMU_CMD_CFGI_STE_RANGE,
+ SMMU_CMD_CFGI_CD,
+ SMMU_CMD_CFGI_CD_ALL,
+ SMMU_CMD_CFGI_ALL,
+ SMMU_CMD_TLBI_NH_ALL = 0x10,
+ SMMU_CMD_TLBI_NH_ASID,
+ SMMU_CMD_TLBI_NH_VA,
+ SMMU_CMD_TLBI_NH_VAA,
+ SMMU_CMD_TLBI_EL3_ALL = 0x18,
+ SMMU_CMD_TLBI_EL3_VA = 0x1a,
+ SMMU_CMD_TLBI_EL2_ALL = 0x20,
+ SMMU_CMD_TLBI_EL2_ASID,
+ SMMU_CMD_TLBI_EL2_VA,
+ SMMU_CMD_TLBI_EL2_VAA,
+ SMMU_CMD_TLBI_S12_VMALL = 0x28,
+ SMMU_CMD_TLBI_S2_IPA = 0x2a,
+ SMMU_CMD_TLBI_NSNH_ALL = 0x30,
+ SMMU_CMD_ATC_INV = 0x40,
+ SMMU_CMD_PRI_RESP,
+ SMMU_CMD_RESUME = 0x44,
+ SMMU_CMD_STALL_TERM,
+ SMMU_CMD_SYNC,
+} SMMUCommandType;
+
+#endif /* HW_ARM_SMMUV3_COMMON_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h
2025-12-24 3:46 ` [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h Tao Tang
@ 2025-12-26 22:45 ` Philippe Mathieu-Daudé
2025-12-27 5:01 ` Tao Tang
2026-01-15 8:43 ` Eric Auger
1 sibling, 1 reply; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2025-12-26 22:45 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Eric Auger, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
On 24/12/25 04:46, Tao Tang wrote:
> Move register definitions, command enums, and Stream Table Entry (STE) /
> Context Descriptor (CD) structure definitions from the internal header
> hw/arm/smmuv3-internal.h to a new common header
> include/hw/arm/smmuv3-common.h.
>
> This allows other components, such as generic SMMUv3 tests or test devices,
> to utilize these definitions without including the specific SMMUv3 device
> internal state.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> ---
> hw/arm/smmuv3-internal.h | 255 +------------------------------
> include/hw/arm/smmuv3-common.h | 268 +++++++++++++++++++++++++++++++++
> 2 files changed, 269 insertions(+), 254 deletions(-)
> create mode 100644 include/hw/arm/smmuv3-common.h
> --- /dev/null
> +++ b/include/hw/arm/smmuv3-common.h
> @@ -0,0 +1,268 @@
> +/*
> + * ARM SMMUv3 support - Common API
> + *
> + * Copyright (C) 2014-2016 Broadcom Corporation
> + * Copyright (c) 2017 Red Hat, Inc.
> + * Written by Prem Mallappa, Eric Auger
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_ARM_SMMUV3_COMMON_H
> +#define HW_ARM_SMMUV3_COMMON_H
> +
> +/* Configuration Data */
> +
> +/* STE Level 1 Descriptor */
> +typedef struct STEDesc {
> + uint32_t word[2];
> +} STEDesc;
> +
> +/* CD Level 1 Descriptor */
> +typedef struct CDDesc {
> + uint32_t word[2];
> +} CDDesc;
> +
> +/* Stream Table Entry(STE) */
> +typedef struct STE {
> + uint32_t word[16];
> +} STE;
> +
> +/* Context Descriptor(CD) */
> +typedef struct CD {
> + uint32_t word[16];
> +} CD;
Pre-existing: should these be QEMU_PACKED?
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h
2025-12-26 22:45 ` Philippe Mathieu-Daudé
@ 2025-12-27 5:01 ` Tao Tang
2025-12-27 21:22 ` Pierrick Bouvier
0 siblings, 1 reply; 35+ messages in thread
From: Tao Tang @ 2025-12-27 5:01 UTC (permalink / raw)
To: Philippe Mathieu-Daudé, Paolo Bonzini, Fabiano Rosas,
Laurent Vivier, Eric Auger, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
Hi Philippe,
On 2025/12/27 06:45, Philippe Mathieu-Daudé wrote:
> On 24/12/25 04:46, Tao Tang wrote:
>> Move register definitions, command enums, and Stream Table Entry (STE) /
>> Context Descriptor (CD) structure definitions from the internal header
>> hw/arm/smmuv3-internal.h to a new common header
>> include/hw/arm/smmuv3-common.h.
>>
>> This allows other components, such as generic SMMUv3 tests or test
>> devices,
>> to utilize these definitions without including the specific SMMUv3
>> device
>> internal state.
>>
>> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
>> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>> ---
>> hw/arm/smmuv3-internal.h | 255 +------------------------------
>> include/hw/arm/smmuv3-common.h | 268 +++++++++++++++++++++++++++++++++
>> 2 files changed, 269 insertions(+), 254 deletions(-)
>> create mode 100644 include/hw/arm/smmuv3-common.h
>
>
>> --- /dev/null
>> +++ b/include/hw/arm/smmuv3-common.h
>> @@ -0,0 +1,268 @@
>> +/*
>> + * ARM SMMUv3 support - Common API
>> + *
>> + * Copyright (C) 2014-2016 Broadcom Corporation
>> + * Copyright (c) 2017 Red Hat, Inc.
>> + * Written by Prem Mallappa, Eric Auger
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_ARM_SMMUV3_COMMON_H
>> +#define HW_ARM_SMMUV3_COMMON_H
>> +
>> +/* Configuration Data */
>> +
>> +/* STE Level 1 Descriptor */
>> +typedef struct STEDesc {
>> + uint32_t word[2];
>> +} STEDesc;
>> +
>> +/* CD Level 1 Descriptor */
>> +typedef struct CDDesc {
>> + uint32_t word[2];
>> +} CDDesc;
>> +
>> +/* Stream Table Entry(STE) */
>> +typedef struct STE {
>> + uint32_t word[16];
>> +} STE;
>> +
>> +/* Context Descriptor(CD) */
>> +typedef struct CD {
>> + uint32_t word[16];
>> +} CD;
> Pre-existing: should these be QEMU_PACKED?
Thanks for the feedback.
I tried adding QEMU_PACKED to STEDesc/CDDesc/STE/CD in smmuv3-common.h,
but that means some call sites need updates.
For example, with packed structs, this triggers
-Waddress-of-packed-member (and becomes a build failure with -Werror):
le32_to_cpus(&buf->word[i]); // smmu_get_ste function in
hw/arm/smmuv3.c
It needs to be changed to something like:
buf->word[i] = le32_to_cpu(buf->word[i]);
Do you prefer that I send an extra commit to fix all affected call sites
and keep QEMU_PACKED, or should we instead drop QEMU_PACKED and add
compile-time size checks, e.g.:
QEMU_BUILD_BUG_ON(sizeof(STEDesc) != 8);
QEMU_BUILD_BUG_ON(sizeof(CDDesc) != 8);
QEMU_BUILD_BUG_ON(sizeof(STE) != 64);
QEMU_BUILD_BUG_ON(sizeof(CD) != 64);
Best regards,
Tao
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h
2025-12-27 5:01 ` Tao Tang
@ 2025-12-27 21:22 ` Pierrick Bouvier
0 siblings, 0 replies; 35+ messages in thread
From: Pierrick Bouvier @ 2025-12-27 21:22 UTC (permalink / raw)
To: Tao Tang, Philippe Mathieu-Daudé, Paolo Bonzini,
Fabiano Rosas, Laurent Vivier, Eric Auger, Peter Maydell,
Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Jean-Philippe Brucker,
Mostafa Saleh, CLEMENT MATHIEU--DRIF
On 12/26/25 9:01 PM, Tao Tang wrote:
> Hi Philippe,
>
> On 2025/12/27 06:45, Philippe Mathieu-Daudé wrote:
>> On 24/12/25 04:46, Tao Tang wrote:
>>> Move register definitions, command enums, and Stream Table Entry (STE) /
>>> Context Descriptor (CD) structure definitions from the internal header
>>> hw/arm/smmuv3-internal.h to a new common header
>>> include/hw/arm/smmuv3-common.h.
>>>
>>> This allows other components, such as generic SMMUv3 tests or test
>>> devices,
>>> to utilize these definitions without including the specific SMMUv3
>>> device
>>> internal state.
>>>
>>> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
>>> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>>> ---
>>> hw/arm/smmuv3-internal.h | 255 +------------------------------
>>> include/hw/arm/smmuv3-common.h | 268 +++++++++++++++++++++++++++++++++
>>> 2 files changed, 269 insertions(+), 254 deletions(-)
>>> create mode 100644 include/hw/arm/smmuv3-common.h
>>
>>
>>> --- /dev/null
>>> +++ b/include/hw/arm/smmuv3-common.h
>>> @@ -0,0 +1,268 @@
>>> +/*
>>> + * ARM SMMUv3 support - Common API
>>> + *
>>> + * Copyright (C) 2014-2016 Broadcom Corporation
>>> + * Copyright (c) 2017 Red Hat, Inc.
>>> + * Written by Prem Mallappa, Eric Auger
>>> + *
>>> + * SPDX-License-Identifier: GPL-2.0-or-later
>>> + */
>>> +
>>> +#ifndef HW_ARM_SMMUV3_COMMON_H
>>> +#define HW_ARM_SMMUV3_COMMON_H
>>> +
>>> +/* Configuration Data */
>>> +
>>> +/* STE Level 1 Descriptor */
>>> +typedef struct STEDesc {
>>> + uint32_t word[2];
>>> +} STEDesc;
>>> +
>>> +/* CD Level 1 Descriptor */
>>> +typedef struct CDDesc {
>>> + uint32_t word[2];
>>> +} CDDesc;
>>> +
>>> +/* Stream Table Entry(STE) */
>>> +typedef struct STE {
>>> + uint32_t word[16];
>>> +} STE;
>>> +
>>> +/* Context Descriptor(CD) */
>>> +typedef struct CD {
>>> + uint32_t word[16];
>>> +} CD;
>> Pre-existing: should these be QEMU_PACKED?
>
>
> Thanks for the feedback.
>
>
> I tried adding QEMU_PACKED to STEDesc/CDDesc/STE/CD in smmuv3-common.h,
> but that means some call sites need updates.
>
> For example, with packed structs, this triggers
> -Waddress-of-packed-member (and becomes a build failure with -Werror):
>
> le32_to_cpus(&buf->word[i]); // smmu_get_ste function in
> hw/arm/smmuv3.c
>
> It needs to be changed to something like:
>
> buf->word[i] = le32_to_cpu(buf->word[i]);
>
>
> Do you prefer that I send an extra commit to fix all affected call sites
> and keep QEMU_PACKED, or should we instead drop QEMU_PACKED and add
> compile-time size checks, e.g.:
>
> QEMU_BUILD_BUG_ON(sizeof(STEDesc) != 8);
> QEMU_BUILD_BUG_ON(sizeof(CDDesc) != 8);
> QEMU_BUILD_BUG_ON(sizeof(STE) != 64);
> QEMU_BUILD_BUG_ON(sizeof(CD) != 64);
>
I think it's not really needed.
Those structs have only one member, which is an array, thus contiguous,
so packed attribute will have no effect in practice.
> Best regards,
>
> Tao
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h
2025-12-24 3:46 ` [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h Tao Tang
2025-12-26 22:45 ` Philippe Mathieu-Daudé
@ 2026-01-15 8:43 ` Eric Auger
1 sibling, 0 replies; 35+ messages in thread
From: Eric Auger @ 2026-01-15 8:43 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
On 12/24/25 4:46 AM, Tao Tang wrote:
> Move register definitions, command enums, and Stream Table Entry (STE) /
> Context Descriptor (CD) structure definitions from the internal header
> hw/arm/smmuv3-internal.h to a new common header
> include/hw/arm/smmuv3-common.h.
>
> This allows other components, such as generic SMMUv3 tests or test devices,
> to utilize these definitions without including the specific SMMUv3 device
> internal state.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Eric
> ---
> hw/arm/smmuv3-internal.h | 255 +------------------------------
> include/hw/arm/smmuv3-common.h | 268 +++++++++++++++++++++++++++++++++
> 2 files changed, 269 insertions(+), 254 deletions(-)
> create mode 100644 include/hw/arm/smmuv3-common.h
>
> diff --git a/hw/arm/smmuv3-internal.h b/hw/arm/smmuv3-internal.h
> index b6b7399347..8679ab6d09 100644
> --- a/hw/arm/smmuv3-internal.h
> +++ b/hw/arm/smmuv3-internal.h
> @@ -23,6 +23,7 @@
>
> #include "hw/registerfields.h"
> #include "hw/arm/smmu-common.h"
> +#include "hw/arm/smmuv3-common.h"
>
> typedef enum SMMUTranslationStatus {
> SMMU_TRANS_DISABLE,
> @@ -38,147 +39,6 @@ typedef enum SMMUTranslationClass {
> SMMU_CLASS_IN,
> } SMMUTranslationClass;
>
> -/* MMIO Registers */
> -
> -REG32(IDR0, 0x0)
> - FIELD(IDR0, S2P, 0 , 1)
> - FIELD(IDR0, S1P, 1 , 1)
> - FIELD(IDR0, TTF, 2 , 2)
> - FIELD(IDR0, COHACC, 4 , 1)
> - FIELD(IDR0, BTM, 5 , 1)
> - FIELD(IDR0, HTTU, 6 , 2)
> - FIELD(IDR0, DORMHINT, 8 , 1)
> - FIELD(IDR0, HYP, 9 , 1)
> - FIELD(IDR0, ATS, 10, 1)
> - FIELD(IDR0, NS1ATS, 11, 1)
> - FIELD(IDR0, ASID16, 12, 1)
> - FIELD(IDR0, MSI, 13, 1)
> - FIELD(IDR0, SEV, 14, 1)
> - FIELD(IDR0, ATOS, 15, 1)
> - FIELD(IDR0, PRI, 16, 1)
> - FIELD(IDR0, VMW, 17, 1)
> - FIELD(IDR0, VMID16, 18, 1)
> - FIELD(IDR0, CD2L, 19, 1)
> - FIELD(IDR0, VATOS, 20, 1)
> - FIELD(IDR0, TTENDIAN, 21, 2)
> - FIELD(IDR0, ATSRECERR, 23, 1)
> - FIELD(IDR0, STALL_MODEL, 24, 2)
> - FIELD(IDR0, TERM_MODEL, 26, 1)
> - FIELD(IDR0, STLEVEL, 27, 2)
> - FIELD(IDR0, RME_IMPL, 30, 1)
> -
> -REG32(IDR1, 0x4)
> - FIELD(IDR1, SIDSIZE, 0 , 6)
> - FIELD(IDR1, SSIDSIZE, 6 , 5)
> - FIELD(IDR1, PRIQS, 11, 5)
> - FIELD(IDR1, EVENTQS, 16, 5)
> - FIELD(IDR1, CMDQS, 21, 5)
> - FIELD(IDR1, ATTR_PERMS_OVR, 26, 1)
> - FIELD(IDR1, ATTR_TYPES_OVR, 27, 1)
> - FIELD(IDR1, REL, 28, 1)
> - FIELD(IDR1, QUEUES_PRESET, 29, 1)
> - FIELD(IDR1, TABLES_PRESET, 30, 1)
> - FIELD(IDR1, ECMDQ, 31, 1)
> -
> -#define SMMU_IDR1_SIDSIZE 16
> -#define SMMU_CMDQS 19
> -#define SMMU_EVENTQS 19
> -
> -REG32(IDR2, 0x8)
> - FIELD(IDR2, BA_VATOS, 0, 10)
> -
> -REG32(IDR3, 0xc)
> - FIELD(IDR3, HAD, 2, 1);
> - FIELD(IDR3, PBHA, 3, 1);
> - FIELD(IDR3, XNX, 4, 1);
> - FIELD(IDR3, PPS, 5, 1);
> - FIELD(IDR3, MPAM, 7, 1);
> - FIELD(IDR3, FWB, 8, 1);
> - FIELD(IDR3, STT, 9, 1);
> - FIELD(IDR3, RIL, 10, 1);
> - FIELD(IDR3, BBML, 11, 2);
> - FIELD(IDR3, E0PD, 13, 1);
> - FIELD(IDR3, PTWNNC, 14, 1);
> - FIELD(IDR3, DPT, 15, 1);
> -
> -REG32(IDR4, 0x10)
> -
> -REG32(IDR5, 0x14)
> - FIELD(IDR5, OAS, 0, 3);
> - FIELD(IDR5, GRAN4K, 4, 1);
> - FIELD(IDR5, GRAN16K, 5, 1);
> - FIELD(IDR5, GRAN64K, 6, 1);
> - FIELD(IDR5, VAX, 10, 2);
> - FIELD(IDR5, STALL_MAX, 16, 16);
> -
> -#define SMMU_IDR5_OAS 4
> -
> -REG32(IIDR, 0x18)
> -REG32(AIDR, 0x1c)
> -REG32(CR0, 0x20)
> - FIELD(CR0, SMMU_ENABLE, 0, 1)
> - FIELD(CR0, EVENTQEN, 2, 1)
> - FIELD(CR0, CMDQEN, 3, 1)
> -
> -#define SMMU_CR0_RESERVED 0xFFFFFC20
> -
> -REG32(CR0ACK, 0x24)
> -REG32(CR1, 0x28)
> -REG32(CR2, 0x2c)
> -REG32(STATUSR, 0x40)
> -REG32(GBPA, 0x44)
> - FIELD(GBPA, ABORT, 20, 1)
> - FIELD(GBPA, UPDATE, 31, 1)
> -
> -/* Use incoming. */
> -#define SMMU_GBPA_RESET_VAL 0x1000
> -
> -REG32(IRQ_CTRL, 0x50)
> - FIELD(IRQ_CTRL, GERROR_IRQEN, 0, 1)
> - FIELD(IRQ_CTRL, PRI_IRQEN, 1, 1)
> - FIELD(IRQ_CTRL, EVENTQ_IRQEN, 2, 1)
> -
> -REG32(IRQ_CTRL_ACK, 0x54)
> -REG32(GERROR, 0x60)
> - FIELD(GERROR, CMDQ_ERR, 0, 1)
> - FIELD(GERROR, EVENTQ_ABT_ERR, 2, 1)
> - FIELD(GERROR, PRIQ_ABT_ERR, 3, 1)
> - FIELD(GERROR, MSI_CMDQ_ABT_ERR, 4, 1)
> - FIELD(GERROR, MSI_EVENTQ_ABT_ERR, 5, 1)
> - FIELD(GERROR, MSI_PRIQ_ABT_ERR, 6, 1)
> - FIELD(GERROR, MSI_GERROR_ABT_ERR, 7, 1)
> - FIELD(GERROR, MSI_SFM_ERR, 8, 1)
> -
> -REG32(GERRORN, 0x64)
> -
> -#define A_GERROR_IRQ_CFG0 0x68 /* 64b */
> -REG32(GERROR_IRQ_CFG1, 0x70)
> -REG32(GERROR_IRQ_CFG2, 0x74)
> -
> -#define A_STRTAB_BASE 0x80 /* 64b */
> -
> -#define SMMU_BASE_ADDR_MASK 0xfffffffffffc0
> -
> -REG32(STRTAB_BASE_CFG, 0x88)
> - FIELD(STRTAB_BASE_CFG, FMT, 16, 2)
> - FIELD(STRTAB_BASE_CFG, SPLIT, 6 , 5)
> - FIELD(STRTAB_BASE_CFG, LOG2SIZE, 0 , 6)
> -
> -#define A_CMDQ_BASE 0x90 /* 64b */
> -REG32(CMDQ_PROD, 0x98)
> -REG32(CMDQ_CONS, 0x9c)
> - FIELD(CMDQ_CONS, ERR, 24, 7)
> -
> -#define A_EVENTQ_BASE 0xa0 /* 64b */
> -REG32(EVENTQ_PROD, 0xa8)
> -REG32(EVENTQ_CONS, 0xac)
> -
> -#define A_EVENTQ_IRQ_CFG0 0xb0 /* 64b */
> -REG32(EVENTQ_IRQ_CFG1, 0xb8)
> -REG32(EVENTQ_IRQ_CFG2, 0xbc)
> -
> -#define A_IDREGS 0xfd0
> -
> static inline int smmu_enabled(SMMUv3State *s)
> {
> return FIELD_EX32(s->cr[0], CR0, SMMU_ENABLE);
> @@ -272,37 +132,6 @@ static inline void smmu_write_cmdq_err(SMMUv3State *s, uint32_t err_type)
> s->cmdq.cons = FIELD_DP32(s->cmdq.cons, CMDQ_CONS, ERR, err_type);
> }
>
> -/* Commands */
> -
> -typedef enum SMMUCommandType {
> - SMMU_CMD_NONE = 0x00,
> - SMMU_CMD_PREFETCH_CONFIG ,
> - SMMU_CMD_PREFETCH_ADDR,
> - SMMU_CMD_CFGI_STE,
> - SMMU_CMD_CFGI_STE_RANGE,
> - SMMU_CMD_CFGI_CD,
> - SMMU_CMD_CFGI_CD_ALL,
> - SMMU_CMD_CFGI_ALL,
> - SMMU_CMD_TLBI_NH_ALL = 0x10,
> - SMMU_CMD_TLBI_NH_ASID,
> - SMMU_CMD_TLBI_NH_VA,
> - SMMU_CMD_TLBI_NH_VAA,
> - SMMU_CMD_TLBI_EL3_ALL = 0x18,
> - SMMU_CMD_TLBI_EL3_VA = 0x1a,
> - SMMU_CMD_TLBI_EL2_ALL = 0x20,
> - SMMU_CMD_TLBI_EL2_ASID,
> - SMMU_CMD_TLBI_EL2_VA,
> - SMMU_CMD_TLBI_EL2_VAA,
> - SMMU_CMD_TLBI_S12_VMALL = 0x28,
> - SMMU_CMD_TLBI_S2_IPA = 0x2a,
> - SMMU_CMD_TLBI_NSNH_ALL = 0x30,
> - SMMU_CMD_ATC_INV = 0x40,
> - SMMU_CMD_PRI_RESP,
> - SMMU_CMD_RESUME = 0x44,
> - SMMU_CMD_STALL_TERM,
> - SMMU_CMD_SYNC,
> -} SMMUCommandType;
> -
> static const char *cmd_stringify[] = {
> [SMMU_CMD_PREFETCH_CONFIG] = "SMMU_CMD_PREFETCH_CONFIG",
> [SMMU_CMD_PREFETCH_ADDR] = "SMMU_CMD_PREFETCH_ADDR",
> @@ -525,64 +354,6 @@ typedef struct SMMUEventInfo {
>
> void smmuv3_record_event(SMMUv3State *s, SMMUEventInfo *event);
>
> -/* Configuration Data */
> -
> -/* STE Level 1 Descriptor */
> -typedef struct STEDesc {
> - uint32_t word[2];
> -} STEDesc;
> -
> -/* CD Level 1 Descriptor */
> -typedef struct CDDesc {
> - uint32_t word[2];
> -} CDDesc;
> -
> -/* Stream Table Entry(STE) */
> -typedef struct STE {
> - uint32_t word[16];
> -} STE;
> -
> -/* Context Descriptor(CD) */
> -typedef struct CD {
> - uint32_t word[16];
> -} CD;
> -
> -/* STE fields */
> -
> -#define STE_VALID(x) extract32((x)->word[0], 0, 1)
> -
> -#define STE_CONFIG(x) extract32((x)->word[0], 1, 3)
> -#define STE_CFG_S1_ENABLED(config) (config & 0x1)
> -#define STE_CFG_S2_ENABLED(config) (config & 0x2)
> -#define STE_CFG_ABORT(config) (!(config & 0x4))
> -#define STE_CFG_BYPASS(config) (config == 0x4)
> -
> -#define STE_S1FMT(x) extract32((x)->word[0], 4 , 2)
> -#define STE_S1CDMAX(x) extract32((x)->word[1], 27, 5)
> -#define STE_S1STALLD(x) extract32((x)->word[2], 27, 1)
> -#define STE_EATS(x) extract32((x)->word[2], 28, 2)
> -#define STE_STRW(x) extract32((x)->word[2], 30, 2)
> -#define STE_S2VMID(x) extract32((x)->word[4], 0 , 16)
> -#define STE_S2T0SZ(x) extract32((x)->word[5], 0 , 6)
> -#define STE_S2SL0(x) extract32((x)->word[5], 6 , 2)
> -#define STE_S2TG(x) extract32((x)->word[5], 14, 2)
> -#define STE_S2PS(x) extract32((x)->word[5], 16, 3)
> -#define STE_S2AA64(x) extract32((x)->word[5], 19, 1)
> -#define STE_S2ENDI(x) extract32((x)->word[5], 20, 1)
> -#define STE_S2AFFD(x) extract32((x)->word[5], 21, 1)
> -#define STE_S2HD(x) extract32((x)->word[5], 23, 1)
> -#define STE_S2HA(x) extract32((x)->word[5], 24, 1)
> -#define STE_S2S(x) extract32((x)->word[5], 25, 1)
> -#define STE_S2R(x) extract32((x)->word[5], 26, 1)
> -
> -#define STE_CTXPTR(x) \
> - ((extract64((x)->word[1], 0, 16) << 32) | \
> - ((x)->word[0] & 0xffffffc0))
> -
> -#define STE_S2TTB(x) \
> - ((extract64((x)->word[7], 0, 16) << 32) | \
> - ((x)->word[6] & 0xfffffff0))
> -
> static inline int oas2bits(int oas_field)
> {
> switch (oas_field) {
> @@ -603,30 +374,6 @@ static inline int oas2bits(int oas_field)
> g_assert_not_reached();
> }
>
> -/* CD fields */
> -
> -#define CD_VALID(x) extract32((x)->word[0], 31, 1)
> -#define CD_ASID(x) extract32((x)->word[1], 16, 16)
> -#define CD_TTB(x, sel) \
> - ((extract64((x)->word[(sel) * 2 + 3], 0, 19) << 32) | \
> - ((x)->word[(sel) * 2 + 2] & ~0xfULL))
> -
> -#define CD_HAD(x, sel) extract32((x)->word[(sel) * 2 + 2], 1, 1)
> -
> -#define CD_TSZ(x, sel) extract32((x)->word[0], (16 * (sel)) + 0, 6)
> -#define CD_TG(x, sel) extract32((x)->word[0], (16 * (sel)) + 6, 2)
> -#define CD_EPD(x, sel) extract32((x)->word[0], (16 * (sel)) + 14, 1)
> -#define CD_ENDI(x) extract32((x)->word[0], 15, 1)
> -#define CD_IPS(x) extract32((x)->word[1], 0 , 3)
> -#define CD_AFFD(x) extract32((x)->word[1], 3 , 1)
> -#define CD_TBI(x) extract32((x)->word[1], 6 , 2)
> -#define CD_HD(x) extract32((x)->word[1], 10 , 1)
> -#define CD_HA(x) extract32((x)->word[1], 11 , 1)
> -#define CD_S(x) extract32((x)->word[1], 12, 1)
> -#define CD_R(x) extract32((x)->word[1], 13, 1)
> -#define CD_A(x) extract32((x)->word[1], 14, 1)
> -#define CD_AARCH64(x) extract32((x)->word[1], 9 , 1)
> -
> /**
> * tg2granule - Decodes the CD translation granule size field according
> * to the ttbr in use
> diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
> new file mode 100644
> index 0000000000..9da817f41a
> --- /dev/null
> +++ b/include/hw/arm/smmuv3-common.h
> @@ -0,0 +1,268 @@
> +/*
> + * ARM SMMUv3 support - Common API
> + *
> + * Copyright (C) 2014-2016 Broadcom Corporation
> + * Copyright (c) 2017 Red Hat, Inc.
> + * Written by Prem Mallappa, Eric Auger
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_ARM_SMMUV3_COMMON_H
> +#define HW_ARM_SMMUV3_COMMON_H
> +
> +/* Configuration Data */
> +
> +/* STE Level 1 Descriptor */
> +typedef struct STEDesc {
> + uint32_t word[2];
> +} STEDesc;
> +
> +/* CD Level 1 Descriptor */
> +typedef struct CDDesc {
> + uint32_t word[2];
> +} CDDesc;
> +
> +/* Stream Table Entry(STE) */
> +typedef struct STE {
> + uint32_t word[16];
> +} STE;
> +
> +/* Context Descriptor(CD) */
> +typedef struct CD {
> + uint32_t word[16];
> +} CD;
> +
> +/* STE fields */
> +
> +#define STE_VALID(x) extract32((x)->word[0], 0, 1)
> +
> +#define STE_CONFIG(x) extract32((x)->word[0], 1, 3)
> +#define STE_CFG_S1_ENABLED(config) (config & 0x1)
> +#define STE_CFG_S2_ENABLED(config) (config & 0x2)
> +#define STE_CFG_ABORT(config) (!(config & 0x4))
> +#define STE_CFG_BYPASS(config) (config == 0x4)
> +
> +#define STE_S1FMT(x) extract32((x)->word[0], 4 , 2)
> +#define STE_S1CDMAX(x) extract32((x)->word[1], 27, 5)
> +#define STE_S1STALLD(x) extract32((x)->word[2], 27, 1)
> +#define STE_EATS(x) extract32((x)->word[2], 28, 2)
> +#define STE_STRW(x) extract32((x)->word[2], 30, 2)
> +#define STE_S2VMID(x) extract32((x)->word[4], 0 , 16)
> +#define STE_S2T0SZ(x) extract32((x)->word[5], 0 , 6)
> +#define STE_S2SL0(x) extract32((x)->word[5], 6 , 2)
> +#define STE_S2TG(x) extract32((x)->word[5], 14, 2)
> +#define STE_S2PS(x) extract32((x)->word[5], 16, 3)
> +#define STE_S2AA64(x) extract32((x)->word[5], 19, 1)
> +#define STE_S2ENDI(x) extract32((x)->word[5], 20, 1)
> +#define STE_S2AFFD(x) extract32((x)->word[5], 21, 1)
> +#define STE_S2HD(x) extract32((x)->word[5], 23, 1)
> +#define STE_S2HA(x) extract32((x)->word[5], 24, 1)
> +#define STE_S2S(x) extract32((x)->word[5], 25, 1)
> +#define STE_S2R(x) extract32((x)->word[5], 26, 1)
> +
> +#define STE_CTXPTR(x) \
> + ((extract64((x)->word[1], 0, 16) << 32) | \
> + ((x)->word[0] & 0xffffffc0))
> +
> +#define STE_S2TTB(x) \
> + ((extract64((x)->word[7], 0, 16) << 32) | \
> + ((x)->word[6] & 0xfffffff0))
> +
> +/* CD fields */
> +
> +#define CD_VALID(x) extract32((x)->word[0], 31, 1)
> +#define CD_ASID(x) extract32((x)->word[1], 16, 16)
> +#define CD_TTB(x, sel) \
> + ((extract64((x)->word[(sel) * 2 + 3], 0, 19) << 32) | \
> + ((x)->word[(sel) * 2 + 2] & ~0xfULL))
> +
> +#define CD_HAD(x, sel) extract32((x)->word[(sel) * 2 + 2], 1, 1)
> +
> +#define CD_TSZ(x, sel) extract32((x)->word[0], (16 * (sel)) + 0, 6)
> +#define CD_TG(x, sel) extract32((x)->word[0], (16 * (sel)) + 6, 2)
> +#define CD_EPD(x, sel) extract32((x)->word[0], (16 * (sel)) + 14, 1)
> +#define CD_ENDI(x) extract32((x)->word[0], 15, 1)
> +#define CD_IPS(x) extract32((x)->word[1], 0 , 3)
> +#define CD_AFFD(x) extract32((x)->word[1], 3 , 1)
> +#define CD_TBI(x) extract32((x)->word[1], 6 , 2)
> +#define CD_HD(x) extract32((x)->word[1], 10 , 1)
> +#define CD_HA(x) extract32((x)->word[1], 11 , 1)
> +#define CD_S(x) extract32((x)->word[1], 12, 1)
> +#define CD_R(x) extract32((x)->word[1], 13, 1)
> +#define CD_A(x) extract32((x)->word[1], 14, 1)
> +#define CD_AARCH64(x) extract32((x)->word[1], 9 , 1)
> +
> +/* MMIO Registers */
> +
> +REG32(IDR0, 0x0)
> + FIELD(IDR0, S2P, 0 , 1)
> + FIELD(IDR0, S1P, 1 , 1)
> + FIELD(IDR0, TTF, 2 , 2)
> + FIELD(IDR0, COHACC, 4 , 1)
> + FIELD(IDR0, BTM, 5 , 1)
> + FIELD(IDR0, HTTU, 6 , 2)
> + FIELD(IDR0, DORMHINT, 8 , 1)
> + FIELD(IDR0, HYP, 9 , 1)
> + FIELD(IDR0, ATS, 10, 1)
> + FIELD(IDR0, NS1ATS, 11, 1)
> + FIELD(IDR0, ASID16, 12, 1)
> + FIELD(IDR0, MSI, 13, 1)
> + FIELD(IDR0, SEV, 14, 1)
> + FIELD(IDR0, ATOS, 15, 1)
> + FIELD(IDR0, PRI, 16, 1)
> + FIELD(IDR0, VMW, 17, 1)
> + FIELD(IDR0, VMID16, 18, 1)
> + FIELD(IDR0, CD2L, 19, 1)
> + FIELD(IDR0, VATOS, 20, 1)
> + FIELD(IDR0, TTENDIAN, 21, 2)
> + FIELD(IDR0, ATSRECERR, 23, 1)
> + FIELD(IDR0, STALL_MODEL, 24, 2)
> + FIELD(IDR0, TERM_MODEL, 26, 1)
> + FIELD(IDR0, STLEVEL, 27, 2)
> + FIELD(IDR0, RME_IMPL, 30, 1)
> +
> +REG32(IDR1, 0x4)
> + FIELD(IDR1, SIDSIZE, 0 , 6)
> + FIELD(IDR1, SSIDSIZE, 6 , 5)
> + FIELD(IDR1, PRIQS, 11, 5)
> + FIELD(IDR1, EVENTQS, 16, 5)
> + FIELD(IDR1, CMDQS, 21, 5)
> + FIELD(IDR1, ATTR_PERMS_OVR, 26, 1)
> + FIELD(IDR1, ATTR_TYPES_OVR, 27, 1)
> + FIELD(IDR1, REL, 28, 1)
> + FIELD(IDR1, QUEUES_PRESET, 29, 1)
> + FIELD(IDR1, TABLES_PRESET, 30, 1)
> + FIELD(IDR1, ECMDQ, 31, 1)
> +
> +#define SMMU_IDR1_SIDSIZE 16
> +#define SMMU_CMDQS 19
> +#define SMMU_EVENTQS 19
> +
> +REG32(IDR2, 0x8)
> + FIELD(IDR2, BA_VATOS, 0, 10)
> +
> +REG32(IDR3, 0xc)
> + FIELD(IDR3, HAD, 2, 1);
> + FIELD(IDR3, PBHA, 3, 1);
> + FIELD(IDR3, XNX, 4, 1);
> + FIELD(IDR3, PPS, 5, 1);
> + FIELD(IDR3, MPAM, 7, 1);
> + FIELD(IDR3, FWB, 8, 1);
> + FIELD(IDR3, STT, 9, 1);
> + FIELD(IDR3, RIL, 10, 1);
> + FIELD(IDR3, BBML, 11, 2);
> + FIELD(IDR3, E0PD, 13, 1);
> + FIELD(IDR3, PTWNNC, 14, 1);
> + FIELD(IDR3, DPT, 15, 1);
> +
> +REG32(IDR4, 0x10)
> +
> +REG32(IDR5, 0x14)
> + FIELD(IDR5, OAS, 0, 3);
> + FIELD(IDR5, GRAN4K, 4, 1);
> + FIELD(IDR5, GRAN16K, 5, 1);
> + FIELD(IDR5, GRAN64K, 6, 1);
> + FIELD(IDR5, VAX, 10, 2);
> + FIELD(IDR5, STALL_MAX, 16, 16);
> +
> +#define SMMU_IDR5_OAS 4
> +
> +REG32(IIDR, 0x18)
> +REG32(AIDR, 0x1c)
> +REG32(CR0, 0x20)
> + FIELD(CR0, SMMU_ENABLE, 0, 1)
> + FIELD(CR0, EVENTQEN, 2, 1)
> + FIELD(CR0, CMDQEN, 3, 1)
> +
> +#define SMMU_CR0_RESERVED 0xFFFFFC20
> +
> +REG32(CR0ACK, 0x24)
> +REG32(CR1, 0x28)
> +REG32(CR2, 0x2c)
> +REG32(STATUSR, 0x40)
> +REG32(GBPA, 0x44)
> + FIELD(GBPA, ABORT, 20, 1)
> + FIELD(GBPA, UPDATE, 31, 1)
> +
> +/* Use incoming. */
> +#define SMMU_GBPA_RESET_VAL 0x1000
> +
> +REG32(IRQ_CTRL, 0x50)
> + FIELD(IRQ_CTRL, GERROR_IRQEN, 0, 1)
> + FIELD(IRQ_CTRL, PRI_IRQEN, 1, 1)
> + FIELD(IRQ_CTRL, EVENTQ_IRQEN, 2, 1)
> +
> +REG32(IRQ_CTRL_ACK, 0x54)
> +REG32(GERROR, 0x60)
> + FIELD(GERROR, CMDQ_ERR, 0, 1)
> + FIELD(GERROR, EVENTQ_ABT_ERR, 2, 1)
> + FIELD(GERROR, PRIQ_ABT_ERR, 3, 1)
> + FIELD(GERROR, MSI_CMDQ_ABT_ERR, 4, 1)
> + FIELD(GERROR, MSI_EVENTQ_ABT_ERR, 5, 1)
> + FIELD(GERROR, MSI_PRIQ_ABT_ERR, 6, 1)
> + FIELD(GERROR, MSI_GERROR_ABT_ERR, 7, 1)
> + FIELD(GERROR, MSI_SFM_ERR, 8, 1)
> +
> +REG32(GERRORN, 0x64)
> +
> +#define A_GERROR_IRQ_CFG0 0x68 /* 64b */
> +REG32(GERROR_IRQ_CFG1, 0x70)
> +REG32(GERROR_IRQ_CFG2, 0x74)
> +
> +#define A_STRTAB_BASE 0x80 /* 64b */
> +
> +#define SMMU_BASE_ADDR_MASK 0xfffffffffffc0
> +
> +REG32(STRTAB_BASE_CFG, 0x88)
> + FIELD(STRTAB_BASE_CFG, FMT, 16, 2)
> + FIELD(STRTAB_BASE_CFG, SPLIT, 6 , 5)
> + FIELD(STRTAB_BASE_CFG, LOG2SIZE, 0 , 6)
> +
> +#define A_CMDQ_BASE 0x90 /* 64b */
> +REG32(CMDQ_PROD, 0x98)
> +REG32(CMDQ_CONS, 0x9c)
> + FIELD(CMDQ_CONS, ERR, 24, 7)
> +
> +#define A_EVENTQ_BASE 0xa0 /* 64b */
> +REG32(EVENTQ_PROD, 0xa8)
> +REG32(EVENTQ_CONS, 0xac)
> +
> +#define A_EVENTQ_IRQ_CFG0 0xb0 /* 64b */
> +REG32(EVENTQ_IRQ_CFG1, 0xb8)
> +REG32(EVENTQ_IRQ_CFG2, 0xbc)
> +
> +#define A_IDREGS 0xfd0
> +
> +/* Commands */
> +
> +typedef enum SMMUCommandType {
> + SMMU_CMD_NONE = 0x00,
> + SMMU_CMD_PREFETCH_CONFIG ,
> + SMMU_CMD_PREFETCH_ADDR,
> + SMMU_CMD_CFGI_STE,
> + SMMU_CMD_CFGI_STE_RANGE,
> + SMMU_CMD_CFGI_CD,
> + SMMU_CMD_CFGI_CD_ALL,
> + SMMU_CMD_CFGI_ALL,
> + SMMU_CMD_TLBI_NH_ALL = 0x10,
> + SMMU_CMD_TLBI_NH_ASID,
> + SMMU_CMD_TLBI_NH_VA,
> + SMMU_CMD_TLBI_NH_VAA,
> + SMMU_CMD_TLBI_EL3_ALL = 0x18,
> + SMMU_CMD_TLBI_EL3_VA = 0x1a,
> + SMMU_CMD_TLBI_EL2_ALL = 0x20,
> + SMMU_CMD_TLBI_EL2_ASID,
> + SMMU_CMD_TLBI_EL2_VA,
> + SMMU_CMD_TLBI_EL2_VAA,
> + SMMU_CMD_TLBI_S12_VMALL = 0x28,
> + SMMU_CMD_TLBI_S2_IPA = 0x2a,
> + SMMU_CMD_TLBI_NSNH_ALL = 0x30,
> + SMMU_CMD_ATC_INV = 0x40,
> + SMMU_CMD_PRI_RESP,
> + SMMU_CMD_RESUME = 0x44,
> + SMMU_CMD_STALL_TERM,
> + SMMU_CMD_SYNC,
> +} SMMUCommandType;
> +
> +#endif /* HW_ARM_SMMUV3_COMMON_H */
^ permalink raw reply [flat|nested] 35+ messages in thread
* [RFC v8 2/7] hw/arm/smmuv3-common: Define STE/CD fields via registerfields
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
2025-12-24 3:46 ` [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h Tao Tang
@ 2025-12-24 3:46 ` Tao Tang
2025-12-24 17:05 ` Pierrick Bouvier
2026-01-15 9:14 ` Eric Auger
2025-12-24 3:46 ` [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing Tao Tang
` (6 subsequent siblings)
8 siblings, 2 replies; 35+ messages in thread
From: Tao Tang @ 2025-12-24 3:46 UTC (permalink / raw)
To: Paolo Bonzini, Fabiano Rosas, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF, Tao Tang
Switch STE/CD bitfield definitions and accessors to the
'hw/registerfields.h' REG/FIELD API.
Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
---
include/hw/arm/smmuv3-common.h | 169 +++++++++++++++++++++++----------
1 file changed, 120 insertions(+), 49 deletions(-)
diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
index 9da817f41a..b6da2fd62c 100644
--- a/include/hw/arm/smmuv3-common.h
+++ b/include/hw/arm/smmuv3-common.h
@@ -11,6 +11,8 @@
#ifndef HW_ARM_SMMUV3_COMMON_H
#define HW_ARM_SMMUV3_COMMON_H
+#include "hw/registerfields.h"
+
/* Configuration Data */
/* STE Level 1 Descriptor */
@@ -35,63 +37,132 @@ typedef struct CD {
/* STE fields */
-#define STE_VALID(x) extract32((x)->word[0], 0, 1)
+REG32(STE_0, 0)
+ FIELD(STE_0, VALID, 0, 1)
+ FIELD(STE_0, CONFIG, 1, 3)
+ FIELD(STE_0, S1FMT, 4, 2)
+ FIELD(STE_0, CTXPTR_LO, 6, 26)
+REG32(STE_1, 4)
+ FIELD(STE_1, CTXPTR_HI, 0, 16)
+ FIELD(STE_1, S1CDMAX, 27, 5)
+REG32(STE_2, 8)
+ FIELD(STE_2, S1STALLD, 27, 1)
+ FIELD(STE_2, EATS, 28, 2)
+ FIELD(STE_2, STRW, 30, 2)
+REG32(STE_4, 16)
+ FIELD(STE_4, S2VMID, 0, 16)
+REG32(STE_5, 20)
+ FIELD(STE_5, S2T0SZ, 0, 6)
+ FIELD(STE_5, S2SL0, 6, 2)
+ FIELD(STE_5, S2TG, 14, 2)
+ FIELD(STE_5, S2PS, 16, 3)
+ FIELD(STE_5, S2AA64, 19, 1)
+ FIELD(STE_5, S2ENDI, 20, 1)
+ FIELD(STE_5, S2AFFD, 21, 1)
+ FIELD(STE_5, S2HD, 23, 1)
+ FIELD(STE_5, S2HA, 24, 1)
+ FIELD(STE_5, S2S, 25, 1)
+ FIELD(STE_5, S2R, 26, 1)
+REG32(STE_6, 24)
+ FIELD(STE_6, S2TTB_LO, 4, 28)
+REG32(STE_7, 28)
+ FIELD(STE_7, S2TTB_HI, 0, 16)
+
+/* Get STE fields */
+#define STE_VALID(x) FIELD_EX32((x)->word[0], STE_0, VALID)
+#define STE_CONFIG(x) FIELD_EX32((x)->word[0], STE_0, CONFIG)
+#define STE_S1FMT(x) FIELD_EX32((x)->word[0], STE_0, S1FMT)
+#define STE_CTXPTR(x) \
+ (((uint64_t)FIELD_EX32((x)->word[0], STE_0, CTXPTR_LO) << 6) | \
+ ((uint64_t)FIELD_EX32((x)->word[1], STE_1, CTXPTR_HI) << 32))
+#define STE_S1CDMAX(x) FIELD_EX32((x)->word[1], STE_1, S1CDMAX)
+#define STE_S1STALLD(x) FIELD_EX32((x)->word[2], STE_2, S1STALLD)
+#define STE_EATS(x) FIELD_EX32((x)->word[2], STE_2, EATS)
+#define STE_STRW(x) FIELD_EX32((x)->word[2], STE_2, STRW)
+#define STE_S2VMID(x) FIELD_EX32((x)->word[4], STE_4, S2VMID)
+#define STE_S2T0SZ(x) FIELD_EX32((x)->word[5], STE_5, S2T0SZ)
+#define STE_S2SL0(x) FIELD_EX32((x)->word[5], STE_5, S2SL0)
+#define STE_S2TG(x) FIELD_EX32((x)->word[5], STE_5, S2TG)
+#define STE_S2PS(x) FIELD_EX32((x)->word[5], STE_5, S2PS)
+#define STE_S2AA64(x) FIELD_EX32((x)->word[5], STE_5, S2AA64)
+#define STE_S2ENDI(x) FIELD_EX32((x)->word[5], STE_5, S2ENDI)
+#define STE_S2AFFD(x) FIELD_EX32((x)->word[5], STE_5, S2AFFD)
+#define STE_S2HD(x) FIELD_EX32((x)->word[5], STE_5, S2HD)
+#define STE_S2HA(x) FIELD_EX32((x)->word[5], STE_5, S2HA)
+#define STE_S2S(x) FIELD_EX32((x)->word[5], STE_5, S2S)
+#define STE_S2R(x) FIELD_EX32((x)->word[5], STE_5, S2R)
+#define STE_S2TTB(x) \
+ (((uint64_t)FIELD_EX32((x)->word[6], STE_6, S2TTB_LO) << 4) | \
+ ((uint64_t)FIELD_EX32((x)->word[7], STE_7, S2TTB_HI) << 32))
-#define STE_CONFIG(x) extract32((x)->word[0], 1, 3)
#define STE_CFG_S1_ENABLED(config) (config & 0x1)
#define STE_CFG_S2_ENABLED(config) (config & 0x2)
#define STE_CFG_ABORT(config) (!(config & 0x4))
#define STE_CFG_BYPASS(config) (config == 0x4)
-#define STE_S1FMT(x) extract32((x)->word[0], 4 , 2)
-#define STE_S1CDMAX(x) extract32((x)->word[1], 27, 5)
-#define STE_S1STALLD(x) extract32((x)->word[2], 27, 1)
-#define STE_EATS(x) extract32((x)->word[2], 28, 2)
-#define STE_STRW(x) extract32((x)->word[2], 30, 2)
-#define STE_S2VMID(x) extract32((x)->word[4], 0 , 16)
-#define STE_S2T0SZ(x) extract32((x)->word[5], 0 , 6)
-#define STE_S2SL0(x) extract32((x)->word[5], 6 , 2)
-#define STE_S2TG(x) extract32((x)->word[5], 14, 2)
-#define STE_S2PS(x) extract32((x)->word[5], 16, 3)
-#define STE_S2AA64(x) extract32((x)->word[5], 19, 1)
-#define STE_S2ENDI(x) extract32((x)->word[5], 20, 1)
-#define STE_S2AFFD(x) extract32((x)->word[5], 21, 1)
-#define STE_S2HD(x) extract32((x)->word[5], 23, 1)
-#define STE_S2HA(x) extract32((x)->word[5], 24, 1)
-#define STE_S2S(x) extract32((x)->word[5], 25, 1)
-#define STE_S2R(x) extract32((x)->word[5], 26, 1)
-
-#define STE_CTXPTR(x) \
- ((extract64((x)->word[1], 0, 16) << 32) | \
- ((x)->word[0] & 0xffffffc0))
-
-#define STE_S2TTB(x) \
- ((extract64((x)->word[7], 0, 16) << 32) | \
- ((x)->word[6] & 0xfffffff0))
-
/* CD fields */
-#define CD_VALID(x) extract32((x)->word[0], 31, 1)
-#define CD_ASID(x) extract32((x)->word[1], 16, 16)
-#define CD_TTB(x, sel) \
- ((extract64((x)->word[(sel) * 2 + 3], 0, 19) << 32) | \
- ((x)->word[(sel) * 2 + 2] & ~0xfULL))
-
-#define CD_HAD(x, sel) extract32((x)->word[(sel) * 2 + 2], 1, 1)
-
-#define CD_TSZ(x, sel) extract32((x)->word[0], (16 * (sel)) + 0, 6)
-#define CD_TG(x, sel) extract32((x)->word[0], (16 * (sel)) + 6, 2)
-#define CD_EPD(x, sel) extract32((x)->word[0], (16 * (sel)) + 14, 1)
-#define CD_ENDI(x) extract32((x)->word[0], 15, 1)
-#define CD_IPS(x) extract32((x)->word[1], 0 , 3)
-#define CD_AFFD(x) extract32((x)->word[1], 3 , 1)
-#define CD_TBI(x) extract32((x)->word[1], 6 , 2)
-#define CD_HD(x) extract32((x)->word[1], 10 , 1)
-#define CD_HA(x) extract32((x)->word[1], 11 , 1)
-#define CD_S(x) extract32((x)->word[1], 12, 1)
-#define CD_R(x) extract32((x)->word[1], 13, 1)
-#define CD_A(x) extract32((x)->word[1], 14, 1)
-#define CD_AARCH64(x) extract32((x)->word[1], 9 , 1)
+REG32(CD_0, 0)
+ FIELD(CD_0, TSZ0, 0, 6)
+ FIELD(CD_0, TG0, 6, 2)
+ FIELD(CD_0, EPD0, 14, 1)
+ FIELD(CD_0, ENDI, 15, 1)
+ FIELD(CD_0, TSZ1, 16, 6)
+ FIELD(CD_0, TG1, 22, 2)
+ FIELD(CD_0, EPD1, 30, 1)
+ FIELD(CD_0, VALID, 31, 1)
+REG32(CD_1, 4)
+ FIELD(CD_1, IPS, 0, 3)
+ FIELD(CD_1, AFFD, 3, 1)
+ FIELD(CD_1, TBI, 6, 2)
+ FIELD(CD_1, AARCH64, 9, 1)
+ FIELD(CD_1, HD, 10, 1)
+ FIELD(CD_1, HA, 11, 1)
+ FIELD(CD_1, S, 12, 1)
+ FIELD(CD_1, R, 13, 1)
+ FIELD(CD_1, A, 14, 1)
+ FIELD(CD_1, ASID, 16, 16)
+REG32(CD_2, 8)
+ FIELD(CD_2, HAD0, 1, 1)
+ FIELD(CD_2, TTB0_LO, 4, 28)
+REG32(CD_3, 12)
+ FIELD(CD_3, TTB0_HI, 0, 19)
+REG32(CD_4, 16)
+ FIELD(CD_4, HAD1, 1, 1)
+ FIELD(CD_4, TTB1_LO, 4, 28)
+REG32(CD_5, 20)
+ FIELD(CD_5, TTB1_HI, 0, 19)
+
+/* Get CD fields */
+#define CD_TSZ(x, sel) ((sel) ? \
+ FIELD_EX32((x)->word[0], CD_0, TSZ1) : \
+ FIELD_EX32((x)->word[0], CD_0, TSZ0))
+#define CD_TG(x, sel) ((sel) ? \
+ FIELD_EX32((x)->word[0], CD_0, TG1) : \
+ FIELD_EX32((x)->word[0], CD_0, TG0))
+#define CD_EPD(x, sel) ((sel) ? \
+ FIELD_EX32((x)->word[0], CD_0, EPD1) : \
+ FIELD_EX32((x)->word[0], CD_0, EPD0))
+#define CD_ENDI(x) FIELD_EX32((x)->word[0], CD_0, ENDI)
+#define CD_VALID(x) FIELD_EX32((x)->word[0], CD_0, VALID)
+#define CD_IPS(x) FIELD_EX32((x)->word[1], CD_1, IPS)
+#define CD_AFFD(x) FIELD_EX32((x)->word[1], CD_1, AFFD)
+#define CD_TBI(x) FIELD_EX32((x)->word[1], CD_1, TBI)
+#define CD_AARCH64(x) FIELD_EX32((x)->word[1], CD_1, AARCH64)
+#define CD_HD(x) FIELD_EX32((x)->word[1], CD_1, HD)
+#define CD_HA(x) FIELD_EX32((x)->word[1], CD_1, HA)
+#define CD_S(x) FIELD_EX32((x)->word[1], CD_1, S)
+#define CD_R(x) FIELD_EX32((x)->word[1], CD_1, R)
+#define CD_A(x) FIELD_EX32((x)->word[1], CD_1, A)
+#define CD_ASID(x) FIELD_EX32((x)->word[1], CD_1, ASID)
+#define CD_HAD(x, sel) ((sel) ? \
+ FIELD_EX32((x)->word[4], CD_4, HAD1) : \
+ FIELD_EX32((x)->word[2], CD_2, HAD0))
+#define CD_TTB(x, sel) \
+ ((sel) ? (((uint64_t)FIELD_EX32((x)->word[5], CD_5, TTB1_HI) << 32) | \
+ ((uint64_t)FIELD_EX32((x)->word[4], CD_4, TTB1_LO) << 4)) : \
+ (((uint64_t)FIELD_EX32((x)->word[3], CD_3, TTB0_HI) << 32) | \
+ ((uint64_t)FIELD_EX32((x)->word[2], CD_2, TTB0_LO) << 4)))
/* MMIO Registers */
--
2.34.1
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [RFC v8 2/7] hw/arm/smmuv3-common: Define STE/CD fields via registerfields
2025-12-24 3:46 ` [RFC v8 2/7] hw/arm/smmuv3-common: Define STE/CD fields via registerfields Tao Tang
@ 2025-12-24 17:05 ` Pierrick Bouvier
2026-01-15 9:14 ` Eric Auger
1 sibling, 0 replies; 35+ messages in thread
From: Pierrick Bouvier @ 2025-12-24 17:05 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Eric Auger, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Philippe Mathieu-Daudé,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
On 12/23/25 7:46 PM, Tao Tang wrote:
> Switch STE/CD bitfield definitions and accessors to the
> 'hw/registerfields.h' REG/FIELD API.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> ---
> include/hw/arm/smmuv3-common.h | 169 +++++++++++++++++++++++----------
> 1 file changed, 120 insertions(+), 49 deletions(-)
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 2/7] hw/arm/smmuv3-common: Define STE/CD fields via registerfields
2025-12-24 3:46 ` [RFC v8 2/7] hw/arm/smmuv3-common: Define STE/CD fields via registerfields Tao Tang
2025-12-24 17:05 ` Pierrick Bouvier
@ 2026-01-15 9:14 ` Eric Auger
2026-01-16 9:51 ` Tao Tang
1 sibling, 1 reply; 35+ messages in thread
From: Eric Auger @ 2026-01-15 9:14 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Tao,
On 12/24/25 4:46 AM, Tao Tang wrote:
> Switch STE/CD bitfield definitions and accessors to the
> 'hw/registerfields.h' REG/FIELD API.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> ---
> include/hw/arm/smmuv3-common.h | 169 +++++++++++++++++++++++----------
> 1 file changed, 120 insertions(+), 49 deletions(-)
>
> diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
> index 9da817f41a..b6da2fd62c 100644
> --- a/include/hw/arm/smmuv3-common.h
> +++ b/include/hw/arm/smmuv3-common.h
> @@ -11,6 +11,8 @@
> #ifndef HW_ARM_SMMUV3_COMMON_H
> #define HW_ARM_SMMUV3_COMMON_H
>
> +#include "hw/registerfields.h"
> +
> /* Configuration Data */
>
> /* STE Level 1 Descriptor */
> @@ -35,63 +37,132 @@ typedef struct CD {
>
> /* STE fields */
>
> -#define STE_VALID(x) extract32((x)->word[0], 0, 1)
> +REG32(STE_0, 0)
> + FIELD(STE_0, VALID, 0, 1)
> + FIELD(STE_0, CONFIG, 1, 3)
> + FIELD(STE_0, S1FMT, 4, 2)
> + FIELD(STE_0, CTXPTR_LO, 6, 26)
> +REG32(STE_1, 4)
> + FIELD(STE_1, CTXPTR_HI, 0, 16)
not related to your patch, but shouldn't it be 24 instead of 16
> + FIELD(STE_1, S1CDMAX, 27, 5)
> +REG32(STE_2, 8)
> + FIELD(STE_2, S1STALLD, 27, 1)
> + FIELD(STE_2, EATS, 28, 2)
> + FIELD(STE_2, STRW, 30, 2)
> +REG32(STE_4, 16)
> + FIELD(STE_4, S2VMID, 0, 16)
> +REG32(STE_5, 20)
> + FIELD(STE_5, S2T0SZ, 0, 6)
> + FIELD(STE_5, S2SL0, 6, 2)
> + FIELD(STE_5, S2TG, 14, 2)
> + FIELD(STE_5, S2PS, 16, 3)
> + FIELD(STE_5, S2AA64, 19, 1)
> + FIELD(STE_5, S2ENDI, 20, 1)
> + FIELD(STE_5, S2AFFD, 21, 1)
> + FIELD(STE_5, S2HD, 23, 1)
> + FIELD(STE_5, S2HA, 24, 1)
> + FIELD(STE_5, S2S, 25, 1)
> + FIELD(STE_5, S2R, 26, 1)
> +REG32(STE_6, 24)
> + FIELD(STE_6, S2TTB_LO, 4, 28)
> +REG32(STE_7, 28)
> + FIELD(STE_7, S2TTB_HI, 0, 16)
same here?
> +
> +/* Get STE fields */
> +#define STE_VALID(x) FIELD_EX32((x)->word[0], STE_0, VALID)
> +#define STE_CONFIG(x) FIELD_EX32((x)->word[0], STE_0, CONFIG)
> +#define STE_S1FMT(x) FIELD_EX32((x)->word[0], STE_0, S1FMT)
> +#define STE_CTXPTR(x) \
> + (((uint64_t)FIELD_EX32((x)->word[0], STE_0, CTXPTR_LO) << 6) | \
> + ((uint64_t)FIELD_EX32((x)->word[1], STE_1, CTXPTR_HI) << 32))
> +#define STE_S1CDMAX(x) FIELD_EX32((x)->word[1], STE_1, S1CDMAX)
> +#define STE_S1STALLD(x) FIELD_EX32((x)->word[2], STE_2, S1STALLD)
> +#define STE_EATS(x) FIELD_EX32((x)->word[2], STE_2, EATS)
> +#define STE_STRW(x) FIELD_EX32((x)->word[2], STE_2, STRW)
> +#define STE_S2VMID(x) FIELD_EX32((x)->word[4], STE_4, S2VMID)
> +#define STE_S2T0SZ(x) FIELD_EX32((x)->word[5], STE_5, S2T0SZ)
> +#define STE_S2SL0(x) FIELD_EX32((x)->word[5], STE_5, S2SL0)
> +#define STE_S2TG(x) FIELD_EX32((x)->word[5], STE_5, S2TG)
> +#define STE_S2PS(x) FIELD_EX32((x)->word[5], STE_5, S2PS)
> +#define STE_S2AA64(x) FIELD_EX32((x)->word[5], STE_5, S2AA64)
> +#define STE_S2ENDI(x) FIELD_EX32((x)->word[5], STE_5, S2ENDI)
> +#define STE_S2AFFD(x) FIELD_EX32((x)->word[5], STE_5, S2AFFD)
> +#define STE_S2HD(x) FIELD_EX32((x)->word[5], STE_5, S2HD)
> +#define STE_S2HA(x) FIELD_EX32((x)->word[5], STE_5, S2HA)
> +#define STE_S2S(x) FIELD_EX32((x)->word[5], STE_5, S2S)
> +#define STE_S2R(x) FIELD_EX32((x)->word[5], STE_5, S2R)
> +#define STE_S2TTB(x) \
> + (((uint64_t)FIELD_EX32((x)->word[6], STE_6, S2TTB_LO) << 4) | \
> + ((uint64_t)FIELD_EX32((x)->word[7], STE_7, S2TTB_HI) << 32))
>
> -#define STE_CONFIG(x) extract32((x)->word[0], 1, 3)
> #define STE_CFG_S1_ENABLED(config) (config & 0x1)
> #define STE_CFG_S2_ENABLED(config) (config & 0x2)
> #define STE_CFG_ABORT(config) (!(config & 0x4))
> #define STE_CFG_BYPASS(config) (config == 0x4)
>
> -#define STE_S1FMT(x) extract32((x)->word[0], 4 , 2)
> -#define STE_S1CDMAX(x) extract32((x)->word[1], 27, 5)
> -#define STE_S1STALLD(x) extract32((x)->word[2], 27, 1)
> -#define STE_EATS(x) extract32((x)->word[2], 28, 2)
> -#define STE_STRW(x) extract32((x)->word[2], 30, 2)
> -#define STE_S2VMID(x) extract32((x)->word[4], 0 , 16)
> -#define STE_S2T0SZ(x) extract32((x)->word[5], 0 , 6)
> -#define STE_S2SL0(x) extract32((x)->word[5], 6 , 2)
> -#define STE_S2TG(x) extract32((x)->word[5], 14, 2)
> -#define STE_S2PS(x) extract32((x)->word[5], 16, 3)
> -#define STE_S2AA64(x) extract32((x)->word[5], 19, 1)
> -#define STE_S2ENDI(x) extract32((x)->word[5], 20, 1)
> -#define STE_S2AFFD(x) extract32((x)->word[5], 21, 1)
> -#define STE_S2HD(x) extract32((x)->word[5], 23, 1)
> -#define STE_S2HA(x) extract32((x)->word[5], 24, 1)
> -#define STE_S2S(x) extract32((x)->word[5], 25, 1)
> -#define STE_S2R(x) extract32((x)->word[5], 26, 1)
> -
> -#define STE_CTXPTR(x) \
> - ((extract64((x)->word[1], 0, 16) << 32) | \
> - ((x)->word[0] & 0xffffffc0))
> -
> -#define STE_S2TTB(x) \
> - ((extract64((x)->word[7], 0, 16) << 32) | \
> - ((x)->word[6] & 0xfffffff0))
> -
> /* CD fields */
>
> -#define CD_VALID(x) extract32((x)->word[0], 31, 1)
> -#define CD_ASID(x) extract32((x)->word[1], 16, 16)
> -#define CD_TTB(x, sel) \
> - ((extract64((x)->word[(sel) * 2 + 3], 0, 19) << 32) | \
> - ((x)->word[(sel) * 2 + 2] & ~0xfULL))
> -
> -#define CD_HAD(x, sel) extract32((x)->word[(sel) * 2 + 2], 1, 1)
> -
> -#define CD_TSZ(x, sel) extract32((x)->word[0], (16 * (sel)) + 0, 6)
> -#define CD_TG(x, sel) extract32((x)->word[0], (16 * (sel)) + 6, 2)
> -#define CD_EPD(x, sel) extract32((x)->word[0], (16 * (sel)) + 14, 1)
> -#define CD_ENDI(x) extract32((x)->word[0], 15, 1)
> -#define CD_IPS(x) extract32((x)->word[1], 0 , 3)
> -#define CD_AFFD(x) extract32((x)->word[1], 3 , 1)
> -#define CD_TBI(x) extract32((x)->word[1], 6 , 2)
> -#define CD_HD(x) extract32((x)->word[1], 10 , 1)
> -#define CD_HA(x) extract32((x)->word[1], 11 , 1)
> -#define CD_S(x) extract32((x)->word[1], 12, 1)
> -#define CD_R(x) extract32((x)->word[1], 13, 1)
> -#define CD_A(x) extract32((x)->word[1], 14, 1)
> -#define CD_AARCH64(x) extract32((x)->word[1], 9 , 1)
> +REG32(CD_0, 0)
> + FIELD(CD_0, TSZ0, 0, 6)
> + FIELD(CD_0, TG0, 6, 2)
> + FIELD(CD_0, EPD0, 14, 1)
> + FIELD(CD_0, ENDI, 15, 1)
> + FIELD(CD_0, TSZ1, 16, 6)
> + FIELD(CD_0, TG1, 22, 2)
> + FIELD(CD_0, EPD1, 30, 1)
> + FIELD(CD_0, VALID, 31, 1)
> +REG32(CD_1, 4)
> + FIELD(CD_1, IPS, 0, 3)
> + FIELD(CD_1, AFFD, 3, 1)
> + FIELD(CD_1, TBI, 6, 2)
> + FIELD(CD_1, AARCH64, 9, 1)
> + FIELD(CD_1, HD, 10, 1)
> + FIELD(CD_1, HA, 11, 1)
> + FIELD(CD_1, S, 12, 1)
> + FIELD(CD_1, R, 13, 1)
> + FIELD(CD_1, A, 14, 1)
> + FIELD(CD_1, ASID, 16, 16)
> +REG32(CD_2, 8)
> + FIELD(CD_2, HAD0, 1, 1)
> + FIELD(CD_2, TTB0_LO, 4, 28)
> +REG32(CD_3, 12)
> + FIELD(CD_3, TTB0_HI, 0, 19)
I think it is 24 now
> +REG32(CD_4, 16)
> + FIELD(CD_4, HAD1, 1, 1)
> + FIELD(CD_4, TTB1_LO, 4, 28)
> +REG32(CD_5, 20)
> + FIELD(CD_5, TTB1_HI, 0, 19)
same
> +
> +/* Get CD fields */
> +#define CD_TSZ(x, sel) ((sel) ? \
> + FIELD_EX32((x)->word[0], CD_0, TSZ1) : \
> + FIELD_EX32((x)->word[0], CD_0, TSZ0))
> +#define CD_TG(x, sel) ((sel) ? \
> + FIELD_EX32((x)->word[0], CD_0, TG1) : \
> + FIELD_EX32((x)->word[0], CD_0, TG0))
> +#define CD_EPD(x, sel) ((sel) ? \
> + FIELD_EX32((x)->word[0], CD_0, EPD1) : \
> + FIELD_EX32((x)->word[0], CD_0, EPD0))
> +#define CD_ENDI(x) FIELD_EX32((x)->word[0], CD_0, ENDI)
> +#define CD_VALID(x) FIELD_EX32((x)->word[0], CD_0, VALID)
> +#define CD_IPS(x) FIELD_EX32((x)->word[1], CD_1, IPS)
> +#define CD_AFFD(x) FIELD_EX32((x)->word[1], CD_1, AFFD)
> +#define CD_TBI(x) FIELD_EX32((x)->word[1], CD_1, TBI)
> +#define CD_AARCH64(x) FIELD_EX32((x)->word[1], CD_1, AARCH64)
> +#define CD_HD(x) FIELD_EX32((x)->word[1], CD_1, HD)
> +#define CD_HA(x) FIELD_EX32((x)->word[1], CD_1, HA)
> +#define CD_S(x) FIELD_EX32((x)->word[1], CD_1, S)
> +#define CD_R(x) FIELD_EX32((x)->word[1], CD_1, R)
> +#define CD_A(x) FIELD_EX32((x)->word[1], CD_1, A)
> +#define CD_ASID(x) FIELD_EX32((x)->word[1], CD_1, ASID)
> +#define CD_HAD(x, sel) ((sel) ? \
> + FIELD_EX32((x)->word[4], CD_4, HAD1) : \
> + FIELD_EX32((x)->word[2], CD_2, HAD0))
> +#define CD_TTB(x, sel) \
> + ((sel) ? (((uint64_t)FIELD_EX32((x)->word[5], CD_5, TTB1_HI) << 32) | \
> + ((uint64_t)FIELD_EX32((x)->word[4], CD_4, TTB1_LO) << 4)) : \
> + (((uint64_t)FIELD_EX32((x)->word[3], CD_3, TTB0_HI) << 32) | \
> + ((uint64_t)FIELD_EX32((x)->word[2], CD_2, TTB0_LO) << 4)))
>
> /* MMIO Registers */
>
Otherwise looks good to me
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Eric
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 2/7] hw/arm/smmuv3-common: Define STE/CD fields via registerfields
2026-01-15 9:14 ` Eric Auger
@ 2026-01-16 9:51 ` Tao Tang
0 siblings, 0 replies; 35+ messages in thread
From: Tao Tang @ 2026-01-16 9:51 UTC (permalink / raw)
To: eric.auger, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Eric,
On 2026/1/15 17:14, Eric Auger wrote:
> Hi Tao,
>
> On 12/24/25 4:46 AM, Tao Tang wrote:
>> Switch STE/CD bitfield definitions and accessors to the
>> 'hw/registerfields.h' REG/FIELD API.
>>
>> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
>> ---
>> include/hw/arm/smmuv3-common.h | 169 +++++++++++++++++++++++----------
>> 1 file changed, 120 insertions(+), 49 deletions(-)
>>
>> diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
>> index 9da817f41a..b6da2fd62c 100644
>> --- a/include/hw/arm/smmuv3-common.h
>> +++ b/include/hw/arm/smmuv3-common.h
>> @@ -11,6 +11,8 @@
>> #ifndef HW_ARM_SMMUV3_COMMON_H
>> #define HW_ARM_SMMUV3_COMMON_H
>>
>> +#include "hw/registerfields.h"
>> +
>> /* Configuration Data */
>>
>> /* STE Level 1 Descriptor */
>> @@ -35,63 +37,132 @@ typedef struct CD {
>>
>> /* STE fields */
>>
>> -#define STE_VALID(x) extract32((x)->word[0], 0, 1)
>> +REG32(STE_0, 0)
>> + FIELD(STE_0, VALID, 0, 1)
>> + FIELD(STE_0, CONFIG, 1, 3)
>> + FIELD(STE_0, S1FMT, 4, 2)
>> + FIELD(STE_0, CTXPTR_LO, 6, 26)
>> +REG32(STE_1, 4)
>> + FIELD(STE_1, CTXPTR_HI, 0, 16)
> not related to your patch, but shouldn't it be 24 instead of 16
>>
>> +REG32(STE_6, 24)
>> + FIELD(STE_6, S2TTB_LO, 4, 28)
>> +REG32(STE_7, 28)
>> + FIELD(STE_7, S2TTB_HI, 0, 16)
> same here?
>>
>> +REG32(CD_2, 8)
>> + FIELD(CD_2, HAD0, 1, 1)
>> + FIELD(CD_2, TTB0_LO, 4, 28)
>> +REG32(CD_3, 12)
>> + FIELD(CD_3, TTB0_HI, 0, 19)
> I think it is 24 now
>> +REG32(CD_4, 16)
>> + FIELD(CD_4, HAD1, 1, 1)
>> + FIELD(CD_4, TTB1_LO, 4, 28)
>> +REG32(CD_5, 20)
>> + FIELD(CD_5, TTB1_HI, 0, 19)
> same
Thank you for your review and the careful observation — you are
absolutely correct.
These field width definitions originally came from the existing
implementation when I moved them into the new REG/FIELD API, so I did
not update the widths at that time.
Would you prefer that I submit a separate patch to correct the field
widths after this series patch are merged?
Best regards,
Tao
> Otherwise looks good to me
>
> Reviewed-by: Eric Auger <eric.auger@redhat.com>
>
> Eric
^ permalink raw reply [flat|nested] 35+ messages in thread
* [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
2025-12-24 3:46 ` [RFC v8 1/7] hw/arm/smmuv3: Extract common definitions to smmuv3-common.h Tao Tang
2025-12-24 3:46 ` [RFC v8 2/7] hw/arm/smmuv3-common: Define STE/CD fields via registerfields Tao Tang
@ 2025-12-24 3:46 ` Tao Tang
2025-12-26 22:56 ` Philippe Mathieu-Daudé
2026-01-15 12:59 ` Eric Auger
2025-12-24 3:46 ` [RFC v8 4/7] hw/arm/smmuv3-common: Add NSCFG bit definition for CD Tao Tang
` (5 subsequent siblings)
8 siblings, 2 replies; 35+ messages in thread
From: Tao Tang @ 2025-12-24 3:46 UTC (permalink / raw)
To: Paolo Bonzini, Fabiano Rosas, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF, Tao Tang
Add a minimal PCI test device designed to exercise IOMMU translation
(such as ARM SMMUv3) without requiring guest firmware or OS. The device
provides MMIO registers to configure and trigger DMA operations with
controllable attributes (security state, address space), enabling
deterministic IOMMU testing.
Key features:
- Bare-metal IOMMU testing via simple MMIO interface
- Configurable DMA attributes for security states and address spaces
- Write-then-read verification pattern with automatic result checking
The device performs a deterministic DMA test pattern: write a known
value (0x12345678) to a configured GVA, read it back, and verify data
integrity. Results are reported through a dedicated result register,
eliminating the need for complex interrupt handling or driver
infrastructure in tests.
This is purely a test device and not intended for production use or
machine realism. It complements existing test infrastructure like
pci-testdev but focuses specifically on IOMMU translation path
validation.
Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
---
MAINTAINERS | 7 +
docs/specs/index.rst | 1 +
docs/specs/iommu-testdev.rst | 112 +++++++++++++
hw/misc/Kconfig | 5 +
hw/misc/iommu-testdev.c | 271 ++++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
hw/misc/trace-events | 10 ++
include/hw/misc/iommu-testdev.h | 68 ++++++++
8 files changed, 475 insertions(+)
create mode 100644 docs/specs/iommu-testdev.rst
create mode 100644 hw/misc/iommu-testdev.c
create mode 100644 include/hw/misc/iommu-testdev.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 63e9ba521b..3c88ed5dc4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2349,6 +2349,13 @@ F: include/qemu/chardev_open.h
F: util/chardev_open.c
F: docs/devel/vfio-iommufd.rst
+iommu-testdev
+M: Tao Tang <tangtao1634@phytium.com.cn>
+S: Maintained
+F: hw/misc/iommu-testdev.c
+F: include/hw/misc/iommu-testdev.h
+F: docs/specs/iommu-testdev.rst
+
vhost
M: Michael S. Tsirkin <mst@redhat.com>
R: Stefano Garzarella <sgarzare@redhat.com>
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index f19d73c9f6..1fc7fae6bb 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -39,3 +39,4 @@ guest hardware that is specific to QEMU.
riscv-iommu
riscv-aia
aspeed-intc
+ iommu-testdev
\ No newline at end of file
diff --git a/docs/specs/iommu-testdev.rst b/docs/specs/iommu-testdev.rst
new file mode 100644
index 0000000000..2bcd9bec93
--- /dev/null
+++ b/docs/specs/iommu-testdev.rst
@@ -0,0 +1,112 @@
+iommu-testdev — IOMMU test device for bare-metal testing
+=========================================================
+
+Overview
+--------
+``iommu-testdev`` is a minimal, test-only PCI device designed to exercise
+IOMMU translation (such as ARM SMMUv3) without requiring firmware or a guest
+OS. Tests can populate IOMMU translation tables with known values and trigger
+DMA operations that flow through the IOMMU translation path. It is **not** a
+faithful PCIe endpoint and must be considered a QEMU-internal test vehicle.
+
+Key Features
+------------
+* **Bare-metal IOMMU testing**: No guest kernel or firmware required
+* **Configurable DMA attributes**: Supports address space configuration via
+ MMIO registers
+* **Deterministic verification**: Write-then-read DMA pattern with automatic
+ result checking
+
+Status
+------
+* Location: ``hw/misc/iommu-testdev.c``
+* Header: ``include/hw/misc/iommu-testdev.h``
+* Build guard: ``CONFIG_IOMMU_TESTDEV``
+
+Device Interface
+----------------
+The device exposes a single PCI BAR0 with 32bit MMIO registers:
+
+* ``ITD_REG_DMA_TRIGGERING`` (0x00): Reading triggers DMA execution
+* ``ITD_REG_DMA_GVA_LO`` (0x04): GVA bits [31:0]
+* ``ITD_REG_DMA_GVA_HI`` (0x08): GVA bits [63:32]
+* ``ITD_REG_DMA_LEN`` (0x0C): DMA transfer length
+* ``ITD_REG_DMA_RESULT`` (0x10): DMA operation result (0=success)
+* ``ITD_REG_DMA_DBELL`` (0x14): Write 1 to arm DMA
+* ``ITD_REG_DMA_ATTRS`` (0x18): DMA attributes which shadow MemTxAttrs format:
+
+ - bit[0]: secure (1=Secure, 0=Non-Secure)
+ - bits[2:1]: address space (0=Non-Secure, 1=Secure)
+ Only these MemTxAttrs fields (``secure`` and ``space``) are consumed today;
+ other bits are reserved but can be wired up easily if future tests need
+ to pass extra attributes.
+
+Translation Setup Workflow
+--------------------------
+``iommu-testdev`` never builds SMMU/AMD-Vi/RISC-V IOMMU structures on its own.
+Architecture-specific construction lives entirely in qtest/libqos helpers.
+Those helpers populate guest memory with page tables/architecture-specific
+structures and program the emulated IOMMU registers directly. See the
+``qsmmu_setup_and_enable_translation()`` function in
+``tests/qtest/libqos/qos-smmuv3.c`` for an example of how SMMUv3 translation
+is set up for this device, which will be introduced in the next commit.
+
+DMA Operation Flow
+------------------
+The flow would be split into these steps, mainly for timing control and
+debuggability: qtests can easily exercise and assert distinct paths
+(NOT_ARMED, BAD_LEN, TX/RD failures, mismatch) instead of having all side
+effects hidden behind a single step:
+1. Test programs IOMMU translation tables
+2. Test configures DMA address (GVA_LO/HI), length, and attributes
+3. Test writes 1 to DMA_DBELL to arm the operation
+4. Test reads DMA_TRIGGERING to execute DMA
+5. Test polls DMA_RESULT:
+
+ - 0x00000000: Success
+ - 0xFFFFFFFE: Busy (still in progress)
+ - 0xDEAD000X: Various error codes
+
+The device performs a write-then-read sequence using a known pattern
+(0x12345678) and verifies data integrity automatically.
+
+Running the qtest
+-----------------
+The SMMUv3 test suite uses this device and covers multiple translation modes::
+
+ cd build-debug
+ QTEST_QEMU_BINARY=./qemu-system-aarch64 \\
+ ./tests/qtest/iommu-smmuv3-test --tap -k
+
+This test suite exercises:
+
+* Stage 1 only translation
+* Stage 2 only translation
+* Nested (Stage 1 + Stage 2) translation
+
+Instantiation
+-------------
+The device is not wired into any board by default. Tests instantiate it
+via QEMU command line::
+
+ -device iommu-testdev
+
+For ARM platforms with SMMUv3::
+
+ -M virt,iommu=smmuv3 -device iommu-testdev
+
+The device will be placed behind the IOMMU automatically.
+
+Limitations
+-----------
+* No realistic PCIe enumeration, MSI/MSI-X, or interrupt handling
+* No ATS/PRI support
+* No actual device functionality beyond DMA test pattern
+* Test-only; not suitable for production or machine realism
+* Address space support (Secure/Root/Realm) is architecture-dependent
+
+See also
+--------
+* ``tests/qtest/iommu-smmuv3-test.c`` — SMMUv3 test suite
+* ``tests/qtest/libqos/qos-smmuv3.{c,h}`` — SMMUv3 test library
+* SMMUv3 emulation: ``hw/arm/smmu*``
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index fccd735c24..b5f6fdbd9c 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 IOMMU_TESTDEV
+ bool
+ default y if TEST_DEVICES
+ depends on PCI
+
config EDU
bool
default y if TEST_DEVICES
diff --git a/hw/misc/iommu-testdev.c b/hw/misc/iommu-testdev.c
new file mode 100644
index 0000000000..2cc1176aa6
--- /dev/null
+++ b/hw/misc/iommu-testdev.c
@@ -0,0 +1,271 @@
+/*
+ * A test device for IOMMU
+ *
+ * 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 "system/address-spaces.h"
+#include "trace.h"
+#include "hw/pci/pci_device.h"
+#include "hw/qdev-properties.h"
+#include "qom/object.h"
+#include "hw/misc/iommu-testdev.h"
+
+#define TYPE_IOMMU_TESTDEV "iommu-testdev"
+OBJECT_DECLARE_SIMPLE_TYPE(IOMMUTestDevState, IOMMU_TESTDEV)
+
+struct IOMMUTestDevState {
+ PCIDevice parent_obj;
+ MemoryRegion bar0;
+ uint64_t dma_vaddr;
+ uint32_t dma_len;
+ uint32_t dma_result;
+ bool dma_pending;
+
+ AddressSpace *dma_as; /* IOMMU-mediated DMA AS for this device */
+ uint32_t dma_attrs_cfg; /* bit0 secure, bits[2:1] space, bit3 unspecified */
+};
+
+static void iommu_testdev_maybe_run_dma(IOMMUTestDevState *s)
+{
+ uint32_t expected_val, actual_val;
+ g_autofree uint8_t *write_buf = NULL;
+ g_autofree uint8_t *read_buf = NULL;
+ MemTxResult write_res, read_res;
+ MemTxAttrs attrs;
+ AddressSpace *as;
+
+ if (!s->dma_pending) {
+ s->dma_result = ITD_DMA_ERR_NOT_ARMED;
+ trace_iommu_testdev_dma_result(s->dma_result);
+ return;
+ }
+ trace_iommu_testdev_dma_start();
+
+ s->dma_pending = false;
+
+ if (!s->dma_len) {
+ s->dma_result = ITD_DMA_ERR_BAD_LEN;
+ return;
+ }
+
+ write_buf = g_malloc(s->dma_len);
+ read_buf = g_malloc(s->dma_len);
+
+ /* Initialize MemTxAttrs from generic register */
+ attrs.secure = ITD_ATTRS_GET_SECURE(s->dma_attrs_cfg);
+
+ /* The 'space' field in MemTxAttrs is ARM-specific. */
+ attrs.space = ITD_ATTRS_GET_SPACE(s->dma_attrs_cfg);
+
+ as = s->dma_as;
+
+ /* Step 1: Write ITD_DMA_WRITE_VAL to DMA address */
+ trace_iommu_testdev_dma_write(s->dma_vaddr, s->dma_len);
+
+ for (int i = 0; i < s->dma_len; i++) {
+ /* Data is written in little-endian order */
+ write_buf[i] = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
+ }
+ write_res = dma_memory_write(as, s->dma_vaddr, write_buf, s->dma_len, attrs);
+
+ if (write_res != MEMTX_OK) {
+ s->dma_result = ITD_DMA_ERR_TX_FAIL;
+ trace_iommu_testdev_dma_result(s->dma_result);
+ return;
+ }
+
+ /* Step 2: Read back from the same DMA address */
+ trace_iommu_testdev_dma_read(s->dma_vaddr, s->dma_len);
+
+ read_res = dma_memory_read(as, s->dma_vaddr, read_buf, s->dma_len, attrs);
+
+ if (read_res != MEMTX_OK) {
+ s->dma_result = ITD_DMA_ERR_RD_FAIL;
+ trace_iommu_testdev_dma_result(s->dma_result);
+ return;
+ }
+
+ /* Step 3: Verify the read data matches what we wrote */
+ for (int i = 0; i < s->dma_len; i += 4) {
+ int remaining_bytes = MIN(4, s->dma_len - i);
+
+ expected_val = 0;
+ actual_val = 0;
+
+ for (int j = 0; j < remaining_bytes; j++) {
+ expected_val |= ((uint32_t)write_buf[i + j]) << (j * 8);
+ actual_val |= ((uint32_t)read_buf[i + j]) << (j * 8);
+ }
+
+ trace_iommu_testdev_dma_verify(expected_val, actual_val);
+
+ if (expected_val != actual_val) {
+ s->dma_result = ITD_DMA_ERR_MISMATCH;
+ trace_iommu_testdev_dma_result(s->dma_result);
+ return;
+ }
+ }
+
+ /* All checks passed */
+ s->dma_result = 0;
+ trace_iommu_testdev_dma_result(s->dma_result);
+}
+
+static uint64_t iommu_testdev_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ IOMMUTestDevState *s = opaque;
+ uint64_t value = 0;
+
+ switch (addr) {
+ case ITD_REG_DMA_TRIGGERING:
+ /*
+ * This lets tests poll ITD_REG_DMA_RESULT to observe BUSY before
+ * consuming the DMA.
+ */
+ iommu_testdev_maybe_run_dma(s);
+ value = 0;
+ break;
+ case ITD_REG_DMA_GVA_LO:
+ value = (uint32_t)(s->dma_vaddr & 0xffffffffu);
+ break;
+ case ITD_REG_DMA_GVA_HI:
+ value = (uint32_t)(s->dma_vaddr >> 32);
+ break;
+ case ITD_REG_DMA_LEN:
+ value = s->dma_len;
+ break;
+ case ITD_REG_DMA_RESULT:
+ value = s->dma_result;
+ break;
+ case ITD_REG_DMA_ATTRS:
+ value = s->dma_attrs_cfg;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+
+ trace_iommu_testdev_mmio_read(addr, value, size);
+ return value;
+}
+
+static void iommu_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ IOMMUTestDevState *s = opaque;
+ uint32_t data = val;
+
+ trace_iommu_testdev_mmio_write(addr, val, size);
+
+ switch (addr) {
+ case ITD_REG_DMA_GVA_LO:
+ s->dma_vaddr = (s->dma_vaddr & ~0xffffffffull) | data;
+ break;
+ case ITD_REG_DMA_GVA_HI:
+ s->dma_vaddr = (s->dma_vaddr & 0xffffffffull) |
+ ((uint64_t)data << 32);
+ break;
+ case ITD_REG_DMA_LEN:
+ s->dma_len = data;
+ break;
+ case ITD_REG_DMA_RESULT:
+ s->dma_result = data;
+ break;
+ case ITD_REG_DMA_DBELL:
+ if (data & ITD_DMA_DBELL_ARM) {
+ /* Arm the DMA operation */
+ s->dma_pending = true;
+ s->dma_result = ITD_DMA_RESULT_BUSY;
+ trace_iommu_testdev_dma_pending(true);
+ } else {
+ /* Disarm the DMA operation */
+ s->dma_pending = false;
+ s->dma_result = ITD_DMA_RESULT_IDLE;
+ trace_iommu_testdev_dma_pending(false);
+ }
+ break;
+ case ITD_REG_DMA_ATTRS:
+ s->dma_attrs_cfg = data;
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps iommu_testdev_mmio_ops = {
+ .read = iommu_testdev_mmio_read,
+ .write = iommu_testdev_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void iommu_testdev_realize(PCIDevice *pdev, Error **errp)
+{
+ IOMMUTestDevState *s = IOMMU_TESTDEV(pdev);
+
+ s->dma_vaddr = 0;
+ s->dma_len = 0;
+ s->dma_result = ITD_DMA_RESULT_IDLE;
+ s->dma_pending = false;
+ s->dma_attrs_cfg = 0;
+ s->dma_as = pci_device_iommu_address_space(pdev);
+
+ memory_region_init_io(&s->bar0, OBJECT(pdev), &iommu_testdev_mmio_ops, s,
+ TYPE_IOMMU_TESTDEV ".bar0", BAR0_SIZE);
+ pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
+}
+
+static void iommu_testdev_reset(DeviceState *dev)
+{
+ IOMMUTestDevState *s = IOMMU_TESTDEV(dev);
+
+ s->dma_vaddr = 0;
+ s->dma_len = 0;
+ s->dma_result = ITD_DMA_RESULT_IDLE;
+ s->dma_pending = false;
+ s->dma_attrs_cfg = 0;
+}
+
+static void iommu_testdev_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+
+ pc->realize = iommu_testdev_realize;
+ pc->vendor_id = IOMMU_TESTDEV_VENDOR_ID;
+ pc->device_id = IOMMU_TESTDEV_DEVICE_ID;
+ pc->revision = 0;
+ pc->class_id = PCI_CLASS_OTHERS;
+ dc->desc = "A test device for IOMMU";
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ device_class_set_legacy_reset(dc, iommu_testdev_reset);
+}
+
+static const TypeInfo iommu_testdev_info = {
+ .name = TYPE_IOMMU_TESTDEV,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(IOMMUTestDevState),
+ .class_init = iommu_testdev_class_init,
+ .interfaces = (const InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { }
+ },
+};
+
+static void iommu_testdev_register_types(void)
+{
+ type_register_static(&iommu_testdev_info);
+}
+
+type_init(iommu_testdev_register_types);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index b1d8d8e5d2..6f9bb9bb0f 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_IOMMU_TESTDEV', if_true: files('iommu-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/trace-events b/hw/misc/trace-events
index eeb9243898..84fd349fb8 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -409,3 +409,13 @@ ivshmem_flat_interrupt_peer(uint16_t peer_id, uint16_t vector_id) "Interrupting
i2c_echo_event(const char *id, const char *event) "%s: %s"
i2c_echo_recv(const char *id, uint8_t data) "%s: recv 0x%02" PRIx8
i2c_echo_send(const char *id, uint8_t data) "%s: send 0x%02" PRIx8
+
+# iommu-testdev.c
+iommu_testdev_mmio_read(uint64_t addr, uint64_t value, unsigned size) "addr=0x%" PRIx64 " value=0x%" PRIx64 " size=%u"
+iommu_testdev_mmio_write(uint64_t addr, uint64_t value, unsigned size) "addr=0x%" PRIx64 " value=0x%" PRIx64 " size=%u"
+iommu_testdev_dma_start(void) "DMA operation started"
+iommu_testdev_dma_write(uint64_t gva, uint32_t len) "gva=0x%" PRIx64 " len=%u"
+iommu_testdev_dma_read(uint64_t gva, uint32_t len) "gva=0x%" PRIx64 " len=%u"
+iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actual=0x%x"
+iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
+iommu_testdev_dma_pending(bool pending) "pending=%d"
diff --git a/include/hw/misc/iommu-testdev.h b/include/hw/misc/iommu-testdev.h
new file mode 100644
index 0000000000..14a5bca721
--- /dev/null
+++ b/include/hw/misc/iommu-testdev.h
@@ -0,0 +1,68 @@
+/*
+ * A test device for IOMMU
+ *
+ * Copyright (c) 2025 Phytium Technology
+ *
+ * Author:
+ * Tao Tang <tangtao1634@phytium.com.cn>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_IOMMU_TESTDEV_H
+#define HW_MISC_IOMMU_TESTDEV_H
+
+#include "hw/pci/pci.h"
+
+#define IOMMU_TESTDEV_VENDOR_ID PCI_VENDOR_ID_REDHAT
+#define IOMMU_TESTDEV_DEVICE_ID PCI_DEVICE_ID_REDHAT_TEST
+
+/* DMA_ATTRS register bit definitions (architecture-agnostic) */
+#define ITD_ATTRS_SECURE_SHIFT 0
+#define ITD_ATTRS_SECURE_MASK 0x1
+#define ITD_ATTRS_SPACE_SHIFT 1
+#define ITD_ATTRS_SPACE_MASK 0x3
+
+/* Helper macros for setting fields */
+#define ITD_ATTRS_SET_SECURE(attrs, val) \
+ (((attrs) & ~(ITD_ATTRS_SECURE_MASK << ITD_ATTRS_SECURE_SHIFT)) | \
+ (((val) & ITD_ATTRS_SECURE_MASK) << ITD_ATTRS_SECURE_SHIFT))
+
+#define ITD_ATTRS_SET_SPACE(attrs, val) \
+ (((attrs) & ~(ITD_ATTRS_SPACE_MASK << ITD_ATTRS_SPACE_SHIFT)) | \
+ (((val) & ITD_ATTRS_SPACE_MASK) << ITD_ATTRS_SPACE_SHIFT))
+
+/* Helper macros for getting fields */
+#define ITD_ATTRS_GET_SECURE(attrs) \
+ (((attrs) >> ITD_ATTRS_SECURE_SHIFT) & ITD_ATTRS_SECURE_MASK)
+
+#define ITD_ATTRS_GET_SPACE(attrs) \
+ (((attrs) >> ITD_ATTRS_SPACE_SHIFT) & ITD_ATTRS_SPACE_MASK)
+
+/* DMA result/status values shared with tests */
+#define ITD_DMA_RESULT_IDLE 0xffffffffu
+#define ITD_DMA_RESULT_BUSY 0xfffffffeu
+#define ITD_DMA_ERR_BAD_LEN 0xdead0001u
+#define ITD_DMA_ERR_TX_FAIL 0xdead0002u
+#define ITD_DMA_ERR_RD_FAIL 0xdead0003u
+#define ITD_DMA_ERR_MISMATCH 0xdead0004u
+#define ITD_DMA_ERR_NOT_ARMED 0xdead0005u
+
+#define ITD_DMA_WRITE_VAL 0x12345678u
+
+/* DMA doorbell bits */
+#define ITD_DMA_DBELL_ARM 0x1u
+
+/* BAR0 layout of iommu-testdev */
+enum {
+ ITD_REG_DMA_TRIGGERING = 0x00,
+ ITD_REG_DMA_GVA_LO = 0x04,
+ ITD_REG_DMA_GVA_HI = 0x08,
+ ITD_REG_DMA_LEN = 0x0c,
+ ITD_REG_DMA_RESULT = 0x10,
+ ITD_REG_DMA_DBELL = 0x14,
+ ITD_REG_DMA_ATTRS = 0x18, /* [0] secure,[2:1] space,[3] unspecified */
+ BAR0_SIZE = 0x1000,
+};
+
+#endif /* HW_MISC_IOMMU_TESTDEV_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing
2025-12-24 3:46 ` [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing Tao Tang
@ 2025-12-26 22:56 ` Philippe Mathieu-Daudé
2025-12-27 5:16 ` Tao Tang
2026-01-15 12:59 ` Eric Auger
1 sibling, 1 reply; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2025-12-26 22:56 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Eric Auger, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
Hi Tao,
On 24/12/25 04:46, Tao Tang wrote:
> Add a minimal PCI test device designed to exercise IOMMU translation
> (such as ARM SMMUv3) without requiring guest firmware or OS. The device
> provides MMIO registers to configure and trigger DMA operations with
> controllable attributes (security state, address space), enabling
> deterministic IOMMU testing.
>
> Key features:
> - Bare-metal IOMMU testing via simple MMIO interface
> - Configurable DMA attributes for security states and address spaces
> - Write-then-read verification pattern with automatic result checking
>
> The device performs a deterministic DMA test pattern: write a known
> value (0x12345678) to a configured GVA, read it back, and verify data
> integrity. Results are reported through a dedicated result register,
> eliminating the need for complex interrupt handling or driver
> infrastructure in tests.
>
> This is purely a test device and not intended for production use or
> machine realism. It complements existing test infrastructure like
> pci-testdev but focuses specifically on IOMMU translation path
> validation.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> Reviewed-by: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com>
> Reviewed-by: Fabiano Rosas <farosas@suse.de>
> ---
> MAINTAINERS | 7 +
> docs/specs/index.rst | 1 +
> docs/specs/iommu-testdev.rst | 112 +++++++++++++
> hw/misc/Kconfig | 5 +
> hw/misc/iommu-testdev.c | 271 ++++++++++++++++++++++++++++++++
> hw/misc/meson.build | 1 +
> hw/misc/trace-events | 10 ++
> include/hw/misc/iommu-testdev.h | 68 ++++++++
> 8 files changed, 475 insertions(+)
> create mode 100644 docs/specs/iommu-testdev.rst
> create mode 100644 hw/misc/iommu-testdev.c
> create mode 100644 include/hw/misc/iommu-testdev.h
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index fccd735c24..b5f6fdbd9c 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 IOMMU_TESTDEV
> + bool
> + default y if TEST_DEVICES
> + depends on PCI
"depends on PCI && ARM"
?
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing
2025-12-26 22:56 ` Philippe Mathieu-Daudé
@ 2025-12-27 5:16 ` Tao Tang
2025-12-27 15:54 ` Philippe Mathieu-Daudé
0 siblings, 1 reply; 35+ messages in thread
From: Tao Tang @ 2025-12-27 5:16 UTC (permalink / raw)
To: Philippe Mathieu-Daudé, Paolo Bonzini, Fabiano Rosas,
Laurent Vivier, Eric Auger, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
Hi Philippe,
On 2025/12/27 06:56, Philippe Mathieu-Daudé wrote:
> Hi Tao,
>
> On 24/12/25 04:46, Tao Tang wrote:
>> Add a minimal PCI test device designed to exercise IOMMU translation
>> (such as ARM SMMUv3) without requiring guest firmware or OS. The device
>> provides MMIO registers to configure and trigger DMA operations with
>> controllable attributes (security state, address space), enabling
>> deterministic IOMMU testing.
>>
>> Key features:
>> - Bare-metal IOMMU testing via simple MMIO interface
>> - Configurable DMA attributes for security states and address spaces
>> - Write-then-read verification pattern with automatic result checking
>>
>> The device performs a deterministic DMA test pattern: write a known
>> value (0x12345678) to a configured GVA, read it back, and verify data
>> integrity. Results are reported through a dedicated result register,
>> eliminating the need for complex interrupt handling or driver
>> infrastructure in tests.
>>
>> This is purely a test device and not intended for production use or
>> machine realism. It complements existing test infrastructure like
>> pci-testdev but focuses specifically on IOMMU translation path
>> validation.
>>
>> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
>> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>> Reviewed-by: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com>
>> Reviewed-by: Fabiano Rosas <farosas@suse.de>
>> ---
>> MAINTAINERS | 7 +
>> docs/specs/index.rst | 1 +
>> docs/specs/iommu-testdev.rst | 112 +++++++++++++
>> hw/misc/Kconfig | 5 +
>> hw/misc/iommu-testdev.c | 271 ++++++++++++++++++++++++++++++++
>> hw/misc/meson.build | 1 +
>> hw/misc/trace-events | 10 ++
>> include/hw/misc/iommu-testdev.h | 68 ++++++++
>> 8 files changed, 475 insertions(+)
>> create mode 100644 docs/specs/iommu-testdev.rst
>> create mode 100644 hw/misc/iommu-testdev.c
>> create mode 100644 include/hw/misc/iommu-testdev.h
>
>
>> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
>> index fccd735c24..b5f6fdbd9c 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 IOMMU_TESTDEV
>> + bool
>> + default y if TEST_DEVICES
>> + depends on PCI
>
> "depends on PCI && ARM"
>
> ?
Thanks for your review.
iommu-testdev is intended to be a generic PCI test DMA engine, usable to
exercise different IOMMU models, not ARM-specific. The current series
uses SMMUv3 as the first backend/example, but the device itself should
only depend on PCI and is gated by TEST_DEVICES.
Regards,
Tao
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing
2025-12-27 5:16 ` Tao Tang
@ 2025-12-27 15:54 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2025-12-27 15:54 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Eric Auger, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
On 27/12/25 06:16, Tao Tang wrote:
> Hi Philippe,
>
> On 2025/12/27 06:56, Philippe Mathieu-Daudé wrote:
>> Hi Tao,
>>
>> On 24/12/25 04:46, Tao Tang wrote:
>>> Add a minimal PCI test device designed to exercise IOMMU translation
>>> (such as ARM SMMUv3) without requiring guest firmware or OS. The device
>>> provides MMIO registers to configure and trigger DMA operations with
>>> controllable attributes (security state, address space), enabling
>>> deterministic IOMMU testing.
>>>
>>> Key features:
>>> - Bare-metal IOMMU testing via simple MMIO interface
>>> - Configurable DMA attributes for security states and address spaces
>>> - Write-then-read verification pattern with automatic result checking
>>>
>>> The device performs a deterministic DMA test pattern: write a known
>>> value (0x12345678) to a configured GVA, read it back, and verify data
>>> integrity. Results are reported through a dedicated result register,
>>> eliminating the need for complex interrupt handling or driver
>>> infrastructure in tests.
>>>
>>> This is purely a test device and not intended for production use or
>>> machine realism. It complements existing test infrastructure like
>>> pci-testdev but focuses specifically on IOMMU translation path
>>> validation.
>>>
>>> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
>>> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>>> Reviewed-by: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com>
>>> Reviewed-by: Fabiano Rosas <farosas@suse.de>
>>> ---
>>> MAINTAINERS | 7 +
>>> docs/specs/index.rst | 1 +
>>> docs/specs/iommu-testdev.rst | 112 +++++++++++++
>>> hw/misc/Kconfig | 5 +
>>> hw/misc/iommu-testdev.c | 271 ++++++++++++++++++++++++++++++++
>>> hw/misc/meson.build | 1 +
>>> hw/misc/trace-events | 10 ++
>>> include/hw/misc/iommu-testdev.h | 68 ++++++++
>>> 8 files changed, 475 insertions(+)
>>> create mode 100644 docs/specs/iommu-testdev.rst
>>> create mode 100644 hw/misc/iommu-testdev.c
>>> create mode 100644 include/hw/misc/iommu-testdev.h
>>
>>
>>> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
>>> index fccd735c24..b5f6fdbd9c 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 IOMMU_TESTDEV
>>> + bool
>>> + default y if TEST_DEVICES
>>> + depends on PCI
>>
>> "depends on PCI && ARM"
>>
>> ?
>
>
> Thanks for your review.
>
>
> iommu-testdev is intended to be a generic PCI test DMA engine, usable to
> exercise different IOMMU models, not ARM-specific. The current series
> uses SMMUv3 as the first backend/example, but the device itself should
> only depend on PCI and is gated by TEST_DEVICES.
Ah great, I just noticed the "Translation Setup Workflow" chapter of
this patch :)
Regards,
Phil.
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing
2025-12-24 3:46 ` [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing Tao Tang
2025-12-26 22:56 ` Philippe Mathieu-Daudé
@ 2026-01-15 12:59 ` Eric Auger
2026-01-16 14:37 ` Tao Tang
1 sibling, 1 reply; 35+ messages in thread
From: Eric Auger @ 2026-01-15 12:59 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Tao,
On 12/24/25 4:46 AM, Tao Tang wrote:
> Add a minimal PCI test device designed to exercise IOMMU translation
> (such as ARM SMMUv3) without requiring guest firmware or OS. The device
> provides MMIO registers to configure and trigger DMA operations with
> controllable attributes (security state, address space), enabling
> deterministic IOMMU testing.
>
> Key features:
> - Bare-metal IOMMU testing via simple MMIO interface
> - Configurable DMA attributes for security states and address spaces
> - Write-then-read verification pattern with automatic result checking
>
> The device performs a deterministic DMA test pattern: write a known
> value (0x12345678) to a configured GVA, read it back, and verify data
> integrity. Results are reported through a dedicated result register,
> eliminating the need for complex interrupt handling or driver
> infrastructure in tests.
>
> This is purely a test device and not intended for production use or
> machine realism. It complements existing test infrastructure like
> pci-testdev but focuses specifically on IOMMU translation path
> validation.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> Reviewed-by: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com>
> Reviewed-by: Fabiano Rosas <farosas@suse.de>
> ---
> MAINTAINERS | 7 +
> docs/specs/index.rst | 1 +
> docs/specs/iommu-testdev.rst | 112 +++++++++++++
> hw/misc/Kconfig | 5 +
> hw/misc/iommu-testdev.c | 271 ++++++++++++++++++++++++++++++++
> hw/misc/meson.build | 1 +
> hw/misc/trace-events | 10 ++
> include/hw/misc/iommu-testdev.h | 68 ++++++++
> 8 files changed, 475 insertions(+)
> create mode 100644 docs/specs/iommu-testdev.rst
> create mode 100644 hw/misc/iommu-testdev.c
> create mode 100644 include/hw/misc/iommu-testdev.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 63e9ba521b..3c88ed5dc4 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2349,6 +2349,13 @@ F: include/qemu/chardev_open.h
> F: util/chardev_open.c
> F: docs/devel/vfio-iommufd.rst
>
> +iommu-testdev
> +M: Tao Tang <tangtao1634@phytium.com.cn>
> +S: Maintained
> +F: hw/misc/iommu-testdev.c
> +F: include/hw/misc/iommu-testdev.h
> +F: docs/specs/iommu-testdev.rst
> +
> vhost
> M: Michael S. Tsirkin <mst@redhat.com>
> R: Stefano Garzarella <sgarzare@redhat.com>
> diff --git a/docs/specs/index.rst b/docs/specs/index.rst
> index f19d73c9f6..1fc7fae6bb 100644
> --- a/docs/specs/index.rst
> +++ b/docs/specs/index.rst
> @@ -39,3 +39,4 @@ guest hardware that is specific to QEMU.
> riscv-iommu
> riscv-aia
> aspeed-intc
> + iommu-testdev
> \ No newline at end of file
> diff --git a/docs/specs/iommu-testdev.rst b/docs/specs/iommu-testdev.rst
> new file mode 100644
> index 0000000000..2bcd9bec93
> --- /dev/null
> +++ b/docs/specs/iommu-testdev.rst
> @@ -0,0 +1,112 @@
> +iommu-testdev — IOMMU test device for bare-metal testing
> +=========================================================
> +
> +Overview
> +--------
> +``iommu-testdev`` is a minimal, test-only PCI device designed to exercise
> +IOMMU translation (such as ARM SMMUv3) without requiring firmware or a guest
> +OS. Tests can populate IOMMU translation tables with known values and trigger
> +DMA operations that flow through the IOMMU translation path. It is **not** a
> +faithful PCIe endpoint and must be considered a QEMU-internal test vehicle.
> +
> +Key Features
> +------------
> +* **Bare-metal IOMMU testing**: No guest kernel or firmware required
> +* **Configurable DMA attributes**: Supports address space configuration via
> + MMIO registers
> +* **Deterministic verification**: Write-then-read DMA pattern with automatic
> + result checking
> +
> +Status
> +------
> +* Location: ``hw/misc/iommu-testdev.c``
> +* Header: ``include/hw/misc/iommu-testdev.h``
> +* Build guard: ``CONFIG_IOMMU_TESTDEV``
> +
> +Device Interface
> +----------------
> +The device exposes a single PCI BAR0 with 32bit MMIO registers:
> +
> +* ``ITD_REG_DMA_TRIGGERING`` (0x00): Reading triggers DMA execution
> +* ``ITD_REG_DMA_GVA_LO`` (0x04): GVA bits [31:0]
> +* ``ITD_REG_DMA_GVA_HI`` (0x08): GVA bits [63:32]
> +* ``ITD_REG_DMA_LEN`` (0x0C): DMA transfer length
> +* ``ITD_REG_DMA_RESULT`` (0x10): DMA operation result (0=success)
> +* ``ITD_REG_DMA_DBELL`` (0x14): Write 1 to arm DMA
I know you provided some explanations to be me earlier about separation
between "arming" and "triggering" but I don't find info in the doc about
what arming practically does and what kind of checks it can enable
compared to the actual trigger. I am not asking for removing it but
maybe document what it aims at.
> +* ``ITD_REG_DMA_ATTRS`` (0x18): DMA attributes which shadow MemTxAttrs format:
> +
> + - bit[0]: secure (1=Secure, 0=Non-Secure)
> + - bits[2:1]: address space (0=Non-Secure, 1=Secure)
I was confused by the diff between those 2 fields. If my understanding
is correct they should equal today, until we got some further RME stuff?
Correct?
In MemTxAttrs it is said:
unsigned int secure:1;
/*
* ARM: ArmSecuritySpace. This partially overlaps secure, but it is
* easier to have both fields to assist code that does not understand
* ARMv9 RME, or no specific knowledge of ARM at all (e.g. pflash).
*/
unsigned int space:2;
> + Only these MemTxAttrs fields (``secure`` and ``space``) are consumed today;
> + other bits are reserved but can be wired up easily if future tests need
> + to pass extra attributes.
> +
> +Translation Setup Workflow
> +--------------------------
> +``iommu-testdev`` never builds SMMU/AMD-Vi/RISC-V IOMMU structures on its own.
> +Architecture-specific construction lives entirely in qtest/libqos helpers.
> +Those helpers populate guest memory with page tables/architecture-specific
> +structures and program the emulated IOMMU registers directly. See the
> +``qsmmu_setup_and_enable_translation()`` function in
> +``tests/qtest/libqos/qos-smmuv3.c`` for an example of how SMMUv3 translation
> +is set up for this device, which will be introduced in the next commit.
nit: this is rather a commit description comment.
> +
> +DMA Operation Flow
> +------------------
> +The flow would be split into these steps, mainly for timing control and
> +debuggability: qtests can easily exercise and assert distinct paths
> +(NOT_ARMED, BAD_LEN, TX/RD failures, mismatch) instead of having all side
> +effects hidden behind a single step:
> +1. Test programs IOMMU translation tables
> +2. Test configures DMA address (GVA_LO/HI), length, and attributes
> +3. Test writes 1 to DMA_DBELL to arm the operation
so what does it? is it possible to arm if a transaction is already pending?
> +4. Test reads DMA_TRIGGERING to execute DMA
> +5. Test polls DMA_RESULT:
> +
> + - 0x00000000: Success
> + - 0xFFFFFFFE: Busy (still in progress)
> + - 0xDEAD000X: Various error codes
> +
> +The device performs a write-then-read sequence using a known pattern
> +(0x12345678) and verifies data integrity automatically.
> +
> +Running the qtest
> +-----------------
> +The SMMUv3 test suite uses this device and covers multiple translation modes::
> +
> + cd build-debug
> + QTEST_QEMU_BINARY=./qemu-system-aarch64 \\
> + ./tests/qtest/iommu-smmuv3-test --tap -k
> +
> +This test suite exercises:
> +
> +* Stage 1 only translation
> +* Stage 2 only translation
> +* Nested (Stage 1 + Stage 2) translation
> +
> +Instantiation
> +-------------
> +The device is not wired into any board by default. Tests instantiate it
> +via QEMU command line::
> +
> + -device iommu-testdev
> +
> +For ARM platforms with SMMUv3::
> +
> + -M virt,iommu=smmuv3 -device iommu-testdev
> +
> +The device will be placed behind the IOMMU automatically.
I guess the device is added on pci.0. so it works by default with
machine wide instantiation but not necessarily with arm-smmuv3 device
which can plugged downstream to a pxb. Maybe reword to avoid confusion.
> +
> +Limitations
> +-----------
> +* No realistic PCIe enumeration, MSI/MSI-X, or interrupt handling
> +* No ATS/PRI support
> +* No actual device functionality beyond DMA test pattern
> +* Test-only; not suitable for production or machine realism
> +* Address space support (Secure/Root/Realm) is architecture-dependent
> +
> +See also
> +--------
> +* ``tests/qtest/iommu-smmuv3-test.c`` — SMMUv3 test suite
> +* ``tests/qtest/libqos/qos-smmuv3.{c,h}`` — SMMUv3 test library
> +* SMMUv3 emulation: ``hw/arm/smmu*``
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index fccd735c24..b5f6fdbd9c 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 IOMMU_TESTDEV
> + bool
> + default y if TEST_DEVICES
> + depends on PCI
> +
> config EDU
> bool
> default y if TEST_DEVICES
> diff --git a/hw/misc/iommu-testdev.c b/hw/misc/iommu-testdev.c
> new file mode 100644
> index 0000000000..2cc1176aa6
> --- /dev/null
> +++ b/hw/misc/iommu-testdev.c
> @@ -0,0 +1,271 @@
> +/*
> + * A test device for IOMMU
> + *
> + * Copyright (c) 2025 Phytium Technology
nit 26
> + *
> + * Author:
> + * Tao Tang <tangtao1634@phytium.com.cn>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "system/address-spaces.h"
> +#include "trace.h"
> +#include "hw/pci/pci_device.h"
> +#include "hw/qdev-properties.h"
> +#include "qom/object.h"
> +#include "hw/misc/iommu-testdev.h"
> +
> +#define TYPE_IOMMU_TESTDEV "iommu-testdev"
> +OBJECT_DECLARE_SIMPLE_TYPE(IOMMUTestDevState, IOMMU_TESTDEV)
> +
> +struct IOMMUTestDevState {
> + PCIDevice parent_obj;
> + MemoryRegion bar0;
> + uint64_t dma_vaddr;
> + uint32_t dma_len;
> + uint32_t dma_result;
> + bool dma_pending;
> +
> + AddressSpace *dma_as; /* IOMMU-mediated DMA AS for this device */
> + uint32_t dma_attrs_cfg; /* bit0 secure, bits[2:1] space, bit3 unspecified */
> +};
> +
> +static void iommu_testdev_maybe_run_dma(IOMMUTestDevState *s)
> +{
> + uint32_t expected_val, actual_val;
> + g_autofree uint8_t *write_buf = NULL;
> + g_autofree uint8_t *read_buf = NULL;
> + MemTxResult write_res, read_res;
> + MemTxAttrs attrs;
> + AddressSpace *as;
> +
> + if (!s->dma_pending) {
> + s->dma_result = ITD_DMA_ERR_NOT_ARMED;
> + trace_iommu_testdev_dma_result(s->dma_result);
> + return;
> + }
> + trace_iommu_testdev_dma_start();
> +
> + s->dma_pending = false;
strange you reset pending before the very access. if I understand
correctly your dma_pending means dma_armed though
> +
> + if (!s->dma_len) {
> + s->dma_result = ITD_DMA_ERR_BAD_LEN;
> + return;
> + }
> +
> + write_buf = g_malloc(s->dma_len);
> + read_buf = g_malloc(s->dma_len);
> +
> + /* Initialize MemTxAttrs from generic register */
> + attrs.secure = ITD_ATTRS_GET_SECURE(s->dma_attrs_cfg);
> +
> + /* The 'space' field in MemTxAttrs is ARM-specific. */
> + attrs.space = ITD_ATTRS_GET_SPACE(s->dma_attrs_cfg);
maybe check they both secure and space are somehow consistent?
> +
> + as = s->dma_as;
> +
> + /* Step 1: Write ITD_DMA_WRITE_VAL to DMA address */
> + trace_iommu_testdev_dma_write(s->dma_vaddr, s->dma_len);
> +
> + for (int i = 0; i < s->dma_len; i++) {
> + /* Data is written in little-endian order */
> + write_buf[i] = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
> + }
> + write_res = dma_memory_write(as, s->dma_vaddr, write_buf, s->dma_len, attrs);
> +
> + if (write_res != MEMTX_OK) {
> + s->dma_result = ITD_DMA_ERR_TX_FAIL;
> + trace_iommu_testdev_dma_result(s->dma_result);
> + return;
> + }
> +
> + /* Step 2: Read back from the same DMA address */
> + trace_iommu_testdev_dma_read(s->dma_vaddr, s->dma_len);
> +
> + read_res = dma_memory_read(as, s->dma_vaddr, read_buf, s->dma_len, attrs);
This assumes the IOMMU translation is correct. If it is bad in both
directions, you may read the same value but does not necessarily means
the translated addr is correct.
Wouldn't it be better to read the mem at expected GPA without going
through the IOMMU?
> +
> + if (read_res != MEMTX_OK) {
> + s->dma_result = ITD_DMA_ERR_RD_FAIL;
> + trace_iommu_testdev_dma_result(s->dma_result);
> + return;
> + }
> +
> + /* Step 3: Verify the read data matches what we wrote */
> + for (int i = 0; i < s->dma_len; i += 4) {
> + int remaining_bytes = MIN(4, s->dma_len - i);
> +
> + expected_val = 0;
> + actual_val = 0;
> +
> + for (int j = 0; j < remaining_bytes; j++) {
> + expected_val |= ((uint32_t)write_buf[i + j]) << (j * 8);
> + actual_val |= ((uint32_t)read_buf[i + j]) << (j * 8);
> + }
> +
> + trace_iommu_testdev_dma_verify(expected_val, actual_val);
> +
> + if (expected_val != actual_val) {
> + s->dma_result = ITD_DMA_ERR_MISMATCH;
> + trace_iommu_testdev_dma_result(s->dma_result);
> + return;
> + }
> + }
> +
> + /* All checks passed */
> + s->dma_result = 0;
> + trace_iommu_testdev_dma_result(s->dma_result);
> +}
> +
> +static uint64_t iommu_testdev_mmio_read(void *opaque, hwaddr addr,
> + unsigned size)
> +{
> + IOMMUTestDevState *s = opaque;
> + uint64_t value = 0;
> +
> + switch (addr) {
> + case ITD_REG_DMA_TRIGGERING:
> + /*
> + * This lets tests poll ITD_REG_DMA_RESULT to observe BUSY before
> + * consuming the DMA.
> + */
> + iommu_testdev_maybe_run_dma(s);
> + value = 0;
> + break;
> + case ITD_REG_DMA_GVA_LO:
> + value = (uint32_t)(s->dma_vaddr & 0xffffffffu);
> + break;
> + case ITD_REG_DMA_GVA_HI:
> + value = (uint32_t)(s->dma_vaddr >> 32);
> + break;
> + case ITD_REG_DMA_LEN:
> + value = s->dma_len;
> + break;
> + case ITD_REG_DMA_RESULT:
> + value = s->dma_result;
> + break;
> + case ITD_REG_DMA_ATTRS:
> + value = s->dma_attrs_cfg;
> + break;
> + default:
> + value = 0;
> + break;
> + }
> +
> + trace_iommu_testdev_mmio_read(addr, value, size);
> + return value;
> +}
> +
> +static void iommu_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
> + unsigned size)
> +{
> + IOMMUTestDevState *s = opaque;
> + uint32_t data = val;
> +
> + trace_iommu_testdev_mmio_write(addr, val, size);
> +
> + switch (addr) {
> + case ITD_REG_DMA_GVA_LO:
> + s->dma_vaddr = (s->dma_vaddr & ~0xffffffffull) | data;
> + break;
> + case ITD_REG_DMA_GVA_HI:
> + s->dma_vaddr = (s->dma_vaddr & 0xffffffffull) |
> + ((uint64_t)data << 32);
> + break;
> + case ITD_REG_DMA_LEN:
> + s->dma_len = data;
> + break;
> + case ITD_REG_DMA_RESULT:
> + s->dma_result = data;
> + break;
> + case ITD_REG_DMA_DBELL:
> + if (data & ITD_DMA_DBELL_ARM) {
> + /* Arm the DMA operation */
> + s->dma_pending = true;
dma_armed?
> + s->dma_result = ITD_DMA_RESULT_BUSY;
> + trace_iommu_testdev_dma_pending(true);
> + } else {
> + /* Disarm the DMA operation */
> + s->dma_pending = false;
> + s->dma_result = ITD_DMA_RESULT_IDLE;
> + trace_iommu_testdev_dma_pending(false);
> + }
> + break;
> + case ITD_REG_DMA_ATTRS:
> + s->dma_attrs_cfg = data;
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps iommu_testdev_mmio_ops = {
> + .read = iommu_testdev_mmio_read,
> + .write = iommu_testdev_mmio_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void iommu_testdev_realize(PCIDevice *pdev, Error **errp)
> +{
> + IOMMUTestDevState *s = IOMMU_TESTDEV(pdev);
> +
> + s->dma_vaddr = 0;
> + s->dma_len = 0;
> + s->dma_result = ITD_DMA_RESULT_IDLE;
> + s->dma_pending = false;
> + s->dma_attrs_cfg = 0;
> + s->dma_as = pci_device_iommu_address_space(pdev);
> +
> + memory_region_init_io(&s->bar0, OBJECT(pdev), &iommu_testdev_mmio_ops, s,
> + TYPE_IOMMU_TESTDEV ".bar0", BAR0_SIZE);
> + pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
> +}
> +
> +static void iommu_testdev_reset(DeviceState *dev)
> +{
> + IOMMUTestDevState *s = IOMMU_TESTDEV(dev);
> +
> + s->dma_vaddr = 0;
> + s->dma_len = 0;
> + s->dma_result = ITD_DMA_RESULT_IDLE;
> + s->dma_pending = false;
> + s->dma_attrs_cfg = 0;
> +}
> +
> +static void iommu_testdev_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
> +
> + pc->realize = iommu_testdev_realize;
> + pc->vendor_id = IOMMU_TESTDEV_VENDOR_ID;
> + pc->device_id = IOMMU_TESTDEV_DEVICE_ID;
> + pc->revision = 0;
> + pc->class_id = PCI_CLASS_OTHERS;
> + dc->desc = "A test device for IOMMU";
> + set_bit(DEVICE_CATEGORY_MISC, dc->categories);
> + device_class_set_legacy_reset(dc, iommu_testdev_reset);
> +}
> +
> +static const TypeInfo iommu_testdev_info = {
> + .name = TYPE_IOMMU_TESTDEV,
> + .parent = TYPE_PCI_DEVICE,
> + .instance_size = sizeof(IOMMUTestDevState),
> + .class_init = iommu_testdev_class_init,
> + .interfaces = (const InterfaceInfo[]) {
> + { INTERFACE_CONVENTIONAL_PCI_DEVICE },
> + { }
> + },
> +};
> +
> +static void iommu_testdev_register_types(void)
> +{
> + type_register_static(&iommu_testdev_info);
> +}
> +
> +type_init(iommu_testdev_register_types);
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index b1d8d8e5d2..6f9bb9bb0f 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_IOMMU_TESTDEV', if_true: files('iommu-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/trace-events b/hw/misc/trace-events
> index eeb9243898..84fd349fb8 100644
> --- a/hw/misc/trace-events
> +++ b/hw/misc/trace-events
> @@ -409,3 +409,13 @@ ivshmem_flat_interrupt_peer(uint16_t peer_id, uint16_t vector_id) "Interrupting
> i2c_echo_event(const char *id, const char *event) "%s: %s"
> i2c_echo_recv(const char *id, uint8_t data) "%s: recv 0x%02" PRIx8
> i2c_echo_send(const char *id, uint8_t data) "%s: send 0x%02" PRIx8
> +
> +# iommu-testdev.c
> +iommu_testdev_mmio_read(uint64_t addr, uint64_t value, unsigned size) "addr=0x%" PRIx64 " value=0x%" PRIx64 " size=%u"
> +iommu_testdev_mmio_write(uint64_t addr, uint64_t value, unsigned size) "addr=0x%" PRIx64 " value=0x%" PRIx64 " size=%u"
> +iommu_testdev_dma_start(void) "DMA operation started"
> +iommu_testdev_dma_write(uint64_t gva, uint32_t len) "gva=0x%" PRIx64 " len=%u"
> +iommu_testdev_dma_read(uint64_t gva, uint32_t len) "gva=0x%" PRIx64 " len=%u"
> +iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actual=0x%x"
> +iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
> +iommu_testdev_dma_pending(bool pending) "pending=%d"
> diff --git a/include/hw/misc/iommu-testdev.h b/include/hw/misc/iommu-testdev.h
> new file mode 100644
> index 0000000000..14a5bca721
> --- /dev/null
> +++ b/include/hw/misc/iommu-testdev.h
> @@ -0,0 +1,68 @@
> +/*
> + * A test device for IOMMU
> + *
> + * Copyright (c) 2025 Phytium Technology
> + *
> + * Author:
> + * Tao Tang <tangtao1634@phytium.com.cn>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_IOMMU_TESTDEV_H
> +#define HW_MISC_IOMMU_TESTDEV_H
> +
> +#include "hw/pci/pci.h"
> +
> +#define IOMMU_TESTDEV_VENDOR_ID PCI_VENDOR_ID_REDHAT
> +#define IOMMU_TESTDEV_DEVICE_ID PCI_DEVICE_ID_REDHAT_TEST
> +
> +/* DMA_ATTRS register bit definitions (architecture-agnostic) */
> +#define ITD_ATTRS_SECURE_SHIFT 0
> +#define ITD_ATTRS_SECURE_MASK 0x1
> +#define ITD_ATTRS_SPACE_SHIFT 1
> +#define ITD_ATTRS_SPACE_MASK 0x3
> +
> +/* Helper macros for setting fields */
> +#define ITD_ATTRS_SET_SECURE(attrs, val) \
> + (((attrs) & ~(ITD_ATTRS_SECURE_MASK << ITD_ATTRS_SECURE_SHIFT)) | \
> + (((val) & ITD_ATTRS_SECURE_MASK) << ITD_ATTRS_SECURE_SHIFT))
> +
> +#define ITD_ATTRS_SET_SPACE(attrs, val) \
> + (((attrs) & ~(ITD_ATTRS_SPACE_MASK << ITD_ATTRS_SPACE_SHIFT)) | \
> + (((val) & ITD_ATTRS_SPACE_MASK) << ITD_ATTRS_SPACE_SHIFT))
> +
> +/* Helper macros for getting fields */
> +#define ITD_ATTRS_GET_SECURE(attrs) \
> + (((attrs) >> ITD_ATTRS_SECURE_SHIFT) & ITD_ATTRS_SECURE_MASK)
> +
> +#define ITD_ATTRS_GET_SPACE(attrs) \
> + (((attrs) >> ITD_ATTRS_SPACE_SHIFT) & ITD_ATTRS_SPACE_MASK)
> +
> +/* DMA result/status values shared with tests */
> +#define ITD_DMA_RESULT_IDLE 0xffffffffu
> +#define ITD_DMA_RESULT_BUSY 0xfffffffeu
> +#define ITD_DMA_ERR_BAD_LEN 0xdead0001u
> +#define ITD_DMA_ERR_TX_FAIL 0xdead0002u
> +#define ITD_DMA_ERR_RD_FAIL 0xdead0003u
> +#define ITD_DMA_ERR_MISMATCH 0xdead0004u
> +#define ITD_DMA_ERR_NOT_ARMED 0xdead0005u
> +
> +#define ITD_DMA_WRITE_VAL 0x12345678u
> +
> +/* DMA doorbell bits */
> +#define ITD_DMA_DBELL_ARM 0x1u
> +
> +/* BAR0 layout of iommu-testdev */
> +enum {
> + ITD_REG_DMA_TRIGGERING = 0x00,
> + ITD_REG_DMA_GVA_LO = 0x04,
> + ITD_REG_DMA_GVA_HI = 0x08,
> + ITD_REG_DMA_LEN = 0x0c,
> + ITD_REG_DMA_RESULT = 0x10,
> + ITD_REG_DMA_DBELL = 0x14,
> + ITD_REG_DMA_ATTRS = 0x18, /* [0] secure,[2:1] space,[3] unspecified */
> + BAR0_SIZE = 0x1000,
> +};
> +
> +#endif /* HW_MISC_IOMMU_TESTDEV_H */
Thanks
Eric
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing
2026-01-15 12:59 ` Eric Auger
@ 2026-01-16 14:37 ` Tao Tang
0 siblings, 0 replies; 35+ messages in thread
From: Tao Tang @ 2026-01-16 14:37 UTC (permalink / raw)
To: eric.auger, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Eric,
On 2026/1/15 20:59, Eric Auger wrote:
> Hi Tao,
>
> On 12/24/25 4:46 AM, Tao Tang wrote:
>> ------------------------------<snip>------------------------------
>>
>>
>>
>> ------------------------------<snip>------------------------------
>> +
>> +Status
>> +------
>> +* Location: ``hw/misc/iommu-testdev.c``
>> +* Header: ``include/hw/misc/iommu-testdev.h``
>> +* Build guard: ``CONFIG_IOMMU_TESTDEV``
>> +
>> +Device Interface
>> +----------------
>> +The device exposes a single PCI BAR0 with 32bit MMIO registers:
>> +
>> +* ``ITD_REG_DMA_TRIGGERING`` (0x00): Reading triggers DMA execution
>> +* ``ITD_REG_DMA_GVA_LO`` (0x04): GVA bits [31:0]
>> +* ``ITD_REG_DMA_GVA_HI`` (0x08): GVA bits [63:32]
>> +* ``ITD_REG_DMA_LEN`` (0x0C): DMA transfer length
>> +* ``ITD_REG_DMA_RESULT`` (0x10): DMA operation result (0=success)
>> +* ``ITD_REG_DMA_DBELL`` (0x14): Write 1 to arm DMA
> I know you provided some explanations to be me earlier about separation
> between "arming" and "triggering" but I don't find info in the doc about
> what arming practically does and what kind of checks it can enable
> compared to the actual trigger. I am not asking for removing it but
> maybe document what it aims at.
Thanks for the feedback. I’ll document the intent in the doc.
Today arming only marks the request as armed and sets BUSY; it does not
latch parameters or run checks, so its current effect is intentionally
light. It’s still useful as an explicit gate so qtests can distinguish
NOT_ARMED vs trigger-time failures, and it keeps room for future
extensions (async execution or parameter latching).
>> +* ``ITD_REG_DMA_ATTRS`` (0x18): DMA attributes which shadow MemTxAttrs format:
>> +
>> + - bit[0]: secure (1=Secure, 0=Non-Secure)
>> + - bits[2:1]: address space (0=Non-Secure, 1=Secure)
> I was confused by the diff between those 2 fields. If my understanding
> is correct they should equal today, until we got some further RME stuff?
> Correct?
>
> In MemTxAttrs it is said:
> unsigned int secure:1;
> /*
> * ARM: ArmSecuritySpace. This partially overlaps secure, but it is
> * easier to have both fields to assist code that does not understand
> * ARMv9 RME, or no specific knowledge of ARM at all (e.g. pflash).
> */
> unsigned int space:2;
Yes — today they end up equal in value. But they carry different
semantics: the same numeric value is interpreted differently (e.g.
space==1 means Non-secure, while secure==0 also means Non-secure). I
added both fields to predefine the upcoming split needed for RME,
without implying any RME support in the SMMU yet.
BTW I'll change `bits[2:1]: address space` to be `bits[2:1]:
ArmSecuritySpace` to match the ARM spec as address space is not
particularly accurate.
>> + Only these MemTxAttrs fields (``secure`` and ``space``) are consumed today;
>> + other bits are reserved but can be wired up easily if future tests need
>> + to pass extra attributes.
>> +
>> +Translation Setup Workflow
>> +--------------------------
>> +``iommu-testdev`` never builds SMMU/AMD-Vi/RISC-V IOMMU structures on its own.
>> +Architecture-specific construction lives entirely in qtest/libqos helpers.
>> +Those helpers populate guest memory with page tables/architecture-specific
>> +structures and program the emulated IOMMU registers directly. See the
>> +``qsmmu_setup_and_enable_translation()`` function in
>> +``tests/qtest/libqos/qos-smmuv3.c`` for an example of how SMMUv3 translation
>> +is set up for this device, which will be introduced in the next commit.
> nit: this is rather a commit description comment.
I'll move it into commit message.
>> +
>> +DMA Operation Flow
>> +------------------
>> +The flow would be split into these steps, mainly for timing control and
>> +debuggability: qtests can easily exercise and assert distinct paths
>> +(NOT_ARMED, BAD_LEN, TX/RD failures, mismatch) instead of having all side
>> +effects hidden behind a single step:
>> +1. Test programs IOMMU translation tables
>> +2. Test configures DMA address (GVA_LO/HI), length, and attributes
>> +3. Test writes 1 to DMA_DBELL to arm the operation
> so what does it? is it possible to arm if a transaction is already pending?
Arming just sets the armed gate and BUSY; it doesn’t run checks or start
DMA. Re‑arming while already armed is idempotent: it keeps BUSY and does
not queue a second request.
>> +4. Test reads DMA_TRIGGERING to execute DMA
>> +5. Test polls DMA_RESULT:
>> +
>> + - 0x00000000: Success
>> + - 0xFFFFFFFE: Busy (still in progress)
>> + - 0xDEAD000X: Various error codes
>> +
>> +The device performs a write-then-read sequence using a known pattern
>> +(0x12345678) and verifies data integrity automatically.
>> +
>> +Running the qtest
>> +-----------------
>> +The SMMUv3 test suite uses this device and covers multiple translation modes::
>> +
>> + cd build-debug
>> + QTEST_QEMU_BINARY=./qemu-system-aarch64 \\
>> + ./tests/qtest/iommu-smmuv3-test --tap -k
>> +
>> +This test suite exercises:
>> +
>> +* Stage 1 only translation
>> +* Stage 2 only translation
>> +* Nested (Stage 1 + Stage 2) translation
>> +
>> +Instantiation
>> +-------------
>> +The device is not wired into any board by default. Tests instantiate it
>> +via QEMU command line::
>> +
>> + -device iommu-testdev
>> +
>> +For ARM platforms with SMMUv3::
>> +
>> + -M virt,iommu=smmuv3 -device iommu-testdev
>> +
>> +The device will be placed behind the IOMMU automatically.
> I guess the device is added on pci.0. so it works by default with
> machine wide instantiation but not necessarily with arm-smmuv3 device
> which can plugged downstream to a pxb. Maybe reword to avoid confusion.
I’ll reword it in next version.
>> +
>> +Limitations
>> +-----------
>> +* No realistic PCIe enumeration, MSI/MSI-X, or interrupt handling
>> +* No ATS/PRI support
>> +* No actual device functionality beyond DMA test pattern
>> +* Test-only; not suitable for production or machine realism
>> +* Address space support (Secure/Root/Realm) is architecture-dependent
>> +
>> +See also
>> +--------
>> +* ``tests/qtest/iommu-smmuv3-test.c`` — SMMUv3 test suite
>> +* ``tests/qtest/libqos/qos-smmuv3.{c,h}`` — SMMUv3 test library
>> +* SMMUv3 emulation: ``hw/arm/smmu*``
>> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
>> index fccd735c24..b5f6fdbd9c 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 IOMMU_TESTDEV
>> + bool
>> + default y if TEST_DEVICES
>> + depends on PCI
>> +
>> config EDU
>> bool
>> default y if TEST_DEVICES
>> diff --git a/hw/misc/iommu-testdev.c b/hw/misc/iommu-testdev.c
>> new file mode 100644
>> index 0000000000..2cc1176aa6
>> --- /dev/null
>> +++ b/hw/misc/iommu-testdev.c
>> @@ -0,0 +1,271 @@
>> +/*
>> + * A test device for IOMMU
>> + *
>> + * Copyright (c) 2025 Phytium Technology
> nit 26
I’ll fix the copyright formatting, including other files.
>> + *
>> + * Author:
>> + * Tao Tang <tangtao1634@phytium.com.cn>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "system/address-spaces.h"
>> +#include "trace.h"
>> +#include "hw/pci/pci_device.h"
>> +#include "hw/qdev-properties.h"
>> +#include "qom/object.h"
>> +#include "hw/misc/iommu-testdev.h"
>> +
>> +#define TYPE_IOMMU_TESTDEV "iommu-testdev"
>> +OBJECT_DECLARE_SIMPLE_TYPE(IOMMUTestDevState, IOMMU_TESTDEV)
>> +
>> +struct IOMMUTestDevState {
>> + PCIDevice parent_obj;
>> + MemoryRegion bar0;
>> + uint64_t dma_vaddr;
>> + uint32_t dma_len;
>> + uint32_t dma_result;
>> + bool dma_pending;
>> +
>> + AddressSpace *dma_as; /* IOMMU-mediated DMA AS for this device */
>> + uint32_t dma_attrs_cfg; /* bit0 secure, bits[2:1] space, bit3 unspecified */
>> +};
>> +
>> +static void iommu_testdev_maybe_run_dma(IOMMUTestDevState *s)
>> +{
>> + uint32_t expected_val, actual_val;
>> + g_autofree uint8_t *write_buf = NULL;
>> + g_autofree uint8_t *read_buf = NULL;
>> + MemTxResult write_res, read_res;
>> + MemTxAttrs attrs;
>> + AddressSpace *as;
>> +
>> + if (!s->dma_pending) {
>> + s->dma_result = ITD_DMA_ERR_NOT_ARMED;
>> + trace_iommu_testdev_dma_result(s->dma_result);
>> + return;
>> + }
>> + trace_iommu_testdev_dma_start();
>> +
>> + s->dma_pending = false;
> strange you reset pending before the very access. if I understand
> correctly your dma_pending means dma_armed though
You’re right — pending was meant to mean ‘armed’. I’ve changed it so the
flag stays set until the DMA finishes (cleared only at the end), and
renamed it to dma_armed for clarity.
>> +
>> + if (!s->dma_len) {
>> + s->dma_result = ITD_DMA_ERR_BAD_LEN;
>> + return;
>> + }
>> +
>> + write_buf = g_malloc(s->dma_len);
>> + read_buf = g_malloc(s->dma_len);
>> +
>> + /* Initialize MemTxAttrs from generic register */
>> + attrs.secure = ITD_ATTRS_GET_SECURE(s->dma_attrs_cfg);
>> +
>> + /* The 'space' field in MemTxAttrs is ARM-specific. */
>> + attrs.space = ITD_ATTRS_GET_SPACE(s->dma_attrs_cfg);
> maybe check they both secure and space are somehow consistent?
I will check the consistency between space and secure.
>> +
>> + as = s->dma_as;
>> +
>> + /* Step 1: Write ITD_DMA_WRITE_VAL to DMA address */
>> + trace_iommu_testdev_dma_write(s->dma_vaddr, s->dma_len);
>> +
>> + for (int i = 0; i < s->dma_len; i++) {
>> + /* Data is written in little-endian order */
>> + write_buf[i] = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
>> + }
>> + write_res = dma_memory_write(as, s->dma_vaddr, write_buf, s->dma_len, attrs);
>> +
>> + if (write_res != MEMTX_OK) {
>> + s->dma_result = ITD_DMA_ERR_TX_FAIL;
>> + trace_iommu_testdev_dma_result(s->dma_result);
>> + return;
>> + }
>> +
>> + /* Step 2: Read back from the same DMA address */
>> + trace_iommu_testdev_dma_read(s->dma_vaddr, s->dma_len);
>> +
>> + read_res = dma_memory_read(as, s->dma_vaddr, read_buf, s->dma_len, attrs);
> This assumes the IOMMU translation is correct. If it is bad in both
> directions, you may read the same value but does not necessarily means
> the translated addr is correct.
> Wouldn't it be better to read the mem at expected GPA without going
> through the IOMMU?
Sure. I just tried to pass GPA into iommu-testdev and address_space_read
from GPA and then validated the result. It works well and I'll add this
logic in V9. Thanks for your suggestion.
>> ------------------------------<snip>------------------------------
>>
>>
>>
>> ------------------------------<snip>------------------------------
>> +static void iommu_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
>> + unsigned size)
>> +{
>> + IOMMUTestDevState *s = opaque;
>> + uint32_t data = val;
>> +
>> + trace_iommu_testdev_mmio_write(addr, val, size);
>> +
>> + switch (addr) {
>> + case ITD_REG_DMA_GVA_LO:
>> + s->dma_vaddr = (s->dma_vaddr & ~0xffffffffull) | data;
>> + break;
>> + case ITD_REG_DMA_GVA_HI:
>> + s->dma_vaddr = (s->dma_vaddr & 0xffffffffull) |
>> + ((uint64_t)data << 32);
>> + break;
>> + case ITD_REG_DMA_LEN:
>> + s->dma_len = data;
>> + break;
>> + case ITD_REG_DMA_RESULT:
>> + s->dma_result = data;
>> + break;
>> + case ITD_REG_DMA_DBELL:
>> + if (data & ITD_DMA_DBELL_ARM) {
>> + /* Arm the DMA operation */
>> + s->dma_pending = true;
> dma_armed?
I'll change it as mentioned earlier.
Thank you again for your thorough and detailed code review.
Best regards,
Tao
^ permalink raw reply [flat|nested] 35+ messages in thread
* [RFC v8 4/7] hw/arm/smmuv3-common: Add NSCFG bit definition for CD
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
` (2 preceding siblings ...)
2025-12-24 3:46 ` [RFC v8 3/7] hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing Tao Tang
@ 2025-12-24 3:46 ` Tao Tang
2026-01-15 17:32 ` Eric Auger
2025-12-24 3:46 ` [RFC v8 5/7] hw/arm/smmuv3-common: Add STE/CD set helpers for repeated field setup Tao Tang
` (4 subsequent siblings)
8 siblings, 1 reply; 35+ messages in thread
From: Tao Tang @ 2025-12-24 3:46 UTC (permalink / raw)
To: Paolo Bonzini, Fabiano Rosas, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF, Tao Tang
Add NSCFG bit definition for CD structure. This allows proper
configuration of non-secure access settings in CD.
Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
include/hw/arm/smmuv3-common.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
index b6da2fd62c..7f7dd02221 100644
--- a/include/hw/arm/smmuv3-common.h
+++ b/include/hw/arm/smmuv3-common.h
@@ -123,11 +123,13 @@ REG32(CD_1, 4)
FIELD(CD_1, A, 14, 1)
FIELD(CD_1, ASID, 16, 16)
REG32(CD_2, 8)
+ FIELD(CD_2, NSCFG0, 0, 1)
FIELD(CD_2, HAD0, 1, 1)
FIELD(CD_2, TTB0_LO, 4, 28)
REG32(CD_3, 12)
FIELD(CD_3, TTB0_HI, 0, 19)
REG32(CD_4, 16)
+ FIELD(CD_4, NSCFG1, 0, 1)
FIELD(CD_4, HAD1, 1, 1)
FIELD(CD_4, TTB1_LO, 4, 28)
REG32(CD_5, 20)
@@ -155,6 +157,9 @@ REG32(CD_5, 20)
#define CD_R(x) FIELD_EX32((x)->word[1], CD_1, R)
#define CD_A(x) FIELD_EX32((x)->word[1], CD_1, A)
#define CD_ASID(x) FIELD_EX32((x)->word[1], CD_1, ASID)
+#define CD_NSCFG(x, sel) ((sel) ? \
+ FIELD_EX32((x)->word[4], CD_4, NSCFG1) : \
+ FIELD_EX32((x)->word[2], CD_2, NSCFG0))
#define CD_HAD(x, sel) ((sel) ? \
FIELD_EX32((x)->word[4], CD_4, HAD1) : \
FIELD_EX32((x)->word[2], CD_2, HAD0))
--
2.34.1
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [RFC v8 4/7] hw/arm/smmuv3-common: Add NSCFG bit definition for CD
2025-12-24 3:46 ` [RFC v8 4/7] hw/arm/smmuv3-common: Add NSCFG bit definition for CD Tao Tang
@ 2026-01-15 17:32 ` Eric Auger
0 siblings, 0 replies; 35+ messages in thread
From: Eric Auger @ 2026-01-15 17:32 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
On 12/24/25 4:46 AM, Tao Tang wrote:
> Add NSCFG bit definition for CD structure. This allows proper
> configuration of non-secure access settings in CD.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Eric
> ---
> include/hw/arm/smmuv3-common.h | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
> index b6da2fd62c..7f7dd02221 100644
> --- a/include/hw/arm/smmuv3-common.h
> +++ b/include/hw/arm/smmuv3-common.h
> @@ -123,11 +123,13 @@ REG32(CD_1, 4)
> FIELD(CD_1, A, 14, 1)
> FIELD(CD_1, ASID, 16, 16)
> REG32(CD_2, 8)
> + FIELD(CD_2, NSCFG0, 0, 1)
> FIELD(CD_2, HAD0, 1, 1)
> FIELD(CD_2, TTB0_LO, 4, 28)
> REG32(CD_3, 12)
> FIELD(CD_3, TTB0_HI, 0, 19)
> REG32(CD_4, 16)
> + FIELD(CD_4, NSCFG1, 0, 1)
> FIELD(CD_4, HAD1, 1, 1)
> FIELD(CD_4, TTB1_LO, 4, 28)
> REG32(CD_5, 20)
> @@ -155,6 +157,9 @@ REG32(CD_5, 20)
> #define CD_R(x) FIELD_EX32((x)->word[1], CD_1, R)
> #define CD_A(x) FIELD_EX32((x)->word[1], CD_1, A)
> #define CD_ASID(x) FIELD_EX32((x)->word[1], CD_1, ASID)
> +#define CD_NSCFG(x, sel) ((sel) ? \
> + FIELD_EX32((x)->word[4], CD_4, NSCFG1) : \
> + FIELD_EX32((x)->word[2], CD_2, NSCFG0))
> #define CD_HAD(x, sel) ((sel) ? \
> FIELD_EX32((x)->word[4], CD_4, HAD1) : \
> FIELD_EX32((x)->word[2], CD_2, HAD0))
^ permalink raw reply [flat|nested] 35+ messages in thread
* [RFC v8 5/7] hw/arm/smmuv3-common: Add STE/CD set helpers for repeated field setup
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
` (3 preceding siblings ...)
2025-12-24 3:46 ` [RFC v8 4/7] hw/arm/smmuv3-common: Add NSCFG bit definition for CD Tao Tang
@ 2025-12-24 3:46 ` Tao Tang
2026-01-15 17:45 ` Eric Auger
2025-12-24 3:46 ` [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library Tao Tang
` (3 subsequent siblings)
8 siblings, 1 reply; 35+ messages in thread
From: Tao Tang @ 2025-12-24 3:46 UTC (permalink / raw)
To: Paolo Bonzini, Fabiano Rosas, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF, Tao Tang
This change introduces STE_SET_* and CD_SET_* helpers to centralize and
simplify repeated field setting logic.
Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
include/hw/arm/smmuv3-common.h | 79 ++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
index 7f7dd02221..0c6c162288 100644
--- a/include/hw/arm/smmuv3-common.h
+++ b/include/hw/arm/smmuv3-common.h
@@ -100,6 +100,37 @@ REG32(STE_7, 28)
#define STE_CFG_ABORT(config) (!(config & 0x4))
#define STE_CFG_BYPASS(config) (config == 0x4)
+/* Update STE fields */
+#define STE_SET_VALID(ste, v) \
+ ((ste)->word[0] = FIELD_DP32((ste)->word[0], STE_0, VALID, (v)))
+#define STE_SET_CONFIG(ste, v) \
+ ((ste)->word[0] = FIELD_DP32((ste)->word[0], STE_0, CONFIG, (v)))
+
+#define STE_SET_CTXPTR(ste, v) do { \
+ (ste)->word[0] = FIELD_DP32((ste)->word[0], STE_0, CTXPTR_LO, (v) >> 6); \
+ (ste)->word[1] = FIELD_DP32((ste)->word[1], STE_1, CTXPTR_HI, (v) >> 32); \
+} while (0)
+#define STE_SET_S2T0SZ(ste, v) \
+ ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2T0SZ, (v)))
+#define STE_SET_S2SL0(ste, v) \
+ ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2SL0, (v)))
+#define STE_SET_S2TG(ste, v) \
+ ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2TG, (v)))
+#define STE_SET_S2PS(ste, v) \
+ ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2PS, (v)))
+#define STE_SET_S2AA64(ste, v) \
+ ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2AA64, (v)))
+#define STE_SET_S2ENDI(ste, v) \
+ ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2ENDI, (v)))
+#define STE_SET_S2AFFD(ste, v) \
+ ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2AFFD, (v)))
+#define STE_SET_S2S(ste, v) \
+ ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2S, (v)))
+#define STE_SET_S2TTB(ste, v) do { \
+ (ste)->word[6] = FIELD_DP32((ste)->word[6], STE_6, S2TTB_LO, (v) >> 4); \
+ (ste)->word[7] = FIELD_DP32((ste)->word[7], STE_7, S2TTB_HI, (v) >> 32); \
+} while (0)
+
/* CD fields */
REG32(CD_0, 0)
@@ -169,6 +200,54 @@ REG32(CD_5, 20)
(((uint64_t)FIELD_EX32((x)->word[3], CD_3, TTB0_HI) << 32) | \
((uint64_t)FIELD_EX32((x)->word[2], CD_2, TTB0_LO) << 4)))
+/* Update CD fields */
+#define CD_SET_VALID(cd, v) \
+ ((cd)->word[0] = FIELD_DP32((cd)->word[0], CD_0, VALID, (v)))
+#define CD_SET_ASID(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, ASID, (v)))
+#define CD_SET_TTB(cd, sel, v) do { \
+ if (sel) { \
+ (cd)->word[4] = FIELD_DP32((cd)->word[4], CD_4, TTB1_LO, (v) >> 4); \
+ (cd)->word[5] = FIELD_DP32((cd)->word[5], CD_5, TTB1_HI, (v) >> 32); \
+ } else { \
+ (cd)->word[2] = FIELD_DP32((cd)->word[2], CD_2, TTB0_LO, (v) >> 4); \
+ (cd)->word[3] = FIELD_DP32((cd)->word[3], CD_3, TTB0_HI, (v) >> 32); \
+ } \
+} while (0)
+
+#define CD_SET_TSZ(cd, sel, v) \
+ ((cd)->word[0] = (sel) ? FIELD_DP32((cd)->word[0], CD_0, TSZ1, (v)) : \
+ FIELD_DP32((cd)->word[0], CD_0, TSZ0, (v)))
+#define CD_SET_TG(cd, sel, v) \
+ ((cd)->word[0] = (sel) ? FIELD_DP32((cd)->word[0], CD_0, TG1, (v)) : \
+ FIELD_DP32((cd)->word[0], CD_0, TG0, (v)))
+#define CD_SET_EPD(cd, sel, v) \
+ ((cd)->word[0] = (sel) ? FIELD_DP32((cd)->word[0], CD_0, EPD1, (v)) : \
+ FIELD_DP32((cd)->word[0], CD_0, EPD0, (v)))
+#define CD_SET_ENDI(cd, v) \
+ ((cd)->word[0] = FIELD_DP32((cd)->word[0], CD_0, ENDI, (v)))
+#define CD_SET_IPS(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, IPS, (v)))
+#define CD_SET_AFFD(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, AFFD, (v)))
+#define CD_SET_TBI(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, TBI, (v)))
+#define CD_SET_HD(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, HD, (v)))
+#define CD_SET_HA(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, HA, (v)))
+#define CD_SET_S(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, S, (v)))
+#define CD_SET_R(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, R, (v)))
+#define CD_SET_A(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, A, (v)))
+#define CD_SET_AARCH64(cd, v) \
+ ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, AARCH64, (v)))
+#define CD_SET_NSCFG(cd, sel, v) \
+ ((sel) ? ((cd)->word[4] = FIELD_DP32((cd)->word[4], CD_4, NSCFG1, (v))) : \
+ ((cd)->word[2] = FIELD_DP32((cd)->word[2], CD_2, NSCFG0, (v))))
+
/* MMIO Registers */
REG32(IDR0, 0x0)
--
2.34.1
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [RFC v8 5/7] hw/arm/smmuv3-common: Add STE/CD set helpers for repeated field setup
2025-12-24 3:46 ` [RFC v8 5/7] hw/arm/smmuv3-common: Add STE/CD set helpers for repeated field setup Tao Tang
@ 2026-01-15 17:45 ` Eric Auger
0 siblings, 0 replies; 35+ messages in thread
From: Eric Auger @ 2026-01-15 17:45 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
On 12/24/25 4:46 AM, Tao Tang wrote:
> This change introduces STE_SET_* and CD_SET_* helpers to centralize and
> simplify repeated field setting logic.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Eric
> ---
> include/hw/arm/smmuv3-common.h | 79 ++++++++++++++++++++++++++++++++++
> 1 file changed, 79 insertions(+)
>
> diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h
> index 7f7dd02221..0c6c162288 100644
> --- a/include/hw/arm/smmuv3-common.h
> +++ b/include/hw/arm/smmuv3-common.h
> @@ -100,6 +100,37 @@ REG32(STE_7, 28)
> #define STE_CFG_ABORT(config) (!(config & 0x4))
> #define STE_CFG_BYPASS(config) (config == 0x4)
>
> +/* Update STE fields */
> +#define STE_SET_VALID(ste, v) \
> + ((ste)->word[0] = FIELD_DP32((ste)->word[0], STE_0, VALID, (v)))
> +#define STE_SET_CONFIG(ste, v) \
> + ((ste)->word[0] = FIELD_DP32((ste)->word[0], STE_0, CONFIG, (v)))
> +
> +#define STE_SET_CTXPTR(ste, v) do { \
> + (ste)->word[0] = FIELD_DP32((ste)->word[0], STE_0, CTXPTR_LO, (v) >> 6); \
> + (ste)->word[1] = FIELD_DP32((ste)->word[1], STE_1, CTXPTR_HI, (v) >> 32); \
> +} while (0)
> +#define STE_SET_S2T0SZ(ste, v) \
> + ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2T0SZ, (v)))
> +#define STE_SET_S2SL0(ste, v) \
> + ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2SL0, (v)))
> +#define STE_SET_S2TG(ste, v) \
> + ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2TG, (v)))
> +#define STE_SET_S2PS(ste, v) \
> + ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2PS, (v)))
> +#define STE_SET_S2AA64(ste, v) \
> + ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2AA64, (v)))
> +#define STE_SET_S2ENDI(ste, v) \
> + ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2ENDI, (v)))
> +#define STE_SET_S2AFFD(ste, v) \
> + ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2AFFD, (v)))
> +#define STE_SET_S2S(ste, v) \
> + ((ste)->word[5] = FIELD_DP32((ste)->word[5], STE_5, S2S, (v)))
> +#define STE_SET_S2TTB(ste, v) do { \
> + (ste)->word[6] = FIELD_DP32((ste)->word[6], STE_6, S2TTB_LO, (v) >> 4); \
> + (ste)->word[7] = FIELD_DP32((ste)->word[7], STE_7, S2TTB_HI, (v) >> 32); \
> +} while (0)
> +
> /* CD fields */
>
> REG32(CD_0, 0)
> @@ -169,6 +200,54 @@ REG32(CD_5, 20)
> (((uint64_t)FIELD_EX32((x)->word[3], CD_3, TTB0_HI) << 32) | \
> ((uint64_t)FIELD_EX32((x)->word[2], CD_2, TTB0_LO) << 4)))
>
> +/* Update CD fields */
> +#define CD_SET_VALID(cd, v) \
> + ((cd)->word[0] = FIELD_DP32((cd)->word[0], CD_0, VALID, (v)))
> +#define CD_SET_ASID(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, ASID, (v)))
> +#define CD_SET_TTB(cd, sel, v) do { \
> + if (sel) { \
> + (cd)->word[4] = FIELD_DP32((cd)->word[4], CD_4, TTB1_LO, (v) >> 4); \
> + (cd)->word[5] = FIELD_DP32((cd)->word[5], CD_5, TTB1_HI, (v) >> 32); \
> + } else { \
> + (cd)->word[2] = FIELD_DP32((cd)->word[2], CD_2, TTB0_LO, (v) >> 4); \
> + (cd)->word[3] = FIELD_DP32((cd)->word[3], CD_3, TTB0_HI, (v) >> 32); \
> + } \
> +} while (0)
> +
> +#define CD_SET_TSZ(cd, sel, v) \
> + ((cd)->word[0] = (sel) ? FIELD_DP32((cd)->word[0], CD_0, TSZ1, (v)) : \
> + FIELD_DP32((cd)->word[0], CD_0, TSZ0, (v)))
> +#define CD_SET_TG(cd, sel, v) \
> + ((cd)->word[0] = (sel) ? FIELD_DP32((cd)->word[0], CD_0, TG1, (v)) : \
> + FIELD_DP32((cd)->word[0], CD_0, TG0, (v)))
> +#define CD_SET_EPD(cd, sel, v) \
> + ((cd)->word[0] = (sel) ? FIELD_DP32((cd)->word[0], CD_0, EPD1, (v)) : \
> + FIELD_DP32((cd)->word[0], CD_0, EPD0, (v)))
> +#define CD_SET_ENDI(cd, v) \
> + ((cd)->word[0] = FIELD_DP32((cd)->word[0], CD_0, ENDI, (v)))
> +#define CD_SET_IPS(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, IPS, (v)))
> +#define CD_SET_AFFD(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, AFFD, (v)))
> +#define CD_SET_TBI(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, TBI, (v)))
> +#define CD_SET_HD(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, HD, (v)))
> +#define CD_SET_HA(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, HA, (v)))
> +#define CD_SET_S(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, S, (v)))
> +#define CD_SET_R(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, R, (v)))
> +#define CD_SET_A(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, A, (v)))
> +#define CD_SET_AARCH64(cd, v) \
> + ((cd)->word[1] = FIELD_DP32((cd)->word[1], CD_1, AARCH64, (v)))
> +#define CD_SET_NSCFG(cd, sel, v) \
> + ((sel) ? ((cd)->word[4] = FIELD_DP32((cd)->word[4], CD_4, NSCFG1, (v))) : \
> + ((cd)->word[2] = FIELD_DP32((cd)->word[2], CD_2, NSCFG0, (v))))
> +
> /* MMIO Registers */
>
> REG32(IDR0, 0x0)
^ permalink raw reply [flat|nested] 35+ messages in thread
* [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
` (4 preceding siblings ...)
2025-12-24 3:46 ` [RFC v8 5/7] hw/arm/smmuv3-common: Add STE/CD set helpers for repeated field setup Tao Tang
@ 2025-12-24 3:46 ` Tao Tang
2026-01-15 18:30 ` Eric Auger
2026-01-17 10:02 ` Eric Auger
2025-12-24 3:46 ` [RFC v8 7/7] tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev Tao Tang
` (2 subsequent siblings)
8 siblings, 2 replies; 35+ messages in thread
From: Tao Tang @ 2025-12-24 3:46 UTC (permalink / raw)
To: Paolo Bonzini, Fabiano Rosas, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF, Tao Tang
Introduce qos-smmuv3, a reusable library for SMMUv3-related qtest
operations. This module encapsulates common tasks like:
- SMMUv3 initialization (enabling, configuring command/event queues)
- Stream Table Entry (STE) and Context Descriptor (CD) setup
- Multi-level page table construction (L0-L3 for 4KB granules)
- Support for Stage 1, Stage 2, and nested translation modes
- Could be easily extended to support multi-space testing infrastructure
(Non-Secure, Secure, Root, Realm)
The library provides high-level abstractions that allow test code to
focus on IOMMU behavior validation rather than low-level register
manipulation and page table encoding. Key features include:
- Provide memory allocation for translation structures with proper
alignment
- Helper functions to build valid STEs/CDs for different translation
scenarios
- Page table walkers that handle address offset calculations per
security space
This infrastructure is designed to be used by iommu-testdev-based tests
and future SMMUv3 test suites, reducing code duplication and improving
test maintainability.
Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
---
MAINTAINERS | 6 +
tests/qtest/libqos/meson.build | 3 +
tests/qtest/libqos/qos-smmuv3.c | 633 ++++++++++++++++++++++++++++++++
tests/qtest/libqos/qos-smmuv3.h | 256 +++++++++++++
4 files changed, 898 insertions(+)
create mode 100644 tests/qtest/libqos/qos-smmuv3.c
create mode 100644 tests/qtest/libqos/qos-smmuv3.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 3c88ed5dc4..68ab713429 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3546,6 +3546,12 @@ F: docs/devel/testing/qtest.rst
X: tests/qtest/bios-tables-test*
X: tests/qtest/migration-*
+QTest SMMUv3 helpers
+M: Tao Tang <tangtao1634@phytium.com.cn>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: tests/qtest/libqos/qos-smmuv3*
+
Device Fuzzing
M: Alexander Bulekov <alxndr@bu.edu>
R: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
index 1ddaf7b095..8d6758ec2b 100644
--- a/tests/qtest/libqos/meson.build
+++ b/tests/qtest/libqos/meson.build
@@ -60,6 +60,9 @@ libqos_srcs = files(
'x86_64_pc-machine.c',
'riscv-virt-machine.c',
'loongarch-virt-machine.c',
+
+ # SMMU:
+ 'qos-smmuv3.c',
)
if have_virtfs
diff --git a/tests/qtest/libqos/qos-smmuv3.c b/tests/qtest/libqos/qos-smmuv3.c
new file mode 100644
index 0000000000..a48abb186b
--- /dev/null
+++ b/tests/qtest/libqos/qos-smmuv3.c
@@ -0,0 +1,633 @@
+/*
+ * QOS SMMUv3 Module
+ *
+ * This module provides SMMUv3-specific helper functions for libqos tests,
+ * encapsulating SMMUv3 setup, and assertions.
+ *
+ * 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 "hw/arm/smmuv3-common.h"
+#include "tests/qtest/libqos/pci.h"
+#include "hw/misc/iommu-testdev.h"
+#include "qos-smmuv3.h"
+
+#define QSMMU_STE_S2T0SZ_VAL 0x14
+
+/* Apply space offset to address */
+static inline uint64_t qsmmu_apply_space_offs(QSMMUSpace sp,
+ uint64_t address)
+{
+ return address + qsmmu_space_offset(sp);
+}
+
+uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx)
+{
+ /* Currently only non-secure space is supported. */
+ if (ctx->tx_space != QSMMU_SPACE_NONSECURE) {
+ return ITD_DMA_ERR_TX_FAIL;
+ }
+ return ctx->config.expected_result;
+}
+
+uint32_t qsmmu_build_dma_attrs(QSMMUSpace space)
+{
+ uint32_t attrs = 0;
+ switch (space) {
+ case QSMMU_SPACE_NONSECURE:
+ /* Non-secure: secure=0, space=1 */
+ attrs = ITD_ATTRS_SET_SECURE(attrs, 0);
+ attrs = ITD_ATTRS_SET_SPACE(attrs, QSMMU_SPACE_NONSECURE);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ return attrs;
+}
+
+uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx)
+{
+ uint32_t build_result;
+
+ /* Build page tables and SMMU structures first */
+ build_result = qsmmu_build_translation(
+ ctx->qts, ctx->config.trans_mode,
+ ctx->tx_space, ctx->sid);
+ if (build_result != 0) {
+ g_test_message("Build failed: mode=%u sid=%u status=0x%x",
+ ctx->config.trans_mode, ctx->sid, build_result);
+ ctx->trans_status = build_result;
+ return ctx->trans_status;
+ }
+
+ /* Program SMMU registers for the appropriate security space */
+ qsmmu_program_regs(ctx->qts, ctx->smmu_base, ctx->tx_space);
+
+ ctx->trans_status = 0;
+ return ctx->trans_status;
+}
+
+uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx)
+{
+ uint32_t result, attrs_val;
+
+ /* Program DMA parameters */
+ qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_LO,
+ (uint32_t)ctx->config.dma_iova);
+ qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_HI,
+ (uint32_t)(ctx->config.dma_iova >> 32));
+ qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_LEN,
+ ctx->config.dma_len);
+
+ /* Build and write DMA attributes based on device security state. */
+ attrs_val = qsmmu_build_dma_attrs(QSMMU_SPACE_NONSECURE);
+ qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_ATTRS, attrs_val);
+
+ /* Flip status */
+ /* Arm iommu-testdev so the next read triggers DMA */
+ qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_DBELL, ITD_DMA_DBELL_ARM);
+
+ /* Trigger DMA */
+ qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_TRIGGERING);
+
+ /* Poll for DMA completion */
+ for (int i = 0; i < 1000; i++) {
+ result = qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_RESULT);
+ if (result != ITD_DMA_RESULT_BUSY) {
+ ctx->dma_result = result;
+ break;
+ }
+ g_usleep(1000);
+ }
+
+ /* Fallback for timeout */
+ if (ctx->dma_result == ITD_DMA_RESULT_BUSY) {
+ ctx->dma_result = ITD_DMA_ERR_TX_FAIL;
+ }
+
+ return ctx->dma_result;
+}
+
+bool qsmmu_validate_test_result(QSMMUTestContext *ctx)
+{
+ uint32_t expected = qsmmu_expected_dma_result(ctx);
+ g_test_message("-> Validating result: expected=0x%x actual=0x%x",
+ expected, ctx->dma_result);
+ return (ctx->dma_result == expected);
+}
+
+QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid)
+{
+ switch (sec_sid) {
+ case QSMMU_SEC_SID_NONSECURE:
+ return QSMMU_SPACE_NONSECURE;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+uint64_t qsmmu_space_offset(QSMMUSpace sp)
+{
+ switch (sp) {
+ case QSMMU_SPACE_NONSECURE:
+ return QSMMU_SPACE_OFFS_NS;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void qsmmu_single_translation(QSMMUTestContext *ctx)
+{
+ uint32_t config_result;
+ uint32_t dma_result;
+
+ /* Configure SMMU translation */
+ config_result = qsmmu_setup_and_enable_translation(ctx);
+ g_assert_cmpuint(config_result, ==, 0);
+
+ /* Trigger DMA operation */
+ dma_result = qsmmu_trigger_dma(ctx);
+ if (dma_result != 0) {
+ g_test_message("DMA failed: mode=%u result=0x%x",
+ ctx->config.trans_mode, dma_result);
+ } else {
+ g_test_message("-> DMA succeeded: mode=%u", ctx->config.trans_mode);
+ }
+
+ /* Validate test result */
+ g_assert_true(qsmmu_validate_test_result(ctx));
+}
+
+void qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev,
+ QPCIBar bar, uint64_t smmu_base,
+ const QSMMUTestConfig *cfg)
+{
+ QSMMUTestContext ctx = {
+ .qts = qts,
+ .dev = dev,
+ .bar = bar,
+ .smmu_base = smmu_base,
+ .config = *cfg,
+ .sid = dev->devfn,
+ .tx_space = qsmmu_sec_sid_to_space(cfg->sec_sid),
+ };
+
+ qtest_memset(qts, cfg->dma_iova, 0x00, cfg->dma_len);
+ qsmmu_single_translation(&ctx);
+}
+
+uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode,
+ QSMMUSpace tx_space, uint32_t sid)
+{
+ uint64_t ste_addr, ste_addr_real, cd_addr_real;
+ uint64_t cd_ttb, vttb, vttb_real;
+ uint8_t nscfg0, nscfg1;
+ QSMMUSpace build_space;
+ size_t ste_cd_entry_bytes = sizeof(STE);
+ STE ste;
+ CD cd;
+
+ build_space = tx_space;
+ if (build_space != QSMMU_SPACE_NONSECURE) {
+ return 0xdeadbeafu;
+ }
+
+ /* Build STE image */
+ memset(&ste, 0, sizeof(ste));
+ switch (mode) {
+ case QSMMU_TM_S1_ONLY:
+ STE_SET_CONFIG(&ste, 0x5);
+ break;
+ case QSMMU_TM_S2_ONLY:
+ STE_SET_CONFIG(&ste, 0x6);
+ break;
+ case QSMMU_TM_NESTED:
+ default:
+ STE_SET_CONFIG(&ste, 0x7);
+ break;
+ }
+
+ STE_SET_VALID(&ste, 1);
+ STE_SET_S2T0SZ(&ste, QSMMU_STE_S2T0SZ_VAL);
+ STE_SET_S2SL0(&ste, 0x2);
+ STE_SET_S2TG(&ste, 0);
+ STE_SET_S2PS(&ste, 0x5);
+ STE_SET_S2AA64(&ste, 1);
+ STE_SET_S2ENDI(&ste, 0);
+ STE_SET_S2AFFD(&ste, 0);
+
+ /*
+ * The consistent policy also extends to pointer fetches. For cases that
+ * require reading STE.S1ContextPtr or STE.S2TTB, we still follow the same
+ * policy:
+ * - The PA space security attribute of the address pointed to
+ * (e.g., the CD or S2L1 table) must also match the input 'SEC_SID'.
+ */
+ cd_addr_real = qsmmu_apply_space_offs(build_space, QSMMU_CD_GPA);
+ STE_SET_CTXPTR(&ste, cd_addr_real);
+
+ vttb = QSMMU_VTTB;
+ vttb_real = qsmmu_apply_space_offs(build_space, vttb);
+ STE_SET_S2TTB(&ste, vttb_real);
+
+ ste_addr = sid * ste_cd_entry_bytes + QSMMU_STR_TAB_BASE;
+ ste_addr_real = qsmmu_apply_space_offs(build_space, ste_addr);
+
+ /* Write STE to memory */
+ for (int i = 0; i < ARRAY_SIZE(ste.word); i++) {
+ qtest_writel(qts, ste_addr_real + i * 4, ste.word[i]);
+ }
+
+ switch (tx_space) {
+ case QSMMU_SPACE_NONSECURE:
+ nscfg0 = 0x1;
+ nscfg1 = 0x1;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ /* Build CD image for S1 path if needed */
+ if (mode != QSMMU_TM_S2_ONLY) {
+ memset(&cd, 0, sizeof(cd));
+
+ CD_SET_ASID(&cd, 0x1e20);
+ CD_SET_AARCH64(&cd, 1);
+ CD_SET_VALID(&cd, 1);
+ CD_SET_A(&cd, 1);
+ CD_SET_S(&cd, 0);
+ CD_SET_HD(&cd, 0);
+ CD_SET_HA(&cd, 0);
+ CD_SET_IPS(&cd, 0x4);
+ CD_SET_TBI(&cd, 0x0);
+ CD_SET_AFFD(&cd, 0x0);
+ CD_SET_EPD(&cd, 0, 0x0);
+ CD_SET_EPD(&cd, 1, 0x1);
+ CD_SET_TSZ(&cd, 0, 0x10);
+ CD_SET_TG(&cd, 0, 0x0);
+ CD_SET_ENDI(&cd, 0x0);
+
+ CD_SET_NSCFG(&cd, 0, nscfg0);
+ CD_SET_NSCFG(&cd, 1, nscfg1);
+ CD_SET_R(&cd, 0x1);
+ cd_ttb = vttb_real;
+ CD_SET_TTB(&cd, 0, cd_ttb);
+
+ for (int i = 0; i < ARRAY_SIZE(cd.word); i++) {
+ /* TODO: Maybe need more work to write to secure RAM in future */
+ qtest_writel(qts, cd_addr_real + i * 4, cd.word[i]);
+ g_assert_cmpint(qtest_readl(qts, cd_addr_real + i * 4), ==,
+ cd.word[i]);
+ }
+ }
+
+ qsmmu_setup_translation_tables(qts, QSMMU_IOVA, build_space,
+ false, mode);
+ /* Nested extras: CD S2 tables */
+ if (mode == QSMMU_TM_NESTED) {
+ /*
+ * Extra Stage 2 page tables is needed if
+ * SMMUTranslationClass == SMMU_CLASS_CD
+ * as smmuv3_do_translate would translate an IPA of the CD to the final
+ * output CD after a Stage 2 translation.
+ */
+ qsmmu_setup_translation_tables(qts, cd_addr_real, build_space,
+ true, mode);
+ }
+
+ return 0;
+}
+
+uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp)
+{
+ switch (sp) {
+ case QSMMU_SPACE_NONSECURE:
+ return base;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp)
+{
+ uint64_t cmdq_base, eventq_base, strtab_base;
+
+ qtest_writel(qts, bank_base + A_GBPA, 0x80000000); /* UPDATE */
+ qtest_writel(qts, bank_base + A_CR0, 0x0); /* Disable */
+ qtest_writel(qts, bank_base + A_CR1, 0x0d75); /* Config */
+
+ /* CMDQ_BASE: add address-space offset*/
+ cmdq_base = qsmmu_apply_space_offs(sp, QSMMU_CMDQ_BASE_ADDR);
+ cmdq_base |= 0x0a; /* Size and valid bits */
+ qtest_writeq(qts, bank_base + A_CMDQ_BASE, cmdq_base);
+
+ qtest_writel(qts, bank_base + A_CMDQ_CONS, 0x0);
+ qtest_writel(qts, bank_base + A_CMDQ_PROD, 0x0);
+
+ /* EVENTQ_BASE: add address-space offset */
+ eventq_base = qsmmu_apply_space_offs(sp, QSMMU_EVENTQ_BASE_ADDR);
+ eventq_base |= 0x0a; /* Size and valid bits */
+ qtest_writeq(qts, bank_base + A_EVENTQ_BASE, eventq_base);
+
+ qtest_writel(qts, bank_base + A_EVENTQ_PROD, 0x0);
+ qtest_writel(qts, bank_base + A_EVENTQ_CONS, 0x0);
+
+ /* STRTAB_BASE_CFG: linear stream table, LOG2SIZE=5 */
+ qtest_writel(qts, bank_base + A_STRTAB_BASE_CFG, 0x5);
+
+ /* STRTAB_BASE: add address-space offset */
+ strtab_base = qsmmu_apply_space_offs(sp, QSMMU_STR_TAB_BASE);
+ qtest_writeq(qts, bank_base + A_STRTAB_BASE, strtab_base);
+
+ /* CR0: Enable SMMU with appropriate flags */
+ qtest_writel(qts, bank_base + A_CR0, 0xd);
+}
+
+void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace space)
+{
+ uint64_t sp_base;
+ /* Always program Non-Secure bank first */
+ uint64_t ns_base = qsmmu_bank_base(smmu_base, QSMMU_SPACE_NONSECURE);
+ qsmmu_program_bank(qts, ns_base, QSMMU_SPACE_NONSECURE);
+
+ /* Program the requested space if different from Non-Secure */
+ sp_base = qsmmu_bank_base(smmu_base, space);
+ if (sp_base != ns_base) {
+ qsmmu_program_bank(qts, sp_base, space);
+ }
+}
+
+static uint32_t qsmmu_get_table_index(uint64_t addr, int level)
+{
+ switch (level) {
+ case 0:
+ return (addr >> 39) & 0x1ff;
+ case 1:
+ return (addr >> 30) & 0x1ff;
+ case 2:
+ return (addr >> 21) & 0x1ff;
+ case 3:
+ return (addr >> 12) & 0x1ff;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static uint64_t qsmmu_get_table_addr(uint64_t base, int level, uint64_t iova)
+{
+ uint32_t index = qsmmu_get_table_index(iova, level);
+ return (base & QSMMU_PTE_MASK) + (index * 8);
+}
+
+/*
+ * qsmmu_get_pte_attrs - Calculate the S1 leaf PTE value
+ *
+ * IOMMU need to set different attributes for PTEs based on the translation mode
+ */
+static uint64_t qsmmu_get_pte_attrs(QSMMUTransMode mode, bool is_leaf,
+ QSMMUSpace space)
+{
+ uint64_t rw_mask = QSMMU_LEAF_PTE_RW_MASK;
+ uint64_t ro_mask = QSMMU_LEAF_PTE_RO_MASK;
+ uint64_t non_leaf_mask = QSMMU_NON_LEAF_PTE_MASK;
+
+ switch (space) {
+ case QSMMU_SPACE_NONSECURE:
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ if (!is_leaf) {
+ return non_leaf_mask;
+ }
+
+ /* For leaf PTE */
+ if (mode == QSMMU_TM_NESTED || mode == QSMMU_TM_S1_ONLY) {
+ return rw_mask;
+ }
+
+ return ro_mask;
+}
+
+/*
+ * qsmmu_setup_s2_walk_for_ipa - Setup Stage 2 page table walk for an IPA
+ *
+ * @qts: QTest state handle
+ * @space: Security space
+ * @ipa: Intermediate Physical Address to translate
+ * @s2_vttb: Stage 2 VTTB (page table base)
+ * @mode: Translation mode
+ * @is_final: Whether this is the final S2 walk (not nested within S1)
+ *
+ * Calculates and writes a 4-level Stage 2 page table walk for the given IPA.
+ * This function dynamically generates and writes all page table entries
+ * (L0-L3) to guest memory based on the input IPA and configuration.
+ */
+static void qsmmu_setup_s2_walk_for_ipa(QTestState *qts,
+ QSMMUSpace space,
+ uint64_t ipa,
+ uint64_t s2_vttb,
+ QSMMUTransMode mode,
+ bool is_final)
+{
+ uint64_t all_s2_l0_pte_val;
+ uint64_t all_s2_l1_pte_val;
+ uint64_t all_s2_l2_pte_val;
+ uint64_t all_s2_l3_pte_val;
+ uint64_t s2_l0_addr, s2_l1_addr, s2_l2_addr, s2_l3_addr;
+
+ /* Shared intermediate PTE values for all S2 walks */
+ all_s2_l0_pte_val = qsmmu_apply_space_offs(
+ space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
+ all_s2_l1_pte_val = qsmmu_apply_space_offs(
+ space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
+ all_s2_l2_pte_val = qsmmu_apply_space_offs(
+ space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
+
+ /* Stage 2 Level 0 */
+ s2_l0_addr = qsmmu_get_table_addr(s2_vttb, 0, ipa);
+ qtest_writeq(qts, s2_l0_addr, all_s2_l0_pte_val);
+
+ /* Stage 2 Level 1 */
+ s2_l1_addr = qsmmu_get_table_addr(all_s2_l0_pte_val, 1, ipa);
+ qtest_writeq(qts, s2_l1_addr, all_s2_l1_pte_val);
+
+ /* Stage 2 Level 2 */
+ s2_l2_addr = qsmmu_get_table_addr(all_s2_l1_pte_val, 2, ipa);
+ qtest_writeq(qts, s2_l2_addr, all_s2_l2_pte_val);
+
+ /* Stage 2 Level 3 (leaf) */
+ s2_l3_addr = qsmmu_get_table_addr(all_s2_l2_pte_val, 3, ipa);
+
+ /*
+ * Stage 2 L3 PTE attributes depend on the context:
+ * - For nested S1 table address translations (!is_final):
+ * Use LEAF attrs (0x763) because these PTEs map S1 table pages directly
+ * - For final S2 walk (is_final):
+ * Use TABLE attrs (0x7e3) for the final IPA→PA mapping
+ */
+ if (!is_final) {
+ all_s2_l3_pte_val =
+ (ipa & QSMMU_PTE_MASK) |
+ qsmmu_get_pte_attrs(QSMMU_TM_NESTED, true, space);
+ } else {
+ all_s2_l3_pte_val =
+ (ipa & QSMMU_PTE_MASK) |
+ qsmmu_get_pte_attrs(QSMMU_TM_S2_ONLY, true, space);
+ }
+
+ qtest_writeq(qts, s2_l3_addr, all_s2_l3_pte_val);
+}
+
+/*
+ * qsmmu_setup_s1_level_with_nested_s2 - Setup S1 level with nested S2 walk
+ *
+ * @qts: QTest state handle
+ * @space: Security space
+ * @s1_level: Stage 1 level (0-3)
+ * @s1_pte_addr: Stage 1 PTE address (as IPA)
+ * @s1_pte_val: Stage 1 PTE value to write
+ * @s2_vttb: Stage 2 VTTB for nested translation
+ * @mode: Translation mode
+ *
+ * For nested translation, each S1 table access requires a full S2 walk
+ * to translate the S1 table's IPA to PA. This function performs the nested
+ * S2 walk and writes the S1 PTE value to guest memory.
+ */
+static void qsmmu_setup_s1_level_with_nested_s2(QTestState *qts,
+ QSMMUSpace space,
+ int s1_level,
+ uint64_t s1_pte_addr,
+ uint64_t s1_pte_val,
+ uint64_t s2_vttb,
+ QSMMUTransMode mode)
+{
+ /*
+ * Perform nested S2 walk to translate S1 table IPA to PA.
+ * This is always needed for S1_ONLY/S2_ONLY/NESTED modes because:
+ * - S1_ONLY: Needs S2 tables for "IPA as PA" mapping (for testing)
+ * - S2_ONLY: Needs S2 tables for direct translation
+ * - NESTED: Needs S2 tables for nested translation
+ */
+ qsmmu_setup_s2_walk_for_ipa(qts, space, s1_pte_addr,
+ s2_vttb, mode, false);
+
+ /* Write the S1 PTE value */
+ qtest_writeq(qts, s1_pte_addr, s1_pte_val);
+}
+
+/*
+ * qsmmu_setup_translation_tables - Setup SMMU translation tables
+ *
+ * The 'SEC_SID' represents the input security state of the device/transaction,
+ * whether it's a static Secure state or a dynamically-switched Realm state.
+ * SEC_SID has been converted to the corresponding Security Space (QSMMUSpace)
+ * before calling this function.
+ *
+ * In a real SMMU translation, this input security state does not unilaterally
+ * determine the output Physical Address (PA) space. The output PA space is
+ * ultimately determined by attributes encountered during the page table walk,
+ * such as NSCFG and NSTable.
+ *
+ * However, for the specific context of testing the SMMU with the iommu-testdev,
+ * and to simplify the future support for Secure and Realm states, we adopt a
+ * consistent policy:
+ *
+ * - We always ensure that the page table attributes (e.g., nscfg, nstable)
+ * *match* the input 'SEC_SID' of the test case.
+ *
+ * For example: If 'SEC_SID' is Non-Secure, the corresponding nscfg and nstable
+ * attributes in the translation tables will always be set to 1.
+ *
+ */
+void qsmmu_setup_translation_tables(QTestState *qts,
+ uint64_t iova,
+ QSMMUSpace space,
+ bool is_cd,
+ QSMMUTransMode mode)
+{
+ uint64_t all_s2_l0_pte_val, all_s2_l1_pte_val, all_s2_l2_pte_val;
+ uint64_t s1_vttb, s2_vttb, s1_leaf_pte_val;
+ uint64_t l0_addr, l1_addr, l2_addr, l3_addr;
+
+ g_test_message("Begin of construction: IOVA=0x%" PRIx64
+ " mode=%d is_building_CD=%s ===",
+ iova, mode, is_cd ? "yes" : "no");
+
+ /* Initialize shared S2 PTE values used across all walks */
+ all_s2_l0_pte_val = qsmmu_apply_space_offs(
+ space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
+ all_s2_l1_pte_val = qsmmu_apply_space_offs(
+ space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
+ all_s2_l2_pte_val = qsmmu_apply_space_offs(
+ space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
+
+ /* Both S1 and S2 share the same VTTB base */
+ s1_vttb = qsmmu_apply_space_offs(space, QSMMU_VTTB & QSMMU_PTE_MASK);
+ s2_vttb = s1_vttb;
+
+ if (!is_cd) {
+ /*
+ * Setup Stage 1 page tables with nested Stage 2 walks.
+ * For each S1 level (L0-L3), we need to:
+ * 1. Calculate S1 PTE address (as IPA)
+ * 2. Perform nested S2 walk to translate that IPA to PA
+ * 3. Write the S1 PTE value
+ */
+
+ /* Stage 1 Level 0 */
+ l0_addr = qsmmu_get_table_addr(s1_vttb, 0, iova);
+ qsmmu_setup_s1_level_with_nested_s2(qts, space, 0, l0_addr,
+ all_s2_l0_pte_val, s2_vttb, mode);
+
+ /* Stage 1 Level 1 */
+ l1_addr = qsmmu_get_table_addr(all_s2_l0_pte_val & QSMMU_PTE_MASK,
+ 1, iova);
+ qsmmu_setup_s1_level_with_nested_s2(qts, space, 1, l1_addr,
+ all_s2_l1_pte_val, s2_vttb, mode);
+
+ /* Stage 1 Level 2 */
+ l2_addr = qsmmu_get_table_addr(all_s2_l1_pte_val & QSMMU_PTE_MASK,
+ 2, iova);
+ qsmmu_setup_s1_level_with_nested_s2(qts, space, 2, l2_addr,
+ all_s2_l2_pte_val, s2_vttb, mode);
+
+ /* Stage 1 Level 3 (leaf) */
+ l3_addr = qsmmu_get_table_addr(all_s2_l2_pte_val & QSMMU_PTE_MASK,
+ 3, iova);
+
+ s1_leaf_pte_val = qsmmu_apply_space_offs(
+ space, QSMMU_L3_PTE_VAL | qsmmu_get_pte_attrs(mode, true, space)
+ );
+
+ qsmmu_setup_s1_level_with_nested_s2(qts, space, 3, l3_addr,
+ s1_leaf_pte_val, s2_vttb, mode);
+ } else {
+ /*
+ * For CD address translation, we start directly with the IPA.
+ */
+ s1_leaf_pte_val = iova | qsmmu_get_pte_attrs(QSMMU_TM_NESTED,
+ false, space);
+ }
+
+ /*
+ * Final Stage 2 walk: Translate the result from Stage 1.
+ * - For S1_ONLY: This is skipped in hardware but we set it up for testing
+ * - For S2_ONLY: This is the only walk
+ * - For NESTED: This translates the IPA from S1 to final PA
+ * - For CD address (is_cd=true): This is a table address, use !is_final
+ */
+ qsmmu_setup_s2_walk_for_ipa(qts, space, s1_leaf_pte_val, s2_vttb,
+ mode, !is_cd);
+
+ /* Calculate and log final translated PA */
+ g_test_message("End of construction: PA=0x%llx ===",
+ (s1_leaf_pte_val & QSMMU_PTE_MASK) + (iova & 0xfff));
+}
diff --git a/tests/qtest/libqos/qos-smmuv3.h b/tests/qtest/libqos/qos-smmuv3.h
new file mode 100644
index 0000000000..375778517f
--- /dev/null
+++ b/tests/qtest/libqos/qos-smmuv3.h
@@ -0,0 +1,256 @@
+/*
+ * QOS SMMUv3 Module
+ *
+ * This module provides SMMUv3-specific helper functions for libqos tests,
+ * encapsulating SMMUv3 setup, and assertions.
+ *
+ * Copyright (c) 2025 Phytium Technology
+ *
+ * Author:
+ * Tao Tang <tangtao1634@phytium.com.cn>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef QTEST_LIBQOS_SMMUV3_H
+#define QTEST_LIBQOS_SMMUV3_H
+
+#include "hw/misc/iommu-testdev.h"
+
+/*
+ * SMMU MMIO register base. We hardcode this as base_memmap is 'static const'
+ * in hw/arm/virt.c and not directly accessible here.
+ */
+#define VIRT_SMMU_BASE 0x0000000009050000ull
+
+/* SMMU queue and table base addresses */
+#define QSMMU_CMDQ_BASE_ADDR 0x000000000e16b000ull
+#define QSMMU_EVENTQ_BASE_ADDR 0x000000000e170000ull
+
+/*
+ * Translation tables and descriptors for a mapping of
+ * - IOVA(Stage 1 only)
+ * - IPA (Stage 2 only)
+ * to 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 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 (QSMMU_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.
+ */
+#define QSMMU_IOVA 0x0000008080604567ull
+#define QSMMU_VTTB 0x000000000e4d0000ull
+#define QSMMU_STR_TAB_BASE 0x000000000e179000ull
+#define QSMMU_CD_GPA (QSMMU_STR_TAB_BASE - 0x40ull)
+
+
+#define QSMMU_L0_PTE_VAL 0x000000000e4d1000ull
+#define QSMMU_L1_PTE_VAL 0x000000000e4d2000ull
+#define QSMMU_L2_PTE_VAL 0x000000000e4d3000ull
+#define QSMMU_L3_PTE_VAL 0x000000000ecba000ull
+
+#define QSMMU_NON_LEAF_PTE_MASK 0x8000000000000003ull
+#define QSMMU_LEAF_PTE_RO_MASK 0x04000000000007e3ull
+#define QSMMU_LEAF_PTE_RW_MASK 0x0400000000000763ull
+#define QSMMU_PTE_MASK 0x0000fffffffff000ull
+
+/*
+ * 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 QSMMU_SPACE_OFFS_NS 0x0000000040000000ull
+
+typedef enum QSMMUSecSID {
+ QSMMU_SEC_SID_NONSECURE = 0,
+} QSMMUSecSID;
+
+typedef enum QSMMUSpace {
+ QSMMU_SPACE_NONSECURE = 1,
+} QSMMUSpace;
+
+typedef enum QSMMUTransMode {
+ QSMMU_TM_S1_ONLY = 0,
+ QSMMU_TM_S2_ONLY = 1,
+ QSMMU_TM_NESTED = 2,
+} QSMMUTransMode;
+
+typedef struct QSMMUTestConfig {
+ QSMMUTransMode trans_mode; /* Translation mode (S1, S2, Nested) */
+ QSMMUSecSID sec_sid; /* SEC_SID of test device */
+ uint64_t dma_iova; /* DMA IOVA address for testing */
+ uint32_t dma_len; /* DMA length for testing */
+ uint32_t expected_result; /* Expected DMA result for validation */
+} QSMMUTestConfig;
+
+typedef struct QSMMUTestContext {
+ QTestState *qts; /* QTest state handle */
+ QPCIDevice *dev; /* PCI device handle */
+ QPCIBar bar; /* PCI BAR for MMIO access */
+ QSMMUTestConfig config; /* Test configuration */
+ uint64_t smmu_base; /* SMMU base address */
+ uint32_t trans_status; /* Translation configuration status */
+ uint32_t dma_result; /* DMA operation result */
+ uint32_t sid; /* Stream ID for the test */
+ QSMMUSpace tx_space; /* Cached transaction space */
+} QSMMUTestContext;
+
+/* Convert SEC_SID to corresponding Security Space */
+QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid);
+
+/* Get base offset of the specific Security space */
+uint64_t qsmmu_space_offset(QSMMUSpace sp);
+
+uint32_t qsmmu_build_dma_attrs(QSMMUSpace space);
+
+/*
+ * qsmmu_setup_and_enable_translation - Complete translation setup and enable
+ *
+ * @ctx: Test context containing configuration and device handles
+ *
+ * Returns: Translation status (0 = success, non-zero = error)
+ *
+ * This function performs the complete translation setup sequence:
+ * 1. Builds all required SMMU structures (STE, CD, page tables)
+ * 2. Programs SMMU registers for the appropriate security space
+ * 3. Returns configuration status
+ */
+uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx);
+
+/*
+ * qsmmu_build_translation - Build SMMU translation structures
+ *
+ * @qts: QTest state handle
+ * @mode: Translation mode (S1_ONLY, S2_ONLY, NESTED)
+ * @tx_space: Transaction security space
+ * @sid: Stream ID
+ *
+ * Returns: Build status (0 = success, non-zero = error)
+ *
+ * Constructs all necessary SMMU translation structures in guest memory:
+ * - Stream Table Entry (STE) for the given SID
+ * - Context Descriptor (CD) if Stage 1 translation is involved
+ * - Complete page table hierarchy based on translation mode
+ *
+ * The structures are written to security-space-specific memory regions.
+ */
+uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode,
+ QSMMUSpace tx_space, uint32_t sid);
+
+/*
+ * qsmmu_bank_base - Get SMMU control bank base address
+ *
+ * @base: SMMU base address
+ * @sp: Security space
+ *
+ * Returns: Bank base address for the given security space
+ *
+ * Maps security space to the corresponding SMMU control register bank.
+ * Currently only Non-Secure bank is supported.
+ */
+uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp);
+
+/*
+ * qsmmu_program_bank - Program SMMU control bank registers
+ *
+ * @qts: QTest state handle
+ * @bank_base: SMMU bank base address
+ * @sp: Security space
+ *
+ * Programs a specific SMMU control bank with minimal configuration:
+ * - Global Bypass Attribute (GBPA)
+ * - Control registers (CR0, CR1)
+ * - Command queue (base, producer, consumer)
+ * - Event queue (base, producer, consumer)
+ * - Stream table configuration (base, format)
+ *
+ * Addresses are adjusted based on security space offset.
+ */
+void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp);
+
+/*
+ * qsmmu_program_regs - Program all required SMMU register banks
+ *
+ * @qts: QTest state handle
+ * @smmu_base: SMMU base address
+ * @space: Target security space
+ *
+ * Programs SMMU registers for the requested security space which is called in
+ * qsmmu_setup_and_enable_translation. Always programs Non-Secure bank first,
+ * then the target space if different.
+ */
+void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace space);
+
+uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx);
+
+/* qsmmu_expected_dma_result - Calculate expected DMA result */
+uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx);
+
+/*
+ * qsmmu_validate_test_result - Validate actual VS expected test result
+ *
+ * @ctx: Test context containing actual and expected results
+ *
+ * Returns: true if test passed (actual == expected), false otherwise
+ *
+ * Compares the actual DMA result with the expected result and logs
+ * the comparison for debugging purposes.
+ */
+bool qsmmu_validate_test_result(QSMMUTestContext *ctx);
+
+/*
+ * qsmmu_setup_translation_tables - Setup complete SMMU page table hierarchy
+ *
+ * @qts: QTest state handle
+ * @iova: Input Virtual Address or IPA to translate
+ * @space: Security space (NONSECURE, SECURE, REALM, ROOT)
+ * @is_cd: Whether translating CD address (vs regular IOVA)
+ * @mode: Translation mode (S1_ONLY, S2_ONLY, NESTED)
+ *
+ * This function builds the complete page table structure for translating
+ * the given IOVA through the SMMU. The structure varies based on mode:
+ *
+ * - S1_ONLY: Single Stage 1 walk (IOVA -> PA)
+ * - S2_ONLY: Single Stage 2 walk (IPA -> PA)
+ * - NESTED: Stage 1 walk (IOVA -> IPA) with nested S2 walks for each
+ * S1 table access, plus final S2 walk for the result IPA
+ *
+ * For nested mode, this creates a complex hierarchy:
+ * - 4 Stage 1 levels (L0-L3), each requiring a 4-level Stage 2 walk
+ * - 1 final Stage 2 walk for the resulting IPA
+ *
+ * The function writes all necessary Page Table Entries (PTEs) to guest
+ * memory using qtest_writeq(), setting up the complete translation path
+ * that the SMMU hardware will traverse during DMA operations.
+ */
+void qsmmu_setup_translation_tables(QTestState *qts,
+ uint64_t iova,
+ QSMMUSpace space,
+ bool is_cd,
+ QSMMUTransMode mode);
+
+/* High-level test execution helpers */
+void qsmmu_single_translation(QSMMUTestContext *ctx);
+void qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev,
+ QPCIBar bar, uint64_t smmu_base,
+ const QSMMUTestConfig *cfg);
+
+#endif /* QTEST_LIBQOS_SMMUV3_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library
2025-12-24 3:46 ` [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library Tao Tang
@ 2026-01-15 18:30 ` Eric Auger
2026-01-16 10:00 ` Tao Tang
2026-01-17 10:02 ` Eric Auger
1 sibling, 1 reply; 35+ messages in thread
From: Eric Auger @ 2026-01-15 18:30 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Tao,
On 12/24/25 4:46 AM, Tao Tang wrote:
> Introduce qos-smmuv3, a reusable library for SMMUv3-related qtest
> operations. This module encapsulates common tasks like:
>
> - SMMUv3 initialization (enabling, configuring command/event queues)
> - Stream Table Entry (STE) and Context Descriptor (CD) setup
> - Multi-level page table construction (L0-L3 for 4KB granules)
> - Support for Stage 1, Stage 2, and nested translation modes
> - Could be easily extended to support multi-space testing infrastructure
> (Non-Secure, Secure, Root, Realm)
>
> The library provides high-level abstractions that allow test code to
> focus on IOMMU behavior validation rather than low-level register
> manipulation and page table encoding. Key features include:
>
> - Provide memory allocation for translation structures with proper
> alignment
> - Helper functions to build valid STEs/CDs for different translation
> scenarios
> - Page table walkers that handle address offset calculations per
> security space
>
> This infrastructure is designed to be used by iommu-testdev-based tests
> and future SMMUv3 test suites, reducing code duplication and improving
> test maintainability.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> Reviewed-by: Fabiano Rosas <farosas@suse.de>
> ---
> MAINTAINERS | 6 +
> tests/qtest/libqos/meson.build | 3 +
> tests/qtest/libqos/qos-smmuv3.c | 633 ++++++++++++++++++++++++++++++++
> tests/qtest/libqos/qos-smmuv3.h | 256 +++++++++++++
> 4 files changed, 898 insertions(+)
> create mode 100644 tests/qtest/libqos/qos-smmuv3.c
> create mode 100644 tests/qtest/libqos/qos-smmuv3.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3c88ed5dc4..68ab713429 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3546,6 +3546,12 @@ F: docs/devel/testing/qtest.rst
> X: tests/qtest/bios-tables-test*
> X: tests/qtest/migration-*
>
> +QTest SMMUv3 helpers
> +M: Tao Tang <tangtao1634@phytium.com.cn>
> +L: qemu-arm@nongnu.org
> +S: Maintained
> +F: tests/qtest/libqos/qos-smmuv3*
> +
> Device Fuzzing
> M: Alexander Bulekov <alxndr@bu.edu>
> R: Paolo Bonzini <pbonzini@redhat.com>
> diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
> index 1ddaf7b095..8d6758ec2b 100644
> --- a/tests/qtest/libqos/meson.build
> +++ b/tests/qtest/libqos/meson.build
> @@ -60,6 +60,9 @@ libqos_srcs = files(
> 'x86_64_pc-machine.c',
> 'riscv-virt-machine.c',
> 'loongarch-virt-machine.c',
> +
> + # SMMU:
> + 'qos-smmuv3.c',
> )
>
> if have_virtfs
> diff --git a/tests/qtest/libqos/qos-smmuv3.c b/tests/qtest/libqos/qos-smmuv3.c
> new file mode 100644
> index 0000000000..a48abb186b
> --- /dev/null
> +++ b/tests/qtest/libqos/qos-smmuv3.c
> @@ -0,0 +1,633 @@
> +/*
> + * QOS SMMUv3 Module
> + *
> + * This module provides SMMUv3-specific helper functions for libqos tests,
> + * encapsulating SMMUv3 setup, and assertions.
> + *
> + * 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 "hw/arm/smmuv3-common.h"
In file included from ../tests/qtest/libqos/qos-smmuv3.c:16:
/home/augere/UPSTREAM/qemu/include/hw/arm/smmuv3-common.h:14:10: fatal
error: hw/registerfields.h: No such file or directory
14 | #include "hw/registerfields.h"
| ^~~~~~~~~~~~~~~~~~~~~
This needs a rebase after the move to header files I am afraid
> +#include "tests/qtest/libqos/pci.h"
> +#include "hw/misc/iommu-testdev.h"
> +#include "qos-smmuv3.h"
> +
> +#define QSMMU_STE_S2T0SZ_VAL 0x14
> +
> +/* Apply space offset to address */
> +static inline uint64_t qsmmu_apply_space_offs(QSMMUSpace sp,
> + uint64_t address)
> +{
> + return address + qsmmu_space_offset(sp);
> +}
> +
> +uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx)
> +{
> + /* Currently only non-secure space is supported. */
> + if (ctx->tx_space != QSMMU_SPACE_NONSECURE) {
> + return ITD_DMA_ERR_TX_FAIL;
> + }
> + return ctx->config.expected_result;
> +}
> +
> +uint32_t qsmmu_build_dma_attrs(QSMMUSpace space)
> +{
> + uint32_t attrs = 0;
> + switch (space) {
> + case QSMMU_SPACE_NONSECURE:
> + /* Non-secure: secure=0, space=1 */
> + attrs = ITD_ATTRS_SET_SECURE(attrs, 0);
> + attrs = ITD_ATTRS_SET_SPACE(attrs, QSMMU_SPACE_NONSECURE);
> + break;
> + default:
> + g_assert_not_reached();
> + }
> +
> + return attrs;
> +}
> +
> +uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx)
> +{
> + uint32_t build_result;
> +
> + /* Build page tables and SMMU structures first */
> + build_result = qsmmu_build_translation(
> + ctx->qts, ctx->config.trans_mode,
> + ctx->tx_space, ctx->sid);
> + if (build_result != 0) {
> + g_test_message("Build failed: mode=%u sid=%u status=0x%x",
> + ctx->config.trans_mode, ctx->sid, build_result);
> + ctx->trans_status = build_result;
> + return ctx->trans_status;
> + }
> +
> + /* Program SMMU registers for the appropriate security space */
> + qsmmu_program_regs(ctx->qts, ctx->smmu_base, ctx->tx_space);
> +
> + ctx->trans_status = 0;
> + return ctx->trans_status;
> +}
> +
> +uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx)
> +{
> + uint32_t result, attrs_val;
> +
> + /* Program DMA parameters */
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_LO,
> + (uint32_t)ctx->config.dma_iova);
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_HI,
> + (uint32_t)(ctx->config.dma_iova >> 32));
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_LEN,
> + ctx->config.dma_len);
> +
> + /* Build and write DMA attributes based on device security state. */
> + attrs_val = qsmmu_build_dma_attrs(QSMMU_SPACE_NONSECURE);
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_ATTRS, attrs_val);
> +
> + /* Flip status */
> + /* Arm iommu-testdev so the next read triggers DMA */
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_DBELL, ITD_DMA_DBELL_ARM);
> +
> + /* Trigger DMA */
> + qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_TRIGGERING);
> +
> + /* Poll for DMA completion */
> + for (int i = 0; i < 1000; i++) {
> + result = qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_RESULT);
> + if (result != ITD_DMA_RESULT_BUSY) {
> + ctx->dma_result = result;
> + break;
> + }
> + g_usleep(1000);
> + }
> +
> + /* Fallback for timeout */
> + if (ctx->dma_result == ITD_DMA_RESULT_BUSY) {
> + ctx->dma_result = ITD_DMA_ERR_TX_FAIL;
> + }
> +
> + return ctx->dma_result;
> +}
> +
> +bool qsmmu_validate_test_result(QSMMUTestContext *ctx)
> +{
> + uint32_t expected = qsmmu_expected_dma_result(ctx);
> + g_test_message("-> Validating result: expected=0x%x actual=0x%x",
> + expected, ctx->dma_result);
> + return (ctx->dma_result == expected);
> +}
> +
> +QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid)
> +{
> + switch (sec_sid) {
> + case QSMMU_SEC_SID_NONSECURE:
> + return QSMMU_SPACE_NONSECURE;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +uint64_t qsmmu_space_offset(QSMMUSpace sp)
> +{
> + switch (sp) {
> + case QSMMU_SPACE_NONSECURE:
> + return QSMMU_SPACE_OFFS_NS;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +void qsmmu_single_translation(QSMMUTestContext *ctx)
> +{
> + uint32_t config_result;
> + uint32_t dma_result;
> +
> + /* Configure SMMU translation */
> + config_result = qsmmu_setup_and_enable_translation(ctx);
> + g_assert_cmpuint(config_result, ==, 0);
> +
> + /* Trigger DMA operation */
> + dma_result = qsmmu_trigger_dma(ctx);
> + if (dma_result != 0) {
> + g_test_message("DMA failed: mode=%u result=0x%x",
> + ctx->config.trans_mode, dma_result);
> + } else {
> + g_test_message("-> DMA succeeded: mode=%u", ctx->config.trans_mode);
> + }
> +
> + /* Validate test result */
> + g_assert_true(qsmmu_validate_test_result(ctx));
> +}
> +
> +void qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev,
> + QPCIBar bar, uint64_t smmu_base,
> + const QSMMUTestConfig *cfg)
> +{
> + QSMMUTestContext ctx = {
> + .qts = qts,
> + .dev = dev,
> + .bar = bar,
> + .smmu_base = smmu_base,
> + .config = *cfg,
> + .sid = dev->devfn,
> + .tx_space = qsmmu_sec_sid_to_space(cfg->sec_sid),
> + };
> +
> + qtest_memset(qts, cfg->dma_iova, 0x00, cfg->dma_len);
> + qsmmu_single_translation(&ctx);
> +}
> +
> +uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode,
> + QSMMUSpace tx_space, uint32_t sid)
> +{
> + uint64_t ste_addr, ste_addr_real, cd_addr_real;
> + uint64_t cd_ttb, vttb, vttb_real;
> + uint8_t nscfg0, nscfg1;
> + QSMMUSpace build_space;
> + size_t ste_cd_entry_bytes = sizeof(STE);
> + STE ste;
> + CD cd;
> +
> + build_space = tx_space;
> + if (build_space != QSMMU_SPACE_NONSECURE) {
> + return 0xdeadbeafu;
> + }
> +
> + /* Build STE image */
> + memset(&ste, 0, sizeof(ste));
> + switch (mode) {
> + case QSMMU_TM_S1_ONLY:
> + STE_SET_CONFIG(&ste, 0x5);
> + break;
> + case QSMMU_TM_S2_ONLY:
> + STE_SET_CONFIG(&ste, 0x6);
> + break;
> + case QSMMU_TM_NESTED:
> + default:
> + STE_SET_CONFIG(&ste, 0x7);
> + break;
> + }
> +
> + STE_SET_VALID(&ste, 1);
> + STE_SET_S2T0SZ(&ste, QSMMU_STE_S2T0SZ_VAL);
> + STE_SET_S2SL0(&ste, 0x2);
> + STE_SET_S2TG(&ste, 0);
> + STE_SET_S2PS(&ste, 0x5);
> + STE_SET_S2AA64(&ste, 1);
> + STE_SET_S2ENDI(&ste, 0);
> + STE_SET_S2AFFD(&ste, 0);
> +
> + /*
> + * The consistent policy also extends to pointer fetches. For cases that
> + * require reading STE.S1ContextPtr or STE.S2TTB, we still follow the same
> + * policy:
> + * - The PA space security attribute of the address pointed to
> + * (e.g., the CD or S2L1 table) must also match the input 'SEC_SID'.
> + */
> + cd_addr_real = qsmmu_apply_space_offs(build_space, QSMMU_CD_GPA);
> + STE_SET_CTXPTR(&ste, cd_addr_real);
> +
> + vttb = QSMMU_VTTB;
> + vttb_real = qsmmu_apply_space_offs(build_space, vttb);
> + STE_SET_S2TTB(&ste, vttb_real);
> +
> + ste_addr = sid * ste_cd_entry_bytes + QSMMU_STR_TAB_BASE;
> + ste_addr_real = qsmmu_apply_space_offs(build_space, ste_addr);
> +
> + /* Write STE to memory */
> + for (int i = 0; i < ARRAY_SIZE(ste.word); i++) {
> + qtest_writel(qts, ste_addr_real + i * 4, ste.word[i]);
> + }
> +
> + switch (tx_space) {
> + case QSMMU_SPACE_NONSECURE:
> + nscfg0 = 0x1;
> + nscfg1 = 0x1;
> + break;
> + default:
> + g_assert_not_reached();
> + }
> + /* Build CD image for S1 path if needed */
> + if (mode != QSMMU_TM_S2_ONLY) {
> + memset(&cd, 0, sizeof(cd));
> +
> + CD_SET_ASID(&cd, 0x1e20);
> + CD_SET_AARCH64(&cd, 1);
> + CD_SET_VALID(&cd, 1);
> + CD_SET_A(&cd, 1);
> + CD_SET_S(&cd, 0);
> + CD_SET_HD(&cd, 0);
> + CD_SET_HA(&cd, 0);
> + CD_SET_IPS(&cd, 0x4);
> + CD_SET_TBI(&cd, 0x0);
> + CD_SET_AFFD(&cd, 0x0);
> + CD_SET_EPD(&cd, 0, 0x0);
> + CD_SET_EPD(&cd, 1, 0x1);
> + CD_SET_TSZ(&cd, 0, 0x10);
> + CD_SET_TG(&cd, 0, 0x0);
> + CD_SET_ENDI(&cd, 0x0);
> +
> + CD_SET_NSCFG(&cd, 0, nscfg0);
> + CD_SET_NSCFG(&cd, 1, nscfg1);
> + CD_SET_R(&cd, 0x1);
> + cd_ttb = vttb_real;
> + CD_SET_TTB(&cd, 0, cd_ttb);
> +
> + for (int i = 0; i < ARRAY_SIZE(cd.word); i++) {
> + /* TODO: Maybe need more work to write to secure RAM in future */
> + qtest_writel(qts, cd_addr_real + i * 4, cd.word[i]);
> + g_assert_cmpint(qtest_readl(qts, cd_addr_real + i * 4), ==,
> + cd.word[i]);
> + }
> + }
> +
> + qsmmu_setup_translation_tables(qts, QSMMU_IOVA, build_space,
> + false, mode);
> + /* Nested extras: CD S2 tables */
> + if (mode == QSMMU_TM_NESTED) {
> + /*
> + * Extra Stage 2 page tables is needed if
> + * SMMUTranslationClass == SMMU_CLASS_CD
> + * as smmuv3_do_translate would translate an IPA of the CD to the final
> + * output CD after a Stage 2 translation.
> + */
> + qsmmu_setup_translation_tables(qts, cd_addr_real, build_space,
> + true, mode);
> + }
> +
> + return 0;
> +}
> +
> +uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp)
> +{
> + switch (sp) {
> + case QSMMU_SPACE_NONSECURE:
> + return base;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp)
> +{
> + uint64_t cmdq_base, eventq_base, strtab_base;
> +
> + qtest_writel(qts, bank_base + A_GBPA, 0x80000000); /* UPDATE */
> + qtest_writel(qts, bank_base + A_CR0, 0x0); /* Disable */
> + qtest_writel(qts, bank_base + A_CR1, 0x0d75); /* Config */
> +
> + /* CMDQ_BASE: add address-space offset*/
> + cmdq_base = qsmmu_apply_space_offs(sp, QSMMU_CMDQ_BASE_ADDR);
> + cmdq_base |= 0x0a; /* Size and valid bits */
> + qtest_writeq(qts, bank_base + A_CMDQ_BASE, cmdq_base);
> +
> + qtest_writel(qts, bank_base + A_CMDQ_CONS, 0x0);
> + qtest_writel(qts, bank_base + A_CMDQ_PROD, 0x0);
> +
> + /* EVENTQ_BASE: add address-space offset */
> + eventq_base = qsmmu_apply_space_offs(sp, QSMMU_EVENTQ_BASE_ADDR);
> + eventq_base |= 0x0a; /* Size and valid bits */
> + qtest_writeq(qts, bank_base + A_EVENTQ_BASE, eventq_base);
> +
> + qtest_writel(qts, bank_base + A_EVENTQ_PROD, 0x0);
> + qtest_writel(qts, bank_base + A_EVENTQ_CONS, 0x0);
> +
> + /* STRTAB_BASE_CFG: linear stream table, LOG2SIZE=5 */
> + qtest_writel(qts, bank_base + A_STRTAB_BASE_CFG, 0x5);
> +
> + /* STRTAB_BASE: add address-space offset */
> + strtab_base = qsmmu_apply_space_offs(sp, QSMMU_STR_TAB_BASE);
> + qtest_writeq(qts, bank_base + A_STRTAB_BASE, strtab_base);
> +
> + /* CR0: Enable SMMU with appropriate flags */
> + qtest_writel(qts, bank_base + A_CR0, 0xd);
> +}
> +
> +void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace space)
> +{
> + uint64_t sp_base;
> + /* Always program Non-Secure bank first */
> + uint64_t ns_base = qsmmu_bank_base(smmu_base, QSMMU_SPACE_NONSECURE);
> + qsmmu_program_bank(qts, ns_base, QSMMU_SPACE_NONSECURE);
> +
> + /* Program the requested space if different from Non-Secure */
> + sp_base = qsmmu_bank_base(smmu_base, space);
> + if (sp_base != ns_base) {
> + qsmmu_program_bank(qts, sp_base, space);
> + }
> +}
> +
> +static uint32_t qsmmu_get_table_index(uint64_t addr, int level)
> +{
> + switch (level) {
> + case 0:
> + return (addr >> 39) & 0x1ff;
> + case 1:
> + return (addr >> 30) & 0x1ff;
> + case 2:
> + return (addr >> 21) & 0x1ff;
> + case 3:
> + return (addr >> 12) & 0x1ff;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +static uint64_t qsmmu_get_table_addr(uint64_t base, int level, uint64_t iova)
> +{
> + uint32_t index = qsmmu_get_table_index(iova, level);
> + return (base & QSMMU_PTE_MASK) + (index * 8);
> +}
> +
> +/*
> + * qsmmu_get_pte_attrs - Calculate the S1 leaf PTE value
> + *
> + * IOMMU need to set different attributes for PTEs based on the translation mode
> + */
> +static uint64_t qsmmu_get_pte_attrs(QSMMUTransMode mode, bool is_leaf,
> + QSMMUSpace space)
> +{
> + uint64_t rw_mask = QSMMU_LEAF_PTE_RW_MASK;
> + uint64_t ro_mask = QSMMU_LEAF_PTE_RO_MASK;
> + uint64_t non_leaf_mask = QSMMU_NON_LEAF_PTE_MASK;
> +
> + switch (space) {
> + case QSMMU_SPACE_NONSECURE:
> + break;
> + default:
> + g_assert_not_reached();
> + }
> +
> + if (!is_leaf) {
> + return non_leaf_mask;
> + }
> +
> + /* For leaf PTE */
> + if (mode == QSMMU_TM_NESTED || mode == QSMMU_TM_S1_ONLY) {
> + return rw_mask;
> + }
> +
> + return ro_mask;
> +}
> +
> +/*
> + * qsmmu_setup_s2_walk_for_ipa - Setup Stage 2 page table walk for an IPA
> + *
> + * @qts: QTest state handle
> + * @space: Security space
> + * @ipa: Intermediate Physical Address to translate
> + * @s2_vttb: Stage 2 VTTB (page table base)
> + * @mode: Translation mode
> + * @is_final: Whether this is the final S2 walk (not nested within S1)
> + *
> + * Calculates and writes a 4-level Stage 2 page table walk for the given IPA.
> + * This function dynamically generates and writes all page table entries
> + * (L0-L3) to guest memory based on the input IPA and configuration.
> + */
> +static void qsmmu_setup_s2_walk_for_ipa(QTestState *qts,
> + QSMMUSpace space,
> + uint64_t ipa,
> + uint64_t s2_vttb,
> + QSMMUTransMode mode,
> + bool is_final)
> +{
> + uint64_t all_s2_l0_pte_val;
> + uint64_t all_s2_l1_pte_val;
> + uint64_t all_s2_l2_pte_val;
> + uint64_t all_s2_l3_pte_val;
> + uint64_t s2_l0_addr, s2_l1_addr, s2_l2_addr, s2_l3_addr;
> +
> + /* Shared intermediate PTE values for all S2 walks */
> + all_s2_l0_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> + all_s2_l1_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> + all_s2_l2_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> +
> + /* Stage 2 Level 0 */
> + s2_l0_addr = qsmmu_get_table_addr(s2_vttb, 0, ipa);
> + qtest_writeq(qts, s2_l0_addr, all_s2_l0_pte_val);
> +
> + /* Stage 2 Level 1 */
> + s2_l1_addr = qsmmu_get_table_addr(all_s2_l0_pte_val, 1, ipa);
> + qtest_writeq(qts, s2_l1_addr, all_s2_l1_pte_val);
> +
> + /* Stage 2 Level 2 */
> + s2_l2_addr = qsmmu_get_table_addr(all_s2_l1_pte_val, 2, ipa);
> + qtest_writeq(qts, s2_l2_addr, all_s2_l2_pte_val);
> +
> + /* Stage 2 Level 3 (leaf) */
> + s2_l3_addr = qsmmu_get_table_addr(all_s2_l2_pte_val, 3, ipa);
> +
> + /*
> + * Stage 2 L3 PTE attributes depend on the context:
> + * - For nested S1 table address translations (!is_final):
> + * Use LEAF attrs (0x763) because these PTEs map S1 table pages directly
> + * - For final S2 walk (is_final):
> + * Use TABLE attrs (0x7e3) for the final IPA→PA mapping
> + */
> + if (!is_final) {
> + all_s2_l3_pte_val =
> + (ipa & QSMMU_PTE_MASK) |
> + qsmmu_get_pte_attrs(QSMMU_TM_NESTED, true, space);
> + } else {
> + all_s2_l3_pte_val =
> + (ipa & QSMMU_PTE_MASK) |
> + qsmmu_get_pte_attrs(QSMMU_TM_S2_ONLY, true, space);
> + }
> +
> + qtest_writeq(qts, s2_l3_addr, all_s2_l3_pte_val);
> +}
> +
> +/*
> + * qsmmu_setup_s1_level_with_nested_s2 - Setup S1 level with nested S2 walk
> + *
> + * @qts: QTest state handle
> + * @space: Security space
> + * @s1_level: Stage 1 level (0-3)
> + * @s1_pte_addr: Stage 1 PTE address (as IPA)
> + * @s1_pte_val: Stage 1 PTE value to write
> + * @s2_vttb: Stage 2 VTTB for nested translation
> + * @mode: Translation mode
> + *
> + * For nested translation, each S1 table access requires a full S2 walk
> + * to translate the S1 table's IPA to PA. This function performs the nested
> + * S2 walk and writes the S1 PTE value to guest memory.
> + */
> +static void qsmmu_setup_s1_level_with_nested_s2(QTestState *qts,
> + QSMMUSpace space,
> + int s1_level,
> + uint64_t s1_pte_addr,
> + uint64_t s1_pte_val,
> + uint64_t s2_vttb,
> + QSMMUTransMode mode)
> +{
> + /*
> + * Perform nested S2 walk to translate S1 table IPA to PA.
> + * This is always needed for S1_ONLY/S2_ONLY/NESTED modes because:
> + * - S1_ONLY: Needs S2 tables for "IPA as PA" mapping (for testing)
> + * - S2_ONLY: Needs S2 tables for direct translation
> + * - NESTED: Needs S2 tables for nested translation
> + */
> + qsmmu_setup_s2_walk_for_ipa(qts, space, s1_pte_addr,
> + s2_vttb, mode, false);
> +
> + /* Write the S1 PTE value */
> + qtest_writeq(qts, s1_pte_addr, s1_pte_val);
> +}
> +
> +/*
> + * qsmmu_setup_translation_tables - Setup SMMU translation tables
> + *
> + * The 'SEC_SID' represents the input security state of the device/transaction,
> + * whether it's a static Secure state or a dynamically-switched Realm state.
> + * SEC_SID has been converted to the corresponding Security Space (QSMMUSpace)
> + * before calling this function.
> + *
> + * In a real SMMU translation, this input security state does not unilaterally
> + * determine the output Physical Address (PA) space. The output PA space is
> + * ultimately determined by attributes encountered during the page table walk,
> + * such as NSCFG and NSTable.
> + *
> + * However, for the specific context of testing the SMMU with the iommu-testdev,
> + * and to simplify the future support for Secure and Realm states, we adopt a
> + * consistent policy:
> + *
> + * - We always ensure that the page table attributes (e.g., nscfg, nstable)
> + * *match* the input 'SEC_SID' of the test case.
> + *
> + * For example: If 'SEC_SID' is Non-Secure, the corresponding nscfg and nstable
> + * attributes in the translation tables will always be set to 1.
> + *
> + */
> +void qsmmu_setup_translation_tables(QTestState *qts,
> + uint64_t iova,
> + QSMMUSpace space,
> + bool is_cd,
> + QSMMUTransMode mode)
> +{
> + uint64_t all_s2_l0_pte_val, all_s2_l1_pte_val, all_s2_l2_pte_val;
> + uint64_t s1_vttb, s2_vttb, s1_leaf_pte_val;
> + uint64_t l0_addr, l1_addr, l2_addr, l3_addr;
> +
> + g_test_message("Begin of construction: IOVA=0x%" PRIx64
> + " mode=%d is_building_CD=%s ===",
> + iova, mode, is_cd ? "yes" : "no");
> +
> + /* Initialize shared S2 PTE values used across all walks */
> + all_s2_l0_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> + all_s2_l1_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> + all_s2_l2_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> +
> + /* Both S1 and S2 share the same VTTB base */
> + s1_vttb = qsmmu_apply_space_offs(space, QSMMU_VTTB & QSMMU_PTE_MASK);
> + s2_vttb = s1_vttb;
> +
> + if (!is_cd) {
> + /*
> + * Setup Stage 1 page tables with nested Stage 2 walks.
> + * For each S1 level (L0-L3), we need to:
> + * 1. Calculate S1 PTE address (as IPA)
> + * 2. Perform nested S2 walk to translate that IPA to PA
> + * 3. Write the S1 PTE value
> + */
> +
> + /* Stage 1 Level 0 */
> + l0_addr = qsmmu_get_table_addr(s1_vttb, 0, iova);
> + qsmmu_setup_s1_level_with_nested_s2(qts, space, 0, l0_addr,
> + all_s2_l0_pte_val, s2_vttb, mode);
> +
> + /* Stage 1 Level 1 */
> + l1_addr = qsmmu_get_table_addr(all_s2_l0_pte_val & QSMMU_PTE_MASK,
> + 1, iova);
> + qsmmu_setup_s1_level_with_nested_s2(qts, space, 1, l1_addr,
> + all_s2_l1_pte_val, s2_vttb, mode);
> +
> + /* Stage 1 Level 2 */
> + l2_addr = qsmmu_get_table_addr(all_s2_l1_pte_val & QSMMU_PTE_MASK,
> + 2, iova);
> + qsmmu_setup_s1_level_with_nested_s2(qts, space, 2, l2_addr,
> + all_s2_l2_pte_val, s2_vttb, mode);
> +
> + /* Stage 1 Level 3 (leaf) */
> + l3_addr = qsmmu_get_table_addr(all_s2_l2_pte_val & QSMMU_PTE_MASK,
> + 3, iova);
> +
> + s1_leaf_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L3_PTE_VAL | qsmmu_get_pte_attrs(mode, true, space)
> + );
> +
> + qsmmu_setup_s1_level_with_nested_s2(qts, space, 3, l3_addr,
> + s1_leaf_pte_val, s2_vttb, mode);
> + } else {
> + /*
> + * For CD address translation, we start directly with the IPA.
> + */
> + s1_leaf_pte_val = iova | qsmmu_get_pte_attrs(QSMMU_TM_NESTED,
> + false, space);
> + }
> +
> + /*
> + * Final Stage 2 walk: Translate the result from Stage 1.
> + * - For S1_ONLY: This is skipped in hardware but we set it up for testing
> + * - For S2_ONLY: This is the only walk
> + * - For NESTED: This translates the IPA from S1 to final PA
> + * - For CD address (is_cd=true): This is a table address, use !is_final
> + */
> + qsmmu_setup_s2_walk_for_ipa(qts, space, s1_leaf_pte_val, s2_vttb,
> + mode, !is_cd);
> +
> + /* Calculate and log final translated PA */
> + g_test_message("End of construction: PA=0x%llx ===",
> + (s1_leaf_pte_val & QSMMU_PTE_MASK) + (iova & 0xfff));
> +}
> diff --git a/tests/qtest/libqos/qos-smmuv3.h b/tests/qtest/libqos/qos-smmuv3.h
> new file mode 100644
> index 0000000000..375778517f
> --- /dev/null
> +++ b/tests/qtest/libqos/qos-smmuv3.h
> @@ -0,0 +1,256 @@
> +/*
> + * QOS SMMUv3 Module
> + *
> + * This module provides SMMUv3-specific helper functions for libqos tests,
> + * encapsulating SMMUv3 setup, and assertions.
> + *
> + * Copyright (c) 2025 Phytium Technology
> + *
> + * Author:
> + * Tao Tang <tangtao1634@phytium.com.cn>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef QTEST_LIBQOS_SMMUV3_H
> +#define QTEST_LIBQOS_SMMUV3_H
> +
> +#include "hw/misc/iommu-testdev.h"
> +
> +/*
> + * SMMU MMIO register base. We hardcode this as base_memmap is 'static const'
> + * in hw/arm/virt.c and not directly accessible here.
this is not true anymore with -device arm-smmuv3. So you can explicitly
state that will only work with machine wide vsmmu
> + */
> +#define VIRT_SMMU_BASE 0x0000000009050000ull
> +
> +/* SMMU queue and table base addresses */
> +#define QSMMU_CMDQ_BASE_ADDR 0x000000000e16b000ull
> +#define QSMMU_EVENTQ_BASE_ADDR 0x000000000e170000ull
> +
> +/*
> + * Translation tables and descriptors for a mapping of
> + * - IOVA(Stage 1 only)
> + * - IPA (Stage 2 only)
> + * to 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 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 (QSMMU_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.
> + */
> +#define QSMMU_IOVA 0x0000008080604567ull
> +#define QSMMU_VTTB 0x000000000e4d0000ull
> +#define QSMMU_STR_TAB_BASE 0x000000000e179000ull
> +#define QSMMU_CD_GPA (QSMMU_STR_TAB_BASE - 0x40ull)
> +
> +
> +#define QSMMU_L0_PTE_VAL 0x000000000e4d1000ull
> +#define QSMMU_L1_PTE_VAL 0x000000000e4d2000ull
> +#define QSMMU_L2_PTE_VAL 0x000000000e4d3000ull
> +#define QSMMU_L3_PTE_VAL 0x000000000ecba000ull
> +
> +#define QSMMU_NON_LEAF_PTE_MASK 0x8000000000000003ull
> +#define QSMMU_LEAF_PTE_RO_MASK 0x04000000000007e3ull
> +#define QSMMU_LEAF_PTE_RW_MASK 0x0400000000000763ull
> +#define QSMMU_PTE_MASK 0x0000fffffffff000ull
> +
> +/*
> + * 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 QSMMU_SPACE_OFFS_NS 0x0000000040000000ull
> +
> +typedef enum QSMMUSecSID {
> + QSMMU_SEC_SID_NONSECURE = 0,
> +} QSMMUSecSID;
> +
> +typedef enum QSMMUSpace {
> + QSMMU_SPACE_NONSECURE = 1,
> +} QSMMUSpace;
> +
> +typedef enum QSMMUTransMode {
> + QSMMU_TM_S1_ONLY = 0,
> + QSMMU_TM_S2_ONLY = 1,
> + QSMMU_TM_NESTED = 2,
> +} QSMMUTransMode;
> +
> +typedef struct QSMMUTestConfig {
> + QSMMUTransMode trans_mode; /* Translation mode (S1, S2, Nested) */
> + QSMMUSecSID sec_sid; /* SEC_SID of test device */
> + uint64_t dma_iova; /* DMA IOVA address for testing */
> + uint32_t dma_len; /* DMA length for testing */
> + uint32_t expected_result; /* Expected DMA result for validation */
> +} QSMMUTestConfig;
> +
> +typedef struct QSMMUTestContext {
> + QTestState *qts; /* QTest state handle */
> + QPCIDevice *dev; /* PCI device handle */
> + QPCIBar bar; /* PCI BAR for MMIO access */
> + QSMMUTestConfig config; /* Test configuration */
> + uint64_t smmu_base; /* SMMU base address */
> + uint32_t trans_status; /* Translation configuration status */
> + uint32_t dma_result; /* DMA operation result */
> + uint32_t sid; /* Stream ID for the test */
> + QSMMUSpace tx_space; /* Cached transaction space */
> +} QSMMUTestContext;
> +
> +/* Convert SEC_SID to corresponding Security Space */
> +QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid);
> +
> +/* Get base offset of the specific Security space */
> +uint64_t qsmmu_space_offset(QSMMUSpace sp);
> +
> +uint32_t qsmmu_build_dma_attrs(QSMMUSpace space);
> +
> +/*
> + * qsmmu_setup_and_enable_translation - Complete translation setup and enable
> + *
> + * @ctx: Test context containing configuration and device handles
> + *
> + * Returns: Translation status (0 = success, non-zero = error)
> + *
> + * This function performs the complete translation setup sequence:
> + * 1. Builds all required SMMU structures (STE, CD, page tables)
> + * 2. Programs SMMU registers for the appropriate security space
> + * 3. Returns configuration status
> + */
> +uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx);
> +
> +/*
> + * qsmmu_build_translation - Build SMMU translation structures
> + *
> + * @qts: QTest state handle
> + * @mode: Translation mode (S1_ONLY, S2_ONLY, NESTED)
> + * @tx_space: Transaction security space
> + * @sid: Stream ID
> + *
> + * Returns: Build status (0 = success, non-zero = error)
> + *
> + * Constructs all necessary SMMU translation structures in guest memory:
> + * - Stream Table Entry (STE) for the given SID
> + * - Context Descriptor (CD) if Stage 1 translation is involved
> + * - Complete page table hierarchy based on translation mode
> + *
> + * The structures are written to security-space-specific memory regions.
> + */
> +uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode,
> + QSMMUSpace tx_space, uint32_t sid);
> +
> +/*
> + * qsmmu_bank_base - Get SMMU control bank base address
> + *
> + * @base: SMMU base address
> + * @sp: Security space
> + *
> + * Returns: Bank base address for the given security space
> + *
> + * Maps security space to the corresponding SMMU control register bank.
> + * Currently only Non-Secure bank is supported.
> + */
> +uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp);
> +
> +/*
> + * qsmmu_program_bank - Program SMMU control bank registers
> + *
> + * @qts: QTest state handle
> + * @bank_base: SMMU bank base address
> + * @sp: Security space
> + *
> + * Programs a specific SMMU control bank with minimal configuration:
> + * - Global Bypass Attribute (GBPA)
> + * - Control registers (CR0, CR1)
> + * - Command queue (base, producer, consumer)
> + * - Event queue (base, producer, consumer)
> + * - Stream table configuration (base, format)
> + *
> + * Addresses are adjusted based on security space offset.
> + */
> +void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp);
> +
> +/*
> + * qsmmu_program_regs - Program all required SMMU register banks
> + *
> + * @qts: QTest state handle
> + * @smmu_base: SMMU base address
> + * @space: Target security space
> + *
> + * Programs SMMU registers for the requested security space which is called in
> + * qsmmu_setup_and_enable_translation. Always programs Non-Secure bank first,
> + * then the target space if different.
> + */
> +void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace space);
> +
> +uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx);
> +
> +/* qsmmu_expected_dma_result - Calculate expected DMA result */
> +uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx);
> +
> +/*
> + * qsmmu_validate_test_result - Validate actual VS expected test result
> + *
> + * @ctx: Test context containing actual and expected results
> + *
> + * Returns: true if test passed (actual == expected), false otherwise
> + *
> + * Compares the actual DMA result with the expected result and logs
> + * the comparison for debugging purposes.
> + */
> +bool qsmmu_validate_test_result(QSMMUTestContext *ctx);
> +
> +/*
> + * qsmmu_setup_translation_tables - Setup complete SMMU page table hierarchy
> + *
> + * @qts: QTest state handle
> + * @iova: Input Virtual Address or IPA to translate
> + * @space: Security space (NONSECURE, SECURE, REALM, ROOT)
> + * @is_cd: Whether translating CD address (vs regular IOVA)
> + * @mode: Translation mode (S1_ONLY, S2_ONLY, NESTED)
> + *
> + * This function builds the complete page table structure for translating
> + * the given IOVA through the SMMU. The structure varies based on mode:
> + *
> + * - S1_ONLY: Single Stage 1 walk (IOVA -> PA)
> + * - S2_ONLY: Single Stage 2 walk (IPA -> PA)
> + * - NESTED: Stage 1 walk (IOVA -> IPA) with nested S2 walks for each
> + * S1 table access, plus final S2 walk for the result IPA
> + *
> + * For nested mode, this creates a complex hierarchy:
> + * - 4 Stage 1 levels (L0-L3), each requiring a 4-level Stage 2 walk
> + * - 1 final Stage 2 walk for the resulting IPA
> + *
> + * The function writes all necessary Page Table Entries (PTEs) to guest
> + * memory using qtest_writeq(), setting up the complete translation path
> + * that the SMMU hardware will traverse during DMA operations.
> + */
> +void qsmmu_setup_translation_tables(QTestState *qts,
> + uint64_t iova,
> + QSMMUSpace space,
> + bool is_cd,
> + QSMMUTransMode mode);
> +
> +/* High-level test execution helpers */
> +void qsmmu_single_translation(QSMMUTestContext *ctx);
> +void qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev,
> + QPCIBar bar, uint64_t smmu_base,
> + const QSMMUTestConfig *cfg);
> +
> +#endif /* QTEST_LIBQOS_SMMUV3_H */
Thanks
Eric
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library
2026-01-15 18:30 ` Eric Auger
@ 2026-01-16 10:00 ` Tao Tang
0 siblings, 0 replies; 35+ messages in thread
From: Tao Tang @ 2026-01-16 10:00 UTC (permalink / raw)
To: eric.auger, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Eric,
On 2026/1/16 02:30, Eric Auger wrote:
> Hi Tao,
>
> On 12/24/25 4:46 AM, Tao Tang wrote:
>> Introduce qos-smmuv3, a reusable library for SMMUv3-related qtest
>> operations. This module encapsulates common tasks like:
>> ------------------------------<snip>------------------------------
>>
>>
>>
>> ------------------------------<snip>------------------------------
>> +
>> +#include "qemu/osdep.h"
>> +#include "hw/arm/smmuv3-common.h"
> In file included from ../tests/qtest/libqos/qos-smmuv3.c:16:
> /home/augere/UPSTREAM/qemu/include/hw/arm/smmuv3-common.h:14:10: fatal
> error: hw/registerfields.h: No such file or directory
> 14 | #include "hw/registerfields.h"
> | ^~~~~~~~~~~~~~~~~~~~~
>
> This needs a rebase after the move to header files I am afraid
Thanks for the hint. I'll rebase it in V9.
>> ------------------------------<snip>------------------------------
>>
>>
>>
>> ------------------------------<snip>------------------------------
>> +
>> +#ifndef QTEST_LIBQOS_SMMUV3_H
>> +#define QTEST_LIBQOS_SMMUV3_H
>> +
>> +#include "hw/misc/iommu-testdev.h"
>> +
>> +/*
>> + * SMMU MMIO register base. We hardcode this as base_memmap is 'static const'
>> + * in hw/arm/virt.c and not directly accessible here.
> this is not true anymore with -device arm-smmuv3. So you can explicitly
> state that will only work with machine wide vsmmu
OK. I'll explicitly state it here and other related places.
Best regards,
Tao
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library
2025-12-24 3:46 ` [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library Tao Tang
2026-01-15 18:30 ` Eric Auger
@ 2026-01-17 10:02 ` Eric Auger
2026-01-17 13:06 ` Tao Tang
1 sibling, 1 reply; 35+ messages in thread
From: Eric Auger @ 2026-01-17 10:02 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Tao,
On 12/24/25 4:46 AM, Tao Tang wrote:
> Introduce qos-smmuv3, a reusable library for SMMUv3-related qtest
> operations. This module encapsulates common tasks like:
>
> - SMMUv3 initialization (enabling, configuring command/event queues)
> - Stream Table Entry (STE) and Context Descriptor (CD) setup
> - Multi-level page table construction (L0-L3 for 4KB granules)
> - Support for Stage 1, Stage 2, and nested translation modes
> - Could be easily extended to support multi-space testing infrastructure
> (Non-Secure, Secure, Root, Realm)
>
> The library provides high-level abstractions that allow test code to
> focus on IOMMU behavior validation rather than low-level register
> manipulation and page table encoding. Key features include:
>
> - Provide memory allocation for translation structures with proper
> alignment
> - Helper functions to build valid STEs/CDs for different translation
> scenarios
> - Page table walkers that handle address offset calculations per
> security space
>
> This infrastructure is designed to be used by iommu-testdev-based tests
> and future SMMUv3 test suites, reducing code duplication and improving
> test maintainability.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> Reviewed-by: Fabiano Rosas <farosas@suse.de>
> ---
> MAINTAINERS | 6 +
> tests/qtest/libqos/meson.build | 3 +
> tests/qtest/libqos/qos-smmuv3.c | 633 ++++++++++++++++++++++++++++++++
> tests/qtest/libqos/qos-smmuv3.h | 256 +++++++++++++
> 4 files changed, 898 insertions(+)
> create mode 100644 tests/qtest/libqos/qos-smmuv3.c
> create mode 100644 tests/qtest/libqos/qos-smmuv3.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3c88ed5dc4..68ab713429 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3546,6 +3546,12 @@ F: docs/devel/testing/qtest.rst
> X: tests/qtest/bios-tables-test*
> X: tests/qtest/migration-*
>
> +QTest SMMUv3 helpers
> +M: Tao Tang <tangtao1634@phytium.com.cn>
> +L: qemu-arm@nongnu.org
> +S: Maintained
> +F: tests/qtest/libqos/qos-smmuv3*
> +
> Device Fuzzing
> M: Alexander Bulekov <alxndr@bu.edu>
> R: Paolo Bonzini <pbonzini@redhat.com>
> diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
> index 1ddaf7b095..8d6758ec2b 100644
> --- a/tests/qtest/libqos/meson.build
> +++ b/tests/qtest/libqos/meson.build
> @@ -60,6 +60,9 @@ libqos_srcs = files(
> 'x86_64_pc-machine.c',
> 'riscv-virt-machine.c',
> 'loongarch-virt-machine.c',
> +
> + # SMMU:
> + 'qos-smmuv3.c',
> )
>
> if have_virtfs
> diff --git a/tests/qtest/libqos/qos-smmuv3.c b/tests/qtest/libqos/qos-smmuv3.c
> new file mode 100644
> index 0000000000..a48abb186b
> --- /dev/null
> +++ b/tests/qtest/libqos/qos-smmuv3.c
> @@ -0,0 +1,633 @@
> +/*
> + * QOS SMMUv3 Module
> + *
> + * This module provides SMMUv3-specific helper functions for libqos tests,
> + * encapsulating SMMUv3 setup, and assertions.
> + *
> + * 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 "hw/arm/smmuv3-common.h"
> +#include "tests/qtest/libqos/pci.h"
> +#include "hw/misc/iommu-testdev.h"
> +#include "qos-smmuv3.h"
> +
> +#define QSMMU_STE_S2T0SZ_VAL 0x14
> +
> +/* Apply space offset to address */
> +static inline uint64_t qsmmu_apply_space_offs(QSMMUSpace sp,
> + uint64_t address)
> +{
> + return address + qsmmu_space_offset(sp);
> +}
> +
> +uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx)
> +{
> + /* Currently only non-secure space is supported. */
> + if (ctx->tx_space != QSMMU_SPACE_NONSECURE) {
> + return ITD_DMA_ERR_TX_FAIL;
> + }
> + return ctx->config.expected_result;
> +}
> +
> +uint32_t qsmmu_build_dma_attrs(QSMMUSpace space)
> +{
> + uint32_t attrs = 0;
> + switch (space) {
> + case QSMMU_SPACE_NONSECURE:
> + /* Non-secure: secure=0, space=1 */
> + attrs = ITD_ATTRS_SET_SECURE(attrs, 0);
> + attrs = ITD_ATTRS_SET_SPACE(attrs, QSMMU_SPACE_NONSECURE);
> + break;
> + default:
> + g_assert_not_reached();
> + }
> +
> + return attrs;
> +}
> +
> +uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx)
> +{
> + uint32_t build_result;
> +
> + /* Build page tables and SMMU structures first */
> + build_result = qsmmu_build_translation(
> + ctx->qts, ctx->config.trans_mode,
> + ctx->tx_space, ctx->sid);
> + if (build_result != 0) {
> + g_test_message("Build failed: mode=%u sid=%u status=0x%x",
> + ctx->config.trans_mode, ctx->sid, build_result);
> + ctx->trans_status = build_result;
> + return ctx->trans_status;
> + }
> +
> + /* Program SMMU registers for the appropriate security space */
> + qsmmu_program_regs(ctx->qts, ctx->smmu_base, ctx->tx_space);
> +
> + ctx->trans_status = 0;
> + return ctx->trans_status;
> +}
> +
> +uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx)
qsmmu_trigger_dma and qsmmu_single_translation helpers which do not really relate to SMMU but rather relate to the pci
test dev. put them in a specific section of this file or move them in a
separate file. They could be reused by other non SMMU specific tests
> +{
> + uint32_t result, attrs_val;
> +
> + /* Program DMA parameters */
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_LO,
> + (uint32_t)ctx->config.dma_iova);
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_HI,
> + (uint32_t)(ctx->config.dma_iova >> 32));
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_LEN,
> + ctx->config.dma_len);
> +
> + /* Build and write DMA attributes based on device security state. */
> + attrs_val = qsmmu_build_dma_attrs(QSMMU_SPACE_NONSECURE);
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_ATTRS, attrs_val);
> +
> + /* Flip status */
> + /* Arm iommu-testdev so the next read triggers DMA */
> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_DBELL, ITD_DMA_DBELL_ARM);
> +
> + /* Trigger DMA */
> + qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_TRIGGERING);
> +
> + /* Poll for DMA completion */
> + for (int i = 0; i < 1000; i++) {
> + result = qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_RESULT);
> + if (result != ITD_DMA_RESULT_BUSY) {
> + ctx->dma_result = result;
> + break;
> + }
> + g_usleep(1000);
> + }
> +
> + /* Fallback for timeout */
> + if (ctx->dma_result == ITD_DMA_RESULT_BUSY) {
> + ctx->dma_result = ITD_DMA_ERR_TX_FAIL;
> + }
> +
> + return ctx->dma_result;
> +}
> +
> +bool qsmmu_validate_test_result(QSMMUTestContext *ctx)
> +{
> + uint32_t expected = qsmmu_expected_dma_result(ctx);
> + g_test_message("-> Validating result: expected=0x%x actual=0x%x",
> + expected, ctx->dma_result);
> + return (ctx->dma_result == expected);
> +}
> +
> +QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid)
> +{
> + switch (sec_sid) {
> + case QSMMU_SEC_SID_NONSECURE:
> + return QSMMU_SPACE_NONSECURE;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +uint64_t qsmmu_space_offset(QSMMUSpace sp)
> +{
> + switch (sp) {
> + case QSMMU_SPACE_NONSECURE:
> + return QSMMU_SPACE_OFFS_NS;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +void qsmmu_single_translation(QSMMUTestContext *ctx)
> +{
> + uint32_t config_result;
> + uint32_t dma_result;
> +
> + /* Configure SMMU translation */
> + config_result = qsmmu_setup_and_enable_translation(ctx);
> + g_assert_cmpuint(config_result, ==, 0);
> +
> + /* Trigger DMA operation */
> + dma_result = qsmmu_trigger_dma(ctx);
> + if (dma_result != 0) {
> + g_test_message("DMA failed: mode=%u result=0x%x",
> + ctx->config.trans_mode, dma_result);
> + } else {
> + g_test_message("-> DMA succeeded: mode=%u", ctx->config.trans_mode);
> + }
> +
> + /* Validate test result */
> + g_assert_true(qsmmu_validate_test_result(ctx));
> +}
> +
> +void qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev,
> + QPCIBar bar, uint64_t smmu_base,
> + const QSMMUTestConfig *cfg)
> +{
> + QSMMUTestContext ctx = {
> + .qts = qts,
> + .dev = dev,
> + .bar = bar,
> + .smmu_base = smmu_base,
> + .config = *cfg,
> + .sid = dev->devfn,
> + .tx_space = qsmmu_sec_sid_to_space(cfg->sec_sid),
> + };
> +
> + qtest_memset(qts, cfg->dma_iova, 0x00, cfg->dma_len);
> + qsmmu_single_translation(&ctx);
> +}
> +
> +uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode,
> + QSMMUSpace tx_space, uint32_t sid)
> +{
> + uint64_t ste_addr, ste_addr_real, cd_addr_real;
> + uint64_t cd_ttb, vttb, vttb_real;
> + uint8_t nscfg0, nscfg1;
> + QSMMUSpace build_space;
> + size_t ste_cd_entry_bytes = sizeof(STE);
> + STE ste;
> + CD cd;
> +
> + build_space = tx_space;
> + if (build_space != QSMMU_SPACE_NONSECURE) {
> + return 0xdeadbeafu;
> + }
> +
> + /* Build STE image */
> + memset(&ste, 0, sizeof(ste));
> + switch (mode) {
> + case QSMMU_TM_S1_ONLY:
> + STE_SET_CONFIG(&ste, 0x5);
> + break;
> + case QSMMU_TM_S2_ONLY:
> + STE_SET_CONFIG(&ste, 0x6);
> + break;
> + case QSMMU_TM_NESTED:
> + default:
> + STE_SET_CONFIG(&ste, 0x7);
> + break;
> + }
> +
> + STE_SET_VALID(&ste, 1);
> + STE_SET_S2T0SZ(&ste, QSMMU_STE_S2T0SZ_VAL);
> + STE_SET_S2SL0(&ste, 0x2);
> + STE_SET_S2TG(&ste, 0);
> + STE_SET_S2PS(&ste, 0x5);
> + STE_SET_S2AA64(&ste, 1);
> + STE_SET_S2ENDI(&ste, 0);
> + STE_SET_S2AFFD(&ste, 0);
> +
> + /*
> + * The consistent policy also extends to pointer fetches. For cases that
> + * require reading STE.S1ContextPtr or STE.S2TTB, we still follow the same
> + * policy:
> + * - The PA space security attribute of the address pointed to
> + * (e.g., the CD or S2L1 table) must also match the input 'SEC_SID'.
> + */
> + cd_addr_real = qsmmu_apply_space_offs(build_space, QSMMU_CD_GPA);
> + STE_SET_CTXPTR(&ste, cd_addr_real);
> +
> + vttb = QSMMU_VTTB;
> + vttb_real = qsmmu_apply_space_offs(build_space, vttb);
> + STE_SET_S2TTB(&ste, vttb_real);
> +
> + ste_addr = sid * ste_cd_entry_bytes + QSMMU_STR_TAB_BASE;
> + ste_addr_real = qsmmu_apply_space_offs(build_space, ste_addr);
> +
> + /* Write STE to memory */
> + for (int i = 0; i < ARRAY_SIZE(ste.word); i++) {
> + qtest_writel(qts, ste_addr_real + i * 4, ste.word[i]);
> + }
> +
> + switch (tx_space) {
> + case QSMMU_SPACE_NONSECURE:
> + nscfg0 = 0x1;
> + nscfg1 = 0x1;
> + break;
> + default:
> + g_assert_not_reached();
> + }
> + /* Build CD image for S1 path if needed */
> + if (mode != QSMMU_TM_S2_ONLY) {
> + memset(&cd, 0, sizeof(cd));
> +
> + CD_SET_ASID(&cd, 0x1e20);
> + CD_SET_AARCH64(&cd, 1);
> + CD_SET_VALID(&cd, 1);
> + CD_SET_A(&cd, 1);
> + CD_SET_S(&cd, 0);
> + CD_SET_HD(&cd, 0);
> + CD_SET_HA(&cd, 0);
> + CD_SET_IPS(&cd, 0x4);
> + CD_SET_TBI(&cd, 0x0);
> + CD_SET_AFFD(&cd, 0x0);
> + CD_SET_EPD(&cd, 0, 0x0);
> + CD_SET_EPD(&cd, 1, 0x1);
> + CD_SET_TSZ(&cd, 0, 0x10);
> + CD_SET_TG(&cd, 0, 0x0);
> + CD_SET_ENDI(&cd, 0x0);
> +
> + CD_SET_NSCFG(&cd, 0, nscfg0);
> + CD_SET_NSCFG(&cd, 1, nscfg1);
> + CD_SET_R(&cd, 0x1);
> + cd_ttb = vttb_real;
> + CD_SET_TTB(&cd, 0, cd_ttb);
> +
> + for (int i = 0; i < ARRAY_SIZE(cd.word); i++) {
> + /* TODO: Maybe need more work to write to secure RAM in future */
> + qtest_writel(qts, cd_addr_real + i * 4, cd.word[i]);
> + g_assert_cmpint(qtest_readl(qts, cd_addr_real + i * 4), ==,
> + cd.word[i]);
> + }
> + }
> +
> + qsmmu_setup_translation_tables(qts, QSMMU_IOVA, build_space,
> + false, mode);
> + /* Nested extras: CD S2 tables */
> + if (mode == QSMMU_TM_NESTED) {
> + /*
> + * Extra Stage 2 page tables is needed if
> + * SMMUTranslationClass == SMMU_CLASS_CD
> + * as smmuv3_do_translate would translate an IPA of the CD to the final
> + * output CD after a Stage 2 translation.
> + */
> + qsmmu_setup_translation_tables(qts, cd_addr_real, build_space,
> + true, mode);
> + }
> +
> + return 0;
> +}
> +
> +uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp)
> +{
> + switch (sp) {
> + case QSMMU_SPACE_NONSECURE:
> + return base;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp)
> +{
> + uint64_t cmdq_base, eventq_base, strtab_base;
> +
> + qtest_writel(qts, bank_base + A_GBPA, 0x80000000); /* UPDATE */
> + qtest_writel(qts, bank_base + A_CR0, 0x0); /* Disable */
> + qtest_writel(qts, bank_base + A_CR1, 0x0d75); /* Config */
> +
> + /* CMDQ_BASE: add address-space offset*/
> + cmdq_base = qsmmu_apply_space_offs(sp, QSMMU_CMDQ_BASE_ADDR);
> + cmdq_base |= 0x0a; /* Size and valid bits */
> + qtest_writeq(qts, bank_base + A_CMDQ_BASE, cmdq_base);
> +
> + qtest_writel(qts, bank_base + A_CMDQ_CONS, 0x0);
> + qtest_writel(qts, bank_base + A_CMDQ_PROD, 0x0);
> +
> + /* EVENTQ_BASE: add address-space offset */
> + eventq_base = qsmmu_apply_space_offs(sp, QSMMU_EVENTQ_BASE_ADDR);
> + eventq_base |= 0x0a; /* Size and valid bits */
> + qtest_writeq(qts, bank_base + A_EVENTQ_BASE, eventq_base);
> +
> + qtest_writel(qts, bank_base + A_EVENTQ_PROD, 0x0);
> + qtest_writel(qts, bank_base + A_EVENTQ_CONS, 0x0);
> +
> + /* STRTAB_BASE_CFG: linear stream table, LOG2SIZE=5 */
> + qtest_writel(qts, bank_base + A_STRTAB_BASE_CFG, 0x5);
> +
> + /* STRTAB_BASE: add address-space offset */
> + strtab_base = qsmmu_apply_space_offs(sp, QSMMU_STR_TAB_BASE);
> + qtest_writeq(qts, bank_base + A_STRTAB_BASE, strtab_base);
> +
> + /* CR0: Enable SMMU with appropriate flags */
> + qtest_writel(qts, bank_base + A_CR0, 0xd);
> +}
> +
> +void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace space)
> +{
> + uint64_t sp_base;
> + /* Always program Non-Secure bank first */
> + uint64_t ns_base = qsmmu_bank_base(smmu_base, QSMMU_SPACE_NONSECURE);
> + qsmmu_program_bank(qts, ns_base, QSMMU_SPACE_NONSECURE);
> +
> + /* Program the requested space if different from Non-Secure */
> + sp_base = qsmmu_bank_base(smmu_base, space);
> + if (sp_base != ns_base) {
> + qsmmu_program_bank(qts, sp_base, space);
> + }
> +}
> +
> +static uint32_t qsmmu_get_table_index(uint64_t addr, int level)
> +{
> + switch (level) {
> + case 0:
> + return (addr >> 39) & 0x1ff;
> + case 1:
> + return (addr >> 30) & 0x1ff;
> + case 2:
> + return (addr >> 21) & 0x1ff;
> + case 3:
> + return (addr >> 12) & 0x1ff;
> + default:
> + g_assert_not_reached();
> + }
> +}
> +
> +static uint64_t qsmmu_get_table_addr(uint64_t base, int level, uint64_t iova)
> +{
> + uint32_t index = qsmmu_get_table_index(iova, level);
> + return (base & QSMMU_PTE_MASK) + (index * 8);
> +}
> +
> +/*
> + * qsmmu_get_pte_attrs - Calculate the S1 leaf PTE value
> + *
> + * IOMMU need to set different attributes for PTEs based on the translation mode
> + */
> +static uint64_t qsmmu_get_pte_attrs(QSMMUTransMode mode, bool is_leaf,
> + QSMMUSpace space)
> +{
> + uint64_t rw_mask = QSMMU_LEAF_PTE_RW_MASK;
> + uint64_t ro_mask = QSMMU_LEAF_PTE_RO_MASK;
> + uint64_t non_leaf_mask = QSMMU_NON_LEAF_PTE_MASK;
> +
> + switch (space) {
> + case QSMMU_SPACE_NONSECURE:
> + break;
> + default:
> + g_assert_not_reached();
> + }
> +
> + if (!is_leaf) {
> + return non_leaf_mask;
> + }
> +
> + /* For leaf PTE */
> + if (mode == QSMMU_TM_NESTED || mode == QSMMU_TM_S1_ONLY) {
> + return rw_mask;
> + }
> +
> + return ro_mask;
> +}
> +
> +/*
> + * qsmmu_setup_s2_walk_for_ipa - Setup Stage 2 page table walk for an IPA
> + *
> + * @qts: QTest state handle
> + * @space: Security space
> + * @ipa: Intermediate Physical Address to translate
> + * @s2_vttb: Stage 2 VTTB (page table base)
> + * @mode: Translation mode
> + * @is_final: Whether this is the final S2 walk (not nested within S1)
> + *
> + * Calculates and writes a 4-level Stage 2 page table walk for the given IPA.
> + * This function dynamically generates and writes all page table entries
> + * (L0-L3) to guest memory based on the input IPA and configuration.
> + */
> +static void qsmmu_setup_s2_walk_for_ipa(QTestState *qts,
> + QSMMUSpace space,
> + uint64_t ipa,
> + uint64_t s2_vttb,
> + QSMMUTransMode mode,
> + bool is_final)
> +{
> + uint64_t all_s2_l0_pte_val;
> + uint64_t all_s2_l1_pte_val;
> + uint64_t all_s2_l2_pte_val;
> + uint64_t all_s2_l3_pte_val;
> + uint64_t s2_l0_addr, s2_l1_addr, s2_l2_addr, s2_l3_addr;
> +
> + /* Shared intermediate PTE values for all S2 walks */
> + all_s2_l0_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> + all_s2_l1_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> + all_s2_l2_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> +
> + /* Stage 2 Level 0 */
> + s2_l0_addr = qsmmu_get_table_addr(s2_vttb, 0, ipa);
> + qtest_writeq(qts, s2_l0_addr, all_s2_l0_pte_val);
> +
> + /* Stage 2 Level 1 */
> + s2_l1_addr = qsmmu_get_table_addr(all_s2_l0_pte_val, 1, ipa);
> + qtest_writeq(qts, s2_l1_addr, all_s2_l1_pte_val);
> +
> + /* Stage 2 Level 2 */
> + s2_l2_addr = qsmmu_get_table_addr(all_s2_l1_pte_val, 2, ipa);
> + qtest_writeq(qts, s2_l2_addr, all_s2_l2_pte_val);
> +
> + /* Stage 2 Level 3 (leaf) */
> + s2_l3_addr = qsmmu_get_table_addr(all_s2_l2_pte_val, 3, ipa);
> +
> + /*
> + * Stage 2 L3 PTE attributes depend on the context:
> + * - For nested S1 table address translations (!is_final):
> + * Use LEAF attrs (0x763) because these PTEs map S1 table pages directly
> + * - For final S2 walk (is_final):
> + * Use TABLE attrs (0x7e3) for the final IPA→PA mapping
> + */
> + if (!is_final) {
> + all_s2_l3_pte_val =
> + (ipa & QSMMU_PTE_MASK) |
> + qsmmu_get_pte_attrs(QSMMU_TM_NESTED, true, space);
> + } else {
> + all_s2_l3_pte_val =
> + (ipa & QSMMU_PTE_MASK) |
> + qsmmu_get_pte_attrs(QSMMU_TM_S2_ONLY, true, space);
> + }
> +
> + qtest_writeq(qts, s2_l3_addr, all_s2_l3_pte_val);
> +}
> +
> +/*
> + * qsmmu_setup_s1_level_with_nested_s2 - Setup S1 level with nested S2 walk
> + *
> + * @qts: QTest state handle
> + * @space: Security space
> + * @s1_level: Stage 1 level (0-3)
> + * @s1_pte_addr: Stage 1 PTE address (as IPA)
> + * @s1_pte_val: Stage 1 PTE value to write
> + * @s2_vttb: Stage 2 VTTB for nested translation
> + * @mode: Translation mode
> + *
> + * For nested translation, each S1 table access requires a full S2 walk
> + * to translate the S1 table's IPA to PA. This function performs the nested
> + * S2 walk and writes the S1 PTE value to guest memory.
> + */
> +static void qsmmu_setup_s1_level_with_nested_s2(QTestState *qts,
> + QSMMUSpace space,
> + int s1_level,
> + uint64_t s1_pte_addr,
> + uint64_t s1_pte_val,
> + uint64_t s2_vttb,
> + QSMMUTransMode mode)
> +{
> + /*
> + * Perform nested S2 walk to translate S1 table IPA to PA.
> + * This is always needed for S1_ONLY/S2_ONLY/NESTED modes because:
> + * - S1_ONLY: Needs S2 tables for "IPA as PA" mapping (for testing)
> + * - S2_ONLY: Needs S2 tables for direct translation
> + * - NESTED: Needs S2 tables for nested translation
> + */
> + qsmmu_setup_s2_walk_for_ipa(qts, space, s1_pte_addr,
> + s2_vttb, mode, false);
> +
> + /* Write the S1 PTE value */
> + qtest_writeq(qts, s1_pte_addr, s1_pte_val);
> +}
> +
> +/*
> + * qsmmu_setup_translation_tables - Setup SMMU translation tables
> + *
> + * The 'SEC_SID' represents the input security state of the device/transaction,
> + * whether it's a static Secure state or a dynamically-switched Realm state.
> + * SEC_SID has been converted to the corresponding Security Space (QSMMUSpace)
> + * before calling this function.
> + *
> + * In a real SMMU translation, this input security state does not unilaterally
> + * determine the output Physical Address (PA) space. The output PA space is
> + * ultimately determined by attributes encountered during the page table walk,
> + * such as NSCFG and NSTable.
> + *
> + * However, for the specific context of testing the SMMU with the iommu-testdev,
> + * and to simplify the future support for Secure and Realm states, we adopt a
> + * consistent policy:
> + *
> + * - We always ensure that the page table attributes (e.g., nscfg, nstable)
> + * *match* the input 'SEC_SID' of the test case.
> + *
> + * For example: If 'SEC_SID' is Non-Secure, the corresponding nscfg and nstable
> + * attributes in the translation tables will always be set to 1.
> + *
> + */
> +void qsmmu_setup_translation_tables(QTestState *qts,
> + uint64_t iova,
> + QSMMUSpace space,
> + bool is_cd,
> + QSMMUTransMode mode)
> +{
> + uint64_t all_s2_l0_pte_val, all_s2_l1_pte_val, all_s2_l2_pte_val;
> + uint64_t s1_vttb, s2_vttb, s1_leaf_pte_val;
> + uint64_t l0_addr, l1_addr, l2_addr, l3_addr;
> +
> + g_test_message("Begin of construction: IOVA=0x%" PRIx64
> + " mode=%d is_building_CD=%s ===",
> + iova, mode, is_cd ? "yes" : "no");
> +
> + /* Initialize shared S2 PTE values used across all walks */
> + all_s2_l0_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> + all_s2_l1_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> + all_s2_l2_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
> +
> + /* Both S1 and S2 share the same VTTB base */
> + s1_vttb = qsmmu_apply_space_offs(space, QSMMU_VTTB & QSMMU_PTE_MASK);
> + s2_vttb = s1_vttb;
> +
> + if (!is_cd) {
> + /*
> + * Setup Stage 1 page tables with nested Stage 2 walks.
> + * For each S1 level (L0-L3), we need to:
> + * 1. Calculate S1 PTE address (as IPA)
> + * 2. Perform nested S2 walk to translate that IPA to PA
> + * 3. Write the S1 PTE value
> + */
> +
> + /* Stage 1 Level 0 */
> + l0_addr = qsmmu_get_table_addr(s1_vttb, 0, iova);
> + qsmmu_setup_s1_level_with_nested_s2(qts, space, 0, l0_addr,
> + all_s2_l0_pte_val, s2_vttb, mode);
> +
> + /* Stage 1 Level 1 */
> + l1_addr = qsmmu_get_table_addr(all_s2_l0_pte_val & QSMMU_PTE_MASK,
> + 1, iova);
> + qsmmu_setup_s1_level_with_nested_s2(qts, space, 1, l1_addr,
> + all_s2_l1_pte_val, s2_vttb, mode);
> +
> + /* Stage 1 Level 2 */
> + l2_addr = qsmmu_get_table_addr(all_s2_l1_pte_val & QSMMU_PTE_MASK,
> + 2, iova);
> + qsmmu_setup_s1_level_with_nested_s2(qts, space, 2, l2_addr,
> + all_s2_l2_pte_val, s2_vttb, mode);
> +
> + /* Stage 1 Level 3 (leaf) */
> + l3_addr = qsmmu_get_table_addr(all_s2_l2_pte_val & QSMMU_PTE_MASK,
> + 3, iova);
> +
> + s1_leaf_pte_val = qsmmu_apply_space_offs(
> + space, QSMMU_L3_PTE_VAL | qsmmu_get_pte_attrs(mode, true, space)
> + );
> +
> + qsmmu_setup_s1_level_with_nested_s2(qts, space, 3, l3_addr,
> + s1_leaf_pte_val, s2_vttb, mode);
> + } else {
> + /*
> + * For CD address translation, we start directly with the IPA.
> + */
> + s1_leaf_pte_val = iova | qsmmu_get_pte_attrs(QSMMU_TM_NESTED,
> + false, space);
> + }
> +
> + /*
> + * Final Stage 2 walk: Translate the result from Stage 1.
> + * - For S1_ONLY: This is skipped in hardware but we set it up for testing
> + * - For S2_ONLY: This is the only walk
> + * - For NESTED: This translates the IPA from S1 to final PA
> + * - For CD address (is_cd=true): This is a table address, use !is_final
> + */
> + qsmmu_setup_s2_walk_for_ipa(qts, space, s1_leaf_pte_val, s2_vttb,
> + mode, !is_cd);
> +
> + /* Calculate and log final translated PA */
> + g_test_message("End of construction: PA=0x%llx ===",
> + (s1_leaf_pte_val & QSMMU_PTE_MASK) + (iova & 0xfff));
> +}
> diff --git a/tests/qtest/libqos/qos-smmuv3.h b/tests/qtest/libqos/qos-smmuv3.h
> new file mode 100644
> index 0000000000..375778517f
> --- /dev/null
> +++ b/tests/qtest/libqos/qos-smmuv3.h
> @@ -0,0 +1,256 @@
> +/*
> + * QOS SMMUv3 Module
> + *
> + * This module provides SMMUv3-specific helper functions for libqos tests,
> + * encapsulating SMMUv3 setup, and assertions.
> + *
> + * Copyright (c) 2025 Phytium Technology
> + *
> + * Author:
> + * Tao Tang <tangtao1634@phytium.com.cn>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef QTEST_LIBQOS_SMMUV3_H
> +#define QTEST_LIBQOS_SMMUV3_H
> +
> +#include "hw/misc/iommu-testdev.h"
> +
> +/*
> + * SMMU MMIO register base. We hardcode this as base_memmap is 'static const'
> + * in hw/arm/virt.c and not directly accessible here.
> + */
> +#define VIRT_SMMU_BASE 0x0000000009050000ull
> +
> +/* SMMU queue and table base addresses */
> +#define QSMMU_CMDQ_BASE_ADDR 0x000000000e16b000ull
> +#define QSMMU_EVENTQ_BASE_ADDR 0x000000000e170000ull
> +
> +/*
> + * Translation tables and descriptors for a mapping of
> + * - IOVA(Stage 1 only)
you mentionned you also support S1 + S2
> + * - IPA (Stage 2 only)
> + * to GPA.
> + *
> + * The translation is based on the Arm architecture with the following
> + * prerequisites:
> + * - Granule size: 4KB pages.
would be nice to support 64kB as well in the future. Can be done later
though.
> + * - Page table levels: 4 levels (L0, L1, L2, L3), starting at level 0.
> + * - IOVA size: The walk resolves a IOVA: 0x8080604567
well this is passed as a test config parameter and this is does not
sound relevant to hardcode it.
> + * - 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 (QSMMU_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.
> + */
> +#define QSMMU_IOVA 0x0000008080604567ull
> +#define QSMMU_VTTB 0x000000000e4d0000ull
> +#define QSMMU_STR_TAB_BASE 0x000000000e179000ull
> +#define QSMMU_CD_GPA (QSMMU_STR_TAB_BASE - 0x40ull)
> +
> +
> +#define QSMMU_L0_PTE_VAL 0x000000000e4d1000ull
> +#define QSMMU_L1_PTE_VAL 0x000000000e4d2000ull
> +#define QSMMU_L2_PTE_VAL 0x000000000e4d3000ull
> +#define QSMMU_L3_PTE_VAL 0x000000000ecba000ull
> +
> +#define QSMMU_NON_LEAF_PTE_MASK 0x8000000000000003ull
> +#define QSMMU_LEAF_PTE_RO_MASK 0x04000000000007e3ull
> +#define QSMMU_LEAF_PTE_RW_MASK 0x0400000000000763ull
> +#define QSMMU_PTE_MASK 0x0000fffffffff000ull
> +
> +/*
> + * 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 QSMMU_SPACE_OFFS_NS 0x0000000040000000ull
> +
> +typedef enum QSMMUSecSID {
> + QSMMU_SEC_SID_NONSECURE = 0,
> +} QSMMUSecSID;
> +
> +typedef enum QSMMUSpace {
> + QSMMU_SPACE_NONSECURE = 1,
> +} QSMMUSpace;
> +
> +typedef enum QSMMUTransMode {
> + QSMMU_TM_S1_ONLY = 0,
> + QSMMU_TM_S2_ONLY = 1,
> + QSMMU_TM_NESTED = 2,
> +} QSMMUTransMode;
> +
> +typedef struct QSMMUTestConfig {
> + QSMMUTransMode trans_mode; /* Translation mode (S1, S2, Nested) */
> + QSMMUSecSID sec_sid; /* SEC_SID of test device */
> + uint64_t dma_iova; /* DMA IOVA address for testing */
> + uint32_t dma_len; /* DMA length for testing */
> + uint32_t expected_result; /* Expected DMA result for validation */
> +} QSMMUTestConfig;
> +
> +typedef struct QSMMUTestContext {
> + QTestState *qts; /* QTest state handle */
> + QPCIDevice *dev; /* PCI device handle */
> + QPCIBar bar; /* PCI BAR for MMIO access */
> + QSMMUTestConfig config; /* Test configuration */
> + uint64_t smmu_base; /* SMMU base address */
> + uint32_t trans_status; /* Translation configuration status */
> + uint32_t dma_result; /* DMA operation result */
> + uint32_t sid; /* Stream ID for the test */
> + QSMMUSpace tx_space; /* Cached transaction space */
> +} QSMMUTestContext;
> +
> +/* Convert SEC_SID to corresponding Security Space */
> +QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid);
> +
> +/* Get base offset of the specific Security space */
> +uint64_t qsmmu_space_offset(QSMMUSpace sp);
> +
> +uint32_t qsmmu_build_dma_attrs(QSMMUSpace space);
> +
> +/*
> + * qsmmu_setup_and_enable_translation - Complete translation setup and enable
> + *
> + * @ctx: Test context containing configuration and device handles
> + *
> + * Returns: Translation status (0 = success, non-zero = error)
> + *
> + * This function performs the complete translation setup sequence:
> + * 1. Builds all required SMMU structures (STE, CD, page tables)
> + * 2. Programs SMMU registers for the appropriate security space
> + * 3. Returns configuration status
> + */
> +uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx);
> +
> +/*
> + * qsmmu_build_translation - Build SMMU translation structures
> + *
> + * @qts: QTest state handle
> + * @mode: Translation mode (S1_ONLY, S2_ONLY, NESTED)
> + * @tx_space: Transaction security space
> + * @sid: Stream ID
> + *
> + * Returns: Build status (0 = success, non-zero = error)
> + *
> + * Constructs all necessary SMMU translation structures in guest memory:
> + * - Stream Table Entry (STE) for the given SID
> + * - Context Descriptor (CD) if Stage 1 translation is involved
> + * - Complete page table hierarchy based on translation mode
> + *
> + * The structures are written to security-space-specific memory regions.
> + */
> +uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode,
> + QSMMUSpace tx_space, uint32_t sid);
> +
> +/*
> + * qsmmu_bank_base - Get SMMU control bank base address
> + *
> + * @base: SMMU base address
> + * @sp: Security space
> + *
> + * Returns: Bank base address for the given security space
> + *
> + * Maps security space to the corresponding SMMU control register bank.
> + * Currently only Non-Secure bank is supported.
> + */
> +uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp);
> +
> +/*
> + * qsmmu_program_bank - Program SMMU control bank registers
> + *
> + * @qts: QTest state handle
> + * @bank_base: SMMU bank base address
> + * @sp: Security space
> + *
> + * Programs a specific SMMU control bank with minimal configuration:
> + * - Global Bypass Attribute (GBPA)
> + * - Control registers (CR0, CR1)
> + * - Command queue (base, producer, consumer)
> + * - Event queue (base, producer, consumer)
> + * - Stream table configuration (base, format)
> + *
> + * Addresses are adjusted based on security space offset.
> + */
> +void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp);
> +
> +/*
> + * qsmmu_program_regs - Program all required SMMU register banks
> + *
> + * @qts: QTest state handle
> + * @smmu_base: SMMU base address
> + * @space: Target security space
> + *
> + * Programs SMMU registers for the requested security space which is called in
> + * qsmmu_setup_and_enable_translation. Always programs Non-Secure bank first,
> + * then the target space if different.
> + */
> +void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace space);
> +
> +uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx);
> +
> +/* qsmmu_expected_dma_result - Calculate expected DMA result */
> +uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx);
> +
> +/*
> + * qsmmu_validate_test_result - Validate actual VS expected test result
> + *
> + * @ctx: Test context containing actual and expected results
> + *
> + * Returns: true if test passed (actual == expected), false otherwise
> + *
> + * Compares the actual DMA result with the expected result and logs
> + * the comparison for debugging purposes.
> + */
> +bool qsmmu_validate_test_result(QSMMUTestContext *ctx);
> +
> +/*
> + * qsmmu_setup_translation_tables - Setup complete SMMU page table hierarchy
> + *
> + * @qts: QTest state handle
> + * @iova: Input Virtual Address or IPA to translate
> + * @space: Security space (NONSECURE, SECURE, REALM, ROOT)
> + * @is_cd: Whether translating CD address (vs regular IOVA)
> + * @mode: Translation mode (S1_ONLY, S2_ONLY, NESTED)
> + *
> + * This function builds the complete page table structure for translating
> + * the given IOVA through the SMMU. The structure varies based on mode:
> + *
> + * - S1_ONLY: Single Stage 1 walk (IOVA -> PA)
> + * - S2_ONLY: Single Stage 2 walk (IPA -> PA)
> + * - NESTED: Stage 1 walk (IOVA -> IPA) with nested S2 walks for each
> + * S1 table access, plus final S2 walk for the result IPA
> + *
> + * For nested mode, this creates a complex hierarchy:
> + * - 4 Stage 1 levels (L0-L3), each requiring a 4-level Stage 2 walk
> + * - 1 final Stage 2 walk for the resulting IPA
> + *
> + * The function writes all necessary Page Table Entries (PTEs) to guest
> + * memory using qtest_writeq(), setting up the complete translation path
> + * that the SMMU hardware will traverse during DMA operations.
> + */
> +void qsmmu_setup_translation_tables(QTestState *qts,
> + uint64_t iova,
> + QSMMUSpace space,
> + bool is_cd,
> + QSMMUTransMode mode);
> +
> +/* High-level test execution helpers */
> +void qsmmu_single_translation(QSMMUTestContext *ctx);
> +void qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev,
> + QPCIBar bar, uint64_t smmu_base,
> + const QSMMUTestConfig *cfg);
> +
> +#endif /* QTEST_LIBQOS_SMMUV3_H */
Thanks
Eric
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library
2026-01-17 10:02 ` Eric Auger
@ 2026-01-17 13:06 ` Tao Tang
0 siblings, 0 replies; 35+ messages in thread
From: Tao Tang @ 2026-01-17 13:06 UTC (permalink / raw)
To: eric.auger, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Mostafa Saleh, CLEMENT MATHIEU--DRIF
Hi Eric,
On 2026/1/17 18:02, Eric Auger wrote:
> Hi Tao,
>
> On 12/24/25 4:46 AM, Tao Tang wrote:
>> Introduce qos-smmuv3, a reusable library for SMMUv3-related qtest
>> operations. This module encapsulates common tasks like:
>>
>> - SMMUv3 initialization (enabling, configuring command/event queues)
>> - Stream Table Entry (STE) and Context Descriptor (CD) setup
>> - Multi-level page table construction (L0-L3 for 4KB granules)
>> - Support for Stage 1, Stage 2, and nested translation modes
>> - Could be easily extended to support multi-space testing infrastructure
>> (Non-Secure, Secure, Root, Realm)
>>
>> The library provides high-level abstractions that allow test code to
>> focus on IOMMU behavior validation rather than low-level register
>> manipulation and page table encoding. Key features include:
>>
>> - Provide memory allocation for translation structures with proper
>> alignment
>> - Helper functions to build valid STEs/CDs for different translation
>> scenarios
>> - Page table walkers that handle address offset calculations per
>> security space
>>
>> This infrastructure is designed to be used by iommu-testdev-based tests
>> and future SMMUv3 test suites, reducing code duplication and improving
>> test maintainability.
>>
>> Signed-off-by: Tao Tang<tangtao1634@phytium.com.cn>
>> Reviewed-by: Pierrick Bouvier<pierrick.bouvier@linaro.org>
>> Reviewed-by: Fabiano Rosas<farosas@suse.de>
>> ---
>> MAINTAINERS | 6 +
>> tests/qtest/libqos/meson.build | 3 +
>> tests/qtest/libqos/qos-smmuv3.c | 633 ++++++++++++++++++++++++++++++++
>> tests/qtest/libqos/qos-smmuv3.h | 256 +++++++++++++
>> 4 files changed, 898 insertions(+)
>> create mode 100644 tests/qtest/libqos/qos-smmuv3.c
>> create mode 100644 tests/qtest/libqos/qos-smmuv3.h
>>
>> ------------------------------<snip>------------------------------
>>
>>
>>
>> ------------------------------<snip>------------------------------
>> +
>> +uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx)
> qsmmu_trigger_dma and qsmmu_single_translation helpers which do not really relate to SMMU but rather relate to the pci
> test dev. put them in a specific section of this file or move them in a
> separate file. They could be reused by other non SMMU specific tests
Agreed — that’s a better separation. I’ll move these two helpers out of
qos-smmuv3 into a generic libqos helper (something like
qos-iommu-testdev.[ch]).
>
>> +{
>> + uint32_t result, attrs_val;
>> +
>> + /* Program DMA parameters */
>> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_LO,
>> + (uint32_t)ctx->config.dma_iova);
>> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_HI,
>> + (uint32_t)(ctx->config.dma_iova >> 32));
>> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_LEN,
>> + ctx->config.dma_len);
>> +
>> + /* Build and write DMA attributes based on device security state. */
>> + attrs_val = qsmmu_build_dma_attrs(QSMMU_SPACE_NONSECURE);
>> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_ATTRS, attrs_val);
>> +
>> + /* Flip status */
>> + /* Arm iommu-testdev so the next read triggers DMA */
>> + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_DBELL, ITD_DMA_DBELL_ARM);
>> +
>> + /* Trigger DMA */
>> + qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_TRIGGERING);
>> +
>> + /* Poll for DMA completion */
>> + for (int i = 0; i < 1000; i++) {
>> + result = qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_RESULT);
>> + if (result != ITD_DMA_RESULT_BUSY) {
>> + ctx->dma_result = result;
>> + break;
>> + }
>> + g_usleep(1000);
>> + }
>> +
>> + /* Fallback for timeout */
>> + if (ctx->dma_result == ITD_DMA_RESULT_BUSY) {
>> + ctx->dma_result = ITD_DMA_ERR_TX_FAIL;
>> + }
>> +
>> + return ctx->dma_result;
>> +}
>> +
>> +bool qsmmu_validate_test_result(QSMMUTestContext *ctx)
>> +{
>> + uint32_t expected = qsmmu_expected_dma_result(ctx);
>> + g_test_message("-> Validating result: expected=0x%x actual=0x%x",
>> + expected, ctx->dma_result);
>> + return (ctx->dma_result == expected);
>> +}
>> +
>> +QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid)
>> +{
>> + switch (sec_sid) {
>> + case QSMMU_SEC_SID_NONSECURE:
>> + return QSMMU_SPACE_NONSECURE;
>> + default:
>> + g_assert_not_reached();
>> + }
>> +}
>> +
>> +uint64_t qsmmu_space_offset(QSMMUSpace sp)
>> +{
>> + switch (sp) {
>> + case QSMMU_SPACE_NONSECURE:
>> + return QSMMU_SPACE_OFFS_NS;
>> + default:
>> + g_assert_not_reached();
>> + }
>> +}
>> +
>> +void qsmmu_single_translation(QSMMUTestContext *ctx)
>> +{
>> + uint32_t config_result;
>> + uint32_t dma_result;
>> +
>> + /* Configure SMMU translation */
>> + config_result = qsmmu_setup_and_enable_translation(ctx);
>> + g_assert_cmpuint(config_result, ==, 0);
>> +
>> + /* Trigger DMA operation */
>> + dma_result = qsmmu_trigger_dma(ctx);
>> + if (dma_result != 0) {
>> + g_test_message("DMA failed: mode=%u result=0x%x",
>> + ctx->config.trans_mode, dma_result);
>> + } else {
>> + g_test_message("-> DMA succeeded: mode=%u", ctx->config.trans_mode);
>> + }
>> +
>> + /* Validate test result */
>> + g_assert_true(qsmmu_validate_test_result(ctx));
>> +}
>> +
>> ------------------------------<snip>------------------------------
>>
>>
>>
>> ------------------------------<snip>------------------------------
>> diff --git a/tests/qtest/libqos/qos-smmuv3.h b/tests/qtest/libqos/qos-smmuv3.h
>> new file mode 100644
>> index 0000000000..375778517f
>> --- /dev/null
>> +++ b/tests/qtest/libqos/qos-smmuv3.h
>> @@ -0,0 +1,256 @@
>> +/*
>> + * QOS SMMUv3 Module
>> + *
>> + * This module provides SMMUv3-specific helper functions for libqos tests,
>> + * encapsulating SMMUv3 setup, and assertions.
>> + *
>> + * Copyright (c) 2025 Phytium Technology
>> + *
>> + * Author:
>> + * Tao Tang<tangtao1634@phytium.com.cn>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef QTEST_LIBQOS_SMMUV3_H
>> +#define QTEST_LIBQOS_SMMUV3_H
>> +
>> +#include "hw/misc/iommu-testdev.h"
>> +
>> +/*
>> + * SMMU MMIO register base. We hardcode this as base_memmap is 'static const'
>> + * in hw/arm/virt.c and not directly accessible here.
>> + */
>> +#define VIRT_SMMU_BASE 0x0000000009050000ull
>> +
>> +/* SMMU queue and table base addresses */
>> +#define QSMMU_CMDQ_BASE_ADDR 0x000000000e16b000ull
>> +#define QSMMU_EVENTQ_BASE_ADDR 0x000000000e170000ull
>> +
>> +/*
>> + * Translation tables and descriptors for a mapping of
>> + * - IOVA(Stage 1 only)
> you mentionned you also support S1 + S2
Yes there should be `- IOVA(Stage 1 and nested translation stage)`.
>> + * - IPA (Stage 2 only)
>> + * to GPA.
>> + *
>> + * The translation is based on the Arm architecture with the following
>> + * prerequisites:
>> + * - Granule size: 4KB pages.
> would be nice to support 64kB as well in the future. Can be done later
> though.
>> + * - Page table levels: 4 levels (L0, L1, L2, L3), starting at level 0.
>> + * - IOVA size: The walk resolves a IOVA: 0x8080604567
> well this is passed as a test config parameter and this is does not
> sound relevant to hardcode it.
I initially wanted dma_iova to be configurable and have the page-table
builder follow it, but I didn’t find a clean way to keep the table
construction in sync.
So for now I’ll likely drop QSMMUTestConfig.dma_iova and consistently
use the fixed QSMMU_IOVA for both table setup and DMA, to avoid a
misleading knob. We can revisit later to make IOVA + tables
configurable, and potentially add 64KB granule support as well.
Thanks,
Tao
^ permalink raw reply [flat|nested] 35+ messages in thread
* [RFC v8 7/7] tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
` (5 preceding siblings ...)
2025-12-24 3:46 ` [RFC v8 6/7] tests/qtest/libqos: Add SMMUv3 helper library Tao Tang
@ 2025-12-24 3:46 ` Tao Tang
2025-12-26 20:44 ` Fabiano Rosas
2026-01-17 10:04 ` Eric Auger
2025-12-24 17:07 ` [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Pierrick Bouvier
2026-01-14 13:30 ` Tao Tang
8 siblings, 2 replies; 35+ messages in thread
From: Tao Tang @ 2025-12-24 3:46 UTC (permalink / raw)
To: Paolo Bonzini, Fabiano Rosas, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF, Tao Tang
Add a qtest suite that validates ARM SMMUv3 translation without guest
firmware or OS. The tests leverage iommu-testdev to trigger DMA
operations and the qos-smmuv3 library to configure IOMMU translation
structures.
This test suite targets the virt machine and covers:
- Stage 1 only translation (VA -> PA via CD page tables)
- Stage 2 only translation (IPA -> PA via STE S2 tables)
- Nested translation (VA -> IPA -> PA, Stage 1 + Stage 2)
- Design to extended to support multiple security spaces
(Non-Secure, Secure, Root, Realm)
Each test case follows this sequence:
1. Initialize SMMUv3 with appropriate command/event queues
2. Build translation tables (STE/CD/PTE) for the target scenario
3. Configure iommu-testdev with IOVA and DMA attributes via MMIO
4. Trigger DMA and validate successful translation
5. Verify data integrity through a deterministic write-read pattern
This bare-metal approach provides deterministic IOMMU testing with
minimal dependencies, making failures directly attributable to the SMMU
translation path.
Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
Tested-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
---
tests/qtest/iommu-smmuv3-test.c | 121 ++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 122 insertions(+)
create mode 100644 tests/qtest/iommu-smmuv3-test.c
diff --git a/tests/qtest/iommu-smmuv3-test.c b/tests/qtest/iommu-smmuv3-test.c
new file mode 100644
index 0000000000..b612f5ca6c
--- /dev/null
+++ b/tests/qtest/iommu-smmuv3-test.c
@@ -0,0 +1,121 @@
+/*
+ * QTest for SMMUv3 with iommu-testdev
+ *
+ * This QTest file is used to test the SMMUv3 with iommu-testdev so that we can
+ * test SMMUv3 without any guest kernel or firmware.
+ *
+ * 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 "libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/generic-pcihost.h"
+#include "hw/pci/pci_regs.h"
+#include "hw/misc/iommu-testdev.h"
+#include "libqos/qos-smmuv3.h"
+
+#define DMA_LEN 4
+
+static void save_fn(QPCIDevice *dev, int devfn, void *data)
+{
+ QPCIDevice **pdev = (QPCIDevice **) data;
+
+ *pdev = dev;
+}
+
+static QPCIDevice *setup_qtest_pci_device(QTestState *qts, QGenericPCIBus *gbus,
+ QPCIBar *bar)
+{
+ QPCIDevice *dev = NULL;
+
+ qpci_init_generic(gbus, qts, NULL, false);
+
+ qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID,
+ IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev);
+ g_assert(dev);
+
+ qpci_device_enable(dev);
+ *bar = qpci_iomap(dev, 0, NULL);
+ g_assert_false(bar->is_io);
+
+ return dev;
+}
+
+static void run_smmuv3_translation(const QSMMUTestConfig *cfg)
+{
+ QTestState *qts;
+ QGenericPCIBus gbus;
+ QPCIDevice *dev;
+ QPCIBar bar;
+
+ /* Initialize QEMU environment for SMMU testing */
+ qts = qtest_init("-machine virt,acpi=off,gic-version=3,iommu=smmuv3 "
+ "-smp 1 -m 512 -cpu max -net none "
+ "-device iommu-testdev");
+
+ /* Setup and configure PCI device */
+ dev = setup_qtest_pci_device(qts, &gbus, &bar);
+ g_assert(dev);
+
+ g_test_message("### SMMUv3 translation mode=%d sec_sid=%d ###",
+ cfg->trans_mode, cfg->sec_sid);
+ qsmmu_run_translation_case(qts, dev, bar, VIRT_SMMU_BASE, cfg);
+ qtest_quit(qts);
+}
+
+static void test_smmuv3_ns_s1_only(void)
+{
+ QSMMUTestConfig cfg = {
+ .trans_mode = QSMMU_TM_S1_ONLY,
+ .sec_sid = QSMMU_SEC_SID_NONSECURE,
+ .dma_iova = QSMMU_IOVA,
+ .dma_len = DMA_LEN,
+ .expected_result = 0,
+ };
+
+ run_smmuv3_translation(&cfg);
+}
+
+static void test_smmuv3_ns_s2_only(void)
+{
+ QSMMUTestConfig cfg = {
+ .trans_mode = QSMMU_TM_S2_ONLY,
+ .sec_sid = QSMMU_SEC_SID_NONSECURE,
+ .dma_iova = QSMMU_IOVA,
+ .dma_len = DMA_LEN,
+ .expected_result = 0,
+ };
+
+ run_smmuv3_translation(&cfg);
+}
+
+static void test_smmuv3_ns_nested(void)
+{
+ QSMMUTestConfig cfg = {
+ .trans_mode = QSMMU_TM_NESTED,
+ .sec_sid = QSMMU_SEC_SID_NONSECURE,
+ .dma_iova = QSMMU_IOVA,
+ .dma_len = DMA_LEN,
+ .expected_result = 0,
+ };
+
+ run_smmuv3_translation(&cfg);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/iommu-testdev/translation/ns-s1-only",
+ test_smmuv3_ns_s1_only);
+ qtest_add_func("/iommu-testdev/translation/ns-s2-only",
+ test_smmuv3_ns_s2_only);
+ qtest_add_func("/iommu-testdev/translation/ns-nested",
+ test_smmuv3_ns_nested);
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 669d07c06b..e2d2e68092 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -263,6 +263,7 @@ qtests_aarch64 = \
config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \
(config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed64 : []) + \
(config_all_devices.has_key('CONFIG_NPCM8XX') ? qtests_npcm8xx : []) + \
+ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') ? ['iommu-smmuv3-test'] : []) + \
qtests_cxl + \
['arm-cpu-features',
'numa-test',
--
2.34.1
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [RFC v8 7/7] tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev
2025-12-24 3:46 ` [RFC v8 7/7] tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev Tao Tang
@ 2025-12-26 20:44 ` Fabiano Rosas
2026-01-17 10:04 ` Eric Auger
1 sibling, 0 replies; 35+ messages in thread
From: Fabiano Rosas @ 2025-12-26 20:44 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF, Tao Tang
Tao Tang <tangtao1634@phytium.com.cn> writes:
> Add a qtest suite that validates ARM SMMUv3 translation without guest
> firmware or OS. The tests leverage iommu-testdev to trigger DMA
> operations and the qos-smmuv3 library to configure IOMMU translation
> structures.
>
> This test suite targets the virt machine and covers:
> - Stage 1 only translation (VA -> PA via CD page tables)
> - Stage 2 only translation (IPA -> PA via STE S2 tables)
> - Nested translation (VA -> IPA -> PA, Stage 1 + Stage 2)
> - Design to extended to support multiple security spaces
> (Non-Secure, Secure, Root, Realm)
>
> Each test case follows this sequence:
> 1. Initialize SMMUv3 with appropriate command/event queues
> 2. Build translation tables (STE/CD/PTE) for the target scenario
> 3. Configure iommu-testdev with IOVA and DMA attributes via MMIO
> 4. Trigger DMA and validate successful translation
> 5. Verify data integrity through a deterministic write-read pattern
>
> This bare-metal approach provides deterministic IOMMU testing with
> minimal dependencies, making failures directly attributable to the SMMU
> translation path.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Tested-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 7/7] tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev
2025-12-24 3:46 ` [RFC v8 7/7] tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev Tao Tang
2025-12-26 20:44 ` Fabiano Rosas
@ 2026-01-17 10:04 ` Eric Auger
2026-01-17 13:31 ` Tao Tang
1 sibling, 1 reply; 35+ messages in thread
From: Eric Auger @ 2026-01-17 10:04 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Tao,
On 12/24/25 4:46 AM, Tao Tang wrote:
> Add a qtest suite that validates ARM SMMUv3 translation without guest
> firmware or OS. The tests leverage iommu-testdev to trigger DMA
> operations and the qos-smmuv3 library to configure IOMMU translation
> structures.
>
> This test suite targets the virt machine and covers:
> - Stage 1 only translation (VA -> PA via CD page tables)
> - Stage 2 only translation (IPA -> PA via STE S2 tables)
> - Nested translation (VA -> IPA -> PA, Stage 1 + Stage 2)
> - Design to extended to support multiple security spaces
> (Non-Secure, Secure, Root, Realm)
>
> Each test case follows this sequence:
> 1. Initialize SMMUv3 with appropriate command/event queues
> 2. Build translation tables (STE/CD/PTE) for the target scenario
> 3. Configure iommu-testdev with IOVA and DMA attributes via MMIO
> 4. Trigger DMA and validate successful translation
> 5. Verify data integrity through a deterministic write-read pattern
>
> This bare-metal approach provides deterministic IOMMU testing with
> minimal dependencies, making failures directly attributable to the SMMU
> translation path.
>
> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
> Tested-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
> ---
> tests/qtest/iommu-smmuv3-test.c | 121 ++++++++++++++++++++++++++++++++
> tests/qtest/meson.build | 1 +
> 2 files changed, 122 insertions(+)
> create mode 100644 tests/qtest/iommu-smmuv3-test.c
>
> diff --git a/tests/qtest/iommu-smmuv3-test.c b/tests/qtest/iommu-smmuv3-test.c
> new file mode 100644
> index 0000000000..b612f5ca6c
> --- /dev/null
> +++ b/tests/qtest/iommu-smmuv3-test.c
> @@ -0,0 +1,121 @@
> +/*
> + * QTest for SMMUv3 with iommu-testdev
> + *
> + * This QTest file is used to test the SMMUv3 with iommu-testdev so that we can
> + * test SMMUv3 without any guest kernel or firmware.
> + *
> + * 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 "libqtest.h"
> +#include "libqos/pci.h"
> +#include "libqos/generic-pcihost.h"
> +#include "hw/pci/pci_regs.h"
> +#include "hw/misc/iommu-testdev.h"
> +#include "libqos/qos-smmuv3.h"
> +
> +#define DMA_LEN 4
> +
> +static void save_fn(QPCIDevice *dev, int devfn, void *data)
> +{
> + QPCIDevice **pdev = (QPCIDevice **) data;
> +
> + *pdev = dev;
> +}
> +
> +static QPCIDevice *setup_qtest_pci_device(QTestState *qts, QGenericPCIBus *gbus,
> + QPCIBar *bar)
> +{
> + QPCIDevice *dev = NULL;
> +
> + qpci_init_generic(gbus, qts, NULL, false);
> +
> + qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID,
> + IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev);
> + g_assert(dev);
> +
> + qpci_device_enable(dev);
> + *bar = qpci_iomap(dev, 0, NULL);
> + g_assert_false(bar->is_io);
> +
> + return dev;
> +}
> +
> +static void run_smmuv3_translation(const QSMMUTestConfig *cfg)
> +{
> + QTestState *qts;
> + QGenericPCIBus gbus;
> + QPCIDevice *dev;
> + QPCIBar bar;
> +
> + /* Initialize QEMU environment for SMMU testing */
> + qts = qtest_init("-machine virt,acpi=off,gic-version=3,iommu=smmuv3 "
> + "-smp 1 -m 512 -cpu max -net none "
> + "-device iommu-testdev");
> +
> + /* Setup and configure PCI device */
> + dev = setup_qtest_pci_device(qts, &gbus, &bar);
> + g_assert(dev);
> +
> + g_test_message("### SMMUv3 translation mode=%d sec_sid=%d ###",
> + cfg->trans_mode, cfg->sec_sid);
> + qsmmu_run_translation_case(qts, dev, bar, VIRT_SMMU_BASE, cfg);
> + qtest_quit(qts);
> +}
> +
> +static void test_smmuv3_ns_s1_only(void)
> +{
> + QSMMUTestConfig cfg = {
> + .trans_mode = QSMMU_TM_S1_ONLY,
> + .sec_sid = QSMMU_SEC_SID_NONSECURE,
> + .dma_iova = QSMMU_IOVA,
> + .dma_len = DMA_LEN,
> + .expected_result = 0,
> + };
> +
> + run_smmuv3_translation(&cfg);
> +}
> +
> +static void test_smmuv3_ns_s2_only(void)
> +{
> + QSMMUTestConfig cfg = {
> + .trans_mode = QSMMU_TM_S2_ONLY,
> + .sec_sid = QSMMU_SEC_SID_NONSECURE,
> + .dma_iova = QSMMU_IOVA,
> + .dma_len = DMA_LEN,
> + .expected_result = 0,
> + };
> +
> + run_smmuv3_translation(&cfg);
> +}
> +
> +static void test_smmuv3_ns_nested(void)
> +{
> + QSMMUTestConfig cfg = {
> + .trans_mode = QSMMU_TM_NESTED,
> + .sec_sid = QSMMU_SEC_SID_NONSECURE,
> + .dma_iova = QSMMU_IOVA,
> + .dma_len = DMA_LEN,
> + .expected_result = 0,
> + };
> +
> + run_smmuv3_translation(&cfg);
> +}
> +
> +int main(int argc, char **argv)
> +{
> + g_test_init(&argc, &argv, NULL);
> + qtest_add_func("/iommu-testdev/translation/ns-s1-only",
> + test_smmuv3_ns_s1_only);
> + qtest_add_func("/iommu-testdev/translation/ns-s2-only",
> + test_smmuv3_ns_s2_only);
> + qtest_add_func("/iommu-testdev/translation/ns-nested",
> + test_smmuv3_ns_nested);
> + return g_test_run();
> +}
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 669d07c06b..e2d2e68092 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -263,6 +263,7 @@ qtests_aarch64 = \
> config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \
> (config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed64 : []) + \
> (config_all_devices.has_key('CONFIG_NPCM8XX') ? qtests_npcm8xx : []) + \
> + (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') ? ['iommu-smmuv3-test'] : []) + \
One question: since it can only run along with VIRT machine, how do we
make sure this only runs with that machine enabled?
Thanks
Eric
> qtests_cxl + \
> ['arm-cpu-features',
> 'numa-test',
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 7/7] tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev
2026-01-17 10:04 ` Eric Auger
@ 2026-01-17 13:31 ` Tao Tang
0 siblings, 0 replies; 35+ messages in thread
From: Tao Tang @ 2026-01-17 13:31 UTC (permalink / raw)
To: eric.auger, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
Hi Eric,
On 2026/1/17 18:04, Eric Auger wrote:
> Hi Tao,
>
> On 12/24/25 4:46 AM, Tao Tang wrote:
>> Add a qtest suite that validates ARM SMMUv3 translation without guest
>> firmware or OS. The tests leverage iommu-testdev to trigger DMA
>> operations and the qos-smmuv3 library to configure IOMMU translation
>> structures.
>>
>> This test suite targets the virt machine and covers:
>> - Stage 1 only translation (VA -> PA via CD page tables)
>> - Stage 2 only translation (IPA -> PA via STE S2 tables)
>> - Nested translation (VA -> IPA -> PA, Stage 1 + Stage 2)
>> - Design to extended to support multiple security spaces
>> (Non-Secure, Secure, Root, Realm)
>>
>> Each test case follows this sequence:
>> 1. Initialize SMMUv3 with appropriate command/event queues
>> 2. Build translation tables (STE/CD/PTE) for the target scenario
>> 3. Configure iommu-testdev with IOVA and DMA attributes via MMIO
>> 4. Trigger DMA and validate successful translation
>> 5. Verify data integrity through a deterministic write-read pattern
>>
>> This bare-metal approach provides deterministic IOMMU testing with
>> minimal dependencies, making failures directly attributable to the SMMU
>> translation path.
>>
>> Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn>
>> Tested-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
>> ---
>> tests/qtest/iommu-smmuv3-test.c | 121 ++++++++++++++++++++++++++++++++
>> tests/qtest/meson.build | 1 +
>> 2 files changed, 122 insertions(+)
>> create mode 100644 tests/qtest/iommu-smmuv3-test.c
>>
>> ------------------------------<snip>------------------------------
>>
>>
>>
>> ------------------------------<snip>------------------------------
>> +
>> +int main(int argc, char **argv)
>> +{
>> + g_test_init(&argc, &argv, NULL);
>> + qtest_add_func("/iommu-testdev/translation/ns-s1-only",
>> + test_smmuv3_ns_s1_only);
>> + qtest_add_func("/iommu-testdev/translation/ns-s2-only",
>> + test_smmuv3_ns_s2_only);
>> + qtest_add_func("/iommu-testdev/translation/ns-nested",
>> + test_smmuv3_ns_nested);
>> + return g_test_run();
>> +}
>> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
>> index 669d07c06b..e2d2e68092 100644
>> --- a/tests/qtest/meson.build
>> +++ b/tests/qtest/meson.build
>> @@ -263,6 +263,7 @@ qtests_aarch64 = \
>> config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \
>> (config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed64 : []) + \
>> (config_all_devices.has_key('CONFIG_NPCM8XX') ? qtests_npcm8xx : []) + \
>> + (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') ? ['iommu-smmuv3-test'] : []) + \
> One question: since it can only run along with VIRT machine, how do we
> make sure this only runs with that machine enabled?
Thanks for the catch. I'll address this in two places:
- Build-time: the test is now added only when both CONFIG_IOMMU_TESTDEV
and CONFIG_ARM_VIRT are enabled in meson.build.
- Run-time: the test checks qtest_has_machine("virt") and calls
g_test_skip() if the virt machine is not available.
This ensures the test is only built/selected when virt is present and
also skips cleanly if the machine isn’t supported at runtime.
Best regards,
Tao
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
` (6 preceding siblings ...)
2025-12-24 3:46 ` [RFC v8 7/7] tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev Tao Tang
@ 2025-12-24 17:07 ` Pierrick Bouvier
2026-01-14 13:30 ` Tao Tang
8 siblings, 0 replies; 35+ messages in thread
From: Pierrick Bouvier @ 2025-12-24 17:07 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Eric Auger, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Philippe Mathieu-Daudé,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
On 12/23/25 7:46 PM, Tao Tang wrote:
> Changes since v7: Following Pierrick’s feedback, this version fixes the
> uint64_t formatting issue
> (switching to PRIx64 to avoid %lx mismatches on non-LP64 platforms). I also
> validated the series with Pierrick’s qemu-ci workflow, and all jobs passed;
> the CI run is available at:
>
> https://github.com/hnusdr/qemu/actions/runs/20476259310
>
> Motivation
> ----------
>
> Currently, thoroughly testing IOMMU emulation (e.g., ARM SMMUv3) requires
> a significant software stack. We need to boot a full guest operating
> system (like Linux) with the appropriate drivers (e.g., IOMMUFD) and rely
> on firmware (e.g., ACPI with IORT tables or Hafnium) to correctly
> configure the IOMMU and orchestrate DMA from a peripheral device.
>
> This dependency on a complex software stack presents several challenges:
>
> * High Barrier to Entry: Writing targeted tests for specific IOMMU
> features (like fault handling, specific translation regimes, etc.)
> becomes cumbersome.
>
> * Difficult to Debug: It's hard to distinguish whether a bug originates
> from the IOMMU emulation itself, the guest driver, the firmware
> tables, or the guest kernel's configuration.
>
> * Slow Iteration: The need to boot a full guest OS slows down the
> development and testing cycle.
>
> The primary goal of this work is to create a lightweight, self-contained
> testing environment that allows us to exercise the IOMMU's core logic
> directly at the qtest level, removing the need for any guest-side software.
>
> Our Approach: A Dedicated Test Framework
> -----------------------------------------
>
> To achieve this, we introduce three main components:
>
> * A minimal hardware device: iommu-testdev
> * A reusable IOMMU helper library: libqos/qos-smmuv3
> * A comprehensive qtest suite: iommu-smmuv3-test
>
> The iommu-testdev is intentionally not a conformant, general-purpose PCIe
> or platform device. It is a purpose-built, highly simplified "DMA engine"
> designed to be analogous to a minimal PCIe Root Complex that bypasses the
> full, realistic topology (Host Bridges, Switches, Endpoints) to provide a
> direct, programmable path for a DMA request to reach the IOMMU. Its sole
> purpose is to trigger a DMA transaction when its registers are written to,
> making it perfectly suited for direct control from a test environment like
> qtest.
>
> The Qtest Framework
> -------------------
>
> The new qtest (iommu-smmuv3-test.c) serves as the "bare-metal driver"
> for both the IOMMU and the iommu-testdev. It leverages the libqos helper
> library to manually perform all the setup that would typically be handled
> by the guest kernel and firmware, but in a completely controlled and
> predictable manner:
>
> 1. IOMMU Configuration: It directly initializes the SMMU's registers to a
> known state using helper functions from qos-smmuv3.
>
> 2. Translation Structure Setup: It uses the libqos library to construct
> the necessary translation structures in memory, including Stream Table
> Entries (STEs), Context Descriptors (CDs), and Page Tables (PTEs).
>
> 3. DMA Trigger: It programs the iommu-testdev to initiate a DMA operation
> targeting a specific IOVA with configurable attributes.
>
> 4. Verification: It waits for the transaction to complete and verifies
> that the memory was accessed correctly after address translation by
> the IOMMU.
>
> This framework provides a solid and extensible foundation for validating
> the IOMMU's core translation paths. The current test suite covers:
>
> - Stage 1 only translation (VA -> PA via CD page tables)
> - Stage 2 only translation (IPA -> PA via STE S2 tables)
> - Nested translation (VA -> IPA -> PA, Stage 1 + Stage 2)
>
> The infrastructure is designed to be extended to support additional
> security spaces and IOMMU features.
>
>
> Testing:
> --------
> QTEST_QEMU_BINARY=./build/qemu-system-aarch64 \
> ./build/tests/qtest/iommu-smmuv3-test --tap -k
>
>
> If you want to check coverage report, please follow instructions below
> which are shared by Pierrick in previous review [1]
>
> # install gcovr if not already installed, `sudo apt install gcovr`
> # on ubuntu for example
> $ export CFLAGS="--coverage"
> $ ./configure --target-list=aarch64-softmmu
> $ ninja -C build
> $ QTEST_QEMU_BINARY=./build/qemu-system-aarch64 \
> ./build/tests/qtest/iommu-smmuv3-test
> $ rm -rf build/coverage_html
> $ mkdir build/coverage_html
> $ gcovr \
> --gcov-ignore-parse-errors suspicious_hits.warn \
> --gcov-ignore-parse-errors negative_hits.warn \
> --merge-mode-functions=separate \
> --html-details build/coverage_html/index.html \
> --filter 'hw/arm/smmu*'
> # check the version of gcovr if meeting some unsupported options error.
> # Upgrading to 8.4 version may resolve the error.
> $ gcovr --version
> $ pip install --user --upgrade gcovr
> $ echo file://$(pwd)/build/coverage_html/index.html
> # open this in browser by clicking on your terminal
>
> [1] https://lore.kernel.org/qemu-devel/a361b46f-2173-4c98-a5d3-6b4d2ac004af@linaro.org/
>
>
> Major Changes from v7 to v8:
> -----------------------------
> - Fix uint64_t formatting issue by using PRIx64 for portability
> - Validate the series with Pierrick's qemu-ci workflow; all jobs passed
> - This series is also saved in github repo [5]
> [5] https://github.com/hnusdr/qemu/tree/iommu-testdev-v8-community
>
>
> Major Changes from v6 to v7:
> -----------------------------
> - Split the smmuv3-common.h work into smaller patches
> (registerfields conversion, NSCFG bits, helper setters)
> - Add MAINTAINERS entries for iommu-testdev and qos-smmuv3 helpers
> - Tighten documentation wording and update the DMA test pattern value
> - Simplify qtest PCI device discovery and rename QSMMU_IOVA constant
> - This series is also saved in github repo [4]
>
> [4] https://github.com/hnusdr/qemu/tree/iommu-testdev-v7-community
>
>
> Major Changes from v5 to v6:
> -----------------------------
> - Split batch testing into individual test cases per translation mode
> - Removed cleanup functions and related codes or comments
> - Improved error handling with explicit assertions
> - Add coverage build and test instructions shared by Pierrick
> - Keep Reviewed-by and Tested-by from Pierrick's review
> - This series is also saved in github repo [2]
>
> [2] https://github.com/hnusdr/qemu/tree/iommu-testdev-v6-community
>
>
> Major Changes from v4 to v5:
> -----------------------------
> - Remove a duplicated patch that was accidentally included in v4.
>
>
> Major Changes from v3 to v4:
> -----------------------------
>
> 1. Added shared smmuv3-common.h so both the device and libqos consume the same
> STE/CD/register definitions as Alex suggested [3]
> 2. Slimmed iommu-testdev down to a pure DMA trigger with a tighter MMIO
> contract (new doorbell helper, simplified attributes/errors).
> 3. Updated `qos-smmuv3` and the qtest so they include the common header,
> honor per-test expected results, and rely solely on the streamlined device
> interface.
> 4. Compacted changes of v2 to v3.
>
> [3] https://lore.kernel.org/qemu-devel/87zf8jk244.fsf@draig.linaro.org/
>
>
> Major Changes from v2 to v3:
> -----------------------------
>
> 1. Generalization/Renaming: rebranded `smmu-testdev` → `iommu-testdev` (code,
> headers, docs) to reflect the broadened scope.
> 2. Separation of concerns: iommu-testdev is now a pure DMA trigger; all
> SMMUv3-specific setup (STE/CD/page tables, multi-mode support, space offsets)
> lives in `qos-smmuv3.{c,h}` and is consumed by the new qtest.
> 3. Improved modularity & coverage: the stacked design (device + helper + qtest)
> made it straightforward to add S1/S2/Nested tests, a cleaner config system,
> and clearer validation logic.
> 4. Code/documentation quality: added tracepoints, better error handling/naming,
> and refreshed `docs/specs/iommu-testdev.rst` with the new layout.
>
> Future Work
> -----------
>
> The current implementation focuses on basic translation path validation
> in the Non-Secure address space. Future extensions could include:
>
> * Multi-space testing (Secure, Root, Realm) for SMMUv3
> * Support for other IOMMU types (Intel VT-d, AMD-Vi, RISC-V IOMMU)
>
>
> Tao Tang (7):
> hw/arm/smmuv3: Extract common definitions to smmuv3-common.h
> hw/arm/smmuv3-common: Define STE/CD fields via registerfields
> hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing
> hw/arm/smmuv3-common: Add NSCFG bit definition for CD
> hw/arm/smmuv3-common: Add STE/CD set helpers for repeated field setup
> tests/qtest/libqos: Add SMMUv3 helper library
> tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev
>
> MAINTAINERS | 13 +
> docs/specs/index.rst | 1 +
> docs/specs/iommu-testdev.rst | 112 ++++++
> hw/arm/smmuv3-internal.h | 255 +------------
> hw/misc/Kconfig | 5 +
> hw/misc/iommu-testdev.c | 271 ++++++++++++++
> hw/misc/meson.build | 1 +
> hw/misc/trace-events | 10 +
> include/hw/arm/smmuv3-common.h | 423 +++++++++++++++++++++
> include/hw/misc/iommu-testdev.h | 68 ++++
> tests/qtest/iommu-smmuv3-test.c | 121 ++++++
> tests/qtest/libqos/meson.build | 3 +
> tests/qtest/libqos/qos-smmuv3.c | 633 ++++++++++++++++++++++++++++++++
> tests/qtest/libqos/qos-smmuv3.h | 256 +++++++++++++
> tests/qtest/meson.build | 1 +
> 15 files changed, 1919 insertions(+), 254 deletions(-)
> create mode 100644 docs/specs/iommu-testdev.rst
> create mode 100644 hw/misc/iommu-testdev.c
> create mode 100644 include/hw/arm/smmuv3-common.h
> create mode 100644 include/hw/misc/iommu-testdev.h
> create mode 100644 tests/qtest/iommu-smmuv3-test.c
> create mode 100644 tests/qtest/libqos/qos-smmuv3.c
> create mode 100644 tests/qtest/libqos/qos-smmuv3.h
>
Thanks for updating, all CI is green now.
On my side, it's all good and ready to be merged. Let's see if
maintainers have final comments to do.
Regards,
Pierrick
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework
2025-12-24 3:46 [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Tao Tang
` (7 preceding siblings ...)
2025-12-24 17:07 ` [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework Pierrick Bouvier
@ 2026-01-14 13:30 ` Tao Tang
2026-01-14 23:39 ` Pierrick Bouvier
8 siblings, 1 reply; 35+ messages in thread
From: Tao Tang @ 2026-01-14 13:30 UTC (permalink / raw)
To: Paolo Bonzini, Fabiano Rosas, Laurent Vivier, Eric Auger,
Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Pierrick Bouvier,
Philippe Mathieu-Daudé, Jean-Philippe Brucker, Mostafa Saleh,
CLEMENT MATHIEU--DRIF
A gentle ping on this series. Any further suggestions would be greatly
appreciated.
Link:
https://lore.kernel.org/qemu-devel/20251224034647.2596434-1-tangtao1634@phytium.com.cn/
Best regards,
Tao
On 2025/12/24 11:46, Tao Tang wrote:
> Changes since v7: Following Pierrick’s feedback, this version fixes the
> uint64_t formatting issue
> (switching to PRIx64 to avoid %lx mismatches on non-LP64 platforms). I also
> validated the series with Pierrick’s qemu-ci workflow, and all jobs passed;
> the CI run is available at:
>
> https://github.com/hnusdr/qemu/actions/runs/20476259310
>
> Motivation
> ----------
>
> Currently, thoroughly testing IOMMU emulation (e.g., ARM SMMUv3) requires
> a significant software stack. We need to boot a full guest operating
> system (like Linux) with the appropriate drivers (e.g., IOMMUFD) and rely
> on firmware (e.g., ACPI with IORT tables or Hafnium) to correctly
> configure the IOMMU and orchestrate DMA from a peripheral device.
>
> This dependency on a complex software stack presents several challenges:
>
> * High Barrier to Entry: Writing targeted tests for specific IOMMU
> features (like fault handling, specific translation regimes, etc.)
> becomes cumbersome.
>
> * Difficult to Debug: It's hard to distinguish whether a bug originates
> from the IOMMU emulation itself, the guest driver, the firmware
> tables, or the guest kernel's configuration.
>
> * Slow Iteration: The need to boot a full guest OS slows down the
> development and testing cycle.
>
> The primary goal of this work is to create a lightweight, self-contained
> testing environment that allows us to exercise the IOMMU's core logic
> directly at the qtest level, removing the need for any guest-side software.
>
> Our Approach: A Dedicated Test Framework
> -----------------------------------------
>
> To achieve this, we introduce three main components:
>
> * A minimal hardware device: iommu-testdev
> * A reusable IOMMU helper library: libqos/qos-smmuv3
> * A comprehensive qtest suite: iommu-smmuv3-test
>
> The iommu-testdev is intentionally not a conformant, general-purpose PCIe
> or platform device. It is a purpose-built, highly simplified "DMA engine"
> designed to be analogous to a minimal PCIe Root Complex that bypasses the
> full, realistic topology (Host Bridges, Switches, Endpoints) to provide a
> direct, programmable path for a DMA request to reach the IOMMU. Its sole
> purpose is to trigger a DMA transaction when its registers are written to,
> making it perfectly suited for direct control from a test environment like
> qtest.
>
> The Qtest Framework
> -------------------
>
> The new qtest (iommu-smmuv3-test.c) serves as the "bare-metal driver"
> for both the IOMMU and the iommu-testdev. It leverages the libqos helper
> library to manually perform all the setup that would typically be handled
> by the guest kernel and firmware, but in a completely controlled and
> predictable manner:
>
> 1. IOMMU Configuration: It directly initializes the SMMU's registers to a
> known state using helper functions from qos-smmuv3.
>
> 2. Translation Structure Setup: It uses the libqos library to construct
> the necessary translation structures in memory, including Stream Table
> Entries (STEs), Context Descriptors (CDs), and Page Tables (PTEs).
>
> 3. DMA Trigger: It programs the iommu-testdev to initiate a DMA operation
> targeting a specific IOVA with configurable attributes.
>
> 4. Verification: It waits for the transaction to complete and verifies
> that the memory was accessed correctly after address translation by
> the IOMMU.
>
> This framework provides a solid and extensible foundation for validating
> the IOMMU's core translation paths. The current test suite covers:
>
> - Stage 1 only translation (VA -> PA via CD page tables)
> - Stage 2 only translation (IPA -> PA via STE S2 tables)
> - Nested translation (VA -> IPA -> PA, Stage 1 + Stage 2)
>
> The infrastructure is designed to be extended to support additional
> security spaces and IOMMU features.
>
>
> Testing:
> --------
> QTEST_QEMU_BINARY=./build/qemu-system-aarch64 \
> ./build/tests/qtest/iommu-smmuv3-test --tap -k
>
>
> If you want to check coverage report, please follow instructions below
> which are shared by Pierrick in previous review [1]
>
> # install gcovr if not already installed, `sudo apt install gcovr`
> # on ubuntu for example
> $ export CFLAGS="--coverage"
> $ ./configure --target-list=aarch64-softmmu
> $ ninja -C build
> $ QTEST_QEMU_BINARY=./build/qemu-system-aarch64 \
> ./build/tests/qtest/iommu-smmuv3-test
> $ rm -rf build/coverage_html
> $ mkdir build/coverage_html
> $ gcovr \
> --gcov-ignore-parse-errors suspicious_hits.warn \
> --gcov-ignore-parse-errors negative_hits.warn \
> --merge-mode-functions=separate \
> --html-details build/coverage_html/index.html \
> --filter 'hw/arm/smmu*'
> # check the version of gcovr if meeting some unsupported options error.
> # Upgrading to 8.4 version may resolve the error.
> $ gcovr --version
> $ pip install --user --upgrade gcovr
> $ echo file://$(pwd)/build/coverage_html/index.html
> # open this in browser by clicking on your terminal
>
> [1] https://lore.kernel.org/qemu-devel/a361b46f-2173-4c98-a5d3-6b4d2ac004af@linaro.org/
>
>
> Major Changes from v7 to v8:
> -----------------------------
> - Fix uint64_t formatting issue by using PRIx64 for portability
> - Validate the series with Pierrick's qemu-ci workflow; all jobs passed
> - This series is also saved in github repo [5]
> [5] https://github.com/hnusdr/qemu/tree/iommu-testdev-v8-community
>
>
> Major Changes from v6 to v7:
> -----------------------------
> - Split the smmuv3-common.h work into smaller patches
> (registerfields conversion, NSCFG bits, helper setters)
> - Add MAINTAINERS entries for iommu-testdev and qos-smmuv3 helpers
> - Tighten documentation wording and update the DMA test pattern value
> - Simplify qtest PCI device discovery and rename QSMMU_IOVA constant
> - This series is also saved in github repo [4]
>
> [4] https://github.com/hnusdr/qemu/tree/iommu-testdev-v7-community
>
>
> Major Changes from v5 to v6:
> -----------------------------
> - Split batch testing into individual test cases per translation mode
> - Removed cleanup functions and related codes or comments
> - Improved error handling with explicit assertions
> - Add coverage build and test instructions shared by Pierrick
> - Keep Reviewed-by and Tested-by from Pierrick's review
> - This series is also saved in github repo [2]
>
> [2] https://github.com/hnusdr/qemu/tree/iommu-testdev-v6-community
>
>
> Major Changes from v4 to v5:
> -----------------------------
> - Remove a duplicated patch that was accidentally included in v4.
>
>
> Major Changes from v3 to v4:
> -----------------------------
>
> 1. Added shared smmuv3-common.h so both the device and libqos consume the same
> STE/CD/register definitions as Alex suggested [3]
> 2. Slimmed iommu-testdev down to a pure DMA trigger with a tighter MMIO
> contract (new doorbell helper, simplified attributes/errors).
> 3. Updated `qos-smmuv3` and the qtest so they include the common header,
> honor per-test expected results, and rely solely on the streamlined device
> interface.
> 4. Compacted changes of v2 to v3.
>
> [3] https://lore.kernel.org/qemu-devel/87zf8jk244.fsf@draig.linaro.org/
>
>
> Major Changes from v2 to v3:
> -----------------------------
>
> 1. Generalization/Renaming: rebranded `smmu-testdev` → `iommu-testdev` (code,
> headers, docs) to reflect the broadened scope.
> 2. Separation of concerns: iommu-testdev is now a pure DMA trigger; all
> SMMUv3-specific setup (STE/CD/page tables, multi-mode support, space offsets)
> lives in `qos-smmuv3.{c,h}` and is consumed by the new qtest.
> 3. Improved modularity & coverage: the stacked design (device + helper + qtest)
> made it straightforward to add S1/S2/Nested tests, a cleaner config system,
> and clearer validation logic.
> 4. Code/documentation quality: added tracepoints, better error handling/naming,
> and refreshed `docs/specs/iommu-testdev.rst` with the new layout.
>
> Future Work
> -----------
>
> The current implementation focuses on basic translation path validation
> in the Non-Secure address space. Future extensions could include:
>
> * Multi-space testing (Secure, Root, Realm) for SMMUv3
> * Support for other IOMMU types (Intel VT-d, AMD-Vi, RISC-V IOMMU)
>
>
> Tao Tang (7):
> hw/arm/smmuv3: Extract common definitions to smmuv3-common.h
> hw/arm/smmuv3-common: Define STE/CD fields via registerfields
> hw/misc: Introduce iommu-testdev for bare-metal IOMMU testing
> hw/arm/smmuv3-common: Add NSCFG bit definition for CD
> hw/arm/smmuv3-common: Add STE/CD set helpers for repeated field setup
> tests/qtest/libqos: Add SMMUv3 helper library
> tests/qtest: Add SMMUv3 bare-metal test using iommu-testdev
>
> MAINTAINERS | 13 +
> docs/specs/index.rst | 1 +
> docs/specs/iommu-testdev.rst | 112 ++++++
> hw/arm/smmuv3-internal.h | 255 +------------
> hw/misc/Kconfig | 5 +
> hw/misc/iommu-testdev.c | 271 ++++++++++++++
> hw/misc/meson.build | 1 +
> hw/misc/trace-events | 10 +
> include/hw/arm/smmuv3-common.h | 423 +++++++++++++++++++++
> include/hw/misc/iommu-testdev.h | 68 ++++
> tests/qtest/iommu-smmuv3-test.c | 121 ++++++
> tests/qtest/libqos/meson.build | 3 +
> tests/qtest/libqos/qos-smmuv3.c | 633 ++++++++++++++++++++++++++++++++
> tests/qtest/libqos/qos-smmuv3.h | 256 +++++++++++++
> tests/qtest/meson.build | 1 +
> 15 files changed, 1919 insertions(+), 254 deletions(-)
> create mode 100644 docs/specs/iommu-testdev.rst
> create mode 100644 hw/misc/iommu-testdev.c
> create mode 100644 include/hw/arm/smmuv3-common.h
> create mode 100644 include/hw/misc/iommu-testdev.h
> create mode 100644 tests/qtest/iommu-smmuv3-test.c
> create mode 100644 tests/qtest/libqos/qos-smmuv3.c
> create mode 100644 tests/qtest/libqos/qos-smmuv3.h
>
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework
2026-01-14 13:30 ` Tao Tang
@ 2026-01-14 23:39 ` Pierrick Bouvier
2026-01-15 17:47 ` Eric Auger
0 siblings, 1 reply; 35+ messages in thread
From: Pierrick Bouvier @ 2026-01-14 23:39 UTC (permalink / raw)
To: Tao Tang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier,
Eric Auger, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Philippe Mathieu-Daudé,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
On 1/14/26 5:30 AM, Tao Tang wrote:
>
> A gentle ping on this series. Any further suggestions would be greatly
> appreciated.
>
>
> Link:
>
> https://lore.kernel.org/qemu-devel/20251224034647.2596434-1-tangtao1634@phytium.com.cn/
>
>
> Best regards,
>
> Tao
>
This version is in good shape and is ready to be merged.
Eric, do you feel like merging it, or would you prefer someone else to
pick it up?
Regards,
Pierrick
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework
2026-01-14 23:39 ` Pierrick Bouvier
@ 2026-01-15 17:47 ` Eric Auger
2026-01-15 20:56 ` Pierrick Bouvier
0 siblings, 1 reply; 35+ messages in thread
From: Eric Auger @ 2026-01-15 17:47 UTC (permalink / raw)
To: Pierrick Bouvier, Tao Tang, Paolo Bonzini, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Philippe Mathieu-Daudé,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
Hi Pierrick,
On 1/15/26 12:39 AM, Pierrick Bouvier wrote:
> On 1/14/26 5:30 AM, Tao Tang wrote:
>>
>> A gentle ping on this series. Any further suggestions would be greatly
>> appreciated.
>>
>>
>> Link:
>>
>> https://lore.kernel.org/qemu-devel/20251224034647.2596434-1-tangtao1634@phytium.com.cn/
>>
>>
>>
>> Best regards,
>>
>> Tao
>>
> This version is in good shape and is ready to be merged.
> Eric, do you feel like merging it, or would you prefer someone else to
> pick it up?
Peter generally sends the PRs for the vSMMU. Maybe this can also be
taken by qtests maintainers too.
Thanks
Eric
>
> Regards,
> Pierrick
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework
2026-01-15 17:47 ` Eric Auger
@ 2026-01-15 20:56 ` Pierrick Bouvier
2026-01-19 14:09 ` Philippe Mathieu-Daudé
0 siblings, 1 reply; 35+ messages in thread
From: Pierrick Bouvier @ 2026-01-15 20:56 UTC (permalink / raw)
To: eric.auger, Tao Tang, Paolo Bonzini, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Philippe Mathieu-Daudé,
Jean-Philippe Brucker, Mostafa Saleh, CLEMENT MATHIEU--DRIF
On 1/15/26 9:47 AM, Eric Auger wrote:
> Hi Pierrick,
>
> On 1/15/26 12:39 AM, Pierrick Bouvier wrote:
>> On 1/14/26 5:30 AM, Tao Tang wrote:
>>>
>>> A gentle ping on this series. Any further suggestions would be greatly
>>> appreciated.
>>>
>>>
>>> Link:
>>>
>>> https://lore.kernel.org/qemu-devel/20251224034647.2596434-1-tangtao1634@phytium.com.cn/
>>>
>>>
>>>
>>> Best regards,
>>>
>>> Tao
>>>
>> This version is in good shape and is ready to be merged.
>> Eric, do you feel like merging it, or would you prefer someone else to
>> pick it up?
>
> Peter generally sends the PRs for the vSMMU. Maybe this can also be
> taken by qtests maintainers too.
>
> Thanks
>
> Eric
>>
>> Regards,
>> Pierrick
>>
>
Ok, thanks.
We really need to help Tao to get this in, as it's really useful and he
put a lot of effort into it, and waiting times between two reviews makes
it probably a bit complicated for him.
Regards,
Pierrick
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [RFC v8 0/7] hw/misc: Introduce a generalized IOMMU test framework
2026-01-15 20:56 ` Pierrick Bouvier
@ 2026-01-19 14:09 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-01-19 14:09 UTC (permalink / raw)
To: Pierrick Bouvier, eric.auger, Tao Tang, Paolo Bonzini,
Fabiano Rosas, Laurent Vivier, Peter Maydell, Alex Bennée
Cc: qemu-devel, qemu-arm, Chen Baozi, Jean-Philippe Brucker,
Mostafa Saleh, CLEMENT MATHIEU--DRIF
On 15/1/26 21:56, Pierrick Bouvier wrote:
> On 1/15/26 9:47 AM, Eric Auger wrote:
>> Hi Pierrick,
>>
>> On 1/15/26 12:39 AM, Pierrick Bouvier wrote:
>>> On 1/14/26 5:30 AM, Tao Tang wrote:
>>>>
>>>> A gentle ping on this series. Any further suggestions would be greatly
>>>> appreciated.
>>>>
>>>>
>>>> Link:
>>>>
>>>> https://lore.kernel.org/qemu-devel/20251224034647.2596434-1-
>>>> tangtao1634@phytium.com.cn/
>>>>
>>>>
>>>>
>>>> Best regards,
>>>>
>>>> Tao
>>>>
>>> This version is in good shape and is ready to be merged.
>>> Eric, do you feel like merging it, or would you prefer someone else to
>>> pick it up?
>>
>> Peter generally sends the PRs for the vSMMU. Maybe this can also be
>> taken by qtests maintainers too.
>>
>> Thanks
>>
>> Eric
>>>
>>> Regards,
>>> Pierrick
>>>
>>
>
> Ok, thanks.
> We really need to help Tao to get this in, as it's really useful and he
> put a lot of effort into it, and waiting times between two reviews makes
> it probably a bit complicated for him.
Since this series subject starts with 'hw/misc:' I'll queue it via
my hw-misc tree, thanks all!
^ permalink raw reply [flat|nested] 35+ messages in thread