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)$<) + # 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
