* [PATCH 1/6] target/riscv: Match PMP entries lying inside the checked range
2026-09-09 8:41 [PATCH 0/6] tests/tcg/riscv64: Add system mode rvv ld/st tests Max Chou
@ 2026-09-09 8:41 ` Max Chou
2026-09-09 8:41 ` [PATCH 2/6] target/riscv: rvv: Probe unit-stride accesses by the first element Max Chou
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Max Chou @ 2026-09-09 8:41 UTC (permalink / raw)
To: qemu-devel, qemu-riscv, richard.henderson
Cc: Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Max Chou
pmp_hart_has_privs decides the permissions of a byte range by testing
only the two endpoint bytes against each PMP entry. An active entry
lying strictly between the endpoints matches neither byte and is
skipped, so a lower-priority entry silently grants an access that the
higher-priority entry must deny.
Replace the endpoint sampling with interval tests, mirroring the
predicate pmp_get_tlb_size already uses.
Signed-off-by: Max Chou <max.chou@sifive.com>
---
target/riscv/tcg/pmp.c | 31 ++++++++++---------------------
1 file changed, 10 insertions(+), 21 deletions(-)
diff --git a/target/riscv/tcg/pmp.c b/target/riscv/tcg/pmp.c
index 41b55519a8e..94224920d8d 100644
--- a/target/riscv/tcg/pmp.c
+++ b/target/riscv/tcg/pmp.c
@@ -299,20 +299,6 @@ void pmp_update_rule_nums(CPURISCVState *env)
}
}
-static int pmp_is_in_range(CPURISCVState *env, int pmp_index, hwaddr addr)
-{
- int result = 0;
-
- if ((addr >= env->pmp_state.addr[pmp_index].sa) &&
- (addr <= env->pmp_state.addr[pmp_index].ea)) {
- result = 1;
- } else {
- result = 0;
- }
-
- return result;
-}
-
/*
* Check if the address has required RWX privs when no PMP entry is matched.
*/
@@ -387,8 +373,8 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr,
{
int i = 0;
int pmp_size = 0;
- hwaddr s = 0;
- hwaddr e = 0;
+ hwaddr last = 0;
+ bool size_known = size != 0;
uint8_t pmp_regions = riscv_cpu_cfg(env)->pmp_regions;
/* Short cut if no rules */
@@ -414,12 +400,15 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr,
* 1.10 draft priv spec states there is an implicit order
* from low to high
*/
+ last = addr + pmp_size - 1;
+
for (i = 0; i < pmp_regions; i++) {
- s = pmp_is_in_range(env, i, addr);
- e = pmp_is_in_range(env, i, addr + pmp_size - 1);
+ hwaddr sa = env->pmp_state.addr[i].sa;
+ hwaddr ea = env->pmp_state.addr[i].ea;
+ bool contains = (sa <= addr) && (last <= ea);
+ bool overlaps = (addr <= ea) && (sa <= last);
- /* partially inside */
- if ((s + e) == 1) {
+ if (size_known && overlaps && !contains) {
qemu_log_mask(LOG_GUEST_ERROR,
"pmp violation - access is partially inside\n");
*allowed_privs = 0;
@@ -430,7 +419,7 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr,
const uint8_t a_field =
pmp_get_a_field(env->pmp_state.pmp[i].cfg_reg);
- if (((s + e) == 2) && (PMP_AMATCH_OFF != a_field)) {
+ if (contains && (PMP_AMATCH_OFF != a_field)) {
/*
* If the PMP entry is not off and the address is in range,
* do the priv check
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 2/6] target/riscv: rvv: Probe unit-stride accesses by the first element
2026-09-09 8:41 [PATCH 0/6] tests/tcg/riscv64: Add system mode rvv ld/st tests Max Chou
2026-09-09 8:41 ` [PATCH 1/6] target/riscv: Match PMP entries lying inside the checked range Max Chou
@ 2026-09-09 8:41 ` Max Chou
2026-09-09 8:41 ` [PATCH 3/6] tests/tcg/riscv64: Add vector masked fault-only-first PMP test Max Chou
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Max Chou @ 2026-09-09 8:41 UTC (permalink / raw)
To: qemu-devel, qemu-riscv, richard.henderson
Cc: Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Max Chou
Probe only the first access of the range with probe_access_full and
inspect the resulting lg_page_size: when the page is subdivided, fall
back to the per-element TLB path. Pages with uniform permissions keep
the direct host fast path.
Signed-off-by: Max Chou <max.chou@sifive.com>
---
target/riscv/tcg/vector_helper.c | 45 ++++++++++++++++++++++++++++----
1 file changed, 40 insertions(+), 5 deletions(-)
diff --git a/target/riscv/tcg/vector_helper.c b/target/riscv/tcg/vector_helper.c
index efe10156daa..569da995482 100644
--- a/target/riscv/tcg/vector_helper.c
+++ b/target/riscv/tcg/vector_helper.c
@@ -406,6 +406,41 @@ static void vext_test_alignment(CPURISCVState *env, vaddr addr, uint32_t esz,
}
}
+static void *vext_probe_host_page(CPURISCVState *env, target_ulong addr,
+ target_ulong probe_bytes, uint32_t msize,
+ MMUAccessType access_type, int mmu_index,
+ uintptr_t ra)
+{
+#ifdef CONFIG_USER_ONLY
+ return probe_access(env, addr, probe_bytes, access_type, mmu_index, ra);
+#else
+ CPUTLBEntryFull *full;
+ void *host;
+ int flags;
+
+ flags = probe_access_full(env, addr, MIN(msize, probe_bytes),
+ access_type, mmu_index, false, &host, &full, ra);
+ if (flags || full->lg_page_size < TARGET_PAGE_BITS) {
+ return NULL;
+ }
+
+ if (access_type == MMU_DATA_STORE) {
+ /*
+ * The permissions are uniform across the page, so probing the
+ * first access has validated the whole range. It has only
+ * marked MIN(msize, probe_bytes) bytes as dirty, though, while
+ * the caller writes probe_bytes through the returned host
+ * pointer. Probe the whole range as well, so that
+ * notdirty_write invalidates every translation block that it
+ * overlaps and the migration dirty bitmap covers all of it.
+ */
+ return probe_access(env, addr, probe_bytes, access_type,
+ mmu_index, ra);
+ }
+ return host;
+#endif
+}
+
static void
vext_ldst_us_notail(void *vd, target_ulong base, CPURISCVState *env,
uint32_t log2_esz, uint32_t nf, uint32_t evl,
@@ -448,9 +483,9 @@ vext_ldst_us_notail(void *vd, target_ulong base, CPURISCVState *env,
page_split = -(addr | TARGET_PAGE_MASK);
/* Validate the first page is accessible. */
- host = probe_access(env, adjust_addr(env, addr),
- MIN(last, last_in_page) - addr + 1,
- access_type, mmu_index, ra);
+ host = vext_probe_host_page(env, adjust_addr(env, addr),
+ MIN(last, last_in_page) - addr + 1,
+ msize, access_type, mmu_index, ra);
/* Get number of complete elements in the first page. */
elems = MIN(page_split / msize, evl - i);
@@ -490,8 +525,8 @@ vext_ldst_us_notail(void *vd, target_ulong base, CPURISCVState *env,
/* Validate the second page is accessible. */
assert(i < evl);
elems = evl - i;
- host = probe_access(env, adjust_addr(env, addr), elems * msize,
- access_type, mmu_index, ra);
+ host = vext_probe_host_page(env, adjust_addr(env, addr), elems * msize,
+ msize, access_type, mmu_index, ra);
if (host) {
vext_page_ldst_us_host(vd, host, i, evl, nf, log2_esz,
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 3/6] tests/tcg/riscv64: Add vector masked fault-only-first PMP test
2026-09-09 8:41 [PATCH 0/6] tests/tcg/riscv64: Add system mode rvv ld/st tests Max Chou
2026-09-09 8:41 ` [PATCH 1/6] target/riscv: Match PMP entries lying inside the checked range Max Chou
2026-09-09 8:41 ` [PATCH 2/6] target/riscv: rvv: Probe unit-stride accesses by the first element Max Chou
@ 2026-09-09 8:41 ` Max Chou
2026-09-11 5:49 ` Chao Liu
2026-09-09 8:41 ` [PATCH 4/6] tests/tcg/riscv64: Add vector unit-stride " Max Chou
` (2 subsequent siblings)
5 siblings, 1 reply; 8+ messages in thread
From: Max Chou @ 2026-09-09 8:41 UTC (permalink / raw)
To: qemu-devel, qemu-riscv, richard.henderson
Cc: Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Max Chou
Add a bare-metal test for masked vector fault-only-first loads across
locked NA4 PMP regions inside one page. The test covers masked-off
elements, a faulting active element 0, and later active faults that
shorten vl.
Signed-off-by: Max Chou <max.chou@sifive.com>
---
tests/tcg/riscv64/Makefile.softmmu-target | 13 ++
tests/tcg/riscv64/rvv-ldst.inc | 91 ++++++++
tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S | 259 ++++++++++++++++++++++
3 files changed, 363 insertions(+)
create mode 100644 tests/tcg/riscv64/rvv-ldst.inc
create mode 100644 tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S
diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target
index f2c75abd57a..0fdf242f735 100644
--- a/tests/tcg/riscv64/Makefile.softmmu-target
+++ b/tests/tcg/riscv64/Makefile.softmmu-target
@@ -85,5 +85,18 @@ run-test-vle32ff: test-vle32ff
$(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true $(QEMU_OPTS)$<)
test-vle32ff: CFLAGS += -march=rv64gcv
+RVV_LDST_MARCH = -march=rv64gcv
+RVV_LDST_TESTS = test-rvv-ldst-ff-pmp
+CLEANFILES += $(RVV_LDST_TESTS)
+
+$(RVV_LDST_TESTS): %: %.S rvv-ldst.inc $(LINK_SCRIPT)
+ $(CC) $(CFLAGS) $(RVV_LDST_MARCH) $< -Wa,--noexecstack -c -o $@.o
+ $(LD) $(LDFLAGS) $@.o -o $@
+
+EXTRA_RUNS += run-test-rvv-ldst-ff-pmp
+
+run-test-rvv-ldst-ff-pmp: test-rvv-ldst-ff-pmp
+ $(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0$(comma)rvv_ta_all_1s=true$(comma)rvv_ma_all_1s=true $(QEMU_OPTS)$<)
+
# We don't currently support the multiarch system tests
undefine MULTIARCH_TESTS
diff --git a/tests/tcg/riscv64/rvv-ldst.inc b/tests/tcg/riscv64/rvv-ldst.inc
new file mode 100644
index 00000000000..061f330abef
--- /dev/null
+++ b/tests/tcg/riscv64/rvv-ldst.inc
@@ -0,0 +1,91 @@
+/*
+ * Common support for bare-metal RVV load/store regressions
+ *
+ * Register contract: these macros use t0, t1, t5 and t6 as scratch and
+ * keep the current case number in s11. ASSERT_EQ and CHECK_VELEM hold
+ * their expected value in t6 across a branch, so a trap handler that can
+ * run in between must leave t6 alone; use t5 and s5 for that instead.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+ .option norelax
+ .option norvc
+
+ .macro RVV_ENABLE
+ li t0, 0x6600
+ csrs mstatus, t0
+ csrw vcsr, zero
+ .endm
+
+ .macro ASSERT_EQ actual, expected
+ li t6, \expected
+ bne \actual, t6, fail
+ .endm
+
+ .macro CASE number
+ li s11, \number
+ .endm
+
+ .macro SEMI_EXIT
+ lla a1, semiargs
+ li t0, 0x20026
+ sd t0, 0(a1)
+ sd a0, 8(a1)
+ li a0, 0x20
+ .balign 16
+ slli zero, zero, 0x1f
+ ebreak
+ srai zero, zero, 0x7
+ j .
+ .endm
+
+ .macro FAIL
+fail:
+ mv a0, s11
+ bnez a0, 1f
+ li a0, 1
+1:
+ j exit
+ .endm
+
+ /* Pre-fill selected registers with a sentinel neither data nor 1s. */
+ .macro PREFILL vd=, vl=4, sew=e32, value=0x05050505
+ vsetivli zero, \vl, \sew, m1, ta, ma
+ li t0, \value
+ .ifb \vd
+ vmv.v.x v2, t0
+ vmv.v.x v3, t0
+ .else
+ vmv.v.x \vd, t0
+ .endif
+ .endm
+
+ /* Set the low mask bits of v0 to \val. */
+ .macro SET_MASK val
+ vsetivli zero, 1, e8, m1, ta, ma
+ li t0, \val
+ vmv.s.x v0, t0
+ .endm
+
+ /* Assert element \idx of \vsrc (e32) equals \expected. */
+ .macro CHECK_VELEM vsrc, idx, expected
+ vsetivli zero, 4, e32, m1, ta, ma
+ vslidedown.vi v8, \vsrc, \idx
+ vmv.x.s t0, v8
+ li t6, \expected
+ bne t0, t6, fail
+ .endm
+
+ /* Assert that no trap has been taken since the last check. */
+ .macro CHECK_NO_TRAP
+ bne s2, s4, fail
+ .endm
+
+ /* Assert that exactly one expected trap has been taken. */
+ .macro CHECK_TRAP
+ addi s4, s4, 1
+ bne s2, s4, fail
+ li s0, 0
+ li s1, 0
+ .endm
diff --git a/tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S b/tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S
new file mode 100644
index 00000000000..80b48965625
--- /dev/null
+++ b/tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S
@@ -0,0 +1,259 @@
+/*
+ * RISC-V vector masked fault-only-first with PMP tests
+ *
+ * PMP permissions may change at NA4 (4-byte) granularity inside one
+ * target page, matching one e32 element exactly. A masked-off body
+ * element performs no memory access, so a read-denied PMP region under
+ * a masked-off element must not fault.
+ *
+ * Runs with rvv_ta_all_1s=true and rvv_ma_all_1s=true so that with a
+ * "ta, ma" vtype every masked-off and tail element must read back as
+ * all-1s, distinct from the 0x05050505 sentinel and the loaded data.
+ *
+ * PMP layout (locked entries, lowest number wins; everything outside
+ * the test page is unmatched and so fully accessible from M-mode):
+ * pmp0: NA4 buf_a+4, L, --- deny element 1 of buf_a
+ * pmp1: NA4 buf_b+0, L, --- deny element 0 of buf_b
+ * pmp2: NA4 buf_c+8, L, --- deny element 2 of buf_c
+ * pmp3: NAPOT test page, L, R lower-priority page allow
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+ #include "rvv-ldst.inc"
+
+ .text
+ .global _start
+_start:
+ RVV_ENABLE
+ lla t0, trap_handler
+ csrw mtvec, t0
+
+ /*
+ * Trap handler protocol:
+ * s0: expected mcause (0: no trap expected)
+ * s1: expected mtval (-1 accepts any value)
+ * s2: traps taken
+ * s3: vstart seen at last trap
+ * s4: traps expected
+ * s5: mtval seen at last trap
+ */
+ li s0, 0
+ li s1, 0
+ li s2, 0
+ li s3, -1
+ li s4, 0
+
+ /* Program the locked PMP entries; single locking cfg write last. */
+ lla t0, buf_a + 4
+ srli t0, t0, 2
+ csrw pmpaddr0, t0
+ lla t0, buf_b
+ srli t0, t0, 2
+ csrw pmpaddr1, t0
+ lla t0, buf_c + 8
+ srli t0, t0, 2
+ csrw pmpaddr2, t0
+ lla t0, pmp_page
+ srli t0, t0, 2
+ ori t0, t0, 0x1ff
+ csrw pmpaddr3, t0
+ li t0, 0x99909090
+ csrw pmpcfg0, t0
+
+ /*
+ * Case 1: sanity: the NA4 deny is in effect for a scalar load.
+ */
+ CASE 1
+ li s0, 5
+ li s1, -1
+ lla t1, buf_a
+ lw t0, 4(t1)
+ CHECK_TRAP
+
+ /*
+ * Case 2: denied bytes lie only under masked-off element 1: no trap.
+ * QEMU retains vl at 3 for this successful access.
+ */
+ CASE 2
+ PREFILL
+ SET_MASK 0b101
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_a
+ vle32ff.v v2, (a0), v0.t
+ CHECK_NO_TRAP
+ csrr t0, vl
+ ASSERT_EQ t0, 3
+ csrr t0, vstart
+ bnez t0, fail
+ CHECK_VELEM v2, 0, 0x00aa0000
+ CHECK_VELEM v2, 1, -1
+ CHECK_VELEM v2, 2, 0x00aa0002
+ CHECK_VELEM v2, 3, -1
+
+ /*
+ * Case 3: active element 0 denied: trap, vstart 0.
+ */
+ CASE 3
+ PREFILL
+ li s0, 5
+ li s1, -1
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_b
+ vle32ff.v v2, (a0)
+ CHECK_TRAP
+ bnez s3, fail
+
+ /*
+ * Case 4: masked-off element 0 over denied bytes: no trap.
+ * retains vl at 3 and loads elements 1 and 2.
+ */
+ CASE 4
+ PREFILL
+ SET_MASK 0b110
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_b
+ vle32ff.v v2, (a0), v0.t
+ CHECK_NO_TRAP
+ csrr t0, vl
+ ASSERT_EQ t0, 3
+ CHECK_VELEM v2, 0, -1
+ CHECK_VELEM v2, 1, 0x00bb0001
+ CHECK_VELEM v2, 2, 0x00bb0002
+ CHECK_VELEM v2, 3, -1
+
+ /*
+ * Case 5: active element 2 denied, unmasked: no trap, vl 2.
+ */
+ CASE 5
+ PREFILL
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_c
+ vle32ff.v v2, (a0)
+ CHECK_NO_TRAP
+ csrr t0, vl
+ ASSERT_EQ t0, 2
+ CHECK_VELEM v2, 0, 0x00cc0000
+ CHECK_VELEM v2, 1, 0x00cc0001
+ CHECK_VELEM v2, 2, -1
+ CHECK_VELEM v2, 3, -1
+
+ /*
+ * Case 6: masked-off element 0, active element 2 denied: vl 2.
+ */
+ CASE 6
+ PREFILL
+ SET_MASK 0b110
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_c
+ vle32ff.v v2, (a0), v0.t
+ CHECK_NO_TRAP
+ csrr t0, vl
+ ASSERT_EQ t0, 2
+ CHECK_VELEM v2, 0, -1
+ CHECK_VELEM v2, 1, 0x00cc0001
+ CHECK_VELEM v2, 2, -1
+ CHECK_VELEM v2, 3, -1
+
+ /*
+ * Case 7: nf=2 segments, masked-off segment 0 covers the denied bytes at
+ * buf_b: no trap. retains vl at 3 and loads segments 1 and 2.
+ */
+ CASE 7
+ PREFILL
+ SET_MASK 0b110
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_b
+ vlseg2e32ff.v v2, (a0), v0.t
+ CHECK_NO_TRAP
+ csrr t0, vl
+ ASSERT_EQ t0, 3
+ CHECK_VELEM v2, 0, -1
+ CHECK_VELEM v3, 0, -1
+ CHECK_VELEM v2, 1, 0x00bb0002
+ CHECK_VELEM v3, 1, 0x00bb0003
+ CHECK_VELEM v2, 2, 0x00bb0004
+ CHECK_VELEM v3, 2, 0x00bb0005
+ CHECK_VELEM v2, 3, -1
+ CHECK_VELEM v3, 3, -1
+
+ /*
+ * Case 8: nf=2 segments, unmasked, field 0 of segment 1 denied
+ * at buf_c+8: no trap, vl truncates to 1, segment 0 loaded.
+ */
+ CASE 8
+ PREFILL
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_c
+ vlseg2e32ff.v v2, (a0)
+ CHECK_NO_TRAP
+ csrr t0, vl
+ ASSERT_EQ t0, 1
+ CHECK_VELEM v2, 0, 0x00cc0000
+ CHECK_VELEM v3, 0, 0x00cc0001
+ CHECK_VELEM v2, 1, -1
+ CHECK_VELEM v3, 1, -1
+ CHECK_VELEM v2, 2, -1
+ CHECK_VELEM v3, 2, -1
+ CHECK_VELEM v2, 3, -1
+ CHECK_VELEM v3, 3, -1
+
+ /*
+ * Case 9: the denied masked-off element is the last element of the
+ * accessed range, so a range probe cannot miss it as an interior
+ * region: no trap. retains vl at 2.
+ */
+ CASE 9
+ PREFILL
+ SET_MASK 0b01
+ vsetivli zero, 2, e32, m1, ta, ma
+ lla a0, buf_a
+ vle32ff.v v2, (a0), v0.t
+ CHECK_NO_TRAP
+ csrr t0, vl
+ ASSERT_EQ t0, 2
+ CHECK_VELEM v2, 0, 0x00aa0000
+ CHECK_VELEM v2, 1, -1
+ CHECK_VELEM v2, 2, -1
+ CHECK_VELEM v2, 3, -1
+
+ li a0, 0
+exit:
+ SEMI_EXIT
+ FAIL
+
+ .balign 4
+trap_handler:
+ csrr t5, mcause
+ bne t5, s0, fail
+ csrr s5, mtval
+ li t5, -1
+ beq s1, t5, 1f
+ bne s5, s1, fail
+1:
+ csrr s3, vstart
+ addi s2, s2, 1
+ csrw vstart, zero
+ csrr t5, mepc
+ addi t5, t5, 4
+ csrw mepc, t5
+ mret
+
+ .data
+ .balign 16
+semiargs: .space 16
+
+ /* One dedicated page; the locked NAPOT entry grants R only. */
+ .balign 4096
+pmp_page:
+buf_a:
+ .word 0x00aa0000, 0x00aa0001, 0x00aa0002, 0x00aa0003
+ .word 0x00aa0004, 0x00aa0005
+ .skip 40
+buf_b:
+ .word 0x00bb0000, 0x00bb0001, 0x00bb0002, 0x00bb0003
+ .word 0x00bb0004, 0x00bb0005
+ .skip 40
+buf_c:
+ .word 0x00cc0000, 0x00cc0001, 0x00cc0002, 0x00cc0003
+ .word 0x00cc0004, 0x00cc0005
+ .skip 3944
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH 3/6] tests/tcg/riscv64: Add vector masked fault-only-first PMP test
2026-09-09 8:41 ` [PATCH 3/6] tests/tcg/riscv64: Add vector masked fault-only-first PMP test Max Chou
@ 2026-09-11 5:49 ` Chao Liu
0 siblings, 0 replies; 8+ messages in thread
From: Chao Liu @ 2026-09-11 5:49 UTC (permalink / raw)
To: Max Chou
Cc: qemu-devel, qemu-riscv, richard.henderson, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei
Hi Max,
On Wed, Sep 09, 2026 at 04:41:50PM +0800, Max Chou wrote:
> Add a bare-metal test for masked vector fault-only-first loads across
> locked NA4 PMP regions inside one page. The test covers masked-off
> elements, a faulting active element 0, and later active faults that
> shorten vl.
>
> Signed-off-by: Max Chou <max.chou@sifive.com>
> ---
> tests/tcg/riscv64/Makefile.softmmu-target | 13 ++
> tests/tcg/riscv64/rvv-ldst.inc | 91 ++++++++
> tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S | 259 ++++++++++++++++++++++
> 3 files changed, 363 insertions(+)
> create mode 100644 tests/tcg/riscv64/rvv-ldst.inc
> create mode 100644 tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S
>
> diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target
> index f2c75abd57a..0fdf242f735 100644
> --- a/tests/tcg/riscv64/Makefile.softmmu-target
> +++ b/tests/tcg/riscv64/Makefile.softmmu-target
> @@ -85,5 +85,18 @@ run-test-vle32ff: test-vle32ff
> $(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true $(QEMU_OPTS)$<)
> test-vle32ff: CFLAGS += -march=rv64gcv
>
> +RVV_LDST_MARCH = -march=rv64gcv
> +RVV_LDST_TESTS = test-rvv-ldst-ff-pmp
> +CLEANFILES += $(RVV_LDST_TESTS)
> +
> +$(RVV_LDST_TESTS): %: %.S rvv-ldst.inc $(LINK_SCRIPT)
> + $(CC) $(CFLAGS) $(RVV_LDST_MARCH) $< -Wa,--noexecstack -c -o $@.o
> + $(LD) $(LDFLAGS) $@.o -o $@
> +
> +EXTRA_RUNS += run-test-rvv-ldst-ff-pmp
> +
> +run-test-rvv-ldst-ff-pmp: test-rvv-ldst-ff-pmp
> + $(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0$(comma)rvv_ta_all_1s=true$(comma)rvv_ma_all_1s=true $(QEMU_OPTS)$<)
> +
Just a heads-up, TCG tests are moving to Meson build, so this needs to be
based on:
https://lore.kernel.org/qemu-devel/20260818192309.22169-1-pierrick.bouvier@oss.qualcomm.com/
Thanks,
Chao
> # We don't currently support the multiarch system tests
> undefine MULTIARCH_TESTS
> diff --git a/tests/tcg/riscv64/rvv-ldst.inc b/tests/tcg/riscv64/rvv-ldst.inc
> new file mode 100644
> index 00000000000..061f330abef
> --- /dev/null
> +++ b/tests/tcg/riscv64/rvv-ldst.inc
> @@ -0,0 +1,91 @@
> +/*
> + * Common support for bare-metal RVV load/store regressions
> + *
> + * Register contract: these macros use t0, t1, t5 and t6 as scratch and
> + * keep the current case number in s11. ASSERT_EQ and CHECK_VELEM hold
> + * their expected value in t6 across a branch, so a trap handler that can
> + * run in between must leave t6 alone; use t5 and s5 for that instead.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> + .option norelax
> + .option norvc
> +
> + .macro RVV_ENABLE
> + li t0, 0x6600
> + csrs mstatus, t0
> + csrw vcsr, zero
> + .endm
> +
> + .macro ASSERT_EQ actual, expected
> + li t6, \expected
> + bne \actual, t6, fail
> + .endm
> +
> + .macro CASE number
> + li s11, \number
> + .endm
> +
> + .macro SEMI_EXIT
> + lla a1, semiargs
> + li t0, 0x20026
> + sd t0, 0(a1)
> + sd a0, 8(a1)
> + li a0, 0x20
> + .balign 16
> + slli zero, zero, 0x1f
> + ebreak
> + srai zero, zero, 0x7
> + j .
> + .endm
> +
> + .macro FAIL
> +fail:
> + mv a0, s11
> + bnez a0, 1f
> + li a0, 1
> +1:
> + j exit
> + .endm
> +
> + /* Pre-fill selected registers with a sentinel neither data nor 1s. */
> + .macro PREFILL vd=, vl=4, sew=e32, value=0x05050505
> + vsetivli zero, \vl, \sew, m1, ta, ma
> + li t0, \value
> + .ifb \vd
> + vmv.v.x v2, t0
> + vmv.v.x v3, t0
> + .else
> + vmv.v.x \vd, t0
> + .endif
> + .endm
> +
> + /* Set the low mask bits of v0 to \val. */
> + .macro SET_MASK val
> + vsetivli zero, 1, e8, m1, ta, ma
> + li t0, \val
> + vmv.s.x v0, t0
> + .endm
> +
> + /* Assert element \idx of \vsrc (e32) equals \expected. */
> + .macro CHECK_VELEM vsrc, idx, expected
> + vsetivli zero, 4, e32, m1, ta, ma
> + vslidedown.vi v8, \vsrc, \idx
> + vmv.x.s t0, v8
> + li t6, \expected
> + bne t0, t6, fail
> + .endm
> +
> + /* Assert that no trap has been taken since the last check. */
> + .macro CHECK_NO_TRAP
> + bne s2, s4, fail
> + .endm
> +
> + /* Assert that exactly one expected trap has been taken. */
> + .macro CHECK_TRAP
> + addi s4, s4, 1
> + bne s2, s4, fail
> + li s0, 0
> + li s1, 0
> + .endm
> diff --git a/tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S b/tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S
> new file mode 100644
> index 00000000000..80b48965625
> --- /dev/null
> +++ b/tests/tcg/riscv64/test-rvv-ldst-ff-pmp.S
> @@ -0,0 +1,259 @@
> +/*
> + * RISC-V vector masked fault-only-first with PMP tests
> + *
> + * PMP permissions may change at NA4 (4-byte) granularity inside one
> + * target page, matching one e32 element exactly. A masked-off body
> + * element performs no memory access, so a read-denied PMP region under
> + * a masked-off element must not fault.
> + *
> + * Runs with rvv_ta_all_1s=true and rvv_ma_all_1s=true so that with a
> + * "ta, ma" vtype every masked-off and tail element must read back as
> + * all-1s, distinct from the 0x05050505 sentinel and the loaded data.
> + *
> + * PMP layout (locked entries, lowest number wins; everything outside
> + * the test page is unmatched and so fully accessible from M-mode):
> + * pmp0: NA4 buf_a+4, L, --- deny element 1 of buf_a
> + * pmp1: NA4 buf_b+0, L, --- deny element 0 of buf_b
> + * pmp2: NA4 buf_c+8, L, --- deny element 2 of buf_c
> + * pmp3: NAPOT test page, L, R lower-priority page allow
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> + #include "rvv-ldst.inc"
> +
> + .text
> + .global _start
> +_start:
> + RVV_ENABLE
> + lla t0, trap_handler
> + csrw mtvec, t0
> +
> + /*
> + * Trap handler protocol:
> + * s0: expected mcause (0: no trap expected)
> + * s1: expected mtval (-1 accepts any value)
> + * s2: traps taken
> + * s3: vstart seen at last trap
> + * s4: traps expected
> + * s5: mtval seen at last trap
> + */
> + li s0, 0
> + li s1, 0
> + li s2, 0
> + li s3, -1
> + li s4, 0
> +
> + /* Program the locked PMP entries; single locking cfg write last. */
> + lla t0, buf_a + 4
> + srli t0, t0, 2
> + csrw pmpaddr0, t0
> + lla t0, buf_b
> + srli t0, t0, 2
> + csrw pmpaddr1, t0
> + lla t0, buf_c + 8
> + srli t0, t0, 2
> + csrw pmpaddr2, t0
> + lla t0, pmp_page
> + srli t0, t0, 2
> + ori t0, t0, 0x1ff
> + csrw pmpaddr3, t0
> + li t0, 0x99909090
> + csrw pmpcfg0, t0
> +
> + /*
> + * Case 1: sanity: the NA4 deny is in effect for a scalar load.
> + */
> + CASE 1
> + li s0, 5
> + li s1, -1
> + lla t1, buf_a
> + lw t0, 4(t1)
> + CHECK_TRAP
> +
> + /*
> + * Case 2: denied bytes lie only under masked-off element 1: no trap.
> + * QEMU retains vl at 3 for this successful access.
> + */
> + CASE 2
> + PREFILL
> + SET_MASK 0b101
> + vsetivli zero, 3, e32, m1, ta, ma
> + lla a0, buf_a
> + vle32ff.v v2, (a0), v0.t
> + CHECK_NO_TRAP
> + csrr t0, vl
> + ASSERT_EQ t0, 3
> + csrr t0, vstart
> + bnez t0, fail
> + CHECK_VELEM v2, 0, 0x00aa0000
> + CHECK_VELEM v2, 1, -1
> + CHECK_VELEM v2, 2, 0x00aa0002
> + CHECK_VELEM v2, 3, -1
> +
> + /*
> + * Case 3: active element 0 denied: trap, vstart 0.
> + */
> + CASE 3
> + PREFILL
> + li s0, 5
> + li s1, -1
> + vsetivli zero, 3, e32, m1, ta, ma
> + lla a0, buf_b
> + vle32ff.v v2, (a0)
> + CHECK_TRAP
> + bnez s3, fail
> +
> + /*
> + * Case 4: masked-off element 0 over denied bytes: no trap.
> + * retains vl at 3 and loads elements 1 and 2.
> + */
> + CASE 4
> + PREFILL
> + SET_MASK 0b110
> + vsetivli zero, 3, e32, m1, ta, ma
> + lla a0, buf_b
> + vle32ff.v v2, (a0), v0.t
> + CHECK_NO_TRAP
> + csrr t0, vl
> + ASSERT_EQ t0, 3
> + CHECK_VELEM v2, 0, -1
> + CHECK_VELEM v2, 1, 0x00bb0001
> + CHECK_VELEM v2, 2, 0x00bb0002
> + CHECK_VELEM v2, 3, -1
> +
> + /*
> + * Case 5: active element 2 denied, unmasked: no trap, vl 2.
> + */
> + CASE 5
> + PREFILL
> + vsetivli zero, 3, e32, m1, ta, ma
> + lla a0, buf_c
> + vle32ff.v v2, (a0)
> + CHECK_NO_TRAP
> + csrr t0, vl
> + ASSERT_EQ t0, 2
> + CHECK_VELEM v2, 0, 0x00cc0000
> + CHECK_VELEM v2, 1, 0x00cc0001
> + CHECK_VELEM v2, 2, -1
> + CHECK_VELEM v2, 3, -1
> +
> + /*
> + * Case 6: masked-off element 0, active element 2 denied: vl 2.
> + */
> + CASE 6
> + PREFILL
> + SET_MASK 0b110
> + vsetivli zero, 3, e32, m1, ta, ma
> + lla a0, buf_c
> + vle32ff.v v2, (a0), v0.t
> + CHECK_NO_TRAP
> + csrr t0, vl
> + ASSERT_EQ t0, 2
> + CHECK_VELEM v2, 0, -1
> + CHECK_VELEM v2, 1, 0x00cc0001
> + CHECK_VELEM v2, 2, -1
> + CHECK_VELEM v2, 3, -1
> +
> + /*
> + * Case 7: nf=2 segments, masked-off segment 0 covers the denied bytes at
> + * buf_b: no trap. retains vl at 3 and loads segments 1 and 2.
> + */
> + CASE 7
> + PREFILL
> + SET_MASK 0b110
> + vsetivli zero, 3, e32, m1, ta, ma
> + lla a0, buf_b
> + vlseg2e32ff.v v2, (a0), v0.t
> + CHECK_NO_TRAP
> + csrr t0, vl
> + ASSERT_EQ t0, 3
> + CHECK_VELEM v2, 0, -1
> + CHECK_VELEM v3, 0, -1
> + CHECK_VELEM v2, 1, 0x00bb0002
> + CHECK_VELEM v3, 1, 0x00bb0003
> + CHECK_VELEM v2, 2, 0x00bb0004
> + CHECK_VELEM v3, 2, 0x00bb0005
> + CHECK_VELEM v2, 3, -1
> + CHECK_VELEM v3, 3, -1
> +
> + /*
> + * Case 8: nf=2 segments, unmasked, field 0 of segment 1 denied
> + * at buf_c+8: no trap, vl truncates to 1, segment 0 loaded.
> + */
> + CASE 8
> + PREFILL
> + vsetivli zero, 3, e32, m1, ta, ma
> + lla a0, buf_c
> + vlseg2e32ff.v v2, (a0)
> + CHECK_NO_TRAP
> + csrr t0, vl
> + ASSERT_EQ t0, 1
> + CHECK_VELEM v2, 0, 0x00cc0000
> + CHECK_VELEM v3, 0, 0x00cc0001
> + CHECK_VELEM v2, 1, -1
> + CHECK_VELEM v3, 1, -1
> + CHECK_VELEM v2, 2, -1
> + CHECK_VELEM v3, 2, -1
> + CHECK_VELEM v2, 3, -1
> + CHECK_VELEM v3, 3, -1
> +
> + /*
> + * Case 9: the denied masked-off element is the last element of the
> + * accessed range, so a range probe cannot miss it as an interior
> + * region: no trap. retains vl at 2.
> + */
> + CASE 9
> + PREFILL
> + SET_MASK 0b01
> + vsetivli zero, 2, e32, m1, ta, ma
> + lla a0, buf_a
> + vle32ff.v v2, (a0), v0.t
> + CHECK_NO_TRAP
> + csrr t0, vl
> + ASSERT_EQ t0, 2
> + CHECK_VELEM v2, 0, 0x00aa0000
> + CHECK_VELEM v2, 1, -1
> + CHECK_VELEM v2, 2, -1
> + CHECK_VELEM v2, 3, -1
> +
> + li a0, 0
> +exit:
> + SEMI_EXIT
> + FAIL
> +
> + .balign 4
> +trap_handler:
> + csrr t5, mcause
> + bne t5, s0, fail
> + csrr s5, mtval
> + li t5, -1
> + beq s1, t5, 1f
> + bne s5, s1, fail
> +1:
> + csrr s3, vstart
> + addi s2, s2, 1
> + csrw vstart, zero
> + csrr t5, mepc
> + addi t5, t5, 4
> + csrw mepc, t5
> + mret
> +
> + .data
> + .balign 16
> +semiargs: .space 16
> +
> + /* One dedicated page; the locked NAPOT entry grants R only. */
> + .balign 4096
> +pmp_page:
> +buf_a:
> + .word 0x00aa0000, 0x00aa0001, 0x00aa0002, 0x00aa0003
> + .word 0x00aa0004, 0x00aa0005
> + .skip 40
> +buf_b:
> + .word 0x00bb0000, 0x00bb0001, 0x00bb0002, 0x00bb0003
> + .word 0x00bb0004, 0x00bb0005
> + .skip 40
> +buf_c:
> + .word 0x00cc0000, 0x00cc0001, 0x00cc0002, 0x00cc0003
> + .word 0x00cc0004, 0x00cc0005
> + .skip 3944
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 4/6] tests/tcg/riscv64: Add vector unit-stride PMP test
2026-09-09 8:41 [PATCH 0/6] tests/tcg/riscv64: Add system mode rvv ld/st tests Max Chou
` (2 preceding siblings ...)
2026-09-09 8:41 ` [PATCH 3/6] tests/tcg/riscv64: Add vector masked fault-only-first PMP test Max Chou
@ 2026-09-09 8:41 ` Max Chou
2026-09-09 8:41 ` [PATCH 5/6] tests/tcg/riscv64: Add vector fault-only-first page probe test Max Chou
2026-09-09 8:41 ` [PATCH 6/6] tests/tcg/riscv64: Add vector segment PMP region spanning test Max Chou
5 siblings, 0 replies; 8+ messages in thread
From: Max Chou @ 2026-09-09 8:41 UTC (permalink / raw)
To: qemu-devel, qemu-riscv, richard.henderson
Cc: Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Max Chou
Add a bare-metal test for locked NA4 PMP permissions lying inside
unit-stride access ranges. The test covers unmasked and masked reads
and writes, and checks that an interior deny is reported with the
exact faulting mtval and vstart rather than merely trapping somewhere
in the range.
Signed-off-by: Max Chou <max.chou@sifive.com>
---
tests/tcg/riscv64/Makefile.softmmu-target | 7 +-
tests/tcg/riscv64/test-rvv-ldst-us-pmp.S | 281 ++++++++++++++++++++++
2 files changed, 286 insertions(+), 2 deletions(-)
create mode 100644 tests/tcg/riscv64/test-rvv-ldst-us-pmp.S
diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target
index 0fdf242f735..9e0e0490753 100644
--- a/tests/tcg/riscv64/Makefile.softmmu-target
+++ b/tests/tcg/riscv64/Makefile.softmmu-target
@@ -86,17 +86,20 @@ run-test-vle32ff: test-vle32ff
test-vle32ff: CFLAGS += -march=rv64gcv
RVV_LDST_MARCH = -march=rv64gcv
-RVV_LDST_TESTS = test-rvv-ldst-ff-pmp
+RVV_LDST_TESTS = test-rvv-ldst-ff-pmp test-rvv-ldst-us-pmp
CLEANFILES += $(RVV_LDST_TESTS)
$(RVV_LDST_TESTS): %: %.S rvv-ldst.inc $(LINK_SCRIPT)
$(CC) $(CFLAGS) $(RVV_LDST_MARCH) $< -Wa,--noexecstack -c -o $@.o
$(LD) $(LDFLAGS) $@.o -o $@
-EXTRA_RUNS += run-test-rvv-ldst-ff-pmp
+EXTRA_RUNS += run-test-rvv-ldst-ff-pmp run-test-rvv-ldst-us-pmp
run-test-rvv-ldst-ff-pmp: test-rvv-ldst-ff-pmp
$(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0$(comma)rvv_ta_all_1s=true$(comma)rvv_ma_all_1s=true $(QEMU_OPTS)$<)
+run-test-rvv-ldst-us-pmp: test-rvv-ldst-us-pmp
+ $(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0$(comma)rvv_ta_all_1s=true$(comma)rvv_ma_all_1s=true $(QEMU_OPTS)$<)
+
# We don't currently support the multiarch system tests
undefine MULTIARCH_TESTS
diff --git a/tests/tcg/riscv64/test-rvv-ldst-us-pmp.S b/tests/tcg/riscv64/test-rvv-ldst-us-pmp.S
new file mode 100644
index 00000000000..521667628da
--- /dev/null
+++ b/tests/tcg/riscv64/test-rvv-ldst-us-pmp.S
@@ -0,0 +1,281 @@
+/*
+ * RISC-V vector unit-stride with PMP tests
+ *
+ * Runs with rvv_ta_all_1s=true and rvv_ma_all_1s=true so that with a
+ * "ta, ma" vtype QEMU fills every masked-off and tail element with
+ * all-1s, distinct from the 0x05050505 sentinel and the loaded data.
+ *
+ * PMP layout (locked entries, lowest number wins; everything outside
+ * the test page is unmatched and so fully accessible from M-mode):
+ * pmp0: NA4 buf_a+4, L, --- deny word 1 of buf_a
+ * pmp1: NA4 buf_b+8, L, --- deny word 2 of buf_b
+ * pmp2: NA4 buf_c+4, L, R-- word 1 of buf_c readable, no write
+ * pmp3: NAPOT test page, L, RW lower-priority page allow
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+ #include "rvv-ldst.inc"
+
+ /* Assert the word at \sym+\off (readable) still equals \expected. */
+ .macro CHECK_WORD sym, off, expected
+ lla t1, \sym
+ lw t0, \off(t1)
+ li t6, \expected
+ bne t0, t6, fail
+ .endm
+
+ .text
+ .global _start
+_start:
+ RVV_ENABLE
+ lla t0, trap_handler
+ csrw mtvec, t0
+
+ /*
+ * Trap handler protocol:
+ * s0: expected mcause (0: no trap expected)
+ * s1: expected mtval (-1: any mtval accepted)
+ * s2: traps taken s3: vstart seen at last trap
+ * s4: traps expected s5: mtval seen at last trap
+ */
+ li s0, 0
+ li s1, 0
+ li s2, 0
+ li s3, -1
+ li s4, 0
+ li s5, -1
+
+ /* Program the locked PMP entries; single locking cfg write last. */
+ lla t0, buf_a + 4
+ srli t0, t0, 2
+ csrw pmpaddr0, t0
+ lla t0, buf_b + 8
+ srli t0, t0, 2
+ csrw pmpaddr1, t0
+ lla t0, buf_c + 4
+ srli t0, t0, 2
+ csrw pmpaddr2, t0
+ lla t0, pmp_page
+ srli t0, t0, 2
+ ori t0, t0, 0x1ff
+ csrw pmpaddr3, t0
+ li t0, 0x000000009b919090
+ csrw pmpcfg0, t0
+
+ /*
+ * Case 1: sanity: the NA4 read deny traps a scalar load.
+ */
+ CASE 1
+ li s0, 5
+ li s1, -1
+ lla t1, buf_a
+ lw t0, 4(t1)
+ CHECK_TRAP
+
+ /*
+ * Case 2: sanity: the read-only NA4 word traps a scalar store and
+ * still reads back, proving stores can be verified via loads.
+ */
+ CASE 2
+ li s0, 7
+ li s1, -1
+ lla t1, buf_c
+ sw t1, 4(t1)
+ CHECK_TRAP
+ CHECK_WORD buf_c, 4, 0x00cc0001
+
+ /*
+ * Case 3: unmasked vle32.v, vl 3, read deny strictly inside the
+ * range at buf_a+4 (element 1): element 1 is active, so the access
+ * must raise a load access fault. A probe that samples only the range
+ * endpoints sees just the page allow and loads the denied word silently.
+ */
+ CASE 3
+ PREFILL
+ li s0, 5
+ li s1, -1
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_a
+ vle32.v v2, (a0)
+ CHECK_TRAP
+
+ /*
+ * Case 4: unmasked vle32.v with the denied word first in the
+ * range: element 0 faults and vstart is zero.
+ */
+ CASE 4
+ PREFILL
+ li s0, 5
+ li s1, -1
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_a + 4
+ vle32.v v2, (a0)
+ CHECK_TRAP
+ bnez s3, fail
+
+ /*
+ * Case 5: unmasked vle32.v with the denied word last in the range
+ * at buf_b+8 (element 2): must trap.
+ */
+ CASE 5
+ PREFILL
+ li s0, 5
+ li s1, -1
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_b
+ vle32.v v2, (a0)
+ CHECK_TRAP
+
+ /*
+ * Case 6: unmasked vse32.v, vl 3, write deny strictly inside the
+ * range at buf_c+4 (element 1): the protected word must remain
+ * unmodified. Element 0 must have been stored and element 2 must
+ * not, so that a "store every element whose own probe succeeds"
+ * implementation is rejected rather than passing on the protected
+ * word alone.
+ */
+ CASE 6
+ vsetivli zero, 4, e32, m1, ta, ma
+ lla a0, st_data_a
+ vle32.v v4, (a0)
+ li s0, 7
+ li s1, -1
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_c
+ vse32.v v4, (a0)
+ CHECK_TRAP
+ CHECK_WORD buf_c, 0, 0x11111111
+ CHECK_WORD buf_c, 4, 0x00cc0001
+ CHECK_WORD buf_c, 8, 0x00cc0002
+
+ /*
+ * Case 7: masked vle32.v with the read deny only under masked-off
+ * element 1: no access is performed there, so no trap and the
+ * mask-agnostic all-1s fill applies.
+ */
+ CASE 7
+ PREFILL
+ SET_MASK 0b101
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_a
+ vle32.v v2, (a0), v0.t
+ CHECK_NO_TRAP
+ CHECK_VELEM v2, 0, 0x00aa0000
+ CHECK_VELEM v2, 1, -1
+ CHECK_VELEM v2, 2, 0x00aa0002
+ CHECK_VELEM v2, 3, -1
+
+ /*
+ * Case 8: masked vse32.v with the write deny only under masked-off
+ * element 1: no trap, elements 0 and 2 stored, the protected word
+ * untouched.
+ */
+ CASE 8
+ vsetivli zero, 4, e32, m1, ta, ma
+ lla a0, st_data_b
+ vle32.v v4, (a0)
+ SET_MASK 0b101
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_c
+ vse32.v v4, (a0), v0.t
+ CHECK_NO_TRAP
+ CHECK_WORD buf_c, 0, 0x44444444
+ CHECK_WORD buf_c, 4, 0x00cc0001
+ CHECK_WORD buf_c, 8, 0x66666666
+
+ /*
+ * Case 9: like case 3 (read deny at element 1 of 3), the mtval should be
+ * the denied word's own address and vstart should be 1, not the range
+ * base and 0.
+ */
+ CASE 9
+ PREFILL
+ li s0, 5
+ lla s1, buf_a + 4
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_a
+ vle32.v v2, (a0)
+ CHECK_TRAP
+ ASSERT_EQ s3, 1
+
+ /*
+ * Case 10: like case 5 (read deny at element 2, the last of 3),
+ * confirming precise reporting also holds when the denied element
+ * is not the first one probed.
+ */
+ CASE 10
+ PREFILL
+ li s0, 5
+ lla s1, buf_b + 8
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_b
+ vle32.v v2, (a0)
+ CHECK_TRAP
+ ASSERT_EQ s3, 2
+
+ /*
+ * Case 11: like case 6 (write deny at element 1 of 3), on the
+ * store side. buf_c+8 still holds the 0x66666666 that case 8
+ * stored, so checking it here proves element 2 was not written
+ * after the trap.
+ */
+ CASE 11
+ vsetivli zero, 4, e32, m1, ta, ma
+ lla a0, st_data_a
+ vle32.v v4, (a0)
+ li s0, 7
+ lla s1, buf_c + 4
+ vsetivli zero, 3, e32, m1, ta, ma
+ lla a0, buf_c
+ vse32.v v4, (a0)
+ CHECK_TRAP
+ ASSERT_EQ s3, 1
+ CHECK_WORD buf_c, 0, 0x11111111
+ CHECK_WORD buf_c, 4, 0x00cc0001
+ CHECK_WORD buf_c, 8, 0x66666666
+
+ li a0, 0
+exit:
+ SEMI_EXIT
+ FAIL
+
+ .balign 4
+trap_handler:
+ csrr t5, mcause
+ bne t5, s0, fail
+ csrr s5, mtval
+ li t5, -1
+ beq s1, t5, 1f
+ bne s5, s1, fail
+1:
+ csrr s3, vstart
+ addi s2, s2, 1
+ csrw vstart, zero
+ csrr t5, mepc
+ addi t5, t5, 4
+ csrw mepc, t5
+ mret
+
+ .data
+ .balign 16
+semiargs: .space 16
+
+ /* Store source data; outside every PMP region. */
+st_data_a: .word 0x11111111, 0x22222222, 0x33333333, 0x77777777
+st_data_b: .word 0x44444444, 0x55555555, 0x66666666, 0x88888888
+
+ /* One dedicated page governed by the locked NAPOT RW entry. */
+ .balign 4096
+pmp_page:
+buf_a:
+ .word 0x00aa0000, 0x00aa0001, 0x00aa0002, 0x00aa0003
+ .word 0x00aa0004, 0x00aa0005
+ .skip 40
+buf_b:
+ .word 0x00bb0000, 0x00bb0001, 0x00bb0002, 0x00bb0003
+ .word 0x00bb0004, 0x00bb0005
+ .skip 40
+buf_c:
+ .word 0x00cc0000, 0x00cc0001, 0x00cc0002, 0x00cc0003
+ .word 0x00cc0004, 0x00cc0005
+ .skip 3944
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 5/6] tests/tcg/riscv64: Add vector fault-only-first page probe test
2026-09-09 8:41 [PATCH 0/6] tests/tcg/riscv64: Add system mode rvv ld/st tests Max Chou
` (3 preceding siblings ...)
2026-09-09 8:41 ` [PATCH 4/6] tests/tcg/riscv64: Add vector unit-stride " Max Chou
@ 2026-09-09 8:41 ` Max Chou
2026-09-09 8:41 ` [PATCH 6/6] tests/tcg/riscv64: Add vector segment PMP region spanning test Max Chou
5 siblings, 0 replies; 8+ messages in thread
From: Max Chou @ 2026-09-09 8:41 UTC (permalink / raw)
To: qemu-devel, qemu-riscv, richard.henderson
Cc: Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Max Chou
Add a bare-metal test for vector fault-only-first loads whose
first-element probe crosses a page boundary. The test covers a body
access that crosses the boundary after the first element, and a first
element that itself straddles the boundary, checking that both cases
report a full vl and vstart of 0 rather than faulting on a mapped page.
Signed-off-by: Max Chou <max.chou@sifive.com>
---
tests/tcg/riscv64/Makefile.softmmu-target | 9 ++-
tests/tcg/riscv64/test-rvv-ldst-ff-page.S | 83 +++++++++++++++++++++++
2 files changed, 90 insertions(+), 2 deletions(-)
create mode 100644 tests/tcg/riscv64/test-rvv-ldst-ff-page.S
diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target
index 9e0e0490753..93c4e58e0c2 100644
--- a/tests/tcg/riscv64/Makefile.softmmu-target
+++ b/tests/tcg/riscv64/Makefile.softmmu-target
@@ -86,14 +86,16 @@ run-test-vle32ff: test-vle32ff
test-vle32ff: CFLAGS += -march=rv64gcv
RVV_LDST_MARCH = -march=rv64gcv
-RVV_LDST_TESTS = test-rvv-ldst-ff-pmp test-rvv-ldst-us-pmp
+RVV_LDST_TESTS = test-rvv-ldst-ff-pmp test-rvv-ldst-us-pmp \
+ test-rvv-ldst-ff-page
CLEANFILES += $(RVV_LDST_TESTS)
$(RVV_LDST_TESTS): %: %.S rvv-ldst.inc $(LINK_SCRIPT)
$(CC) $(CFLAGS) $(RVV_LDST_MARCH) $< -Wa,--noexecstack -c -o $@.o
$(LD) $(LDFLAGS) $@.o -o $@
-EXTRA_RUNS += run-test-rvv-ldst-ff-pmp run-test-rvv-ldst-us-pmp
+EXTRA_RUNS += run-test-rvv-ldst-ff-pmp run-test-rvv-ldst-us-pmp \
+ run-test-rvv-ldst-ff-page
run-test-rvv-ldst-ff-pmp: test-rvv-ldst-ff-pmp
$(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0$(comma)rvv_ta_all_1s=true$(comma)rvv_ma_all_1s=true $(QEMU_OPTS)$<)
@@ -101,5 +103,8 @@ run-test-rvv-ldst-ff-pmp: test-rvv-ldst-ff-pmp
run-test-rvv-ldst-us-pmp: test-rvv-ldst-us-pmp
$(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0$(comma)rvv_ta_all_1s=true$(comma)rvv_ma_all_1s=true $(QEMU_OPTS)$<)
+run-test-rvv-ldst-ff-page: test-rvv-ldst-ff-page
+ $(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0 $(QEMU_OPTS)$<)
+
# We don't currently support the multiarch system tests
undefine MULTIARCH_TESTS
diff --git a/tests/tcg/riscv64/test-rvv-ldst-ff-page.S b/tests/tcg/riscv64/test-rvv-ldst-ff-page.S
new file mode 100644
index 00000000000..63b1e0ac8a8
--- /dev/null
+++ b/tests/tcg/riscv64/test-rvv-ldst-ff-page.S
@@ -0,0 +1,83 @@
+/*
+ * RISC-V vector fault-only-first probe page-crossing tests
+ *
+ * A first-element probe validates the mapping only for the bytes that
+ * the first (fault-only) element itself touches, then lets the
+ * remaining elements of the vector body fault normally. An
+ * implementation that instead probes the whole multi-element access
+ * range as one host operation aborts as soon as that range crosses a
+ * page boundary, even though every page involved is in fact mapped.
+ *
+ * Case 1 crosses the page boundary only in the vector body, after the
+ * first element. Case 2 crosses it inside the first element itself,
+ * so the first-element probe must span both pages it touches.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+ #include "rvv-ldst.inc"
+
+ .text
+ .global _start
+_start:
+ RVV_ENABLE
+ lla t0, trap_handler
+ csrw mtvec, t0
+
+ /*
+ * Case 1: the body crosses the page boundary; the first element does not.
+ */
+ CASE 1
+ li t0, 3
+ vsetvli t1, t0, e8, m1, tu, mu
+ lla a0, cross_segment
+ vlseg2e8ff.v v2, (a0)
+ csrr t0, vl
+ ASSERT_EQ t0, 3
+ csrr t0, vstart
+ ASSERT_EQ t0, 0
+ lla a0, output
+ vsseg2e8.v v2, (a0)
+ lbu t0, 5(a0)
+ ASSERT_EQ t0, 0x66
+
+ /*
+ * Case 2: the first segment element itself straddles the page boundary.
+ */
+ CASE 2
+ li t0, 3
+ vsetvli t1, t0, e8, m1, tu, mu
+ lla a0, straddle_segment
+ vlseg2e8ff.v v2, (a0)
+ csrr t0, vl
+ ASSERT_EQ t0, 3
+ csrr t0, vstart
+ ASSERT_EQ t0, 0
+ lla a0, output
+ vsseg2e8.v v2, (a0)
+ lbu t0, 5(a0)
+ ASSERT_EQ t0, 0xf6
+ li a0, 0
+exit:
+ SEMI_EXIT
+ FAIL
+
+ /*
+ * No trap is expected: a spurious fault from the page-crossing probe
+ * is the very thing under test. Report the case number rather than
+ * vectoring to the reset value of mtvec and hanging until the
+ * harness timeout.
+ */
+ .balign 4
+trap_handler:
+ j fail
+
+ .data
+ .balign 16
+semiargs: .space 16
+ .balign 4096
+ .space 4094
+cross_segment: .byte 0x11, 0x22, 0x33, 0x44, 0x55, 0x66
+output: .space 6
+ .balign 4096
+ .space 4095
+straddle_segment: .byte 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6/6] tests/tcg/riscv64: Add vector segment PMP region spanning test
2026-09-09 8:41 [PATCH 0/6] tests/tcg/riscv64: Add system mode rvv ld/st tests Max Chou
` (4 preceding siblings ...)
2026-09-09 8:41 ` [PATCH 5/6] tests/tcg/riscv64: Add vector fault-only-first page probe test Max Chou
@ 2026-09-09 8:41 ` Max Chou
5 siblings, 0 replies; 8+ messages in thread
From: Max Chou @ 2026-09-09 8:41 UTC (permalink / raw)
To: qemu-devel, qemu-riscv, richard.henderson
Cc: Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Max Chou
A segment load performs nf independent eew-sized accesses, so PMP checks
each field on its own. Probing the whole nf * eew segment as one range
instead reports a fault whenever a PMP boundary falls inside a segment.
Signed-off-by: Max Chou <max.chou@sifive.com>
---
tests/tcg/riscv64/Makefile.softmmu-target | 21 +++-
tests/tcg/riscv64/test-rvv-ldst-seg-pmp.S | 141 ++++++++++++++++++++++
2 files changed, 160 insertions(+), 2 deletions(-)
create mode 100644 tests/tcg/riscv64/test-rvv-ldst-seg-pmp.S
diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target
index 93c4e58e0c2..aea1e7fcb15 100644
--- a/tests/tcg/riscv64/Makefile.softmmu-target
+++ b/tests/tcg/riscv64/Makefile.softmmu-target
@@ -87,7 +87,8 @@ test-vle32ff: CFLAGS += -march=rv64gcv
RVV_LDST_MARCH = -march=rv64gcv
RVV_LDST_TESTS = test-rvv-ldst-ff-pmp test-rvv-ldst-us-pmp \
- test-rvv-ldst-ff-page
+ test-rvv-ldst-ff-page \
+ test-rvv-ldst-seg-pmp
CLEANFILES += $(RVV_LDST_TESTS)
$(RVV_LDST_TESTS): %: %.S rvv-ldst.inc $(LINK_SCRIPT)
@@ -95,7 +96,8 @@ $(RVV_LDST_TESTS): %: %.S rvv-ldst.inc $(LINK_SCRIPT)
$(LD) $(LDFLAGS) $@.o -o $@
EXTRA_RUNS += run-test-rvv-ldst-ff-pmp run-test-rvv-ldst-us-pmp \
- run-test-rvv-ldst-ff-page
+ run-test-rvv-ldst-ff-page \
+ run-test-rvv-ldst-seg-pmp
run-test-rvv-ldst-ff-pmp: test-rvv-ldst-ff-pmp
$(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0$(comma)rvv_ta_all_1s=true$(comma)rvv_ma_all_1s=true $(QEMU_OPTS)$<)
@@ -106,5 +108,20 @@ run-test-rvv-ldst-us-pmp: test-rvv-ldst-us-pmp
run-test-rvv-ldst-ff-page: test-rvv-ldst-ff-page
$(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0 $(QEMU_OPTS)$<)
+run-test-rvv-ldst-seg-pmp: test-rvv-ldst-seg-pmp
+ $(call run-test, $<, $(QEMU) -cpu rv64$(comma)v=true$(comma)vlen=128$(comma)elen=64$(comma)vext_spec=v1.0 $(QEMU_OPTS)$<)
+
+PMP_TESTS = test-pmp-interior-entry
+CLEANFILES += $(PMP_TESTS)
+
+$(PMP_TESTS): %: %.S rvv-ldst.inc $(LINK_SCRIPT)
+ $(CC) $(CFLAGS) -march=rv64gc_zicboz $< -Wa,--noexecstack -c -o $@.o
+ $(LD) $(LDFLAGS) $@.o -o $@
+
+EXTRA_RUNS += run-test-pmp-interior-entry
+
+run-test-pmp-interior-entry: test-pmp-interior-entry
+ $(call run-test, $<, $(QEMU) -cpu rv64$(comma)zicboz=true$(comma)cboz_blocksize=64 $(QEMU_OPTS)$<)
+
# We don't currently support the multiarch system tests
undefine MULTIARCH_TESTS
diff --git a/tests/tcg/riscv64/test-rvv-ldst-seg-pmp.S b/tests/tcg/riscv64/test-rvv-ldst-seg-pmp.S
new file mode 100644
index 00000000000..9b3703ba338
--- /dev/null
+++ b/tests/tcg/riscv64/test-rvv-ldst-seg-pmp.S
@@ -0,0 +1,141 @@
+/*
+ * RISC-V vector segment load spanning two PMP regions
+ *
+ * A segment load performs nf independent eew-sized accesses, so each
+ * field is checked against PMP on its own. A probe that presents the
+ * whole nf * eew segment as one range instead reports a fault whenever
+ * a PMP boundary falls inside the segment, even though every access the
+ * instruction actually performs is permitted.
+ *
+ * PMP layout (locked entries, lowest number wins; everything outside
+ * the test page is unmatched and so fully accessible from M-mode):
+ * pmp0: NA4 buf+0, L, R-- field 0 readable
+ * pmp1: NA4 buf+4, L, R-- field 1 readable, adjacent to pmp0
+ * pmp2: NAPOT test page, L, RW- lower-priority page allow
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+ #include "rvv-ldst.inc"
+
+ .text
+ .global _start
+_start:
+ RVV_ENABLE
+ lla t0, trap_handler
+ csrw mtvec, t0
+
+ /*
+ * Trap handler protocol:
+ * s0: expected mcause (0: no trap expected)
+ * s1: expected mtval (-1: any mtval accepted)
+ * s2: traps taken s3: vstart seen at last trap
+ * s4: traps expected s5: mtval seen at last trap
+ */
+ li s0, 0
+ li s1, 0
+ li s2, 0
+ li s3, -1
+ li s4, 0
+
+ /* Program the locked PMP entries; single locking cfg write last. */
+ lla t0, buf
+ srli t0, t0, 2
+ csrw pmpaddr0, t0
+ lla t0, buf + 4
+ srli t0, t0, 2
+ csrw pmpaddr1, t0
+ lla t0, pmp_page
+ srli t0, t0, 2
+ ori t0, t0, 0x1ff
+ csrw pmpaddr2, t0
+ li t0, 0x9b9191
+ csrw pmpcfg0, t0
+
+ /* Case 1: a 4-byte load fully inside pmp0 is granted. */
+ CASE 1
+ lla t1, buf
+ lw t0, 0(t1)
+ CHECK_NO_TRAP
+ ASSERT_EQ t0, 0x00dd0000
+
+ /* Case 2: a 4-byte load fully inside pmp1 is granted. */
+ CASE 2
+ lla t1, buf
+ lw t0, 4(t1)
+ CHECK_NO_TRAP
+ ASSERT_EQ t0, 0x00dd0001
+
+ /*
+ * Case 3: control. One 8-byte access covers both entries and is
+ * contained by neither, so it must fault however permissive the
+ * two entries are on their own.
+ */
+ CASE 3
+ li s0, 5
+ lla s1, buf
+ lla t1, buf
+ ld t0, 0(t1)
+ CHECK_TRAP
+
+ /*
+ * Case 4: vlseg2e32.v performs one 4-byte access per field, each
+ * contained by its own entry, so the segment must load. A probe
+ * covering the whole 8-byte segment reports a load access fault
+ * here instead.
+ */
+ CASE 4
+ PREFILL
+ vsetivli zero, 1, e32, m1, ta, ma
+ lla a0, buf
+ vlseg2e32.v v2, (a0)
+ CHECK_NO_TRAP
+ CHECK_VELEM v2, 0, 0x00dd0000
+ CHECK_VELEM v3, 0, 0x00dd0001
+
+ /*
+ * Case 5: the same on the fault-only-first path, which must not
+ * truncate vl either.
+ */
+ CASE 5
+ PREFILL
+ vsetivli zero, 1, e32, m1, ta, ma
+ lla a0, buf
+ vlseg2e32ff.v v2, (a0)
+ csrr t2, vl
+ CHECK_NO_TRAP
+ ASSERT_EQ t2, 1
+ CHECK_VELEM v2, 0, 0x00dd0000
+ CHECK_VELEM v3, 0, 0x00dd0001
+
+ li a0, 0
+exit:
+ SEMI_EXIT
+ FAIL
+
+ .balign 4
+trap_handler:
+ csrr t5, mcause
+ bne t5, s0, fail
+ csrr s5, mtval
+ li t5, -1
+ beq s1, t5, 1f
+ bne s5, s1, fail
+1:
+ csrr s3, vstart
+ addi s2, s2, 1
+ csrw vstart, zero
+ csrr t5, mepc
+ addi t5, t5, 4
+ csrw mepc, t5
+ mret
+
+ .data
+ .balign 16
+semiargs: .space 16
+
+ /* One dedicated page governed by the locked NAPOT RW entry. */
+ .balign 4096
+pmp_page:
+buf:
+ .word 0x00dd0000, 0x00dd0001, 0x00dd0002, 0x00dd0003
+ .skip 4080
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread