hw/riscv/riscv-iommu.c | 14 ++- tests/qtest/iommu-riscv-test.c | 193 +++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 1 deletion(-)
The debug translation response aligns the translated physical address to
the effective translation range, but always reports S=0. For a request
inside a superpage, the response therefore identifies the wrong 4 KiB
physical page.
For effective ranges larger than 4 KiB, set S and encode the range size
using trailing ones in the PPN field. Keep the existing response format
for 4 KiB translations.
The RISC-V IOMMU specification permits reporting a 4 KiB translation even
when the page tables use superpages. Reporting the effective range is an
implementation choice; this change does not claim that S=1 is mandatory
for every translation through a superpage mapping.
Add debug-response tests for S-stage-only 4 KiB, 64 KiB NAPOT, and 2 MiB
translations, G-stage-only 2 MiB translations, and nested S4K/G2M,
S2M/G4K, and S2M/G2M mappings. Check requests inside large ranges and at
both ends. The mixed nested cases verify that the response uses the
smaller effective translation granularity.
Fixes: a7aa525b93c3 ("hw/riscv/riscv-iommu: add DBG support")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3552
Signed-off-by: Zephyr Li <fritchleybohrer@gmail.com>
---
hw/riscv/riscv-iommu.c | 14 ++-
tests/qtest/iommu-riscv-test.c | 193 +++++++++++++++++++++++++++++++++
2 files changed, 206 insertions(+), 1 deletion(-)
diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
index 323a041b4a558136819b1cd41452e96481ba164a..67d8456b9d3ed34308ca794e8d700664ebacce0f 100644
--- a/hw/riscv/riscv-iommu.c
+++ b/hw/riscv/riscv-iommu.c
@@ -2281,8 +2281,20 @@ static void riscv_iommu_process_dbg(RISCVIOMMUState *s)
if (fault) {
iova = RISCV_IOMMU_TR_RESPONSE_FAULT | (((uint64_t) fault) << 10);
} else {
+ uint64_t ppn;
+
iova = iotlb.translated_addr & ~iotlb.addr_mask;
- iova = set_field(0, RISCV_IOMMU_TR_RESPONSE_PPN, PPN_DOWN(iova));
+ ppn = PPN_DOWN(iova);
+
+ if (iotlb.addr_mask > TARGET_PAGE_SIZE - 1) {
+ /* Encode the translation range as trailing ones in the PPN. */
+ ppn |= PPN_DOWN(iotlb.addr_mask) >> 1;
+ iova = RISCV_IOMMU_TR_RESPONSE_S;
+ } else {
+ iova = 0;
+ }
+
+ iova = set_field(iova, RISCV_IOMMU_TR_RESPONSE_PPN, ppn);
}
riscv_iommu_reg_set64(s, RISCV_IOMMU_REG_TR_RESPONSE, iova);
}
diff --git a/tests/qtest/iommu-riscv-test.c b/tests/qtest/iommu-riscv-test.c
index 26380248912cc281e313e20a3daf1831be1eded3..1cd753c4f243f207dc34b7acbe41ef0d55772cba 100644
--- a/tests/qtest/iommu-riscv-test.c
+++ b/tests/qtest/iommu-riscv-test.c
@@ -19,6 +19,12 @@
#define DMA_LEN 4
+#define DBG_PAGE_SIZE 0x1000ull
+#define DBG_NAPOT_SIZE 0x10000ull
+#define DBG_SUPERPAGE_SIZE 0x200000ull
+#define DBG_TRANSLATED_PA 0x90000000ull
+#define DBG_NESTED_GPA 0x200000ull
+
/* RISC-V virt machine PCI configuration */
#define RISCV_GPEX_PIO_BASE 0x3000000
#define RISCV_BUS_PIO_LIMIT 0x10000
@@ -35,6 +41,64 @@ typedef struct RiscvIommuTestState {
uint64_t iommu_base;
} RiscvIommuTestState;
+typedef struct RiscvIommuDbgTest {
+ const char *name;
+ QRIOMMUTransMode mode;
+ uint64_t s_page_size;
+ uint64_t g_page_size;
+ uint64_t pa;
+ uint64_t expected_response;
+} RiscvIommuDbgTest;
+
+static const RiscvIommuDbgTest debug_tests[] = {
+ {
+ .name = "page-4k",
+ .mode = QRIOMMU_TM_S_STAGE_ONLY,
+ .s_page_size = DBG_PAGE_SIZE,
+ .pa = DBG_TRANSLATED_PA + DBG_PAGE_SIZE,
+ .expected_response = 0x24000400ull,
+ }, {
+ .name = "napot-64k",
+ .mode = QRIOMMU_TM_S_STAGE_ONLY,
+ .s_page_size = DBG_NAPOT_SIZE,
+ .pa = DBG_TRANSLATED_PA,
+ .expected_response = 0x24001e00ull,
+ }, {
+ .name = "superpage",
+ .mode = QRIOMMU_TM_S_STAGE_ONLY,
+ .s_page_size = DBG_SUPERPAGE_SIZE,
+ .pa = DBG_TRANSLATED_PA,
+ .expected_response = 0x2403fe00ull,
+ }, {
+ .name = "g-stage-superpage",
+ .mode = QRIOMMU_TM_G_STAGE_ONLY,
+ .g_page_size = DBG_SUPERPAGE_SIZE,
+ .pa = DBG_TRANSLATED_PA,
+ .expected_response = 0x2403fe00ull,
+ }, {
+ .name = "nested-s4k-g2m",
+ .mode = QRIOMMU_TM_NESTED,
+ .s_page_size = DBG_PAGE_SIZE,
+ .g_page_size = DBG_SUPERPAGE_SIZE,
+ .pa = DBG_TRANSLATED_PA,
+ .expected_response = 0x24001000ull,
+ }, {
+ .name = "nested-s2m-g4k",
+ .mode = QRIOMMU_TM_NESTED,
+ .s_page_size = DBG_SUPERPAGE_SIZE,
+ .g_page_size = DBG_PAGE_SIZE,
+ .pa = DBG_TRANSLATED_PA,
+ .expected_response = 0x24001000ull,
+ }, {
+ .name = "nested-s2m-g2m",
+ .mode = QRIOMMU_TM_NESTED,
+ .s_page_size = DBG_SUPERPAGE_SIZE,
+ .g_page_size = DBG_SUPERPAGE_SIZE,
+ .pa = DBG_TRANSLATED_PA,
+ .expected_response = 0x2403fe00ull,
+ },
+};
+
static void riscv_config_qpci_bus(QGenericPCIBus *qpci)
{
qpci->gpex_pio_base = RISCV_GPEX_PIO_BASE;
@@ -264,6 +328,129 @@ static void test_riscv_iommu_nested(void)
run_riscv_iommu_translation(&cfg);
}
+static void riscv_iommu_dbg_map_leaf(QTestState *qts, uint64_t l1_pa,
+ uint64_t l2_pa, uint64_t va, uint64_t pa,
+ uint64_t page_size)
+{
+ uint64_t pte = ((pa >> 12) << 10) | QRIOMMU_LEAF_PTE_RW_MASK;
+ uint64_t pte_addr;
+
+ g_assert_cmphex(pa & (page_size - 1), ==, 0);
+
+ if (page_size == DBG_SUPERPAGE_SIZE) {
+ pte_addr = l1_pa + (((va >> 21) & 0x1ff) * sizeof(uint64_t));
+ qtest_writeq(qts, pte_addr, pte);
+ } else if (page_size == DBG_NAPOT_SIZE) {
+ /* A 64 KiB NAPOT PTE has N=1 and PPN[3:0]=1000. */
+ pte |= BIT_ULL(63) | (8ull << 10);
+ va &= ~(DBG_NAPOT_SIZE - 1);
+ for (unsigned i = 0; i < DBG_NAPOT_SIZE / DBG_PAGE_SIZE; i++) {
+ pte_addr = l2_pa + ((((va >> 12) + i) & 0x1ff) *
+ sizeof(uint64_t));
+ qtest_writeq(qts, pte_addr, pte);
+ }
+ } else {
+ g_assert_cmphex(page_size, ==, DBG_PAGE_SIZE);
+ pte_addr = l2_pa + (((va >> 12) & 0x1ff) * sizeof(uint64_t));
+ qtest_writeq(qts, pte_addr, pte);
+ }
+}
+
+static uint64_t riscv_iommu_dbg_translate(RiscvIommuTestState *state,
+ uint64_t iova)
+{
+ uint64_t request;
+
+ qtest_writeq(state->qts,
+ state->iommu_base + RISCV_IOMMU_REG_TR_REQ_IOVA, iova);
+ request = ((uint64_t)state->testdev->devfn << 40) |
+ RISCV_IOMMU_TR_REQ_CTL_GO_BUSY;
+ qtest_writeq(state->qts,
+ state->iommu_base + RISCV_IOMMU_REG_TR_REQ_CTL, request);
+
+ request = qtest_readq(state->qts,
+ state->iommu_base + RISCV_IOMMU_REG_TR_REQ_CTL);
+ g_assert_cmpuint(request & RISCV_IOMMU_TR_REQ_CTL_GO_BUSY, ==, 0);
+
+ return qtest_readq(state->qts,
+ state->iommu_base + RISCV_IOMMU_REG_TR_RESPONSE);
+}
+
+static void test_riscv_iommu_dbg(gconstpointer data)
+{
+ const RiscvIommuDbgTest *cfg = data;
+ RiscvIommuTestState state = { 0 };
+ uint64_t iova = QRIOMMU_IOVA & ~(DBG_PAGE_SIZE - 1);
+ uint64_t s_pa = cfg->pa;
+ uint64_t page_size = cfg->mode == QRIOMMU_TM_G_STAGE_ONLY ?
+ cfg->g_page_size : cfg->s_page_size;
+ uint64_t response;
+
+ if (!riscv_iommu_test_setup(&state)) {
+ return;
+ }
+
+ riscv_iommu_check(state.qts, state.iommu_base, cfg->mode);
+ qriommu_build_translation(state.qts, cfg->mode, state.testdev->devfn);
+
+ if (cfg->mode == QRIOMMU_TM_NESTED) {
+ uint64_t g_l1_pa = QRIOMMU_SPACE_OFFS + QRIOMMU_G_L0_PTE_VAL;
+ uint64_t g_l2_pa = QRIOMMU_SPACE_OFFS + QRIOMMU_G_L1_PTE_VAL;
+ uint64_t offset = iova & (DBG_SUPERPAGE_SIZE - 1);
+ uint64_t gpa = DBG_NESTED_GPA + offset;
+ uint64_t pte_addr;
+ uint64_t pte;
+
+ /* Map all S-stage page tables through one 2 MiB G-stage leaf. */
+ riscv_iommu_dbg_map_leaf(state.qts, g_l1_pa, g_l2_pa, 0,
+ QRIOMMU_SPACE_OFFS, DBG_SUPERPAGE_SIZE);
+
+ if (cfg->g_page_size == DBG_PAGE_SIZE) {
+ pte_addr = g_l1_pa + (((gpa >> 21) & 0x1ff) * sizeof(uint64_t));
+ pte = ((g_l2_pa >> 12) << 10) | QRIOMMU_NON_LEAF_PTE_MASK;
+ qtest_writeq(state.qts, pte_addr, pte);
+ }
+ riscv_iommu_dbg_map_leaf(state.qts, g_l1_pa, g_l2_pa, gpa,
+ cfg->pa + (offset & ~(cfg->g_page_size - 1)),
+ cfg->g_page_size);
+ s_pa = gpa & ~(cfg->s_page_size - 1);
+ page_size = MIN(page_size, cfg->g_page_size);
+ }
+
+ if (cfg->mode == QRIOMMU_TM_G_STAGE_ONLY) {
+ riscv_iommu_dbg_map_leaf(state.qts,
+ QRIOMMU_SPACE_OFFS + QRIOMMU_G_L0_PTE_VAL,
+ QRIOMMU_SPACE_OFFS + QRIOMMU_G_L1_PTE_VAL,
+ iova, cfg->pa, cfg->g_page_size);
+ } else {
+ riscv_iommu_dbg_map_leaf(state.qts,
+ QRIOMMU_SPACE_OFFS + QRIOMMU_L0_PTE_VAL,
+ QRIOMMU_SPACE_OFFS + QRIOMMU_L1_PTE_VAL,
+ iova, s_pa, cfg->s_page_size);
+ }
+ qriommu_program_regs(state.qts, state.iommu_base);
+
+ /*
+ * The specification permits reporting a smaller range. These tests
+ * verify QEMU's choice to report the effective translation range.
+ */
+ response = riscv_iommu_dbg_translate(&state, iova);
+ g_assert_cmphex(response, ==, cfg->expected_response);
+
+ if (page_size > DBG_PAGE_SIZE) {
+ /* Check the first and last pages, in addition to the interior. */
+ iova &= ~(page_size - 1);
+ response = riscv_iommu_dbg_translate(&state, iova);
+ g_assert_cmphex(response, ==, cfg->expected_response);
+ response = riscv_iommu_dbg_translate(&state,
+ iova + page_size - DBG_PAGE_SIZE);
+ g_assert_cmphex(response, ==, cfg->expected_response);
+ }
+
+ riscv_iommu_test_teardown(&state);
+}
+
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
@@ -275,5 +462,11 @@ int main(int argc, char **argv)
test_riscv_iommu_g_stage_only);
qtest_add_func("/iommu-testdev/translation/ns-nested",
test_riscv_iommu_nested);
+ for (unsigned i = 0; i < G_N_ELEMENTS(debug_tests); i++) {
+ g_autofree char *path = g_strdup_printf("/iommu-testdev/debug/%s",
+ debug_tests[i].name);
+
+ qtest_add_data_func(path, &debug_tests[i], test_riscv_iommu_dbg);
+ }
return g_test_run();
}
--
2.43.0
Hi Zephyr,
I am unable to apply this patch because git got nervous with a "corrupt
file error":
$ git am \[PATCH\]\ hw_riscv_riscv-iommu_\ encode\ debug\ translation\ ranges\ -\ Zephyr\ Li\ \(fritchleybohrer@gmail.com\)\ -\ 2026-10-16\ 0608.eml
Applying: hw/riscv/riscv-iommu: encode debug translation ranges
error: corrupt patch at line 243
Patch failed at 0001 hw/riscv/riscv-iommu: encode debug translation ranges
hint: Use 'git am --show-current-patch=diff' to see the failed patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Tried the usual stuff with 'dos2unix', redownloading the patch and so on but
still the same error.
Can you please re-send? Thanks,
Daniel
On 10/16/2026 6:08 AM, Zephyr Li wrote:
> The debug translation response aligns the translated physical address to
> the effective translation range, but always reports S=0. For a request
> inside a superpage, the response therefore identifies the wrong 4 KiB
> physical page.
>
> For effective ranges larger than 4 KiB, set S and encode the range size
> using trailing ones in the PPN field. Keep the existing response format
> for 4 KiB translations.
>
> The RISC-V IOMMU specification permits reporting a 4 KiB translation even
> when the page tables use superpages. Reporting the effective range is an
> implementation choice; this change does not claim that S=1 is mandatory
> for every translation through a superpage mapping.
>
> Add debug-response tests for S-stage-only 4 KiB, 64 KiB NAPOT, and 2 MiB
> translations, G-stage-only 2 MiB translations, and nested S4K/G2M,
> S2M/G4K, and S2M/G2M mappings. Check requests inside large ranges and at
> both ends. The mixed nested cases verify that the response uses the
> smaller effective translation granularity.
>
> Fixes: a7aa525b93c3 ("hw/riscv/riscv-iommu: add DBG support")
> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3552
> Signed-off-by: Zephyr Li <fritchleybohrer@gmail.com>
> ---
> hw/riscv/riscv-iommu.c | 14 ++-
> tests/qtest/iommu-riscv-test.c | 193 +++++++++++++++++++++++++++++++++
> 2 files changed, 206 insertions(+), 1 deletion(-)
>
> diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
> index 323a041b4a558136819b1cd41452e96481ba164a..67d8456b9d3ed34308ca794e8d700664ebacce0f 100644
> --- a/hw/riscv/riscv-iommu.c
> +++ b/hw/riscv/riscv-iommu.c
> @@ -2281,8 +2281,20 @@ static void riscv_iommu_process_dbg(RISCVIOMMUState *s)
> if (fault) {
> iova = RISCV_IOMMU_TR_RESPONSE_FAULT | (((uint64_t) fault) << 10);
> } else {
> + uint64_t ppn;
> +
> iova = iotlb.translated_addr & ~iotlb.addr_mask;
> - iova = set_field(0, RISCV_IOMMU_TR_RESPONSE_PPN, PPN_DOWN(iova));
> + ppn = PPN_DOWN(iova);
> +
> + if (iotlb.addr_mask > TARGET_PAGE_SIZE - 1) {
> + /* Encode the translation range as trailing ones in the PPN. */
> + ppn |= PPN_DOWN(iotlb.addr_mask) >> 1;
> + iova = RISCV_IOMMU_TR_RESPONSE_S;
> + } else {
> + iova = 0;
> + }
> +
> + iova = set_field(iova, RISCV_IOMMU_TR_RESPONSE_PPN, ppn);
> }
> riscv_iommu_reg_set64(s, RISCV_IOMMU_REG_TR_RESPONSE, iova);
> }
> diff --git a/tests/qtest/iommu-riscv-test.c b/tests/qtest/iommu-riscv-test.c
> index 26380248912cc281e313e20a3daf1831be1eded3..1cd753c4f243f207dc34b7acbe41ef0d55772cba 100644
> --- a/tests/qtest/iommu-riscv-test.c
> +++ b/tests/qtest/iommu-riscv-test.c
> @@ -19,6 +19,12 @@
>
> #define DMA_LEN 4
>
> +#define DBG_PAGE_SIZE 0x1000ull
> +#define DBG_NAPOT_SIZE 0x10000ull
> +#define DBG_SUPERPAGE_SIZE 0x200000ull
> +#define DBG_TRANSLATED_PA 0x90000000ull
> +#define DBG_NESTED_GPA 0x200000ull
> +
> /* RISC-V virt machine PCI configuration */
> #define RISCV_GPEX_PIO_BASE 0x3000000
> #define RISCV_BUS_PIO_LIMIT 0x10000
> @@ -35,6 +41,64 @@ typedef struct RiscvIommuTestState {
> uint64_t iommu_base;
> } RiscvIommuTestState;
>
> +typedef struct RiscvIommuDbgTest {
> + const char *name;
> + QRIOMMUTransMode mode;
> + uint64_t s_page_size;
> + uint64_t g_page_size;
> + uint64_t pa;
> + uint64_t expected_response;
> +} RiscvIommuDbgTest;
> +
> +static const RiscvIommuDbgTest debug_tests[] = {
> + {
> + .name = "page-4k",
> + .mode = QRIOMMU_TM_S_STAGE_ONLY,
> + .s_page_size = DBG_PAGE_SIZE,
> + .pa = DBG_TRANSLATED_PA + DBG_PAGE_SIZE,
> + .expected_response = 0x24000400ull,
> + }, {
> + .name = "napot-64k",
> + .mode = QRIOMMU_TM_S_STAGE_ONLY,
> + .s_page_size = DBG_NAPOT_SIZE,
> + .pa = DBG_TRANSLATED_PA,
> + .expected_response = 0x24001e00ull,
> + }, {
> + .name = "superpage",
> + .mode = QRIOMMU_TM_S_STAGE_ONLY,
> + .s_page_size = DBG_SUPERPAGE_SIZE,
> + .pa = DBG_TRANSLATED_PA,
> + .expected_response = 0x2403fe00ull,
> + }, {
> + .name = "g-stage-superpage",
> + .mode = QRIOMMU_TM_G_STAGE_ONLY,
> + .g_page_size = DBG_SUPERPAGE_SIZE,
> + .pa = DBG_TRANSLATED_PA,
> + .expected_response = 0x2403fe00ull,
> + }, {
> + .name = "nested-s4k-g2m",
> + .mode = QRIOMMU_TM_NESTED,
> + .s_page_size = DBG_PAGE_SIZE,
> + .g_page_size = DBG_SUPERPAGE_SIZE,
> + .pa = DBG_TRANSLATED_PA,
> + .expected_response = 0x24001000ull,
> + }, {
> + .name = "nested-s2m-g4k",
> + .mode = QRIOMMU_TM_NESTED,
> + .s_page_size = DBG_SUPERPAGE_SIZE,
> + .g_page_size = DBG_PAGE_SIZE,
> + .pa = DBG_TRANSLATED_PA,
> + .expected_response = 0x24001000ull,
> + }, {
> + .name = "nested-s2m-g2m",
> + .mode = QRIOMMU_TM_NESTED,
> + .s_page_size = DBG_SUPERPAGE_SIZE,
> + .g_page_size = DBG_SUPERPAGE_SIZE,
> + .pa = DBG_TRANSLATED_PA,
> + .expected_response = 0x2403fe00ull,
> + },
> +};
> +
> static void riscv_config_qpci_bus(QGenericPCIBus *qpci)
> {
> qpci->gpex_pio_base = RISCV_GPEX_PIO_BASE;
> @@ -264,6 +328,129 @@ static void test_riscv_iommu_nested(void)
> run_riscv_iommu_translation(&cfg);
> }
>
> +static void riscv_iommu_dbg_map_leaf(QTestState *qts, uint64_t l1_pa,
> + uint64_t l2_pa, uint64_t va, uint64_t pa,
> + uint64_t page_size)
> +{
> + uint64_t pte = ((pa >> 12) << 10) | QRIOMMU_LEAF_PTE_RW_MASK;
> + uint64_t pte_addr;
> +
> + g_assert_cmphex(pa & (page_size - 1), ==, 0);
> +
> + if (page_size == DBG_SUPERPAGE_SIZE) {
> + pte_addr = l1_pa + (((va >> 21) & 0x1ff) * sizeof(uint64_t));
> + qtest_writeq(qts, pte_addr, pte);
> + } else if (page_size == DBG_NAPOT_SIZE) {
> + /* A 64 KiB NAPOT PTE has N=1 and PPN[3:0]=1000. */
> + pte |= BIT_ULL(63) | (8ull << 10);
> + va &= ~(DBG_NAPOT_SIZE - 1);
> + for (unsigned i = 0; i < DBG_NAPOT_SIZE / DBG_PAGE_SIZE; i++) {
> + pte_addr = l2_pa + ((((va >> 12) + i) & 0x1ff) *
> + sizeof(uint64_t));
> + qtest_writeq(qts, pte_addr, pte);
> + }
> + } else {
> + g_assert_cmphex(page_size, ==, DBG_PAGE_SIZE);
> + pte_addr = l2_pa + (((va >> 12) & 0x1ff) * sizeof(uint64_t));
> + qtest_writeq(qts, pte_addr, pte);
> + }
> +}
> +
> +static uint64_t riscv_iommu_dbg_translate(RiscvIommuTestState *state,
> + uint64_t iova)
> +{
> + uint64_t request;
> +
> + qtest_writeq(state->qts,
> + state->iommu_base + RISCV_IOMMU_REG_TR_REQ_IOVA, iova);
> + request = ((uint64_t)state->testdev->devfn << 40) |
> + RISCV_IOMMU_TR_REQ_CTL_GO_BUSY;
> + qtest_writeq(state->qts,
> + state->iommu_base + RISCV_IOMMU_REG_TR_REQ_CTL, request);
> +
> + request = qtest_readq(state->qts,
> + state->iommu_base + RISCV_IOMMU_REG_TR_REQ_CTL);
> + g_assert_cmpuint(request & RISCV_IOMMU_TR_REQ_CTL_GO_BUSY, ==, 0);
> +
> + return qtest_readq(state->qts,
> + state->iommu_base + RISCV_IOMMU_REG_TR_RESPONSE);
> +}
> +
> +static void test_riscv_iommu_dbg(gconstpointer data)
> +{
> + const RiscvIommuDbgTest *cfg = data;
> + RiscvIommuTestState state = { 0 };
> + uint64_t iova = QRIOMMU_IOVA & ~(DBG_PAGE_SIZE - 1);
> + uint64_t s_pa = cfg->pa;
> + uint64_t page_size = cfg->mode == QRIOMMU_TM_G_STAGE_ONLY ?
> + cfg->g_page_size : cfg->s_page_size;
> + uint64_t response;
> +
> + if (!riscv_iommu_test_setup(&state)) {
> + return;
> + }
> +
> + riscv_iommu_check(state.qts, state.iommu_base, cfg->mode);
> + qriommu_build_translation(state.qts, cfg->mode, state.testdev->devfn);
> +
> + if (cfg->mode == QRIOMMU_TM_NESTED) {
> + uint64_t g_l1_pa = QRIOMMU_SPACE_OFFS + QRIOMMU_G_L0_PTE_VAL;
> + uint64_t g_l2_pa = QRIOMMU_SPACE_OFFS + QRIOMMU_G_L1_PTE_VAL;
> + uint64_t offset = iova & (DBG_SUPERPAGE_SIZE - 1);
> + uint64_t gpa = DBG_NESTED_GPA + offset;
> + uint64_t pte_addr;
> + uint64_t pte;
> +
> + /* Map all S-stage page tables through one 2 MiB G-stage leaf. */
> + riscv_iommu_dbg_map_leaf(state.qts, g_l1_pa, g_l2_pa, 0,
> + QRIOMMU_SPACE_OFFS, DBG_SUPERPAGE_SIZE);
> +
> + if (cfg->g_page_size == DBG_PAGE_SIZE) {
> + pte_addr = g_l1_pa + (((gpa >> 21) & 0x1ff) * sizeof(uint64_t));
> + pte = ((g_l2_pa >> 12) << 10) | QRIOMMU_NON_LEAF_PTE_MASK;
> + qtest_writeq(state.qts, pte_addr, pte);
> + }
> + riscv_iommu_dbg_map_leaf(state.qts, g_l1_pa, g_l2_pa, gpa,
> + cfg->pa + (offset & ~(cfg->g_page_size - 1)),
> + cfg->g_page_size);
> + s_pa = gpa & ~(cfg->s_page_size - 1);
> + page_size = MIN(page_size, cfg->g_page_size);
> + }
> +
> + if (cfg->mode == QRIOMMU_TM_G_STAGE_ONLY) {
> + riscv_iommu_dbg_map_leaf(state.qts,
> + QRIOMMU_SPACE_OFFS + QRIOMMU_G_L0_PTE_VAL,
> + QRIOMMU_SPACE_OFFS + QRIOMMU_G_L1_PTE_VAL,
> + iova, cfg->pa, cfg->g_page_size);
> + } else {
> + riscv_iommu_dbg_map_leaf(state.qts,
> + QRIOMMU_SPACE_OFFS + QRIOMMU_L0_PTE_VAL,
> + QRIOMMU_SPACE_OFFS + QRIOMMU_L1_PTE_VAL,
> + iova, s_pa, cfg->s_page_size);
> + }
> + qriommu_program_regs(state.qts, state.iommu_base);
> +
> + /*
> + * The specification permits reporting a smaller range. These tests
> + * verify QEMU's choice to report the effective translation range.
> + */
> + response = riscv_iommu_dbg_translate(&state, iova);
> + g_assert_cmphex(response, ==, cfg->expected_response);
> +
> + if (page_size > DBG_PAGE_SIZE) {
> + /* Check the first and last pages, in addition to the interior. */
> + iova &= ~(page_size - 1);
> + response = riscv_iommu_dbg_translate(&state, iova);
> + g_assert_cmphex(response, ==, cfg->expected_response);
> + response = riscv_iommu_dbg_translate(&state,
> + iova + page_size - DBG_PAGE_SIZE);
> + g_assert_cmphex(response, ==, cfg->expected_response);
> + }
> +
> + riscv_iommu_test_teardown(&state);
> +}
> +
> int main(int argc, char **argv)
> {
> g_test_init(&argc, &argv, NULL);
> @@ -275,5 +462,11 @@ int main(int argc, char **argv)
> test_riscv_iommu_g_stage_only);
> qtest_add_func("/iommu-testdev/translation/ns-nested",
> test_riscv_iommu_nested);
> + for (unsigned i = 0; i < G_N_ELEMENTS(debug_tests); i++) {
> + g_autofree char *path = g_strdup_printf("/iommu-testdev/debug/%s",
> + debug_tests[i].name);
> +
> + qtest_add_data_func(path, &debug_tests[i], test_riscv_iommu_dbg);
> + }
> return g_test_run();
> }
Hi Daniel,
Thanks for letting me know, and sorry about the malformed patch.
I'll regenerate the patch, verify that it applies cleanly with git am, and
resend it as v2.
Thanks,
Zephyr
On Wed, Oct 21, 2026 at 7:57 PM Daniel Henrique Barboza <
daniel.barboza@oss.qualcomm.com> wrote:
> Hi Zephyr,
>
> I am unable to apply this patch because git got nervous with a "corrupt
> file error":
>
> $ git am \[PATCH\]\ hw_riscv_riscv-iommu_\ encode\ debug\ translation\
> ranges\ -\ Zephyr\ Li\ \(fritchleybohrer@gmail.com\)\ -\ 2026-10-16\
> 0608.eml
> Applying: hw/riscv/riscv-iommu: encode debug translation ranges
> error: corrupt patch at line 243
> Patch failed at 0001 hw/riscv/riscv-iommu: encode debug translation ranges
> hint: Use 'git am --show-current-patch=diff' to see the failed patch
> When you have resolved this problem, run "git am --continue".
> If you prefer to skip this patch, run "git am --skip" instead.
> To restore the original branch and stop patching, run "git am --abort".
>
> Tried the usual stuff with 'dos2unix', redownloading the patch and so on
> but
> still the same error.
>
>
> Can you please re-send? Thanks,
> Daniel
>
> On 10/16/2026 6:08 AM, Zephyr Li wrote:
> > The debug translation response aligns the translated physical address to
> > the effective translation range, but always reports S=0. For a request
> > inside a superpage, the response therefore identifies the wrong 4 KiB
> > physical page.
> >
> > For effective ranges larger than 4 KiB, set S and encode the range size
> > using trailing ones in the PPN field. Keep the existing response format
> > for 4 KiB translations.
> >
> > The RISC-V IOMMU specification permits reporting a 4 KiB translation even
> > when the page tables use superpages. Reporting the effective range is an
> > implementation choice; this change does not claim that S=1 is mandatory
> > for every translation through a superpage mapping.
> >
> > Add debug-response tests for S-stage-only 4 KiB, 64 KiB NAPOT, and 2 MiB
> > translations, G-stage-only 2 MiB translations, and nested S4K/G2M,
> > S2M/G4K, and S2M/G2M mappings. Check requests inside large ranges and at
> > both ends. The mixed nested cases verify that the response uses the
> > smaller effective translation granularity.
> >
> > Fixes: a7aa525b93c3 ("hw/riscv/riscv-iommu: add DBG support")
> > Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3552
> > Signed-off-by: Zephyr Li <fritchleybohrer@gmail.com>
> > ---
> > hw/riscv/riscv-iommu.c | 14 ++-
> > tests/qtest/iommu-riscv-test.c | 193 +++++++++++++++++++++++++++++++++
> > 2 files changed, 206 insertions(+), 1 deletion(-)
> >
> > diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
> > index
> 323a041b4a558136819b1cd41452e96481ba164a..67d8456b9d3ed34308ca794e8d700664ebacce0f
> 100644
> > --- a/hw/riscv/riscv-iommu.c
> > +++ b/hw/riscv/riscv-iommu.c
> > @@ -2281,8 +2281,20 @@ static void
> riscv_iommu_process_dbg(RISCVIOMMUState *s)
> > if (fault) {
> > iova = RISCV_IOMMU_TR_RESPONSE_FAULT | (((uint64_t) fault)
> << 10);
> > } else {
> > + uint64_t ppn;
> > +
> > iova = iotlb.translated_addr & ~iotlb.addr_mask;
> > - iova = set_field(0, RISCV_IOMMU_TR_RESPONSE_PPN,
> PPN_DOWN(iova));
> > + ppn = PPN_DOWN(iova);
> > +
> > + if (iotlb.addr_mask > TARGET_PAGE_SIZE - 1) {
> > + /* Encode the translation range as trailing ones in the
> PPN. */
> > + ppn |= PPN_DOWN(iotlb.addr_mask) >> 1;
> > + iova = RISCV_IOMMU_TR_RESPONSE_S;
> > + } else {
> > + iova = 0;
> > + }
> > +
> > + iova = set_field(iova, RISCV_IOMMU_TR_RESPONSE_PPN, ppn);
> > }
> > riscv_iommu_reg_set64(s, RISCV_IOMMU_REG_TR_RESPONSE, iova);
> > }
> > diff --git a/tests/qtest/iommu-riscv-test.c
> b/tests/qtest/iommu-riscv-test.c
> > index
> 26380248912cc281e313e20a3daf1831be1eded3..1cd753c4f243f207dc34b7acbe41ef0d55772cba
> 100644
> > --- a/tests/qtest/iommu-riscv-test.c
> > +++ b/tests/qtest/iommu-riscv-test.c
> > @@ -19,6 +19,12 @@
> >
> > #define DMA_LEN 4
> >
> > +#define DBG_PAGE_SIZE 0x1000ull
> > +#define DBG_NAPOT_SIZE 0x10000ull
> > +#define DBG_SUPERPAGE_SIZE 0x200000ull
> > +#define DBG_TRANSLATED_PA 0x90000000ull
> > +#define DBG_NESTED_GPA 0x200000ull
> > +
> > /* RISC-V virt machine PCI configuration */
> > #define RISCV_GPEX_PIO_BASE 0x3000000
> > #define RISCV_BUS_PIO_LIMIT 0x10000
> > @@ -35,6 +41,64 @@ typedef struct RiscvIommuTestState {
> > uint64_t iommu_base;
> > } RiscvIommuTestState;
> >
> > +typedef struct RiscvIommuDbgTest {
> > + const char *name;
> > + QRIOMMUTransMode mode;
> > + uint64_t s_page_size;
> > + uint64_t g_page_size;
> > + uint64_t pa;
> > + uint64_t expected_response;
> > +} RiscvIommuDbgTest;
> > +
> > +static const RiscvIommuDbgTest debug_tests[] = {
> > + {
> > + .name = "page-4k",
> > + .mode = QRIOMMU_TM_S_STAGE_ONLY,
> > + .s_page_size = DBG_PAGE_SIZE,
> > + .pa = DBG_TRANSLATED_PA + DBG_PAGE_SIZE,
> > + .expected_response = 0x24000400ull,
> > + }, {
> > + .name = "napot-64k",
> > + .mode = QRIOMMU_TM_S_STAGE_ONLY,
> > + .s_page_size = DBG_NAPOT_SIZE,
> > + .pa = DBG_TRANSLATED_PA,
> > + .expected_response = 0x24001e00ull,
> > + }, {
> > + .name = "superpage",
> > + .mode = QRIOMMU_TM_S_STAGE_ONLY,
> > + .s_page_size = DBG_SUPERPAGE_SIZE,
> > + .pa = DBG_TRANSLATED_PA,
> > + .expected_response = 0x2403fe00ull,
> > + }, {
> > + .name = "g-stage-superpage",
> > + .mode = QRIOMMU_TM_G_STAGE_ONLY,
> > + .g_page_size = DBG_SUPERPAGE_SIZE,
> > + .pa = DBG_TRANSLATED_PA,
> > + .expected_response = 0x2403fe00ull,
> > + }, {
> > + .name = "nested-s4k-g2m",
> > + .mode = QRIOMMU_TM_NESTED,
> > + .s_page_size = DBG_PAGE_SIZE,
> > + .g_page_size = DBG_SUPERPAGE_SIZE,
> > + .pa = DBG_TRANSLATED_PA,
> > + .expected_response = 0x24001000ull,
> > + }, {
> > + .name = "nested-s2m-g4k",
> > + .mode = QRIOMMU_TM_NESTED,
> > + .s_page_size = DBG_SUPERPAGE_SIZE,
> > + .g_page_size = DBG_PAGE_SIZE,
> > + .pa = DBG_TRANSLATED_PA,
> > + .expected_response = 0x24001000ull,
> > + }, {
> > + .name = "nested-s2m-g2m",
> > + .mode = QRIOMMU_TM_NESTED,
> > + .s_page_size = DBG_SUPERPAGE_SIZE,
> > + .g_page_size = DBG_SUPERPAGE_SIZE,
> > + .pa = DBG_TRANSLATED_PA,
> > + .expected_response = 0x2403fe00ull,
> > + },
> > +};
> > +
> > static void riscv_config_qpci_bus(QGenericPCIBus *qpci)
> > {
> > qpci->gpex_pio_base = RISCV_GPEX_PIO_BASE;
> > @@ -264,6 +328,129 @@ static void test_riscv_iommu_nested(void)
> > run_riscv_iommu_translation(&cfg);
> > }
> >
> > +static void riscv_iommu_dbg_map_leaf(QTestState *qts, uint64_t l1_pa,
> > + uint64_t l2_pa, uint64_t va,
> uint64_t pa,
> > + uint64_t page_size)
> > +{
> > + uint64_t pte = ((pa >> 12) << 10) | QRIOMMU_LEAF_PTE_RW_MASK;
> > + uint64_t pte_addr;
> > +
> > + g_assert_cmphex(pa & (page_size - 1), ==, 0);
> > +
> > + if (page_size == DBG_SUPERPAGE_SIZE) {
> > + pte_addr = l1_pa + (((va >> 21) & 0x1ff) * sizeof(uint64_t));
> > + qtest_writeq(qts, pte_addr, pte);
> > + } else if (page_size == DBG_NAPOT_SIZE) {
> > + /* A 64 KiB NAPOT PTE has N=1 and PPN[3:0]=1000. */
> > + pte |= BIT_ULL(63) | (8ull << 10);
> > + va &= ~(DBG_NAPOT_SIZE - 1);
> > + for (unsigned i = 0; i < DBG_NAPOT_SIZE / DBG_PAGE_SIZE; i++) {
> > + pte_addr = l2_pa + ((((va >> 12) + i) & 0x1ff) *
> > + sizeof(uint64_t));
> > + qtest_writeq(qts, pte_addr, pte);
> > + }
> > + } else {
> > + g_assert_cmphex(page_size, ==, DBG_PAGE_SIZE);
> > + pte_addr = l2_pa + (((va >> 12) & 0x1ff) * sizeof(uint64_t));
> > + qtest_writeq(qts, pte_addr, pte);
> > + }
> > +}
> > +
> > +static uint64_t riscv_iommu_dbg_translate(RiscvIommuTestState *state,
> > + uint64_t iova)
> > +{
> > + uint64_t request;
> > +
> > + qtest_writeq(state->qts,
> > + state->iommu_base + RISCV_IOMMU_REG_TR_REQ_IOVA, iova);
> > + request = ((uint64_t)state->testdev->devfn << 40) |
> > + RISCV_IOMMU_TR_REQ_CTL_GO_BUSY;
> > + qtest_writeq(state->qts,
> > + state->iommu_base + RISCV_IOMMU_REG_TR_REQ_CTL,
> request);
> > +
> > + request = qtest_readq(state->qts,
> > + state->iommu_base +
> RISCV_IOMMU_REG_TR_REQ_CTL);
> > + g_assert_cmpuint(request & RISCV_IOMMU_TR_REQ_CTL_GO_BUSY, ==, 0);
> > +
> > + return qtest_readq(state->qts,
> > + state->iommu_base + RISCV_IOMMU_REG_TR_RESPONSE);
> > +}
> > +
> > +static void test_riscv_iommu_dbg(gconstpointer data)
> > +{
> > + const RiscvIommuDbgTest *cfg = data;
> > + RiscvIommuTestState state = { 0 };
> > + uint64_t iova = QRIOMMU_IOVA & ~(DBG_PAGE_SIZE - 1);
> > + uint64_t s_pa = cfg->pa;
> > + uint64_t page_size = cfg->mode == QRIOMMU_TM_G_STAGE_ONLY ?
> > + cfg->g_page_size : cfg->s_page_size;
> > + uint64_t response;
> > +
> > + if (!riscv_iommu_test_setup(&state)) {
> > + return;
> > + }
> > +
> > + riscv_iommu_check(state.qts, state.iommu_base, cfg->mode);
> > + qriommu_build_translation(state.qts, cfg->mode,
> state.testdev->devfn);
> > +
> > + if (cfg->mode == QRIOMMU_TM_NESTED) {
> > + uint64_t g_l1_pa = QRIOMMU_SPACE_OFFS + QRIOMMU_G_L0_PTE_VAL;
> > + uint64_t g_l2_pa = QRIOMMU_SPACE_OFFS + QRIOMMU_G_L1_PTE_VAL;
> > + uint64_t offset = iova & (DBG_SUPERPAGE_SIZE - 1);
> > + uint64_t gpa = DBG_NESTED_GPA + offset;
> > + uint64_t pte_addr;
> > + uint64_t pte;
> > +
> > + /* Map all S-stage page tables through one 2 MiB G-stage leaf.
> */
> > + riscv_iommu_dbg_map_leaf(state.qts, g_l1_pa, g_l2_pa, 0,
> > + QRIOMMU_SPACE_OFFS,
> DBG_SUPERPAGE_SIZE);
> > +
> > + if (cfg->g_page_size == DBG_PAGE_SIZE) {
> > + pte_addr = g_l1_pa + (((gpa >> 21) & 0x1ff) *
> sizeof(uint64_t));
> > + pte = ((g_l2_pa >> 12) << 10) | QRIOMMU_NON_LEAF_PTE_MASK;
> > + qtest_writeq(state.qts, pte_addr, pte);
> > + }
> > + riscv_iommu_dbg_map_leaf(state.qts, g_l1_pa, g_l2_pa, gpa,
> > + cfg->pa + (offset & ~(cfg->g_page_size
> - 1)),
> > + cfg->g_page_size);
> > + s_pa = gpa & ~(cfg->s_page_size - 1);
> > + page_size = MIN(page_size, cfg->g_page_size);
> > + }
> > +
> > + if (cfg->mode == QRIOMMU_TM_G_STAGE_ONLY) {
> > + riscv_iommu_dbg_map_leaf(state.qts,
> > + QRIOMMU_SPACE_OFFS +
> QRIOMMU_G_L0_PTE_VAL,
> > + QRIOMMU_SPACE_OFFS +
> QRIOMMU_G_L1_PTE_VAL,
> > + iova, cfg->pa, cfg->g_page_size);
> > + } else {
> > + riscv_iommu_dbg_map_leaf(state.qts,
> > + QRIOMMU_SPACE_OFFS +
> QRIOMMU_L0_PTE_VAL,
> > + QRIOMMU_SPACE_OFFS +
> QRIOMMU_L1_PTE_VAL,
> > + iova, s_pa, cfg->s_page_size);
> > + }
> > + qriommu_program_regs(state.qts, state.iommu_base);
> > +
> > + /*
> > + * The specification permits reporting a smaller range. These tests
> > + * verify QEMU's choice to report the effective translation range.
> > + */
> > + response = riscv_iommu_dbg_translate(&state, iova);
> > + g_assert_cmphex(response, ==, cfg->expected_response);
> > +
> > + if (page_size > DBG_PAGE_SIZE) {
> > + /* Check the first and last pages, in addition to the interior.
> */
> > + iova &= ~(page_size - 1);
> > + response = riscv_iommu_dbg_translate(&state, iova);
> > + g_assert_cmphex(response, ==, cfg->expected_response);
> > + response = riscv_iommu_dbg_translate(&state,
> > + iova + page_size -
> DBG_PAGE_SIZE);
> > + g_assert_cmphex(response, ==, cfg->expected_response);
> > + }
> > +
> > + riscv_iommu_test_teardown(&state);
> > +}
> > +
> > int main(int argc, char **argv)
> > {
> > g_test_init(&argc, &argv, NULL);
> > @@ -275,5 +462,11 @@ int main(int argc, char **argv)
> > test_riscv_iommu_g_stage_only);
> > qtest_add_func("/iommu-testdev/translation/ns-nested",
> > test_riscv_iommu_nested);
> > + for (unsigned i = 0; i < G_N_ELEMENTS(debug_tests); i++) {
> > + g_autofree char *path =
> g_strdup_printf("/iommu-testdev/debug/%s",
> > + debug_tests[i].name);
> > +
> > + qtest_add_data_func(path, &debug_tests[i],
> test_riscv_iommu_dbg);
> > + }
> > return g_test_run();
> > }
>
>
© 2016 - 2026 Red Hat, Inc.