[RFC PATCH v2 2/5] mpt: Add Smsdid and Smmpt supervisor domain core
Pawandeep Oza
pawandeep.oza at oss.qualcomm.com
Fri Aug 14 11:28:31 PDT 2026
On Mon, Jul 27, 2026 at 6:56 AM Rahul Pathak
<rahul.pathak at oss.qualcomm.com> wrote:
>
> Introduce the supervisord omain memory-protection (Smmpt) table
Oza: typo: "supervisord omain"
> 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 | 334 ++++++++++++++
> lib/sbi/objects.mk | 2 +
> lib/sbi/sbi_mpt.c | 926 ++++++++++++++++++++++++++++++++++++++
> lib/sbi/sbi_mpt_mode.c | 997 +++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 2259 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..7d17bd80
> --- /dev/null
> +++ b/include/sbi/sbi_mpt.h
> @@ -0,0 +1,334 @@
> +/* 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 30UL
> +# define SBI_MMPT_MODE_MASK (UL(0x3) << SBI_MMPT_MODE_SHIFT)
> +# define SBI_MMPT_SDID_SHIFT 22UL
> +# 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 - 1UL);
> +
> + __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 - 1UL);
> +
> + __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 - 1UL);
> +
> + __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;
> +
> + /* Assemble the MMPT CSR value */
> + 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);
> +
> + /* map full range of address space which is supported by the mode */
> + int (*map_full_range)(struct sbi_mpt_domain *dom, u8 xwr);
> +
> + /* read the xwr for a PA */
> + u8 (*get_xwr)(struct sbi_mpt_domain *dom, unsigned long pa);
> +};
> +
> +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);
> +
> +int sbi_mpt_query_access(u32 sdid, unsigned long pa, u8 *out_xwr);
> +
> +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..306f82db
> --- /dev/null
> +++ b/lib/sbi/sbi_mpt.c
> @@ -0,0 +1,926 @@
> +/* 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 the mmpt CSR. */
> +static u32 probe_sdidlen(u32 mode_val)
> +{
> + unsigned long saved, sdid_val;
> + u32 sdidlen = 0;
> +
> + saved = csr_read(SBI_CSR_MMPT);
> +
> + /* Program a legal non-Bare MODE with all-ones in the SDID field */
> + csr_write(SBI_CSR_MMPT,
> + (((unsigned long)mode_val << SBI_MMPT_MODE_SHIFT) & SBI_MMPT_MODE_MASK) |
> + SBI_MMPT_SDID_MASK);
> +
> + sdid_val = (csr_read(SBI_CSR_MMPT) & SBI_MMPT_SDID_MASK) >> SBI_MMPT_SDID_SHIFT;
> +
> + while (sdid_val) {
> + sdidlen += 1;
> + sdid_val >>= 1;
> + }
> +
> + if (sdidlen > SBI_MPT_SDIDMAX)
> + sdidlen = SBI_MPT_SDIDMAX;
> +
> + csr_write(SBI_CSR_MMPT, saved);
> +
> + 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(u32 *out_mode_val)
> +{
> + unsigned long saved = csr_read(SBI_CSR_MMPT);
> + struct sbi_mpt_mode *s = NULL;
> + u32 mode_val = 0;
> +
> +#if __riscv_xlen == 64
> + if (probe_mode(SBI_MMPT_MODE_SMMPT64)) {
> + s = &smmpt64_mode;
> + mode_val = SBI_MMPT_MODE_SMMPT64;
> + } else if (probe_mode(SBI_MMPT_MODE_SMMPT52)) {
> + s = &smmpt52_mode;
> + mode_val = SBI_MMPT_MODE_SMMPT52;
> + } else if (probe_mode(SBI_MMPT_MODE_SMMPT43)) {
> + s = &smmpt43_mode;
> + mode_val = SBI_MMPT_MODE_SMMPT43;
> + }
> +#else
> + if (probe_mode(SBI_MMPT_MODE_SMMPT34)) {
> + s = &smmpt34_mode;
> + mode_val = SBI_MMPT_MODE_SMMPT34;
> + }
> +#endif
> +
> + csr_write(SBI_CSR_MMPT, saved);
> +
> + if (out_mode_val)
> + *out_mode_val = mode_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_domain_create(): Create a Supervisor Domain
Oza: "Create a Supervisor Domain" ? You probably meant "Create an mpt 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;
> +}
> +
> +/*
> + * sbi_mpt_query_access(): Returns the XWR for a PA in supervisor domain(sdid).
> + */
> +int sbi_mpt_query_access(u32 sdid, unsigned long pa, u8 *out_xwr)
> +{
> + struct sbi_mpt_domain *dom;
> +
> + if (!mpt_ctrl.ready)
> + return SBI_ENODEV;
> +
> + if (!out_xwr)
> + return SBI_EINVAL;
> +
> + dom = sbi_mpt_domain_get(sdid);
> + if (!dom)
> + return SBI_EINVAL;
> +
> + if (!dom->mode->pa_in_range(pa, SBI_MPT_PAGE_SIZE)) {
> + *out_xwr = SBI_MPT_PERM_NONE;
> + return 0;
> + }
> +
> + spin_lock(&dom->lock);
> + *out_xwr = dom->mode->get_xwr(dom, pa);
> + spin_unlock(&dom->lock);
> +
> + return 0;
> +}
> +
> +/*
> + * sbi_mpt_init(): Initialize MPT structures
> + */
> +int sbi_mpt_init(void)
> +{
> + u32 *p;
> + u32 mode_val;
> + 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.mode = init_mpt_mode(&mode_val);
> + if (!mpt_ctrl.mode) {
> + sbi_printf("sbi_mpt: WARL probe found no mode\n");
> + return SBI_ENODEV;
> + }
> +
> + /*
> + * SDIDLEN must be probed with a legal non-Bare MODE programmed,
> + * probe sdidlen must be done only after the probing the mode.
> + */
> + mpt_ctrl.sdid_len = probe_sdidlen(mode_val);
> + if (!mpt_ctrl.sdid_len) {
> + sbi_printf("sbi_mpt: SDIDLEN=0 — SDID field not implemented\n");
> + return SBI_ENODEV;
> + }
> +
> + mpt_ctrl.max_domains = 1UL << 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);
> +
> + /* 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;
> +}
> +
> +/*
> + * 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..8baf83a3
> --- /dev/null
> +++ b/lib/sbi/sbi_mpt_mode.c
> @@ -0,0 +1,997 @@
> +/* 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;
Oza: need to validate ?? or trust top_level her ?
if (!top_level) return 0;
> +
> + 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);
Oza: can we define 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;
> +}
> +
> +/*
> + * Walk a MPT table and return MPTE PA and its suitable level
> + */
> +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;
Oza: do you need to decrement lvl here since you are splitting ? e.g. lvl--
> + 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);
> +}
> +
> +/*
> + * Get the access permissions(xwr) of a PA
> + */
> +static u8 __rv64_get_xwr(struct sbi_mpt_domain *dom, unsigned long pa,
> + u32 top_level)
> +{
> + const struct sbi_mpt_mode *sch = dom->mode;
> + unsigned long table_pa = dom->root_pa;
> + u32 level, idx, pi;
> + u64 mpte;
> +
> + for (level = top_level; ; level--) {
> + idx = rv64_table_idx(pa, level, sch);
> + mpte = rv64_read_mpte(rv64_mpte_pa(table_pa, idx));
> +
> + if (!(mpte & SBI_MPTE_V))
> + return SBI_MPT_PERM_NONE;
> +
> + if (!(mpte & SBI_MPTE_L) && (mpte & SBI_MPTE_N))
> + return SBI_MPT_PERM_NONE;
> +
> + if (mpte & SBI_MPTE_L) {
> + if (mpte & SBI_MPTE_N)
> + return rv64_napot_leaf_xwr(mpte);
> +
> + pi = rv64_tuple_idx(pa, level);
> +
> + return (mpte >> sbi_mpte_xwr_shift(pi)) & SBI_MPTE_XWR_MASK;
> + }
> +
> + if (level == 0)
> + return SBI_MPT_PERM_NONE;
> +
> + table_pa = rv64_next_table_pa(mpte);
> + }
> +
> + return SBI_MPT_PERM_NONE;
> +}
> +
> +static u8 smmpt43_get_xwr(struct sbi_mpt_domain *dom, unsigned long pa)
> +{
> + return __rv64_get_xwr(dom, pa, 2);
> +}
> +
> +static u8 smmpt52_get_xwr(struct sbi_mpt_domain *dom, unsigned long pa)
> +{
> + return __rv64_get_xwr(dom, pa, 3);
> +}
> +
> +static u8 smmpt64_get_xwr(struct sbi_mpt_domain *dom, unsigned long pa)
> +{
> + return __rv64_get_xwr(dom, pa, 4);
> +}
> +
> +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,
> + .get_xwr = smmpt43_get_xwr,
> +};
> +
> +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,
> + .get_xwr = smmpt52_get_xwr,
> +};
> +
> +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,
> + .get_xwr = smmpt64_get_xwr,
> +};
> +
> +#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;
> +}
> +
> +/* Get the access permissions(xwr) of a PA */
> +static u8 smmpt34_get_xwr(struct sbi_mpt_domain *dom, unsigned long pa)
> +{
> + u32 mpte, pi;
> + unsigned long table_pa = dom->root_pa;
> +
> + mpte = rv32_read_mpte(rv32_mpte_pa(table_pa, rv32_pn1_table_idx(pa)));
> +
> + if (!(mpte & SBI_MPTE_V))
> + return SBI_MPT_PERM_NONE;
> +
> + if (!(mpte & SBI_MPTE_L) && (mpte & SBI_MPTE_N))
> + return SBI_MPT_PERM_NONE;
> +
> + if (mpte & SBI_MPTE_L) {
> + if (mpte & SBI_MPTE_N)
> + return rv32_napot_xwr(mpte);
> +
> + pi = rv32_tuple_idx(pa, 1);
> + return (mpte >> sbi_mpte_xwr_shift(pi)) & SBI_MPTE_XWR_MASK;
> + }
> +
> + table_pa = rv32_next_table_pa(mpte);
> + mpte = rv32_read_mpte(rv32_mpte_pa(table_pa, rv32_pn0_table_idx(pa)));
> +
> + if (!(mpte & SBI_MPTE_V) || !(mpte & SBI_MPTE_L))
> + return SBI_MPT_PERM_NONE;
> +
> + if (mpte & SBI_MPTE_N)
> + return rv32_napot_xwr(mpte);
> +
> + pi = rv32_tuple_idx(pa, 0);
> +
> + return ((mpte >> sbi_mpte_xwr_shift(pi)) & SBI_MPTE_XWR_MASK);
> +}
> +
> +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,
> + .get_xwr = smmpt34_get_xwr,
> +};
> +
> +#endif /* __riscv_xlen == 32 */
> --
> 2.51.0
>
>
> --
> opensbi mailing list
> opensbi at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/opensbi
Reviewed-by: Pawandeep Oza <pawandeep.oza at oss.qualcomm.com>
More information about the opensbi
mailing list