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
> 

Reply via email to