This patch introduces the core implementation for the Memory Protection Table
(MPT) walk, which is the central mechanism of the SMMPT extension.
A new file, `riscv_smmpt.c`, is added to encapsulate the MPT logic. It
implements the `smmpt_lookup()` function, which performs a multi-level
page table-like walk starting from the physical address specified in the
`mptppn` CSR field. This walk determines the access permissions (read,
write, execute) for a given physical address.
The implementation supports various SMMPT modes (SMMPT34, SMMPT43, etc.)
and correctly handles leaf and non-leaf entries, including reserved bit
checks. Both non-NAPOT leaf entries (per-page XWR tuples) and NAPOT leaf
entries (a single XWR tuple with a G granularity field) are supported, as
defined by the v0.4.9 MPTE format. Helper functions for parsing MPT
entries and converting access permissions are included in the new
`riscv_smmpt.h` header.
Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com>
Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com>
Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com>
---
target/riscv/meson.build | 1 +
target/riscv/riscv_smmpt.c | 339 ++++++++++++++++++++++++++++++++++
target/riscv/riscv_smmpt.h | 5 +
target/riscv/tcg/cpu_helper.c | 6 +-
target/riscv/tcg/pmp.h | 3 +
5 files changed, 351 insertions(+), 3 deletions(-)
create mode 100644 target/riscv/riscv_smmpt.c
diff --git a/target/riscv/meson.build b/target/riscv/meson.build
index 42d0f6d538..51257a8f3a 100644
--- a/target/riscv/meson.build
+++ b/target/riscv/meson.build
@@ -22,6 +22,7 @@ riscv_ss.add(files(
riscv_system_ss = ss.source_set()
riscv_system_ss.add(files(
'arch_dump.c',
+ 'riscv_smmpt.c',
'monitor.c',
'machine.c',
'time_helper.c',
diff --git a/target/riscv/riscv_smmpt.c b/target/riscv/riscv_smmpt.c
new file mode 100644
index 0000000000..5bdc474dbb
--- /dev/null
+++ b/target/riscv/riscv_smmpt.c
@@ -0,0 +1,339 @@
+/*
+ * QEMU RISC-V Smmpt (Memory Protection Table)
+ *
+ * Copyright (c) 2024 Alibaba Group. All rights reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Implements the MPT lookup algorithm per SMMTT specification v0.4.9.
+ *
+ * MPTE format (v0.4.9):
+ *
+ * 32-bit non-leaf: V[0], L=0[1], Reserved[9:2], PPN[31:10]
+ * 32-bit non-NAPOT leaf: V[0], L=1[1], N=0[2], Reserved[7:3], XWR[31:8]
+ * 32-bit NAPOT leaf: V[0], L=1[1], N=1[2], Reserved[7:3], XWR[10:8], 0[11],
+ * G[15:12], Reserved[31:16]
+ *
+ * 64-bit non-leaf: V[0], L=0[1], Reserved[9:2], PPN[53:10], Reserved[63:54]
+ * 64-bit non-NAPOT leaf: V[0], L=1[1], N=0[2], Reserved[7:3], XWR[55:8],
+ * Reserved[63:56]
+ * 64-bit NAPOT leaf: V[0], L=1[1], N=1[2], Reserved[7:3], XWR[10:8], 0[11],
+ * G[15:12], Reserved[63:16]
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_smmpt.h"
+#include "tcg/pmp.h"
+#include "exec/page-protection.h"
+#include "system/memory.h"
+
+typedef uint64_t load_entry_fn(AddressSpace *, hwaddr,
+ MemTxAttrs, MemTxResult *);
+
+static uint64_t load_entry_32(AddressSpace *as, hwaddr addr,
+ MemTxAttrs attrs, MemTxResult *result)
+{
+ return address_space_ldl_le(as, addr, attrs, result);
+}
+
+static uint64_t load_entry_64(AddressSpace *as, hwaddr addr,
+ MemTxAttrs attrs, MemTxResult *result)
+{
+ return address_space_ldq_le(as, addr, attrs, result);
+}
+
+static inline bool mpte_is_valid(uint64_t mpte)
+{
+ return mpte & 0x1;
+}
+
+static inline bool mpte_is_leaf(uint64_t mpte)
+{
+ return mpte & 0x2;
+}
+
+static inline bool mpte_get_n(uint64_t mpte)
+{
+ return (mpte >> 2) & 0x1;
+}
+
+/*
+ * Get reserved bits from MPTE. Returns non-zero if any reserved bit is set.
+ */
+static uint64_t mpte_get_rsv(CPURISCVState *env, uint64_t mpte)
+{
+ RISCVMXL mxl = riscv_cpu_mxl(env);
+ bool leaf = mpte_is_leaf(mpte);
+ bool napot = mpte_get_n(mpte);
+
+ if (mxl == MXL_RV32) {
+ if (!leaf) {
+ /* non-leaf32: Reserved = bits[9:2] */
+ return extract32(mpte, 2, 8);
+ }
+ if (!napot) {
+ /* non-NAPOT leaf32: Reserved = bits[7:3] (XWR fills [31:8]) */
+ return extract32(mpte, 3, 5);
+ }
+ /* NAPOT leaf32: Reserved = bits[7:3] | bit[11] (mbz) | bits[31:16] */
+ return extract32(mpte, 3, 5) | extract32(mpte, 11, 1) |
+ extract32(mpte, 16, 16);
+ }
+
+ /* RV64 */
+ if (!leaf) {
+ /* non-leaf64: Reserved = bits[9:2] | bits[63:54] */
+ return extract64(mpte, 2, 8) | extract64(mpte, 54, 10);
+ }
+ if (!napot) {
+ /* non-NAPOT leaf64: Reserved = bits[7:3] | bits[63:56] */
+ return extract64(mpte, 3, 5) | extract64(mpte, 56, 8);
+ }
+ /* NAPOT leaf64: Reserved = bits[7:3] | bit[11] (mbz) | bits[63:16] */
+ return extract64(mpte, 3, 5) | extract64(mpte, 11, 1) |
+ extract64(mpte, 16, 48);
+}
+
+/*
+ * Get PPN from a non-leaf MPTE.
+ * RV32 non-leaf: PPN = bits[31:10] (22 bits)
+ * RV64 non-leaf: PPN = bits[53:10] (44 bits)
+ */
+static uint64_t mpte_get_ppn(CPURISCVState *env, uint64_t mpte)
+{
+ RISCVMXL mxl = riscv_cpu_mxl(env);
+
+ if (mxl == MXL_RV32) {
+ return extract32(mpte, 10, 22);
+ }
+ return extract64(mpte, 10, 44);
+}
+
+/*
+ * Get XWR permission for a specific page index from a non-NAPOT leaf.
+ * The XWR base bit is 8 for both RV32 and RV64; only the number of
+ * entries differs (pi in 0..7 for RV32, 0..15 for RV64).
+ */
+static uint32_t mpte_get_xwr(CPURISCVState *env, uint64_t mpte, int pi)
+{
+ return extract64(mpte, 8 + pi * 3, 3);
+}
+
+/*
+ * Get single XWR from a NAPOT leaf.
+ * The NAPOT XWR base bit is 8 for both RV32 and RV64 (bits[10:8]).
+ */
+static uint32_t mpte_get_napot_xwr(CPURISCVState *env, uint64_t mpte)
+{
+ return extract64(mpte, 8, 3);
+}
+
+/*
+ * Get G field from a NAPOT leaf.
+ * The G base bit is 12 for both RV32 and RV64 (bits[15:12]).
+ */
+static uint32_t mpte_get_g(CPURISCVState *env, uint64_t mpte)
+{
+ return extract64(mpte, 12, 4);
+}
+
+/*
+ * Validate the G encoding for the given MPT mode.
+ * Smmpt34: only G=6 is valid
+ * Smmpt43/52/64: only G=4 is valid
+ */
+static bool mpte_validate_g(uint32_t g, mpt_mode_t mode)
+{
+ switch (mode) {
+ case SMMPT34:
+ return g == 6;
+ case SMMPT43:
+ case SMMPT52:
+ case SMMPT64:
+ return g == 4;
+ default:
+ return false;
+ }
+}
+
+/*
+ * Get page number index pn[i] from the supervisor physical address.
+ *
+ * Smmpt34 (34-bit SPA):
+ * SPA layout: range_offset[14:0], pn[0][24:15] (10 bits),
+ * pn[1][33:25] (9 bits)
+ *
+ * Smmpt43/52/64 (RV64 MPT):
+ * SPA layout: range_offset[15:0], pn[0][24:16] (9 bits), ...
+ * For Smmpt64, pn[4] (top level) is 12 bits.
+ */
+static int mpt_get_pn(hwaddr addr, int i, mpt_mode_t mode)
+{
+ if (mode == SMMPT34) {
+ return i == 0
+ ? extract64(addr, 15, 10)
+ : extract64(addr, 25, 9);
+ } else {
+ int offset = 16 + i * 9;
+ if ((mode == SMMPT64) && (i == 4)) {
+ return extract64(addr, offset, 12);
+ } else {
+ return extract64(addr, offset, 9);
+ }
+ }
+}
+
+/*
+ * Get the page index within a leaf MPTE.
+ *
+ * Smmpt34: pi = SPA[14:12] (3 bits) for level 0, SPA[24:22] for level 1
+ * Smmpt43/52/64: pi = SPA[offset-4 +: 4] (4 bits)
+ */
+static int mpt_get_pi(hwaddr addr, int i, mpt_mode_t mode)
+{
+ if (mode == SMMPT34) {
+ return i == 0
+ ? extract64(addr, 12, 3)
+ : extract64(addr, 22, 3);
+ } else {
+ int offset = 16 + i * 9;
+ return extract64(addr, offset - 4, 4);
+ }
+}
+
+/*
+ * Check XWR permission bits against the access type.
+ * Returns true if access is allowed.
+ *
+ * The 3-bit XWR field uses the same bit order as RISC-V PTE permissions
+ * (bit0 = R, bit1 = W, bit2 = X), matching QEMU's PAGE_READ / PAGE_WRITE /
+ * PAGE_EXEC. As with PTEs, writable-but-not-readable encodings are reserved.
+ *
+ * XWR encoding (bit2=X, bit1=W, bit0=R):
+ * 000 = No access
+ * 001 = Read only
+ * 010 = Reserved (fault)
+ * 011 = Read + Write
+ * 100 = Execute only
+ * 101 = Read + Execute
+ * 110 = Reserved (fault)
+ * 111 = Read + Write + Execute
+ */
+static bool mpt_check_xwr(uint32_t xwr, int *prot, MMUAccessType access_type)
+{
+ switch (xwr) {
+ case 0: /* No access */
+ return false;
+ case PAGE_EXEC: /* 100: Execute only */
+ *prot = PAGE_EXEC;
+ return access_type == MMU_INST_FETCH;
+ case PAGE_READ | PAGE_EXEC: /* 101: Read + Execute */
+ *prot = PAGE_READ | PAGE_EXEC;
+ return (access_type == MMU_DATA_LOAD ||
+ access_type == MMU_INST_FETCH);
+ case PAGE_READ: /* 001: Read only */
+ *prot = PAGE_READ;
+ return access_type == MMU_DATA_LOAD;
+ case PAGE_READ | PAGE_WRITE: /* 011: Read + Write */
+ *prot = PAGE_READ | PAGE_WRITE;
+ return (access_type == MMU_DATA_LOAD ||
+ access_type == MMU_DATA_STORE);
+ case PAGE_READ | PAGE_WRITE | PAGE_EXEC: /* 111: R+W+X */
+ *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+ return true;
+ default: /* 010, 110: Reserved - fault */
+ return false;
+ }
+}
+
+static bool smmpt_lookup(CPURISCVState *env, hwaddr addr, mpt_mode_t mode,
+ int *prot, MMUAccessType access_type)
+{
+ MemTxResult res;
+ MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
+ CPUState *cs = env_cpu(env);
+ hwaddr mpte_addr, base = (hwaddr)env->mptppn << PGSHIFT;
+ load_entry_fn *load_entry;
+ uint32_t mptesize, levels, xwr, g;
+ int pn, pi, pmp_prot, pmp_ret;
+ uint64_t mpte;
+
+ switch (mode) {
+ case SMMPT34:
+ load_entry = &load_entry_32; levels = 2; mptesize = 4; break;
+ case SMMPT43:
+ load_entry = &load_entry_64; levels = 3; mptesize = 8; break;
+ case SMMPT52:
+ load_entry = &load_entry_64; levels = 4; mptesize = 8; break;
+ case SMMPT64:
+ load_entry = &load_entry_64; levels = 5; mptesize = 8; break;
+ case SMMPTBARE:
+ *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+ return true;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ for (int i = levels - 1; i >= 0; i--) {
+ /* Step 1: Get pn[i] as the MPT index */
+ pn = mpt_get_pn(addr, i, mode);
+
+ /* Step 2: Load MPTE from memory */
+ mpte_addr = base + pn * mptesize;
+ pmp_ret = get_physical_address_pmp(env, &pmp_prot, mpte_addr,
+ mptesize, MMU_DATA_LOAD, PRV_M);
+ if (pmp_ret != TRANSLATE_SUCCESS) {
+ return false;
+ }
+ mpte = load_entry(cs->as, mpte_addr, attrs, &res);
+ if (res != MEMTX_OK) {
+ return false;
+ }
+
+ /* Step 3: Check valid bit and reserved bits */
+ if (!mpte_is_valid(mpte) || mpte_get_rsv(env, mpte)) {
+ return false;
+ }
+
+ /* Step 3 (cont): non-leaf with N=1 is a fault */
+ if (!mpte_is_leaf(mpte) && mpte_get_n(mpte)) {
+ return false;
+ }
+
+ /* Step 4: Process non-leaf node */
+ if (!mpte_is_leaf(mpte)) {
+ if (i == 0) {
+ return false;
+ }
+ base = mpte_get_ppn(env, mpte) << PGSHIFT;
+ continue;
+ }
+
+ /* Step 5 & 6: Process leaf node */
+ if (!mpte_get_n(mpte)) {
+ /* Step 5: Non-NAPOT leaf - get XWR[pi] */
+ pi = mpt_get_pi(addr, i, mode);
+ xwr = mpte_get_xwr(env, mpte, pi);
+ } else {
+ /* Step 6: NAPOT leaf - validate G, get single XWR */
+ g = mpte_get_g(env, mpte);
+ if (!mpte_validate_g(g, mode)) {
+ return false;
+ }
+ xwr = mpte_get_napot_xwr(env, mpte);
+ }
+
+ /* Step 7: Check permission */
+ return mpt_check_xwr(xwr, prot, access_type);
+ }
+ return false;
+}
+
+bool smmpt_check_access(CPURISCVState *env, hwaddr addr,
+ int *prot, MMUAccessType access_type)
+{
+ mpt_mode_t mode = env->mptmode;
+
+ return smmpt_lookup(env, addr, mode, prot, access_type);
+}
diff --git a/target/riscv/riscv_smmpt.h b/target/riscv/riscv_smmpt.h
index 74dcccf4be..c15637036e 100644
--- a/target/riscv/riscv_smmpt.h
+++ b/target/riscv/riscv_smmpt.h
@@ -9,6 +9,9 @@
#ifndef RISCV_SMMPT_H
#define RISCV_SMMPT_H
+#include "cpu.h"
+#include "exec/mmu-access-type.h"
+
typedef enum {
SMMPTBARE = 0,
SMMPT34 = 1,
@@ -18,4 +21,6 @@ typedef enum {
SMMPTMAX
} mpt_mode_t;
+bool smmpt_check_access(CPURISCVState *env, hwaddr addr,
+ int *prot, MMUAccessType access_type);
#endif
diff --git a/target/riscv/tcg/cpu_helper.c b/target/riscv/tcg/cpu_helper.c
index 07d9222652..354506c565 100644
--- a/target/riscv/tcg/cpu_helper.c
+++ b/target/riscv/tcg/cpu_helper.c
@@ -903,9 +903,9 @@ void riscv_cpu_set_mode(CPURISCVState *env, privilege_mode_t newpriv,
* @access_type: The type of MMU access
* @mode: Indicates current privilege level.
*/
-static int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr,
- int size, MMUAccessType access_type,
- privilege_mode_t mode)
+int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr,
+ int size, MMUAccessType access_type,
+ privilege_mode_t mode)
{
pmp_priv_t pmp_priv;
bool pmp_has_privs;
diff --git a/target/riscv/tcg/pmp.h b/target/riscv/tcg/pmp.h
index 4c95c2767a..16c0db93c6 100644
--- a/target/riscv/tcg/pmp.h
+++ b/target/riscv/tcg/pmp.h
@@ -80,6 +80,9 @@ void pmp_update_rule_nums(CPURISCVState *env);
uint32_t pmp_get_num_rules(CPURISCVState *env);
int pmp_priv_to_page_prot(pmp_priv_t pmp_priv);
void pmp_unlock_entries(CPURISCVState *env);
+int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr,
+ int size, MMUAccessType access_type,
+ privilege_mode_t mode);
#define MSECCFG_MML_ISSET(env) get_field(env->mseccfg, MSECCFG_MML)
#define MSECCFG_MMWP_ISSET(env) get_field(env->mseccfg, MSECCFG_MMWP)
--
2.43.0
© 2016 - 2026 Red Hat, Inc.