[PATCH v7 03/11] target/riscv: Implement core SMMPT lookup logic

LIU Zhiwei <[email protected]>
Newsgroups org.nongnu.qemu-riscv,org.nongnu.qemu-devel
Message-ID <[email protected]>
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 <[email protected]>
Co-authored-by: TANG Tiancheng <[email protected]>
Signed-off-by: LIU Zhiwei <[email protected]>
---
 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
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.