[PATCH v7 09/11] target/riscv: Add system test for SMMPT extension

LIU Zhiwei <[email protected]>
Newsgroups org.nongnu.qemu-riscv,org.nongnu.qemu-devel
Message-ID <[email protected]>
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 <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.