From: Max Chou <max.chou@sifive.com>
To: qemu-devel@nongnu.org, qemu-riscv@nongnu.org,
richard.henderson@linaro.org
Cc: Palmer Dabbelt <palmer@dabbelt.com>,
Alistair Francis <alistair.francis@wdc.com>,
Weiwei Li <liwei1518@gmail.com>,
Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>,
Liu Zhiwei <zhiwei_liu@linux.alibaba.com>,
Chao Liu <chao.liu@processmission.com>,
Max Chou <max.chou@sifive.com>
Subject: [PATCH 4/6] tests/tcg/riscv64: Add vector unit-stride PMP test
Date: Wed, 9 Sep 2026 16:41:51 +0800 [thread overview]
Message-ID: <20260909084154.223529-5-max.chou@sifive.com> (raw)
In-Reply-To: <20260909084154.223529-1-max.chou@sifive.com>
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
next prev parent reply other threads:[~2026-09-09 8:44 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` [PATCH 3/6] tests/tcg/riscv64: Add vector masked fault-only-first PMP test Max Chou
2026-09-11 5:49 ` Chao Liu
2026-09-09 8:41 ` Max Chou [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260909084154.223529-5-max.chou@sifive.com \
--to=max.chou@sifive.com \
--cc=alistair.francis@wdc.com \
--cc=chao.liu@processmission.com \
--cc=daniel.barboza@oss.qualcomm.com \
--cc=liwei1518@gmail.com \
--cc=palmer@dabbelt.com \
--cc=qemu-devel@nongnu.org \
--cc=qemu-riscv@nongnu.org \
--cc=richard.henderson@linaro.org \
--cc=zhiwei_liu@linux.alibaba.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.