Re: [RFC PATCH v2 2/5] mpt: Add Smsdid and Smmpt supervisor domain core
Pawandeep Oza <[email protected]>
| Newsgroups | org.infradead.lists.opensbi |
|---|---|
| Message-ID | <CAAVgOk+MtstZjfP9+_xtQc9RAhuC1M7Tay9RfUQFkPWsNhRQWw@mail.gmail.com> |
On Mon, Jul 27, 2026 at 6:56 AM Rahul Pathak <[email protected]> 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 <[email protected]> > --- > 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 <[email protected]> > + */ > + > +#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 <[email protected]> > + */ > + > +#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 <[email protected]> > + */ > + > +#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 > [email protected] > http://lists.infradead.org/mailman/listinfo/opensbi Reviewed-by: Pawandeep Oza <[email protected]> -- opensbi mailing list [email protected] http://lists.infradead.org/mailman/listinfo/opensbi