// SafeC Standard Library — MMU Context (real hardware page tables)
// Virtual-to-physical address translation for freestanding targets.
// Freestanding-safe; TLB/activate operations use inline asm inside unsafe{}.
//
// MmuContext<F> builds the page tables the CPU itself walks. F is a
// compile-time page-table format type — every table walk step is a direct,
// inlinable call to F's methods, the same pattern as BST<K, V, C>'s
// ordering. One format per architecture:
//
// MmuX86_64 x86-64 4-level paging (48-bit VA; PML4/PDPT/PD/PT, 8-byte entries)
// MmuX86_32 x86 32-bit paging without PAE (2 levels, 4-byte entries)
// MmuSv39 RISC-V Sv39 (RV64, 3 levels, 8-byte PTEs)
// MmuSv32 RISC-V Sv32 (RV32, 2 levels, 4-byte PTEs)
// MmuAArch64 AArch64 stage 1, 4 KiB granule, 48-bit VA (4 levels)
// MmuArmV7 ARMv7-A short-descriptor (4096-entry L1 + 256-entry L2)
//
// MmuNative names the format of the CPU being compiled for (none on
// Cortex-M, which has an MPU but no MMU, or on wasm). All formats can be
// built and walked on any target — e.g. on the host, for tests or for an
// image builder — but activate()/tlb_flush_*() only touch the CPU when
// the format matches the running bare-metal target (they return 0
// otherwise, and always in a hosted process).
//
// Mappings are 4 KiB pages. walk() also follows larger (block/superpage)
// leaves built by someone else.
//
// Tables are read and written at (physical address + phys_offset): 0 for
// an identity-mapped kernel, the direct-map base for a higher-half one.
// New intermediate tables come from 'frames' (one 4 KiB frame each,
// zeroed); the root table is the caller's.
#pragma once
#include <std/kernel/frame.h>
// Portable mapping flags (map()'s 'flags'). Every mapping is readable.
#define MMU_WRITE 1u // writable
#define MMU_EXEC 2u // executable
#define MMU_USER 4u // reachable from user mode (x86 U/S, RISC-V U, ARM EL0/PL0)
#define MMU_DEVICE 8u // uncached device memory (x86 PCD|PWT, ARM device
// attributes; RISC-V: no effect — PMAs decide)
namespace std {
// ── Formats ──────────────────────────────────────────────────────────────────
// Each format has the same methods (unused_ only gives the struct a size):
// levels() number of table levels
// shift(l) VA bit where level l's index starts (l = 0 is the root)
// entries(l) entries in a level-l table
// esize() bytes per entry (4 or 8)
// table_desc(p) entry pointing at the next-level table at physical p
// leaf_desc(p, f) 4 KiB page entry for physical p with MMU_* flags f
// valid(e) entry present
// is_leaf(e, l) valid entry that maps memory (not a table pointer)
// addr(e, l) physical address an entry points at
// load_root(root) install root on this CPU (CR3/satp/TTBR0/TTBR0)
// flush_all()/flush_page(v) TLB maintenance
// load_root/flush_* return 1 if they ran, 0 if this isn't the running CPU.
// MmuFormat names that set; MmuContext<F> requires it of F.
trait MmuFormat {
int levels() const;
int shift(int level) const;
unsigned long entries(int level) const;
unsigned long esize() const;
unsigned long long table_desc(unsigned long phys) const;
unsigned long long leaf_desc(unsigned long phys, unsigned int flags) const;
int valid(unsigned long long e) const;
int is_leaf(unsigned long long e, int level) const;
unsigned long addr(unsigned long long e, int level) const;
int load_root(unsigned long root) const;
int flush_all() const;
int flush_page(unsigned long virt) const;
}
// Non-leaf entries carry P|RW|US so the leaf alone decides access; non-exec
// pages set NX (bit 63), which needs EFER.NXE enabled.
struct MmuX86_64 {
int unused_;
int levels() const;
int shift(int level) const;
unsigned long entries(int level) const;
unsigned long esize() const;
unsigned long long table_desc(unsigned long phys) const;
unsigned long long leaf_desc(unsigned long phys, unsigned int flags) const;
int valid(unsigned long long e) const;
int is_leaf(unsigned long long e, int level) const;
unsigned long addr(unsigned long long e, int level) const;
int load_root(unsigned long root) const;
int flush_all() const;
int flush_page(unsigned long virt) const;
};
// No NX without PAE: MMU_EXEC is implied.
struct MmuX86_32 {
int unused_;
int levels() const;
int shift(int level) const;
unsigned long entries(int level) const;
unsigned long esize() const;
unsigned long long table_desc(unsigned long phys) const;
unsigned long long leaf_desc(unsigned long phys, unsigned int flags) const;
int valid(unsigned long long e) const;
int is_leaf(unsigned long long e, int level) const;
unsigned long addr(unsigned long long e, int level) const;
int load_root(unsigned long root) const;
int flush_all() const;
int flush_page(unsigned long virt) const;
};
// Leaves set A and D up front (no faults on hardware without A/D updates).
struct MmuSv39 {
int unused_;
int levels() const;
int shift(int level) const;
unsigned long entries(int level) const;
unsigned long esize() const;
unsigned long long table_desc(unsigned long phys) const;
unsigned long long leaf_desc(unsigned long phys, unsigned int flags) const;
int valid(unsigned long long e) const;
int is_leaf(unsigned long long e, int level) const;
unsigned long addr(unsigned long long e, int level) const;
int load_root(unsigned long root) const;
int flush_all() const;
int flush_page(unsigned long virt) const;
};
struct MmuSv32 {
int unused_;
int levels() const;
int shift(int level) const;
unsigned long entries(int level) const;
unsigned long esize() const;
unsigned long long table_desc(unsigned long phys) const;
unsigned long long leaf_desc(unsigned long phys, unsigned int flags) const;
int valid(unsigned long long e) const;
int is_leaf(unsigned long long e, int level) const;
unsigned long addr(unsigned long long e, int level) const;
int load_root(unsigned long root) const;
int flush_all() const;
int flush_page(unsigned long virt) const;
};
// Leaves use MAIR attribute index 0 for normal memory and 1 for device
// memory (program MAIR_EL1 with Attr0 = 0xFF, Attr1 = 0x04), inner-shareable,
// AF set. TCR_EL1 must select a 4 KiB granule and T0SZ = 16.
struct MmuAArch64 {
int unused_;
int levels() const;
int shift(int level) const;
unsigned long entries(int level) const;
unsigned long esize() const;
unsigned long long table_desc(unsigned long phys) const;
unsigned long long leaf_desc(unsigned long phys, unsigned int flags) const;
int valid(unsigned long long e) const;
int is_leaf(unsigned long long e, int level) const;
unsigned long addr(unsigned long long e, int level) const;
int load_root(unsigned long root) const;
int flush_all() const;
int flush_page(unsigned long virt) const;
};
// Small pages; the root L1 table is 16 KiB and must be 16 KiB aligned.
// Normal memory is TEX=001 C=1 B=1 (write-back), device is TEX=000 C=0 B=1.
struct MmuArmV7 {
int unused_;
int levels() const;
int shift(int level) const;
unsigned long entries(int level) const;
unsigned long esize() const;
unsigned long long table_desc(unsigned long phys) const;
unsigned long long leaf_desc(unsigned long phys, unsigned int flags) const;
int valid(unsigned long long e) const;
int is_leaf(unsigned long long e, int level) const;
unsigned long addr(unsigned long long e, int level) const;
int load_root(unsigned long root) const;
int flush_all() const;
int flush_page(unsigned long virt) const;
};
#if defined(__x86_64__)
typedef struct MmuX86_64 MmuNative;
#elif defined(__i386__)
typedef struct MmuX86_32 MmuNative;
#elif defined(__riscv) && __riscv_xlen == 64
typedef struct MmuSv39 MmuNative;
#elif defined(__riscv)
typedef struct MmuSv32 MmuNative;
#elif defined(__aarch64__)
typedef struct MmuAArch64 MmuNative;
#elif defined(__arm__) && defined(__ARM_ARCH_PROFILE) && __ARM_ARCH_PROFILE == 'A'
typedef struct MmuArmV7 MmuNative;
#endif
// ── Context ──────────────────────────────────────────────────────────────────
generic<F: MmuFormat>
struct MmuContext {
unsigned long root; // physical address of the root table
unsigned long phys_offset; // tables live at phys + phys_offset
&static FrameAllocator frames; // source of new intermediate tables
F fmt;
// A context over an existing root table at physical 'root' — zeroed, and
// aligned as the format needs (4 KiB; 16 KiB for MmuArmV7). 'frames' must be
// a live, &static-lifetime FrameAllocator (the kernel's own, usually);
// frame f's table memory is at frames.frame_addr(f) + phys_offset.
// F comes from the target type:
// struct MmuContext<MmuNative> mmu = std::MmuContext::new(root, &g_frames, 0UL);
static struct MmuContext<F> new(unsigned long root, &static FrameAllocator frames,
unsigned long phys_offset);
// Map the 4 KiB page at 'virt' to 'phys' (both 4 KiB aligned; low bits
// ignored) with MMU_* flags, allocating intermediate tables as needed.
// Returns 1, or 0 if a table frame couldn't be allocated or a larger
// leaf already covers 'virt'. Remapping a page overwrites it; flush
// the TLB entry afterwards if the context is live.
int map(unsigned long virt, unsigned long phys, unsigned int flags);
// Remove the page at 'virt' (no-op if unmapped). Tables aren't freed.
void unmap(unsigned long virt);
// Resolve 'virt': 1 and *phys_out on success, 0 if unmapped.
int walk(unsigned long virt, &unsigned long phys_out) const;
// The raw leaf entry mapping 'virt': 1 and *entry_out, or 0 if unmapped.
int entry(unsigned long virt, &unsigned long long entry_out) const;
// TLB maintenance and switching to this address space: x86 reloads CR3 /
// invlpg; RISC-V writes satp (which also turns translation on for S/U
// mode) / sfence.vma; AArch64 writes TTBR0_EL1 / TLBI; ARMv7 writes
// TTBR0 / TLBIALL. Turning paging on (CR0.PG, SCTLR.M) and the MAIR/TCR
// setup are the boot code's. Each returns 1 if it ran, 0 if F isn't the
// running CPU's format (or in a hosted process).
int tlb_flush_all();
int tlb_flush_page(unsigned long virt);
int activate();
// Internal: pointer to the table at physical address 'phys'.
unsigned long table_(unsigned long phys) const;
unsigned long long read_(unsigned long table, unsigned long idx) const;
void write_(unsigned long table, unsigned long idx, unsigned long long e);
unsigned long index_(unsigned long virt, int level) const;
};
generic<F: MmuFormat>
struct MmuContext<F> MmuContext::new(unsigned long root, &static FrameAllocator frames,
unsigned long phys_offset) {
struct MmuContext<F> m;
m.root = root;
m.phys_offset = phys_offset;
m.frames = frames;
return m;
}
generic<F: MmuFormat>
unsigned long MmuContext::table_(unsigned long phys) const {
return phys + self.phys_offset;
}
generic<F: MmuFormat>
unsigned long long MmuContext::read_(unsigned long table, unsigned long idx) const {
unsafe {
if (self.fmt.esize() == 8UL) { return ((unsigned long long*)table)[idx]; }
return (unsigned long long)((unsigned int*)table)[idx];
}
}
generic<F: MmuFormat>
void MmuContext::write_(unsigned long table, unsigned long idx, unsigned long long e) {
unsafe {
if (self.fmt.esize() == 8UL) { ((unsigned long long*)table)[idx] = e; }
else { ((unsigned int*)table)[idx] = (unsigned int)e; }
}
}
generic<F: MmuFormat>
unsigned long MmuContext::index_(unsigned long virt, int level) const {
unsigned long sh = (unsigned long)self.fmt.shift(level);
if (sh >= 8UL * sizeof(unsigned long)) { return 0UL; } // 64-bit format on a 32-bit build
return (virt >> sh) & (self.fmt.entries(level) - 1UL);
}
generic<F: MmuFormat>
int MmuContext::map(unsigned long virt, unsigned long phys, unsigned int flags) {
unsigned long table = self.table_(self.root);
int last = self.fmt.levels() - 1;
int l = 0;
while (l < last) {
unsigned long idx = self.index_(virt, l);
unsigned long long e = self.read_(table, idx);
if (!self.fmt.valid(e)) {
long long frame = self.frames.alloc();
if (frame < 0LL) { return 0; }
unsigned long next = self.frames.frame_addr((unsigned long)frame);
unsafe {
unsigned char* t = (unsigned char*)self.table_(next);
unsigned long k = 0UL;
while (k < 4096UL) { t[k] = (unsigned char)0; k = k + 1UL; }
}
e = self.fmt.table_desc(next);
self.write_(table, idx, e);
} else if (self.fmt.is_leaf(e, l)) {
return 0; // a larger page already maps this range
}
table = self.table_(self.fmt.addr(e, l));
l = l + 1;
}
self.write_(table, self.index_(virt, last), self.fmt.leaf_desc(phys & ~4095UL, flags));
return 1;
}
generic<F: MmuFormat>
void MmuContext::unmap(unsigned long virt) {
unsigned long table = self.table_(self.root);
int last = self.fmt.levels() - 1;
int l = 0;
while (l < last) {
unsigned long long e = self.read_(table, self.index_(virt, l));
if (!self.fmt.valid(e) || self.fmt.is_leaf(e, l)) { return; }
table = self.table_(self.fmt.addr(e, l));
l = l + 1;
}
self.write_(table, self.index_(virt, last), 0ULL);
}
generic<F: MmuFormat>
int MmuContext::entry(unsigned long virt, &unsigned long long entry_out) const {
unsigned long table = self.table_(self.root);
int last = self.fmt.levels() - 1;
int l = 0;
while (1) {
unsigned long long e = self.read_(table, self.index_(virt, l));
if (!self.fmt.valid(e)) { return 0; }
if (l == last || self.fmt.is_leaf(e, l)) {
*entry_out = e;
return l + 1; // 1 + the level the leaf is at
}
table = self.table_(self.fmt.addr(e, l));
l = l + 1;
}
return 0;
}
generic<F: MmuFormat>
int MmuContext::walk(unsigned long virt, &unsigned long phys_out) const {
unsigned long table = self.table_(self.root);
int last = self.fmt.levels() - 1;
int l = 0;
while (1) {
unsigned long long e = self.read_(table, self.index_(virt, l));
if (!self.fmt.valid(e)) { return 0; }
if (l == last || self.fmt.is_leaf(e, l)) {
unsigned long sh = (unsigned long)self.fmt.shift(l); // page or block size
unsigned long low = sh >= 8UL * sizeof(unsigned long) ? ~0UL : (1UL << sh) - 1UL;
*phys_out = self.fmt.addr(e, l) + (virt & low);
return 1;
}
table = self.table_(self.fmt.addr(e, l));
l = l + 1;
}
return 0;
}
generic<F: MmuFormat>
int MmuContext::tlb_flush_all() { return self.fmt.flush_all(); }
generic<F: MmuFormat>
int MmuContext::tlb_flush_page(unsigned long virt) { return self.fmt.flush_page(virt); }
generic<F: MmuFormat>
int MmuContext::activate() { return self.fmt.load_root(self.root); }
} // namespace std