All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev
@ 2026-02-04  3:06 Fengyuan Yu
  2026-02-04  3:06 ` [PATCH RFC v1 1/2] tests/qtest/libqos: Add Intel IOMMU helper library Fengyuan Yu
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Fengyuan Yu @ 2026-02-04  3:06 UTC (permalink / raw)
  To: Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang
  Cc: Chao Liu, qemu-devel, Fengyuan Yu

Hi,

This patch series adds a bare-metal qtest for the Intel IOMMU (VT-d) using
the iommu-testdev framework. The test exercises address translation paths
without requiring a full guest OS boot.

Motivation
----------

The Intel IOMMU implementation in QEMU supports various translation modes
including pass-through and translated (4-level paging) modes. Currently,
comprehensive testing of these translation paths requires booting a full
guest OS with appropriate drivers, which is time-consuming and makes
regression testing difficult.

This new test fills that gap by using iommu-testdev to trigger DMA
transactions and validate the IOMMU's translation logic directly.

Test Coverage
-------------

The new test provides:
- Legacy pass-through mode (identity mapping)
- Legacy translated mode with 4-level page table walks
- Root Entry Table and Context Entry Table configuration
- Complete 48-bit address space translation
- End-to-end DMA verification with memory validation

Testing
-------

QTEST_QEMU_BINARY=./build/qemu-system-x86_64 \
  ./build/tests/qtest/iommu-intel-test --tap -k

Thanks,
Fengyuan

Fengyuan Yu (2):
  tests/qtest/libqos: Add Intel IOMMU helper library
  tests/qtest: Add Intel IOMMU bare-metal test

 MAINTAINERS                          |   1 +
 tests/qtest/iommu-intel-test.c       | 137 +++++++
 tests/qtest/libqos/meson.build       |   3 +
 tests/qtest/libqos/qos-intel-iommu.c | 566 +++++++++++++++++++++++++++
 tests/qtest/libqos/qos-intel-iommu.h | 299 ++++++++++++++
 tests/qtest/meson.build              |   2 +
 6 files changed, 1008 insertions(+)
 create mode 100644 tests/qtest/iommu-intel-test.c
 create mode 100644 tests/qtest/libqos/qos-intel-iommu.c
 create mode 100644 tests/qtest/libqos/qos-intel-iommu.h

-- 
2.39.5



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH RFC v1 1/2] tests/qtest/libqos: Add Intel IOMMU helper library
  2026-02-04  3:06 [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev Fengyuan Yu
@ 2026-02-04  3:06 ` Fengyuan Yu
  2026-02-04  7:31   ` Chao Liu
  2026-02-04  3:06 ` [PATCH RFC v1 2/2] tests/qtest: Add Intel IOMMU bare-metal test Fengyuan Yu
  2026-02-04 13:14 ` [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev Tao Tang
  2 siblings, 1 reply; 9+ messages in thread
From: Fengyuan Yu @ 2026-02-04  3:06 UTC (permalink / raw)
  To: Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang
  Cc: Chao Liu, qemu-devel, Fengyuan Yu

Introduce a libqos helper module for Intel IOMMU (VT-d) testing with
iommu-testdev. The helper provides routines to:

- Build Root Entry Tables, Context Entry Tables, and 4-level page tables
  for 48-bit address translation
- Program VT-d registers (Root Table Pointer, Global Command Register)
  following the Intel VT-d specification
- Execute DMA translations and verify results

The current implementation supports Legacy mode with both pass-through and
translated (4-level paging) modes. Support for Scalable mode (PASID-based)
can be added in future patches.

Signed-off-by: Fengyuan Yu <15fengyuan@gmail.com>

---
 MAINTAINERS                          |   1 +
 tests/qtest/libqos/meson.build       |   3 +
 tests/qtest/libqos/qos-intel-iommu.c | 566 +++++++++++++++++++++++++++
 tests/qtest/libqos/qos-intel-iommu.h | 299 ++++++++++++++
 4 files changed, 869 insertions(+)
 create mode 100644 tests/qtest/libqos/qos-intel-iommu.c
 create mode 100644 tests/qtest/libqos/qos-intel-iommu.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 9b7ed4fccb..1cd2a4f474 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3585,6 +3585,7 @@ M: Tao Tang <tangtao1634@phytium.com.cn>
 S: Maintained
 F: tests/qtest/libqos/qos-iommu*
 F: tests/qtest/libqos/qos-smmuv3*
+F: tests/qtest/libqos/qos-intel-iommu*
 
 Device Fuzzing
 M: Alexander Bulekov <alxndr@bu.edu>
diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
index 8d6758ec2b..7f7c4f9c6f 100644
--- a/tests/qtest/libqos/meson.build
+++ b/tests/qtest/libqos/meson.build
@@ -72,6 +72,9 @@ endif
 if config_all_devices.has_key('CONFIG_RISCV_IOMMU')
   libqos_srcs += files('riscv-iommu.c')
 endif
+if config_all_devices.has_key('CONFIG_VTD')
+  libqos_srcs += files('qos-intel-iommu.c')
+endif
 if config_all_devices.has_key('CONFIG_TPCI200')
   libqos_srcs += files('tpci200.c')
 endif
diff --git a/tests/qtest/libqos/qos-intel-iommu.c b/tests/qtest/libqos/qos-intel-iommu.c
new file mode 100644
index 0000000000..d6a733de7e
--- /dev/null
+++ b/tests/qtest/libqos/qos-intel-iommu.c
@@ -0,0 +1,566 @@
+/*
+ * QOS Intel IOMMU (VT-d) Module Implementation
+ *
+ * This module provides Intel IOMMU-specific helper functions for libqos tests.
+ *
+ * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "../libqtest.h"
+#include "pci.h"
+#include "qos-intel-iommu.h"
+
+#define QVTD_POLL_DELAY_US        1000
+#define QVTD_POLL_MAX_RETRIES     1000
+#define QVTD_AW_48BIT_ENCODING    2
+
+/*
+ * iommu-testdev DMA attribute layout for Intel VT-d traffic.
+ *
+ * Bits [2:0] keep using the generic iommu-testdev encoding
+ * (secure + ArmSecuritySpace). Bits [23:8] carry the PCI Requester ID in the
+ * format defined in the Intel VT-d spec (Figure 3-2 in
+ * spec/Intel-iommu-spec.txt), and bits [31:24] contain the PASID that tags
+ * scalable-mode transactions. Bit 4 distinguishes between pure legacy RID
+ * requests and scalable-mode PASID-tagged requests. The PASID field is
+ * limited to 8 bits because MemTxAttrs::pid only carries 8 bits today (see
+ * include/exec/memattrs.h and the VTD_ECAP_PSS limit in
+ * hw/i386/intel_iommu_internal.h).
+ */
+#define QVTD_DMA_ATTR_MODE_SHIFT      4
+#define QVTD_DMA_ATTR_MODE_MASK       0x1
+#define QVTD_DMA_ATTR_MODE_LEGACY     0
+#define QVTD_DMA_ATTR_MODE_SCALABLE   1
+#define QVTD_DMA_ATTR_RID_SHIFT       8
+#define QVTD_DMA_ATTR_RID_MASK        0xffffu
+#define QVTD_DMA_ATTR_PASID_BITS      8
+#define QVTD_DMA_ATTR_PASID_SHIFT     24
+#define QVTD_DMA_ATTR_PASID_MASK      ((1u << QVTD_DMA_ATTR_PASID_BITS) - 1)
+
+#define QVTD_PCI_FUNCS_PER_DEVICE     8
+#define QVTD_PCI_DEVS_PER_BUS         32
+
+static void qvtd_wait_for_bitsl(QTestState *qts, uint64_t addr,
+                                uint32_t mask, bool expect_set)
+{
+    uint32_t val = 0;
+
+    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
+        val = qtest_readl(qts, addr);
+        if (!!(val & mask) == expect_set) {
+            return;
+        }
+        g_usleep(QVTD_POLL_DELAY_US);
+    }
+
+    g_error("Timeout waiting for bits 0x%x (%s) at 0x%llx, last=0x%x",
+            mask, expect_set ? "set" : "clear",
+            (unsigned long long)addr, val);
+}
+
+static void qvtd_wait_for_bitsq(QTestState *qts, uint64_t addr,
+                                uint64_t mask, bool expect_set)
+{
+    uint64_t val = 0;
+
+    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
+        val = qtest_readq(qts, addr);
+        if (!!(val & mask) == expect_set) {
+            return;
+        }
+        g_usleep(QVTD_POLL_DELAY_US);
+    }
+
+    g_error("Timeout waiting for bits 0x%llx (%s) at 0x%llx, last=0x%llx",
+            (unsigned long long)mask, expect_set ? "set" : "clear",
+            (unsigned long long)addr, (unsigned long long)val);
+}
+
+static uint16_t qvtd_calc_sid(const QPCIDevice *dev)
+{
+    uint16_t devfn = dev->devfn & 0xff;
+    uint16_t bus = (dev->devfn >> 8) & 0xff;
+    uint8_t device = (devfn >> 3) & 0x1f;
+    uint8_t function = devfn & 0x7;
+
+    /* Validate BDF components. */
+    if (device >= QVTD_PCI_DEVS_PER_BUS || function >= QVTD_PCI_FUNCS_PER_DEVICE) {
+        g_error("Invalid BDF: bus=%u device=%u function=%u", bus, device, function);
+    }
+
+    return (bus << 8) | devfn;
+}
+
+static bool qvtd_validate_dma_memory(QVTDTestContext *ctx)
+{
+    uint32_t len = ctx->config.dma_len;
+    g_autofree uint8_t *buf = NULL;
+
+    if (!len) {
+        return true;
+    }
+
+    buf = g_malloc(len);
+    qtest_memread(ctx->qts, ctx->config.dma_pa, buf, len);
+
+    for (uint32_t i = 0; i < len; i++) {
+        uint8_t expected = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
+        if (buf[i] != expected) {
+            g_test_message("Memory mismatch at PA=0x%llx offset=%u "
+                           "expected=0x%02x actual=0x%02x",
+                           (unsigned long long)ctx->config.dma_pa, i,
+                           expected, buf[i]);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx)
+{
+    return ctx->config.expected_result;
+}
+
+uint32_t qvtd_build_dma_attrs(uint16_t bdf, uint32_t pasid)
+{
+    uint32_t attrs = 0;
+    uint8_t bus = (bdf >> 8) & 0xff;
+    uint8_t devfn = bdf & 0xff;
+    uint8_t device = devfn >> 3;
+    uint8_t function = devfn & 0x7;
+    bool scalable_mode = pasid != 0;
+
+    if (device >= QVTD_PCI_DEVS_PER_BUS || function >= QVTD_PCI_FUNCS_PER_DEVICE) {
+        g_error("Invalid requester-id 0x%04x (bus=%u device=%u function=%u)",
+                bdf, bus, device, function);
+    }
+
+    attrs = ITD_ATTRS_SET_SECURE(attrs, 0);
+    attrs = ITD_ATTRS_SET_SPACE(attrs, 0);
+    attrs |= ((uint32_t)bdf & QVTD_DMA_ATTR_RID_MASK) << QVTD_DMA_ATTR_RID_SHIFT;
+
+    if (scalable_mode) {
+        if (pasid > QVTD_DMA_ATTR_PASID_MASK) {
+            g_error("PASID 0x%x exceeds %u-bit limit imposed by MemTxAttrs",
+                    pasid, QVTD_DMA_ATTR_PASID_BITS);
+        }
+
+        attrs |= (QVTD_DMA_ATTR_MODE_SCALABLE << QVTD_DMA_ATTR_MODE_SHIFT);
+        attrs |= ((pasid & QVTD_DMA_ATTR_PASID_MASK) << QVTD_DMA_ATTR_PASID_SHIFT);
+    } else {
+        attrs |= (QVTD_DMA_ATTR_MODE_LEGACY << QVTD_DMA_ATTR_MODE_SHIFT);
+    }
+
+    return attrs;
+}
+
+static void qvtd_build_root_entry(QTestState *qts, uint8_t bus,
+                                  uint64_t context_table_ptr)
+{
+    uint64_t root_entry_addr = QVTD_ROOT_TABLE_BASE + (bus * 16);
+    uint64_t lo, hi;
+
+    /* Root Entry Low: Context Table Pointer + Present bit (VT-d spec Section 9.1). */
+    lo = (context_table_ptr & VTD_CONTEXT_ENTRY_SLPTPTR) | VTD_CONTEXT_ENTRY_P;
+    hi = 0;  /* Reserved. */
+
+    qtest_writeq(qts, root_entry_addr, lo);
+    qtest_writeq(qts, root_entry_addr + 8, hi);
+}
+
+static void qvtd_build_context_entry(QTestState *qts, uint16_t sid,
+                                     QVTDTransMode mode, uint16_t domain_id,
+                                     uint64_t slptptr)
+{
+    uint8_t devfn = sid & 0xff;
+    uint64_t context_entry_addr = QVTD_CONTEXT_TABLE_BASE + (devfn * 16);
+    uint64_t lo, hi;
+
+    if (mode == QVTD_TM_LEGACY_PT) {
+        /* Pass-through mode (VT-d spec Section 3.9, Section 9.3). */
+        lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_PASS_THROUGH;
+        hi = ((uint64_t)domain_id << 8) | QVTD_AW_48BIT_ENCODING;
+    } else {
+        /* Translated mode: 4-level paging (AW=2 for 48-bit, VT-d spec Section 9.3). */
+        lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_MULTI_LEVEL |
+             (slptptr & VTD_CONTEXT_ENTRY_SLPTPTR);
+        hi = ((uint64_t)domain_id << 8) | QVTD_AW_48BIT_ENCODING;
+    }
+
+    qtest_writeq(qts, context_entry_addr, lo);
+    qtest_writeq(qts, context_entry_addr + 8, hi);
+}
+
+void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
+                                   uint64_t pa, QVTDTransMode mode)
+{
+    uint64_t pml4_entry, pdpt_entry, pd_entry, pt_entry;
+    uint64_t pml4_addr, pdpt_addr, pd_addr, pt_addr;
+    uint32_t pml4_idx, pdpt_idx, pd_idx, pt_idx;
+    const char *mode_str = (mode == QVTD_TM_LEGACY_PT) ?
+                           "Pass-Through" : "Translated";
+
+    g_test_message("Begin of page table construction: IOVA=0x%llx PA=0x%llx mode=%s",
+                   (unsigned long long)iova, (unsigned long long)pa, mode_str);
+
+    /* Pass-through mode doesn't need page tables */
+    if (mode == QVTD_TM_LEGACY_PT) {
+        g_test_message("Pass-through mode: skipping page table setup");
+        return;
+    }
+
+    /* Extract indices from IOVA
+     * 4-level paging for 48-bit virtual address space:
+     * - PML4 index: bits [47:39] (9 bits = 512 entries)
+     * - PDPT index:  bits [38:30] (9 bits = 512 entries)
+     * - PD index:    bits [29:21] (9 bits = 512 entries)
+     * - PT index:    bits [20:12] (9 bits = 512 entries)
+     * - Page offset: bits [11:0]  (12 bits = 4KB pages)
+     */
+    pml4_idx = (iova >> 39) & 0x1ff;  /* Bits [47:39] */
+    pdpt_idx = (iova >> 30) & 0x1ff;  /* Bits [38:30] */
+    pd_idx = (iova >> 21) & 0x1ff;    /* Bits [29:21] */
+    pt_idx = (iova >> 12) & 0x1ff;    /* Bits [20:12] */
+
+    /*
+     * Build 4-level page table hierarchy (VT-d spec Section 9.3, Table 9-3).
+     * Non-leaf entries: both R+W set for full access (spec allows R or W individually).
+     * Per VT-d spec Section 9.8: "If either the R or W field of a non-leaf
+     * paging-structure entry is 1", indicating that setting one or both is valid.
+     * We set both R+W for non-leaf entries as standard practice.
+     */
+
+    /* PML4 Entry: points to PDPT. */
+    pml4_addr = QVTD_PT_L4_BASE + (pml4_idx * 8);
+    pml4_entry = QVTD_PT_L3_BASE | VTD_SL_R | VTD_SL_W;
+    qtest_writeq(qts, pml4_addr, pml4_entry);
+
+    /* PDPT Entry: points to PD. */
+    pdpt_addr = QVTD_PT_L3_BASE + (pdpt_idx * 8);
+    pdpt_entry = QVTD_PT_L2_BASE | VTD_SL_R | VTD_SL_W;
+    qtest_writeq(qts, pdpt_addr, pdpt_entry);
+
+    /* PD Entry: points to PT. */
+    pd_addr = QVTD_PT_L2_BASE + (pd_idx * 8);
+    pd_entry = QVTD_PT_L1_BASE | VTD_SL_R | VTD_SL_W;
+    qtest_writeq(qts, pd_addr, pd_entry);
+
+    /* PT Entry: points to physical page (leaf). */
+    pt_addr = QVTD_PT_L1_BASE + (pt_idx * 8);
+    pt_entry = (pa & VTD_PAGE_MASK_4K) | VTD_SL_R | VTD_SL_W;
+    qtest_writeq(qts, pt_addr, pt_entry);
+
+    g_test_message("End of page table construction: mapped IOVA=0x%llx -> PA=0x%llx",
+                   (unsigned long long)iova, (unsigned long long)pa);
+}
+
+static void qvtd_invalidate_context_cache(QTestState *qts,
+                                          uint64_t iommu_base)
+{
+    uint64_t ccmd_val;
+
+    /* Context Command Register: Global invalidation (VT-d spec Section 6.5.1.1). */
+    ccmd_val = VTD_CCMD_ICC | VTD_CCMD_GLOBAL_INVL;
+    qtest_writeq(qts, iommu_base + DMAR_CCMD_REG, ccmd_val);
+
+    /* Wait for ICC bit to clear. */
+    qvtd_wait_for_bitsq(qts, iommu_base + DMAR_CCMD_REG,
+                        VTD_CCMD_ICC, false);
+}
+
+static void qvtd_invalidate_iotlb(QTestState *qts, uint64_t iommu_base)
+{
+    uint64_t iotlb_val;
+
+    /* IOTLB Invalidate Register: Global flush (VT-d spec Section 6.5.1.2). */
+    iotlb_val = VTD_TLB_IVT | VTD_TLB_GLOBAL_FLUSH;
+    qtest_writeq(qts, iommu_base + DMAR_IOTLB_REG, iotlb_val);
+
+    /* Wait for IVT bit to clear. */
+    qvtd_wait_for_bitsq(qts, iommu_base + DMAR_IOTLB_REG,
+                        VTD_TLB_IVT, false);
+}
+
+static void qvtd_clear_memory_regions(QTestState *qts)
+{
+    /* Clear root table. */
+    qtest_memset(qts, QVTD_ROOT_TABLE_BASE, 0, 4096);
+
+    /* Clear context table. */
+    qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 4096);
+
+    /* Clear all page table levels (4 levels * 4KB each = 16KB). */
+    qtest_memset(qts, QVTD_PT_L4_BASE, 0, 16384);
+}
+
+void qvtd_program_regs(QTestState *qts, uint64_t iommu_base)
+{
+    uint32_t gcmd;
+
+    /* 1. Disable translation (VT-d spec Section 11.4.4). */
+    gcmd = qtest_readl(qts, iommu_base + DMAR_GCMD_REG);
+    gcmd &= ~VTD_GCMD_TE;
+    qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
+
+    /* Wait for TES to clear. */
+    qvtd_wait_for_bitsl(qts, iommu_base + DMAR_GSTS_REG,
+                        VTD_GSTS_TES, false);
+
+    /* 2. Program root table address (VT-d spec Section 11.4.5). */
+    qtest_writeq(qts, iommu_base + DMAR_RTADDR_REG, QVTD_ROOT_TABLE_BASE);
+
+    /* 3. Set root table pointer (VT-d spec Section 6.6). */
+    gcmd = qtest_readl(qts, iommu_base + DMAR_GCMD_REG);
+    gcmd |= VTD_GCMD_SRTP;
+    qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
+
+    /* Wait for RTPS. */
+    qvtd_wait_for_bitsl(qts, iommu_base + DMAR_GSTS_REG,
+                        VTD_GSTS_RTPS, true);
+
+    /* Invalidate context cache after setting root table pointer. */
+    qvtd_invalidate_context_cache(qts, iommu_base);
+
+    /* 4. Unmask fault event interrupt to avoid warning messages. */
+    qtest_writel(qts, iommu_base + DMAR_FECTL_REG, 0);
+
+    /* NOTE: Translation is NOT enabled here - caller must enable after building structures. */
+}
+
+uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
+                                uint16_t sid, uint16_t domain_id,
+                                uint64_t iova, uint64_t pa)
+{
+    uint8_t bus = (sid >> 8) & 0xff;
+    const char *mode_str = (mode == QVTD_TM_LEGACY_PT) ?
+                           "Pass-Through" : "Translated";
+
+    g_test_message("Begin of construction: IOVA=0x%llx PA=0x%llx "
+                   "mode=%s domain_id=%u ===",
+                   (unsigned long long)iova, (unsigned long long)pa,
+                   mode_str, domain_id);
+
+    /* Build root entry */
+    qvtd_build_root_entry(qts, bus, QVTD_CONTEXT_TABLE_BASE);
+
+    /* Build context entry */
+    if (mode == QVTD_TM_LEGACY_PT) {
+        /* Pass-through mode: no page tables needed */
+        qvtd_build_context_entry(qts, sid, mode, domain_id, 0);
+        g_test_message("End of construction: identity mapping to PA=0x%llx ===",
+                       (unsigned long long)pa);
+    } else {
+        /* Translated mode: build 4-level page tables */
+        qvtd_setup_translation_tables(qts, iova, pa, QVTD_TM_LEGACY_TRANS);
+        qvtd_build_context_entry(qts, sid, mode, domain_id, QVTD_PT_L4_BASE);
+        g_test_message("End of construction: mapped IOVA=0x%llx -> PA=0x%llx ===",
+                       (unsigned long long)iova, (unsigned long long)pa);
+    }
+
+    return 0;
+}
+
+uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx)
+{
+    uint32_t gcmd;
+
+    /* Clear memory regions once during setup */
+    qvtd_clear_memory_regions(ctx->qts);
+
+    /* Program IOMMU registers (sets up root table pointer) */
+    qvtd_program_regs(ctx->qts, ctx->iommu_base);
+
+    /* Build translation structures AFTER clearing memory */
+    ctx->trans_status = qvtd_build_translation(ctx->qts, ctx->config.trans_mode,
+                                               ctx->sid, ctx->config.domain_id,
+                                               ctx->config.dma_iova,
+                                               ctx->config.dma_pa);
+    if (ctx->trans_status != 0) {
+        return ctx->trans_status;
+    }
+
+    /* Invalidate caches using register-based invalidation */
+    qvtd_invalidate_context_cache(ctx->qts, ctx->iommu_base);
+    qvtd_invalidate_iotlb(ctx->qts, ctx->iommu_base);
+
+    /* Enable translation AFTER building structures and invalidating caches */
+    gcmd = qtest_readl(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG);
+    gcmd |= VTD_GCMD_TE;
+    qtest_writel(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG, gcmd);
+
+    /* Wait for TES */
+    qvtd_wait_for_bitsl(ctx->qts, ctx->iommu_base + DMAR_GSTS_REG,
+                        VTD_GSTS_TES, true);
+
+    return 0;
+}
+
+uint32_t qvtd_trigger_dma(QVTDTestContext *ctx)
+{
+    uint64_t iova = ctx->config.dma_iova;
+    uint32_t len = ctx->config.dma_len;
+    uint32_t result, attrs_val;
+    const char *mode_str = (ctx->config.trans_mode == QVTD_TM_LEGACY_PT) ?
+                           "Pass-Through" : "Translated";
+
+    /* Write IOVA low 32 bits */
+    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_LO, (uint32_t)iova);
+
+    /* Write IOVA high 32 bits */
+    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_HI, (uint32_t)(iova >> 32));
+
+    /* Write DMA length */
+    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_LEN, len);
+
+    /* Build and write DMA attributes with BDF (PASID=0 for Legacy mode) */
+    attrs_val = qvtd_build_dma_attrs(ctx->sid, 0);
+    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_ATTRS, attrs_val);
+
+    /* Arm DMA by writing 1 to doorbell */
+    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_DBELL, ITD_DMA_DBELL_ARM);
+
+    /* Trigger DMA by reading from triggering register */
+    qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_TRIGGERING);
+
+    /* Poll for completion */
+    ctx->dma_result = ITD_DMA_RESULT_BUSY;
+    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
+        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(QVTD_POLL_DELAY_US);
+    }
+
+    if (ctx->dma_result == ITD_DMA_RESULT_BUSY) {
+        ctx->dma_result = ITD_DMA_ERR_TX_FAIL;
+        g_test_message("-> DMA timeout detected, forcing failure");
+    }
+
+    if (ctx->dma_result == 0) {
+        g_test_message("-> DMA succeeded: mode=%s", mode_str);
+    } else {
+        g_test_message("-> DMA failed: mode=%s result=0x%x",
+                       mode_str, ctx->dma_result);
+    }
+
+    return ctx->dma_result;
+}
+
+void qvtd_cleanup_translation(QVTDTestContext *ctx)
+{
+    uint8_t bus = (ctx->sid >> 8) & 0xff;
+    uint8_t devfn = ctx->sid & 0xff;
+    uint64_t root_entry_addr = QVTD_ROOT_TABLE_BASE + (bus * 16);
+    uint64_t context_entry_addr = QVTD_CONTEXT_TABLE_BASE + (devfn * 16);
+    uint32_t gcmd;
+
+    /* Disable translation before tearing down the structures */
+    gcmd = qtest_readl(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG);
+    if (gcmd & VTD_GCMD_TE) {
+        gcmd &= ~VTD_GCMD_TE;
+        qtest_writel(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG, gcmd);
+        qvtd_wait_for_bitsl(ctx->qts, ctx->iommu_base + DMAR_GSTS_REG,
+                            VTD_GSTS_TES, false);
+    }
+
+    /* Clear context entry */
+    qtest_writeq(ctx->qts, context_entry_addr, 0);
+    qtest_writeq(ctx->qts, context_entry_addr + 8, 0);
+
+    /* Clear root entry */
+    qtest_writeq(ctx->qts, root_entry_addr, 0);
+    qtest_writeq(ctx->qts, root_entry_addr + 8, 0);
+
+    /* Invalidate caches using register-based invalidation */
+    qvtd_invalidate_context_cache(ctx->qts, ctx->iommu_base);
+    qvtd_invalidate_iotlb(ctx->qts, ctx->iommu_base);
+}
+
+bool qvtd_validate_test_result(QVTDTestContext *ctx)
+{
+    uint32_t expected = qvtd_expected_dma_result(ctx);
+    bool passed = (ctx->dma_result == expected);
+    bool mem_ok = true;
+
+    g_test_message("-> Validating result: expected=0x%x actual=0x%x",
+                   expected, ctx->dma_result);
+
+    if (passed && expected == 0) {
+        mem_ok = qvtd_validate_dma_memory(ctx);
+        g_test_message("-> Memory validation %s at PA=0x%llx",
+                       mem_ok ? "passed" : "failed",
+                       (unsigned long long)ctx->config.dma_pa);
+        passed = mem_ok;
+    }
+
+    return passed;
+}
+
+void qvtd_single_translation(QVTDTestContext *ctx)
+{
+    uint32_t config_result;
+    bool test_passed;
+
+    /* Configure Intel IOMMU translation */
+    config_result = qvtd_setup_and_enable_translation(ctx);
+    if (config_result != 0) {
+        g_test_message("Configuration failed: mode=%u status=0x%x",
+                       ctx->config.trans_mode, config_result);
+    }
+    g_assert_cmpint(config_result, ==, 0);
+
+    /* Trigger DMA operation */
+    qvtd_trigger_dma(ctx);
+
+    /* Validate test result */
+    test_passed = qvtd_validate_test_result(ctx);
+    g_assert_true(test_passed);
+
+    /* Clean up translation state to prepare for the next test */
+    qvtd_cleanup_translation(ctx);
+}
+
+void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
+                               QPCIBar bar, uint64_t iommu_base,
+                               const QVTDTestConfig *cfg)
+{
+    /* Initialize test memory */
+    qtest_memset(qts, cfg->dma_pa, 0x00, cfg->dma_len);
+
+    /* Create test context on stack */
+    QVTDTestContext ctx = {
+        .qts = qts,
+        .dev = dev,
+        .bar = bar,
+        .iommu_base = iommu_base,
+        .config = *cfg,
+        .trans_status = 0,
+        .dma_result = 0,
+        .sid = qvtd_calc_sid(dev),
+    };
+
+    /* Execute the test using existing single_translation logic */
+    qvtd_single_translation(&ctx);
+
+    /* Report results */
+    g_test_message("--> Test completed: mode=%u domain_id=%u "
+                   "status=0x%x result=0x%x",
+                   cfg->trans_mode, cfg->domain_id,
+                   ctx.trans_status, ctx.dma_result);
+}
+
+void qvtd_translation_batch(const QVTDTestConfig *configs, size_t count,
+                            QTestState *qts, QPCIDevice *dev,
+                            QPCIBar bar, uint64_t iommu_base)
+{
+    for (size_t i = 0; i < count; i++) {
+        g_test_message("=== Running test %zu/%zu ===", i + 1, count);
+        qvtd_run_translation_case(qts, dev, bar, iommu_base, &configs[i]);
+    }
+}
diff --git a/tests/qtest/libqos/qos-intel-iommu.h b/tests/qtest/libqos/qos-intel-iommu.h
new file mode 100644
index 0000000000..dab5d4df63
--- /dev/null
+++ b/tests/qtest/libqos/qos-intel-iommu.h
@@ -0,0 +1,299 @@
+/*
+ * QOS Intel IOMMU (VT-d) Module
+ *
+ * This module provides Intel IOMMU-specific helper functions for libqos tests,
+ * encapsulating VT-d setup, assertion, and cleanup operations.
+ *
+ * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef QTEST_LIBQOS_INTEL_IOMMU_H
+#define QTEST_LIBQOS_INTEL_IOMMU_H
+
+#include "hw/misc/iommu-testdev.h"
+#include "hw/i386/intel_iommu_internal.h"
+
+/*
+ * Intel IOMMU MMIO register base. This is the standard Q35 IOMMU address.
+ */
+#define Q35_IOMMU_BASE            0xfed90000ULL
+
+/*
+ * Guest memory layout for IOMMU structures.
+ * All structures are placed in guest physical memory inside the 512MB RAM.
+ * Using 256MB mark (0x10000000) as base to ensure all structures fit in RAM.
+ */
+#define QVTD_MEM_BASE             0x10000000ULL
+
+/* Root Entry Table: 256 entries * 16 bytes = 4KB */
+#define QVTD_ROOT_TABLE_BASE      (QVTD_MEM_BASE + 0x00000000)
+
+/* Context Entry Table: 256 entries * 16 bytes = 4KB per bus */
+#define QVTD_CONTEXT_TABLE_BASE   (QVTD_MEM_BASE + 0x00001000)
+
+/* Page Tables: 4-level hierarchy for 48-bit address translation */
+#define QVTD_PT_L4_BASE           (QVTD_MEM_BASE + 0x00010000)  /* PML4 */
+#define QVTD_PT_L3_BASE           (QVTD_MEM_BASE + 0x00011000)  /* PDPT */
+#define QVTD_PT_L2_BASE           (QVTD_MEM_BASE + 0x00012000)  /* PD */
+#define QVTD_PT_L1_BASE           (QVTD_MEM_BASE + 0x00013000)  /* PT */
+
+/* Invalidation Queue: 256 entries * 16 bytes = 4KB */
+#define QVTD_INV_QUEUE_BASE       (QVTD_MEM_BASE + 0x00020000)
+
+/* Test IOVA and target physical address */
+#define QVTD_TEST_IOVA            0x0000008080604000ULL
+#define QVTD_TEST_PA              (QVTD_MEM_BASE + 0x00100000)
+
+/*
+ * Translation modes supported by Intel IOMMU
+ */
+typedef enum QVTDTransMode {
+    QVTD_TM_LEGACY_PT,      /* Legacy pass-through mode */
+    QVTD_TM_LEGACY_TRANS,   /* Legacy translated mode (4-level paging) */
+} QVTDTransMode;
+
+/*
+ * Test configuration structure
+ */
+typedef struct QVTDTestConfig {
+    QVTDTransMode trans_mode;     /* Translation mode */
+    uint64_t dma_iova;            /* DMA IOVA address for testing */
+    uint64_t dma_pa;              /* Target physical address */
+    uint32_t dma_len;             /* DMA length for testing */
+    uint32_t expected_result;     /* Expected DMA result */
+    uint16_t domain_id;           /* Domain ID for this test */
+} QVTDTestConfig;
+
+/*
+ * Test context structure
+ */
+typedef struct QVTDTestContext {
+    QTestState *qts;              /* QTest state handle */
+    QPCIDevice *dev;              /* PCI device handle */
+    QPCIBar bar;                  /* PCI BAR for MMIO access */
+    QVTDTestConfig config;        /* Test configuration */
+    uint64_t iommu_base;          /* Intel IOMMU base address */
+    uint32_t trans_status;        /* Translation configuration status */
+    uint32_t dma_result;          /* DMA operation result */
+    uint16_t sid;                 /* Source ID (bus:devfn) */
+} QVTDTestContext;
+
+/*
+ * qvtd_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 VT-d structures (root entry, context entry, page tables)
+ * 2. Programs IOMMU registers
+ * 3. Invalidates caches
+ * 4. Enables translation
+ */
+uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx);
+
+/*
+ * qvtd_build_translation - Build Intel IOMMU translation structures
+ *
+ * @qts: QTest state handle
+ * @mode: Translation mode (pass-through or translated)
+ * @sid: Source ID (bus:devfn)
+ * @domain_id: Domain ID
+ * @iova: IOVA address for logging purposes
+ * @pa: Physical address backed by the mapping
+ *
+ * Returns: Build status (0 = success, non-zero = error)
+ *
+ * Constructs all necessary VT-d translation structures in guest memory:
+ * - Root Entry for the device's bus
+ * - Context Entry for the device
+ * - Complete 4-level page table hierarchy (if translated mode)
+ */
+uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
+                                uint16_t sid, uint16_t domain_id,
+                                uint64_t iova, uint64_t pa);
+
+/*
+ * qvtd_program_regs - Program Intel IOMMU registers
+ *
+ * @qts: QTest state handle
+ * @iommu_base: IOMMU base address
+ *
+ * Programs IOMMU registers with the following sequence:
+ * 1. Disable translation
+ * 2. Program root table address
+ * 3. Set root table pointer
+ * 4. Unmask fault event interrupt
+ *
+ * Note: This function does NOT clear memory regions or enable translation.
+ * Memory clearing should be done once during test setup via qvtd_clear_memory_regions().
+ * Translation is enabled separately after building all structures.
+ */
+void qvtd_program_regs(QTestState *qts, uint64_t iommu_base);
+
+/*
+ * qvtd_trigger_dma - Trigger DMA operation via iommu-testdev
+ *
+ * @ctx: Test context
+ *
+ * Returns: DMA result code
+ *
+ * Programs iommu-testdev BAR0 registers to trigger a DMA operation:
+ * 1. Write IOVA address (GVA_LO/HI)
+ * 2. Write DMA length
+ * 3. Arm DMA (write to DBELL)
+ * 4. Trigger DMA (read from TRIGGERING)
+ * 5. Poll for completion (read DMA_RESULT)
+ */
+uint32_t qvtd_trigger_dma(QVTDTestContext *ctx);
+
+/*
+ * qvtd_cleanup_translation - Clean up translation configuration
+ *
+ * @ctx: Test context containing configuration and device handles
+ *
+ * Clears all translation structures and invalidates IOMMU caches.
+ */
+void qvtd_cleanup_translation(QVTDTestContext *ctx);
+
+/*
+ * qvtd_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 qvtd_validate_test_result(QVTDTestContext *ctx);
+
+/*
+ * qvtd_setup_translation_tables - Setup complete VT-d page table hierarchy
+ *
+ * @qts: QTest state handle
+ * @iova: Input Virtual Address to translate
+ * @pa: Physical address to map to
+ * @mode: Translation mode
+ *
+ * This function builds the complete 4-level page table structure for translating
+ * the given IOVA to PA through Intel VT-d. The structure is:
+ * - PML4 (Level 4): IOVA bits [47:39]
+ * - PDPT (Level 3): IOVA bits [38:30]
+ * - PD (Level 2): IOVA bits [29:21]
+ * - PT (Level 1): IOVA bits [20:12]
+ * - Page offset: IOVA bits [11:0]
+ *
+ * The function writes all necessary Page Table Entries (PTEs) to guest
+ * memory using qtest_writeq(), setting up the complete translation path
+ * that the VT-d hardware will traverse during DMA operations.
+ */
+void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
+                                   uint64_t pa, QVTDTransMode mode);
+
+/*
+ * qvtd_expected_dma_result - Calculate expected DMA result
+ *
+ * @ctx: Test context containing configuration
+ *
+ * Returns: Expected DMA result code
+ *
+ * This function acts as a test oracle, calculating the expected DMA result
+ * based on the test configuration. It centralizes validation logic for
+ * different scenarios (pass-through vs. translated, fault conditions).
+ */
+uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx);
+
+/*
+ * qvtd_build_dma_attrs - Build DMA attributes for an Intel VT-d DMA request
+ *
+ * @bdf: PCI requester ID encoded as Bus[15:8]/Device[7:3]/Function[2:0]
+ * @pasid: PASID tag (0 for legacy requests, non-zero for scalable mode)
+ *
+ * Returns: Value to program into iommu-testdev's DMA_ATTRS register
+ *
+ * The iommu-testdev attribute register mirrors Intel VT-d request metadata:
+ *   - bits[2:0] keep the generic iommu-testdev fields (secure + space)
+ *   - bit[4] selects legacy (0) vs. scalable (1) transactions
+ *   - bits[23:8] carry the requester ID as defined in the VT-d spec
+ *   - bits[31:24] carry the PASID (limited to 8 bits in QEMU, matching
+ *     the MemTxAttrs::pid width and ECAP.PSS advertisement)
+ *
+ * The helper validates the BDF layout (bus <= 255, device <= 31, function <= 7)
+ * and makes sure PASID fits in the supported width before returning the value.
+ */
+uint32_t qvtd_build_dma_attrs(uint16_t bdf, uint32_t pasid);
+
+/*
+ * High-level test execution functions
+ */
+
+/*
+ * qvtd_single_translation - Execute single translation test
+ *
+ * @ctx: Test context
+ *
+ * Performs a complete test cycle:
+ * 1. Setup translation structures
+ * 2. Trigger DMA operation
+ * 3. Validate results
+ * 4. Cleanup
+ */
+void qvtd_single_translation(QVTDTestContext *ctx);
+
+/*
+ * qvtd_run_translation_case - Execute a single Intel VT-d translation test
+ *
+ * @qts: QTestState for the test
+ * @dev: PCI device (iommu-testdev)
+ * @bar: BAR0 of iommu-testdev
+ * @iommu_base: Base address of Intel IOMMU MMIO registers
+ * @cfg: Test configuration
+ *
+ * High-level wrapper that creates test context internally and executes
+ * a single translation test case. This provides a simpler API compared
+ * to qvtd_single_translation() which requires manual context initialization.
+ *
+ * This function is analogous to qriommu_run_translation_case() in the
+ * RISC-V IOMMU test framework, providing a consistent API across different
+ * IOMMU architectures.
+ *
+ * Example usage:
+ *   QVTDTestConfig cfg = {
+ *       .trans_mode = QVTD_TM_LEGACY_PT,
+ *       .domain_id = 1,
+ *       .dma_iova = 0x40100000,
+ *       .dma_pa = 0x40100000,
+ *       .dma_len = 4,
+ *   };
+ *   qvtd_run_translation_case(qts, dev, bar, iommu_base, &cfg);
+ */
+void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
+                               QPCIBar bar, uint64_t iommu_base,
+                               const QVTDTestConfig *cfg);
+
+/*
+ * qvtd_translation_batch - Execute batch of translation tests
+ *
+ * @configs: Array of test configurations
+ * @count: Number of configurations
+ * @qts: QTest state handle
+ * @dev: PCI device handle
+ * @bar: PCI BAR for MMIO access
+ * @iommu_base: IOMMU base address
+ *
+ * Executes multiple translation tests in sequence, each with its own
+ * configuration. Useful for testing different translation modes and
+ * scenarios in a single test run.
+ *
+ * This function now uses qvtd_run_translation_case() internally to
+ * reduce code duplication.
+ */
+void qvtd_translation_batch(const QVTDTestConfig *configs, size_t count,
+                            QTestState *qts, QPCIDevice *dev,
+                            QPCIBar bar, uint64_t iommu_base);
+
+#endif /* QTEST_LIBQOS_INTEL_IOMMU_H */
-- 
2.39.5



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH RFC v1 2/2] tests/qtest: Add Intel IOMMU bare-metal test
  2026-02-04  3:06 [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev Fengyuan Yu
  2026-02-04  3:06 ` [PATCH RFC v1 1/2] tests/qtest/libqos: Add Intel IOMMU helper library Fengyuan Yu
@ 2026-02-04  3:06 ` Fengyuan Yu
  2026-02-04  7:43   ` Chao Liu
  2026-02-04 13:14 ` [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev Tao Tang
  2 siblings, 1 reply; 9+ messages in thread
From: Fengyuan Yu @ 2026-02-04  3:06 UTC (permalink / raw)
  To: Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang
  Cc: Chao Liu, qemu-devel, Fengyuan Yu

Add a qtest suite for the Intel IOMMU (VT-d) device on the Q35 machine.
The test exercises pass-through and translated translation modes using
iommu-testdev and the qos-intel-iommu helpers.

The test validates:
- Root Entry Table and Context Entry Table configuration
- 4-level page table walks for 48-bit address translation
- Pass-through mode (identity mapping)
- Translated mode with complete IOVA-to-PA translation
- DMA transaction execution with memory content verification

Signed-off-by: Fengyuan Yu <15fengyuan@gmail.com>
---
 tests/qtest/iommu-intel-test.c | 137 +++++++++++++++++++++++++++++++++
 tests/qtest/meson.build        |   2 +
 2 files changed, 139 insertions(+)
 create mode 100644 tests/qtest/iommu-intel-test.c

diff --git a/tests/qtest/iommu-intel-test.c b/tests/qtest/iommu-intel-test.c
new file mode 100644
index 0000000000..9f631be2c5
--- /dev/null
+++ b/tests/qtest/iommu-intel-test.c
@@ -0,0 +1,137 @@
+/*
+ * QTest for Intel IOMMU (VT-d) with iommu-testdev
+ *
+ * This QTest file is used to test the Intel IOMMU with iommu-testdev so that
+ * we can test VT-d without any guest kernel or firmware.
+ *
+ * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+#include "hw/pci/pci_regs.h"
+#include "hw/misc/iommu-testdev.h"
+#include "libqos/qos-intel-iommu.h"
+
+#define DMA_LEN           4
+
+/* Test configurations for different Intel IOMMU modes */
+static const QVTDTestConfig base_test_configs[] = {
+    {
+        .trans_mode = QVTD_TM_LEGACY_PT,
+        .dma_iova = 0x10100000,  /* Use address in guest RAM range (inside 512MB) */
+        .dma_pa = 0x10100000,
+        .dma_len = DMA_LEN,
+        .expected_result = 0,
+        .domain_id = 1,
+    },
+    {
+        .trans_mode = QVTD_TM_LEGACY_TRANS,
+        .dma_iova = QVTD_TEST_IOVA,
+        .dma_pa = QVTD_TEST_PA,
+        .dma_len = DMA_LEN,
+        .expected_result = 0,
+        .domain_id = 1,
+    },
+};
+
+static QPCIDevice *setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus,
+                                          QPCIBar *bar)
+{
+    uint16_t vid, did;
+    QPCIDevice *dev = NULL;
+    int device_count = 0;
+
+    *pcibus = qpci_new_pc(qts, NULL);
+    g_assert(*pcibus != NULL);
+
+    g_test_message("Scanning PCI bus for iommu-testdev (vendor:device = 0x%04x:0x%04x)...",
+                   IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID);
+
+    /* Find device by vendor/device ID to avoid slot surprises. */
+    for (int s = 0; s < 32 && !dev; s++) {
+        for (int fn = 0; fn < 8 && !dev; fn++) {
+            QPCIDevice *cand = qpci_device_find(*pcibus, QPCI_DEVFN(s, fn));
+            if (!cand) {
+                continue;
+            }
+            vid = qpci_config_readw(cand, PCI_VENDOR_ID);
+            did = qpci_config_readw(cand, PCI_DEVICE_ID);
+
+            device_count++;
+            g_test_message("  Found PCI device at %02x:%x - vendor:device = 0x%04x:0x%04x",
+                           s, fn, vid, did);
+
+            if (vid == IOMMU_TESTDEV_VENDOR_ID &&
+                did == IOMMU_TESTDEV_DEVICE_ID) {
+                dev = cand;
+                g_test_message("Found iommu-testdev! devfn: 0x%x", cand->devfn);
+            } else {
+                g_free(cand);
+            }
+        }
+    }
+
+    if (!dev) {
+        g_test_message("ERROR: iommu-testdev not found after scanning %d PCI devices", device_count);
+        g_test_message("Expected vendor:device = 0x%04x:0x%04x (PCI_VENDOR_ID_REDHAT:PCI_DEVICE_ID_REDHAT_TEST)",
+                       IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID);
+        qpci_free_pc(*pcibus);
+        *pcibus = NULL;
+        g_test_skip("iommu-testdev not found on PCI bus - device may not be compiled or registered");
+        return NULL;
+    }
+
+    /* Enable device - iommu-testdev only uses MMIO, not I/O ports */
+    uint16_t cmd = qpci_config_readw(dev, PCI_COMMAND);
+    cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
+    qpci_config_writew(dev, PCI_COMMAND, cmd);
+
+    *bar = qpci_iomap(dev, 0, NULL);
+    g_assert_false(bar->is_io);
+
+    return dev;
+}
+
+static void test_intel_iommu_translation(void)
+{
+    QTestState *qts;
+    QPCIBus *pcibus;
+    QPCIDevice *dev;
+    QPCIBar bar;
+
+    /* Initialize QEMU environment for Intel IOMMU testing */
+    qts = qtest_init("-machine q35,kernel-irqchip=split "
+                     "-accel tcg "
+                     "-device intel-iommu,pt=on,aw-bits=48 "
+                     "-device iommu-testdev,bus=pcie.0,addr=0x4 "
+                     "-m 512");
+
+    /* Setup and configure PCI device */
+    dev = setup_qtest_pci_device(qts, &pcibus, &bar);
+    if (!dev) {
+        qtest_quit(qts);
+        return;
+    }
+
+    /* Run the translation tests */
+    g_test_message("### Starting Intel IOMMU translation tests...###");
+    qvtd_translation_batch(base_test_configs, ARRAY_SIZE(base_test_configs),
+                           qts, dev, bar, Q35_IOMMU_BASE);
+    g_test_message("### Intel IOMMU translation tests completed successfully! ###");
+
+    g_free(dev);
+    qpci_free_pc(pcibus);
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    qtest_add_func("/iommu-testdev/intel-translation", test_intel_iommu_translation);
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e2d2e68092..344e836300 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -95,6 +95,8 @@ qtests_i386 = \
   (config_all_devices.has_key('CONFIG_SDHCI_PCI') ? ['fuzz-sdcard-test'] : []) +            \
   (config_all_devices.has_key('CONFIG_ESP_PCI') ? ['am53c974-test'] : []) +                 \
   (config_all_devices.has_key('CONFIG_VTD') ? ['intel-iommu-test'] : []) +                 \
+  (config_all_devices.has_key('CONFIG_VTD') and
+   config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') ? ['iommu-intel-test'] : []) +      \
   (host_os != 'windows' and                                                                \
    config_all_devices.has_key('CONFIG_ACPI_ERST') ? ['erst-test'] : []) +                   \
   (config_all_devices.has_key('CONFIG_PCIE_PORT') and                                       \
-- 
2.39.5



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH RFC v1 1/2] tests/qtest/libqos: Add Intel IOMMU helper library
  2026-02-04  3:06 ` [PATCH RFC v1 1/2] tests/qtest/libqos: Add Intel IOMMU helper library Fengyuan Yu
@ 2026-02-04  7:31   ` Chao Liu
  2026-02-05  9:20     ` Fengyuan
  0 siblings, 1 reply; 9+ messages in thread
From: Chao Liu @ 2026-02-04  7:31 UTC (permalink / raw)
  To: Fengyuan Yu
  Cc: Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang,
	qemu-devel

Hi Fengyuan,

Thanks for working on Intel IOMMU bare-metal testing support. I have some
comments below.

On Wed, Feb 04, 2026 at 11:06:19AM +0800, Fengyuan Yu wrote:
> Introduce a libqos helper module for Intel IOMMU (VT-d) testing with
> iommu-testdev. The helper provides routines to:
> 
> - Build Root Entry Tables, Context Entry Tables, and 4-level page tables
>   for 48-bit address translation
> - Program VT-d registers (Root Table Pointer, Global Command Register)
>   following the Intel VT-d specification
> - Execute DMA translations and verify results
> 
> The current implementation supports Legacy mode with both pass-through and
> translated (4-level paging) modes. Support for Scalable mode (PASID-based)
> can be added in future patches.
> 
The commit message looks good.

> Signed-off-by: Fengyuan Yu <15fengyuan@gmail.com>
> 
> ---
>  MAINTAINERS                          |   1 +
>  tests/qtest/libqos/meson.build       |   3 +
>  tests/qtest/libqos/qos-intel-iommu.c | 566 +++++++++++++++++++++++++++
>  tests/qtest/libqos/qos-intel-iommu.h | 299 ++++++++++++++
>  4 files changed, 869 insertions(+)
>  create mode 100644 tests/qtest/libqos/qos-intel-iommu.c
>  create mode 100644 tests/qtest/libqos/qos-intel-iommu.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9b7ed4fccb..1cd2a4f474 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3585,6 +3585,7 @@ M: Tao Tang <tangtao1634@phytium.com.cn>
>  S: Maintained
>  F: tests/qtest/libqos/qos-iommu*
>  F: tests/qtest/libqos/qos-smmuv3*
> +F: tests/qtest/libqos/qos-intel-iommu*
>  
>  Device Fuzzing
>  M: Alexander Bulekov <alxndr@bu.edu>
> diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
> index 8d6758ec2b..7f7c4f9c6f 100644
> --- a/tests/qtest/libqos/meson.build
> +++ b/tests/qtest/libqos/meson.build
> @@ -72,6 +72,9 @@ endif
>  if config_all_devices.has_key('CONFIG_RISCV_IOMMU')
>    libqos_srcs += files('riscv-iommu.c')
>  endif
> +if config_all_devices.has_key('CONFIG_VTD')
> +  libqos_srcs += files('qos-intel-iommu.c')
> +endif
>  if config_all_devices.has_key('CONFIG_TPCI200')
>    libqos_srcs += files('tpci200.c')
>  endif
> diff --git a/tests/qtest/libqos/qos-intel-iommu.c b/tests/qtest/libqos/qos-intel-iommu.c
> new file mode 100644
> index 0000000000..d6a733de7e
> --- /dev/null
> +++ b/tests/qtest/libqos/qos-intel-iommu.c
> @@ -0,0 +1,566 @@
> +/*
> + * QOS Intel IOMMU (VT-d) Module Implementation
> + *
> + * This module provides Intel IOMMU-specific helper functions for libqos tests.
> + *
> + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "../libqtest.h"
> +#include "pci.h"
> +#include "qos-intel-iommu.h"
> +
You should also include "qos-iommu-testdev.h" here, as the SMMUv3 helper
does. This would allow reusing the common DMA trigger infrastructure.

> +#define QVTD_POLL_DELAY_US        1000
> +#define QVTD_POLL_MAX_RETRIES     1000
> +#define QVTD_AW_48BIT_ENCODING    2
> +
> +/*
> + * iommu-testdev DMA attribute layout for Intel VT-d traffic.
> + *
> + * Bits [2:0] keep using the generic iommu-testdev encoding
> + * (secure + ArmSecuritySpace). Bits [23:8] carry the PCI Requester ID in the
> + * format defined in the Intel VT-d spec (Figure 3-2 in
> + * spec/Intel-iommu-spec.txt), and bits [31:24] contain the PASID that tags
> + * scalable-mode transactions. Bit 4 distinguishes between pure legacy RID
> + * requests and scalable-mode PASID-tagged requests. The PASID field is
> + * limited to 8 bits because MemTxAttrs::pid only carries 8 bits today (see
> + * include/exec/memattrs.h and the VTD_ECAP_PSS limit in
> + * hw/i386/intel_iommu_internal.h).
> + */
> +#define QVTD_DMA_ATTR_MODE_SHIFT      4
> +#define QVTD_DMA_ATTR_MODE_MASK       0x1
> +#define QVTD_DMA_ATTR_MODE_LEGACY     0
> +#define QVTD_DMA_ATTR_MODE_SCALABLE   1
> +#define QVTD_DMA_ATTR_RID_SHIFT       8
> +#define QVTD_DMA_ATTR_RID_MASK        0xffffu
> +#define QVTD_DMA_ATTR_PASID_BITS      8
> +#define QVTD_DMA_ATTR_PASID_SHIFT     24
> +#define QVTD_DMA_ATTR_PASID_MASK      ((1u << QVTD_DMA_ATTR_PASID_BITS) - 1)
> +
> +#define QVTD_PCI_FUNCS_PER_DEVICE     8
> +#define QVTD_PCI_DEVS_PER_BUS         32
> +
> +static void qvtd_wait_for_bitsl(QTestState *qts, uint64_t addr,
> +                                uint32_t mask, bool expect_set)
> +{
> +    uint32_t val = 0;
> +
> +    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
> +        val = qtest_readl(qts, addr);
> +        if (!!(val & mask) == expect_set) {
> +            return;
> +        }
> +        g_usleep(QVTD_POLL_DELAY_US);
> +    }
> +
> +    g_error("Timeout waiting for bits 0x%x (%s) at 0x%llx, last=0x%x",
Please use PRIx64 instead of %llx with (unsigned long long) cast.
This applies to all similar occurrences in this file.

> +            mask, expect_set ? "set" : "clear",
> +            (unsigned long long)addr, val);
> +}
> +
> +static void qvtd_wait_for_bitsq(QTestState *qts, uint64_t addr,
> +                                uint64_t mask, bool expect_set)
> +{
> +    uint64_t val = 0;
> +
> +    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
> +        val = qtest_readq(qts, addr);
> +        if (!!(val & mask) == expect_set) {
> +            return;
> +        }
> +        g_usleep(QVTD_POLL_DELAY_US);
> +    }
> +
> +    g_error("Timeout waiting for bits 0x%llx (%s) at 0x%llx, last=0x%llx",
> +            (unsigned long long)mask, expect_set ? "set" : "clear",
> +            (unsigned long long)addr, (unsigned long long)val);
> +}
> +
> +static uint16_t qvtd_calc_sid(const QPCIDevice *dev)
> +{
> +    uint16_t devfn = dev->devfn & 0xff;
> +    uint16_t bus = (dev->devfn >> 8) & 0xff;
> +    uint8_t device = (devfn >> 3) & 0x1f;
> +    uint8_t function = devfn & 0x7;
> +
> +    /* Validate BDF components. */
> +    if (device >= QVTD_PCI_DEVS_PER_BUS || function >= QVTD_PCI_FUNCS_PER_DEVICE) {
> +        g_error("Invalid BDF: bus=%u device=%u function=%u", bus, device, function);
> +    }
> +
> +    return (bus << 8) | devfn;
> +}
> +
> +static bool qvtd_validate_dma_memory(QVTDTestContext *ctx)
> +{
> +    uint32_t len = ctx->config.dma_len;
> +    g_autofree uint8_t *buf = NULL;
> +
> +    if (!len) {
> +        return true;
> +    }
> +
> +    buf = g_malloc(len);
> +    qtest_memread(ctx->qts, ctx->config.dma_pa, buf, len);
> +
> +    for (uint32_t i = 0; i < len; i++) {
> +        uint8_t expected = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
> +        if (buf[i] != expected) {
> +            g_test_message("Memory mismatch at PA=0x%llx offset=%u "
> +                           "expected=0x%02x actual=0x%02x",
> +                           (unsigned long long)ctx->config.dma_pa, i,
> +                           expected, buf[i]);
> +            return false;
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx)
> +{
> +    return ctx->config.expected_result;
> +}
> +
> +uint32_t qvtd_build_dma_attrs(uint16_t bdf, uint32_t pasid)
> +{
> +    uint32_t attrs = 0;
> +    uint8_t bus = (bdf >> 8) & 0xff;
> +    uint8_t devfn = bdf & 0xff;
> +    uint8_t device = devfn >> 3;
> +    uint8_t function = devfn & 0x7;
> +    bool scalable_mode = pasid != 0;
> +
> +    if (device >= QVTD_PCI_DEVS_PER_BUS || function >= QVTD_PCI_FUNCS_PER_DEVICE) {
> +        g_error("Invalid requester-id 0x%04x (bus=%u device=%u function=%u)",
> +                bdf, bus, device, function);
> +    }
> +
> +    attrs = ITD_ATTRS_SET_SECURE(attrs, 0);
> +    attrs = ITD_ATTRS_SET_SPACE(attrs, 0);
> +    attrs |= ((uint32_t)bdf & QVTD_DMA_ATTR_RID_MASK) << QVTD_DMA_ATTR_RID_SHIFT;
> +
> +    if (scalable_mode) {
> +        if (pasid > QVTD_DMA_ATTR_PASID_MASK) {
> +            g_error("PASID 0x%x exceeds %u-bit limit imposed by MemTxAttrs",
> +                    pasid, QVTD_DMA_ATTR_PASID_BITS);
> +        }
> +
> +        attrs |= (QVTD_DMA_ATTR_MODE_SCALABLE << QVTD_DMA_ATTR_MODE_SHIFT);
> +        attrs |= ((pasid & QVTD_DMA_ATTR_PASID_MASK) << QVTD_DMA_ATTR_PASID_SHIFT);
> +    } else {
> +        attrs |= (QVTD_DMA_ATTR_MODE_LEGACY << QVTD_DMA_ATTR_MODE_SHIFT);
> +    }
> +
> +    return attrs;
> +}
> +
> +static void qvtd_build_root_entry(QTestState *qts, uint8_t bus,
> +                                  uint64_t context_table_ptr)
> +{
> +    uint64_t root_entry_addr = QVTD_ROOT_TABLE_BASE + (bus * 16);
> +    uint64_t lo, hi;
> +
> +    /* Root Entry Low: Context Table Pointer + Present bit (VT-d spec Section 9.1). */
> +    lo = (context_table_ptr & VTD_CONTEXT_ENTRY_SLPTPTR) | VTD_CONTEXT_ENTRY_P;
> +    hi = 0;  /* Reserved. */
> +
> +    qtest_writeq(qts, root_entry_addr, lo);
> +    qtest_writeq(qts, root_entry_addr + 8, hi);
> +}
> +
> +static void qvtd_build_context_entry(QTestState *qts, uint16_t sid,
> +                                     QVTDTransMode mode, uint16_t domain_id,
> +                                     uint64_t slptptr)
> +{
> +    uint8_t devfn = sid & 0xff;
> +    uint64_t context_entry_addr = QVTD_CONTEXT_TABLE_BASE + (devfn * 16);
> +    uint64_t lo, hi;
> +
> +    if (mode == QVTD_TM_LEGACY_PT) {
> +        /* Pass-through mode (VT-d spec Section 3.9, Section 9.3). */
> +        lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_PASS_THROUGH;
> +        hi = ((uint64_t)domain_id << 8) | QVTD_AW_48BIT_ENCODING;
> +    } else {
> +        /* Translated mode: 4-level paging (AW=2 for 48-bit, VT-d spec Section 9.3). */
> +        lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_MULTI_LEVEL |
> +             (slptptr & VTD_CONTEXT_ENTRY_SLPTPTR);
> +        hi = ((uint64_t)domain_id << 8) | QVTD_AW_48BIT_ENCODING;
> +    }
> +
> +    qtest_writeq(qts, context_entry_addr, lo);
> +    qtest_writeq(qts, context_entry_addr + 8, hi);
> +}
> +
> +void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
> +                                   uint64_t pa, QVTDTransMode mode)
> +{
> +    uint64_t pml4_entry, pdpt_entry, pd_entry, pt_entry;
> +    uint64_t pml4_addr, pdpt_addr, pd_addr, pt_addr;
> +    uint32_t pml4_idx, pdpt_idx, pd_idx, pt_idx;
> +    const char *mode_str = (mode == QVTD_TM_LEGACY_PT) ?
> +                           "Pass-Through" : "Translated";
> +
> +    g_test_message("Begin of page table construction: IOVA=0x%llx PA=0x%llx mode=%s",
> +                   (unsigned long long)iova, (unsigned long long)pa, mode_str);
> +
> +    /* Pass-through mode doesn't need page tables */
> +    if (mode == QVTD_TM_LEGACY_PT) {
> +        g_test_message("Pass-through mode: skipping page table setup");
> +        return;
> +    }
> +
> +    /* Extract indices from IOVA
> +     * 4-level paging for 48-bit virtual address space:
> +     * - PML4 index: bits [47:39] (9 bits = 512 entries)
> +     * - PDPT index:  bits [38:30] (9 bits = 512 entries)
> +     * - PD index:    bits [29:21] (9 bits = 512 entries)
> +     * - PT index:    bits [20:12] (9 bits = 512 entries)
> +     * - Page offset: bits [11:0]  (12 bits = 4KB pages)
> +     */
> +    pml4_idx = (iova >> 39) & 0x1ff;  /* Bits [47:39] */
> +    pdpt_idx = (iova >> 30) & 0x1ff;  /* Bits [38:30] */
> +    pd_idx = (iova >> 21) & 0x1ff;    /* Bits [29:21] */
> +    pt_idx = (iova >> 12) & 0x1ff;    /* Bits [20:12] */
> +
> +    /*
> +     * Build 4-level page table hierarchy (VT-d spec Section 9.3, Table 9-3).
> +     * Non-leaf entries: both R+W set for full access (spec allows R or W individually).
> +     * Per VT-d spec Section 9.8: "If either the R or W field of a non-leaf
> +     * paging-structure entry is 1", indicating that setting one or both is valid.
> +     * We set both R+W for non-leaf entries as standard practice.
> +     */
> +
> +    /* PML4 Entry: points to PDPT. */
> +    pml4_addr = QVTD_PT_L4_BASE + (pml4_idx * 8);
> +    pml4_entry = QVTD_PT_L3_BASE | VTD_SL_R | VTD_SL_W;
> +    qtest_writeq(qts, pml4_addr, pml4_entry);
> +
> +    /* PDPT Entry: points to PD. */
> +    pdpt_addr = QVTD_PT_L3_BASE + (pdpt_idx * 8);
> +    pdpt_entry = QVTD_PT_L2_BASE | VTD_SL_R | VTD_SL_W;
> +    qtest_writeq(qts, pdpt_addr, pdpt_entry);
> +
> +    /* PD Entry: points to PT. */
> +    pd_addr = QVTD_PT_L2_BASE + (pd_idx * 8);
> +    pd_entry = QVTD_PT_L1_BASE | VTD_SL_R | VTD_SL_W;
> +    qtest_writeq(qts, pd_addr, pd_entry);
> +
> +    /* PT Entry: points to physical page (leaf). */
> +    pt_addr = QVTD_PT_L1_BASE + (pt_idx * 8);
> +    pt_entry = (pa & VTD_PAGE_MASK_4K) | VTD_SL_R | VTD_SL_W;
> +    qtest_writeq(qts, pt_addr, pt_entry);
> +
> +    g_test_message("End of page table construction: mapped IOVA=0x%llx -> PA=0x%llx",
> +                   (unsigned long long)iova, (unsigned long long)pa);
> +}
> +
> +static void qvtd_invalidate_context_cache(QTestState *qts,
> +                                          uint64_t iommu_base)
> +{
> +    uint64_t ccmd_val;
> +
> +    /* Context Command Register: Global invalidation (VT-d spec Section 6.5.1.1). */
> +    ccmd_val = VTD_CCMD_ICC | VTD_CCMD_GLOBAL_INVL;
> +    qtest_writeq(qts, iommu_base + DMAR_CCMD_REG, ccmd_val);
> +
> +    /* Wait for ICC bit to clear. */
> +    qvtd_wait_for_bitsq(qts, iommu_base + DMAR_CCMD_REG,
> +                        VTD_CCMD_ICC, false);
> +}
> +
> +static void qvtd_invalidate_iotlb(QTestState *qts, uint64_t iommu_base)
> +{
> +    uint64_t iotlb_val;
> +
> +    /* IOTLB Invalidate Register: Global flush (VT-d spec Section 6.5.1.2). */
> +    iotlb_val = VTD_TLB_IVT | VTD_TLB_GLOBAL_FLUSH;
> +    qtest_writeq(qts, iommu_base + DMAR_IOTLB_REG, iotlb_val);
> +
> +    /* Wait for IVT bit to clear. */
> +    qvtd_wait_for_bitsq(qts, iommu_base + DMAR_IOTLB_REG,
> +                        VTD_TLB_IVT, false);
> +}
> +
> +static void qvtd_clear_memory_regions(QTestState *qts)
> +{
> +    /* Clear root table. */
> +    qtest_memset(qts, QVTD_ROOT_TABLE_BASE, 0, 4096);
> +
> +    /* Clear context table. */
> +    qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 4096);
> +
> +    /* Clear all page table levels (4 levels * 4KB each = 16KB). */
> +    qtest_memset(qts, QVTD_PT_L4_BASE, 0, 16384);
> +}
> +
> +void qvtd_program_regs(QTestState *qts, uint64_t iommu_base)
> +{
> +    uint32_t gcmd;
> +
> +    /* 1. Disable translation (VT-d spec Section 11.4.4). */
> +    gcmd = qtest_readl(qts, iommu_base + DMAR_GCMD_REG);
> +    gcmd &= ~VTD_GCMD_TE;
> +    qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
> +
> +    /* Wait for TES to clear. */
> +    qvtd_wait_for_bitsl(qts, iommu_base + DMAR_GSTS_REG,
> +                        VTD_GSTS_TES, false);
> +
> +    /* 2. Program root table address (VT-d spec Section 11.4.5). */
> +    qtest_writeq(qts, iommu_base + DMAR_RTADDR_REG, QVTD_ROOT_TABLE_BASE);
> +
> +    /* 3. Set root table pointer (VT-d spec Section 6.6). */
> +    gcmd = qtest_readl(qts, iommu_base + DMAR_GCMD_REG);
> +    gcmd |= VTD_GCMD_SRTP;
> +    qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
> +
> +    /* Wait for RTPS. */
> +    qvtd_wait_for_bitsl(qts, iommu_base + DMAR_GSTS_REG,
> +                        VTD_GSTS_RTPS, true);
> +
> +    /* Invalidate context cache after setting root table pointer. */
> +    qvtd_invalidate_context_cache(qts, iommu_base);
> +
> +    /* 4. Unmask fault event interrupt to avoid warning messages. */
> +    qtest_writel(qts, iommu_base + DMAR_FECTL_REG, 0);
> +
> +    /* NOTE: Translation is NOT enabled here - caller must enable after building structures. */
> +}
> +
> +uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
> +                                uint16_t sid, uint16_t domain_id,
> +                                uint64_t iova, uint64_t pa)
> +{
> +    uint8_t bus = (sid >> 8) & 0xff;
> +    const char *mode_str = (mode == QVTD_TM_LEGACY_PT) ?
> +                           "Pass-Through" : "Translated";
> +
> +    g_test_message("Begin of construction: IOVA=0x%llx PA=0x%llx "
> +                   "mode=%s domain_id=%u ===",
> +                   (unsigned long long)iova, (unsigned long long)pa,
> +                   mode_str, domain_id);
> +
> +    /* Build root entry */
> +    qvtd_build_root_entry(qts, bus, QVTD_CONTEXT_TABLE_BASE);
> +
> +    /* Build context entry */
> +    if (mode == QVTD_TM_LEGACY_PT) {
> +        /* Pass-through mode: no page tables needed */
> +        qvtd_build_context_entry(qts, sid, mode, domain_id, 0);
> +        g_test_message("End of construction: identity mapping to PA=0x%llx ===",
> +                       (unsigned long long)pa);
> +    } else {
> +        /* Translated mode: build 4-level page tables */
> +        qvtd_setup_translation_tables(qts, iova, pa, QVTD_TM_LEGACY_TRANS);
> +        qvtd_build_context_entry(qts, sid, mode, domain_id, QVTD_PT_L4_BASE);
> +        g_test_message("End of construction: mapped IOVA=0x%llx -> PA=0x%llx ===",
> +                       (unsigned long long)iova, (unsigned long long)pa);
> +    }
> +
> +    return 0;
> +}
> +
> +uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx)
> +{
> +    uint32_t gcmd;
> +
> +    /* Clear memory regions once during setup */
> +    qvtd_clear_memory_regions(ctx->qts);
> +
> +    /* Program IOMMU registers (sets up root table pointer) */
> +    qvtd_program_regs(ctx->qts, ctx->iommu_base);
> +
> +    /* Build translation structures AFTER clearing memory */
> +    ctx->trans_status = qvtd_build_translation(ctx->qts, ctx->config.trans_mode,
> +                                               ctx->sid, ctx->config.domain_id,
> +                                               ctx->config.dma_iova,
> +                                               ctx->config.dma_pa);
> +    if (ctx->trans_status != 0) {
> +        return ctx->trans_status;
> +    }
> +
> +    /* Invalidate caches using register-based invalidation */
> +    qvtd_invalidate_context_cache(ctx->qts, ctx->iommu_base);
> +    qvtd_invalidate_iotlb(ctx->qts, ctx->iommu_base);
> +
> +    /* Enable translation AFTER building structures and invalidating caches */
> +    gcmd = qtest_readl(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG);
> +    gcmd |= VTD_GCMD_TE;
> +    qtest_writel(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG, gcmd);
> +
> +    /* Wait for TES */
> +    qvtd_wait_for_bitsl(ctx->qts, ctx->iommu_base + DMAR_GSTS_REG,
> +                        VTD_GSTS_TES, true);
> +
> +    return 0;
> +}
> +
> +uint32_t qvtd_trigger_dma(QVTDTestContext *ctx)
This function duplicates the logic in qos_iommu_testdev_trigger_dma().
The SMMUv3 helper uses the common qos-iommu-testdev infrastructure with
callback functions (setup_fn, attrs_fn, validate_fn, report_fn).

I'd suggest refactoring to use qos_iommu_testdev_single_translation()
instead, similar to how qsmmu_run_translation_case() does it in
qos-smmuv3.c

Thanks,
Chao
> +{
> +    uint64_t iova = ctx->config.dma_iova;
> +    uint32_t len = ctx->config.dma_len;
> +    uint32_t result, attrs_val;
> +    const char *mode_str = (ctx->config.trans_mode == QVTD_TM_LEGACY_PT) ?
> +                           "Pass-Through" : "Translated";
> +
> +    /* Write IOVA low 32 bits */
> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_LO, (uint32_t)iova);
> +
> +    /* Write IOVA high 32 bits */
> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_HI, (uint32_t)(iova >> 32));
> +
> +    /* Write DMA length */
> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_LEN, len);
> +
> +    /* Build and write DMA attributes with BDF (PASID=0 for Legacy mode) */
> +    attrs_val = qvtd_build_dma_attrs(ctx->sid, 0);
> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_ATTRS, attrs_val);
> +
> +    /* Arm DMA by writing 1 to doorbell */
> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_DBELL, ITD_DMA_DBELL_ARM);
> +
> +    /* Trigger DMA by reading from triggering register */
> +    qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_TRIGGERING);
> +
> +    /* Poll for completion */
> +    ctx->dma_result = ITD_DMA_RESULT_BUSY;
> +    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
> +        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(QVTD_POLL_DELAY_US);
> +    }
> +
> +    if (ctx->dma_result == ITD_DMA_RESULT_BUSY) {
> +        ctx->dma_result = ITD_DMA_ERR_TX_FAIL;
> +        g_test_message("-> DMA timeout detected, forcing failure");
> +    }
> +
> +    if (ctx->dma_result == 0) {
> +        g_test_message("-> DMA succeeded: mode=%s", mode_str);
> +    } else {
> +        g_test_message("-> DMA failed: mode=%s result=0x%x",
> +                       mode_str, ctx->dma_result);
> +    }
> +
> +    return ctx->dma_result;
> +}
> +
> +void qvtd_cleanup_translation(QVTDTestContext *ctx)
> +{
> +    uint8_t bus = (ctx->sid >> 8) & 0xff;
> +    uint8_t devfn = ctx->sid & 0xff;
> +    uint64_t root_entry_addr = QVTD_ROOT_TABLE_BASE + (bus * 16);
> +    uint64_t context_entry_addr = QVTD_CONTEXT_TABLE_BASE + (devfn * 16);
> +    uint32_t gcmd;
> +
> +    /* Disable translation before tearing down the structures */
> +    gcmd = qtest_readl(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG);
> +    if (gcmd & VTD_GCMD_TE) {
> +        gcmd &= ~VTD_GCMD_TE;
> +        qtest_writel(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG, gcmd);
> +        qvtd_wait_for_bitsl(ctx->qts, ctx->iommu_base + DMAR_GSTS_REG,
> +                            VTD_GSTS_TES, false);
> +    }
> +
> +    /* Clear context entry */
> +    qtest_writeq(ctx->qts, context_entry_addr, 0);
> +    qtest_writeq(ctx->qts, context_entry_addr + 8, 0);
> +
> +    /* Clear root entry */
> +    qtest_writeq(ctx->qts, root_entry_addr, 0);
> +    qtest_writeq(ctx->qts, root_entry_addr + 8, 0);
> +
> +    /* Invalidate caches using register-based invalidation */
> +    qvtd_invalidate_context_cache(ctx->qts, ctx->iommu_base);
> +    qvtd_invalidate_iotlb(ctx->qts, ctx->iommu_base);
> +}
> +
> +bool qvtd_validate_test_result(QVTDTestContext *ctx)
> +{
> +    uint32_t expected = qvtd_expected_dma_result(ctx);
> +    bool passed = (ctx->dma_result == expected);
> +    bool mem_ok = true;
> +
> +    g_test_message("-> Validating result: expected=0x%x actual=0x%x",
> +                   expected, ctx->dma_result);
> +
> +    if (passed && expected == 0) {
> +        mem_ok = qvtd_validate_dma_memory(ctx);
> +        g_test_message("-> Memory validation %s at PA=0x%llx",
> +                       mem_ok ? "passed" : "failed",
> +                       (unsigned long long)ctx->config.dma_pa);
> +        passed = mem_ok;
> +    }
> +
> +    return passed;
> +}
> +
> +void qvtd_single_translation(QVTDTestContext *ctx)
> +{
> +    uint32_t config_result;
> +    bool test_passed;
> +
> +    /* Configure Intel IOMMU translation */
> +    config_result = qvtd_setup_and_enable_translation(ctx);
> +    if (config_result != 0) {
> +        g_test_message("Configuration failed: mode=%u status=0x%x",
> +                       ctx->config.trans_mode, config_result);
> +    }
> +    g_assert_cmpint(config_result, ==, 0);
> +
> +    /* Trigger DMA operation */
> +    qvtd_trigger_dma(ctx);
> +
> +    /* Validate test result */
> +    test_passed = qvtd_validate_test_result(ctx);
> +    g_assert_true(test_passed);
> +
> +    /* Clean up translation state to prepare for the next test */
> +    qvtd_cleanup_translation(ctx);
> +}
> +
> +void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
> +                               QPCIBar bar, uint64_t iommu_base,
> +                               const QVTDTestConfig *cfg)
> +{
> +    /* Initialize test memory */
> +    qtest_memset(qts, cfg->dma_pa, 0x00, cfg->dma_len);
> +
> +    /* Create test context on stack */
> +    QVTDTestContext ctx = {
> +        .qts = qts,
> +        .dev = dev,
> +        .bar = bar,
> +        .iommu_base = iommu_base,
> +        .config = *cfg,
> +        .trans_status = 0,
> +        .dma_result = 0,
> +        .sid = qvtd_calc_sid(dev),
> +    };
> +
> +    /* Execute the test using existing single_translation logic */
> +    qvtd_single_translation(&ctx);
> +
> +    /* Report results */
> +    g_test_message("--> Test completed: mode=%u domain_id=%u "
> +                   "status=0x%x result=0x%x",
> +                   cfg->trans_mode, cfg->domain_id,
> +                   ctx.trans_status, ctx.dma_result);
> +}
> +
> +void qvtd_translation_batch(const QVTDTestConfig *configs, size_t count,
> +                            QTestState *qts, QPCIDevice *dev,
> +                            QPCIBar bar, uint64_t iommu_base)
> +{
> +    for (size_t i = 0; i < count; i++) {
> +        g_test_message("=== Running test %zu/%zu ===", i + 1, count);
> +        qvtd_run_translation_case(qts, dev, bar, iommu_base, &configs[i]);
> +    }
> +}
> diff --git a/tests/qtest/libqos/qos-intel-iommu.h b/tests/qtest/libqos/qos-intel-iommu.h
> new file mode 100644
> index 0000000000..dab5d4df63
> --- /dev/null
> +++ b/tests/qtest/libqos/qos-intel-iommu.h
> @@ -0,0 +1,299 @@
> +/*
> + * QOS Intel IOMMU (VT-d) Module
> + *
> + * This module provides Intel IOMMU-specific helper functions for libqos tests,
> + * encapsulating VT-d setup, assertion, and cleanup operations.
> + *
> + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef QTEST_LIBQOS_INTEL_IOMMU_H
> +#define QTEST_LIBQOS_INTEL_IOMMU_H
> +
> +#include "hw/misc/iommu-testdev.h"
> +#include "hw/i386/intel_iommu_internal.h"
> +
> +/*
> + * Intel IOMMU MMIO register base. This is the standard Q35 IOMMU address.
> + */
> +#define Q35_IOMMU_BASE            0xfed90000ULL
> +
> +/*
> + * Guest memory layout for IOMMU structures.
> + * All structures are placed in guest physical memory inside the 512MB RAM.
> + * Using 256MB mark (0x10000000) as base to ensure all structures fit in RAM.
> + */
> +#define QVTD_MEM_BASE             0x10000000ULL
> +
> +/* Root Entry Table: 256 entries * 16 bytes = 4KB */
> +#define QVTD_ROOT_TABLE_BASE      (QVTD_MEM_BASE + 0x00000000)
> +
> +/* Context Entry Table: 256 entries * 16 bytes = 4KB per bus */
> +#define QVTD_CONTEXT_TABLE_BASE   (QVTD_MEM_BASE + 0x00001000)
> +
> +/* Page Tables: 4-level hierarchy for 48-bit address translation */
> +#define QVTD_PT_L4_BASE           (QVTD_MEM_BASE + 0x00010000)  /* PML4 */
> +#define QVTD_PT_L3_BASE           (QVTD_MEM_BASE + 0x00011000)  /* PDPT */
> +#define QVTD_PT_L2_BASE           (QVTD_MEM_BASE + 0x00012000)  /* PD */
> +#define QVTD_PT_L1_BASE           (QVTD_MEM_BASE + 0x00013000)  /* PT */
> +
> +/* Invalidation Queue: 256 entries * 16 bytes = 4KB */
> +#define QVTD_INV_QUEUE_BASE       (QVTD_MEM_BASE + 0x00020000)
> +
> +/* Test IOVA and target physical address */
> +#define QVTD_TEST_IOVA            0x0000008080604000ULL
> +#define QVTD_TEST_PA              (QVTD_MEM_BASE + 0x00100000)
> +
> +/*
> + * Translation modes supported by Intel IOMMU
> + */
> +typedef enum QVTDTransMode {
> +    QVTD_TM_LEGACY_PT,      /* Legacy pass-through mode */
> +    QVTD_TM_LEGACY_TRANS,   /* Legacy translated mode (4-level paging) */
> +} QVTDTransMode;
> +
> +/*
> + * Test configuration structure
> + */
> +typedef struct QVTDTestConfig {
> +    QVTDTransMode trans_mode;     /* Translation mode */
> +    uint64_t dma_iova;            /* DMA IOVA address for testing */
> +    uint64_t dma_pa;              /* Target physical address */
> +    uint32_t dma_len;             /* DMA length for testing */
> +    uint32_t expected_result;     /* Expected DMA result */
> +    uint16_t domain_id;           /* Domain ID for this test */
> +} QVTDTestConfig;
> +
> +/*
> + * Test context structure
> + */
> +typedef struct QVTDTestContext {
> +    QTestState *qts;              /* QTest state handle */
> +    QPCIDevice *dev;              /* PCI device handle */
> +    QPCIBar bar;                  /* PCI BAR for MMIO access */
> +    QVTDTestConfig config;        /* Test configuration */
> +    uint64_t iommu_base;          /* Intel IOMMU base address */
> +    uint32_t trans_status;        /* Translation configuration status */
> +    uint32_t dma_result;          /* DMA operation result */
> +    uint16_t sid;                 /* Source ID (bus:devfn) */
> +} QVTDTestContext;
> +
> +/*
> + * qvtd_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 VT-d structures (root entry, context entry, page tables)
> + * 2. Programs IOMMU registers
> + * 3. Invalidates caches
> + * 4. Enables translation
> + */
> +uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx);
> +
> +/*
> + * qvtd_build_translation - Build Intel IOMMU translation structures
> + *
> + * @qts: QTest state handle
> + * @mode: Translation mode (pass-through or translated)
> + * @sid: Source ID (bus:devfn)
> + * @domain_id: Domain ID
> + * @iova: IOVA address for logging purposes
> + * @pa: Physical address backed by the mapping
> + *
> + * Returns: Build status (0 = success, non-zero = error)
> + *
> + * Constructs all necessary VT-d translation structures in guest memory:
> + * - Root Entry for the device's bus
> + * - Context Entry for the device
> + * - Complete 4-level page table hierarchy (if translated mode)
> + */
> +uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
> +                                uint16_t sid, uint16_t domain_id,
> +                                uint64_t iova, uint64_t pa);
> +
> +/*
> + * qvtd_program_regs - Program Intel IOMMU registers
> + *
> + * @qts: QTest state handle
> + * @iommu_base: IOMMU base address
> + *
> + * Programs IOMMU registers with the following sequence:
> + * 1. Disable translation
> + * 2. Program root table address
> + * 3. Set root table pointer
> + * 4. Unmask fault event interrupt
> + *
> + * Note: This function does NOT clear memory regions or enable translation.
> + * Memory clearing should be done once during test setup via qvtd_clear_memory_regions().
> + * Translation is enabled separately after building all structures.
> + */
> +void qvtd_program_regs(QTestState *qts, uint64_t iommu_base);
> +
> +/*
> + * qvtd_trigger_dma - Trigger DMA operation via iommu-testdev
> + *
> + * @ctx: Test context
> + *
> + * Returns: DMA result code
> + *
> + * Programs iommu-testdev BAR0 registers to trigger a DMA operation:
> + * 1. Write IOVA address (GVA_LO/HI)
> + * 2. Write DMA length
> + * 3. Arm DMA (write to DBELL)
> + * 4. Trigger DMA (read from TRIGGERING)
> + * 5. Poll for completion (read DMA_RESULT)
> + */
> +uint32_t qvtd_trigger_dma(QVTDTestContext *ctx);
> +
> +/*
> + * qvtd_cleanup_translation - Clean up translation configuration
> + *
> + * @ctx: Test context containing configuration and device handles
> + *
> + * Clears all translation structures and invalidates IOMMU caches.
> + */
> +void qvtd_cleanup_translation(QVTDTestContext *ctx);
> +
> +/*
> + * qvtd_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 qvtd_validate_test_result(QVTDTestContext *ctx);
> +
> +/*
> + * qvtd_setup_translation_tables - Setup complete VT-d page table hierarchy
> + *
> + * @qts: QTest state handle
> + * @iova: Input Virtual Address to translate
> + * @pa: Physical address to map to
> + * @mode: Translation mode
> + *
> + * This function builds the complete 4-level page table structure for translating
> + * the given IOVA to PA through Intel VT-d. The structure is:
> + * - PML4 (Level 4): IOVA bits [47:39]
> + * - PDPT (Level 3): IOVA bits [38:30]
> + * - PD (Level 2): IOVA bits [29:21]
> + * - PT (Level 1): IOVA bits [20:12]
> + * - Page offset: IOVA bits [11:0]
> + *
> + * The function writes all necessary Page Table Entries (PTEs) to guest
> + * memory using qtest_writeq(), setting up the complete translation path
> + * that the VT-d hardware will traverse during DMA operations.
> + */
> +void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
> +                                   uint64_t pa, QVTDTransMode mode);
> +
> +/*
> + * qvtd_expected_dma_result - Calculate expected DMA result
> + *
> + * @ctx: Test context containing configuration
> + *
> + * Returns: Expected DMA result code
> + *
> + * This function acts as a test oracle, calculating the expected DMA result
> + * based on the test configuration. It centralizes validation logic for
> + * different scenarios (pass-through vs. translated, fault conditions).
> + */
> +uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx);
> +
> +/*
> + * qvtd_build_dma_attrs - Build DMA attributes for an Intel VT-d DMA request
> + *
> + * @bdf: PCI requester ID encoded as Bus[15:8]/Device[7:3]/Function[2:0]
> + * @pasid: PASID tag (0 for legacy requests, non-zero for scalable mode)
> + *
> + * Returns: Value to program into iommu-testdev's DMA_ATTRS register
> + *
> + * The iommu-testdev attribute register mirrors Intel VT-d request metadata:
> + *   - bits[2:0] keep the generic iommu-testdev fields (secure + space)
> + *   - bit[4] selects legacy (0) vs. scalable (1) transactions
> + *   - bits[23:8] carry the requester ID as defined in the VT-d spec
> + *   - bits[31:24] carry the PASID (limited to 8 bits in QEMU, matching
> + *     the MemTxAttrs::pid width and ECAP.PSS advertisement)
> + *
> + * The helper validates the BDF layout (bus <= 255, device <= 31, function <= 7)
> + * and makes sure PASID fits in the supported width before returning the value.
> + */
> +uint32_t qvtd_build_dma_attrs(uint16_t bdf, uint32_t pasid);
> +
> +/*
> + * High-level test execution functions
> + */
> +
> +/*
> + * qvtd_single_translation - Execute single translation test
> + *
> + * @ctx: Test context
> + *
> + * Performs a complete test cycle:
> + * 1. Setup translation structures
> + * 2. Trigger DMA operation
> + * 3. Validate results
> + * 4. Cleanup
> + */
> +void qvtd_single_translation(QVTDTestContext *ctx);
> +
> +/*
> + * qvtd_run_translation_case - Execute a single Intel VT-d translation test
> + *
> + * @qts: QTestState for the test
> + * @dev: PCI device (iommu-testdev)
> + * @bar: BAR0 of iommu-testdev
> + * @iommu_base: Base address of Intel IOMMU MMIO registers
> + * @cfg: Test configuration
> + *
> + * High-level wrapper that creates test context internally and executes
> + * a single translation test case. This provides a simpler API compared
> + * to qvtd_single_translation() which requires manual context initialization.
> + *
> + * This function is analogous to qriommu_run_translation_case() in the
> + * RISC-V IOMMU test framework, providing a consistent API across different
> + * IOMMU architectures.
> + *
> + * Example usage:
> + *   QVTDTestConfig cfg = {
> + *       .trans_mode = QVTD_TM_LEGACY_PT,
> + *       .domain_id = 1,
> + *       .dma_iova = 0x40100000,
> + *       .dma_pa = 0x40100000,
> + *       .dma_len = 4,
> + *   };
> + *   qvtd_run_translation_case(qts, dev, bar, iommu_base, &cfg);
> + */
> +void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
> +                               QPCIBar bar, uint64_t iommu_base,
> +                               const QVTDTestConfig *cfg);
> +
> +/*
> + * qvtd_translation_batch - Execute batch of translation tests
> + *
> + * @configs: Array of test configurations
> + * @count: Number of configurations
> + * @qts: QTest state handle
> + * @dev: PCI device handle
> + * @bar: PCI BAR for MMIO access
> + * @iommu_base: IOMMU base address
> + *
> + * Executes multiple translation tests in sequence, each with its own
> + * configuration. Useful for testing different translation modes and
> + * scenarios in a single test run.
> + *
> + * This function now uses qvtd_run_translation_case() internally to
> + * reduce code duplication.
> + */
> +void qvtd_translation_batch(const QVTDTestConfig *configs, size_t count,
> +                            QTestState *qts, QPCIDevice *dev,
> +                            QPCIBar bar, uint64_t iommu_base);
> +
> +#endif /* QTEST_LIBQOS_INTEL_IOMMU_H */
> -- 
> 2.39.5
> 


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RFC v1 2/2] tests/qtest: Add Intel IOMMU bare-metal test
  2026-02-04  3:06 ` [PATCH RFC v1 2/2] tests/qtest: Add Intel IOMMU bare-metal test Fengyuan Yu
@ 2026-02-04  7:43   ` Chao Liu
  2026-02-05  9:18     ` Fengyuan
  0 siblings, 1 reply; 9+ messages in thread
From: Chao Liu @ 2026-02-04  7:43 UTC (permalink / raw)
  To: Fengyuan Yu
  Cc: Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang,
	qemu-devel

Hi Fengyuan,

On Wed, Feb 04, 2026 at 11:06:20AM +0800, Fengyuan Yu wrote:
> Add a qtest suite for the Intel IOMMU (VT-d) device on the Q35 machine.
> The test exercises pass-through and translated translation modes using
> iommu-testdev and the qos-intel-iommu helpers.
> 
> The test validates:
> - Root Entry Table and Context Entry Table configuration
> - 4-level page table walks for 48-bit address translation
> - Pass-through mode (identity mapping)
> - Translated mode with complete IOVA-to-PA translation
> - DMA transaction execution with memory content verification
> 
Good commit message.

> Signed-off-by: Fengyuan Yu <15fengyuan@gmail.com>
> ---
>  tests/qtest/iommu-intel-test.c | 137 +++++++++++++++++++++++++++++++++
>  tests/qtest/meson.build        |   2 +
>  2 files changed, 139 insertions(+)
>  create mode 100644 tests/qtest/iommu-intel-test.c
> 
> diff --git a/tests/qtest/iommu-intel-test.c b/tests/qtest/iommu-intel-test.c
> new file mode 100644
> index 0000000000..9f631be2c5
> --- /dev/null
> +++ b/tests/qtest/iommu-intel-test.c
> @@ -0,0 +1,137 @@
> +/*
> + * QTest for Intel IOMMU (VT-d) with iommu-testdev
> + *
> + * This QTest file is used to test the Intel IOMMU with iommu-testdev so that
> + * we can test VT-d without any guest kernel or firmware.
> + *
> + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include "libqos/pci.h"
> +#include "libqos/pci-pc.h"
> +#include "hw/pci/pci_regs.h"
> +#include "hw/misc/iommu-testdev.h"
> +#include "libqos/qos-intel-iommu.h"
> +
> +#define DMA_LEN           4
> +
> +/* Test configurations for different Intel IOMMU modes */
> +static const QVTDTestConfig base_test_configs[] = {
> +    {
> +        .trans_mode = QVTD_TM_LEGACY_PT,
> +        .dma_iova = 0x10100000,  /* Use address in guest RAM range (inside 512MB) */
> +        .dma_pa = 0x10100000,
> +        .dma_len = DMA_LEN,
> +        .expected_result = 0,
> +        .domain_id = 1,
> +    },
> +    {
> +        .trans_mode = QVTD_TM_LEGACY_TRANS,
> +        .dma_iova = QVTD_TEST_IOVA,
> +        .dma_pa = QVTD_TEST_PA,
> +        .dma_len = DMA_LEN,
> +        .expected_result = 0,
> +        .domain_id = 1,
> +    },
> +};
> +
> +static QPCIDevice *setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus,
> +                                          QPCIBar *bar)
> +{
> +    uint16_t vid, did;
> +    QPCIDevice *dev = NULL;
> +    int device_count = 0;
> +
> +    *pcibus = qpci_new_pc(qts, NULL);
> +    g_assert(*pcibus != NULL);
> +
> +    g_test_message("Scanning PCI bus for iommu-testdev (vendor:device = 0x%04x:0x%04x)...",
> +                   IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID);
> +
> +    /* Find device by vendor/device ID to avoid slot surprises. */
> +    for (int s = 0; s < 32 && !dev; s++) {
> +        for (int fn = 0; fn < 8 && !dev; fn++) {
> +            QPCIDevice *cand = qpci_device_find(*pcibus, QPCI_DEVFN(s, fn));
> +            if (!cand) {
> +                continue;
> +            }
> +            vid = qpci_config_readw(cand, PCI_VENDOR_ID);
> +            did = qpci_config_readw(cand, PCI_DEVICE_ID);
> +
> +            device_count++;
> +            g_test_message("  Found PCI device at %02x:%x - vendor:device = 0x%04x:0x%04x",
> +                           s, fn, vid, did);
> +
> +            if (vid == IOMMU_TESTDEV_VENDOR_ID &&
> +                did == IOMMU_TESTDEV_DEVICE_ID) {
> +                dev = cand;
> +                g_test_message("Found iommu-testdev! devfn: 0x%x", cand->devfn);
> +            } else {
> +                g_free(cand);
> +            }
> +        }
> +    }
> +

This manual PCI bus scanning is verbose. The SMMUv3 test uses
qpci_device_foreach() which is more concise:

    qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID,
                        IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev);

See iommu-smmuv3-test.c for reference.

> +    if (!dev) {
> +        g_test_message("ERROR: iommu-testdev not found after scanning %d PCI devices", device_count);
> +        g_test_message("Expected vendor:device = 0x%04x:0x%04x (PCI_VENDOR_ID_REDHAT:PCI_DEVICE_ID_REDHAT_TEST)",
> +                       IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID);
> +        qpci_free_pc(*pcibus);
> +        *pcibus = NULL;
> +        g_test_skip("iommu-testdev not found on PCI bus - device may not be compiled or registered");
> +        return NULL;
> +    }
> +
> +    /* Enable device - iommu-testdev only uses MMIO, not I/O ports */
> +    uint16_t cmd = qpci_config_readw(dev, PCI_COMMAND);
> +    cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
> +    qpci_config_writew(dev, PCI_COMMAND, cmd);
> +
> +    *bar = qpci_iomap(dev, 0, NULL);
> +    g_assert_false(bar->is_io);
> +
> +    return dev;
> +}
> +
> +static void test_intel_iommu_translation(void)
> +{
> +    QTestState *qts;
> +    QPCIBus *pcibus;
> +    QPCIDevice *dev;
> +    QPCIBar bar;
> +
Please add a machine availability check before qtest_init(), similar to
what the SMMUv3 test does:

    if (!qtest_has_machine("q35")) {
        g_test_skip("q35 machine not available");
        return;
    }

> +    /* Initialize QEMU environment for Intel IOMMU testing */
> +    qts = qtest_init("-machine q35,kernel-irqchip=split "
> +                     "-accel tcg "
> +                     "-device intel-iommu,pt=on,aw-bits=48 "
> +                     "-device iommu-testdev,bus=pcie.0,addr=0x4 "
> +                     "-m 512");
> +
> +    /* Setup and configure PCI device */
> +    dev = setup_qtest_pci_device(qts, &pcibus, &bar);
> +    if (!dev) {
> +        qtest_quit(qts);
> +        return;
> +    }
> +
> +    /* Run the translation tests */
> +    g_test_message("### Starting Intel IOMMU translation tests...###");
> +    qvtd_translation_batch(base_test_configs, ARRAY_SIZE(base_test_configs),
> +                           qts, dev, bar, Q35_IOMMU_BASE);
> +    g_test_message("### Intel IOMMU translation tests completed successfully! ###");
> +
> +    g_free(dev);
> +    qpci_free_pc(pcibus);
> +    qtest_quit(qts);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +    qtest_add_func("/iommu-testdev/intel-translation", test_intel_iommu_translation);
The test path naming differs from the SMMUv3 convention. For consistency,
consider splitting into separate test functions and using paths like:

    qtest_add_func("/iommu-testdev/intel/legacy-pt", test_intel_legacy_pt);
    qtest_add_func("/iommu-testdev/intel/legacy-trans", test_intel_legacy_trans);

This makes it easier to run individual test cases and debug failures.

> +    return g_test_run();
> +}
Missing newline at end of file. Please run scripts/checkpatch.pl check
it.

> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index e2d2e68092..344e836300 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -95,6 +95,8 @@ qtests_i386 = \
>    (config_all_devices.has_key('CONFIG_SDHCI_PCI') ? ['fuzz-sdcard-test'] : []) +            \
>    (config_all_devices.has_key('CONFIG_ESP_PCI') ? ['am53c974-test'] : []) +                 \
>    (config_all_devices.has_key('CONFIG_VTD') ? ['intel-iommu-test'] : []) +                 \
> +  (config_all_devices.has_key('CONFIG_VTD') and
> +   config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') ? ['iommu-intel-test'] : []) +      \
>    (host_os != 'windows' and                                                                \
>     config_all_devices.has_key('CONFIG_ACPI_ERST') ? ['erst-test'] : []) +                   \
>    (config_all_devices.has_key('CONFIG_PCIE_PORT') and                                       \
> -- 
> 2.39.5
> 

One more thing: the MAINTAINERS entry in patch 1/2 only adds the libqos
files. Please also add to 'X86 general architecture support':

    F: tests/qtest/iommu-intel-test.c

to the IOMMU section.

Overall, this is a nice addition to the IOMMU testing infrastructure.

Thanks,
Chao


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev
  2026-02-04  3:06 [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev Fengyuan Yu
  2026-02-04  3:06 ` [PATCH RFC v1 1/2] tests/qtest/libqos: Add Intel IOMMU helper library Fengyuan Yu
  2026-02-04  3:06 ` [PATCH RFC v1 2/2] tests/qtest: Add Intel IOMMU bare-metal test Fengyuan Yu
@ 2026-02-04 13:14 ` Tao Tang
  2026-02-05  9:35   ` Fengyuan
  2 siblings, 1 reply; 9+ messages in thread
From: Tao Tang @ 2026-02-04 13:14 UTC (permalink / raw)
  To: Fengyuan Yu, Fabiano Rosas, Laurent Vivier, Paolo Bonzini
  Cc: Chao Liu, qemu-devel

Hi Fengyuan,

On 2026/2/4 11:06, Fengyuan Yu wrote:
> Hi,
>
> This patch series adds a bare-metal qtest for the Intel IOMMU (VT-d) using
> the iommu-testdev framework. The test exercises address translation paths
> without requiring a full guest OS boot.
>
> Motivation
> ----------
>
> The Intel IOMMU implementation in QEMU supports various translation modes
> including pass-through and translated (4-level paging) modes. Currently,
> comprehensive testing of these translation paths requires booting a full
> guest OS with appropriate drivers, which is time-consuming and makes
> regression testing difficult.
>
> This new test fills that gap by using iommu-testdev to trigger DMA
> transactions and validate the IOMMU's translation logic directly.
>
> Test Coverage
> -------------
>
> The new test provides:
> - Legacy pass-through mode (identity mapping)
> - Legacy translated mode with 4-level page table walks
> - Root Entry Table and Context Entry Table configuration
> - Complete 48-bit address space translation
> - End-to-end DMA verification with memory validation
>
> Testing
> -------
>
> QTEST_QEMU_BINARY=./build/qemu-system-x86_64 \
>    ./build/tests/qtest/iommu-intel-test --tap -k
>
> Thanks,
> Fengyuan


Thanks for working on VT-d qtests. As a first-time patch contributor, 
you’ve already done a great job with good cover letter and commit 
messages to accurately summarize your work.


It seems that your code cannot be applied to the latest master branch. 
And I also noticed multiple lines >80 columns (some > 90). QEMU style 
says try to keep lines to 80 columns, only going a bit over when 
wrapping would harm readability but never > 90 columns.

../tests/qtest/libqos/qos-intel-iommu.c: In function 
‘qvtd_build_root_entry’:
../tests/qtest/libqos/qos-intel-iommu.c:168:31: error: 
‘VTD_CONTEXT_ENTRY_SLPTPTR’ undeclared (first use in this function); did 
you mean ‘VTD_CONTEXT_ENTRY_SSPTPTR’?
   168 |     lo = (context_table_ptr & VTD_CONTEXT_ENTRY_SLPTPTR) | 
VTD_CONTEXT_ENTRY_P;
       |                               ^~~~~~~~~~~~~~~~~~~~~~~~~
       |                               VTD_CONTEXT_ENTRY_SSPTPTR
../tests/qtest/libqos/qos-intel-iommu.c:168:31: note: each undeclared 
identifier is reported only once for each function it appears in
../tests/qtest/libqos/qos-intel-iommu.c: In function 
‘qvtd_build_context_entry’:
../tests/qtest/libqos/qos-intel-iommu.c:190:25: error: 
‘VTD_CONTEXT_ENTRY_SLPTPTR’ undeclared (first use in this function); did 
you mean ‘VTD_CONTEXT_ENTRY_SSPTPTR’?
   190 |              (slptptr & VTD_CONTEXT_ENTRY_SLPTPTR);
       |                         ^~~~~~~~~~~~~~~~~~~~~~~~~
       |                         VTD_CONTEXT_ENTRY_SSPTPTR
../tests/qtest/libqos/qos-intel-iommu.c: In function 
‘qvtd_setup_translation_tables’:
../tests/qtest/libqos/qos-intel-iommu.c:239:36: error: ‘VTD_SL_R’ 
undeclared (first use in this function); did you mean ‘VTD_SS_R’?
   239 |     pml4_entry = QVTD_PT_L3_BASE | VTD_SL_R | VTD_SL_W;
       |                                    ^~~~~~~~
       |                                    VTD_SS_R
../tests/qtest/libqos/qos-intel-iommu.c:239:47: error: ‘VTD_SL_W’ 
undeclared (first use in this function); did you mean ‘VTD_SS_W’?
   239 |     pml4_entry = QVTD_PT_L3_BASE | VTD_SL_R | VTD_SL_W;
       |                                               ^~~~~~~~
       |                                               VTD_SS_W
[92/2328] Compiling C object libblock.a.p/block.c.o



Besides when submitting patches it’s a good idea to read 
`docs/devel/submitting-a-patch.rst`. It may be a bit long, but making 
sure the code builds cleanly and running scripts/checkpatch.pl for style 
checks beforehand can help avoid many basic issues.


Finally I think CC the experts on x86 and VT-d emulation might be a more 
efficient way to review this sereis patch.

M: Paolo Bonzini <pbonzini@redhat.com>
R: Zhao Liu <zhao1.liu@intel.com>

M: Michael S. Tsirkin <mst@redhat.com>
R: Jason Wang <jasowang@redhat.com>
R: Yi Liu <yi.l.liu@intel.com>
R: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com>


Anyway I'm looking forward to your v2 patches.

Best regards,

Tao



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RFC v1 2/2] tests/qtest: Add Intel IOMMU bare-metal test
  2026-02-04  7:43   ` Chao Liu
@ 2026-02-05  9:18     ` Fengyuan
  0 siblings, 0 replies; 9+ messages in thread
From: Fengyuan @ 2026-02-05  9:18 UTC (permalink / raw)
  To: Chao Liu; +Cc: Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang,
	qemu-devel

Hi Chao,

Thanks for the review!

On 2/4/2026 3:43 PM, Chao Liu wrote:
> Hi Fengyuan,
> 
> On Wed, Feb 04, 2026 at 11:06:20AM +0800, Fengyuan Yu wrote:
>> Add a qtest suite for the Intel IOMMU (VT-d) device on the Q35 machine.
>> The test exercises pass-through and translated translation modes using
>> iommu-testdev and the qos-intel-iommu helpers.
>>
>> The test validates:
>> - Root Entry Table and Context Entry Table configuration
>> - 4-level page table walks for 48-bit address translation
>> - Pass-through mode (identity mapping)
>> - Translated mode with complete IOVA-to-PA translation
>> - DMA transaction execution with memory content verification
>>
> Good commit message.
> 
>> Signed-off-by: Fengyuan Yu <15fengyuan@gmail.com>
>> ---
>>   tests/qtest/iommu-intel-test.c | 137 +++++++++++++++++++++++++++++++++
>>   tests/qtest/meson.build        |   2 +
>>   2 files changed, 139 insertions(+)
>>   create mode 100644 tests/qtest/iommu-intel-test.c
>>
>> diff --git a/tests/qtest/iommu-intel-test.c b/tests/qtest/iommu-intel-test.c
>> new file mode 100644
>> index 0000000000..9f631be2c5
>> --- /dev/null
>> +++ b/tests/qtest/iommu-intel-test.c
>> @@ -0,0 +1,137 @@
>> +/*
>> + * QTest for Intel IOMMU (VT-d) with iommu-testdev
>> + *
>> + * This QTest file is used to test the Intel IOMMU with iommu-testdev so that
>> + * we can test VT-d without any guest kernel or firmware.
>> + *
>> + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "libqtest.h"
>> +#include "libqos/pci.h"
>> +#include "libqos/pci-pc.h"
>> +#include "hw/pci/pci_regs.h"
>> +#include "hw/misc/iommu-testdev.h"
>> +#include "libqos/qos-intel-iommu.h"
>> +
>> +#define DMA_LEN           4
>> +
>> +/* Test configurations for different Intel IOMMU modes */
>> +static const QVTDTestConfig base_test_configs[] = {
>> +    {
>> +        .trans_mode = QVTD_TM_LEGACY_PT,
>> +        .dma_iova = 0x10100000,  /* Use address in guest RAM range (inside 512MB) */
>> +        .dma_pa = 0x10100000,
>> +        .dma_len = DMA_LEN,
>> +        .expected_result = 0,
>> +        .domain_id = 1,
>> +    },
>> +    {
>> +        .trans_mode = QVTD_TM_LEGACY_TRANS,
>> +        .dma_iova = QVTD_TEST_IOVA,
>> +        .dma_pa = QVTD_TEST_PA,
>> +        .dma_len = DMA_LEN,
>> +        .expected_result = 0,
>> +        .domain_id = 1,
>> +    },
>> +};
>> +
>> +static QPCIDevice *setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus,
>> +                                          QPCIBar *bar)
>> +{
>> +    uint16_t vid, did;
>> +    QPCIDevice *dev = NULL;
>> +    int device_count = 0;
>> +
>> +    *pcibus = qpci_new_pc(qts, NULL);
>> +    g_assert(*pcibus != NULL);
>> +
>> +    g_test_message("Scanning PCI bus for iommu-testdev (vendor:device = 0x%04x:0x%04x)...",
>> +                   IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID);
>> +
>> +    /* Find device by vendor/device ID to avoid slot surprises. */
>> +    for (int s = 0; s < 32 && !dev; s++) {
>> +        for (int fn = 0; fn < 8 && !dev; fn++) {
>> +            QPCIDevice *cand = qpci_device_find(*pcibus, QPCI_DEVFN(s, fn));
>> +            if (!cand) {
>> +                continue;
>> +            }
>> +            vid = qpci_config_readw(cand, PCI_VENDOR_ID);
>> +            did = qpci_config_readw(cand, PCI_DEVICE_ID);
>> +
>> +            device_count++;
>> +            g_test_message("  Found PCI device at %02x:%x - vendor:device = 0x%04x:0x%04x",
>> +                           s, fn, vid, did);
>> +
>> +            if (vid == IOMMU_TESTDEV_VENDOR_ID &&
>> +                did == IOMMU_TESTDEV_DEVICE_ID) {
>> +                dev = cand;
>> +                g_test_message("Found iommu-testdev! devfn: 0x%x", cand->devfn);
>> +            } else {
>> +                g_free(cand);
>> +            }
>> +        }
>> +    }
>> +
> 
> This manual PCI bus scanning is verbose. The SMMUv3 test uses
> qpci_device_foreach() which is more concise:
> 
>      qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID,
>                          IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev);
> 
> See iommu-smmuv3-test.c for reference.
> 
Thanks for the review. I'ill improve the PCI bus scanning in v2.

>> +    if (!dev) {
>> +        g_test_message("ERROR: iommu-testdev not found after scanning %d PCI devices", device_count);
>> +        g_test_message("Expected vendor:device = 0x%04x:0x%04x (PCI_VENDOR_ID_REDHAT:PCI_DEVICE_ID_REDHAT_TEST)",
>> +                       IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID);
>> +        qpci_free_pc(*pcibus);
>> +        *pcibus = NULL;
>> +        g_test_skip("iommu-testdev not found on PCI bus - device may not be compiled or registered");
>> +        return NULL;
>> +    }
>> +
>> +    /* Enable device - iommu-testdev only uses MMIO, not I/O ports */
>> +    uint16_t cmd = qpci_config_readw(dev, PCI_COMMAND);
>> +    cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
>> +    qpci_config_writew(dev, PCI_COMMAND, cmd);
>> +
>> +    *bar = qpci_iomap(dev, 0, NULL);
>> +    g_assert_false(bar->is_io);
>> +
>> +    return dev;
>> +}
>> +
>> +static void test_intel_iommu_translation(void)
>> +{
>> +    QTestState *qts;
>> +    QPCIBus *pcibus;
>> +    QPCIDevice *dev;
>> +    QPCIBar bar;
>> +
> Please add a machine availability check before qtest_init(), similar to
> what the SMMUv3 test does:
> 
Thanks for the review. I'ill add this check in v2.

>      if (!qtest_has_machine("q35")) {
>          g_test_skip("q35 machine not available");
>          return;
>      }
> 
>> +    /* Initialize QEMU environment for Intel IOMMU testing */
>> +    qts = qtest_init("-machine q35,kernel-irqchip=split "
>> +                     "-accel tcg "
>> +                     "-device intel-iommu,pt=on,aw-bits=48 "
>> +                     "-device iommu-testdev,bus=pcie.0,addr=0x4 "
>> +                     "-m 512");
>> +
>> +    /* Setup and configure PCI device */
>> +    dev = setup_qtest_pci_device(qts, &pcibus, &bar);
>> +    if (!dev) {
>> +        qtest_quit(qts);
>> +        return;
>> +    }
>> +
>> +    /* Run the translation tests */
>> +    g_test_message("### Starting Intel IOMMU translation tests...###");
>> +    qvtd_translation_batch(base_test_configs, ARRAY_SIZE(base_test_configs),
>> +                           qts, dev, bar, Q35_IOMMU_BASE);
>> +    g_test_message("### Intel IOMMU translation tests completed successfully! ###");
>> +
>> +    g_free(dev);
>> +    qpci_free_pc(pcibus);
>> +    qtest_quit(qts);
>> +}
>> +
>> +int main(int argc, char **argv)
>> +{
>> +    g_test_init(&argc, &argv, NULL);
>> +    qtest_add_func("/iommu-testdev/intel-translation", test_intel_iommu_translation);
> The test path naming differs from the SMMUv3 convention. For consistency,
> consider splitting into separate test functions and using paths like:
> 
>      qtest_add_func("/iommu-testdev/intel/legacy-pt", test_intel_legacy_pt);
>      qtest_add_func("/iommu-testdev/intel/legacy-trans", test_intel_legacy_trans);
> 
> This makes it easier to run individual test cases and debug failures.
> 
Thanks for the review. I will improve the test cases to make them consistent 
with the SMMUv3 implementation.

>> +    return g_test_run();
>> +}
> Missing newline at end of file. Please run scripts/checkpatch.pl check
> it.
> 
Thanks for the review. I will carefully use scripts/checkpatch.pl to review the 
v2 patch before submitting.

>> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
>> index e2d2e68092..344e836300 100644
>> --- a/tests/qtest/meson.build
>> +++ b/tests/qtest/meson.build
>> @@ -95,6 +95,8 @@ qtests_i386 = \
>>     (config_all_devices.has_key('CONFIG_SDHCI_PCI') ? ['fuzz-sdcard-test'] : []) +            \
>>     (config_all_devices.has_key('CONFIG_ESP_PCI') ? ['am53c974-test'] : []) +                 \
>>     (config_all_devices.has_key('CONFIG_VTD') ? ['intel-iommu-test'] : []) +                 \
>> +  (config_all_devices.has_key('CONFIG_VTD') and
>> +   config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') ? ['iommu-intel-test'] : []) +      \
>>     (host_os != 'windows' and                                                                \
>>      config_all_devices.has_key('CONFIG_ACPI_ERST') ? ['erst-test'] : []) +                   \
>>     (config_all_devices.has_key('CONFIG_PCIE_PORT') and                                       \
>> -- 
>> 2.39.5
>>
> 
> One more thing: the MAINTAINERS entry in patch 1/2 only adds the libqos
> files. Please also add to 'X86 general architecture support':
> 
>      F: tests/qtest/iommu-intel-test.c
> 
> to the IOMMU section.
> 
It seems that 'VT-d Emulation' is more appropriate. I will add it to 'VT-d 
Emulation' first.

Thanks,
Fengyuan

> Overall, this is a nice addition to the IOMMU testing infrastructure.
> 
> Thanks,
> Chao



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RFC v1 1/2] tests/qtest/libqos: Add Intel IOMMU helper library
  2026-02-04  7:31   ` Chao Liu
@ 2026-02-05  9:20     ` Fengyuan
  0 siblings, 0 replies; 9+ messages in thread
From: Fengyuan @ 2026-02-05  9:20 UTC (permalink / raw)
  To: Chao Liu; +Cc: Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang,
	qemu-devel

Hi Chao,

Thanks for the review!

On 2/4/2026 3:31 PM, Chao Liu wrote:
> Hi Fengyuan,
> 
> Thanks for working on Intel IOMMU bare-metal testing support. I have some
> comments below.
> 
> On Wed, Feb 04, 2026 at 11:06:19AM +0800, Fengyuan Yu wrote:
>> Introduce a libqos helper module for Intel IOMMU (VT-d) testing with
>> iommu-testdev. The helper provides routines to:
>>
>> - Build Root Entry Tables, Context Entry Tables, and 4-level page tables
>>    for 48-bit address translation
>> - Program VT-d registers (Root Table Pointer, Global Command Register)
>>    following the Intel VT-d specification
>> - Execute DMA translations and verify results
>>
>> The current implementation supports Legacy mode with both pass-through and
>> translated (4-level paging) modes. Support for Scalable mode (PASID-based)
>> can be added in future patches.
>>
> The commit message looks good.
> 
>> Signed-off-by: Fengyuan Yu <15fengyuan@gmail.com>
>>
>> ---
>>   MAINTAINERS                          |   1 +
>>   tests/qtest/libqos/meson.build       |   3 +
>>   tests/qtest/libqos/qos-intel-iommu.c | 566 +++++++++++++++++++++++++++
>>   tests/qtest/libqos/qos-intel-iommu.h | 299 ++++++++++++++
>>   4 files changed, 869 insertions(+)
>>   create mode 100644 tests/qtest/libqos/qos-intel-iommu.c
>>   create mode 100644 tests/qtest/libqos/qos-intel-iommu.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 9b7ed4fccb..1cd2a4f474 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -3585,6 +3585,7 @@ M: Tao Tang <tangtao1634@phytium.com.cn>
>>   S: Maintained
>>   F: tests/qtest/libqos/qos-iommu*
>>   F: tests/qtest/libqos/qos-smmuv3*
>> +F: tests/qtest/libqos/qos-intel-iommu*
>>   
>>   Device Fuzzing
>>   M: Alexander Bulekov <alxndr@bu.edu>
>> diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
>> index 8d6758ec2b..7f7c4f9c6f 100644
>> --- a/tests/qtest/libqos/meson.build
>> +++ b/tests/qtest/libqos/meson.build
>> @@ -72,6 +72,9 @@ endif
>>   if config_all_devices.has_key('CONFIG_RISCV_IOMMU')
>>     libqos_srcs += files('riscv-iommu.c')
>>   endif
>> +if config_all_devices.has_key('CONFIG_VTD')
>> +  libqos_srcs += files('qos-intel-iommu.c')
>> +endif
>>   if config_all_devices.has_key('CONFIG_TPCI200')
>>     libqos_srcs += files('tpci200.c')
>>   endif
>> diff --git a/tests/qtest/libqos/qos-intel-iommu.c b/tests/qtest/libqos/qos-intel-iommu.c
>> new file mode 100644
>> index 0000000000..d6a733de7e
>> --- /dev/null
>> +++ b/tests/qtest/libqos/qos-intel-iommu.c
>> @@ -0,0 +1,566 @@
>> +/*
>> + * QOS Intel IOMMU (VT-d) Module Implementation
>> + *
>> + * This module provides Intel IOMMU-specific helper functions for libqos tests.
>> + *
>> + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "../libqtest.h"
>> +#include "pci.h"
>> +#include "qos-intel-iommu.h"
>> +
> You should also include "qos-iommu-testdev.h" here, as the SMMUv3 helper
> does. This would allow reusing the common DMA trigger infrastructure.
> 
Thanks for the review. After developing based on an earlier version of 
iommu-testdev, I had overlooked its interface updates. I have now pulled the 
latest changes and adapted the unified interface for Intel IOMMU functionality. 
I will include this update in v2.

>> +#define QVTD_POLL_DELAY_US        1000
>> +#define QVTD_POLL_MAX_RETRIES     1000
>> +#define QVTD_AW_48BIT_ENCODING    2
>> +
>> +/*
>> + * iommu-testdev DMA attribute layout for Intel VT-d traffic.
>> + *
>> + * Bits [2:0] keep using the generic iommu-testdev encoding
>> + * (secure + ArmSecuritySpace). Bits [23:8] carry the PCI Requester ID in the
>> + * format defined in the Intel VT-d spec (Figure 3-2 in
>> + * spec/Intel-iommu-spec.txt), and bits [31:24] contain the PASID that tags
>> + * scalable-mode transactions. Bit 4 distinguishes between pure legacy RID
>> + * requests and scalable-mode PASID-tagged requests. The PASID field is
>> + * limited to 8 bits because MemTxAttrs::pid only carries 8 bits today (see
>> + * include/exec/memattrs.h and the VTD_ECAP_PSS limit in
>> + * hw/i386/intel_iommu_internal.h).
>> + */
>> +#define QVTD_DMA_ATTR_MODE_SHIFT      4
>> +#define QVTD_DMA_ATTR_MODE_MASK       0x1
>> +#define QVTD_DMA_ATTR_MODE_LEGACY     0
>> +#define QVTD_DMA_ATTR_MODE_SCALABLE   1
>> +#define QVTD_DMA_ATTR_RID_SHIFT       8
>> +#define QVTD_DMA_ATTR_RID_MASK        0xffffu
>> +#define QVTD_DMA_ATTR_PASID_BITS      8
>> +#define QVTD_DMA_ATTR_PASID_SHIFT     24
>> +#define QVTD_DMA_ATTR_PASID_MASK      ((1u << QVTD_DMA_ATTR_PASID_BITS) - 1)
>> +
>> +#define QVTD_PCI_FUNCS_PER_DEVICE     8
>> +#define QVTD_PCI_DEVS_PER_BUS         32
>> +
>> +static void qvtd_wait_for_bitsl(QTestState *qts, uint64_t addr,
>> +                                uint32_t mask, bool expect_set)
>> +{
>> +    uint32_t val = 0;
>> +
>> +    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
>> +        val = qtest_readl(qts, addr);
>> +        if (!!(val & mask) == expect_set) {
>> +            return;
>> +        }
>> +        g_usleep(QVTD_POLL_DELAY_US);
>> +    }
>> +
>> +    g_error("Timeout waiting for bits 0x%x (%s) at 0x%llx, last=0x%x",
> Please use PRIx64 instead of %llx with (unsigned long long) cast.
> This applies to all similar occurrences in this file.
> 
Thanks for the review. I'ill fix this in v2.

>> +            mask, expect_set ? "set" : "clear",
>> +            (unsigned long long)addr, val);
>> +}
>> +
>> +static void qvtd_wait_for_bitsq(QTestState *qts, uint64_t addr,
>> +                                uint64_t mask, bool expect_set)
>> +{
>> +    uint64_t val = 0;
>> +
>> +    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
>> +        val = qtest_readq(qts, addr);
>> +        if (!!(val & mask) == expect_set) {
>> +            return;
>> +        }
>> +        g_usleep(QVTD_POLL_DELAY_US);
>> +    }
>> +
>> +    g_error("Timeout waiting for bits 0x%llx (%s) at 0x%llx, last=0x%llx",
>> +            (unsigned long long)mask, expect_set ? "set" : "clear",
>> +            (unsigned long long)addr, (unsigned long long)val);
>> +}
>> +
>> +static uint16_t qvtd_calc_sid(const QPCIDevice *dev)
>> +{
>> +    uint16_t devfn = dev->devfn & 0xff;
>> +    uint16_t bus = (dev->devfn >> 8) & 0xff;
>> +    uint8_t device = (devfn >> 3) & 0x1f;
>> +    uint8_t function = devfn & 0x7;
>> +
>> +    /* Validate BDF components. */
>> +    if (device >= QVTD_PCI_DEVS_PER_BUS || function >= QVTD_PCI_FUNCS_PER_DEVICE) {
>> +        g_error("Invalid BDF: bus=%u device=%u function=%u", bus, device, function);
>> +    }
>> +
>> +    return (bus << 8) | devfn;
>> +}
>> +
>> +static bool qvtd_validate_dma_memory(QVTDTestContext *ctx)
>> +{
>> +    uint32_t len = ctx->config.dma_len;
>> +    g_autofree uint8_t *buf = NULL;
>> +
>> +    if (!len) {
>> +        return true;
>> +    }
>> +
>> +    buf = g_malloc(len);
>> +    qtest_memread(ctx->qts, ctx->config.dma_pa, buf, len);
>> +
>> +    for (uint32_t i = 0; i < len; i++) {
>> +        uint8_t expected = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
>> +        if (buf[i] != expected) {
>> +            g_test_message("Memory mismatch at PA=0x%llx offset=%u "
>> +                           "expected=0x%02x actual=0x%02x",
>> +                           (unsigned long long)ctx->config.dma_pa, i,
>> +                           expected, buf[i]);
>> +            return false;
>> +        }
>> +    }
>> +
>> +    return true;
>> +}
>> +
>> +uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx)
>> +{
>> +    return ctx->config.expected_result;
>> +}
>> +
>> +uint32_t qvtd_build_dma_attrs(uint16_t bdf, uint32_t pasid)
>> +{
>> +    uint32_t attrs = 0;
>> +    uint8_t bus = (bdf >> 8) & 0xff;
>> +    uint8_t devfn = bdf & 0xff;
>> +    uint8_t device = devfn >> 3;
>> +    uint8_t function = devfn & 0x7;
>> +    bool scalable_mode = pasid != 0;
>> +
>> +    if (device >= QVTD_PCI_DEVS_PER_BUS || function >= QVTD_PCI_FUNCS_PER_DEVICE) {
>> +        g_error("Invalid requester-id 0x%04x (bus=%u device=%u function=%u)",
>> +                bdf, bus, device, function);
>> +    }
>> +
>> +    attrs = ITD_ATTRS_SET_SECURE(attrs, 0);
>> +    attrs = ITD_ATTRS_SET_SPACE(attrs, 0);
>> +    attrs |= ((uint32_t)bdf & QVTD_DMA_ATTR_RID_MASK) << QVTD_DMA_ATTR_RID_SHIFT;
>> +
>> +    if (scalable_mode) {
>> +        if (pasid > QVTD_DMA_ATTR_PASID_MASK) {
>> +            g_error("PASID 0x%x exceeds %u-bit limit imposed by MemTxAttrs",
>> +                    pasid, QVTD_DMA_ATTR_PASID_BITS);
>> +        }
>> +
>> +        attrs |= (QVTD_DMA_ATTR_MODE_SCALABLE << QVTD_DMA_ATTR_MODE_SHIFT);
>> +        attrs |= ((pasid & QVTD_DMA_ATTR_PASID_MASK) << QVTD_DMA_ATTR_PASID_SHIFT);
>> +    } else {
>> +        attrs |= (QVTD_DMA_ATTR_MODE_LEGACY << QVTD_DMA_ATTR_MODE_SHIFT);
>> +    }
>> +
>> +    return attrs;
>> +}
>> +
>> +static void qvtd_build_root_entry(QTestState *qts, uint8_t bus,
>> +                                  uint64_t context_table_ptr)
>> +{
>> +    uint64_t root_entry_addr = QVTD_ROOT_TABLE_BASE + (bus * 16);
>> +    uint64_t lo, hi;
>> +
>> +    /* Root Entry Low: Context Table Pointer + Present bit (VT-d spec Section 9.1). */
>> +    lo = (context_table_ptr & VTD_CONTEXT_ENTRY_SLPTPTR) | VTD_CONTEXT_ENTRY_P;
>> +    hi = 0;  /* Reserved. */
>> +
>> +    qtest_writeq(qts, root_entry_addr, lo);
>> +    qtest_writeq(qts, root_entry_addr + 8, hi);
>> +}
>> +
>> +static void qvtd_build_context_entry(QTestState *qts, uint16_t sid,
>> +                                     QVTDTransMode mode, uint16_t domain_id,
>> +                                     uint64_t slptptr)
>> +{
>> +    uint8_t devfn = sid & 0xff;
>> +    uint64_t context_entry_addr = QVTD_CONTEXT_TABLE_BASE + (devfn * 16);
>> +    uint64_t lo, hi;
>> +
>> +    if (mode == QVTD_TM_LEGACY_PT) {
>> +        /* Pass-through mode (VT-d spec Section 3.9, Section 9.3). */
>> +        lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_PASS_THROUGH;
>> +        hi = ((uint64_t)domain_id << 8) | QVTD_AW_48BIT_ENCODING;
>> +    } else {
>> +        /* Translated mode: 4-level paging (AW=2 for 48-bit, VT-d spec Section 9.3). */
>> +        lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_MULTI_LEVEL |
>> +             (slptptr & VTD_CONTEXT_ENTRY_SLPTPTR);
>> +        hi = ((uint64_t)domain_id << 8) | QVTD_AW_48BIT_ENCODING;
>> +    }
>> +
>> +    qtest_writeq(qts, context_entry_addr, lo);
>> +    qtest_writeq(qts, context_entry_addr + 8, hi);
>> +}
>> +
>> +void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
>> +                                   uint64_t pa, QVTDTransMode mode)
>> +{
>> +    uint64_t pml4_entry, pdpt_entry, pd_entry, pt_entry;
>> +    uint64_t pml4_addr, pdpt_addr, pd_addr, pt_addr;
>> +    uint32_t pml4_idx, pdpt_idx, pd_idx, pt_idx;
>> +    const char *mode_str = (mode == QVTD_TM_LEGACY_PT) ?
>> +                           "Pass-Through" : "Translated";
>> +
>> +    g_test_message("Begin of page table construction: IOVA=0x%llx PA=0x%llx mode=%s",
>> +                   (unsigned long long)iova, (unsigned long long)pa, mode_str);
>> +
>> +    /* Pass-through mode doesn't need page tables */
>> +    if (mode == QVTD_TM_LEGACY_PT) {
>> +        g_test_message("Pass-through mode: skipping page table setup");
>> +        return;
>> +    }
>> +
>> +    /* Extract indices from IOVA
>> +     * 4-level paging for 48-bit virtual address space:
>> +     * - PML4 index: bits [47:39] (9 bits = 512 entries)
>> +     * - PDPT index:  bits [38:30] (9 bits = 512 entries)
>> +     * - PD index:    bits [29:21] (9 bits = 512 entries)
>> +     * - PT index:    bits [20:12] (9 bits = 512 entries)
>> +     * - Page offset: bits [11:0]  (12 bits = 4KB pages)
>> +     */
>> +    pml4_idx = (iova >> 39) & 0x1ff;  /* Bits [47:39] */
>> +    pdpt_idx = (iova >> 30) & 0x1ff;  /* Bits [38:30] */
>> +    pd_idx = (iova >> 21) & 0x1ff;    /* Bits [29:21] */
>> +    pt_idx = (iova >> 12) & 0x1ff;    /* Bits [20:12] */
>> +
>> +    /*
>> +     * Build 4-level page table hierarchy (VT-d spec Section 9.3, Table 9-3).
>> +     * Non-leaf entries: both R+W set for full access (spec allows R or W individually).
>> +     * Per VT-d spec Section 9.8: "If either the R or W field of a non-leaf
>> +     * paging-structure entry is 1", indicating that setting one or both is valid.
>> +     * We set both R+W for non-leaf entries as standard practice.
>> +     */
>> +
>> +    /* PML4 Entry: points to PDPT. */
>> +    pml4_addr = QVTD_PT_L4_BASE + (pml4_idx * 8);
>> +    pml4_entry = QVTD_PT_L3_BASE | VTD_SL_R | VTD_SL_W;
>> +    qtest_writeq(qts, pml4_addr, pml4_entry);
>> +
>> +    /* PDPT Entry: points to PD. */
>> +    pdpt_addr = QVTD_PT_L3_BASE + (pdpt_idx * 8);
>> +    pdpt_entry = QVTD_PT_L2_BASE | VTD_SL_R | VTD_SL_W;
>> +    qtest_writeq(qts, pdpt_addr, pdpt_entry);
>> +
>> +    /* PD Entry: points to PT. */
>> +    pd_addr = QVTD_PT_L2_BASE + (pd_idx * 8);
>> +    pd_entry = QVTD_PT_L1_BASE | VTD_SL_R | VTD_SL_W;
>> +    qtest_writeq(qts, pd_addr, pd_entry);
>> +
>> +    /* PT Entry: points to physical page (leaf). */
>> +    pt_addr = QVTD_PT_L1_BASE + (pt_idx * 8);
>> +    pt_entry = (pa & VTD_PAGE_MASK_4K) | VTD_SL_R | VTD_SL_W;
>> +    qtest_writeq(qts, pt_addr, pt_entry);
>> +
>> +    g_test_message("End of page table construction: mapped IOVA=0x%llx -> PA=0x%llx",
>> +                   (unsigned long long)iova, (unsigned long long)pa);
>> +}
>> +
>> +static void qvtd_invalidate_context_cache(QTestState *qts,
>> +                                          uint64_t iommu_base)
>> +{
>> +    uint64_t ccmd_val;
>> +
>> +    /* Context Command Register: Global invalidation (VT-d spec Section 6.5.1.1). */
>> +    ccmd_val = VTD_CCMD_ICC | VTD_CCMD_GLOBAL_INVL;
>> +    qtest_writeq(qts, iommu_base + DMAR_CCMD_REG, ccmd_val);
>> +
>> +    /* Wait for ICC bit to clear. */
>> +    qvtd_wait_for_bitsq(qts, iommu_base + DMAR_CCMD_REG,
>> +                        VTD_CCMD_ICC, false);
>> +}
>> +
>> +static void qvtd_invalidate_iotlb(QTestState *qts, uint64_t iommu_base)
>> +{
>> +    uint64_t iotlb_val;
>> +
>> +    /* IOTLB Invalidate Register: Global flush (VT-d spec Section 6.5.1.2). */
>> +    iotlb_val = VTD_TLB_IVT | VTD_TLB_GLOBAL_FLUSH;
>> +    qtest_writeq(qts, iommu_base + DMAR_IOTLB_REG, iotlb_val);
>> +
>> +    /* Wait for IVT bit to clear. */
>> +    qvtd_wait_for_bitsq(qts, iommu_base + DMAR_IOTLB_REG,
>> +                        VTD_TLB_IVT, false);
>> +}
>> +
>> +static void qvtd_clear_memory_regions(QTestState *qts)
>> +{
>> +    /* Clear root table. */
>> +    qtest_memset(qts, QVTD_ROOT_TABLE_BASE, 0, 4096);
>> +
>> +    /* Clear context table. */
>> +    qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 4096);
>> +
>> +    /* Clear all page table levels (4 levels * 4KB each = 16KB). */
>> +    qtest_memset(qts, QVTD_PT_L4_BASE, 0, 16384);
>> +}
>> +
>> +void qvtd_program_regs(QTestState *qts, uint64_t iommu_base)
>> +{
>> +    uint32_t gcmd;
>> +
>> +    /* 1. Disable translation (VT-d spec Section 11.4.4). */
>> +    gcmd = qtest_readl(qts, iommu_base + DMAR_GCMD_REG);
>> +    gcmd &= ~VTD_GCMD_TE;
>> +    qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
>> +
>> +    /* Wait for TES to clear. */
>> +    qvtd_wait_for_bitsl(qts, iommu_base + DMAR_GSTS_REG,
>> +                        VTD_GSTS_TES, false);
>> +
>> +    /* 2. Program root table address (VT-d spec Section 11.4.5). */
>> +    qtest_writeq(qts, iommu_base + DMAR_RTADDR_REG, QVTD_ROOT_TABLE_BASE);
>> +
>> +    /* 3. Set root table pointer (VT-d spec Section 6.6). */
>> +    gcmd = qtest_readl(qts, iommu_base + DMAR_GCMD_REG);
>> +    gcmd |= VTD_GCMD_SRTP;
>> +    qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
>> +
>> +    /* Wait for RTPS. */
>> +    qvtd_wait_for_bitsl(qts, iommu_base + DMAR_GSTS_REG,
>> +                        VTD_GSTS_RTPS, true);
>> +
>> +    /* Invalidate context cache after setting root table pointer. */
>> +    qvtd_invalidate_context_cache(qts, iommu_base);
>> +
>> +    /* 4. Unmask fault event interrupt to avoid warning messages. */
>> +    qtest_writel(qts, iommu_base + DMAR_FECTL_REG, 0);
>> +
>> +    /* NOTE: Translation is NOT enabled here - caller must enable after building structures. */
>> +}
>> +
>> +uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
>> +                                uint16_t sid, uint16_t domain_id,
>> +                                uint64_t iova, uint64_t pa)
>> +{
>> +    uint8_t bus = (sid >> 8) & 0xff;
>> +    const char *mode_str = (mode == QVTD_TM_LEGACY_PT) ?
>> +                           "Pass-Through" : "Translated";
>> +
>> +    g_test_message("Begin of construction: IOVA=0x%llx PA=0x%llx "
>> +                   "mode=%s domain_id=%u ===",
>> +                   (unsigned long long)iova, (unsigned long long)pa,
>> +                   mode_str, domain_id);
>> +
>> +    /* Build root entry */
>> +    qvtd_build_root_entry(qts, bus, QVTD_CONTEXT_TABLE_BASE);
>> +
>> +    /* Build context entry */
>> +    if (mode == QVTD_TM_LEGACY_PT) {
>> +        /* Pass-through mode: no page tables needed */
>> +        qvtd_build_context_entry(qts, sid, mode, domain_id, 0);
>> +        g_test_message("End of construction: identity mapping to PA=0x%llx ===",
>> +                       (unsigned long long)pa);
>> +    } else {
>> +        /* Translated mode: build 4-level page tables */
>> +        qvtd_setup_translation_tables(qts, iova, pa, QVTD_TM_LEGACY_TRANS);
>> +        qvtd_build_context_entry(qts, sid, mode, domain_id, QVTD_PT_L4_BASE);
>> +        g_test_message("End of construction: mapped IOVA=0x%llx -> PA=0x%llx ===",
>> +                       (unsigned long long)iova, (unsigned long long)pa);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx)
>> +{
>> +    uint32_t gcmd;
>> +
>> +    /* Clear memory regions once during setup */
>> +    qvtd_clear_memory_regions(ctx->qts);
>> +
>> +    /* Program IOMMU registers (sets up root table pointer) */
>> +    qvtd_program_regs(ctx->qts, ctx->iommu_base);
>> +
>> +    /* Build translation structures AFTER clearing memory */
>> +    ctx->trans_status = qvtd_build_translation(ctx->qts, ctx->config.trans_mode,
>> +                                               ctx->sid, ctx->config.domain_id,
>> +                                               ctx->config.dma_iova,
>> +                                               ctx->config.dma_pa);
>> +    if (ctx->trans_status != 0) {
>> +        return ctx->trans_status;
>> +    }
>> +
>> +    /* Invalidate caches using register-based invalidation */
>> +    qvtd_invalidate_context_cache(ctx->qts, ctx->iommu_base);
>> +    qvtd_invalidate_iotlb(ctx->qts, ctx->iommu_base);
>> +
>> +    /* Enable translation AFTER building structures and invalidating caches */
>> +    gcmd = qtest_readl(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG);
>> +    gcmd |= VTD_GCMD_TE;
>> +    qtest_writel(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG, gcmd);
>> +
>> +    /* Wait for TES */
>> +    qvtd_wait_for_bitsl(ctx->qts, ctx->iommu_base + DMAR_GSTS_REG,
>> +                        VTD_GSTS_TES, true);
>> +
>> +    return 0;
>> +}
>> +
>> +uint32_t qvtd_trigger_dma(QVTDTestContext *ctx)
> This function duplicates the logic in qos_iommu_testdev_trigger_dma().
> The SMMUv3 helper uses the common qos-iommu-testdev infrastructure with
> callback functions (setup_fn, attrs_fn, validate_fn, report_fn).
> 
> I'd suggest refactoring to use qos_iommu_testdev_single_translation()
> instead, similar to how qsmmu_run_translation_case() does it in
> qos-smmuv3.c
> 
> Thanks,
> Chao
Thank you. The root cause of this issue is the same as the first review.
I will port my functionality following the coding style of the latest 
iommu-testdev version in v2.

Thanks,
Fengyuan

>> +{
>> +    uint64_t iova = ctx->config.dma_iova;
>> +    uint32_t len = ctx->config.dma_len;
>> +    uint32_t result, attrs_val;
>> +    const char *mode_str = (ctx->config.trans_mode == QVTD_TM_LEGACY_PT) ?
>> +                           "Pass-Through" : "Translated";
>> +
>> +    /* Write IOVA low 32 bits */
>> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_LO, (uint32_t)iova);
>> +
>> +    /* Write IOVA high 32 bits */
>> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_HI, (uint32_t)(iova >> 32));
>> +
>> +    /* Write DMA length */
>> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_LEN, len);
>> +
>> +    /* Build and write DMA attributes with BDF (PASID=0 for Legacy mode) */
>> +    attrs_val = qvtd_build_dma_attrs(ctx->sid, 0);
>> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_ATTRS, attrs_val);
>> +
>> +    /* Arm DMA by writing 1 to doorbell */
>> +    qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_DBELL, ITD_DMA_DBELL_ARM);
>> +
>> +    /* Trigger DMA by reading from triggering register */
>> +    qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_TRIGGERING);
>> +
>> +    /* Poll for completion */
>> +    ctx->dma_result = ITD_DMA_RESULT_BUSY;
>> +    for (int attempt = 0; attempt < QVTD_POLL_MAX_RETRIES; attempt++) {
>> +        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(QVTD_POLL_DELAY_US);
>> +    }
>> +
>> +    if (ctx->dma_result == ITD_DMA_RESULT_BUSY) {
>> +        ctx->dma_result = ITD_DMA_ERR_TX_FAIL;
>> +        g_test_message("-> DMA timeout detected, forcing failure");
>> +    }
>> +
>> +    if (ctx->dma_result == 0) {
>> +        g_test_message("-> DMA succeeded: mode=%s", mode_str);
>> +    } else {
>> +        g_test_message("-> DMA failed: mode=%s result=0x%x",
>> +                       mode_str, ctx->dma_result);
>> +    }
>> +
>> +    return ctx->dma_result;
>> +}
>> +
>> +void qvtd_cleanup_translation(QVTDTestContext *ctx)
>> +{
>> +    uint8_t bus = (ctx->sid >> 8) & 0xff;
>> +    uint8_t devfn = ctx->sid & 0xff;
>> +    uint64_t root_entry_addr = QVTD_ROOT_TABLE_BASE + (bus * 16);
>> +    uint64_t context_entry_addr = QVTD_CONTEXT_TABLE_BASE + (devfn * 16);
>> +    uint32_t gcmd;
>> +
>> +    /* Disable translation before tearing down the structures */
>> +    gcmd = qtest_readl(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG);
>> +    if (gcmd & VTD_GCMD_TE) {
>> +        gcmd &= ~VTD_GCMD_TE;
>> +        qtest_writel(ctx->qts, ctx->iommu_base + DMAR_GCMD_REG, gcmd);
>> +        qvtd_wait_for_bitsl(ctx->qts, ctx->iommu_base + DMAR_GSTS_REG,
>> +                            VTD_GSTS_TES, false);
>> +    }
>> +
>> +    /* Clear context entry */
>> +    qtest_writeq(ctx->qts, context_entry_addr, 0);
>> +    qtest_writeq(ctx->qts, context_entry_addr + 8, 0);
>> +
>> +    /* Clear root entry */
>> +    qtest_writeq(ctx->qts, root_entry_addr, 0);
>> +    qtest_writeq(ctx->qts, root_entry_addr + 8, 0);
>> +
>> +    /* Invalidate caches using register-based invalidation */
>> +    qvtd_invalidate_context_cache(ctx->qts, ctx->iommu_base);
>> +    qvtd_invalidate_iotlb(ctx->qts, ctx->iommu_base);
>> +}
>> +
>> +bool qvtd_validate_test_result(QVTDTestContext *ctx)
>> +{
>> +    uint32_t expected = qvtd_expected_dma_result(ctx);
>> +    bool passed = (ctx->dma_result == expected);
>> +    bool mem_ok = true;
>> +
>> +    g_test_message("-> Validating result: expected=0x%x actual=0x%x",
>> +                   expected, ctx->dma_result);
>> +
>> +    if (passed && expected == 0) {
>> +        mem_ok = qvtd_validate_dma_memory(ctx);
>> +        g_test_message("-> Memory validation %s at PA=0x%llx",
>> +                       mem_ok ? "passed" : "failed",
>> +                       (unsigned long long)ctx->config.dma_pa);
>> +        passed = mem_ok;
>> +    }
>> +
>> +    return passed;
>> +}
>> +
>> +void qvtd_single_translation(QVTDTestContext *ctx)
>> +{
>> +    uint32_t config_result;
>> +    bool test_passed;
>> +
>> +    /* Configure Intel IOMMU translation */
>> +    config_result = qvtd_setup_and_enable_translation(ctx);
>> +    if (config_result != 0) {
>> +        g_test_message("Configuration failed: mode=%u status=0x%x",
>> +                       ctx->config.trans_mode, config_result);
>> +    }
>> +    g_assert_cmpint(config_result, ==, 0);
>> +
>> +    /* Trigger DMA operation */
>> +    qvtd_trigger_dma(ctx);
>> +
>> +    /* Validate test result */
>> +    test_passed = qvtd_validate_test_result(ctx);
>> +    g_assert_true(test_passed);
>> +
>> +    /* Clean up translation state to prepare for the next test */
>> +    qvtd_cleanup_translation(ctx);
>> +}
>> +
>> +void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
>> +                               QPCIBar bar, uint64_t iommu_base,
>> +                               const QVTDTestConfig *cfg)
>> +{
>> +    /* Initialize test memory */
>> +    qtest_memset(qts, cfg->dma_pa, 0x00, cfg->dma_len);
>> +
>> +    /* Create test context on stack */
>> +    QVTDTestContext ctx = {
>> +        .qts = qts,
>> +        .dev = dev,
>> +        .bar = bar,
>> +        .iommu_base = iommu_base,
>> +        .config = *cfg,
>> +        .trans_status = 0,
>> +        .dma_result = 0,
>> +        .sid = qvtd_calc_sid(dev),
>> +    };
>> +
>> +    /* Execute the test using existing single_translation logic */
>> +    qvtd_single_translation(&ctx);
>> +
>> +    /* Report results */
>> +    g_test_message("--> Test completed: mode=%u domain_id=%u "
>> +                   "status=0x%x result=0x%x",
>> +                   cfg->trans_mode, cfg->domain_id,
>> +                   ctx.trans_status, ctx.dma_result);
>> +}
>> +
>> +void qvtd_translation_batch(const QVTDTestConfig *configs, size_t count,
>> +                            QTestState *qts, QPCIDevice *dev,
>> +                            QPCIBar bar, uint64_t iommu_base)
>> +{
>> +    for (size_t i = 0; i < count; i++) {
>> +        g_test_message("=== Running test %zu/%zu ===", i + 1, count);
>> +        qvtd_run_translation_case(qts, dev, bar, iommu_base, &configs[i]);
>> +    }
>> +}
>> diff --git a/tests/qtest/libqos/qos-intel-iommu.h b/tests/qtest/libqos/qos-intel-iommu.h
>> new file mode 100644
>> index 0000000000..dab5d4df63
>> --- /dev/null
>> +++ b/tests/qtest/libqos/qos-intel-iommu.h
>> @@ -0,0 +1,299 @@
>> +/*
>> + * QOS Intel IOMMU (VT-d) Module
>> + *
>> + * This module provides Intel IOMMU-specific helper functions for libqos tests,
>> + * encapsulating VT-d setup, assertion, and cleanup operations.
>> + *
>> + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef QTEST_LIBQOS_INTEL_IOMMU_H
>> +#define QTEST_LIBQOS_INTEL_IOMMU_H
>> +
>> +#include "hw/misc/iommu-testdev.h"
>> +#include "hw/i386/intel_iommu_internal.h"
>> +
>> +/*
>> + * Intel IOMMU MMIO register base. This is the standard Q35 IOMMU address.
>> + */
>> +#define Q35_IOMMU_BASE            0xfed90000ULL
>> +
>> +/*
>> + * Guest memory layout for IOMMU structures.
>> + * All structures are placed in guest physical memory inside the 512MB RAM.
>> + * Using 256MB mark (0x10000000) as base to ensure all structures fit in RAM.
>> + */
>> +#define QVTD_MEM_BASE             0x10000000ULL
>> +
>> +/* Root Entry Table: 256 entries * 16 bytes = 4KB */
>> +#define QVTD_ROOT_TABLE_BASE      (QVTD_MEM_BASE + 0x00000000)
>> +
>> +/* Context Entry Table: 256 entries * 16 bytes = 4KB per bus */
>> +#define QVTD_CONTEXT_TABLE_BASE   (QVTD_MEM_BASE + 0x00001000)
>> +
>> +/* Page Tables: 4-level hierarchy for 48-bit address translation */
>> +#define QVTD_PT_L4_BASE           (QVTD_MEM_BASE + 0x00010000)  /* PML4 */
>> +#define QVTD_PT_L3_BASE           (QVTD_MEM_BASE + 0x00011000)  /* PDPT */
>> +#define QVTD_PT_L2_BASE           (QVTD_MEM_BASE + 0x00012000)  /* PD */
>> +#define QVTD_PT_L1_BASE           (QVTD_MEM_BASE + 0x00013000)  /* PT */
>> +
>> +/* Invalidation Queue: 256 entries * 16 bytes = 4KB */
>> +#define QVTD_INV_QUEUE_BASE       (QVTD_MEM_BASE + 0x00020000)
>> +
>> +/* Test IOVA and target physical address */
>> +#define QVTD_TEST_IOVA            0x0000008080604000ULL
>> +#define QVTD_TEST_PA              (QVTD_MEM_BASE + 0x00100000)
>> +
>> +/*
>> + * Translation modes supported by Intel IOMMU
>> + */
>> +typedef enum QVTDTransMode {
>> +    QVTD_TM_LEGACY_PT,      /* Legacy pass-through mode */
>> +    QVTD_TM_LEGACY_TRANS,   /* Legacy translated mode (4-level paging) */
>> +} QVTDTransMode;
>> +
>> +/*
>> + * Test configuration structure
>> + */
>> +typedef struct QVTDTestConfig {
>> +    QVTDTransMode trans_mode;     /* Translation mode */
>> +    uint64_t dma_iova;            /* DMA IOVA address for testing */
>> +    uint64_t dma_pa;              /* Target physical address */
>> +    uint32_t dma_len;             /* DMA length for testing */
>> +    uint32_t expected_result;     /* Expected DMA result */
>> +    uint16_t domain_id;           /* Domain ID for this test */
>> +} QVTDTestConfig;
>> +
>> +/*
>> + * Test context structure
>> + */
>> +typedef struct QVTDTestContext {
>> +    QTestState *qts;              /* QTest state handle */
>> +    QPCIDevice *dev;              /* PCI device handle */
>> +    QPCIBar bar;                  /* PCI BAR for MMIO access */
>> +    QVTDTestConfig config;        /* Test configuration */
>> +    uint64_t iommu_base;          /* Intel IOMMU base address */
>> +    uint32_t trans_status;        /* Translation configuration status */
>> +    uint32_t dma_result;          /* DMA operation result */
>> +    uint16_t sid;                 /* Source ID (bus:devfn) */
>> +} QVTDTestContext;
>> +
>> +/*
>> + * qvtd_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 VT-d structures (root entry, context entry, page tables)
>> + * 2. Programs IOMMU registers
>> + * 3. Invalidates caches
>> + * 4. Enables translation
>> + */
>> +uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx);
>> +
>> +/*
>> + * qvtd_build_translation - Build Intel IOMMU translation structures
>> + *
>> + * @qts: QTest state handle
>> + * @mode: Translation mode (pass-through or translated)
>> + * @sid: Source ID (bus:devfn)
>> + * @domain_id: Domain ID
>> + * @iova: IOVA address for logging purposes
>> + * @pa: Physical address backed by the mapping
>> + *
>> + * Returns: Build status (0 = success, non-zero = error)
>> + *
>> + * Constructs all necessary VT-d translation structures in guest memory:
>> + * - Root Entry for the device's bus
>> + * - Context Entry for the device
>> + * - Complete 4-level page table hierarchy (if translated mode)
>> + */
>> +uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
>> +                                uint16_t sid, uint16_t domain_id,
>> +                                uint64_t iova, uint64_t pa);
>> +
>> +/*
>> + * qvtd_program_regs - Program Intel IOMMU registers
>> + *
>> + * @qts: QTest state handle
>> + * @iommu_base: IOMMU base address
>> + *
>> + * Programs IOMMU registers with the following sequence:
>> + * 1. Disable translation
>> + * 2. Program root table address
>> + * 3. Set root table pointer
>> + * 4. Unmask fault event interrupt
>> + *
>> + * Note: This function does NOT clear memory regions or enable translation.
>> + * Memory clearing should be done once during test setup via qvtd_clear_memory_regions().
>> + * Translation is enabled separately after building all structures.
>> + */
>> +void qvtd_program_regs(QTestState *qts, uint64_t iommu_base);
>> +
>> +/*
>> + * qvtd_trigger_dma - Trigger DMA operation via iommu-testdev
>> + *
>> + * @ctx: Test context
>> + *
>> + * Returns: DMA result code
>> + *
>> + * Programs iommu-testdev BAR0 registers to trigger a DMA operation:
>> + * 1. Write IOVA address (GVA_LO/HI)
>> + * 2. Write DMA length
>> + * 3. Arm DMA (write to DBELL)
>> + * 4. Trigger DMA (read from TRIGGERING)
>> + * 5. Poll for completion (read DMA_RESULT)
>> + */
>> +uint32_t qvtd_trigger_dma(QVTDTestContext *ctx);
>> +
>> +/*
>> + * qvtd_cleanup_translation - Clean up translation configuration
>> + *
>> + * @ctx: Test context containing configuration and device handles
>> + *
>> + * Clears all translation structures and invalidates IOMMU caches.
>> + */
>> +void qvtd_cleanup_translation(QVTDTestContext *ctx);
>> +
>> +/*
>> + * qvtd_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 qvtd_validate_test_result(QVTDTestContext *ctx);
>> +
>> +/*
>> + * qvtd_setup_translation_tables - Setup complete VT-d page table hierarchy
>> + *
>> + * @qts: QTest state handle
>> + * @iova: Input Virtual Address to translate
>> + * @pa: Physical address to map to
>> + * @mode: Translation mode
>> + *
>> + * This function builds the complete 4-level page table structure for translating
>> + * the given IOVA to PA through Intel VT-d. The structure is:
>> + * - PML4 (Level 4): IOVA bits [47:39]
>> + * - PDPT (Level 3): IOVA bits [38:30]
>> + * - PD (Level 2): IOVA bits [29:21]
>> + * - PT (Level 1): IOVA bits [20:12]
>> + * - Page offset: IOVA bits [11:0]
>> + *
>> + * The function writes all necessary Page Table Entries (PTEs) to guest
>> + * memory using qtest_writeq(), setting up the complete translation path
>> + * that the VT-d hardware will traverse during DMA operations.
>> + */
>> +void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
>> +                                   uint64_t pa, QVTDTransMode mode);
>> +
>> +/*
>> + * qvtd_expected_dma_result - Calculate expected DMA result
>> + *
>> + * @ctx: Test context containing configuration
>> + *
>> + * Returns: Expected DMA result code
>> + *
>> + * This function acts as a test oracle, calculating the expected DMA result
>> + * based on the test configuration. It centralizes validation logic for
>> + * different scenarios (pass-through vs. translated, fault conditions).
>> + */
>> +uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx);
>> +
>> +/*
>> + * qvtd_build_dma_attrs - Build DMA attributes for an Intel VT-d DMA request
>> + *
>> + * @bdf: PCI requester ID encoded as Bus[15:8]/Device[7:3]/Function[2:0]
>> + * @pasid: PASID tag (0 for legacy requests, non-zero for scalable mode)
>> + *
>> + * Returns: Value to program into iommu-testdev's DMA_ATTRS register
>> + *
>> + * The iommu-testdev attribute register mirrors Intel VT-d request metadata:
>> + *   - bits[2:0] keep the generic iommu-testdev fields (secure + space)
>> + *   - bit[4] selects legacy (0) vs. scalable (1) transactions
>> + *   - bits[23:8] carry the requester ID as defined in the VT-d spec
>> + *   - bits[31:24] carry the PASID (limited to 8 bits in QEMU, matching
>> + *     the MemTxAttrs::pid width and ECAP.PSS advertisement)
>> + *
>> + * The helper validates the BDF layout (bus <= 255, device <= 31, function <= 7)
>> + * and makes sure PASID fits in the supported width before returning the value.
>> + */
>> +uint32_t qvtd_build_dma_attrs(uint16_t bdf, uint32_t pasid);
>> +
>> +/*
>> + * High-level test execution functions
>> + */
>> +
>> +/*
>> + * qvtd_single_translation - Execute single translation test
>> + *
>> + * @ctx: Test context
>> + *
>> + * Performs a complete test cycle:
>> + * 1. Setup translation structures
>> + * 2. Trigger DMA operation
>> + * 3. Validate results
>> + * 4. Cleanup
>> + */
>> +void qvtd_single_translation(QVTDTestContext *ctx);
>> +
>> +/*
>> + * qvtd_run_translation_case - Execute a single Intel VT-d translation test
>> + *
>> + * @qts: QTestState for the test
>> + * @dev: PCI device (iommu-testdev)
>> + * @bar: BAR0 of iommu-testdev
>> + * @iommu_base: Base address of Intel IOMMU MMIO registers
>> + * @cfg: Test configuration
>> + *
>> + * High-level wrapper that creates test context internally and executes
>> + * a single translation test case. This provides a simpler API compared
>> + * to qvtd_single_translation() which requires manual context initialization.
>> + *
>> + * This function is analogous to qriommu_run_translation_case() in the
>> + * RISC-V IOMMU test framework, providing a consistent API across different
>> + * IOMMU architectures.
>> + *
>> + * Example usage:
>> + *   QVTDTestConfig cfg = {
>> + *       .trans_mode = QVTD_TM_LEGACY_PT,
>> + *       .domain_id = 1,
>> + *       .dma_iova = 0x40100000,
>> + *       .dma_pa = 0x40100000,
>> + *       .dma_len = 4,
>> + *   };
>> + *   qvtd_run_translation_case(qts, dev, bar, iommu_base, &cfg);
>> + */
>> +void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
>> +                               QPCIBar bar, uint64_t iommu_base,
>> +                               const QVTDTestConfig *cfg);
>> +
>> +/*
>> + * qvtd_translation_batch - Execute batch of translation tests
>> + *
>> + * @configs: Array of test configurations
>> + * @count: Number of configurations
>> + * @qts: QTest state handle
>> + * @dev: PCI device handle
>> + * @bar: PCI BAR for MMIO access
>> + * @iommu_base: IOMMU base address
>> + *
>> + * Executes multiple translation tests in sequence, each with its own
>> + * configuration. Useful for testing different translation modes and
>> + * scenarios in a single test run.
>> + *
>> + * This function now uses qvtd_run_translation_case() internally to
>> + * reduce code duplication.
>> + */
>> +void qvtd_translation_batch(const QVTDTestConfig *configs, size_t count,
>> +                            QTestState *qts, QPCIDevice *dev,
>> +                            QPCIBar bar, uint64_t iommu_base);
>> +
>> +#endif /* QTEST_LIBQOS_INTEL_IOMMU_H */
>> -- 
>> 2.39.5
>>



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev
  2026-02-04 13:14 ` [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev Tao Tang
@ 2026-02-05  9:35   ` Fengyuan
  0 siblings, 0 replies; 9+ messages in thread
From: Fengyuan @ 2026-02-05  9:35 UTC (permalink / raw)
  To: Tao Tang, Fabiano Rosas, Laurent Vivier, Paolo Bonzini
  Cc: Chao Liu, qemu-devel

Hi Tao,

Thanks for the attention!

On 2/4/2026 9:14 PM, Tao Tang wrote:
> Hi Fengyuan,
> 
> On 2026/2/4 11:06, Fengyuan Yu wrote:
>> Hi,
>>
>> This patch series adds a bare-metal qtest for the Intel IOMMU (VT-d) using
>> the iommu-testdev framework. The test exercises address translation paths
>> without requiring a full guest OS boot.
>>
>> Motivation
>> ----------
>>
>> The Intel IOMMU implementation in QEMU supports various translation modes
>> including pass-through and translated (4-level paging) modes. Currently,
>> comprehensive testing of these translation paths requires booting a full
>> guest OS with appropriate drivers, which is time-consuming and makes
>> regression testing difficult.
>>
>> This new test fills that gap by using iommu-testdev to trigger DMA
>> transactions and validate the IOMMU's translation logic directly.
>>
>> Test Coverage
>> -------------
>>
>> The new test provides:
>> - Legacy pass-through mode (identity mapping)
>> - Legacy translated mode with 4-level page table walks
>> - Root Entry Table and Context Entry Table configuration
>> - Complete 48-bit address space translation
>> - End-to-end DMA verification with memory validation
>>
>> Testing
>> -------
>>
>> QTEST_QEMU_BINARY=./build/qemu-system-x86_64 \
>>    ./build/tests/qtest/iommu-intel-test --tap -k
>>
>> Thanks,
>> Fengyuan
> 
> 
> Thanks for working on VT-d qtests. As a first-time patch contributor, you’ve 
> already done a great job with good cover letter and commit messages to 
> accurately summarize your work.
> 
> 
> It seems that your code cannot be applied to the latest master branch. And I 
> also noticed multiple lines >80 columns (some > 90). QEMU style says try to keep 
> lines to 80 columns, only going a bit over when wrapping would harm readability 
> but never > 90 columns.
> 
> ../tests/qtest/libqos/qos-intel-iommu.c: In function ‘qvtd_build_root_entry’:
> ../tests/qtest/libqos/qos-intel-iommu.c:168:31: error: 
> ‘VTD_CONTEXT_ENTRY_SLPTPTR’ undeclared (first use in this function); did you 
> mean ‘VTD_CONTEXT_ENTRY_SSPTPTR’?
>    168 |     lo = (context_table_ptr & VTD_CONTEXT_ENTRY_SLPTPTR) | 
> VTD_CONTEXT_ENTRY_P;
>        |                               ^~~~~~~~~~~~~~~~~~~~~~~~~
>        |                               VTD_CONTEXT_ENTRY_SSPTPTR
> ../tests/qtest/libqos/qos-intel-iommu.c:168:31: note: each undeclared identifier 
> is reported only once for each function it appears in
> ../tests/qtest/libqos/qos-intel-iommu.c: In function ‘qvtd_build_context_entry’:
> ../tests/qtest/libqos/qos-intel-iommu.c:190:25: error: 
> ‘VTD_CONTEXT_ENTRY_SLPTPTR’ undeclared (first use in this function); did you 
> mean ‘VTD_CONTEXT_ENTRY_SSPTPTR’?
>    190 |              (slptptr & VTD_CONTEXT_ENTRY_SLPTPTR);
>        |                         ^~~~~~~~~~~~~~~~~~~~~~~~~
>        |                         VTD_CONTEXT_ENTRY_SSPTPTR
> ../tests/qtest/libqos/qos-intel-iommu.c: In function 
> ‘qvtd_setup_translation_tables’:
> ../tests/qtest/libqos/qos-intel-iommu.c:239:36: error: ‘VTD_SL_R’ undeclared 
> (first use in this function); did you mean ‘VTD_SS_R’?
>    239 |     pml4_entry = QVTD_PT_L3_BASE | VTD_SL_R | VTD_SL_W;
>        |                                    ^~~~~~~~
>        |                                    VTD_SS_R
> ../tests/qtest/libqos/qos-intel-iommu.c:239:47: error: ‘VTD_SL_W’ undeclared 
> (first use in this function); did you mean ‘VTD_SS_W’?
>    239 |     pml4_entry = QVTD_PT_L3_BASE | VTD_SL_R | VTD_SL_W;
>        |                                               ^~~~~~~~
>        |                                               VTD_SS_W
> [92/2328] Compiling C object libblock.a.p/block.c.o
> 
> 
> 
> Besides when submitting patches it’s a good idea to read `docs/devel/submitting- 
> a-patch.rst`. It may be a bit long, but making sure the code builds cleanly and 
> running scripts/checkpatch.pl for style checks beforehand can help avoid many 
> basic issues.
> 
> 
Thank you for your reply, and I sincerely apologize for my oversight.
I have identified the issue: since I started development on iommu-testdev 
earlier, some Intel IOMMU macro definitions changed during my development, 
causing them to be undefined.
I will include comprehensive CI/CD checks and patch verification in v2.

> Finally I think CC the experts on x86 and VT-d emulation might be a more 
> efficient way to review this sereis patch.
> 
> M: Paolo Bonzini <pbonzini@redhat.com>
> R: Zhao Liu <zhao1.liu@intel.com>
> 
> M: Michael S. Tsirkin <mst@redhat.com>
> R: Jason Wang <jasowang@redhat.com>
> R: Yi Liu <yi.l.liu@intel.com>
> R: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com>
> 
> 
Okay, I will cc these reviewers in v2 submission.

Thanks,

Fengyuan

> Anyway I'm looking forward to your v2 patches.
> 
> Best regards,
> 
> Tao
> 



^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-02-05  9:36 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-04  3:06 [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev Fengyuan Yu
2026-02-04  3:06 ` [PATCH RFC v1 1/2] tests/qtest/libqos: Add Intel IOMMU helper library Fengyuan Yu
2026-02-04  7:31   ` Chao Liu
2026-02-05  9:20     ` Fengyuan
2026-02-04  3:06 ` [PATCH RFC v1 2/2] tests/qtest: Add Intel IOMMU bare-metal test Fengyuan Yu
2026-02-04  7:43   ` Chao Liu
2026-02-05  9:18     ` Fengyuan
2026-02-04 13:14 ` [PATCH RFC v1 0/2] tests/qtest: Add Intel IOMMU bare-metal test using iommu-testdev Tao Tang
2026-02-05  9:35   ` Fengyuan

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.