* [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev
@ 2026-01-28 12:09 Chao Liu
2026-01-28 12:09 ` [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library Chao Liu
` (3 more replies)
0 siblings, 4 replies; 13+ messages in thread
From: Chao Liu @ 2026-01-28 12:09 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt,
Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Tao Tang
Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches, 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
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library 2026-01-28 12:09 [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Chao Liu @ 2026-01-28 12:09 ` Chao Liu 2026-01-28 14:30 ` Daniel Henrique Barboza ` (2 more replies) 2026-01-28 12:09 ` [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test Chao Liu ` (2 subsequent siblings) 3 siblings, 3 replies; 13+ messages in thread From: Chao Liu @ 2026-01-28 12:09 UTC (permalink / raw) To: Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches, 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.liu.zevorn@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 <tangtao1634@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 <alxndr@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.liu.zevorn@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.liu.zevorn@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 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library 2026-01-28 12:09 ` [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library Chao Liu @ 2026-01-28 14:30 ` Daniel Henrique Barboza 2026-01-28 15:23 ` Tao Tang 2026-01-29 1:59 ` Tao Tang 2 siblings, 0 replies; 13+ messages in thread From: Daniel Henrique Barboza @ 2026-01-28 14:30 UTC (permalink / raw) To: Chao Liu, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches 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.liu.zevorn@gmail.com> > --- Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.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 <tangtao1634@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 <alxndr@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.liu.zevorn@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.liu.zevorn@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 */ ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library 2026-01-28 12:09 ` [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library Chao Liu 2026-01-28 14:30 ` Daniel Henrique Barboza @ 2026-01-28 15:23 ` Tao Tang 2026-01-28 15:45 ` Chao Liu 2026-01-29 1:59 ` Tao Tang 2 siblings, 1 reply; 13+ messages in thread From: Tao Tang @ 2026-01-28 15:23 UTC (permalink / raw) To: Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches Hi Chao, On 2026/1/28 20:09, 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.liu.zevorn@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 <tangtao1634@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 <alxndr@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.liu.zevorn@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; > +} 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? > + > +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) 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>------------------------------ > 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.liu.zevorn@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 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 > + > +#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 */ ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library 2026-01-28 15:23 ` Tao Tang @ 2026-01-28 15:45 ` Chao Liu 0 siblings, 0 replies; 13+ messages in thread From: Chao Liu @ 2026-01-28 15:45 UTC (permalink / raw) To: Tao Tang, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches Hi Tao, Thanks for the review! On 1/28/2026 11:23 PM, Tao Tang wrote: > Hi Chao, > > On 2026/1/28 20:09, 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.liu.zevorn@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 <tangtao1634@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 <alxndr@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.liu.zevorn@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; >> +} > > > 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? > 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. >> + >> +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) > > > mode doesn't seem to be used? > You're right. The `mode` parameter is currently unused. I'll remove it in the next version. >> +{ >> + if (!is_leaf) { >> + return QRIOMMU_NON_LEAF_PTE_MASK; >> + } >> + >> + /* For leaf PTE, set RWX permissions */ >> + return QRIOMMU_LEAF_PTE_RW_MASK; >> +} >> + >> ------------------------------<snip>------------------------------ >> >> >> >> ------------------------------<snip>------------------------------ >> 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.liu.zevorn@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 > > > 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. > 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 > > Thanks, > > Tao > >> + >> +#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 */ > > ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library 2026-01-28 12:09 ` [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library Chao Liu 2026-01-28 14:30 ` Daniel Henrique Barboza 2026-01-28 15:23 ` Tao Tang @ 2026-01-29 1:59 ` Tao Tang 2 siblings, 0 replies; 13+ messages in thread From: Tao Tang @ 2026-01-29 1:59 UTC (permalink / raw) To: Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches Hi Chao, On 2026/1/28 20:09, 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.liu.zevorn@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 <tangtao1634@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 <alxndr@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.liu.zevorn@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; 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/875xb8l4ns.fsf@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. > + 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( Same style issue in this if block. Thanks, Tao > + 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); > + } > + } > +} ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test 2026-01-28 12:09 [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Chao Liu 2026-01-28 12:09 ` [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library Chao Liu @ 2026-01-28 12:09 ` Chao Liu 2026-01-28 14:30 ` Daniel Henrique Barboza 2026-01-28 15:36 ` Tao Tang 2026-01-28 14:42 ` [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Daniel Henrique Barboza 2026-01-28 14:54 ` Tao Tang 3 siblings, 2 replies; 13+ messages in thread From: Chao Liu @ 2026-01-28 12:09 UTC (permalink / raw) To: Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches, 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.liu.zevorn@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.muellner@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.liu.zevorn@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 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test 2026-01-28 12:09 ` [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test Chao Liu @ 2026-01-28 14:30 ` Daniel Henrique Barboza 2026-01-28 15:36 ` Tao Tang 1 sibling, 0 replies; 13+ messages in thread From: Daniel Henrique Barboza @ 2026-01-28 14:30 UTC (permalink / raw) To: Chao Liu, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches 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.liu.zevorn@gmail.com> > --- Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.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.muellner@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.liu.zevorn@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( ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test 2026-01-28 12:09 ` [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test Chao Liu 2026-01-28 14:30 ` Daniel Henrique Barboza @ 2026-01-28 15:36 ` Tao Tang 2026-01-28 16:17 ` Chao Liu 1 sibling, 1 reply; 13+ messages in thread From: Tao Tang @ 2026-01-28 15:36 UTC (permalink / raw) To: Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches Hi Chao, On 2026/1/28 20:09, 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.liu.zevorn@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.muellner@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.liu.zevorn@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); 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 > + > + 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( ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test 2026-01-28 15:36 ` Tao Tang @ 2026-01-28 16:17 ` Chao Liu 0 siblings, 0 replies; 13+ messages in thread From: Chao Liu @ 2026-01-28 16:17 UTC (permalink / raw) To: Tao Tang, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches Hi Tao, On 1/28/2026 11:36 PM, Tao Tang wrote: > Hi Chao, > > On 2026/1/28 20:09, 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.liu.zevorn@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.muellner@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.liu.zevorn@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); > > > 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 > >> + >> + 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( > ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev 2026-01-28 12:09 [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Chao Liu 2026-01-28 12:09 ` [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library Chao Liu 2026-01-28 12:09 ` [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test Chao Liu @ 2026-01-28 14:42 ` Daniel Henrique Barboza 2026-01-28 16:25 ` Chao Liu 2026-01-28 14:54 ` Tao Tang 3 siblings, 1 reply; 13+ messages in thread From: Daniel Henrique Barboza @ 2026-01-28 14:42 UTC (permalink / raw) To: Chao Liu, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches On 1/28/2026 9:09 AM, 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. > > 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 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 > > 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 > ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev 2026-01-28 14:42 ` [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Daniel Henrique Barboza @ 2026-01-28 16:25 ` Chao Liu 0 siblings, 0 replies; 13+ messages in thread From: Chao Liu @ 2026-01-28 16:25 UTC (permalink / raw) To: Daniel Henrique Barboza, Alistair Francis, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini, Tao Tang Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches Hi Daniel, Thanks for the review and the kind words! On 1/28/2026 10:42 PM, Daniel Henrique Barboza wrote: > > > On 1/28/2026 9:09 AM, 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. >> >> 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 > > 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. > 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 > Thanks, > Daniel > >> >> 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 >> > ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev 2026-01-28 12:09 [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Chao Liu ` (2 preceding siblings ...) 2026-01-28 14:42 ` [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Daniel Henrique Barboza @ 2026-01-28 14:54 ` Tao Tang 3 siblings, 0 replies; 13+ messages in thread From: Tao Tang @ 2026-01-28 14:54 UTC (permalink / raw) To: Chao Liu, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Fabiano Rosas, Laurent Vivier, Paolo Bonzini Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches 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 > > 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 ^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-01-29 2:01 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-01-28 12:09 [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Chao Liu 2026-01-28 12:09 ` [RFC PATCH v1 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library Chao Liu 2026-01-28 14:30 ` Daniel Henrique Barboza 2026-01-28 15:23 ` Tao Tang 2026-01-28 15:45 ` Chao Liu 2026-01-29 1:59 ` Tao Tang 2026-01-28 12:09 ` [RFC PATCH v1 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test Chao Liu 2026-01-28 14:30 ` Daniel Henrique Barboza 2026-01-28 15:36 ` Tao Tang 2026-01-28 16:17 ` Chao Liu 2026-01-28 14:42 ` [RFC PATCH v1 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Daniel Henrique Barboza 2026-01-28 16:25 ` Chao Liu 2026-01-28 14:54 ` Tao Tang
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.