@samitouri / QOSamiQemu / commits / fcbd93e96b

hw/riscv/riscv-iommu: Fix Svnapot 64KB pages

The Svnapot extension encodes a 64KB leaf PTE by setting PTE_N and storing bits [3:0] of the PPN as a NAPOT size indicator. The IOMMU model wasn't checking PTE_N and therefore was using the raw (NAPOT- encoded) PPN directly in the physical address, yielding an address 32 KB above the correct base. Fix both riscv_iommu_spa_fetch() and pdt_memory_read() by mirroring the Svnapot handling already present in target/riscv/cpu_helper.c: napot_bits = ctz64(ppn) + 1 /* 4 for 64KB */ napot_mask = (1 << napot_bits) - 1 /* 0xF */ phys_base = PPN_PHYS(ppn & ~napot_mask) page_offset = addr & (PPN_PHYS(napot_mask) | (TARGET_PAGE_SIZE - 1)) The spec only defines napot_bits == 4 (64KB); any other value is treated as a reserved encoding. This is a fix, rather than new feature support, because the spec says "IOMMU implementations must support the Svnapot standard extension for NAPOT Translation Contiguity." Fixes: 0c54acb8243d ("hw/riscv: add RISC-V IOMMU base emulation") Cc: qemu-stable@nongnu.org Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com> Reviewed-by: Nutty Liu <nutty.liu@hotmail.com> Reviewed-by: Tomasz Jeznach <tjeznach@rivosinc.com> Message-ID: <20260508205129.377032-1-andrew.jones@oss.qualcomm.com> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>

Andrew Jones committed May 8, 2026 at 15:51 UTC fcbd93e96be2ed0e5139542f54be31efa6d2b1dc
1 file changed +40 -4
hw/riscv/riscv-iommu.c
+40 -4
@@ -237,6 +237,25 @@ static bool riscv_iommu_msi_check(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
237 return true;
238 }
239
240 +/* Returns the NAPOT page mask, or 0 for reserved encodings. */
241 +static hwaddr riscv_iommu_napot_page_mask(hwaddr ppn, hwaddr addr, hwaddr *out)
242 +{
243 + int napot_bits = ctz64(ppn) + 1;
244 + hwaddr napot_mask, page_mask;
245 +
246 + /* The spec only defines 64KB (napot_bits == 4) */
247 + if (napot_bits != 4) {
248 + return 0;
249 + }
250 +
251 + napot_mask = (1ULL << napot_bits) - 1;
252 + page_mask = PPN_PHYS(napot_mask) | (TARGET_PAGE_SIZE - 1);
253 +
254 + *out = PPN_PHYS(ppn & ~napot_mask) | (addr & page_mask);
255 +
256 + return page_mask;
257 +}
258 +
259 /*
260 * RISCV IOMMU Address Translation Lookup - Page Table Walk
261 *
@@ -458,9 +477,20 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
477 } else {
478 /* Leaf PTE, translation completed. */
479 sc[pass].step = sc[pass].levels;
461 - base = PPN_PHYS(ppn) | (addr & ((1ULL << va_skip) - 1));
462 - /* Update address mask based on smallest translation granularity */
463 - iotlb->addr_mask &= (1ULL << va_skip) - 1;
480 +
481 + if (pte & PTE_N) {
482 + hwaddr mask = riscv_iommu_napot_page_mask(ppn, addr, &base);
483 +
484 + if (!mask) {
485 + break;
486 + }
487 + iotlb->addr_mask &= mask;
488 + } else {
489 + base = PPN_PHYS(ppn) | (addr & ((1ULL << va_skip) - 1));
490 + /* Update address mask based on smallest translation granularity */
491 + iotlb->addr_mask &= (1ULL << va_skip) - 1;
492 + }
493 +
494 /* Continue with S-Stage translation? */
495 if (pass && sc[0].step != sc[0].levels) {
496 pass = S_STAGE;
@@ -997,7 +1027,13 @@ static MemTxResult pdt_memory_read(RISCVIOMMUState *s,
1027 return MEMTX_ACCESS_ERROR; /* Misaligned PPN */
1028 } else {
1029 /* Leaf PTE, translation completed. */
1000 - base = PPN_PHYS(ppn) | (addr & ((1ULL << va_skip) - 1));
1030 + if (pte & PTE_N) {
1031 + if (!riscv_iommu_napot_page_mask(ppn, addr, &base)) {
1032 + return MEMTX_ACCESS_ERROR;
1033 + }
1034 + } else {
1035 + base = PPN_PHYS(ppn) | (addr & ((1ULL << va_skip) - 1));
1036 + }
1037 break;
1038 }
1039