[RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev

4 views
Skip to first unread message

Chao Liu

unread,
Jan 28, 2026, 7:09:59 AM (11 days ago) Jan 28
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
Hi,

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

Motivation
----------

The existing RISC-V IOMMU qtest (riscv-iommu-test.c) focuses on PCI device
enumeration and register-level validation:
- PCI configuration space verification (vendor/device ID)
- Register reset value checks
- Queue initialization procedures (CQ/FQ/PQ)

However, it does not test the actual address translation functionality.
This new test fills that gap by using iommu-testdev to trigger DMA
transactions and validate the IOMMU's translation logic.

Comparison with Existing Test
-----------------------------

| Feature | riscv-iommu-test.c | iommu-riscv-test.c (new) |
|-----------------------|--------------------|--------------------------|
| PCI config | Yes | No |
| Register reset | Yes | No |
| Queue init | Yes | Yes (via helper) |
| Bare translation | No | Yes |
| S-stage (SV39) | No | Yes |
| G-stage (SV39x4) | No | Yes |
| Nested translation | No | Yes |
| DMA verification | No | Yes |
| Uses iommu-testdev | No | Yes |

The new test provides:
- Device context (DC) configuration and validation
- SV39 page table walks for S-stage translation
- SV39x4 page table walks for G-stage translation
- Nested translation combining both stages
- FCTL register constraint validation
- End-to-end DMA verification

Note: The current implementation only supports SV39/SV39x4. Support for
SV48/SV48x4/SV57/SV57x4 can be added in future patches.

Testing
-------

QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \
./build/tests/qtest/iommu-riscv-test --tap -k

Question for Maintainers
------------------------

The existing riscv-iommu-test.c and the new iommu-riscv-test.c serve
complementary purposes. Would it be beneficial to merge these two tests
into a single source file for easier maintenance? This would consolidate
all RISC-V IOMMU testing in one place while preserving both the
register-level and translation-level test coverage.

Thanks,
Chao

Chao Liu (2):
tests/qtest/libqos: Add RISC-V IOMMU helper library
tests/qtest: Add RISC-V IOMMU bare-metal test

MAINTAINERS | 2 +
tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++
tests/qtest/libqos/meson.build | 2 +-
tests/qtest/libqos/qos-riscv-iommu.c | 400 +++++++++++++++++++++++++++
tests/qtest/libqos/qos-riscv-iommu.h | 172 ++++++++++++
tests/qtest/meson.build | 5 +-
6 files changed, 858 insertions(+), 2 deletions(-)
create mode 100644 tests/qtest/iommu-riscv-test.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h

--
2.52.0

Chao Liu

unread,
Jan 28, 2026, 7:10:02 AM (11 days ago) Jan 28
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
Introduce a libqos helper module for RISC-V IOMMU testing with
iommu-testdev. The helper provides routines to:

- Build device contexts (DC) and 3-level page tables for SV39/SV39x4
- Program command queue (CQ), fault queue (FQ), and DDTP registers
following the RISC-V IOMMU specification
- Execute DMA translations and verify results

The current implementation supports SV39 for S-stage and SV39x4 for
G-stage translation. Support for SV48/SV48x4/SV57/SV57x4 can be added
in future patches.

Signed-off-by: Chao Liu <chao.li...@gmail.com>
---
MAINTAINERS | 1 +
tests/qtest/libqos/meson.build | 2 +-
tests/qtest/libqos/qos-riscv-iommu.c | 400 +++++++++++++++++++++++++++
tests/qtest/libqos/qos-riscv-iommu.h | 172 ++++++++++++
4 files changed, 574 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h

diff --git a/MAINTAINERS b/MAINTAINERS
index dc31be033e..894e05bd2c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3583,6 +3583,7 @@ M: Tao Tang <tangt...@phytium.com.cn>
S: Maintained
F: tests/qtest/libqos/qos-iommu*
F: tests/qtest/libqos/qos-smmuv3*
+F: tests/qtest/libqos/qos-riscv-iommu*

Device Fuzzing
M: Alexander Bulekov <alx...@bu.edu>
diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
index b4daec808f..4a69acad0d 100644
--- a/tests/qtest/libqos/meson.build
+++ b/tests/qtest/libqos/meson.build
@@ -71,7 +71,7 @@ if have_virtfs
endif

if config_all_devices.has_key('CONFIG_RISCV_IOMMU')
- libqos_srcs += files('riscv-iommu.c')
+ libqos_srcs += files('riscv-iommu.c', 'qos-riscv-iommu.c')
endif
if config_all_devices.has_key('CONFIG_TPCI200')
libqos_srcs += files('tpci200.c')
diff --git a/tests/qtest/libqos/qos-riscv-iommu.c b/tests/qtest/libqos/qos-riscv-iommu.c
new file mode 100644
index 0000000000..34ed3df84a
--- /dev/null
+++ b/tests/qtest/libqos/qos-riscv-iommu.c
@@ -0,0 +1,400 @@
+/*
+ * QOS RISC-V IOMMU Module
+ *
+ * This module provides RISC-V IOMMU-specific helper functions for libqos tests,
+ * encapsulating RISC-V IOMMU setup, and assertions.
+ *
+ * Copyright (c) 2026 Chao Liu <chao.li...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "hw/riscv/riscv-iommu-bits.h"
+#include "tests/qtest/libqos/pci.h"
+#include "qos-iommu-testdev.h"
+#include "qos-riscv-iommu.h"
+
+/* Apply space offset to address */
+static inline uint64_t qriommu_apply_space_offs(uint64_t address)
+{
+ return address + QRIOMMU_SPACE_OFFS;
+}
+
+static uint64_t qriommu_encode_pte(uint64_t pa, uint64_t attrs)
+{
+ return ((pa >> 12) << 10) | attrs;
+}
+
+static void qriommu_wait_for_queue_active(QTestState *qts, uint64_t iommu_base,
+ uint32_t queue_csr, uint32_t on_bit)
+{
+ guint64 timeout_us = 2 * 1000 * 1000;
+ gint64 start_time = g_get_monotonic_time();
+ uint32_t reg;
+
+ for (;;) {
+ qtest_clock_step(qts, 100);
+
+ reg = qtest_readl(qts, iommu_base + queue_csr);
+ if (reg & on_bit) {
+ return;
+ }
+ g_assert(g_get_monotonic_time() - start_time <= timeout_us);
+ }
+}
+
+uint32_t qriommu_expected_dma_result(QRIOMMUTestContext *ctx)
+{
+ return ctx->config.expected_result;
+}
+
+uint32_t qriommu_build_dma_attrs(void)
+{
+ /* RISC-V IOMMU uses standard AXI attributes */
+ return 0;
+}
+
+uint32_t qriommu_setup_and_enable_translation(QRIOMMUTestContext *ctx)
+{
+ uint32_t build_result;
+
+ /* Build page tables and RISC-V IOMMU structures first */
+ build_result = qriommu_build_translation(
+ ctx->qts, ctx->config.trans_mode,
+ ctx->device_id);
+ if (build_result != 0) {
+ g_test_message("Build failed: mode=%u device_id=%u status=0x%x",
+ ctx->config.trans_mode, ctx->device_id, build_result);
+ ctx->trans_status = build_result;
+ return ctx->trans_status;
+ }
+
+ /* Program RISC-V IOMMU registers */
+ qriommu_program_regs(ctx->qts, ctx->iommu_base);
+
+ ctx->trans_status = 0;
+ return ctx->trans_status;
+}
+
+static bool qriommu_validate_test_result(QRIOMMUTestContext *ctx)
+{
+ uint32_t expected = qriommu_expected_dma_result(ctx);
+ g_test_message("-> Validating result: expected=0x%x actual=0x%x",
+ expected, ctx->dma_result);
+ return (ctx->dma_result == expected);
+}
+
+static uint32_t qriommu_single_translation_setup(void *opaque)
+{
+ return qriommu_setup_and_enable_translation(opaque);
+}
+
+static uint32_t qriommu_single_translation_attrs(void *opaque)
+{
+ return qriommu_build_dma_attrs();
+}
+
+static bool qriommu_single_translation_validate(void *opaque)
+{
+ return qriommu_validate_test_result(opaque);
+}
+
+static void qriommu_single_translation_report(void *opaque,
+ uint32_t dma_result)
+{
+ QRIOMMUTestContext *ctx = opaque;
+
+ if (dma_result != 0) {
+ g_test_message("DMA failed: mode=%u result=0x%x",
+ ctx->config.trans_mode, dma_result);
+ } else {
+ g_test_message("-> DMA succeeded: mode=%u",
+ ctx->config.trans_mode);
+ }
+}
+
+void qriommu_run_translation_case(QTestState *qts, QPCIDevice *dev,
+ QPCIBar bar, uint64_t iommu_base,
+ const QRIOMMUTestConfig *cfg)
+{
+ QRIOMMUTestContext ctx = {
+ .qts = qts,
+ .dev = dev,
+ .bar = bar,
+ .iommu_base = iommu_base,
+ .config = *cfg,
+ .device_id = dev->devfn,
+ };
+
+ QOSIOMMUTestdevDmaCfg dma = {
+ .dev = dev,
+ .bar = bar,
+ .iova = QRIOMMU_IOVA,
+ .gpa = ctx.config.dma_gpa,
+ .len = ctx.config.dma_len,
+ };
+
+ qtest_memset(qts, cfg->dma_gpa, 0x00, cfg->dma_len);
+ qos_iommu_testdev_single_translation(&dma, &ctx,
+ qriommu_single_translation_setup,
+ qriommu_single_translation_attrs,
+ qriommu_single_translation_validate,
+ qriommu_single_translation_report,
+ &ctx.dma_result);
+
+ if (ctx.dma_result == 0 && ctx.config.expected_result == 0) {
+ g_autofree uint8_t *buf = NULL;
+
+ buf = g_malloc(ctx.config.dma_len);
+ qtest_memread(ctx.qts, ctx.config.dma_gpa, buf, ctx.config.dma_len);
+
+ for (int i = 0; i < ctx.config.dma_len; i++) {
+ uint8_t expected;
+
+ expected = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
+ g_assert_cmpuint(buf[i], ==, expected);
+ }
+ }
+}
+
+static uint32_t qriommu_get_table_index(uint64_t addr, int level)
+{
+ /* SV39: 39-bit virtual address, 3-level page table */
+ switch (level) {
+ case 0:
+ return (addr >> 30) & 0x1ff; /* L0: bits [38:30] */
+ case 1:
+ return (addr >> 21) & 0x1ff; /* L1: bits [29:21] */
+ case 2:
+ return (addr >> 12) & 0x1ff; /* L2: bits [20:12] */
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static uint64_t qriommu_get_table_addr(uint64_t base, int level, uint64_t iova)
+{
+ uint32_t index = qriommu_get_table_index(iova, level);
+ return (base & QRIOMMU_PTE_PPN_MASK) + (index * 8);
+}
+
+static void qriommu_map_leaf(QTestState *qts, uint64_t root_pa,
+ uint64_t l0_pa, uint64_t l1_pa,
+ uint64_t l0_pte_val, uint64_t l1_pte_val,
+ uint64_t va, uint64_t pa, uint64_t leaf_attrs)
+{
+ uint64_t l0_addr = qriommu_get_table_addr(root_pa, 0, va);
+ uint64_t l1_addr = qriommu_get_table_addr(l0_pa, 1, va);
+ uint64_t l2_addr = qriommu_get_table_addr(l1_pa, 2, va);
+
+ qtest_writeq(qts, l0_addr, l0_pte_val);
+ qtest_writeq(qts, l1_addr, l1_pte_val);
+ qtest_writeq(qts, l2_addr, qriommu_encode_pte(pa, leaf_attrs));
+}
+
+static uint64_t qriommu_get_pte_attrs(QRIOMMUTransMode mode, bool is_leaf)
+{
+ if (!is_leaf) {
+ return QRIOMMU_NON_LEAF_PTE_MASK;
+ }
+
+ /* For leaf PTE, set RWX permissions */
+ return QRIOMMU_LEAF_PTE_RW_MASK;
+}
+
+void qriommu_setup_translation_tables(QTestState *qts,
+ uint64_t iova,
+ QRIOMMUTransMode mode)
+{
+ uint64_t s_root = 0, s_l0_pte_val = 0, s_l1_pte_val = 0;
+ uint64_t s_l0_addr = 0, s_l1_addr = 0, s_l2_addr = 0, s_l2_pte_val = 0;
+ uint64_t s_l0_pa = 0, s_l1_pa = 0;
+ uint64_t s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+ uint64_t s_l0_pa_real = 0, s_l1_pa_real = 0;
+ uint64_t s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+ uint64_t non_leaf_attrs = qriommu_get_pte_attrs(mode, false);
+ uint64_t leaf_attrs = qriommu_get_pte_attrs(mode, true);
+
+ if (mode != QRIOMMU_TM_G_STAGE_ONLY) {
+ /* Setup S-stage 3-level page tables (SV39) */
+ s_l0_pa = qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL);
+ s_l1_pa = qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL);
+ s_root = qriommu_apply_space_offs(
+ QRIOMMU_IOHGATP & QRIOMMU_PTE_PPN_MASK);
+ s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+
+ s_l0_pa_real = s_l0_pa;
+ s_l1_pa_real = s_l1_pa;
+ s_l2_pa_real = s_l2_pa;
+
+ if (mode == QRIOMMU_TM_NESTED) {
+ s_l0_pa = QRIOMMU_L0_PTE_VAL;
+ s_l1_pa = QRIOMMU_L1_PTE_VAL;
+ s_l2_pa = QRIOMMU_L2_PTE_VAL;
+
+ s_l0_pa_real = qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL);
+ s_l1_pa_real = qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL);
+ s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+ }
+
+ s_l0_pte_val = qriommu_encode_pte(s_l0_pa, non_leaf_attrs);
+ s_l1_pte_val = qriommu_encode_pte(s_l1_pa, non_leaf_attrs);
+
+ s_l0_addr = qriommu_get_table_addr(s_root, 0, iova);
+ qtest_writeq(qts, s_l0_addr, s_l0_pte_val);
+
+ s_l1_addr = qriommu_get_table_addr(s_l0_pa_real, 1, iova);
+ qtest_writeq(qts, s_l1_addr, s_l1_pte_val);
+
+ s_l2_addr = qriommu_get_table_addr(s_l1_pa_real, 2, iova);
+ s_l2_pte_val = qriommu_encode_pte(s_l2_pa, leaf_attrs);
+ qtest_writeq(qts, s_l2_addr, s_l2_pte_val);
+ }
+
+ if (mode == QRIOMMU_TM_G_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) {
+ uint64_t g_root = qriommu_apply_space_offs(
+ QRIOMMU_G_IOHGATP & QRIOMMU_PTE_PPN_MASK);
+ uint64_t g_l0_pa = qriommu_apply_space_offs(QRIOMMU_G_L0_PTE_VAL);
+ uint64_t g_l1_pa = qriommu_apply_space_offs(QRIOMMU_G_L1_PTE_VAL);
+ uint64_t g_l0_pte_val = qriommu_encode_pte(g_l0_pa, non_leaf_attrs);
+ uint64_t g_l1_pte_val = qriommu_encode_pte(g_l1_pa, non_leaf_attrs);
+
+ if (mode == QRIOMMU_TM_G_STAGE_ONLY) {
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ iova, s_l2_pa_real, leaf_attrs);
+ } else {
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ QRIOMMU_IOHGATP, s_root, leaf_attrs);
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ QRIOMMU_L0_PTE_VAL, s_l0_pa_real, leaf_attrs);
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ QRIOMMU_L1_PTE_VAL, s_l1_pa_real, leaf_attrs);
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ QRIOMMU_L2_PTE_VAL, s_l2_pa_real, leaf_attrs);
+ }
+ }
+}
+
+uint32_t qriommu_build_translation(QTestState *qts, QRIOMMUTransMode mode,
+ uint32_t device_id)
+{
+ uint64_t dc_addr, dc_addr_real;
+ struct riscv_iommu_dc dc;
+ uint64_t iohgatp;
+
+ qtest_memset(qts, qriommu_apply_space_offs(QRIOMMU_DDT_BASE), 0, 0x1000);
+
+ dc_addr = device_id * sizeof(struct riscv_iommu_dc) + QRIOMMU_DC_BASE;
+ dc_addr_real = qriommu_apply_space_offs(dc_addr);
+
+ /* Build Device Context (DC) */
+ memset(&dc, 0, sizeof(dc));
+
+ switch (mode) {
+ case QRIOMMU_TM_BARE:
+ /* Pass-through mode: tc.V=1, no FSC/IOHGATP */
+ dc.tc = RISCV_IOMMU_DC_TC_V;
+ break;
+
+ case QRIOMMU_TM_S_STAGE_ONLY:
+ /* S-stage only: tc.V=1, set FSC */
+ dc.tc = RISCV_IOMMU_DC_TC_V;
+ iohgatp = qriommu_apply_space_offs(QRIOMMU_IOHGATP);
+ /* FSC mode: SV39 (mode=8) */
+ dc.fsc = (iohgatp >> 12) | (8ull << 60);
+ break;
+
+ case QRIOMMU_TM_G_STAGE_ONLY:
+ /* G-stage only: tc.V=1, set IOHGATP */
+ dc.tc = RISCV_IOMMU_DC_TC_V;
+ iohgatp = qriommu_apply_space_offs(QRIOMMU_G_IOHGATP);
+ /* IOHGATP mode: SV39x4 (mode=8) */
+ dc.iohgatp = (iohgatp >> 12) | (8ull << 60);
+ break;
+
+ case QRIOMMU_TM_NESTED:
+ /* Nested: tc.V=1, set both FSC and IOHGATP */
+ dc.tc = RISCV_IOMMU_DC_TC_V;
+ /* FSC mode: SV39 (mode=8) */
+ dc.fsc = (QRIOMMU_IOHGATP >> 12) | (8ull << 60);
+ /* IOHGATP mode: SV39x4 (mode=8) */
+ iohgatp = qriommu_apply_space_offs(QRIOMMU_G_IOHGATP);
+ dc.iohgatp = (iohgatp >> 12) | (8ull << 60);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ /* Write DC to memory */
+ qtest_writeq(qts, dc_addr_real + 0, dc.tc);
+ qtest_writeq(qts, dc_addr_real + 8, dc.iohgatp);
+ qtest_writeq(qts, dc_addr_real + 16, dc.ta);
+ qtest_writeq(qts, dc_addr_real + 24, dc.fsc);
+ qtest_writeq(qts, dc_addr_real + 32, dc.msiptp);
+ qtest_writeq(qts, dc_addr_real + 40, dc.msi_addr_mask);
+ qtest_writeq(qts, dc_addr_real + 48, dc.msi_addr_pattern);
+ qtest_writeq(qts, dc_addr_real + 56, dc._reserved);
+
+ /* Setup translation tables if not in BARE mode */
+ if (mode != QRIOMMU_TM_BARE) {
+ qriommu_setup_translation_tables(qts, QRIOMMU_IOVA, mode);
+ }
+
+ return 0;
+}
+
+void qriommu_program_regs(QTestState *qts, uint64_t iommu_base)
+{
+ uint64_t ddtp, cqb, fqb;
+ uint64_t cq_base, fq_base;
+ uint64_t cq_align, fq_align;
+ uint32_t cq_entries = QRIOMMU_QUEUE_ENTRIES;
+ uint32_t fq_entries = QRIOMMU_QUEUE_ENTRIES;
+ uint32_t cq_log2sz = ctz32(cq_entries) - 1;
+ uint32_t fq_log2sz = ctz32(fq_entries) - 1;
+
+ cq_base = qriommu_apply_space_offs(QRIOMMU_CQ_BASE_ADDR);
+ fq_base = qriommu_apply_space_offs(QRIOMMU_FQ_BASE_ADDR);
+
+ cq_align = MAX(0x1000ull, (uint64_t)cq_entries * QRIOMMU_CQ_ENTRY_SIZE);
+ fq_align = MAX(0x1000ull, (uint64_t)fq_entries * QRIOMMU_FQ_ENTRY_SIZE);
+ g_assert((cq_base & (cq_align - 1)) == 0);
+ g_assert((fq_base & (fq_align - 1)) == 0);
+
+ /* Setup Command Queue */
+ cqb = (cq_base >> 12) << 10 | cq_log2sz;
+ qtest_writeq(qts, iommu_base + RISCV_IOMMU_REG_CQB, cqb);
+ qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_CQH, 0);
+ qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_CQT, 0);
+ qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_CQCSR,
+ RISCV_IOMMU_CQCSR_CQEN);
+ qriommu_wait_for_queue_active(qts, iommu_base, RISCV_IOMMU_REG_CQCSR,
+ RISCV_IOMMU_CQCSR_CQON);
+
+ /* Setup Fault Queue */
+ fqb = (fq_base >> 12) << 10 | fq_log2sz;
+ qtest_writeq(qts, iommu_base + RISCV_IOMMU_REG_FQB, fqb);
+ qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_FQH, 0);
+ qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_FQT, 0);
+ qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_FQCSR,
+ RISCV_IOMMU_FQCSR_FQEN);
+ qriommu_wait_for_queue_active(qts, iommu_base, RISCV_IOMMU_REG_FQCSR,
+ RISCV_IOMMU_FQCSR_FQON);
+
+ /* Set Device Directory Table Pointer (DDTP) */
+ ddtp = qriommu_apply_space_offs(QRIOMMU_DDT_BASE);
+ g_assert((ddtp & 0xfff) == 0);
+ ddtp = ((ddtp >> 12) << 10) | RISCV_IOMMU_DDTP_MODE_1LVL;
+ qtest_writeq(qts, iommu_base + RISCV_IOMMU_REG_DDTP, ddtp);
+ g_assert((qtest_readq(qts, iommu_base + RISCV_IOMMU_REG_DDTP) &
+ (RISCV_IOMMU_DDTP_PPN | RISCV_IOMMU_DDTP_MODE)) ==
+ (ddtp & (RISCV_IOMMU_DDTP_PPN | RISCV_IOMMU_DDTP_MODE)));
+}
diff --git a/tests/qtest/libqos/qos-riscv-iommu.h b/tests/qtest/libqos/qos-riscv-iommu.h
new file mode 100644
index 0000000000..1f4efbf682
--- /dev/null
+++ b/tests/qtest/libqos/qos-riscv-iommu.h
@@ -0,0 +1,172 @@
+/*
+ * QOS RISC-V IOMMU Module
+ *
+ * This module provides RISC-V IOMMU-specific helper functions for libqos tests,
+ * encapsulating RISC-V IOMMU setup, and assertions.
+ *
+ * Copyright (c) 2026 Chao Liu <chao.li...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef QTEST_LIBQOS_RISCV_IOMMU_H
+#define QTEST_LIBQOS_RISCV_IOMMU_H
+
+#include "hw/misc/iommu-testdev.h"
+
+/* RISC-V IOMMU MMIO register base for virt machine */
+#define VIRT_RISCV_IOMMU_BASE 0x0000000003010000ull
+
+/* RISC-V IOMMU queue and table base addresses */
+#define QRIOMMU_CQ_BASE_ADDR 0x000000000e160000ull
+#define QRIOMMU_FQ_BASE_ADDR 0x000000000e170000ull
+
+/* RISC-V IOMMU queue sizing */
+#define QRIOMMU_QUEUE_ENTRIES 1024
+#define QRIOMMU_CQ_ENTRY_SIZE 16
+#define QRIOMMU_FQ_ENTRY_SIZE 32
+
+/*
+ * Translation tables and descriptors for RISC-V IOMMU.
+ * Similar to ARM SMMUv3, but using RISC-V IOMMU terminology:
+ * - Device Context (DC) instead of STE
+ * - First-stage context (FSC) for S-stage translation
+ * - IOHGATP for G-stage translation
+ *
+ * Granule size: 4KB pages
+ * Page table levels: 3 levels for SV39 (L0, L1, L2)
+ * IOVA size: 39-bit virtual address space
+ */
+#define QRIOMMU_IOVA 0x0000000080604567ull
+#define QRIOMMU_IOHGATP 0x0000000000010000ull
+#define QRIOMMU_DDT_BASE 0x0000000000014000ull
+#define QRIOMMU_DC_BASE (QRIOMMU_DDT_BASE)
+
+#define QRIOMMU_L0_PTE_VAL 0x0000000000011000ull
+#define QRIOMMU_L1_PTE_VAL 0x0000000000012000ull
+#define QRIOMMU_L2_PTE_VAL 0x0000000000013000ull
+
+#define QRIOMMU_G_IOHGATP 0x0000000000020000ull
+#define QRIOMMU_G_L0_PTE_VAL 0x0000000000021000ull
+#define QRIOMMU_G_L1_PTE_VAL 0x0000000000022000ull
+
+/* RISC-V page table entry masks */
+#define QRIOMMU_PTE_V 0x0000000000000001ull
+#define QRIOMMU_PTE_R 0x0000000000000002ull
+#define QRIOMMU_PTE_W 0x0000000000000004ull
+#define QRIOMMU_PTE_X 0x0000000000000008ull
+#define QRIOMMU_PTE_U 0x0000000000000010ull
+#define QRIOMMU_PTE_G 0x0000000000000020ull
+#define QRIOMMU_PTE_A 0x0000000000000040ull
+#define QRIOMMU_PTE_D 0x0000000000000080ull
+
+#define QRIOMMU_NON_LEAF_PTE_MASK (QRIOMMU_PTE_V)
+#define QRIOMMU_LEAF_PTE_RW_MASK (QRIOMMU_PTE_V | QRIOMMU_PTE_R | \
+ QRIOMMU_PTE_W | QRIOMMU_PTE_A | \
+ QRIOMMU_PTE_D)
+#define QRIOMMU_PTE_PPN_MASK 0x003ffffffffffc00ull
+
+/* Address-space base offset for test tables */
+#define QRIOMMU_SPACE_OFFS 0x0000000080000000ull
+
+typedef enum QRIOMMUTransMode {
+ QRIOMMU_TM_BARE = 0, /* No translation (pass-through) */
+ QRIOMMU_TM_S_STAGE_ONLY = 1, /* First-stage only (S-stage) */
+ QRIOMMU_TM_G_STAGE_ONLY = 2, /* Second-stage only (G-stage) */
+ QRIOMMU_TM_NESTED = 3, /* Nested translation (S + G) */
+} QRIOMMUTransMode;
+
+typedef struct QRIOMMUTestConfig {
+ QRIOMMUTransMode trans_mode; /* Translation mode */
+ uint64_t dma_gpa; /* GPA for readback validation */
+ uint32_t dma_len; /* DMA length for testing */
+ uint32_t expected_result; /* Expected DMA result */
+} QRIOMMUTestConfig;
+
+typedef struct QRIOMMUTestContext {
+ QTestState *qts; /* QTest state handle */
+ QPCIDevice *dev; /* PCI device handle */
+ QPCIBar bar; /* PCI BAR for MMIO access */
+ QRIOMMUTestConfig config; /* Test configuration */
+ uint64_t iommu_base; /* RISC-V IOMMU base address */
+ uint32_t trans_status; /* Translation configuration status */
+ uint32_t dma_result; /* DMA operation result */
+ uint32_t device_id; /* Device ID for the test */
+} QRIOMMUTestContext;
+
+/*
+ * qriommu_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 RISC-V IOMMU structures (DC, page tables)
+ * 2. Programs RISC-V IOMMU registers
+ * 3. Returns configuration status
+ */
+uint32_t qriommu_setup_and_enable_translation(QRIOMMUTestContext *ctx);
+
+/*
+ * qriommu_build_translation - Build RISC-V IOMMU translation structures
+ *
+ * @qts: QTest state handle
+ * @mode: Translation mode (BARE, S_STAGE_ONLY, G_STAGE_ONLY, NESTED)
+ * @device_id: Device ID
+ *
+ * Returns: Build status (0 = success, non-zero = error)
+ *
+ * Constructs all necessary RISC-V IOMMU translation structures in guest memory:
+ * - Device Context (DC) for the given device ID
+ * - First-stage context (FSC) if S-stage translation is involved
+ * - Complete page table hierarchy based on translation mode
+ */
+uint32_t qriommu_build_translation(QTestState *qts, QRIOMMUTransMode mode,
+ uint32_t device_id);
+
+/*
+ * qriommu_program_regs - Program all required RISC-V IOMMU registers
+ *
+ * @qts: QTest state handle
+ * @iommu_base: RISC-V IOMMU base address
+ *
+ * Programs RISC-V IOMMU registers:
+ * - Device Directory Table Pointer (DDTP)
+ * - Command queue (base, head, tail)
+ * - Fault queue (base, head, tail)
+ * - Control and status registers
+ */
+void qriommu_program_regs(QTestState *qts, uint64_t iommu_base);
+
+/*
+ * qriommu_setup_translation_tables - Setup RISC-V IOMMU page table hierarchy
+ *
+ * @qts: QTest state handle
+ * @iova: Input Virtual Address to translate
+ * @mode: Translation mode
+ *
+ * This function builds the complete page table structure for translating
+ * the given IOVA through the RISC-V IOMMU. The structure varies based on mode:
+ *
+ * - BARE: No translation (pass-through)
+ * - S_STAGE_ONLY: Single S-stage walk (IOVA -> PA)
+ * - G_STAGE_ONLY: Single G-stage walk (IPA -> PA)
+ * - NESTED: S-stage walk (IOVA -> IPA) + G-stage walk (IPA -> PA)
+ */
+void qriommu_setup_translation_tables(QTestState *qts,
+ uint64_t iova,
+ QRIOMMUTransMode mode);
+
+/* High-level test execution helpers */
+void qriommu_run_translation_case(QTestState *qts, QPCIDevice *dev,
+ QPCIBar bar, uint64_t iommu_base,
+ const QRIOMMUTestConfig *cfg);
+
+/* Calculate expected DMA result */
+uint32_t qriommu_expected_dma_result(QRIOMMUTestContext *ctx);
+
+/* Build DMA attributes for RISC-V IOMMU */
+uint32_t qriommu_build_dma_attrs(void);
+
+#endif /* QTEST_LIBQOS_RISCV_IOMMU_H */
--
2.52.0

Chao Liu

unread,
Jan 28, 2026, 7:10:06 AM (11 days ago) Jan 28
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine.
The test exercises bare, S-stage, G-stage, and nested translation paths
using iommu-testdev and the qos-riscv-iommu helpers.

The test validates:
- Device context (DC) configuration
- SV39 page table walks for S-stage translation
- SV39x4 page table walks for G-stage translation
- Nested translation combining both stages
- FCTL register constraints

This provides regression coverage for the RISC-V IOMMU implementation
without requiring a full guest OS boot.

Signed-off-by: Chao Liu <chao.li...@gmail.com>
---
MAINTAINERS | 1 +
tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 5 +-
3 files changed, 284 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/iommu-riscv-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 894e05bd2c..c7400c83d2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -347,6 +347,7 @@ F: common-user/host/riscv*
F: tests/functional/riscv32
F: tests/functional/riscv64
F: tests/tcg/riscv64/
+F: tests/qtest/iommu-riscv-test.c

RISC-V XThead* extensions
M: Christoph Muellner <christoph...@vrull.eu>
diff --git a/tests/qtest/iommu-riscv-test.c b/tests/qtest/iommu-riscv-test.c
new file mode 100644
index 0000000000..9438578e1e
--- /dev/null
+++ b/tests/qtest/iommu-riscv-test.c
@@ -0,0 +1,279 @@
+/*
+ * QTest testcase for RISC-V IOMMU with iommu-testdev
+ *
+ * This QTest file is used to test the RISC-V IOMMU with iommu-testdev so that
+ * we can test RISC-V IOMMU without any guest kernel or firmware.
+ *
+ * Copyright (c) 2026 Chao Liu <chao.li...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/generic-pcihost.h"
+#include "hw/pci/pci_regs.h"
+#include "hw/misc/iommu-testdev.h"
+#include "hw/riscv/riscv-iommu-bits.h"
+#include "libqos/qos-riscv-iommu.h"
+#include "libqos/riscv-iommu.h"
+
+#define DMA_LEN 4
+
+/* RISC-V virt machine PCI configuration */
+#define RISCV_GPEX_PIO_BASE 0x3000000
+#define RISCV_BUS_PIO_LIMIT 0x10000
+#define RISCV_BUS_MMIO_ALLOC_PTR 0x40000000
+#define RISCV_BUS_MMIO_LIMIT 0x80000000
+#define RISCV_ECAM_ALLOC_PTR 0x30000000
+
+typedef struct RiscvIommuTestState {
+ QTestState *qts;
+ QGenericPCIBus gbus;
+ QPCIDevice *iommu_dev;
+ QPCIDevice *testdev;
+ QPCIBar testdev_bar;
+ uint64_t iommu_base;
+} RiscvIommuTestState;
+
+static void riscv_config_qpci_bus(QGenericPCIBus *qpci)
+{
+ qpci->gpex_pio_base = RISCV_GPEX_PIO_BASE;
+ qpci->bus.pio_limit = RISCV_BUS_PIO_LIMIT;
+ qpci->bus.mmio_alloc_ptr = RISCV_BUS_MMIO_ALLOC_PTR;
+ qpci->bus.mmio_limit = RISCV_BUS_MMIO_LIMIT;
+ qpci->ecam_alloc_ptr = RISCV_ECAM_ALLOC_PTR;
+}
+
+static uint64_t riscv_iommu_expected_gpa(uint64_t iova)
+{
+ return QRIOMMU_SPACE_OFFS + QRIOMMU_L2_PTE_VAL + (iova & 0xfff);
+}
+
+static void save_fn(QPCIDevice *dev, int devfn, void *data)
+{
+ QPCIDevice **pdev = (QPCIDevice **) data;
+ uint16_t vendor = qpci_config_readw(dev, 0);
+ uint16_t device = qpci_config_readw(dev, 2);
+
+ g_test_message("Found PCI device: vendor=0x%04x device=0x%04x devfn=0x%02x",
+ vendor, device, devfn);
+
+ if (!*pdev) {
+ *pdev = dev;
+ }
+}
+
+static QPCIDevice *find_riscv_iommu_pci(QGenericPCIBus *gbus,
+ uint64_t *iommu_base)
+{
+ QPCIDevice *iommu_dev = NULL;
+ QPCIBar iommu_bar;
+
+ g_test_message("Searching for riscv-iommu-pci "
+ "(vendor=0x%04x, device=0x%04x)",
+ RISCV_IOMMU_PCI_VENDOR_ID, RISCV_IOMMU_PCI_DEVICE_ID);
+
+ qpci_device_foreach(&gbus->bus, RISCV_IOMMU_PCI_VENDOR_ID,
+ RISCV_IOMMU_PCI_DEVICE_ID, save_fn, &iommu_dev);
+
+ if (!iommu_dev) {
+ g_test_message("riscv-iommu-pci device not found!");
+ return NULL;
+ }
+
+ g_test_message("Found riscv-iommu-pci at devfn=0x%02x", iommu_dev->devfn);
+
+ qpci_device_enable(iommu_dev);
+ iommu_bar = qpci_iomap(iommu_dev, 0, NULL);
+ g_assert_false(iommu_bar.is_io);
+
+ *iommu_base = iommu_bar.addr;
+ g_test_message("RISC-V IOMMU MMIO base address: 0x%lx", *iommu_base);
+
+ return iommu_dev;
+}
+
+static QPCIDevice *find_iommu_testdev(QGenericPCIBus *gbus, QPCIBar *bar)
+{
+ QPCIDevice *dev = NULL;
+
+ g_test_message("Searching for iommu-testdev (vendor=0x%04x, device=0x%04x)",
+ IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID);
+
+ qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID,
+ IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev);
+ g_assert(dev);
+
+ qpci_device_enable(dev);
+ *bar = qpci_iomap(dev, 0, NULL);
+ g_assert_false(bar->is_io);
+
+ return dev;
+}
+
+static bool riscv_iommu_test_setup(RiscvIommuTestState *state)
+{
+ if (!qtest_has_machine("virt")) {
+ g_test_skip("virt machine not available");
+ return false;
+ }
+
+ state->qts = qtest_init("-machine virt,acpi=off "
+ "-cpu max -smp 1 -m 512 -net none "
+ "-device riscv-iommu-pci "
+ "-device iommu-testdev");
+
+ qpci_init_generic(&state->gbus, state->qts, NULL, false);
+ riscv_config_qpci_bus(&state->gbus);
+
+ state->iommu_dev = find_riscv_iommu_pci(&state->gbus, &state->iommu_base);
+ g_assert(state->iommu_dev);
+
+ state->testdev = find_iommu_testdev(&state->gbus, &state->testdev_bar);
+ g_assert(state->testdev);
+
+ return true;
+}
+
+static void riscv_iommu_test_teardown(RiscvIommuTestState *state)
+{
+ qtest_quit(state->qts);
+}
+
+static uint64_t riscv_iommu_check(QTestState *qts, uint64_t iommu_base,
+ QRIOMMUTransMode mode)
+{
+ uint64_t cap;
+ uint64_t ddtp;
+ uint32_t cqcsr;
+ uint32_t fqcsr;
+ uint32_t pqcsr;
+ uint32_t fctl;
+ uint32_t fctl_mask;
+ uint32_t fctl_desired;
+ uint32_t igs;
+
+ cap = qtest_readq(qts, iommu_base + RISCV_IOMMU_REG_CAP);
+ g_assert_cmpuint((uint32_t)(cap & RISCV_IOMMU_CAP_VERSION), ==,
+ RISCV_IOMMU_SPEC_DOT_VER);
+
+ fctl = qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_FCTL);
+ igs = (cap & RISCV_IOMMU_CAP_IGS) >> 28;
+ g_assert_cmpuint(igs, <=, RISCV_IOMMU_CAP_IGS_BOTH);
+
+ fctl_mask = RISCV_IOMMU_FCTL_BE | RISCV_IOMMU_FCTL_WSI |
+ RISCV_IOMMU_FCTL_GXL;
+ fctl_desired = fctl & ~fctl_mask;
+ if (igs == RISCV_IOMMU_CAP_IGS_WSI) {
+ fctl_desired |= RISCV_IOMMU_FCTL_WSI;
+ }
+
+ if ((fctl & fctl_mask) != (fctl_desired & fctl_mask)) {
+ ddtp = qtest_readq(qts, iommu_base + RISCV_IOMMU_REG_DDTP);
+ cqcsr = qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_CQCSR);
+ fqcsr = qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_FQCSR);
+ pqcsr = qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_PQCSR);
+
+ g_assert_cmpuint((uint32_t)(ddtp & RISCV_IOMMU_DDTP_MODE), ==,
+ RISCV_IOMMU_DDTP_MODE_OFF);
+ g_assert_cmpuint(cqcsr & RISCV_IOMMU_CQCSR_CQON, ==, 0);
+ g_assert_cmpuint(fqcsr & RISCV_IOMMU_FQCSR_FQON, ==, 0);
+ g_assert_cmpuint(pqcsr & RISCV_IOMMU_PQCSR_PQON, ==, 0);
+
+ qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_FCTL, fctl_desired);
+ fctl = qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_FCTL);
+ }
+
+ g_assert_cmpuint(fctl & fctl_mask, ==, fctl_desired & fctl_mask);
+
+ if (mode == QRIOMMU_TM_S_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) {
+ g_assert((cap & RISCV_IOMMU_CAP_SV39) != 0);
+ }
+ if (mode == QRIOMMU_TM_G_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) {
+ g_assert((cap & RISCV_IOMMU_CAP_SV39X4) != 0);
+ g_assert_cmpuint(fctl & RISCV_IOMMU_FCTL_GXL, ==, 0);
+ }
+
+ return cap;
+}
+
+static void run_riscv_iommu_translation(const QRIOMMUTestConfig *cfg)
+{
+ RiscvIommuTestState state = { 0 };
+
+ if (!riscv_iommu_test_setup(&state)) {
+ return;
+ }
+
+ riscv_iommu_check(state.qts, state.iommu_base, cfg->trans_mode);
+
+ g_test_message("### RISC-V IOMMU translation mode=%d ###",
+ cfg->trans_mode);
+ qriommu_run_translation_case(state.qts, state.testdev, state.testdev_bar,
+ state.iommu_base, cfg);
+ riscv_iommu_test_teardown(&state);
+}
+
+static void test_riscv_iommu_bare(void)
+{
+ QRIOMMUTestConfig cfg = {
+ .trans_mode = QRIOMMU_TM_BARE,
+ .dma_gpa = QRIOMMU_IOVA,
+ .dma_len = DMA_LEN,
+ .expected_result = 0,
+ };
+
+ run_riscv_iommu_translation(&cfg);
+}
+
+static void test_riscv_iommu_s_stage_only(void)
+{
+ QRIOMMUTestConfig cfg = {
+ .trans_mode = QRIOMMU_TM_S_STAGE_ONLY,
+ .dma_gpa = riscv_iommu_expected_gpa(QRIOMMU_IOVA),
+ .dma_len = DMA_LEN,
+ .expected_result = 0,
+ };
+
+ run_riscv_iommu_translation(&cfg);
+}
+
+static void test_riscv_iommu_g_stage_only(void)
+{
+ QRIOMMUTestConfig cfg = {
+ .trans_mode = QRIOMMU_TM_G_STAGE_ONLY,
+ .dma_gpa = riscv_iommu_expected_gpa(QRIOMMU_IOVA),
+ .dma_len = DMA_LEN,
+ .expected_result = 0,
+ };
+
+ run_riscv_iommu_translation(&cfg);
+}
+
+static void test_riscv_iommu_nested(void)
+{
+ QRIOMMUTestConfig cfg = {
+ .trans_mode = QRIOMMU_TM_NESTED,
+ .dma_gpa = riscv_iommu_expected_gpa(QRIOMMU_IOVA),
+ .dma_len = DMA_LEN,
+ .expected_result = 0,
+ };
+
+ run_riscv_iommu_translation(&cfg);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/iommu-testdev/translation/bare",
+ test_riscv_iommu_bare);
+ qtest_add_func("/iommu-testdev/translation/s-stage-only",
+ test_riscv_iommu_s_stage_only);
+ qtest_add_func("/iommu-testdev/translation/g-stage-only",
+ test_riscv_iommu_g_stage_only);
+ qtest_add_func("/iommu-testdev/translation/ns-nested",
+ test_riscv_iommu_nested);
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index a8b09d065f..eb45e0f97d 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -286,7 +286,10 @@ qtests_riscv32 = \
(config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : [])

qtests_riscv64 = ['riscv-csr-test'] + \
- (unpack_edk2_blobs ? ['bios-tables-test'] : [])
+ (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
+ config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
+ ['iommu-riscv-test'] : [])

qos_test_ss = ss.source_set()
qos_test_ss.add(
--
2.52.0

Daniel Henrique Barboza

unread,
Jan 28, 2026, 9:30:43 AM (11 days ago) Jan 28
to Chao Liu, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com


On 1/28/2026 9:09 AM, Chao Liu wrote:
> Introduce a libqos helper module for RISC-V IOMMU testing with
> iommu-testdev. The helper provides routines to:
>
> - Build device contexts (DC) and 3-level page tables for SV39/SV39x4
> - Program command queue (CQ), fault queue (FQ), and DDTP registers
> following the RISC-V IOMMU specification
> - Execute DMA translations and verify results
>
> The current implementation supports SV39 for S-stage and SV39x4 for
> G-stage translation. Support for SV48/SV48x4/SV57/SV57x4 can be added
> in future patches.
>
> Signed-off-by: Chao Liu <chao.li...@gmail.com>
> ---

Reviewed-by: Daniel Henrique Barboza <daniel....@oss.qualcomm.com>

Daniel Henrique Barboza

unread,
Jan 28, 2026, 9:30:59 AM (11 days ago) Jan 28
to Chao Liu, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com


On 1/28/2026 9:09 AM, Chao Liu wrote:
> Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine.
> The test exercises bare, S-stage, G-stage, and nested translation paths
> using iommu-testdev and the qos-riscv-iommu helpers.
>
> The test validates:
> - Device context (DC) configuration
> - SV39 page table walks for S-stage translation
> - SV39x4 page table walks for G-stage translation
> - Nested translation combining both stages
> - FCTL register constraints
>
> This provides regression coverage for the RISC-V IOMMU implementation
> without requiring a full guest OS boot.
>
> Signed-off-by: Chao Liu <chao.li...@gmail.com>
> ---

Reviewed-by: Daniel Henrique Barboza <daniel....@oss.qualcomm.com>

Daniel Henrique Barboza

unread,
Jan 28, 2026, 9:42:42 AM (11 days ago) Jan 28
to Chao Liu, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com


On 1/28/2026 9:09 AM, Chao Liu wrote:
I have a customized environment just to test IOMMU stuff (boot an Ubuntu
emulated RISC-V host, see if the riscv-iommu works with a PCI net card).
And even then this kind of test is limited by the amount of stuff the
current Linux driver is capable of doing. And yet I have to at least
give it a try with every IOMMU change/fix sent ... This new test will
give us additional coverage that neither the existing test nor my manual
testing can provide.

Thank you for the time and effort putting into this work. This is really
remarkable.

>
> Note: The current implementation only supports SV39/SV39x4. Support for
> SV48/SV48x4/SV57/SV57x4 can be added in future patches.
>
> Testing
> -------
>
> QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \
> ./build/tests/qtest/iommu-riscv-test --tap -k
>
> Question for Maintainers
> ------------------------
>
> The existing riscv-iommu-test.c and the new iommu-riscv-test.c serve
> complementary purposes. Would it be beneficial to merge these two tests
> into a single source file for easier maintenance? This would consolidate
> all RISC-V IOMMU testing in one place while preserving both the
> register-level and translation-level test coverage.

I'll give my non-maintainer opinion because I happen to be the author of
riscv-iommu-test.c: as long as we preserve the existing tests for both
files I'm ok with it. Maybe there are code re-use opportunities that
will be exposed when merging the 2 together.

But I believe this can wait. We should push this upstream first (or at
least get it queued into the maintainer's tree).


Thanks,
Daniel

Tao Tang

unread,
Jan 28, 2026, 10:23:17 AM (11 days ago) Jan 28
to Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Chao,
In the original SMMU/iommu-testdev work, I introduced a similar “base
offset” mainly to make room for future expansion where the same test
code needs to exercise multiple security domains (e.g., Arm Non-secure
vs Secure vs Realm), which in QEMU is reflected via distinct address
spaces / MemTxAttrs (the space field).

I’m not very familiar with the RISC-V security model. In RISC-V, do you
foresee a comparable need to test multiple “security states” for DMA
transactions?
mode doesn't seem to be used?

> +{
> + if (!is_leaf) {
> + return QRIOMMU_NON_LEAF_PTE_MASK;
> + }
> +
> + /* For leaf PTE, set RWX permissions */
> + return QRIOMMU_LEAF_PTE_RW_MASK;
> +}
> +
> ------------------------------<snip>------------------------------
>
>
>
> ------------------------------<snip>------------------------------
I'm not entirely sure if there are similar definitions in the RISC-V
header files, but if there are, I think we should consider reusing those
definitions instead of redefining them.


Besides the correctness of the RISC-V page-table construction may need
RISC-V experts to review closely.


Thanks,

Tao

Chao Liu

unread,
Jan 28, 2026, 10:46:52 AM (11 days ago) Jan 28
to Tao Tang, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Tao,

Thanks for the review!
Good question. RISC-V currently doesn't have a TrustZone-like security
model with multiple security domains (Secure/Non-secure/Realm) like ARM.
However, RISC-V does have the Hypervisor extension (H-extension) with
VS/VU modes, and there's a draft WorldGuard extension for security
isolation.

For now, the QRIOMMU_SPACE_OFFS is mainly used to avoid address conflicts
with other memory regions in the QEMU virt machine. If future RISC-V
security extensions require multi-domain testing, we can extend this
infrastructure accordingly.
You're right. The `mode` parameter is currently unused. I'll remove it
in the next version.
Good catch. I found that `target/riscv/cpu_bits.h` already defines these
PTE bits (PTE_V, PTE_R, PTE_W, etc.). I'll reuse those definitions
instead of redefining them in the next version.


Thanks,
Chao

Chao Liu

unread,
Jan 28, 2026, 11:18:30 AM (11 days ago) Jan 28
to Tao Tang, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Tao,

On 1/28/2026 11:36 PM, Tao Tang wrote:
> Hi Chao,
>
> On 2026/1/28 20:09, Chao Liu wrote:
> There was a cross-compilation error on mingw64:
>
> ../tests/qtest/iommu-riscv-test.c: In function 'find_riscv_iommu_pci':
> ../tests/qtest/iommu-riscv-test.c:93:57: error: format '%lx' expects argument of type 'long unsigned int', but argument 2 has type 'uint64_t' {aka 'long long unsigned int'} [-Werror=format=]
>    93 |     g_test_message("RISC-V IOMMU MMIO base address: 0x%lx", *iommu_base);
>       |  ~~^  ~~~~~~~~~~~
>       |  |  |
>       |  |  uint64_t {aka long long unsigned int}
>       |  long unsigned int
>       |  %llx
>
> I think we should use PRIx64 instead.

|Good catch. I'll fix this by using PRIx64 in the next version:|

|-    g_test_message("RISC-V IOMMU MMIO base address: 0x%lx", *iommu_base);
+    g_test_message("RISC-V IOMMU MMIO base address: 0x%" PRIx64, *iommu_base);|

>
> You can check the result in this link [1]. And try it again with Pierrick's downstream fork [2]
>
> [1] https://github.com/hnusdr/qemu/actions/runs/21438370066/job/61734760185
>
> [2] https://github.com/pbo-linaro/qemu/
>
>
|Thanks for the CI links. I'll validate the v2 series with Pierrick's
qemu-ci workflow before sending.

Thanks,
Chao|

> Thanks,
>
> Tao

Chao Liu

unread,
Jan 28, 2026, 11:26:18 AM (11 days ago) Jan 28
to Daniel Henrique Barboza, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Daniel,

Thanks for the review and the kind words!
I'm glad this patch series can help us better test the RISC-V IOMMU.
The iommu-testdev framework makes it possible to exercise translation
paths directly without booting a full guest OS, which should make
regression testing much faster and more deterministic.
>>
>> Note: The current implementation only supports SV39/SV39x4. Support for
>> SV48/SV48x4/SV57/SV57x4 can be added in future patches.
>>
>> Testing
>> -------
>>
>> QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \
>>    ./build/tests/qtest/iommu-riscv-test --tap -k
>>
>> Question for Maintainers
>> ------------------------
>>
>> The existing riscv-iommu-test.c and the new iommu-riscv-test.c serve
>> complementary purposes. Would it be beneficial to merge these two tests
>> into a single source file for easier maintenance? This would consolidate
>> all RISC-V IOMMU testing in one place while preserving both the
>> register-level and translation-level test coverage.
>
> I'll give my non-maintainer opinion because I happen to be the author of riscv-iommu-test.c: as long as we preserve the existing tests for both files I'm ok with it. Maybe there are code re-use opportunities that will be exposed when merging the 2 together.
>
> But I believe this can wait. We should push this upstream first (or at least get it queued into the maintainer's tree).
>
>
I share the same view. That's exactly why I didn't merge them directly
in this series - the two tests serve different purposes (register-level
vs translation-level), and keeping them separate for now makes the
review process cleaner. We can explore merging opportunities later once
this is upstream.

Thanks,
Chao

Tao Tang

unread,
Jan 28, 2026, 9:00:00 PM (10 days ago) Jan 28
to Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Chao,

On 2026/1/28 20:09, Chao Liu wrote:
Declarations should be at the beginning of blocks. See `Declarations` in
docs/devel/style.rst. And we also had some discussion in another thread.
As Alex mentioned in the thread below:

https://lore.kernel.org/qemu-devel/875xb8l...@draig.linaro.org/

> +
> + buf = g_malloc(ctx.config.dma_len);
> + qtest_memread(ctx.qts, ctx.config.dma_gpa, buf, ctx.config.dma_len);
> +
> + for (int i = 0; i < ctx.config.dma_len; i++) {


This is correct. It is explicitly allowed by the special exemption for
loop variables inside for loops in style.rst.
Same style issue in this if block.


Thanks,

Tao

Chao Liu

unread,
Jan 29, 2026, 4:40:43 AM (10 days ago) Jan 29
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
Note: The current implementation only supports SV39/SV39x4. Support for
SV48/SV48x4/SV57/SV57x4 can be added in future patches.

Testing
-------

QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \
./build/tests/qtest/iommu-riscv-test --tap -k

Changes v1 -> v2
----------------
- Removed unused 'mode' parameter from qriommu_get_pte_attrs() function
- Simplified PTE mask definitions in header file by using direct hex
values instead of individual bit defines (removed QRIOMMU_PTE_V/R/W/X
/U/G/A/D macros), added comment referencing target/riscv/cpu_bits.h
- Cleaned up variable declarations in qriommu_setup_translation_tables()
to follow C99 style (declare at point of use)
- Minor code style improvements


Thanks,
Chao

Chao Liu (2):
tests/qtest/libqos: Add RISC-V IOMMU helper library
tests/qtest: Add RISC-V IOMMU bare-metal test

MAINTAINERS | 2 +
tests/qtest/iommu-riscv-test.c | 279 ++++++++++++++++++
tests/qtest/libqos/meson.build | 2 +-
tests/qtest/libqos/qos-riscv-iommu.c | 405 +++++++++++++++++++++++++++
tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++
tests/qtest/meson.build | 5 +-
6 files changed, 855 insertions(+), 2 deletions(-)
create mode 100644 tests/qtest/iommu-riscv-test.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h

--
2.52.0

Chao Liu

unread,
Jan 29, 2026, 4:40:47 AM (10 days ago) Jan 29
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
Introduce a libqos helper module for RISC-V IOMMU testing with
iommu-testdev. The helper provides routines to:

- Build device contexts (DC) and 3-level page tables for SV39/SV39x4
- Program command queue (CQ), fault queue (FQ), and DDTP registers
following the RISC-V IOMMU specification
- Execute DMA translations and verify results

The current implementation supports SV39 for S-stage and SV39x4 for
G-stage translation. Support for SV48/SV48x4/SV57/SV57x4 can be added
in future patches.

Signed-off-by: Chao Liu <chao.li...@gmail.com>
---
MAINTAINERS | 1 +
tests/qtest/libqos/meson.build | 2 +-
tests/qtest/libqos/qos-riscv-iommu.c | 405 +++++++++++++++++++++++++++
tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++
4 files changed, 571 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h

diff --git a/MAINTAINERS b/MAINTAINERS
index dccdf47888..830f56376b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3584,6 +3584,7 @@ M: Tao Tang <tangt...@phytium.com.cn>
S: Maintained
F: tests/qtest/libqos/qos-iommu*
F: tests/qtest/libqos/qos-smmuv3*
+F: tests/qtest/libqos/qos-riscv-iommu*

Device Fuzzing
M: Alexander Bulekov <alx...@bu.edu>
diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
index b4daec808f..4a69acad0d 100644
--- a/tests/qtest/libqos/meson.build
+++ b/tests/qtest/libqos/meson.build
@@ -71,7 +71,7 @@ if have_virtfs
endif

if config_all_devices.has_key('CONFIG_RISCV_IOMMU')
- libqos_srcs += files('riscv-iommu.c')
+ libqos_srcs += files('riscv-iommu.c', 'qos-riscv-iommu.c')
endif
if config_all_devices.has_key('CONFIG_TPCI200')
libqos_srcs += files('tpci200.c')
diff --git a/tests/qtest/libqos/qos-riscv-iommu.c b/tests/qtest/libqos/qos-riscv-iommu.c
new file mode 100644
index 0000000000..6c60889eeb
--- /dev/null
+++ b/tests/qtest/libqos/qos-riscv-iommu.c
@@ -0,0 +1,405 @@
+ g_autofree uint8_t *buf = g_malloc(ctx.config.dma_len);
+
+ qtest_memread(ctx.qts, ctx.config.dma_gpa, buf, ctx.config.dma_len);
+
+ for (int i = 0; i < ctx.config.dma_len; i++) {
+static uint64_t qriommu_get_pte_attrs(bool is_leaf)
+{
+ if (!is_leaf) {
+ return QRIOMMU_NON_LEAF_PTE_MASK;
+ }
+
+ /* For leaf PTE, set RWX permissions */
+ return QRIOMMU_LEAF_PTE_RW_MASK;
+}
+
+void qriommu_setup_translation_tables(QTestState *qts,
+ uint64_t iova,
+ QRIOMMUTransMode mode)
+{
+ uint64_t s_root = 0, s_l0_pte_val = 0, s_l1_pte_val = 0;
+ uint64_t s_l0_addr = 0, s_l1_addr = 0, s_l2_addr = 0, s_l2_pte_val = 0;
+ uint64_t s_l0_pa = 0, s_l1_pa = 0;
+ uint64_t s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+ uint64_t s_l0_pa_real = 0, s_l1_pa_real = 0;
+ uint64_t s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+ uint64_t non_leaf_attrs = qriommu_get_pte_attrs(false);
+ uint64_t leaf_attrs = qriommu_get_pte_attrs(true);
+ uint64_t g_root;
+ uint64_t g_l0_pa;
+ uint64_t g_l1_pa;
+ uint64_t g_l0_pte_val;
+ uint64_t g_l1_pte_val;
+
+ g_root = qriommu_apply_space_offs(
+ QRIOMMU_G_IOHGATP & QRIOMMU_PTE_PPN_MASK);
+ g_l0_pa = qriommu_apply_space_offs(QRIOMMU_G_L0_PTE_VAL);
+ g_l1_pa = qriommu_apply_space_offs(QRIOMMU_G_L1_PTE_VAL);
+ g_l0_pte_val = qriommu_encode_pte(g_l0_pa, non_leaf_attrs);
+ g_l1_pte_val = qriommu_encode_pte(g_l1_pa, non_leaf_attrs);
+
+ if (mode == QRIOMMU_TM_G_STAGE_ONLY) {
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ iova, s_l2_pa_real, leaf_attrs);
+ } else {
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ QRIOMMU_IOHGATP, s_root, leaf_attrs);
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ QRIOMMU_L0_PTE_VAL, s_l0_pa_real, leaf_attrs);
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ QRIOMMU_L1_PTE_VAL, s_l1_pa_real, leaf_attrs);
+ qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa,
+ g_l0_pte_val, g_l1_pte_val,
+ QRIOMMU_L2_PTE_VAL, s_l2_pa_real, leaf_attrs);
+ }
+ }
+}
+
+uint32_t qriommu_build_translation(QTestState *qts, QRIOMMUTransMode mode,
+ uint32_t device_id)
+ default:
+ g_assert_not_reached();
+ }
+
+ /* Write DC to memory */
+ qtest_writeq(qts, dc_addr_real + 0, dc.tc);
+ qtest_writeq(qts, dc_addr_real + 8, dc.iohgatp);
+ qtest_writeq(qts, dc_addr_real + 16, dc.ta);
+ qtest_writeq(qts, dc_addr_real + 24, dc.fsc);
+ qtest_writeq(qts, dc_addr_real + 32, dc.msiptp);
+ qtest_writeq(qts, dc_addr_real + 40, dc.msi_addr_mask);
+ qtest_writeq(qts, dc_addr_real + 48, dc.msi_addr_pattern);
+ qtest_writeq(qts, dc_addr_real + 56, dc._reserved);
+
+ /* Setup translation tables if not in BARE mode */
+ if (mode != QRIOMMU_TM_BARE) {
+ qriommu_setup_translation_tables(qts, QRIOMMU_IOVA, mode);
+ }
+
+ return 0;
+}
+
+void qriommu_program_regs(QTestState *qts, uint64_t iommu_base)
new file mode 100644
index 0000000000..90e69a5d73
--- /dev/null
+++ b/tests/qtest/libqos/qos-riscv-iommu.h
@@ -0,0 +1,164 @@
+/*
+ * QOS RISC-V IOMMU Module
+ *
+ * This module provides RISC-V IOMMU-specific helper functions for libqos tests,
+ * encapsulating RISC-V IOMMU setup, and assertions.
+ *
+ * Copyright (c) 2026 Chao Liu <chao.li...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+ * PTE masks for RISC-V IOMMU page tables.
+ * Values match PTE_V, PTE_R, PTE_W, PTE_A, PTE_D in target/riscv/cpu_bits.h
+ */
+#define QRIOMMU_NON_LEAF_PTE_MASK 0x001 /* PTE_V */
+#define QRIOMMU_LEAF_PTE_RW_MASK 0x0c7 /* V|R|W|A|D */
+ *
+ *
+ * This function builds the complete page table structure for translating
+ * the given IOVA through the RISC-V IOMMU. The structure varies based on mode:
+ *
+ * - BARE: No translation (pass-through)
+ * - S_STAGE_ONLY: Single S-stage walk (IOVA -> PA)
+ * - G_STAGE_ONLY: Single G-stage walk (IPA -> PA)
+ * - NESTED: S-stage walk (IOVA -> IPA) + G-stage walk (IPA -> PA)
+ */
+void qriommu_setup_translation_tables(QTestState *qts,
+ uint64_t iova,
+ QRIOMMUTransMode mode);
+
+/* High-level test execution helpers */
+void qriommu_run_translation_case(QTestState *qts, QPCIDevice *dev,
+ QPCIBar bar, uint64_t iommu_base,
+ const QRIOMMUTestConfig *cfg);
+
+/* Calculate expected DMA result */
+uint32_t qriommu_expected_dma_result(QRIOMMUTestContext *ctx);
+
+/* Build DMA attributes for RISC-V IOMMU */
+uint32_t qriommu_build_dma_attrs(void);
+
+#endif /* QTEST_LIBQOS_RISCV_IOMMU_H */
--
2.52.0

Chao Liu

unread,
Jan 29, 2026, 4:40:50 AM (10 days ago) Jan 29
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine.
The test exercises bare, S-stage, G-stage, and nested translation paths
using iommu-testdev and the qos-riscv-iommu helpers.

The test validates:
- Device context (DC) configuration
- SV39 page table walks for S-stage translation
- SV39x4 page table walks for G-stage translation
- Nested translation combining both stages
- FCTL register constraints

This provides regression coverage for the RISC-V IOMMU implementation
without requiring a full guest OS boot.

Signed-off-by: Chao Liu <chao.li...@gmail.com>
---
MAINTAINERS | 1 +
tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 5 +-
3 files changed, 284 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/iommu-riscv-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 830f56376b..73daaad841 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -349,6 +349,7 @@ F: common-user/host/riscv*
F: tests/functional/riscv32
F: tests/functional/riscv64
F: tests/tcg/riscv64/
+F: tests/qtest/iommu-riscv-test.c

RISC-V XThead* extensions
M: Christoph Muellner <christoph...@vrull.eu>
diff --git a/tests/qtest/iommu-riscv-test.c b/tests/qtest/iommu-riscv-test.c
new file mode 100644
index 0000000000..5a86b18db9
--- /dev/null
+++ b/tests/qtest/iommu-riscv-test.c
@@ -0,0 +1,279 @@
+/*
+ * QTest testcase for RISC-V IOMMU with iommu-testdev
+ *
+ * This QTest file is used to test the RISC-V IOMMU with iommu-testdev so that
+ * we can test RISC-V IOMMU without any guest kernel or firmware.
+ *
+ * Copyright (c) 2026 Chao Liu <chao.li...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+ g_test_message("RISC-V IOMMU MMIO base address: 0x%" PRIx64, *iommu_base);
+ QRIOMMUTransMode mode)
+{
+ uint64_t cap;
+
+ if (mode == QRIOMMU_TM_S_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) {
+ g_assert((cap & RISCV_IOMMU_CAP_SV39) != 0);
+ }
+ if (mode == QRIOMMU_TM_G_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) {
+ g_assert((cap & RISCV_IOMMU_CAP_SV39X4) != 0);
index dfb83650c6..25fdbc7980 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -287,7 +287,10 @@ qtests_riscv32 = \
(config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : [])

qtests_riscv64 = ['riscv-csr-test'] + \
- (unpack_edk2_blobs ? ['bios-tables-test'] : [])
+ (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
+ config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
+ ['iommu-riscv-test'] : [])

qos_test_ss = ss.source_set()
qos_test_ss.add(
--
2.52.0

Tao Tang

unread,
Jan 29, 2026, 10:46:54 PM (9 days ago) Jan 29
to Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Chao,
This include is unnecessary: hw/misc/iommu-testdev.h (pulled in by
qos-riscv-iommu.h) already includes hw/pci/pci.h, and hw/pci/pci.h
itself includes bitops.h.

> +#include "hw/riscv/riscv-iommu-bits.h"
> +#include "tests/qtest/libqos/pci.h"

Same. qos-iommu-testdev.h has included tests/qtest/libqos/pci.h.


Besides

Reviewed-by: Tao Tang <tangt...@phytium.com.cn>


Thanks,

Tao

Tao Tang

unread,
Jan 31, 2026, 8:15:24 AM (8 days ago) Jan 31
to Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Chao,

On 2026/1/28 20:09, Chao Liu wrote:
> Hi,
>
> This patch series adds a bare-metal qtest for the RISC-V IOMMU using the
> iommu-testdev framework. The test exercises address translation paths
> without requiring a full guest OS boot.


Thanks a lot for working on this and for sending the series.

It's great to see iommu-testdev being used so soon — it’s only been a
week since it was added, and you already have a nice RISC-V IOMMU
translation test based on it.


Best regards,

Tao

Tao Tang

unread,
Jan 31, 2026, 8:15:28 AM (8 days ago) Jan 31
to Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Chao,

On 2026/1/28 20:09, Chao Liu wrote:
There was a cross-compilation error on mingw64:

../tests/qtest/iommu-riscv-test.c: In function 'find_riscv_iommu_pci':
../tests/qtest/iommu-riscv-test.c:93:57: error: format '%lx' expects
argument of type 'long unsigned int', but argument 2 has type 'uint64_t'
{aka 'long long unsigned int'} [-Werror=format=]
   93 |     g_test_message("RISC-V IOMMU MMIO base address: 0x%lx",
*iommu_base);
      |                                                       ~~^
 ~~~~~~~~~~~
      |                                                         |  |
      |                                                         |
 uint64_t {aka long long unsigned int}
      |  long unsigned int
      |                                                       %llx

I think we should use PRIx64 instead.

You can check the result in this link [1]. And try it again with
Pierrick's downstream fork [2]

[1] https://github.com/hnusdr/qemu/actions/runs/21438370066/job/61734760185

[2] https://github.com/pbo-linaro/qemu/


Thanks,

Tao

Tao Tang

unread,
Jan 31, 2026, 8:15:32 AM (8 days ago) Jan 31
to Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Chao,

On 2026/1/29 17:39, Chao Liu wrote:
libqtest.h and libqos/pci.h are redundant because they are already
pulled in by libqos/generic-pcihost.h (libqos/pci.h itself includes
libqtest.h)


Besides

Reviewed-by: Tao Tang <tangt...@phytium.com.cn>


Thanks,

Tao

Fabiano Rosas

unread,
Feb 2, 2026, 2:09:31 PM (6 days ago) Feb 2
to Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
Chao Liu <chao.li...@gmail.com> writes:

> Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine.
> The test exercises bare, S-stage, G-stage, and nested translation paths
> using iommu-testdev and the qos-riscv-iommu helpers.
>
> The test validates:
> - Device context (DC) configuration
> - SV39 page table walks for S-stage translation
> - SV39x4 page table walks for G-stage translation
> - Nested translation combining both stages
> - FCTL register constraints
>
> This provides regression coverage for the RISC-V IOMMU implementation
> without requiring a full guest OS boot.
>
> Signed-off-by: Chao Liu <chao.li...@gmail.com>

...

> +static bool riscv_iommu_test_setup(RiscvIommuTestState *state)
> +{
> + if (!qtest_has_machine("virt")) {
> + g_test_skip("virt machine not available");
> + return false;
> + }
> +
> + state->qts = qtest_init("-machine virt,acpi=off "
> + "-cpu max -smp 1 -m 512 -net none "
> + "-device riscv-iommu-pci "
> + "-device iommu-testdev");
> +
> + qpci_init_generic(&state->gbus, state->qts, NULL, false);
> + riscv_config_qpci_bus(&state->gbus);
> +
> + state->iommu_dev = find_riscv_iommu_pci(&state->gbus, &state->iommu_base);
> + g_assert(state->iommu_dev);
> +
> + state->testdev = find_iommu_testdev(&state->gbus, &state->testdev_bar);
> + g_assert(state->testdev);
> +

These two will leak. Otherwise, looks good.

Reviewed-by: Fabiano Rosas <far...@suse.de>

Chao Liu

unread,
Feb 2, 2026, 9:34:08 PM (5 days ago) Feb 2
to Tao Tang, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Thanks for the review. I'ill fix this in v3.

Thanks,
Chao

Chao Liu

unread,
Feb 2, 2026, 10:44:18 PM (5 days ago) Feb 2
to Tao Tang, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Tao,
Thanks for the review. I'ill fix this in v3.

Thanks,
Chao

Chao Liu

unread,
Feb 2, 2026, 10:52:31 PM (5 days ago) Feb 2
to Fabiano Rosas, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
Hi Fabiano,
Thanks for the review. I'ill fix this in v3.

Thanks,
Chao
> Reviewed-by: Fabiano Rosas <far...@suse.de>

chao.li...@gmail.com

unread,
Feb 3, 2026, 9:27:24 AM (5 days ago) Feb 3
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
From: Chao Liu <chao.li...@gmail.com>

Hi,

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

Motivation
----------

The existing RISC-V IOMMU qtest (riscv-iommu-test.c) focuses on PCI device
enumeration and register-level validation:
- PCI configuration space verification (vendor/device ID)
- Register reset value checks
- Queue initialization procedures (CQ/FQ/PQ)

However, it does not test the actual address translation functionality.
This new test fills that gap by using iommu-testdev to trigger DMA
transactions and validate the IOMMU's translation logic.

Comparison with Existing Test
-----------------------------

| Feature | riscv-iommu-test.c | iommu-riscv-test.c (new) |
|-----------------------|--------------------|--------------------------|
| PCI config | Yes | No |
| Register reset | Yes | No |
| Queue init | Yes | Yes (via helper) |
| Bare translation | No | Yes |
| S-stage (SV39) | No | Yes |
| G-stage (SV39x4) | No | Yes |
| Nested translation | No | Yes |
| DMA verification | No | Yes |
| Uses iommu-testdev | No | Yes |

The new test provides:
- Device context (DC) configuration and validation
- SV39 page table walks for S-stage translation
- SV39x4 page table walks for G-stage translation
- Nested translation combining both stages
- FCTL register constraint validation
- End-to-end DMA verification

Note: The current implementation only supports SV39/SV39x4. Support for
SV48/SV48x4/SV57/SV57x4 can be added in future patches.

Testing
-------

QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \
./build/tests/qtest/iommu-riscv-test --tap -k

Changes v2 -> v3
----------------
- Removed duplicate header includes in both patches (Tao)
- Fixed memory leak of state->iommu_dev and state->testdev in
riscv_iommu_test_setup() in patch 2 (Fabiano)

Changes v1 -> v2
----------------
- Removed unused 'mode' parameter from qriommu_get_pte_attrs() function
- Simplified PTE mask definitions in header file by using direct hex
values instead of individual bit defines (removed QRIOMMU_PTE_V/R/W/X
/U/G/A/D macros), added comment referencing target/riscv/cpu_bits.h
- Cleaned up variable declarations in qriommu_setup_translation_tables()
to follow C99 style (declare at point of use)
- Minor code style improvements

Thanks,
Chao

Chao Liu (2):
tests/qtest/libqos: Add RISC-V IOMMU helper library
tests/qtest: Add RISC-V IOMMU bare-metal test

MAINTAINERS | 2 +
tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++
tests/qtest/libqos/meson.build | 2 +-
tests/qtest/libqos/qos-riscv-iommu.c | 403 +++++++++++++++++++++++++++
tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++
tests/qtest/meson.build | 5 +-
6 files changed, 853 insertions(+), 2 deletions(-)
create mode 100644 tests/qtest/iommu-riscv-test.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h

--
2.53.0

chao.li...@gmail.com

unread,
Feb 3, 2026, 9:27:37 AM (5 days ago) Feb 3
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
From: Chao Liu <chao.li...@gmail.com>

Introduce a libqos helper module for RISC-V IOMMU testing with
iommu-testdev. The helper provides routines to:

- Build device contexts (DC) and 3-level page tables for SV39/SV39x4
- Program command queue (CQ), fault queue (FQ), and DDTP registers
following the RISC-V IOMMU specification
- Execute DMA translations and verify results

The current implementation supports SV39 for S-stage and SV39x4 for
G-stage translation. Support for SV48/SV48x4/SV57/SV57x4 can be added
in future patches.

Signed-off-by: Chao Liu <chao.li...@gmail.com>
Reviewed-by: Tao Tang <tangt...@phytium.com.cn>
Reviewed-by: Fabiano Rosas <far...@suse.de>
---
MAINTAINERS | 1 +
tests/qtest/libqos/meson.build | 2 +-
tests/qtest/libqos/qos-riscv-iommu.c | 403 +++++++++++++++++++++++++++
tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++
4 files changed, 569 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h

diff --git a/MAINTAINERS b/MAINTAINERS
index dccdf47888..830f56376b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3584,6 +3584,7 @@ M: Tao Tang <tangt...@phytium.com.cn>
S: Maintained
F: tests/qtest/libqos/qos-iommu*
F: tests/qtest/libqos/qos-smmuv3*
+F: tests/qtest/libqos/qos-riscv-iommu*

Device Fuzzing
M: Alexander Bulekov <alx...@bu.edu>
diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
index b4daec808f..4a69acad0d 100644
--- a/tests/qtest/libqos/meson.build
+++ b/tests/qtest/libqos/meson.build
@@ -71,7 +71,7 @@ if have_virtfs
endif

if config_all_devices.has_key('CONFIG_RISCV_IOMMU')
- libqos_srcs += files('riscv-iommu.c')
+ libqos_srcs += files('riscv-iommu.c', 'qos-riscv-iommu.c')
endif
if config_all_devices.has_key('CONFIG_TPCI200')
libqos_srcs += files('tpci200.c')
diff --git a/tests/qtest/libqos/qos-riscv-iommu.c b/tests/qtest/libqos/qos-riscv-iommu.c
new file mode 100644
index 0000000000..295583f536
--- /dev/null
+++ b/tests/qtest/libqos/qos-riscv-iommu.c
@@ -0,0 +1,403 @@
+/*
+ * QOS RISC-V IOMMU Module
+ *
+ * This module provides RISC-V IOMMU-specific helper functions for libqos tests,
+ * encapsulating RISC-V IOMMU setup, and assertions.
+ *
+ * Copyright (c) 2026 Chao Liu <chao.li...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/riscv/riscv-iommu-bits.h"
+#include "qos-iommu-testdev.h"
+#include "qos-riscv-iommu.h"
+
+ QRIOMMUTransMode mode)
+{
+ uint64_t s_root = 0, s_l0_pte_val = 0, s_l1_pte_val = 0;
+ uint64_t s_l0_addr = 0, s_l1_addr = 0, s_l2_addr = 0, s_l2_pte_val = 0;
+ uint64_t s_l0_pa = 0, s_l1_pa = 0;
+ uint64_t s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+ uint64_t s_l0_pa_real = 0, s_l1_pa_real = 0;
+ uint64_t s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+ uint64_t non_leaf_attrs = qriommu_get_pte_attrs(false);
+ uint64_t leaf_attrs = qriommu_get_pte_attrs(true);
+
+ if (mode != QRIOMMU_TM_G_STAGE_ONLY) {
+ /* Setup S-stage 3-level page tables (SV39) */
+ s_l0_pa = qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL);
+ s_l1_pa = qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL);
+ s_root = qriommu_apply_space_offs(
+ QRIOMMU_IOHGATP & QRIOMMU_PTE_PPN_MASK);
+ s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+
+ s_l0_pa_real = s_l0_pa;
+ s_l1_pa_real = s_l1_pa;
+ s_l2_pa_real = s_l2_pa;
+
+ if (mode == QRIOMMU_TM_NESTED) {
+ s_l0_pa = QRIOMMU_L0_PTE_VAL;
+ s_l1_pa = QRIOMMU_L1_PTE_VAL;
+ s_l2_pa = QRIOMMU_L2_PTE_VAL;
+
+ s_l0_pa_real = qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL);
+ s_l1_pa_real = qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL);
+ s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL);
+ }
+
+ s_l0_pte_val = qriommu_encode_pte(s_l0_pa, non_leaf_attrs);
+ s_l1_pte_val = qriommu_encode_pte(s_l1_pa, non_leaf_attrs);
+
+ s_l0_addr = qriommu_get_table_addr(s_root, 0, iova);
+ qtest_writeq(qts, s_l0_addr, s_l0_pte_val);
+
+ s_l1_addr = qriommu_get_table_addr(s_l0_pa_real, 1, iova);
+ qtest_writeq(qts, s_l1_addr, s_l1_pte_val);
+
+ s_l2_addr = qriommu_get_table_addr(s_l1_pa_real, 2, iova);
+ s_l2_pte_val = qriommu_encode_pte(s_l2_pa, leaf_attrs);
+ qtest_writeq(qts, s_l2_addr, s_l2_pte_val);
+ }
+
+ if (mode == QRIOMMU_TM_G_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) {
+ uint64_t g_root;
+ uint64_t g_l0_pa;
+ uint64_t g_l1_pa;
+ uint64_t g_l0_pte_val;
+ uint64_t g_l1_pte_val;
+
+ g_root = qriommu_apply_space_offs(
+ QRIOMMU_G_IOHGATP & QRIOMMU_PTE_PPN_MASK);
+ g_l0_pa = qriommu_apply_space_offs(QRIOMMU_G_L0_PTE_VAL);
+ g_l1_pa = qriommu_apply_space_offs(QRIOMMU_G_L1_PTE_VAL);
+ g_l0_pte_val = qriommu_encode_pte(g_l0_pa, non_leaf_attrs);
+ g_l1_pte_val = qriommu_encode_pte(g_l1_pa, non_leaf_attrs);
+
new file mode 100644
index 0000000000..90e69a5d73
--- /dev/null
+++ b/tests/qtest/libqos/qos-riscv-iommu.h
@@ -0,0 +1,164 @@
+/*
+ * QOS RISC-V IOMMU Module
+ *
+ * This module provides RISC-V IOMMU-specific helper functions for libqos tests,
+ * encapsulating RISC-V IOMMU setup, and assertions.
+ *
+ * Copyright (c) 2026 Chao Liu <chao.li...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+ *
+ *
2.53.0

chao.li...@gmail.com

unread,
Feb 3, 2026, 9:27:46 AM (5 days ago) Feb 3
to Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
From: Chao Liu <chao.li...@gmail.com>

Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine.
The test exercises bare, S-stage, G-stage, and nested translation paths
using iommu-testdev and the qos-riscv-iommu helpers.

The test validates:
- Device context (DC) configuration
- SV39 page table walks for S-stage translation
- SV39x4 page table walks for G-stage translation
- Nested translation combining both stages
- FCTL register constraints

This provides regression coverage for the RISC-V IOMMU implementation
without requiring a full guest OS boot.

Signed-off-by: Chao Liu <chao.li...@gmail.com>
Reviewed-by: Tao Tang <tangt...@phytium.com.cn>
Reviewed-by: Fabiano Rosas <far...@suse.de>
---
MAINTAINERS | 1 +
tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 5 +-
3 files changed, 284 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/iommu-riscv-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 830f56376b..73daaad841 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -349,6 +349,7 @@ F: common-user/host/riscv*
F: tests/functional/riscv32
F: tests/functional/riscv64
F: tests/tcg/riscv64/
+F: tests/qtest/iommu-riscv-test.c

RISC-V XThead* extensions
M: Christoph Muellner <christoph...@vrull.eu>
diff --git a/tests/qtest/iommu-riscv-test.c b/tests/qtest/iommu-riscv-test.c
new file mode 100644
index 0000000000..2638024891
--- /dev/null
+++ b/tests/qtest/iommu-riscv-test.c
@@ -0,0 +1,279 @@
+/*
+ * QTest testcase for RISC-V IOMMU with iommu-testdev
+ *
+ * This QTest file is used to test the RISC-V IOMMU with iommu-testdev so that
+ * we can test RISC-V IOMMU without any guest kernel or firmware.
+ *
+ * Copyright (c) 2026 Chao Liu <chao.li...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/generic-pcihost.h"
+#include "hw/pci/pci_regs.h"
+#include "hw/misc/iommu-testdev.h"
+#include "hw/riscv/riscv-iommu-bits.h"
+#include "libqos/qos-riscv-iommu.h"
+#include "libqos/riscv-iommu.h"
+
+#define DMA_LEN 4
+ g_test_message("RISC-V IOMMU MMIO base address: 0x%" PRIx64, *iommu_base);
+
+ return iommu_dev;
+}
+
+static QPCIDevice *find_iommu_testdev(QGenericPCIBus *gbus, QPCIBar *bar)
+{
+ QPCIDevice *dev = NULL;
+
+ g_test_message("Searching for iommu-testdev (vendor=0x%04x, device=0x%04x)",
+ IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID);
+
+ qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID,
+ IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev);
+ g_assert(dev);
+
+ qpci_device_enable(dev);
+ *bar = qpci_iomap(dev, 0, NULL);
+ g_assert_false(bar->is_io);
+
+ return dev;
+}
+
+static bool riscv_iommu_test_setup(RiscvIommuTestState *state)
+{
+ if (!qtest_has_machine("virt")) {
+ g_test_skip("virt machine not available");
+ return false;
+ }
+
+ state->qts = qtest_init("-machine virt,acpi=off "
+ "-cpu max -smp 1 -m 512 -net none "
+ "-device riscv-iommu-pci "
+ "-device iommu-testdev");
+
+ qpci_init_generic(&state->gbus, state->qts, NULL, false);
+ riscv_config_qpci_bus(&state->gbus);
+
+ state->iommu_dev = find_riscv_iommu_pci(&state->gbus, &state->iommu_base);
+ g_assert(state->iommu_dev);
+
+ state->testdev = find_iommu_testdev(&state->gbus, &state->testdev_bar);
+ g_assert(state->testdev);
+
+ return true;
+}
+
+static void riscv_iommu_test_teardown(RiscvIommuTestState *state)
+{
+ g_free(state->iommu_dev);
+ g_free(state->testdev);
+ qtest_quit(state->qts);
+}
+
+static uint64_t riscv_iommu_check(QTestState *qts, uint64_t iommu_base,
+ QRIOMMUTransMode mode)
+{
+
+ if (mode == QRIOMMU_TM_S_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) {
+ g_assert((cap & RISCV_IOMMU_CAP_SV39) != 0);
+ }
+ if (mode == QRIOMMU_TM_G_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) {
index dfb83650c6..25fdbc7980 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -287,7 +287,10 @@ qtests_riscv32 = \
(config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : [])

qtests_riscv64 = ['riscv-csr-test'] + \
- (unpack_edk2_blobs ? ['bios-tables-test'] : [])
+ (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
+ config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
+ ['iommu-riscv-test'] : [])

qos_test_ss = ss.source_set()
qos_test_ss.add(
--
2.53.0

Daniel Henrique Barboza

unread,
Feb 6, 2026, 12:48:36 PM (2 days ago) Feb 6
to chao.li...@gmail.com, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com


On 2/3/2026 11:27 AM, chao.li...@gmail.com wrote:
> From: Chao Liu <chao.li...@gmail.com>
>
> Introduce a libqos helper module for RISC-V IOMMU testing with
> iommu-testdev. The helper provides routines to:
>
> - Build device contexts (DC) and 3-level page tables for SV39/SV39x4
> - Program command queue (CQ), fault queue (FQ), and DDTP registers
> following the RISC-V IOMMU specification
> - Execute DMA translations and verify results
>
> The current implementation supports SV39 for S-stage and SV39x4 for
> G-stage translation. Support for SV48/SV48x4/SV57/SV57x4 can be added
> in future patches.
>
> Signed-off-by: Chao Liu <chao.li...@gmail.com>
> Reviewed-by: Tao Tang <tangt...@phytium.com.cn>
> Reviewed-by: Fabiano Rosas <far...@suse.de>
> ---

Reviewed-by: Daniel Henrique Barboza <daniel....@oss.qualcomm.com>

Daniel Henrique Barboza

unread,
Feb 6, 2026, 12:48:44 PM (2 days ago) Feb 6
to chao.li...@gmail.com, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com


On 2/3/2026 11:27 AM, chao.li...@gmail.com wrote:
> From: Chao Liu <chao.li...@gmail.com>
>
> Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine.
> The test exercises bare, S-stage, G-stage, and nested translation paths
> using iommu-testdev and the qos-riscv-iommu helpers.
>
> The test validates:
> - Device context (DC) configuration
> - SV39 page table walks for S-stage translation
> - SV39x4 page table walks for G-stage translation
> - Nested translation combining both stages
> - FCTL register constraints
>
> This provides regression coverage for the RISC-V IOMMU implementation
> without requiring a full guest OS boot.
>
> Signed-off-by: Chao Liu <chao.li...@gmail.com>
> Reviewed-by: Tao Tang <tangt...@phytium.com.cn>
> Reviewed-by: Fabiano Rosas <far...@suse.de>
> ---

Reviewed-by: Daniel Henrique Barboza <daniel....@oss.qualcomm.com>

Fabiano Rosas

unread,
Feb 6, 2026, 2:53:15 PM (2 days ago) Feb 6
to chao.li...@gmail.com, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Laurent Vivier, Paolo Bonzini, Tao Tang, qemu-...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com, Chao Liu
I'm queuing this, but if riscv folks want to pull it, it's fine as well.
Reply all
Reply to author
Forward
0 new messages