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 <[email protected]> > --- > 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 [email protected] > + $(LD) $(LDFLAGS) [email protected] -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/[email protected]/ 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 >
