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


Reply via email to