[RFC PATCH 2/5] mpt: Add Smsdid and Smmpt supervisor domain core
Rahul Pathak
rahul.pathak at oss.qualcomm.com
Tue Jul 21 20:34:51 PDT 2026
Introduce the supervisord omain memory-protection (Smmpt) table
management core. This adds the mode agnostic core layer and the
per mode table walkers/builder.
Add defines for the mmpt CSR encoding, MPTE bit layout, permission flags,
structures, fence helpers and the public interfaces.
It implements the RV64 supported modes (Smmpt43/52/64) and
the RV32 mode (Smmpt34), its MPT table support.
Also implements mpt core which integrates Smmpt with SBI domains and
other core layers.
Signed-off-by: Rahul Pathak <rahul.pathak at oss.qualcomm.com>
---
include/sbi/sbi_mpt.h | 321 +++++++++++++++
lib/sbi/objects.mk | 2 +
lib/sbi/sbi_mpt.c | 882 ++++++++++++++++++++++++++++++++++++++++
lib/sbi/sbi_mpt_mode.c | 891 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 2096 insertions(+)
create mode 100644 include/sbi/sbi_mpt.h
create mode 100644 lib/sbi/sbi_mpt.c
create mode 100644 lib/sbi/sbi_mpt_mode.c
diff --git a/include/sbi/sbi_mpt.h b/include/sbi/sbi_mpt.h
new file mode 100644
index 00000000..0d76185f
--- /dev/null
+++ b/include/sbi/sbi_mpt.h
@@ -0,0 +1,321 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Qualcomm Inc.
+ *
+ * Authors: Rahul Pathak <rahul.pathak at oss.qualcomm.com>
+ */
+
+#ifndef __SBI_MPT_H__
+#define __SBI_MPT_H__
+
+#include <sbi/sbi_types.h>
+#include <sbi/riscv_locks.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi/sbi_bitmap.h>
+#include <sbi/riscv_asm.h>
+
+struct sbi_domain;
+struct sbi_mpt_domain;
+struct sbi_mpt_ctrl;
+struct sbi_domain_memregion;
+
+/* CSR addresses */
+#define SBI_CSR_MMPT 0x382
+#define SBI_CSR_MSDCFG 0x74E
+
+/* mmpt register field definitions */
+#if __riscv_xlen == 64
+# define SBI_MMPT_MODE_SHIFT 60UL
+# define SBI_MMPT_MODE_MASK (UL(0xF) << SBI_MMPT_MODE_SHIFT)
+# define SBI_MMPT_SDID_SHIFT 52UL
+# define SBI_MMPT_SDID_MASK (UL(0x3F) << SBI_MMPT_SDID_SHIFT)
+# define SBI_MMPT_PPN_MASK UL(0x00000FFFFFFFFFFF)
+#else
+# define SBI_MMPT_MODE_SHIFT UL(30)
+# define SBI_MMPT_MODE_MASK (UL(0x3) << SBI_MMPT_MODE_SHIFT)
+# define SBI_MMPT_SDID_SHIFT UL(22)
+# define SBI_MMPT_SDID_MASK (UL(0x3F) << SBI_MMPT_SDID_SHIFT)
+# define SBI_MMPT_PPN_MASK UL(0x3FFFFF)
+#endif
+
+#define SBI_MMPT_MODE_BARE 0UL
+#if __riscv_xlen == 64
+# define SBI_MMPT_MODE_SMMPT43 1UL
+# define SBI_MMPT_MODE_SMMPT52 2UL
+# define SBI_MMPT_MODE_SMMPT64 3UL
+#else
+# define SBI_MMPT_MODE_SMMPT34 1UL
+#endif
+
+/*
+ * sbi_mmpt_encode() — assemble the full mmpt CSR value.
+ */
+static inline unsigned long sbi_mmpt_encode(u32 mode, u32 sdid,
+ unsigned long ppn)
+{
+ return (((unsigned long)mode << SBI_MMPT_MODE_SHIFT) & SBI_MMPT_MODE_MASK) |
+ (((unsigned long)sdid << SBI_MMPT_SDID_SHIFT) & SBI_MMPT_SDID_MASK) |
+ (ppn & SBI_MMPT_PPN_MASK);
+}
+
+/*
+ * MPTE bit definitions
+ */
+#define SBI_MPTE_V (1UL << 0)
+#define SBI_MPTE_L (1UL << 1)
+#define SBI_MPTE_N (1UL << 2)
+#define SBI_MPTE_PPN_SHIFT 10U
+#define SBI_MPT_PAGE_SHIFT 12U
+#define SBI_MPT_PAGE_SIZE 4096UL
+#define SBI_MPT_PAGE_MASK (SBI_MPT_PAGE_SIZE - 1UL)
+#define SBI_MPTE_XWR_BASE 8U
+#define SBI_MPTE_XWR_WIDTH 3U
+#define SBI_MPTE_XWR_MASK 7UL
+
+static inline u32 sbi_mpte_xwr_shift(u32 n)
+{
+ return (SBI_MPTE_XWR_BASE + n * SBI_MPTE_XWR_WIDTH);
+}
+
+static inline unsigned long sbi_mpte_leaf_set_xwr(unsigned long mpte,
+ u32 n, u8 xwr)
+{
+ u32 sh = sbi_mpte_xwr_shift(n);
+
+ mpte &= ~(SBI_MPTE_XWR_MASK << sh);
+ mpte |= (xwr & SBI_MPTE_XWR_MASK) << sh;
+ return mpte;
+}
+
+static inline unsigned long sbi_mpte_nonleaf(unsigned long table_pa)
+{
+ return (((table_pa >> SBI_MPT_PAGE_SHIFT) << SBI_MPTE_PPN_SHIFT)
+ | SBI_MPTE_V);
+}
+
+static inline unsigned long sbi_mpte_nonleaf_table_pa(unsigned long mpte)
+{
+ return ((mpte >> SBI_MPTE_PPN_SHIFT) << SBI_MPT_PAGE_SHIFT);
+}
+
+/*
+ * XWR permission constants (XWR=000 means no access)
+ */
+#define SBI_MPT_PERM_NONE 0U
+#define SBI_MPT_PERM_R 1U
+#define SBI_MPT_PERM_W 2U
+#define SBI_MPT_PERM_X 4U
+#define SBI_MPT_PERM_RW (SBI_MPT_PERM_R | SBI_MPT_PERM_W)
+#define SBI_MPT_PERM_RX (SBI_MPT_PERM_R | SBI_MPT_PERM_X)
+#define SBI_MPT_PERM_RWX (SBI_MPT_PERM_R | SBI_MPT_PERM_W | SBI_MPT_PERM_X)
+
+/*
+ * SDID constants
+ */
+#define SBI_MPT_SDIDMAX 6U
+#define SBI_MPT_MAX_DOMAINS (1UL << SBI_MPT_SDIDMAX)
+#define SBI_MPT_SDID_INVALID SBI_MPT_MAX_DOMAINS
+
+/*
+ * SBI_MPT_MAX_REGIONS_DOMAIN: Number of regions a domain may have
+ * Used for tracking of regions
+ */
+#define SBI_MPT_MAX_REGIONS_DOMAIN 32U
+
+/*
+ * SMSDID mfence and minval raw encodings
+ *
+ * R-type, opcode=SYSTEM(0x73), funct3=0, rd=x0, rs1=PADDR, rs2=SDID
+ * mfence.pa funct7=0x19 -> 0x32000073 where rs1=rs2=x0
+ * minval.pa funct7=0x1b -> 0x36000073 where rs1=rs2=x0
+ *
+ * .insn r opcode, func3, func7, rd, rs1, rs2
+ */
+#define SBI_MPT_MFENCE_PA(rs1, rs2) ".insn r 0x73, 0, 0x19, x0, " rs1 ", " rs2
+
+#define SBI_MPT_MINVAL_PA(rs1, rs2) ".insn r 0x73, 0, 0x1b, x0, " rs1 ", " rs2
+
+/*
+ * Fence and invalidation helpers
+ */
+static inline void sbi_mpt_fence_all(void)
+{
+ __asm__ volatile(SBI_MPT_MFENCE_PA("x0", "x0") ::: "memory");
+}
+
+static inline void sbi_mpt_fence_sdid(u32 sdid)
+{
+ unsigned long s = (unsigned long)sdid & (SBI_MPT_MAX_DOMAINS - UL(1));
+
+ __asm__ volatile(SBI_MPT_MFENCE_PA("x0", "%0") :: "r"(s) : "memory");
+}
+
+/*
+ * rs1=paddr and rs2=x0
+ */
+static inline void sbi_mpt_mfence_addr(unsigned long paddr)
+{
+ __asm__ volatile(SBI_MPT_MFENCE_PA("%0", "x0") :: "r"(paddr) : "memory");
+}
+
+/*
+ * rs1=paddr and rs2=sdid
+ */
+static inline void sbi_mpt_mfence_pa(unsigned long paddr, u32 sdid)
+{
+ unsigned long s = (unsigned long)sdid & (SBI_MPT_MAX_DOMAINS - UL(1));
+
+ __asm__ volatile(SBI_MPT_MFENCE_PA("%0", "%1")
+ :: "r"(paddr), "r"(s) : "memory");
+}
+
+/*
+ * rs1=0 and rs2=0
+ */
+static inline void sbi_mpt_minval_all(void)
+{
+ /* Prior stores globally visible before invalidation */
+ __asm__ volatile("sfence.w.inval" ::: "memory");
+ __asm__ volatile(SBI_MPT_MINVAL_PA("x0", "x0") ::: "memory");
+ /* Invalidation completes before subsequent implicit accesses */
+ __asm__ volatile("sfence.inval.ir" ::: "memory");
+}
+
+/*
+ * rs1=paddr and rs2=sdid
+ */
+static inline void sbi_mpt_minval_sdid(unsigned long pa, u32 sdid)
+{
+ unsigned long s = (unsigned long)sdid & (SBI_MPT_MAX_DOMAINS - UL(1));
+
+ __asm__ volatile("sfence.w.inval" ::: "memory");
+ __asm__ volatile(SBI_MPT_MINVAL_PA("%0", "%1")
+ :: "r"(pa), "r"(s) : "memory");
+ __asm__ volatile("sfence.inval.ir" ::: "memory");
+}
+
+/*
+ * Core data structures
+ */
+struct sbi_mpt_region {
+ unsigned long pa;
+ unsigned long size;
+ u8 xwr;
+ /* region is locked and deny modification via add/remove_region */
+ bool locked;
+ /* mapped into more than one domain */
+ bool shared;
+};
+
+/*
+ * struct sbi_mpt_mode — SMMPT mode description.
+ */
+struct sbi_mpt_mode {
+ const char *name;
+ u32 mode_val;
+ unsigned long (*encode_mmpt)(unsigned long ppn, u32 sdid);
+ /* map a range in MPT table */
+ int (*map_range)(struct sbi_mpt_domain *dom,
+ unsigned long pa, unsigned long size, u8 xwr);
+ /* number bytes for root table allocation */
+ unsigned long (*root_table_size)(void);
+ /* required root table physical address alignment */
+ unsigned long (*root_table_align)(void);
+ /* check if [pa,pa+size] fits in smmpt mode supported address space */
+ bool (*pa_in_range)(unsigned long pa, unsigned long size);
+ int (*map_full_range)(struct sbi_mpt_domain *dom, u8 xwr);
+};
+
+struct sbi_mpt_domain {
+ u32 sdid;
+ bool valid;
+ unsigned long root_pa;
+ struct sbi_mpt_mode *mode;
+ struct sbi_domain *sbi_dom;
+ unsigned int nregions;
+ struct sbi_mpt_region regions[SBI_MPT_MAX_REGIONS_DOMAIN];
+ spinlock_t lock;
+};
+
+/*
+ * struct sbi_mpt_ctrl — RDSM MPT state
+ */
+struct sbi_mpt_ctrl {
+ bool ready;
+ struct sbi_mpt_mode *mode;
+ /* detected sdidlen from mmpt probe */
+ u32 sdid_len;
+ /* 2^sdid_len — runtime limit for all domain operations */
+ u32 max_domains;
+ unsigned long fw_pa;
+ unsigned long fw_size;
+ struct sbi_mpt_domain domains[SBI_MPT_MAX_DOMAINS];
+ u32 ndomain;
+ /* sdid offset in scratch */
+ unsigned long sdid_offset;
+ /* bitmap to track assigned/free SDID (bit N set = SDID N free) */
+ DECLARE_BITMAP(sdid_bitmap, SBI_MPT_MAX_DOMAINS);
+};
+
+struct sbi_mpt_ctrl *sbi_mpt_ctrl_get(void);
+
+static inline u32 sbi_mpt_thishart_sdid(void)
+{
+ struct sbi_mpt_ctrl *ctrl = sbi_mpt_ctrl_get();
+ u32 *p;
+
+ if (!ctrl->ready || !ctrl->sdid_offset)
+ return (u32)SBI_MPT_SDID_INVALID;
+ p = sbi_scratch_offset_ptr(sbi_scratch_thishart_ptr(),
+ ctrl->sdid_offset);
+ return *p;
+}
+
+/*
+ * Domain creation config
+ *
+ * sbi_dom and regions are mutually exclusive.
+ * when a supervisor domain is backed by a SBI domain
+ * then the regions list and access permissions are derived
+ * from the SBI domain structures.
+ */
+struct sbi_mpt_domain_config {
+ /* SBI domain whose S/U memregions are mapped. */
+ struct sbi_domain *sbi_dom;
+ /* permission for regions inherited from sbi_dom */
+ u8 xwr;
+ /* protect firmware regions for the domain */
+ bool fw_protect;
+ /*
+ * explicit region list mapped in MPT table.
+ * NULL when sbi_dom covers everything
+ */
+ struct sbi_mpt_region *regions;
+ u32 nregions;
+};
+
+int sbi_mpt_init(void);
+
+int sbi_mpt_domain_create(const struct sbi_mpt_domain_config *cfg, u32 *out_sdid);
+
+int sbi_mpt_domain_add_region(u32 sdid, unsigned long pa, unsigned long size, u8 xwr);
+
+int sbi_mpt_domain_remove_region(u32 sdid, unsigned long pa, unsigned long size);
+
+int sbi_mpt_share_region(u32 src_sdid, u32 dst_sdid, unsigned long pa, unsigned long size, u8 dst_xwr);
+
+int sbi_mpt_unshare_region(u32 sdid, unsigned long pa, unsigned long size);
+
+int sbi_mpt_hart_activate(u32 sdid);
+
+int sbi_mpt_hart_activate_for_domain(struct sbi_domain *sbi_dom);
+
+struct sbi_mpt_domain *sbi_mpt_domain_get(u32 sdid);
+
+unsigned long sbi_mpt_pool_alloc(unsigned long size, unsigned long align);
+
+void sbi_mpt_sdid_free(u32 sdid);
+
+void sbi_mpt_dump(void);
+
+#endif /* __SBI_MPT_H__ */
diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk
index c29c888f..3b3c3ea3 100644
--- a/lib/sbi/objects.mk
+++ b/lib/sbi/objects.mk
@@ -108,3 +108,5 @@ libsbi-objs-y += sbi_expected_trap.o
libsbi-objs-y += sbi_cppc.o
libsbi-objs-$(CC_SUPPORT_VECTOR) += sbi_vector.o
libsbi-objs-y += sbi_fp.o
+libsbi-objs-y += sbi_mpt.o
+libsbi-objs-y += sbi_mpt_mode.o
diff --git a/lib/sbi/sbi_mpt.c b/lib/sbi/sbi_mpt.c
new file mode 100644
index 00000000..601fdea0
--- /dev/null
+++ b/lib/sbi/sbi_mpt.c
@@ -0,0 +1,882 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Qualcomm Inc.
+ *
+ * Authors: Rahul Pathak <rahul.pathak at oss.qualcomm.com>
+ */
+
+#include <sbi/sbi_mpt.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_hart.h>
+#include <sbi/sbi_heap.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi/sbi_string.h>
+#include <sbi/sbi_console.h>
+#include <sbi/riscv_locks.h>
+#include <sbi/riscv_asm.h>
+
+static struct sbi_mpt_ctrl mpt_ctrl;
+
+struct sbi_mpt_ctrl *sbi_mpt_ctrl_get(void)
+{
+ return &mpt_ctrl;
+}
+
+struct sbi_mpt_domain *sbi_mpt_domain_get(u32 sdid)
+{
+ struct sbi_mpt_domain *sd;
+
+ if (sdid >= mpt_ctrl.max_domains)
+ return NULL;
+
+ sd = mpt_ctrl.domains[sdid].valid ? &mpt_ctrl.domains[sdid] : NULL;
+
+ return sd;
+}
+
+/* Memory allocator for MPT tables from heap */
+unsigned long sbi_mpt_pool_alloc(unsigned long size, unsigned long align)
+{
+ unsigned long ptr = (unsigned long)sbi_aligned_alloc(align, size);
+
+ if (!ptr) {
+ sbi_printf("sbi_mpt: alloc failed (size=0x%lx align=0x%lx)\n",
+ size, align);
+ return 0;
+ }
+
+ /* MPT tables must be zeroed to mark them invalid (mpte.V = 0) */
+ sbi_memset((void *)ptr, 0, size);
+
+ return ptr;
+}
+
+/* Check if two regions overlap */
+static bool regions_overlap(unsigned long pa1, unsigned long size1,
+ unsigned long pa2, unsigned long size2)
+{
+ return (pa1 < pa2 + size2) && (pa2 < pa1 + size1);
+}
+
+/* Check if a region is completely contained inside another region */
+static bool region_contained(unsigned long child_pa, unsigned long child_size,
+ unsigned long parent_pa, unsigned long parent_size)
+{
+ return (child_pa >= parent_pa) &&
+ (child_pa + child_size <= parent_pa + parent_size);
+}
+
+/*
+ * Check if a region [pa, pa+size] overlaps with any mapped
+ * region which is locked
+ */
+static bool region_overlaps_locked(const struct sbi_mpt_domain *dom,
+ unsigned long pa, unsigned long size)
+{
+ unsigned int i;
+
+ for (i = 0; i < dom->nregions; i++)
+ if (dom->regions[i].locked &&
+ regions_overlap(pa, size, dom->regions[i].pa, dom->regions[i].size))
+ return true;
+ return false;
+}
+
+/* Probe SDIDLEN from MMPT CSR */
+static u32 probe_sdidlen(void)
+{
+ unsigned long mmpt_val, sdid_val;
+ u32 sdidlen = 0;
+
+ mmpt_val = csr_read(SBI_CSR_MMPT);
+
+ /* Write all-ones into SDID field */
+ mmpt_val |= SBI_MMPT_SDID_MASK;
+ csr_write(SBI_CSR_MMPT, SBI_MMPT_SDID_MASK);
+
+ sdid_val = (csr_read(SBI_CSR_MMPT) & SBI_MMPT_SDID_MASK) >> SBI_MMPT_SDID_SHIFT;
+
+ if (sdid_val) {
+ sdidlen = 0;
+ while (sdid_val) {
+ sdidlen+=1;
+ sdid_val >>= 1;
+ }
+
+ if (sdidlen > SBI_MPT_SDIDMAX)
+ sdidlen = SBI_MPT_SDIDMAX;
+ }
+
+ csr_write(SBI_CSR_MMPT, mmpt_val);
+
+ return sdidlen;
+}
+
+#if __riscv_xlen == 64
+extern struct sbi_mpt_mode smmpt43_mode;
+extern struct sbi_mpt_mode smmpt52_mode;
+extern struct sbi_mpt_mode smmpt64_mode;
+#else
+extern struct sbi_mpt_mode smmpt34_mode;
+#endif
+
+static bool probe_mode(u32 mode_val)
+{
+ u32 m;
+ csr_write(SBI_CSR_MMPT,
+ ((unsigned long)mode_val << SBI_MMPT_MODE_SHIFT) & SBI_MMPT_MODE_MASK);
+
+ m = (csr_read(SBI_CSR_MMPT) & SBI_MMPT_MODE_MASK) >> SBI_MMPT_MODE_SHIFT;
+
+ return (m == mode_val);
+}
+
+
+/*
+ * Probe SMMPT mode and initialize the related mode ops
+ *
+ * Deliberately prefer coverage over latency, because latency can be
+ * improved by the MPT cache which hardware may implement but the
+ * coverage is not. So pick the widest mode which is supported by the
+ * hardware.
+ */
+static struct sbi_mpt_mode *init_mpt_mode(void)
+{
+ unsigned long mmpt_val = csr_read(SBI_CSR_MMPT);
+ struct sbi_mpt_mode *s = NULL;
+
+#if __riscv_xlen == 64
+ if (probe_mode(SBI_MMPT_MODE_SMMPT64))
+ s = &smmpt64_mode;
+ else if (probe_mode(SBI_MMPT_MODE_SMMPT52))
+ s = &smmpt52_mode;
+ else if (probe_mode(SBI_MMPT_MODE_SMMPT43))
+ s = &smmpt43_mode;
+#else
+ if (probe_mode(SBI_MMPT_MODE_SMMPT34))
+ s = &smmpt34_mode;
+#endif
+
+ csr_write(SBI_CSR_MMPT, mmpt_val);
+ return s;
+}
+
+/*
+ * mpt_map_range_perm(): Maps [pa, pa+size] region into supervisor domain MPT table
+ * with its access permissions(xwr).
+ *
+ * locked=true: means entry is immutable and it cannot be modified. Used for
+ * firmware deny-all and other such regions for permanenet access policy.
+ *
+ * locked=false: region entry is modifiable at runtime via add/remove_region.
+ * Used for S/U-mode accessible domain regions.
+ */
+
+static int mpt_map_range_perm(struct sbi_mpt_domain *dom, struct sbi_mpt_mode *sch,
+ unsigned long pa, unsigned long size, u8 xwr, bool locked)
+{
+ int rc;
+ bool first_in_range;
+
+ if (!size) {
+ sbi_printf("sbi_mpt: invalid size for range 0x%lx\n", pa);
+ return SBI_EINVAL;
+ }
+
+ if ((pa & SBI_MPT_PAGE_MASK) || (size & SBI_MPT_PAGE_MASK)) {
+ sbi_printf("sbi_mpt: unaligned range 0x%lx+0x%lx\n", pa, size);
+ return SBI_EINVAL;
+ }
+
+ if (!sch->pa_in_range(pa, size)) {
+ /*
+ * A region that pa_in_range() returns false falls into one of two
+ * different cases - PA which is out of range from the mappable
+ * range of that mode and second is PA is in range but the end
+ * (pa + size) is crossing the mappable range.
+ *
+ * Distinguish the two by probing pa_in_range() for just
+ * the first page in that big range and if even the first page
+ * is out of range it is the first case and if the base is in
+ * range but the whole region is not, it is case later.
+ */
+ first_in_range = sch->pa_in_range(pa, SBI_MPT_PAGE_SIZE);
+
+ if (locked) {
+ sbi_printf("sbi_mpt: locked region 0x%lx+0x%lx out of mode range\n",
+ pa, size);
+ return SBI_EINVAL;
+ }
+
+ /* Region with PA which partially crossover the mappable space */
+ if (first_in_range) {
+ sbi_printf("sbi_mpt: fatal region 0x%lx+0x%lx crossover"
+ " mode '%s' ceiling; high part may alias"
+ " with low MPTE entries — not mapping this region\n",
+ pa, size, sch->name);
+ return SBI_EINVAL;
+ }
+
+ /* Region which is entirely above the ceiling — unreachable */
+ sbi_printf("sbi_mpt: skip out-of-range 0x%lx+0x%lx"
+ " (entirely above '%s' ceiling; no MPT entry,\n",
+ pa, size, sch->name);
+ return 0;
+ }
+
+ rc = sch->map_range(dom, pa, size, xwr);
+ if (rc)
+ return rc;
+
+ if (dom->nregions >= SBI_MPT_MAX_REGIONS_DOMAIN)
+ return SBI_ENOMEM;
+
+ dom->regions[dom->nregions].pa = pa;
+ dom->regions[dom->nregions].size = size;
+ dom->regions[dom->nregions].xwr = xwr;
+ dom->regions[dom->nregions].locked = locked;
+ dom->regions[dom->nregions].shared = false;
+ dom->nregions++;
+
+ return 0;
+}
+
+/*
+ * mpt_xwr_from_memregion(): derive an MPTE XWR encoding from a SBI domain
+ * memregion's S/U access flags, clamped by cap.
+ *
+ * cap is an upper bound the caller may impose. A region from SBI domain
+ * may have more permissive access permissions which an supervisor domain
+ * may want to restict and cap them.
+ */
+static u8 mpt_xwr_from_memregion(const struct sbi_domain_memregion *mr,
+ u8 cap)
+{
+ u8 xwr = SBI_MPT_PERM_NONE;
+
+ if (mr->flags & SBI_DOMAIN_MEMREGION_SU_READABLE)
+ xwr |= SBI_MPT_PERM_R;
+ if (mr->flags & SBI_DOMAIN_MEMREGION_SU_WRITABLE)
+ xwr |= SBI_MPT_PERM_W;
+ if (mr->flags & SBI_DOMAIN_MEMREGION_SU_EXECUTABLE)
+ xwr |= SBI_MPT_PERM_X;
+
+ xwr &= cap;
+
+ /* XWR encodings 0b010 (W) and 0b110 (WX) are reserved */
+ if ((xwr & SBI_MPT_PERM_W) && !(xwr & SBI_MPT_PERM_R)) {
+ sbi_printf("sbi_mpt: region 0x%lx: W without R is a reserved"
+ " XWR encoding — denying region\n", mr->base);
+ return SBI_MPT_PERM_NONE;
+ }
+
+ return xwr;
+}
+
+/*
+ * sbi_mpt_init(): Initialize MPT structures
+ */
+int sbi_mpt_init(void)
+{
+ u32 *p;
+ unsigned long fw_end;
+ struct sbi_scratch *s;
+ struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+ unsigned long fw_pa = scratch->fw_start;
+ unsigned long fw_size = scratch->fw_size;
+
+ if (mpt_ctrl.ready)
+ return 0;
+
+ if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMSDID)) {
+ sbi_printf("sbi_mpt: Smsdid extension absent\n");
+ return SBI_ENODEV;
+ }
+
+ if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMMPT)) {
+ sbi_printf("sbi_mpt: Smmpt extension absent\n");
+ return SBI_ENODEV;
+ }
+
+ mpt_ctrl.sdid_len = probe_sdidlen();
+ if (!mpt_ctrl.sdid_len) {
+ sbi_printf("sbi_mpt: SDIDLEN=0 — SDID field not implemented\n");
+ return SBI_ENODEV;
+ }
+ mpt_ctrl.max_domains = UL(1) << mpt_ctrl.sdid_len;
+
+ /*
+ * Initialise SDID bitmap
+ * set bits 0..(max_domains-1) as 1 marking those SDIDs free.
+ */
+ bitmap_fill(mpt_ctrl.sdid_bitmap, mpt_ctrl.max_domains);
+
+ mpt_ctrl.mode = init_mpt_mode();
+ if (!mpt_ctrl.mode) {
+ sbi_printf("sbi_mpt: WARL probe found no mode\n");
+ return SBI_ENODEV;
+ }
+
+ /* Round fw_pa and fw_size to page boundaries before storing. */
+ fw_end = (fw_pa + fw_size + SBI_MPT_PAGE_SIZE - 1UL) & ~SBI_MPT_PAGE_MASK;
+ mpt_ctrl.fw_pa = fw_pa & ~SBI_MPT_PAGE_MASK;
+ mpt_ctrl.fw_size = fw_end - mpt_ctrl.fw_pa;
+
+
+ /* Per-hart SDID scratch slot — initialise all harts to INVALID */
+ mpt_ctrl.sdid_offset = sbi_scratch_alloc_offset(sizeof(u32));
+ if (!mpt_ctrl.sdid_offset) {
+ sbi_printf("sbi_mpt: scratch alloc for SDID failed\n");
+ }
+ else {
+ sbi_for_each_hartindex(i) {
+ s = sbi_hartindex_to_scratch(i);
+ if (s) {
+ p = sbi_scratch_offset_ptr(s, mpt_ctrl.sdid_offset);
+ *p = SBI_MPT_SDID_INVALID;
+ }
+ }
+ }
+
+ mpt_ctrl.ready = true;
+
+ return 0;
+}
+
+/*
+ * sbi_mpt_domain_create(): Create a Supervisor Domain
+ */
+int sbi_mpt_domain_create(const struct sbi_mpt_domain_config *cfg,
+ u32 *out_sdid)
+{
+ int rc;
+ u32 sdid, i;
+ unsigned long root_pa;
+ struct sbi_mpt_mode *sch;
+ struct sbi_mpt_domain *dom;
+ const struct sbi_domain_memregion *mr;
+ struct sbi_mpt_ctrl *ctrl = &mpt_ctrl;
+ u8 base_xwr = SBI_MPT_PERM_NONE;
+
+ if (!ctrl->ready)
+ return SBI_ENODEV;
+
+ if (cfg->sbi_dom && cfg->nregions) {
+ sbi_printf("sbi_mpt: sbi_dom and explicit regions are mutually exclusive\n");
+ return SBI_EINVAL;
+ }
+
+ /*
+ * sdid_bitmap empty means every available SDID is
+ * currently in use and no domain can be created.
+ */
+ if (bitmap_empty(ctrl->sdid_bitmap, ctrl->max_domains))
+ return SBI_ENOMEM;
+
+ sch = ctrl->mode;
+
+ root_pa = sbi_mpt_pool_alloc(sch->root_table_size(),
+ sch->root_table_align());
+ if (!root_pa) {
+ sbi_printf("sbi_mpt: OOM root table\n");
+ return SBI_ENOMEM;
+ }
+
+ /*
+ * Root MPT memory allocated — take SDID from bitmap for root supervisor
+ * domain
+ */
+ for (sdid = 0; sdid < ctrl->max_domains; sdid++)
+ if (bitmap_test(ctrl->sdid_bitmap, sdid))
+ break;
+ bitmap_clear(ctrl->sdid_bitmap, sdid, 1);
+
+ dom = &ctrl->domains[sdid];
+ SPIN_LOCK_INIT(dom->lock);
+
+ dom->sdid = sdid;
+ dom->valid = true;
+ dom->root_pa = root_pa;
+ dom->mode = sch;
+ dom->sbi_dom = cfg->sbi_dom;
+ dom->nregions = 0;
+
+ if (cfg->sbi_dom) {
+ /*
+ * Similar to SBI domain, create a baseline which
+ * maps whole mappable address space by a mode and mark it
+ * with XWR permissions. Later selectively restrict the
+ * permissions in another pass.
+ */
+ sbi_domain_for_each_memregion(cfg->sbi_dom, mr) {
+ if (mr->order < sizeof(unsigned long) * 8)
+ continue;
+
+ if (!(mr->flags & SBI_DOMAIN_MEMREGION_SU_ACCESS_MASK))
+ continue;
+
+ base_xwr = mpt_xwr_from_memregion(mr, cfg->xwr);
+ rc = sch->map_full_range(dom, base_xwr);
+ if (rc)
+ goto fail;
+
+ if (dom->nregions < SBI_MPT_MAX_REGIONS_DOMAIN) {
+ struct sbi_mpt_region *r = &dom->regions[dom->nregions++];
+ r->pa = 0;
+ r->size = ~0UL & ~SBI_MPT_PAGE_MASK;
+ r->xwr = base_xwr;
+ r->locked = false;
+ r->shared = false;
+ }
+
+ break;
+ }
+
+ /*
+ * Another pass to map every other region which has its own
+ * permissions derived from the memregion's S/U flags.
+ */
+ sbi_domain_for_each_memregion(cfg->sbi_dom, mr) {
+ if (mr->order >= sizeof(unsigned long) * 8)
+ continue;
+
+ rc = mpt_map_range_perm(dom, sch, mr->base, BIT(mr->order),
+ mpt_xwr_from_memregion(mr, cfg->xwr), false);
+ if (rc)
+ goto fail;
+ }
+ }
+
+ /* Map explicit regions which are not part of any SBI domain */
+ for (i = 0; i < cfg->nregions; i++) {
+ rc = mpt_map_range_perm(dom, sch, cfg->regions[i].pa, cfg->regions[i].size,
+ cfg->regions[i].xwr, false);
+ if (rc)
+ goto fail;
+ }
+
+ /*
+ * Map firmware region with no permission with and mark as locked
+ */
+ if (cfg->fw_protect) {
+ rc = mpt_map_range_perm(dom, sch, ctrl->fw_pa, ctrl->fw_size,
+ SBI_MPT_PERM_NONE, true);
+ if (rc) {
+ sbi_printf("sbi_mpt: cannot protect firmware"
+ " rc=%d\n", rc);
+ goto fail;
+ }
+ }
+
+ ctrl->ndomain++;
+ if (out_sdid)
+ *out_sdid = sdid;
+
+ sbi_mpt_fence_sdid(sdid);
+
+ /* To be removed */
+ sbi_printf("sbi_mpt: SDID %u root=0x%lx mode=%s dom=%s\n",
+ sdid, root_pa, sch->name, cfg->sbi_dom ? cfg->sbi_dom->name : "(none)");
+ return 0;
+
+fail:
+ bitmap_set(ctrl->sdid_bitmap, (int)sdid, 1);
+ dom->valid = false;
+ return rc;
+}
+
+/*
+ * sbi_mpt_domain_add_region(): Map a region in a MPT table
+ */
+int sbi_mpt_domain_add_region(u32 sdid, unsigned long pa,
+ unsigned long size, u8 xwr)
+{
+ struct sbi_mpt_domain *dom;
+ struct sbi_mpt_region *r;
+ int rc;
+
+ if (!mpt_ctrl.ready)
+ return SBI_ENODEV;
+ if (!size || (pa & SBI_MPT_PAGE_MASK) || (size & SBI_MPT_PAGE_MASK))
+ return SBI_EINVAL;
+
+ dom = sbi_mpt_domain_get(sdid);
+ if (!dom)
+ return SBI_EINVAL;
+
+ if (region_overlaps_locked(dom, pa, size)) {
+ sbi_printf("sbi_mpt: 0x%lx+0x%lx overlaps locked"
+ " (SDID %u)\n", pa, size, sdid);
+ return SBI_EINVAL;
+ }
+
+ if (!dom->mode->pa_in_range(pa, size)) {
+ /*
+ * A region that pa_in_range() rejects falls into one of two
+ * different cases - PA which is out of range from the mappable
+ * range of that mode and second is PA is in range but the end
+ * (PA + SIZE) is crossing the mappable range.
+ *
+ * Distinguish the two by probing pa_in_range() for just
+ * the base page and if even the base is out of range it is
+ * the first case and if the base is in range but the whole
+ * region is not, it is case later.
+ */
+ if (dom->mode->pa_in_range(pa, SBI_MPT_PAGE_SIZE))
+ sbi_printf("sbi_mpt: 0x%lx+0x%lx crossover mode '%s'"
+ " ceiling (SDID %u) — high part would alias;"
+ " rejected\n",
+ pa, size, dom->mode->name, sdid);
+ else
+ sbi_printf("sbi_mpt: 0x%lx+0x%lx out of range"
+ " (SDID %u)\n", pa, size, sdid);
+ return SBI_EINVAL;
+ }
+
+ spin_lock(&dom->lock);
+ rc = dom->mode->map_range(dom, pa, size, xwr);
+ spin_unlock(&dom->lock);
+
+ if (!rc) {
+ if (dom->nregions < SBI_MPT_MAX_REGIONS_DOMAIN) {
+ r = &dom->regions[dom->nregions++];
+ r->pa = pa;
+ r->size = size;
+ r->xwr = xwr;
+ r->locked = false;
+ r->shared = false;
+ }
+
+ sbi_mpt_fence_sdid(sdid);
+ }
+
+ return rc;
+}
+
+/*
+ * sbi_mpt_domain_remove_region(): Unmap a region from the MPT table
+ */
+int sbi_mpt_domain_remove_region(u32 sdid, unsigned long pa,
+ unsigned long size)
+{
+ int rc;
+ unsigned int i;
+ struct sbi_mpt_domain *dom;
+ struct sbi_mpt_region *r;
+
+ if (!mpt_ctrl.ready)
+ return SBI_ENODEV;
+
+ if (!size || (pa & SBI_MPT_PAGE_MASK) || (size & SBI_MPT_PAGE_MASK))
+ return SBI_EINVAL;
+
+ dom = sbi_mpt_domain_get(sdid);
+ if (!dom)
+ return SBI_EINVAL;
+
+ for (i = 0; i < dom->nregions; i++) {
+ r = &dom->regions[i];
+
+ if (r->locked && region_contained(pa, size, r->pa, r->size))
+ return SBI_EDENIED;
+ /*
+ * Shared regions must be removed via sbi_mpt_unshare_region()
+ * because that function prevents one domain revoking a region
+ * that another domain still has mapped.
+ */
+ if (r->shared && r->pa == pa && r->size == size)
+ return SBI_EDENIED;
+ }
+
+ spin_lock(&dom->lock);
+ rc = dom->mode->map_range(dom, pa, size, SBI_MPT_PERM_NONE);
+ spin_unlock(&dom->lock);
+
+ if (!rc)
+ sbi_mpt_fence_sdid(sdid);
+
+ return rc;
+}
+
+/*
+ * Shared memory regions
+ *
+ * sbi_mpt_share_region() — map a region into a second domain.
+ *
+ * Both domains MPT tables are updated independently — each has its
+ * own root/inner/leaf table tree. The same PA range can have different
+ * XWR permissions in different domains.
+ *
+ */
+
+int sbi_mpt_share_region(u32 src_sdid, u32 dst_sdid,
+ unsigned long pa, unsigned long size,
+ u8 dst_xwr)
+{
+ unsigned int i;
+ int rc;
+ struct sbi_mpt_domain *src_dom, *dst_dom;
+
+ if (!mpt_ctrl.ready)
+ return SBI_ENODEV;
+
+ if (!size || (pa & SBI_MPT_PAGE_MASK) || (size & SBI_MPT_PAGE_MASK))
+ return SBI_EINVAL;
+
+ if (src_sdid == dst_sdid)
+ return SBI_EINVAL;
+
+ src_dom = sbi_mpt_domain_get(src_sdid);
+ dst_dom = sbi_mpt_domain_get(dst_sdid);
+ if (!src_dom || !dst_dom)
+ return SBI_EINVAL;
+
+ if (region_overlaps_locked(dst_dom, pa, size)) {
+ sbi_printf("sbi_mpt: share 0x%lx+0x%lx overlaps locked region in SDID %u\n",
+ pa, size, dst_sdid);
+ return SBI_EINVAL;
+ }
+
+ /* Reject reserved xwr permissions: 0b010 and 0b110 (W without R) */
+ if ((dst_xwr & SBI_MPT_PERM_W) && !(dst_xwr & SBI_MPT_PERM_R)) {
+ sbi_printf("sbi_mpt: share 0x%lx+0x%lx: W without R is a reserved XWR encoding\n",
+ pa, size);
+ return SBI_EINVAL;
+ }
+
+ if (dst_dom->nregions >= SBI_MPT_MAX_REGIONS_DOMAIN) {
+ sbi_printf("sbi_mpt: share: SDID %u region table full\n", dst_sdid);
+ return SBI_ENOMEM;
+ }
+
+ spin_lock(&dst_dom->lock);
+ rc = dst_dom->mode->map_range(dst_dom, pa, size, dst_xwr);
+ spin_unlock(&dst_dom->lock);
+ if (rc)
+ return rc;
+
+ dst_dom->regions[dst_dom->nregions].pa = pa;
+ dst_dom->regions[dst_dom->nregions].size = size;
+ dst_dom->regions[dst_dom->nregions].xwr = dst_xwr;
+ dst_dom->regions[dst_dom->nregions].locked = false;
+ dst_dom->regions[dst_dom->nregions].shared = true;
+ dst_dom->nregions++;
+
+ for (i = 0; i < src_dom->nregions; i++) {
+ if (src_dom->regions[i].pa == pa &&
+ src_dom->regions[i].size == size) {
+ src_dom->regions[i].shared = true;
+ break;
+ }
+ }
+
+ sbi_mpt_fence_sdid(src_sdid);
+ sbi_mpt_fence_sdid(dst_sdid);
+
+ sbi_printf("sbi_mpt: shared 0x%lx+0x%lx SDID %u → SDID %u xwr=%u\n",
+ pa, size, src_sdid, dst_sdid, dst_xwr);
+
+ return 0;
+}
+
+/*
+ * sbi_mpt_unshare_region() — Unmap a shared region from a domain.
+ *
+ * The other domain remains unaffected just the shared flag gets removed
+ * for that region from both the domains.
+ */
+int sbi_mpt_unshare_region(u32 sdid, unsigned long pa, unsigned long size)
+{
+ u32 s;
+ int rc;
+ unsigned int i, j;
+ struct sbi_mpt_domain *dom, *peer;
+
+ if (!mpt_ctrl.ready)
+ return SBI_ENODEV;
+
+ if (!size || (pa & SBI_MPT_PAGE_MASK) || (size & SBI_MPT_PAGE_MASK))
+ return SBI_EINVAL;
+
+ dom = sbi_mpt_domain_get(sdid);
+ if (!dom)
+ return SBI_EINVAL;
+
+ for (i = 0; i < dom->nregions; i++) {
+ if (dom->regions[i].pa == pa &&
+ dom->regions[i].size == size &&
+ dom->regions[i].shared)
+ break;
+ }
+ if (i == dom->nregions) {
+ sbi_printf("sbi_mpt: unshare 0x%lx+0x%lx not found in SDID %u\n",
+ pa, size, sdid);
+ return SBI_EINVAL;
+ }
+
+ spin_lock(&dom->lock);
+ rc = dom->mode->map_range(dom, pa, size, SBI_MPT_PERM_NONE);
+ spin_unlock(&dom->lock);
+
+ if (rc)
+ return rc;
+
+ dom->nregions -= 1;
+ dom->regions[i] = dom->regions[dom->nregions];
+
+ /* Clear the shared flag on any other domain that has this PA region shared.*/
+ for (s = 0; s < mpt_ctrl.max_domains; s++) {
+ peer = sbi_mpt_domain_get(s);
+ if (!peer || peer == dom)
+ continue;
+
+ for (j = 0; j < peer->nregions; j++)
+ if (peer->regions[j].pa == pa && peer->regions[j].size == size)
+ peer->regions[j].shared = false;
+ }
+
+ sbi_mpt_fence_sdid(sdid);
+
+ return 0;
+}
+
+/*
+ * Disable SMMPT on hart
+ */
+void sbi_mpt_hart_deactivate(void)
+{
+ /*
+ * mmpt is inactive in M-mode per spec, so no CSR write
+ * is needed.
+ */
+}
+
+/*
+ * Enable SMMPT on hart
+ */
+int sbi_mpt_hart_activate(u32 sdid)
+{
+ u32 *p;
+ unsigned long ppn;
+ struct sbi_mpt_domain *dom;
+
+ if (!mpt_ctrl.ready)
+ return SBI_ENODEV;
+
+ dom = sbi_mpt_domain_get(sdid);
+ if (!dom)
+ return SBI_EINVAL;
+
+ ppn = dom->root_pa >> SBI_MPT_PAGE_SHIFT;
+ csr_write(SBI_CSR_MMPT, dom->mode->encode_mmpt(ppn, sdid));
+ sbi_mpt_fence_sdid(sdid);
+
+ if (mpt_ctrl.sdid_offset) {
+ p = sbi_scratch_offset_ptr(sbi_scratch_thishart_ptr(), mpt_ctrl.sdid_offset);
+ *p = sdid;
+ }
+
+ return 0;
+}
+
+/*
+ * Activate SMMPT for all harts which are associated with an linked
+ * SBI domain in an supervisor domain.
+ */
+int sbi_mpt_hart_activate_for_domain(struct sbi_domain *sbi_dom)
+{
+ u32 s;
+
+ if (!mpt_ctrl.ready)
+ return SBI_ENODEV;
+ if (!sbi_dom)
+ return SBI_EINVAL;
+
+ /*
+ * Search all supervisor domains for associated sbi_dom and
+ * activate harts for that supervisor domain
+ */
+ for (s = 0; s < mpt_ctrl.max_domains; s++) {
+ if (mpt_ctrl.domains[s].valid && mpt_ctrl.domains[s].sbi_dom == sbi_dom)
+ return sbi_mpt_hart_activate(s);
+ }
+
+ return 0;
+}
+
+/*
+ * sbi_mpt_sdid_free(): Returns SDID to the free pool after domain destroy.
+ */
+void sbi_mpt_sdid_free(u32 sdid)
+{
+ if (!mpt_ctrl.ready || sdid >= mpt_ctrl.max_domains)
+ return;
+ bitmap_set(mpt_ctrl.sdid_bitmap, (int)sdid, 1);
+ mpt_ctrl.domains[sdid].valid = false;
+}
+
+/*
+ * Debug dump
+ */
+void sbi_mpt_dump(void)
+{
+ u32 s;
+ unsigned int r;
+ const struct sbi_mpt_domain *d;
+ const unsigned long catchall_size = ~0UL & ~SBI_MPT_PAGE_MASK;
+
+ if (!mpt_ctrl.ready) {
+ sbi_printf("sbi_mpt: not initialised\n");
+ return;
+ }
+
+ sbi_printf("sbi_mpt: mode=%-10s SDIDLEN=%u max_domains=%u\n", mpt_ctrl.mode->name,
+ mpt_ctrl.sdid_len, mpt_ctrl.max_domains);
+ sbi_printf("root_size=0x%lx root_align=0x%lx\n",
+ mpt_ctrl.mode->root_table_size(), mpt_ctrl.mode->root_table_align());
+ sbi_printf("fw 0x%016lx+0x%lx pool=global heap\n",
+ mpt_ctrl.fw_pa, mpt_ctrl.fw_size);
+ for (s = 0; s < mpt_ctrl.max_domains; s++) {
+ bool has_baseline = false;
+ u8 baseline_xwr = SBI_MPT_PERM_NONE;
+
+ d = &mpt_ctrl.domains[s];
+ if (!d->valid)
+ continue;
+ sbi_printf("SDID %2u root=0x%016lx regions=%u dom=%s\n",
+ d->sdid, d->root_pa, d->nregions, d->sbi_dom ? d->sbi_dom->name : "(none)");
+ for (r = 0; r < d->nregions; r++) {
+
+ if (d->regions[r].pa == 0 &&
+ d->regions[r].size == catchall_size) {
+ has_baseline = true;
+ baseline_xwr = d->regions[r].xwr;
+ continue;
+ }
+
+ sbi_printf("S-Domain%u Region%02u : 0x%016lx+0x%-16lx: xwr=%u (%c%c%c)%s%s\n",
+ d->sdid, r, d->regions[r].pa,
+ d->regions[r].size,
+ d->regions[r].xwr,
+ (d->regions[r].xwr & SBI_MPT_PERM_R) ? 'R' : '-',
+ (d->regions[r].xwr & SBI_MPT_PERM_W) ? 'W' : '-',
+ (d->regions[r].xwr & SBI_MPT_PERM_X) ? 'X' : '-',
+ d->regions[r].locked ? " [LOCKED]" : "",
+ d->regions[r].shared ? " [SHARED]" : "");
+ }
+
+ if (has_baseline) {
+ sbi_printf("S-Domain%u Default : %-37s: xwr=%u (%c%c%c) [baseline, overlay by regions above]\n",
+ d->sdid, "Rest of the address space",
+ baseline_xwr,
+ (baseline_xwr & SBI_MPT_PERM_R) ? 'R' : '-',
+ (baseline_xwr & SBI_MPT_PERM_W) ? 'W' : '-',
+ (baseline_xwr & SBI_MPT_PERM_X) ? 'X' : '-');
+ }
+ else {
+ sbi_printf("S-Domain%u : xwr=0 (---) [no baseline — unmapped PAs denied]\n",
+ d->sdid);
+ }
+ }
+}
diff --git a/lib/sbi/sbi_mpt_mode.c b/lib/sbi/sbi_mpt_mode.c
new file mode 100644
index 00000000..f40b8ca0
--- /dev/null
+++ b/lib/sbi/sbi_mpt_mode.c
@@ -0,0 +1,891 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Qualcomm Inc.
+ *
+ * Authors: Rahul Pathak <rahul.pathak at oss.qualcomm.com>
+ */
+
+#include <sbi/sbi_mpt.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_string.h>
+#include <sbi/sbi_console.h>
+
+#if __riscv_xlen == 64
+
+/*
+ * Smmpt43 — 43-bit supervisor physical address (SPA)
+ * +---------+---------+---------+---------+--------+
+ * | pn[2] | pn[1] | pn[0] | pi | offset |
+ * | [42:34] | [33:25] | [24:16] | [15:12] | [11:0] |
+ * | 9b | 9b | 9b | 4b | 12b |
+ * +---------+---------+---------+---------+--------+
+ *
+ * Smmpt52 — 52-bit supervisor physical address (SPA)
+ * +---------+---------+---------+---------+---------+--------+
+ * | pn[3] | pn[2] | pn[1] | pn[0] | pi | offset |
+ * | [51:43] | [42:34] | [33:25] | [24:16] | [15:12] | [11:0] |
+ * | 9b | 9b | 9b | 9b | 4b | 12b |
+ * +---------+---------+---------+---------+---------+--------+
+ *
+ * Smmpt64 — 64-bit supervisor physical address (SPA)
+ * +---------+---------+---------+---------+---------+---------+--------+
+ * | pn[4] | pn[3] | pn[2] | pn[1] | pn[0] | pi | offset |
+ * | [63:52] | [51:43] | [42:34] | [33:25] | [24:16] | [15:12] | [11:0] |
+ * | 12b | 9b | 9b | 9b | 9b | 4b | 12b |
+ * +---------+---------+---------+---------+---------+---------+--------+
+ *
+ */
+
+/* Innter entries are MPTEs not in root MPT page */
+#define RV64_INNER_ENTRIES 512U
+#define RV64_MPTE_SIZE 8UL
+#define RV64_TABLE_SIZE (RV64_INNER_ENTRIES * RV64_MPTE_SIZE)
+
+/* Smmpt64 root: 4096 entries × 8B = 32KB, 32KB aligned */
+#define RV64_ROOT_SMMPT64_ENTRIES 4096U
+#define RV64_ROOT_SMMPT64_SIZE (RV64_ROOT_SMMPT64_ENTRIES * RV64_MPTE_SIZE)
+
+#define RV64_NUMPGINRANGE 4U
+#define RV64_PAGES_PER_MPTE (1U << RV64_NUMPGINRANGE)
+#define RV64_PAGES_PER_MPTE_MASK (RV64_PAGES_PER_MPTE - 1U)
+#define RV64_INNER_PN_BITS 9U
+
+/* Bytes covered by one complete leaf table = pages_per_leaf × PAGE_SIZE */
+#define RV64_LEAF_RANGE (RV64_PAGES_PER_MPTE * SBI_MPT_PAGE_SIZE)
+
+/*
+ * NAPOT G=4 for Smmpt43, Smmpt52 and Smmpt64
+ *
+ * G=4 means 2^(G+1)=32 contiguous leaf mpte which form a NAPOT group.
+ * Each RV64 leaf MPTE covers 64KiB so one NAPOT group covers 2MiB.
+ *
+ * All other G values apart from 4 are reserved.
+ */
+#define RV64_NAPOT_G 4U
+#define RV64_NAPOT_G_SHIFT 12U
+#define RV64_NAPOT_MPTE_COUNT (1U << (RV64_NAPOT_G + 1))
+#define RV64_NAPOT_SIZE ((unsigned long)RV64_NAPOT_MPTE_COUNT * RV64_LEAF_RANGE)
+#define RV64_NAPOT_PN0_ALIGN RV64_NAPOT_MPTE_COUNT
+
+/*
+ * Swith to disable NAPOT compaction of MPTE entries while writing.
+ */
+#define RV64_NAPOT_DISABLE 0
+
+/*
+ * Supervisor Physucal address fields extraction
+ */
+
+/*
+ * rv64_table_idx() — extract pn[level] from a SPA.
+ */
+static inline u32 rv64_table_idx(unsigned long pa, u32 level,
+ const struct sbi_mpt_mode *sch)
+{
+ u32 shift = 16 + level * 9;
+ u32 mask = (sch->mode_val == SBI_MMPT_MODE_SMMPT64 && level == 4) ? 0xFFF : 0x1FF;
+
+ return ((pa >> shift) & mask);
+}
+
+/*
+ * rv64_tuple_idx_shift() — XWR tuple index shift at level i.
+ */
+static inline u32 rv64_tuple_idx_shift(u32 level)
+{
+ return (SBI_MPT_PAGE_SHIFT + level * RV64_INNER_PN_BITS);
+}
+
+static inline u32 rv64_tuple_idx(unsigned long pa, u32 level)
+{
+ return ((pa >> rv64_tuple_idx_shift(level)) & RV64_PAGES_PER_MPTE_MASK);
+}
+
+/*
+ * rv64_mpte_range() — bytes covered by one MPTE at level L.
+ */
+static inline unsigned long rv64_mpte_range(u32 level)
+{
+ return RV64_LEAF_RANGE << (level * RV64_INNER_PN_BITS);
+}
+
+/*
+ * RV64 MPTE Read/Write functions
+ */
+
+static inline u64 rv64_read_mpte(unsigned long pa)
+{
+ return *(volatile u64 *)pa;
+}
+
+static inline void rv64_write_mpte(unsigned long pa, u64 v)
+{
+ *(volatile u64 *)pa = v;
+}
+
+static inline unsigned long rv64_mpte_pa(unsigned long table_pa, u32 idx)
+{
+ return (table_pa + idx * RV64_MPTE_SIZE);
+}
+
+static inline unsigned long rv64_next_table_pa(u64 mpte)
+{
+ return (unsigned long)((u64)(mpte >> SBI_MPTE_PPN_SHIFT) << SBI_MPT_PAGE_SHIFT);
+}
+
+/*
+ * Generate Napot Leaf MPTE
+ *
+ * Constructs one MPTE value used for all
+ * RV64_NAPOT_MPTE_COUNT entries in a NAPOT group.
+ *
+ * All MPTEs in the group are identical
+ */
+static inline u64 rv64_napot_leaf_mpte(u8 xwr)
+{
+ u64 mpte = SBI_MPTE_V | SBI_MPTE_L | SBI_MPTE_N;
+
+ mpte = sbi_mpte_leaf_set_xwr(mpte, 0, xwr);
+ mpte |= ((u64)RV64_NAPOT_G << RV64_NAPOT_G_SHIFT);
+
+ return mpte;
+}
+
+/*
+ * rv64_napot_leaf_xwr() — recover the uniform XWR from a NAPOT leaf.
+ */
+static inline u8 rv64_napot_leaf_xwr(u64 mpte)
+{
+ return ((mpte >> SBI_MPTE_XWR_BASE) & SBI_MPTE_XWR_MASK);
+}
+
+/*
+ * rv64_napot_demote() — Convert a Napot MPTE group into RV64_NAPOT_MPTE_COUNT
+ * normal(Non-Napot) MPTE carrying the SAME permission.
+ *
+ * leaf_mpte_pa is any leaf mpte pa from the Napot group
+ */
+static void rv64_napot_demote(struct sbi_mpt_domain *dom,
+ unsigned long leaf_mpte_pa, unsigned long pa)
+{
+ u8 xwr;
+ u32 pn0, pg, i;
+ u64 leaf;
+ unsigned long grp_base;
+
+ xwr = rv64_napot_leaf_xwr(rv64_read_mpte(leaf_mpte_pa));
+ pn0 = rv64_table_idx(pa, 0, dom->mode);
+ grp_base = leaf_mpte_pa - (pn0 & (RV64_NAPOT_MPTE_COUNT - 1)) * RV64_MPTE_SIZE;
+ leaf = SBI_MPTE_V | SBI_MPTE_L;
+
+ for (pg = 0; pg < RV64_PAGES_PER_MPTE; pg++)
+ leaf = sbi_mpte_leaf_set_xwr(leaf, pg, xwr);
+
+ for (i = 0; i < RV64_NAPOT_MPTE_COUNT; i++)
+ rv64_write_mpte(grp_base + i * RV64_MPTE_SIZE, leaf);
+}
+
+/*
+ * MPT table best-level selection
+ *
+ * Returns the highest level at which the range [pa, pa+size] can be
+ * covered by a single leaf MPTE. Level 0 is always valid because thats
+ * the last resort.
+ */
+static u32 rv64_best_level(unsigned long pa, unsigned long size,
+ u32 top_level)
+{
+ u32 lvl;
+ unsigned long range;
+
+ for (lvl = top_level; lvl >= 1; lvl--) {
+ range = rv64_mpte_range(lvl);
+
+ if (size >= range && (pa & (range - 1)) == 0)
+ return lvl;
+ }
+ return 0;
+}
+
+/*
+ * Generic N-level walk with lazy table allocation
+ */
+static unsigned long rv64_split_leaf(unsigned long parent_ep)
+{
+ u64 parent = rv64_read_mpte(parent_ep);
+ unsigned long sub;
+ u32 j, pg, sh;
+ u8 xwr;
+ u64 child;
+
+ sub = sbi_mpt_pool_alloc(RV64_TABLE_SIZE, SBI_MPT_PAGE_SIZE);
+ if (!sub)
+ return 0;
+
+ for (j = 0; j < RV64_INNER_ENTRIES; j++) {
+ sh = sbi_mpte_xwr_shift(j >> 5);
+ xwr = (((unsigned long)parent >> sh) & SBI_MPTE_XWR_MASK);
+ child = SBI_MPTE_V | SBI_MPTE_L; /* N=0 uniform leaf */
+
+ for (pg = 0; pg < RV64_PAGES_PER_MPTE; pg++)
+ child = sbi_mpte_leaf_set_xwr(child, pg, xwr);
+
+ rv64_write_mpte(sub + j * RV64_MPTE_SIZE, child);
+ }
+
+ /* parent leaf -> non-leaf pointer to MPT sub table. */
+ rv64_write_mpte(parent_ep, sbi_mpte_nonleaf(sub));
+ return sub;
+}
+
+static unsigned long rv64_walk_alloc(struct sbi_mpt_domain *dom,
+ unsigned long pa,
+ unsigned long size,
+ u32 *out_level,
+ u32 top_level)
+{
+ u32 lvl, idx;
+ u64 mpte;
+ unsigned long ep, sub, new_pa;
+ const struct sbi_mpt_mode *sch = dom->mode;
+ unsigned long table_pa = dom->root_pa;
+ u32 best_lvl = rv64_best_level(pa, size, top_level);
+
+
+ for (lvl = top_level; lvl >= 1; lvl--) {
+ idx = rv64_table_idx(pa, lvl, sch);
+ ep = rv64_mpte_pa(table_pa, idx);
+ mpte = rv64_read_mpte(ep);
+
+ if (mpte & SBI_MPTE_L) {
+ if (lvl > best_lvl) {
+ sub = rv64_split_leaf(ep);
+ if (!sub)
+ return 0;
+ table_pa = sub;
+ continue;
+ }
+ *out_level = lvl;
+ return ep;
+ }
+
+ if (!(mpte & SBI_MPTE_V)) {
+ if (lvl <= best_lvl) {
+ *out_level = lvl;
+ return ep;
+ }
+ /* Allocate inner table (always 4KiB, all levels, all modes) */
+ new_pa = sbi_mpt_pool_alloc(RV64_TABLE_SIZE,
+ SBI_MPT_PAGE_SIZE);
+ if (!new_pa)
+ return 0;
+ rv64_write_mpte(ep, (u64)sbi_mpte_nonleaf(new_pa));
+ table_pa = new_pa;
+ }
+ else {
+ table_pa = rv64_next_table_pa(mpte);
+ }
+ }
+
+ *out_level = 0;
+ return rv64_mpte_pa(table_pa, rv64_table_idx(pa, 0, sch));
+}
+
+/*
+ * Generic map_range — shared by Smmpt43, Smmpt52, Smmpt64
+ *
+ * Maps a range [pa, pa+size] in the MPT table
+ */
+static int __rv64_map_range(struct sbi_mpt_domain *dom,
+ unsigned long pa, unsigned long size,
+ u8 xwr, u32 top_level)
+{
+ unsigned long cur = pa;
+ unsigned long end = pa + size;
+ unsigned long leaf_mpte_pa, mpte_range, mpte_base, mpte_end, batch_end;
+ u32 level, pn0, i, pg_first, pg_last, pg;
+ u64 napot, mpte;
+
+ while (cur < end) {
+ /*
+ * Check if the region qualifies for NAPOT range and the
+ * region is contained
+ */
+ if ((cur & (RV64_NAPOT_SIZE - 1)) == 0 &&
+ cur + RV64_NAPOT_SIZE > cur &&
+ cur + RV64_NAPOT_SIZE <= end) {
+ leaf_mpte_pa = rv64_walk_alloc(dom, cur, end - cur, &level, top_level);
+ if (!leaf_mpte_pa)
+ return SBI_ENOMEM;
+
+ pn0 = rv64_table_idx(cur, 0, dom->mode);
+
+ if (!RV64_NAPOT_DISABLE && level == 0 && (pn0 & (RV64_NAPOT_PN0_ALIGN - 1)) == 0) {
+ napot = rv64_napot_leaf_mpte(xwr);
+
+ for (i = 0; i < RV64_NAPOT_MPTE_COUNT; i++)
+ rv64_write_mpte(leaf_mpte_pa + i * RV64_MPTE_SIZE, napot);
+
+ cur += RV64_NAPOT_SIZE;
+ continue;
+ }
+ }
+ else {
+ leaf_mpte_pa = rv64_walk_alloc(dom, cur, end - cur, &level, top_level);
+ if (!leaf_mpte_pa)
+ return SBI_ENOMEM;
+ }
+
+ /* Normal path */
+ mpte_range = rv64_mpte_range(level);
+ pg_first = rv64_tuple_idx(cur, level);
+ mpte_base = cur & ~(mpte_range - 1);
+
+ mpte_end = mpte_base + mpte_range;
+ if (mpte_end < mpte_base)
+ mpte_end = ~0UL;
+
+ batch_end = (end < mpte_end) ? end : mpte_end;
+ pg_last = rv64_tuple_idx(batch_end - SBI_MPT_PAGE_SIZE, level);
+
+ /*
+ * If this level-0 MPTE is a Napot member, expand the
+ * whole naturally-aligned group to plain leaves and write same
+ * permissions xwr
+ */
+ mpte = rv64_read_mpte(leaf_mpte_pa);
+ if (level == 0 && (mpte & (u64)SBI_MPTE_N)) {
+ rv64_napot_demote(dom, leaf_mpte_pa, cur);
+ sbi_mpt_fence_sdid(dom->sdid);
+ mpte = rv64_read_mpte(leaf_mpte_pa);
+ }
+ if (!(mpte & (u64)(SBI_MPTE_V | SBI_MPTE_L)))
+ mpte = (u64)(SBI_MPTE_V | SBI_MPTE_L);
+
+ for (pg = pg_first; pg <= pg_last; pg++)
+ mpte = sbi_mpte_leaf_set_xwr(mpte, pg, xwr);
+
+ rv64_write_mpte(leaf_mpte_pa, mpte);
+
+ cur = batch_end;
+ }
+
+ return 0;
+}
+
+/*
+ * Per-mode mmpt encoding
+ */
+
+static unsigned long smmpt43_encode_mmpt(unsigned long ppn, u32 sdid)
+{
+ return sbi_mmpt_encode(SBI_MMPT_MODE_SMMPT43, sdid, ppn);
+}
+
+static unsigned long smmpt52_encode_mmpt(unsigned long ppn, u32 sdid)
+{
+ return sbi_mmpt_encode(SBI_MMPT_MODE_SMMPT52, sdid, ppn);
+}
+
+static unsigned long smmpt64_encode_mmpt(unsigned long ppn, u32 sdid)
+{
+ return sbi_mmpt_encode(SBI_MMPT_MODE_SMMPT64, sdid, ppn);
+}
+
+static unsigned long smmpt43_root_table_size(void)
+{
+ return RV64_TABLE_SIZE;
+}
+
+static unsigned long smmpt43_root_table_align(void)
+{
+ return SBI_MPT_PAGE_SIZE;
+}
+
+static bool smmpt43_pa_in_range(unsigned long pa, unsigned long size)
+{
+ unsigned long pa_max = 1UL << 43;
+
+ return (pa < pa_max && size <= pa_max - pa);
+}
+
+static unsigned long smmpt52_root_table_size(void)
+{
+ return RV64_TABLE_SIZE;
+}
+
+static unsigned long smmpt52_root_table_align(void)
+{
+ return SBI_MPT_PAGE_SIZE;
+}
+
+static bool smmpt52_pa_in_range(unsigned long pa, unsigned long size)
+{
+ unsigned long pa_max = 1UL << 52;
+
+ return (pa < pa_max && size <= pa_max - pa);
+}
+
+static unsigned long smmpt64_root_table_size(void)
+{
+ return RV64_ROOT_SMMPT64_SIZE;
+}
+
+static unsigned long smmpt64_root_table_align(void)
+{
+ return RV64_ROOT_SMMPT64_SIZE;
+}
+
+static bool smmpt64_pa_in_range(unsigned long pa,
+ unsigned long size)
+{
+ /*
+ * Smmpt64 covers the full 64-bit PA space.
+ */
+ return true;
+}
+
+/*
+ * Per-mode map_range wrappers
+ *
+ * Smmpt43:
+ * Root level 2, 9-bit, 512 entries, 4KiB root MPT size
+ *
+ * Smmpt52:
+ * Root level 3, 9-bit, 512 entries, 4KiB root MPT size
+ *
+ * Smmpt64:
+ * Root level 4, 12-bit, 4096 entries, 32KiB root MPT size
+ */
+
+static int smmpt43_map_range(struct sbi_mpt_domain *dom,
+ unsigned long pa, unsigned long size, u8 xwr)
+{
+ return __rv64_map_range(dom, pa, size, xwr, 2);
+}
+
+static int smmpt52_map_range(struct sbi_mpt_domain *dom,
+ unsigned long pa, unsigned long size, u8 xwr)
+{
+ return __rv64_map_range(dom, pa, size, xwr, 3);
+}
+
+static int smmpt64_map_range(struct sbi_mpt_domain *dom,
+ unsigned long pa, unsigned long size, u8 xwr)
+{
+ return __rv64_map_range(dom, pa, size, xwr, 4);
+}
+
+/*
+ * __rv64_map_full_range() — Write every MPTE at Root MPT
+ *
+ * Writes xwr into every root table MPTE directly as a leaf
+ * superpage covering the mode entire addressable PA range.
+ * No intermidiate MPT tables allocated just all leaf MPTEs at
+ * root level.
+ */
+
+static int __rv64_map_full_range(struct sbi_mpt_domain *dom, u8 xwr,
+ unsigned long root_entries)
+{
+ u32 pg;
+ unsigned long i;
+ u64 mpte = SBI_MPTE_V | SBI_MPTE_L;
+
+ for (pg = 0; pg < RV64_PAGES_PER_MPTE; pg++)
+ mpte = sbi_mpte_leaf_set_xwr(mpte, pg, xwr);
+
+ for (i = 0; i < root_entries; i++)
+ rv64_write_mpte(rv64_mpte_pa(dom->root_pa, i), mpte);
+
+ return 0;
+}
+
+static int smmpt43_map_full_range(struct sbi_mpt_domain *dom, u8 xwr)
+{
+ return __rv64_map_full_range(dom, xwr, RV64_INNER_ENTRIES);
+}
+
+static int smmpt52_map_full_range(struct sbi_mpt_domain *dom, u8 xwr)
+{
+ return __rv64_map_full_range(dom, xwr, RV64_INNER_ENTRIES);
+}
+
+static int smmpt64_map_full_range(struct sbi_mpt_domain *dom, u8 xwr)
+{
+ return __rv64_map_full_range(dom, xwr, RV64_ROOT_SMMPT64_ENTRIES);
+}
+
+struct sbi_mpt_mode smmpt43_mode = {
+ .name = "Smmpt43",
+ .mode_val = SBI_MMPT_MODE_SMMPT43,
+ .encode_mmpt = smmpt43_encode_mmpt,
+ .map_range = smmpt43_map_range,
+ .root_table_size = smmpt43_root_table_size,
+ .root_table_align = smmpt43_root_table_align,
+ .pa_in_range = smmpt43_pa_in_range,
+ .map_full_range = smmpt43_map_full_range,
+};
+
+struct sbi_mpt_mode smmpt52_mode = {
+ .name = "Smmpt52",
+ .mode_val = SBI_MMPT_MODE_SMMPT52,
+ .encode_mmpt = smmpt52_encode_mmpt,
+ .map_range = smmpt52_map_range,
+ .root_table_size = smmpt52_root_table_size,
+ .root_table_align = smmpt52_root_table_align,
+ .pa_in_range = smmpt52_pa_in_range,
+ .map_full_range = smmpt52_map_full_range,
+};
+
+struct sbi_mpt_mode smmpt64_mode = {
+ .name = "Smmpt64",
+ .mode_val = SBI_MMPT_MODE_SMMPT64,
+ .encode_mmpt = smmpt64_encode_mmpt,
+ .map_range = smmpt64_map_range,
+ .root_table_size = smmpt64_root_table_size,
+ .root_table_align = smmpt64_root_table_align,
+ .pa_in_range = smmpt64_pa_in_range,
+ .map_full_range = smmpt64_map_full_range,
+};
+
+#else /* __riscv_xlen == 64 */
+
+/*
+ * Smmpt34 (RV32) — 34-bit supervisor physical address (SPA)
+ *
+ * +---------+---------+---------+--------+
+ * | pn[1] | pn[0] | pi | offset |
+ * | [33:25] | [24:15] | [14:12] | [11:0] |
+ * | 9b | 10b | 3b | 12b |
+ * +---------+---------+---------+--------+
+ *
+ */
+
+#define RV32_MPTE_SIZE 4UL
+#define RV32_ROOT_ENTRIES 512U
+#define RV32_LEAF_ENTRIES 1024U
+#define RV32_ROOT_SIZE (RV32_ROOT_ENTRIES * RV32_MPTE_SIZE)
+#define RV32_LEAF_SIZE (RV32_LEAF_ENTRIES * RV32_MPTE_SIZE)
+
+/* pn field widths */
+#define RV32_PN1_BITS 9U
+#define RV32_PN0_BITS 10U
+
+#define RV32_NUMPGINRANGE 3U
+#define RV32_PAGES_PER_MPTE (1U << RV32_NUMPGINRANGE)
+#define RV32_PAGES_PER_MPTE_MASK (RV32_PAGES_PER_MPTE - 1)
+
+/* Range covered by one MPTE at each level */
+#define RV32_LEAF_RANGE (RV32_PAGES_PER_MPTE * SBI_MPT_PAGE_SIZE)
+#define RV32_ROOT_RANGE (RV32_LEAF_RANGE << RV32_PN0_BITS)
+
+/* NAPOT constants (G=6) */
+#define RV32_NAPOT_G 6U
+#define RV32_NAPOT_G_SHIFT 12U
+#define RV32_NAPOT_COUNT (1U << (RV32_NAPOT_G + 1))
+#define RV32_NAPOT_SIZE (RV32_NAPOT_COUNT * RV32_LEAF_RANGE)
+#define RV32_NAPOT_PN0_ALIGN RV32_NAPOT_COUNT
+
+/*
+ * Swith to disable NAPOT compaction of MPTE entries while writing.
+ */
+#define RV32_NAPOT_DISABLE 0
+
+/*
+ * rv32_pn1_table_idx() — extract pn[1] from a SPA.
+ */
+static inline u32 rv32_pn1_table_idx(unsigned long pa)
+{
+ return ((pa >> 25) & 0x1FFU);
+}
+
+/*
+ * rv32_pn0_table_idx() — extract pn[0] from a SPA.
+ */
+static inline u32 rv32_pn0_table_idx(unsigned long pa)
+{
+ return ((pa >> 15) & 0x3FFU);
+}
+
+static inline u32 rv32_tuple_idx_shift(u32 level)
+{
+ return (level == 0) ? SBI_MPT_PAGE_SHIFT : SBI_MPT_PAGE_SHIFT + RV32_PN0_BITS;
+}
+
+static inline u32 rv32_tuple_idx(unsigned long pa, u32 level)
+{
+ return ((pa >> rv32_tuple_idx_shift(level)) & RV32_PAGES_PER_MPTE_MASK);
+}
+
+/* Bytes covered by one MPTE at the given level */
+static inline unsigned long rv32_mpte_range(u32 level)
+{
+ return (level == 0) ? RV32_LEAF_RANGE : RV32_ROOT_RANGE;
+}
+
+/*
+ * RV32 MPTE Read/Write functions
+ */
+static inline u32 rv32_read_mpte(unsigned long pa)
+{
+ return *(volatile u32 *)pa;
+}
+
+static inline void rv32_write_mpte(unsigned long pa, u32 v)
+{
+ *(volatile u32 *)pa = v;
+}
+
+static inline unsigned long rv32_mpte_pa(unsigned long table_pa, u32 idx)
+{
+ return (table_pa + idx * RV32_MPTE_SIZE);
+}
+
+static inline unsigned long rv32_next_table_pa(u32 mpte)
+{
+ return ((mpte >> SBI_MPTE_PPN_SHIFT) << SBI_MPT_PAGE_SHIFT);
+}
+
+/*
+ * Generate Napot Leaf MPTE
+ *
+ * Constructs one MPTE value used for all RV32_NAPOT_COUNT entries
+ * in a NAPOT group.
+ *
+ * All MPTEs in the group are identical
+ */
+static inline u32 rv32_napot_leaf(u8 xwr)
+{
+ u32 mpte = SBI_MPTE_V | SBI_MPTE_L | SBI_MPTE_N;
+
+ mpte = sbi_mpte_leaf_set_xwr(mpte, 0, xwr);
+ mpte |= ((u32)RV32_NAPOT_G << RV32_NAPOT_G_SHIFT);
+
+ return mpte;
+}
+
+/* Get the xwr from a Napot leaf. */
+static inline u8 rv32_napot_xwr(u32 mpte)
+{
+ return ((mpte >> SBI_MPTE_XWR_BASE) & SBI_MPTE_XWR_MASK);
+}
+
+/*
+ * rv32_napot_demote() — Convert a Napot MPTE group into RV64_NAPOT_MPTE_COUNT
+ * normal(Non-Napot) MPTE carrying the SAME permission.
+ *
+ * leaf_mpte_pa is any of the group MPTE PA
+ */
+static void rv32_napot_demote(struct sbi_mpt_domain *dom,
+ unsigned long leaf_mpte_pa, unsigned long pa)
+{
+ u32 pg, leaf, i;
+ unsigned long grp_base;
+
+ u8 xwr = rv32_napot_xwr(rv32_read_mpte(leaf_mpte_pa));
+ u32 pn0 = rv32_pn0_table_idx(pa);
+
+ grp_base = leaf_mpte_pa - (pn0 & (RV32_NAPOT_COUNT - 1)) * RV32_MPTE_SIZE;
+
+ leaf = (SBI_MPTE_V | SBI_MPTE_L); /* N=0 */
+
+ for (pg = 0; pg < RV32_PAGES_PER_MPTE; pg++)
+ leaf = sbi_mpte_leaf_set_xwr(leaf, pg, xwr);
+
+ for (i = 0; i < RV32_NAPOT_COUNT; i++)
+ rv32_write_mpte(grp_base + i * RV32_MPTE_SIZE, leaf);
+}
+
+static inline u32 rv32_best_level(unsigned long pa, unsigned long size)
+{
+ if (size >= RV32_ROOT_RANGE && (pa & (RV32_ROOT_RANGE - 1)) == 0)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * MPT Walk for RV32 Smmpt43
+ *
+ * Returns the PA of the MPTE covering pa
+ */
+static unsigned long rv32_walk_alloc(struct sbi_mpt_domain *dom,
+ unsigned long pa,
+ unsigned long size,
+ u32 *out_level)
+{
+ unsigned long root_ep = rv32_mpte_pa(dom->root_pa, rv32_pn1_table_idx(pa));
+ u32 root_mpte = rv32_read_mpte(root_ep);
+ u32 best = rv32_best_level(pa, size);
+ unsigned long leaf_pa;
+
+ if (root_mpte & (u32)SBI_MPTE_L) {
+ /* Existing root-level leaf */
+ *out_level = 1;
+ return root_ep;
+ }
+
+ if (!(root_mpte & (u32)SBI_MPTE_V) && best == 1) {
+ /* new entry, range fits at root level so skip leaf allocation */
+ *out_level = 1;
+ return root_ep;
+ }
+
+ if (!(root_mpte & (u32)SBI_MPTE_V)) {
+ /* Allocate leaf table from global heap */
+ leaf_pa = sbi_mpt_pool_alloc(RV32_LEAF_SIZE,
+ SBI_MPT_PAGE_SIZE);
+ if (!leaf_pa)
+ return 0;
+
+ rv32_write_mpte(root_ep, sbi_mpte_nonleaf(leaf_pa));
+ *out_level = 0;
+
+ return rv32_mpte_pa(leaf_pa, rv32_pn0_table_idx(pa));
+ }
+
+ /* Valid non-leaf: follow PPN to existing leaf table */
+ *out_level = 0;
+
+ return rv32_mpte_pa(rv32_next_table_pa(rv32_read_mpte(root_ep)), rv32_pn0_table_idx(pa));
+}
+
+/*
+ * smmpt34_map_range(): Maps a range [pa, pa+size] in the MPT table
+ */
+
+static int smmpt34_map_range(struct sbi_mpt_domain *dom,
+ unsigned long pa, unsigned long size, u8 xwr)
+{
+ unsigned long cur = pa;
+ unsigned long end = pa + size;
+ unsigned long leaf_mpte_pa;
+ unsigned long mpte_range, mpte_base, mpte_end, batch_end;
+ u32 level, pg_first, pg_last, pg, i;
+ u32 mpte, napot;
+
+ while (cur < end) {
+ /*
+ * Check if the region qualifies for NAPOT range and the
+ * region is contained
+ */
+ if ((cur & (RV32_NAPOT_SIZE - 1)) == 0 &&
+ cur + RV32_NAPOT_SIZE > cur &&
+ cur + RV32_NAPOT_SIZE <= end) {
+ leaf_mpte_pa = rv32_walk_alloc(dom, cur, end - cur, &level);
+ if (!leaf_mpte_pa)
+ return SBI_ENOMEM;
+
+ if (!RV32_NAPOT_DISABLE && level == 0 && (rv32_pn0_table_idx(cur) & (RV32_NAPOT_PN0_ALIGN - 1)) == 0) {
+ napot = rv32_napot_leaf(xwr);
+
+ for (i = 0; i < RV32_NAPOT_COUNT; i++)
+ rv32_write_mpte(leaf_mpte_pa + i * RV32_MPTE_SIZE, napot);
+
+ cur += RV32_NAPOT_SIZE;
+
+ continue;
+ }
+ } else {
+ leaf_mpte_pa = rv32_walk_alloc(dom, cur, end - cur, &level);
+ if (!leaf_mpte_pa)
+ return SBI_ENOMEM;
+ }
+
+ /* Normal path */
+ mpte_range = rv32_mpte_range(level);
+ pg_first = rv32_tuple_idx(cur, level);
+ mpte_base = cur & ~(mpte_range - 1UL);
+ mpte_end = mpte_base + mpte_range;
+ batch_end = (end < mpte_end) ? end : mpte_end;
+ pg_last = rv32_tuple_idx(batch_end - SBI_MPT_PAGE_SIZE, level);
+
+ /*
+ * If this level-0 MPTE is a Napot member, expand the
+ * whole naturally-aligned group to plain leaves and write same
+ * permissions xwr
+ */
+ mpte = rv32_read_mpte(leaf_mpte_pa);
+ if (level == 0 && (mpte & (u32)SBI_MPTE_N)) {
+ rv32_napot_demote(dom, leaf_mpte_pa, cur);
+ sbi_mpt_fence_sdid(dom->sdid);
+ mpte = rv32_read_mpte(leaf_mpte_pa);
+ }
+ if (!(mpte & (u32)(SBI_MPTE_V | SBI_MPTE_L)))
+ mpte = SBI_MPTE_V | SBI_MPTE_L;
+
+ for (pg = pg_first; pg <= pg_last; pg++)
+ mpte = sbi_mpte_leaf_set_xwr(mpte, pg, xwr);
+
+ rv32_write_mpte(leaf_mpte_pa, mpte);
+ cur = batch_end;
+ }
+
+ return 0;
+}
+
+/*
+ * mmpt encoding
+ */
+static unsigned long smmpt34_encode_mmpt(unsigned long ppn, u32 sdid)
+{
+ return sbi_mmpt_encode(SBI_MMPT_MODE_SMMPT34, sdid, ppn);
+}
+
+static unsigned long smmpt34_root_table_size(void)
+{
+ return RV32_ROOT_SIZE;
+}
+
+static unsigned long smmpt34_root_table_align(void)
+{
+ return SBI_MPT_PAGE_SIZE;
+}
+
+static bool smmpt34_pa_in_range(unsigned long pa, unsigned long size)
+{
+ /*
+ * Return true since 1 << 34 wil be UB
+ * in RV32 case and anyways variables are limited by RV32
+ * architectural width
+ */
+
+ return true;
+}
+
+/*
+ * smmpt34_map_full_range() — Write every MPTE at Root MPT
+ *
+ * Writes xwr into every root table MPTE directly as a leaf
+ * superpage covering the mode entire addressable PA range.
+ * No intermidiate MPT tables allocated just all leaf MPTEs at
+ * root level.
+ */
+
+static int smmpt34_map_full_range(struct sbi_mpt_domain *dom, u8 xwr)
+{
+ u32 mpte = SBI_MPTE_V | SBI_MPTE_L;
+ u32 pg, i;
+
+ for (pg = 0; pg < RV32_PAGES_PER_MPTE; pg++)
+ mpte = sbi_mpte_leaf_set_xwr(mpte, pg, xwr);
+
+ for (i = 0; i < RV32_ROOT_ENTRIES; i++)
+ rv32_write_mpte(rv32_mpte_pa(dom->root_pa, i), mpte);
+
+ return 0;
+}
+
+struct sbi_mpt_mode smmpt34_mode = {
+ .name = "Smmpt34",
+ .mode_val = SBI_MMPT_MODE_SMMPT34,
+ .encode_mmpt = smmpt34_encode_mmpt,
+ .map_range = smmpt34_map_range,
+ .root_table_size = smmpt34_root_table_size,
+ .root_table_align = smmpt34_root_table_align,
+ .pa_in_range = smmpt34_pa_in_range,
+ .map_full_range = smmpt34_map_full_range,
+};
+
+#endif /* __riscv_xlen == 32 */
--
2.51.0
More information about the opensbi
mailing list