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


Reply via email to