* [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev
@ 2026-02-03 14:27 chao.liu.zevorn
2026-02-03 14:27 ` [PATCH v3 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library chao.liu.zevorn
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: chao.liu.zevorn @ 2026-02-03 14:27 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
From: Chao Liu <chao.liu.zevorn@gmail.com>
Hi,
This patch series adds a bare-metal qtest for the RISC-V IOMMU using the
iommu-testdev framework. The test exercises address translation paths
without requiring a full guest OS boot.
Motivation
----------
The existing RISC-V IOMMU qtest (riscv-iommu-test.c) focuses on PCI device
enumeration and register-level validation:
- PCI configuration space verification (vendor/device ID)
- Register reset value checks
- Queue initialization procedures (CQ/FQ/PQ)
However, it does not test the actual address translation functionality.
This new test fills that gap by using iommu-testdev to trigger DMA
transactions and validate the IOMMU's translation logic.
Comparison with Existing Test
-----------------------------
| Feature | riscv-iommu-test.c | iommu-riscv-test.c (new) |
|-----------------------|--------------------|--------------------------|
| PCI config | Yes | No |
| Register reset | Yes | No |
| Queue init | Yes | Yes (via helper) |
| Bare translation | No | Yes |
| S-stage (SV39) | No | Yes |
| G-stage (SV39x4) | No | Yes |
| Nested translation | No | Yes |
| DMA verification | No | Yes |
| Uses iommu-testdev | No | Yes |
The new test provides:
- Device context (DC) configuration and validation
- SV39 page table walks for S-stage translation
- SV39x4 page table walks for G-stage translation
- Nested translation combining both stages
- FCTL register constraint validation
- End-to-end DMA verification
Note: The current implementation only supports SV39/SV39x4. Support for
SV48/SV48x4/SV57/SV57x4 can be added in future patches.
Testing
-------
QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \
./build/tests/qtest/iommu-riscv-test --tap -k
Changes v2 -> v3
----------------
- Removed duplicate header includes in both patches (Tao)
- Fixed memory leak of state->iommu_dev and state->testdev in
riscv_iommu_test_setup() in patch 2 (Fabiano)
Changes v1 -> v2
----------------
- Removed unused 'mode' parameter from qriommu_get_pte_attrs() function
- Simplified PTE mask definitions in header file by using direct hex
values instead of individual bit defines (removed QRIOMMU_PTE_V/R/W/X
/U/G/A/D macros), added comment referencing target/riscv/cpu_bits.h
- Cleaned up variable declarations in qriommu_setup_translation_tables()
to follow C99 style (declare at point of use)
- Minor code style improvements
Thanks,
Chao
Chao Liu (2):
tests/qtest/libqos: Add RISC-V IOMMU helper library
tests/qtest: Add RISC-V IOMMU bare-metal test
MAINTAINERS | 2 +
tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++
tests/qtest/libqos/meson.build | 2 +-
tests/qtest/libqos/qos-riscv-iommu.c | 403 +++++++++++++++++++++++++++
tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++
tests/qtest/meson.build | 5 +-
6 files changed, 853 insertions(+), 2 deletions(-)
create mode 100644 tests/qtest/iommu-riscv-test.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c
create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v3 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library 2026-02-03 14:27 [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev chao.liu.zevorn @ 2026-02-03 14:27 ` chao.liu.zevorn 2026-02-06 17:48 ` Daniel Henrique Barboza 2026-02-03 14:27 ` [PATCH v3 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test chao.liu.zevorn 2026-02-06 19:53 ` [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Fabiano Rosas 2 siblings, 1 reply; 6+ messages in thread From: chao.liu.zevorn @ 2026-02-03 14:27 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 From: Chao Liu <chao.liu.zevorn@gmail.com> Introduce a libqos helper module for RISC-V IOMMU testing with iommu-testdev. The helper provides routines to: - Build device contexts (DC) and 3-level page tables for SV39/SV39x4 - Program command queue (CQ), fault queue (FQ), and DDTP registers following the RISC-V IOMMU specification - Execute DMA translations and verify results The current implementation supports SV39 for S-stage and SV39x4 for G-stage translation. Support for SV48/SV48x4/SV57/SV57x4 can be added in future patches. Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com> Reviewed-by: Tao Tang <tangtao1634@phytium.com.cn> Reviewed-by: Fabiano Rosas <farosas@suse.de> --- MAINTAINERS | 1 + tests/qtest/libqos/meson.build | 2 +- tests/qtest/libqos/qos-riscv-iommu.c | 403 +++++++++++++++++++++++++++ tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++ 4 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h diff --git a/MAINTAINERS b/MAINTAINERS index dccdf47888..830f56376b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3584,6 +3584,7 @@ M: Tao Tang <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..295583f536 --- /dev/null +++ b/tests/qtest/libqos/qos-riscv-iommu.c @@ -0,0 +1,403 @@ +/* + * QOS RISC-V IOMMU Module + * + * This module provides RISC-V IOMMU-specific helper functions for libqos tests, + * encapsulating RISC-V IOMMU setup, and assertions. + * + * Copyright (c) 2026 Chao Liu <chao.liu.zevorn@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/riscv/riscv-iommu-bits.h" +#include "qos-iommu-testdev.h" +#include "qos-riscv-iommu.h" + +/* 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 = 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(bool is_leaf) +{ + if (!is_leaf) { + return QRIOMMU_NON_LEAF_PTE_MASK; + } + + /* For leaf PTE, set RWX permissions */ + return QRIOMMU_LEAF_PTE_RW_MASK; +} + +void qriommu_setup_translation_tables(QTestState *qts, + uint64_t iova, + QRIOMMUTransMode mode) +{ + uint64_t s_root = 0, s_l0_pte_val = 0, s_l1_pte_val = 0; + uint64_t s_l0_addr = 0, s_l1_addr = 0, s_l2_addr = 0, s_l2_pte_val = 0; + uint64_t s_l0_pa = 0, s_l1_pa = 0; + uint64_t s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); + uint64_t s_l0_pa_real = 0, s_l1_pa_real = 0; + uint64_t s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); + uint64_t non_leaf_attrs = qriommu_get_pte_attrs(false); + uint64_t leaf_attrs = qriommu_get_pte_attrs(true); + + if (mode != QRIOMMU_TM_G_STAGE_ONLY) { + /* Setup S-stage 3-level page tables (SV39) */ + s_l0_pa = qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL); + s_l1_pa = qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL); + s_root = qriommu_apply_space_offs( + QRIOMMU_IOHGATP & QRIOMMU_PTE_PPN_MASK); + s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); + + s_l0_pa_real = s_l0_pa; + s_l1_pa_real = s_l1_pa; + s_l2_pa_real = s_l2_pa; + + if (mode == QRIOMMU_TM_NESTED) { + s_l0_pa = QRIOMMU_L0_PTE_VAL; + s_l1_pa = QRIOMMU_L1_PTE_VAL; + s_l2_pa = QRIOMMU_L2_PTE_VAL; + + s_l0_pa_real = qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL); + s_l1_pa_real = qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL); + s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); + } + + s_l0_pte_val = qriommu_encode_pte(s_l0_pa, non_leaf_attrs); + s_l1_pte_val = qriommu_encode_pte(s_l1_pa, non_leaf_attrs); + + s_l0_addr = qriommu_get_table_addr(s_root, 0, iova); + qtest_writeq(qts, s_l0_addr, s_l0_pte_val); + + s_l1_addr = qriommu_get_table_addr(s_l0_pa_real, 1, iova); + qtest_writeq(qts, s_l1_addr, s_l1_pte_val); + + s_l2_addr = qriommu_get_table_addr(s_l1_pa_real, 2, iova); + s_l2_pte_val = qriommu_encode_pte(s_l2_pa, leaf_attrs); + qtest_writeq(qts, s_l2_addr, s_l2_pte_val); + } + + if (mode == QRIOMMU_TM_G_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) { + uint64_t g_root; + uint64_t g_l0_pa; + uint64_t g_l1_pa; + uint64_t g_l0_pte_val; + uint64_t g_l1_pte_val; + + g_root = qriommu_apply_space_offs( + QRIOMMU_G_IOHGATP & QRIOMMU_PTE_PPN_MASK); + g_l0_pa = qriommu_apply_space_offs(QRIOMMU_G_L0_PTE_VAL); + g_l1_pa = qriommu_apply_space_offs(QRIOMMU_G_L1_PTE_VAL); + g_l0_pte_val = qriommu_encode_pte(g_l0_pa, non_leaf_attrs); + g_l1_pte_val = qriommu_encode_pte(g_l1_pa, non_leaf_attrs); + + 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..90e69a5d73 --- /dev/null +++ b/tests/qtest/libqos/qos-riscv-iommu.h @@ -0,0 +1,164 @@ +/* + * QOS RISC-V IOMMU Module + * + * This module provides RISC-V IOMMU-specific helper functions for libqos tests, + * encapsulating RISC-V IOMMU setup, and assertions. + * + * Copyright (c) 2026 Chao Liu <chao.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 + +/* + * PTE masks for RISC-V IOMMU page tables. + * Values match PTE_V, PTE_R, PTE_W, PTE_A, PTE_D in target/riscv/cpu_bits.h + */ +#define QRIOMMU_NON_LEAF_PTE_MASK 0x001 /* PTE_V */ +#define QRIOMMU_LEAF_PTE_RW_MASK 0x0c7 /* V|R|W|A|D */ +#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.53.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library 2026-02-03 14:27 ` [PATCH v3 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library chao.liu.zevorn @ 2026-02-06 17:48 ` Daniel Henrique Barboza 0 siblings, 0 replies; 6+ messages in thread From: Daniel Henrique Barboza @ 2026-02-06 17:48 UTC (permalink / raw) To: chao.liu.zevorn, 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 2/3/2026 11:27 AM, chao.liu.zevorn@gmail.com wrote: > From: Chao Liu <chao.liu.zevorn@gmail.com> > > Introduce a libqos helper module for RISC-V IOMMU testing with > iommu-testdev. The helper provides routines to: > > - Build device contexts (DC) and 3-level page tables for SV39/SV39x4 > - Program command queue (CQ), fault queue (FQ), and DDTP registers > following the RISC-V IOMMU specification > - Execute DMA translations and verify results > > The current implementation supports SV39 for S-stage and SV39x4 for > G-stage translation. Support for SV48/SV48x4/SV57/SV57x4 can be added > in future patches. > > Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com> > Reviewed-by: Tao Tang <tangtao1634@phytium.com.cn> > Reviewed-by: Fabiano Rosas <farosas@suse.de> > --- 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 | 403 +++++++++++++++++++++++++++ > tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++ > 4 files changed, 569 insertions(+), 1 deletion(-) > create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c > create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index dccdf47888..830f56376b 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3584,6 +3584,7 @@ M: Tao Tang <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..295583f536 > --- /dev/null > +++ b/tests/qtest/libqos/qos-riscv-iommu.c > @@ -0,0 +1,403 @@ > +/* > + * QOS RISC-V IOMMU Module > + * > + * This module provides RISC-V IOMMU-specific helper functions for libqos tests, > + * encapsulating RISC-V IOMMU setup, and assertions. > + * > + * Copyright (c) 2026 Chao Liu <chao.liu.zevorn@gmail.com> > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#include "qemu/osdep.h" > +#include "hw/riscv/riscv-iommu-bits.h" > +#include "qos-iommu-testdev.h" > +#include "qos-riscv-iommu.h" > + > +/* 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 = 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(bool is_leaf) > +{ > + if (!is_leaf) { > + return QRIOMMU_NON_LEAF_PTE_MASK; > + } > + > + /* For leaf PTE, set RWX permissions */ > + return QRIOMMU_LEAF_PTE_RW_MASK; > +} > + > +void qriommu_setup_translation_tables(QTestState *qts, > + uint64_t iova, > + QRIOMMUTransMode mode) > +{ > + uint64_t s_root = 0, s_l0_pte_val = 0, s_l1_pte_val = 0; > + uint64_t s_l0_addr = 0, s_l1_addr = 0, s_l2_addr = 0, s_l2_pte_val = 0; > + uint64_t s_l0_pa = 0, s_l1_pa = 0; > + uint64_t s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); > + uint64_t s_l0_pa_real = 0, s_l1_pa_real = 0; > + uint64_t s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); > + uint64_t non_leaf_attrs = qriommu_get_pte_attrs(false); > + uint64_t leaf_attrs = qriommu_get_pte_attrs(true); > + > + if (mode != QRIOMMU_TM_G_STAGE_ONLY) { > + /* Setup S-stage 3-level page tables (SV39) */ > + s_l0_pa = qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL); > + s_l1_pa = qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL); > + s_root = qriommu_apply_space_offs( > + QRIOMMU_IOHGATP & QRIOMMU_PTE_PPN_MASK); > + s_l2_pa = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); > + > + s_l0_pa_real = s_l0_pa; > + s_l1_pa_real = s_l1_pa; > + s_l2_pa_real = s_l2_pa; > + > + if (mode == QRIOMMU_TM_NESTED) { > + s_l0_pa = QRIOMMU_L0_PTE_VAL; > + s_l1_pa = QRIOMMU_L1_PTE_VAL; > + s_l2_pa = QRIOMMU_L2_PTE_VAL; > + > + s_l0_pa_real = qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL); > + s_l1_pa_real = qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL); > + s_l2_pa_real = qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); > + } > + > + s_l0_pte_val = qriommu_encode_pte(s_l0_pa, non_leaf_attrs); > + s_l1_pte_val = qriommu_encode_pte(s_l1_pa, non_leaf_attrs); > + > + s_l0_addr = qriommu_get_table_addr(s_root, 0, iova); > + qtest_writeq(qts, s_l0_addr, s_l0_pte_val); > + > + s_l1_addr = qriommu_get_table_addr(s_l0_pa_real, 1, iova); > + qtest_writeq(qts, s_l1_addr, s_l1_pte_val); > + > + s_l2_addr = qriommu_get_table_addr(s_l1_pa_real, 2, iova); > + s_l2_pte_val = qriommu_encode_pte(s_l2_pa, leaf_attrs); > + qtest_writeq(qts, s_l2_addr, s_l2_pte_val); > + } > + > + if (mode == QRIOMMU_TM_G_STAGE_ONLY || mode == QRIOMMU_TM_NESTED) { > + uint64_t g_root; > + uint64_t g_l0_pa; > + uint64_t g_l1_pa; > + uint64_t g_l0_pte_val; > + uint64_t g_l1_pte_val; > + > + g_root = qriommu_apply_space_offs( > + QRIOMMU_G_IOHGATP & QRIOMMU_PTE_PPN_MASK); > + g_l0_pa = qriommu_apply_space_offs(QRIOMMU_G_L0_PTE_VAL); > + g_l1_pa = qriommu_apply_space_offs(QRIOMMU_G_L1_PTE_VAL); > + g_l0_pte_val = qriommu_encode_pte(g_l0_pa, non_leaf_attrs); > + g_l1_pte_val = qriommu_encode_pte(g_l1_pa, non_leaf_attrs); > + > + 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..90e69a5d73 > --- /dev/null > +++ b/tests/qtest/libqos/qos-riscv-iommu.h > @@ -0,0 +1,164 @@ > +/* > + * QOS RISC-V IOMMU Module > + * > + * This module provides RISC-V IOMMU-specific helper functions for libqos tests, > + * encapsulating RISC-V IOMMU setup, and assertions. > + * > + * Copyright (c) 2026 Chao Liu <chao.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 > + > +/* > + * PTE masks for RISC-V IOMMU page tables. > + * Values match PTE_V, PTE_R, PTE_W, PTE_A, PTE_D in target/riscv/cpu_bits.h > + */ > +#define QRIOMMU_NON_LEAF_PTE_MASK 0x001 /* PTE_V */ > +#define QRIOMMU_LEAF_PTE_RW_MASK 0x0c7 /* V|R|W|A|D */ > +#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] 6+ messages in thread
* [PATCH v3 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test 2026-02-03 14:27 [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev chao.liu.zevorn 2026-02-03 14:27 ` [PATCH v3 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library chao.liu.zevorn @ 2026-02-03 14:27 ` chao.liu.zevorn 2026-02-06 17:48 ` Daniel Henrique Barboza 2026-02-06 19:53 ` [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Fabiano Rosas 2 siblings, 1 reply; 6+ messages in thread From: chao.liu.zevorn @ 2026-02-03 14:27 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 From: Chao Liu <chao.liu.zevorn@gmail.com> Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine. The test exercises bare, S-stage, G-stage, and nested translation paths using iommu-testdev and the qos-riscv-iommu helpers. The test validates: - Device context (DC) configuration - SV39 page table walks for S-stage translation - SV39x4 page table walks for G-stage translation - Nested translation combining both stages - FCTL register constraints This provides regression coverage for the RISC-V IOMMU implementation without requiring a full guest OS boot. Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com> Reviewed-by: Tao Tang <tangtao1634@phytium.com.cn> Reviewed-by: Fabiano Rosas <farosas@suse.de> --- MAINTAINERS | 1 + tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 5 +- 3 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/iommu-riscv-test.c diff --git a/MAINTAINERS b/MAINTAINERS index 830f56376b..73daaad841 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -349,6 +349,7 @@ F: common-user/host/riscv* F: tests/functional/riscv32 F: tests/functional/riscv64 F: tests/tcg/riscv64/ +F: tests/qtest/iommu-riscv-test.c RISC-V XThead* extensions M: Christoph Muellner <christoph.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..2638024891 --- /dev/null +++ b/tests/qtest/iommu-riscv-test.c @@ -0,0 +1,279 @@ +/* + * QTest testcase for RISC-V IOMMU with iommu-testdev + * + * This QTest file is used to test the RISC-V IOMMU with iommu-testdev so that + * we can test RISC-V IOMMU without any guest kernel or firmware. + * + * Copyright (c) 2026 Chao Liu <chao.liu.zevorn@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqos/generic-pcihost.h" +#include "hw/pci/pci_regs.h" +#include "hw/misc/iommu-testdev.h" +#include "hw/riscv/riscv-iommu-bits.h" +#include "libqos/qos-riscv-iommu.h" +#include "libqos/riscv-iommu.h" + +#define DMA_LEN 4 + +/* 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%" PRIx64, *iommu_base); + + return iommu_dev; +} + +static QPCIDevice *find_iommu_testdev(QGenericPCIBus *gbus, QPCIBar *bar) +{ + QPCIDevice *dev = NULL; + + g_test_message("Searching for iommu-testdev (vendor=0x%04x, device=0x%04x)", + IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID); + + qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID, + IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev); + g_assert(dev); + + qpci_device_enable(dev); + *bar = qpci_iomap(dev, 0, NULL); + g_assert_false(bar->is_io); + + return dev; +} + +static bool riscv_iommu_test_setup(RiscvIommuTestState *state) +{ + if (!qtest_has_machine("virt")) { + g_test_skip("virt machine not available"); + return false; + } + + state->qts = qtest_init("-machine virt,acpi=off " + "-cpu max -smp 1 -m 512 -net none " + "-device riscv-iommu-pci " + "-device iommu-testdev"); + + qpci_init_generic(&state->gbus, state->qts, NULL, false); + riscv_config_qpci_bus(&state->gbus); + + state->iommu_dev = find_riscv_iommu_pci(&state->gbus, &state->iommu_base); + g_assert(state->iommu_dev); + + state->testdev = find_iommu_testdev(&state->gbus, &state->testdev_bar); + g_assert(state->testdev); + + return true; +} + +static void riscv_iommu_test_teardown(RiscvIommuTestState *state) +{ + g_free(state->iommu_dev); + g_free(state->testdev); + qtest_quit(state->qts); +} + +static uint64_t riscv_iommu_check(QTestState *qts, uint64_t iommu_base, + QRIOMMUTransMode mode) +{ + 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 dfb83650c6..25fdbc7980 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -287,7 +287,10 @@ qtests_riscv32 = \ (config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : []) qtests_riscv64 = ['riscv-csr-test'] + \ - (unpack_edk2_blobs ? ['bios-tables-test'] : []) + (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ + (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and + config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? + ['iommu-riscv-test'] : []) qos_test_ss = ss.source_set() qos_test_ss.add( -- 2.53.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test 2026-02-03 14:27 ` [PATCH v3 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test chao.liu.zevorn @ 2026-02-06 17:48 ` Daniel Henrique Barboza 0 siblings, 0 replies; 6+ messages in thread From: Daniel Henrique Barboza @ 2026-02-06 17:48 UTC (permalink / raw) To: chao.liu.zevorn, 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 2/3/2026 11:27 AM, chao.liu.zevorn@gmail.com wrote: > From: Chao Liu <chao.liu.zevorn@gmail.com> > > Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine. > The test exercises bare, S-stage, G-stage, and nested translation paths > using iommu-testdev and the qos-riscv-iommu helpers. > > The test validates: > - Device context (DC) configuration > - SV39 page table walks for S-stage translation > - SV39x4 page table walks for G-stage translation > - Nested translation combining both stages > - FCTL register constraints > > This provides regression coverage for the RISC-V IOMMU implementation > without requiring a full guest OS boot. > > Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com> > Reviewed-by: Tao Tang <tangtao1634@phytium.com.cn> > Reviewed-by: Fabiano Rosas <farosas@suse.de> > --- 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 830f56376b..73daaad841 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -349,6 +349,7 @@ F: common-user/host/riscv* > F: tests/functional/riscv32 > F: tests/functional/riscv64 > F: tests/tcg/riscv64/ > +F: tests/qtest/iommu-riscv-test.c > > RISC-V XThead* extensions > M: Christoph Muellner <christoph.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..2638024891 > --- /dev/null > +++ b/tests/qtest/iommu-riscv-test.c > @@ -0,0 +1,279 @@ > +/* > + * QTest testcase for RISC-V IOMMU with iommu-testdev > + * > + * This QTest file is used to test the RISC-V IOMMU with iommu-testdev so that > + * we can test RISC-V IOMMU without any guest kernel or firmware. > + * > + * Copyright (c) 2026 Chao Liu <chao.liu.zevorn@gmail.com> > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#include "qemu/osdep.h" > +#include "libqos/generic-pcihost.h" > +#include "hw/pci/pci_regs.h" > +#include "hw/misc/iommu-testdev.h" > +#include "hw/riscv/riscv-iommu-bits.h" > +#include "libqos/qos-riscv-iommu.h" > +#include "libqos/riscv-iommu.h" > + > +#define DMA_LEN 4 > + > +/* 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%" PRIx64, *iommu_base); > + > + return iommu_dev; > +} > + > +static QPCIDevice *find_iommu_testdev(QGenericPCIBus *gbus, QPCIBar *bar) > +{ > + QPCIDevice *dev = NULL; > + > + g_test_message("Searching for iommu-testdev (vendor=0x%04x, device=0x%04x)", > + IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID); > + > + qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID, > + IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev); > + g_assert(dev); > + > + qpci_device_enable(dev); > + *bar = qpci_iomap(dev, 0, NULL); > + g_assert_false(bar->is_io); > + > + return dev; > +} > + > +static bool riscv_iommu_test_setup(RiscvIommuTestState *state) > +{ > + if (!qtest_has_machine("virt")) { > + g_test_skip("virt machine not available"); > + return false; > + } > + > + state->qts = qtest_init("-machine virt,acpi=off " > + "-cpu max -smp 1 -m 512 -net none " > + "-device riscv-iommu-pci " > + "-device iommu-testdev"); > + > + qpci_init_generic(&state->gbus, state->qts, NULL, false); > + riscv_config_qpci_bus(&state->gbus); > + > + state->iommu_dev = find_riscv_iommu_pci(&state->gbus, &state->iommu_base); > + g_assert(state->iommu_dev); > + > + state->testdev = find_iommu_testdev(&state->gbus, &state->testdev_bar); > + g_assert(state->testdev); > + > + return true; > +} > + > +static void riscv_iommu_test_teardown(RiscvIommuTestState *state) > +{ > + g_free(state->iommu_dev); > + g_free(state->testdev); > + qtest_quit(state->qts); > +} > + > +static uint64_t riscv_iommu_check(QTestState *qts, uint64_t iommu_base, > + QRIOMMUTransMode mode) > +{ > + 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 dfb83650c6..25fdbc7980 100644 > --- a/tests/qtest/meson.build > +++ b/tests/qtest/meson.build > @@ -287,7 +287,10 @@ qtests_riscv32 = \ > (config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : []) > > qtests_riscv64 = ['riscv-csr-test'] + \ > - (unpack_edk2_blobs ? ['bios-tables-test'] : []) > + (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ > + (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and > + config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? > + ['iommu-riscv-test'] : []) > > qos_test_ss = ss.source_set() > qos_test_ss.add( ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev 2026-02-03 14:27 [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev chao.liu.zevorn 2026-02-03 14:27 ` [PATCH v3 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library chao.liu.zevorn 2026-02-03 14:27 ` [PATCH v3 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test chao.liu.zevorn @ 2026-02-06 19:53 ` Fabiano Rosas 2 siblings, 0 replies; 6+ messages in thread From: Fabiano Rosas @ 2026-02-06 19:53 UTC (permalink / raw) To: chao.liu.zevorn, Alistair Francis, Daniel Henrique Barboza, Palmer Dabbelt, Weiwei Li, Liu Zhiwei, Laurent Vivier, Paolo Bonzini, Tao Tang Cc: qemu-devel, qemu-riscv, hust-os-kernel-patches, Chao Liu chao.liu.zevorn@gmail.com writes: > From: Chao Liu <chao.liu.zevorn@gmail.com> > > Hi, > > This patch series adds a bare-metal qtest for the RISC-V IOMMU using the > iommu-testdev framework. The test exercises address translation paths > without requiring a full guest OS boot. > > Motivation > ---------- > > The existing RISC-V IOMMU qtest (riscv-iommu-test.c) focuses on PCI device > enumeration and register-level validation: > - PCI configuration space verification (vendor/device ID) > - Register reset value checks > - Queue initialization procedures (CQ/FQ/PQ) > > However, it does not test the actual address translation functionality. > This new test fills that gap by using iommu-testdev to trigger DMA > transactions and validate the IOMMU's translation logic. > > Comparison with Existing Test > ----------------------------- > > | Feature | riscv-iommu-test.c | iommu-riscv-test.c (new) | > |-----------------------|--------------------|--------------------------| > | PCI config | Yes | No | > | Register reset | Yes | No | > | Queue init | Yes | Yes (via helper) | > | Bare translation | No | Yes | > | S-stage (SV39) | No | Yes | > | G-stage (SV39x4) | No | Yes | > | Nested translation | No | Yes | > | DMA verification | No | Yes | > | Uses iommu-testdev | No | Yes | > > The new test provides: > - Device context (DC) configuration and validation > - SV39 page table walks for S-stage translation > - SV39x4 page table walks for G-stage translation > - Nested translation combining both stages > - FCTL register constraint validation > - End-to-end DMA verification > > Note: The current implementation only supports SV39/SV39x4. Support for > SV48/SV48x4/SV57/SV57x4 can be added in future patches. > > Testing > ------- > > QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \ > ./build/tests/qtest/iommu-riscv-test --tap -k > > Changes v2 -> v3 > ---------------- > - Removed duplicate header includes in both patches (Tao) > - Fixed memory leak of state->iommu_dev and state->testdev in > riscv_iommu_test_setup() in patch 2 (Fabiano) > > Changes v1 -> v2 > ---------------- > - Removed unused 'mode' parameter from qriommu_get_pte_attrs() function > - Simplified PTE mask definitions in header file by using direct hex > values instead of individual bit defines (removed QRIOMMU_PTE_V/R/W/X > /U/G/A/D macros), added comment referencing target/riscv/cpu_bits.h > - Cleaned up variable declarations in qriommu_setup_translation_tables() > to follow C99 style (declare at point of use) > - Minor code style improvements > > Thanks, > Chao > > Chao Liu (2): > tests/qtest/libqos: Add RISC-V IOMMU helper library > tests/qtest: Add RISC-V IOMMU bare-metal test > > MAINTAINERS | 2 + > tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++ > tests/qtest/libqos/meson.build | 2 +- > tests/qtest/libqos/qos-riscv-iommu.c | 403 +++++++++++++++++++++++++++ > tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++ > tests/qtest/meson.build | 5 +- > 6 files changed, 853 insertions(+), 2 deletions(-) > create mode 100644 tests/qtest/iommu-riscv-test.c > create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c > create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h I'm queuing this, but if riscv folks want to pull it, it's fine as well. ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-02-06 19:53 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-02-03 14:27 [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev chao.liu.zevorn 2026-02-03 14:27 ` [PATCH v3 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library chao.liu.zevorn 2026-02-06 17:48 ` Daniel Henrique Barboza 2026-02-03 14:27 ` [PATCH v3 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test chao.liu.zevorn 2026-02-06 17:48 ` Daniel Henrique Barboza 2026-02-06 19:53 ` [PATCH v3 0/2] tests/qtest: Add RISC-V IOMMU bare-metal test using iommu-testdev Fabiano Rosas
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.