Add a bare-metal M-mode assembly test (test-smmpt.S) that validates the
Smmpt43 MPT lookup and permission enforcement against SMMTT v0.4.9.
The test builds a 3-level Smmpt43 MPT in RAM and uses the MPRV trick
(mstatus.MPRV=1, MPP=S, satp=Bare) so that data accesses are subject to
MPT checks while instruction fetches remain in M-mode (which bypasses
the MPT). PMP is configured to grant all permissions so that only the
MPT gates the accesses.
Coverage: mfence.pa in M-mode; every XWR encoding (RW, no-access,
R-only, R+X, RWX, the reserved 010/110 encodings and X-only) checked for
both load and store, verifying the correct exception cause (load access
fault = 5, store/AMO access fault = 7); an invalid (V=0) leaf; a leaf
with a reserved bit set; and NAPOT leaves with a valid and a reserved G
field.
The mode-independent harness (MPRV helpers, per-access check/verify
subroutines, the M-mode trap handler and the semihosting exit) lives in
a shared smmpt-common.S so the Smmpt52/Smmpt64/Smmpt34 tests can reuse
it.
Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com>
---
tests/tcg/riscv64/Makefile.softmmu-target | 4 +
tests/tcg/riscv64/smmpt-common.S | 256 ++++++++++++++++++++++
tests/tcg/riscv64/test-smmpt.S | 85 +++++++
3 files changed, 345 insertions(+)
create mode 100644 tests/tcg/riscv64/smmpt-common.S
create mode 100644 tests/tcg/riscv64/test-smmpt.S
diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target
index 82be8a2c91..18da9c6299 100644
--- a/tests/tcg/riscv64/Makefile.softmmu-target
+++ b/tests/tcg/riscv64/Makefile.softmmu-target
@@ -41,5 +41,9 @@ comma:= ,
run-test-crc32: test-crc32
$(call run-test, $<, $(QEMU) -cpu rv64$(comma)xlrbr=true $(QEMU_OPTS)$<)
+EXTRA_RUNS += run-test-smmpt
+run-test-smmpt: test-smmpt
+ $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<)
+
# We don't currently support the multiarch system tests
undefine MULTIARCH_TESTS
diff --git a/tests/tcg/riscv64/smmpt-common.S b/tests/tcg/riscv64/smmpt-common.S
new file mode 100644
index 0000000000..020a9de764
--- /dev/null
+++ b/tests/tcg/riscv64/smmpt-common.S
@@ -0,0 +1,256 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Shared harness for the Smmpt (Supervisor Memory Protection Table) system
+ * tests, per SMMTT specification v0.4.9.
+ *
+ * This header is meant to be #included by the per-mode Smmpt test programs
+ * (Smmpt34 on RV32, Smmpt43/52/64 on RV64). All the mode-independent logic
+ * lives here so each test only has to build its mode-specific MPT and invoke
+ * RUN_LEAF_CHECKS.
+ *
+ * The tests run in M-mode and use the MPRV trick (mstatus.MPRV=1,
+ * mstatus.MPP=S, satp=Bare so PA==VA) to subject explicit data accesses to
+ * MPT checks while the instruction stream keeps running in M-mode (M-mode
+ * fetches bypass the MPT). The data-access width is irrelevant to the MPT
+ * check, so the harness uses lw/sw and works unmodified on RV32 and RV64.
+ *
+ * The MPT XWR field uses the standard bit order R=bit0, W=bit1, X=bit2
+ * (matching QEMU PAGE_READ/PAGE_WRITE/PAGE_EXEC), so Read+Write is 0b011=3.
+ * Per the spec the exception is reported for the original access type: a load
+ * violation raises "load access fault" (cause 5) and a store violation raises
+ * "store/AMO access fault" (cause 7).
+ *
+ * Execute permission is not exercised: the MPRV trick only redirects data
+ * accesses, while instruction fetch stays in M-mode and bypasses the MPT.
+ * X-bearing encodings are therefore checked only for their load/store
+ * behaviour (e.g. X-only denies loads, R+X denies stores).
+ *
+ * The tests expect an MPT laid out so that these supervisor physical
+ * addresses resolve as follows (identical across all modes):
+ *
+ * 0x8050_0000..0x8050_7000 non-NAPOT leaf, XWR[pi] tuples (LEAF_ALL_XWR):
+ * pi0 011 (RW) pi1 000 (no access)
+ * pi2 001 (R) pi3 101 (R+X)
+ * pi4 111 (RWX) pi5 010 (reserved)
+ * pi6 110 (rsvd) pi7 100 (X only)
+ * 0x8051_0000 invalid leaf (V=0)
+ * 0x8052_0000 leaf with a reserved bit set
+ * 0x8060_0000 NAPOT leaf with a valid G
+ * 0x8080_0000 NAPOT leaf with a reserved G
+ *
+ * On any mismatch the test exits (via semihosting) with a non-zero code that
+ * identifies the failing check; a successful run exits with 0.
+ */
+
+ /* mstatus bits (same positions on RV32 and RV64) */
+ .equ MSTATUS_MPP_S, (1 << 11) /* MPP = 01 (Supervisor) */
+ .equ MSTATUS_MPP_M, (3 << 11) /* MPP mask */
+ .equ MSTATUS_MPRV, (1 << 17)
+
+ /* Expected fault causes */
+ .equ CAUSE_LOAD_ACCESS_FAULT, 5
+ .equ CAUSE_STORE_ACCESS_FAULT, 7
+
+ .equ EXPECT_OK, 0
+ .equ EXPECT_FAULT, 1
+
+ /*
+ * Non-NAPOT leaf value carrying all eight XWR encodings in pi0..pi7:
+ * V|L | pi0=011 pi1=000 pi2=001 pi3=101 pi4=111 pi5=010 pi6=110 pi7=100
+ * The XWR tuples occupy bits [31:8], valid for both the 4-byte (RV32)
+ * and 8-byte (RV64) leaf formats.
+ */
+ .equ LEAF_ALL_XWR, 0x997A4303
+
+ /*
+ * Switch the effective privilege of data accesses to S-mode so that
+ * they are subject to MPT checks (MPP=S, MPRV=1). Uses t0 only.
+ */
+ .macro ENTER_MPRV
+ li t0, MSTATUS_MPP_M
+ csrc mstatus, t0 /* clear MPP */
+ li t0, MSTATUS_MPP_S
+ csrs mstatus, t0 /* MPP = S */
+ li t0, MSTATUS_MPRV
+ csrs mstatus, t0 /* enable MPRV */
+ .endm
+
+ .macro EXIT_MPRV
+ li t0, MSTATUS_MPRV
+ csrc mstatus, t0 /* disable MPRV */
+ .endm
+
+ /*
+ * Perform an MPT-checked load/store from \addr and verify the outcome.
+ * \eflt : EXPECT_OK or EXPECT_FAULT
+ * \ecause : expected mcause when a fault is expected
+ * \code : exit code reported if the check fails
+ */
+ .macro TEST_LOAD addr, eflt, ecause, code
+ li a3, \addr
+ jal do_load
+ li a4, \eflt
+ li a5, \ecause
+ li a6, \code
+ jal check
+ .endm
+
+ .macro TEST_STORE addr, eflt, ecause, code
+ li a3, \addr
+ li t3, 0x1234
+ jal do_store
+ li a4, \eflt
+ li a5, \ecause
+ li a6, \code
+ jal check
+ .endm
+
+ /*
+ * MFENCE.PA (funct7=0b1000011) must not fault in M-mode. Reuses the
+ * generic check subroutine (expects no fault).
+ */
+ .macro TEST_MFENCE_PA code
+ li s0, 0
+ .insn r 0x73, 0, 0x43, x0, x0, x0
+ li a4, EXPECT_OK
+ li a6, \code
+ jal check
+ .endm
+
+ /*
+ * Run the full set of MPT permission and structural checks against the
+ * shared address layout described above.
+ */
+ .macro RUN_LEAF_CHECKS
+ /* pi0 RW: load and store both allowed */
+ TEST_LOAD 0x80500000, EXPECT_OK, 0, 2
+ TEST_STORE 0x80500000, EXPECT_OK, 0, 3
+ /* pi1 no-access: load faults (cause 5), store faults (cause 7) */
+ TEST_LOAD 0x80501000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 4
+ TEST_STORE 0x80501000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 5
+ /* pi2 R-only: load allowed, store faults */
+ TEST_LOAD 0x80502000, EXPECT_OK, 0, 6
+ TEST_STORE 0x80502000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 7
+ /* pi3 R+X: load allowed, store faults */
+ TEST_LOAD 0x80503000, EXPECT_OK, 0, 8
+ TEST_STORE 0x80503000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 9
+ /* pi4 RWX: load and store both allowed */
+ TEST_LOAD 0x80504000, EXPECT_OK, 0, 10
+ TEST_STORE 0x80504000, EXPECT_OK, 0, 11
+ /* pi5/pi6 reserved encodings (010/110): load faults (cause 5) */
+ TEST_LOAD 0x80505000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 12
+ TEST_LOAD 0x80506000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 13
+ /* pi7 X-only: load faults (cause 5) */
+ TEST_LOAD 0x80507000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 14
+ /* invalid leaf (V=0): load faults (cause 5) */
+ TEST_LOAD 0x80510000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 15
+ /* leaf with a reserved bit set: load faults (cause 5) */
+ TEST_LOAD 0x80520000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 16
+ /* NAPOT leaf, valid G, RW: load and store both allowed */
+ TEST_LOAD 0x80600000, EXPECT_OK, 0, 17
+ TEST_STORE 0x80600000, EXPECT_OK, 0, 18
+ /* NAPOT leaf with a reserved G: load faults (cause 5) */
+ TEST_LOAD 0x80800000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 19
+ .endm
+
+ /*
+ * Emit the shared subroutines, trap handler, semihosting exit and data.
+ * Invoke once, after the test's _start code.
+ */
+ .macro SMMPT_HARNESS
+ /*
+ * do_load / do_store: perform one MPT-checked access from a3.
+ * On return, s0 = 1 if the access faulted (else 0) and s1 = mcause.
+ * do_store uses the value in t3. lw/sw keep these width-agnostic.
+ */
+ .balign 4
+do_load:
+ li s0, 0
+ li s1, 0
+ ENTER_MPRV
+ lw t4, 0(a3) /* MPT-checked load */
+ EXIT_MPRV
+ ret
+
+do_store:
+ li s0, 0
+ li s1, 0
+ ENTER_MPRV
+ sw t3, 0(a3) /* MPT-checked store */
+ EXIT_MPRV
+ ret
+
+ /*
+ * check: verify the outcome of the last access.
+ * a4 = expected fault (0/1), a5 = expected cause, a6 = fail code.
+ * Returns on success; exits with a6 on mismatch.
+ */
+ .balign 4
+check:
+ beqz a4, 1f
+ beqz s0, 2f /* expected a fault but none occurred */
+ bne s1, a5, 2f /* faulted with the wrong cause */
+ ret
+1:
+ bnez s0, 2f /* unexpected fault */
+ ret
+2:
+ mv a0, a6
+ j _exit
+
+ /*
+ * M-mode trap handler. Records that a fault occurred (s0=1) and the
+ * mcause (s1), skips the faulting 4-byte instruction, disables MPRV
+ * and returns. Only clobbers t5/t6 besides s0/s1.
+ */
+ .balign 4
+mtrap:
+ csrr t5, mcause
+ li s0, 1
+ mv s1, t5
+ csrr t6, mepc
+ addi t6, t6, 4 /* skip the faulting instruction */
+ csrw mepc, t6
+ li t5, MSTATUS_MPRV
+ csrc mstatus, t5 /* ensure MPRV is off on return */
+ mret
+
+ /* Exit via semihosting (ADP_Stopped_ApplicationExit) */
+_exit:
+#if __riscv_xlen == 32
+ /*
+ * On RV32 the SYS_EXIT_EXTENDED parameter block cannot be used: when
+ * this test runs on qemu-system-riscv64 with a 32-bit CPU the block
+ * pointer is sign-extended (0xffffffff_8xxx_xxxx) and the semihosting
+ * argument read faults. Use the plain SYS_EXIT, which takes the exit
+ * reason directly in a1: a successful run (a0==0) reports
+ * ADP_Stopped_ApplicationExit and any failure reports a non-zero
+ * reason (reported by QEMU as exit status 1).
+ */
+ li a1, 0x20026 /* ADP_Stopped_ApplicationExit */
+ beqz a0, 1f
+ mv a1, a0 /* non-zero reason -> exit status 1 */
+1:
+ li a0, 0x18 /* TARGET_SYS_EXIT */
+#else
+ lla a1, semiargs
+ li t0, 0x20026
+ sd t0, 0(a1)
+ sd a0, 8(a1)
+ li a0, 0x20 /* TARGET_SYS_EXIT_EXTENDED */
+#endif
+
+ .balign 16
+ slli zero, zero, 0x1f
+ ebreak
+ srai zero, zero, 0x7
+ j .
+
+#if __riscv_xlen != 32
+ .data
+ .balign 8
+semiargs:
+ .space 16
+#endif
+ .endm
diff --git a/tests/tcg/riscv64/test-smmpt.S b/tests/tcg/riscv64/test-smmpt.S
new file mode 100644
index 0000000000..64d0d97756
--- /dev/null
+++ b/tests/tcg/riscv64/test-smmpt.S
@@ -0,0 +1,85 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Test for the Smmpt43 (Supervisor Memory Protection Table) extension,
+ * per SMMTT specification v0.4.9.
+ *
+ * The test runs in M-mode and builds a 3-level Smmpt43 MPT, then drives the
+ * mode-independent checks in smmpt-common.S. See that harness for the MPRV
+ * trick, the shared address layout and the exit-code convention.
+ *
+ * MPT layout (Smmpt43, 3 levels, 8-byte entries, 16 pages per leaf,
+ * pi = SPA[15:12], pn[i] 9 bits at SPA[16 + i*9 +: 9]):
+ *
+ * L2 (root) @ 0x8040_0000 : entry[0] -> non-leaf, PPN(L1)=0x80410
+ * L1 @ 0x8041_0000 : entry[64] -> non-leaf, PPN(L0)=0x80420
+ * L0 @ 0x8042_0000 :
+ * entry[80] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+
+ * entry[81] -> V=0 (invalid) covering 0x8051_0000
+ * entry[82] -> leaf with a reserved bit set covering 0x8052_0000
+ * entry[96] -> NAPOT leaf, G=4, XWR=RW covering 0x8060_0000
+ * entry[128] -> NAPOT leaf, reserved G=5 covering 0x8080_0000
+ *
+ * mmpt = MODE(1=Smmpt43)<<60 | PPN(0x80400)
+ */
+
+#include "smmpt-common.S"
+
+ .option norvc
+
+ .text
+ .global _start
+_start:
+ /* Install the M-mode trap handler */
+ lla t0, mtrap
+ csrw mtvec, t0
+ csrw medeleg, zero
+
+ /* PMP entry 0: NAPOT covering the whole space, RWX, so S-mode
+ * accesses pass PMP and the MPT is the only gate. */
+ li t0, -1
+ csrw pmpaddr0, t0
+ li t0, 0x1f /* A=NAPOT(0x18) | R | W | X */
+ csrw pmpcfg0, t0
+
+ /* Build the MPT tables (M-mode stores bypass the MPT). */
+ /* L2[0] = non-leaf -> L1 (PPN 0x80410): (0x80410 << 10) | V */
+ li t0, 0x80400000
+ li t1, 0x20104001
+ sd t1, 0(t0)
+ /* L1[64] = non-leaf -> L0 (PPN 0x80420): (0x80420 << 10) | V */
+ li t0, 0x80410000
+ li t1, 0x20108001
+ sd t1, 0x200(t0) /* 64 * 8 = 0x200 */
+
+ li t0, 0x80420000
+ /* L0[80] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */
+ li t1, LEAF_ALL_XWR
+ sd t1, 0x280(t0) /* 80 * 8 = 0x280 */
+ /* L0[81] = 0: invalid entry (V=0) */
+ sd x0, 0x288(t0) /* 81 * 8 = 0x288 */
+ /* L0[82] = leaf with reserved bit 3 set (V|L|rsv|XWR[pi0]=RW) */
+ li t1, 0x30B
+ sd t1, 0x290(t0) /* 82 * 8 = 0x290 */
+ /* L0[96] = NAPOT leaf: V|L|N | XWR=RW(0x300) | G=4(0x4000) */
+ li t1, 0x4307
+ sd t1, 0x300(t0) /* 96 * 8 = 0x300 */
+ /* L0[128] = NAPOT leaf with reserved G=5: V|L|N | XWR=RW | G=5(0x5000) */
+ li t1, 0x5307
+ sd t1, 0x400(t0) /* 128 * 8 = 0x400 */
+
+ /* Program mmpt: MODE=1 (Smmpt43), PPN = 0x80400 */
+ li t0, 0x1000000000080400
+ csrw 0x382, t0 /* CSR_MMPT */
+
+ /* mfence.pa must not fault in M-mode */
+ TEST_MFENCE_PA 1
+
+ /* Run the shared permission and structural checks */
+ RUN_LEAF_CHECKS
+
+ /* All tests passed */
+ li a0, 0
+ j _exit
+
+ SMMPT_HARNESS
--
2.43.0
© 2016 - 2026 Red Hat, Inc.