phaser-ags / zig /kernel /memory.zig
SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
829b19c verified
Raw History Blame Contribute Delete
14.4 kB
//! SPDX-License-Identifier: AGPL-3.0-or-later
//! Copyright 2025 Ahmad Ali Parr / SnapKitty οΏ½ https://github.com/SNAPKITTYWEST/phaser-ags
//! ═══════════════════════════════════════════════════════════════════
//! PHASER AGS β€” Physical page allocator + SV32 virtual memory
//! (complete, no stubs)
//!
//! - Bitmap page allocator with alloc/free/allocRange
//! - SV32 two-level page tables with map/unmap/translate
//! - TLB management (sfence.vma)
//! - Per-process address space creation/teardown
//! ═══════════════════════════════════════════════════════════════════
const std = @import("std");
const driver = @import("driver.zig");
pub const PAGE_SIZE: u32 = 4096;
pub const PAGE_SHIFT: u5 = 12;
pub const PAGE_MASK: u32 = PAGE_SIZE - 1;
// ─── PTE bits ────────────────────────────────────────────────────
pub const PTE_V: u32 = 1 << 0;
pub const PTE_R: u32 = 1 << 1;
pub const PTE_W: u32 = 1 << 2;
pub const PTE_X: u32 = 1 << 3;
pub const PTE_U: u32 = 1 << 4;
pub const PTE_G: u32 = 1 << 5;
pub const PTE_A: u32 = 1 << 6;
pub const PTE_D: u32 = 1 << 7;
pub const PTE_RWX: u32 = PTE_R | PTE_W | PTE_X;
pub const PTE_RW: u32 = PTE_R | PTE_W;
pub const PTE_RX: u32 = PTE_R | PTE_X;
// ═══════════════════════════════════════════════════════════════════
// PAGE ALLOCATOR
// ═══════════════════════════════════════════════════════════════════
extern var __pages_start: u8;
extern var __pages_end: u8;
const PageAlloc = struct {
base: u32,
total_pages: u32,
free_count: u32,
bitmap: [*]u32,
bitmap_words: u32,
};
var palloc: PageAlloc = undefined;
pub fn init() void {
const base = @intFromPtr(&__pages_start);
const end = @intFromPtr(&__pages_end);
const total = (end - base) / PAGE_SIZE;
const bitmap_words = (total + 31) / 32;
palloc = .{
.base = base,
.total_pages = total,
.free_count = total,
.bitmap = @ptrFromInt(base),
.bitmap_words = bitmap_words,
};
// Zero bitmap (all pages free)
var i: u32 = 0;
while (i < bitmap_words) : (i += 1) {
palloc.bitmap[i] = 0;
}
// Reserve pages used by the bitmap itself
const bitmap_pages = (bitmap_words * 4 + PAGE_SIZE - 1) / PAGE_SIZE;
i = 0;
while (i < bitmap_pages) : (i += 1) {
markAllocated(i);
}
driver.Uart.puts("Page allocator: ");
driver.Uart.putHex(palloc.free_count * PAGE_SIZE);
driver.Uart.puts(" bytes free, ");
driver.Uart.putHex(palloc.total_pages);
driver.Uart.puts(" total pages\n");
}
inline fn markAllocated(idx: u32) void {
palloc.bitmap[idx / 32] |= @as(u32, 1) << @intCast(idx % 32);
palloc.free_count -= 1;
}
inline fn markFree(idx: u32) void {
palloc.bitmap[idx / 32] &= ~(@as(u32, 1) << @intCast(idx % 32));
palloc.free_count += 1;
}
inline fn isAllocated(idx: u32) bool {
return (palloc.bitmap[idx / 32] & (@as(u32, 1) << @intCast(idx % 32))) != 0;
}
pub fn allocPage() ?u32 {
var w: u32 = 0;
while (w < palloc.bitmap_words) : (w += 1) {
if (palloc.bitmap[w] == 0xFFFFFFFF) continue;
var bits = palloc.bitmap[w];
var bit: u5 = 0;
while (bit < 32) : (bit += 1) {
if (bits & 1 == 0) {
const idx = w * 32 + bit;
if (idx >= palloc.total_pages) return null;
markAllocated(idx);
const addr = palloc.base + idx * PAGE_SIZE;
zeroPage(addr);
return addr;
}
bits >>= 1;
}
}
driver.Uart.puts("OOM: no free pages\n");
return null;
}
pub fn allocRange(count: u32) ?u32 {
if (count == 0) return null;
if (count == 1) return allocPage();
var run_start: u32 = 0;
var run_len: u32 = 0;
var idx: u32 = 0;
while (idx < palloc.total_pages) : (idx += 1) {
if (!isAllocated(idx)) {
if (run_len == 0) run_start = idx;
run_len += 1;
if (run_len >= count) {
var i: u32 = 0;
while (i < count) : (i += 1) {
markAllocated(run_start + i);
}
const addr = palloc.base + run_start * PAGE_SIZE;
zeroRange(addr, count);
return addr;
}
} else {
run_len = 0;
}
}
driver.Uart.puts("OOM: no contiguous range of ");
driver.Uart.putHex(count);
driver.Uart.puts(" pages\n");
return null;
}
pub fn freePage(addr: u32) void {
if (addr < palloc.base) return;
const idx = (addr - palloc.base) / PAGE_SIZE;
if (idx >= palloc.total_pages) return;
if (!isAllocated(idx)) return;
markFree(idx);
}
pub fn freeRange(addr: u32, count: u32) void {
var i: u32 = 0;
while (i < count) : (i += 1) {
freePage(addr + i * PAGE_SIZE);
}
}
pub fn pageCount() u32 {
return palloc.total_pages;
}
pub fn freePageCount() u32 {
return palloc.free_count;
}
fn zeroPage(addr: u32) void {
const ptr: [*]u64 = @ptrFromInt(addr);
var i: u32 = 0;
while (i < PAGE_SIZE / 8) : (i += 1) {
ptr[i] = 0;
}
}
fn zeroRange(addr: u32, pages: u32) void {
var i: u32 = 0;
while (i < pages) : (i += 1) {
zeroPage(addr + i * PAGE_SIZE);
}
}
// ═══════════════════════════════════════════════════════════════════
// SV32 PAGE TABLES
// ═══════════════════════════════════════════════════════════════════
pub const PageTable = [1024]u32;
pub fn allocPageTable() ?*PageTable {
return @ptrFromInt(allocPage() orelse return null);
}
pub fn freePageTable(pt: *PageTable) void {
for (pt) |pte| {
if ((pte & PTE_V) != 0 and !isLeafPte(pte)) {
const l2_paddr = ptePpn(pte) << PAGE_SHIFT;
freePage(l2_paddr);
}
}
freePage(@intFromPtr(pt));
}
inline fn isLeafPte(pte: u32) bool {
return (pte & (PTE_R | PTE_W | PTE_X)) != 0;
}
inline fn ptePpn(pte: u32) u32 {
return (pte >> 10) & 0x3FFFFF;
}
inline fn makePte(ppn: u32, flags: u32) u32 {
return (ppn << 10) | flags;
}
// ─── Map a 4KB page ──────────────────────────────────────────────
pub fn mapPage(root: *PageTable, vaddr: u32, paddr: u32, flags: u32) bool {
const vpn1: u12 = @intCast((vaddr >> 22) & 0x3FF);
const vpn0: u10 = @intCast((vaddr >> 12) & 0x3FF);
var l1_pte = root[vpn1];
if (isLeafPte(l1_pte)) return false; // megapage conflict
var l2: *PageTable = undefined;
if ((l1_pte & PTE_V) == 0) {
l2 = allocPageTable() orelse return false;
root[vpn1] = makePte(@intFromPtr(l2) >> PAGE_SHIFT, PTE_V);
} else {
l2 = @ptrFromInt(ptePpn(l1_pte) << PAGE_SHIFT);
}
const ppn = paddr >> PAGE_SHIFT;
l2[vpn0] = makePte(ppn, flags | PTE_A | PTE_D);
flushTlbPage(vaddr);
return true;
}
// ─── Map a 4MB megapage ──────────────────────────────────────────
pub fn mapMegaPage(root: *PageTable, vaddr: u32, paddr: u32, flags: u32) bool {
const vpn1: u12 = @intCast((vaddr >> 22) & 0x3FF);
if ((vaddr & 0x3FFFFF) != 0 or (paddr & 0x3FFFFF) != 0) return false;
root[vpn1] = makePte(paddr >> PAGE_SHIFT, flags | PTE_A | PTE_D);
flushTlbPage(vaddr);
return true;
}
// ─── Unmap a 4KB page ────────────────────────────────────────────
pub fn unmapPage(root: *PageTable, vaddr: u32) ?u32 {
const vpn1: u12 = @intCast((vaddr >> 22) & 0x3FF);
const vpn0: u10 = @intCast((vaddr >> 12) & 0x3FF);
const l1_pte = root[vpn1];
if ((l1_pte & PTE_V) == 0) return null;
if (isLeafPte(l1_pte)) return null;
const l2: *PageTable = @ptrFromInt(ptePpn(l1_pte) << PAGE_SHIFT);
const l0_pte = l2[vpn0];
if ((l0_pte & PTE_V) == 0) return null;
l2[vpn0] = 0;
flushTlbPage(vaddr);
return ptePpn(l0_pte) << PAGE_SHIFT;
}
// ─── Unmap a 4MB megapage ────────────────────────────────────────
pub fn unmapMegaPage(root: *PageTable, vaddr: u32) ?u32 {
const vpn1: u12 = @intCast((vaddr >> 22) & 0x3FF);
const l1_pte = root[vpn1];
if ((l1_pte & PTE_V) == 0) return null;
if (!isLeafPte(l1_pte)) return null;
root[vpn1] = 0;
flushTlbPage(vaddr);
return ptePpn(l1_pte) << PAGE_SHIFT;
}
// ─── Translate virtual β†’ physical ────────────────────────────────
pub fn translate(root: *PageTable, vaddr: u32) ?u32 {
const vpn1: u12 = @intCast((vaddr >> 22) & 0x3FF);
const vpn0: u10 = @intCast((vaddr >> 12) & 0x3FF);
const offset = vaddr & PAGE_MASK;
const l1_pte = root[vpn1];
if ((l1_pte & PTE_V) == 0) return null;
if (isLeafPte(l1_pte)) {
return (ptePpn(l1_pte) << PAGE_SHIFT) | (vaddr & 0x3FFFFF);
}
const l2: *PageTable = @ptrFromInt(ptePpn(l1_pte) << PAGE_SHIFT);
const l0_pte = l2[vpn0];
if ((l0_pte & PTE_V) == 0) return null;
return ptePpn(l0_pte) << PAGE_SHIFT | offset;
}
// ─── Identity-map a range with 4KB pages ─────────────────────────
pub fn identityMap(root: *PageTable, start: u32, end: u32, flags: u32) void {
var addr = start & ~PAGE_MASK;
while (addr < end) : (addr += PAGE_SIZE) {
if (!mapPage(root, addr, addr, flags)) {
driver.Uart.puts("VM: identity map failed at ");
driver.Uart.putHex(addr);
driver.Uart.putc('\n');
return;
}
}
}
// ─── Map a range of virtual β†’ physical ───────────────────────────
pub fn mapRange(root: *PageTable, vaddr: u32, paddr: u32, pages: u32, flags: u32) void {
var i: u32 = 0;
while (i < pages) : (i += 1) {
if (!mapPage(root, vaddr + i * PAGE_SIZE, paddr + i * PAGE_SIZE, flags)) {
driver.Uart.puts("VM: mapRange failed at page ");
driver.Uart.putHex(i);
driver.Uart.putc('\n');
return;
}
}
}
// ─── Unmap a range and optionally free the physical pages ────────
pub fn unmapRange(root: *PageTable, vaddr: u32, pages: u32, free_phys: bool) void {
var i: u32 = 0;
while (i < pages) : (i += 1) {
const va = vaddr + i * PAGE_SIZE;
if (unmapPage(root, va)) |pa| {
if (free_phys) freePage(pa);
}
}
}
// ─── Create a fresh address space ────────────────────────────────
pub fn createAddressSpace() ?*PageTable {
const root = allocPageTable() orelse return null;
// Identity-map kernel code/data in SRAM
identityMap(root, 0x20000000, 0x20040000, PTE_RWX | PTE_G);
// CLINT
identityMap(root, 0x02000000, 0x02010000, PTE_RW | PTE_G);
// PLIC
identityMap(root, 0x0C000000, 0x10000000, PTE_RW | PTE_G);
// OMAP3530 peripherals
identityMap(root, 0x48000000, 0x4A000000, PTE_RW | PTE_G);
// DRAM (kernel heap, page tables)
identityMap(root, 0x30000000, 0x34000000, PTE_RWX | PTE_G);
return root;
}
// ─── Destroy an address space ────────────────────────────────────
pub fn destroyAddressSpace(root: *PageTable) void {
for (root, 0..) |pte, vpn1| {
if ((pte & PTE_V) == 0) continue;
if (!isLeafPte(pte)) {
const l2: *PageTable = @ptrFromInt(ptePpn(pte) << PAGE_SHIFT);
for (l2) |l0_pte| {
if ((l0_pte & PTE_V) != 0 and (l0_pte & PTE_U) != 0) {
freePage(ptePpn(l0_pte) << PAGE_SHIFT);
}
}
freePage(ptePpn(pte) << PAGE_SHIFT);
} else if ((pte & PTE_U) != 0) {
freePage(ptePpn(pte) << PAGE_SHIFT);
}
root[@intCast(vpn1)] = 0;
}
freePage(@intFromPtr(root));
}
// ─── TLB management ──────────────────────────────────────────────
pub fn flushTlbAll() void {
asm volatile ("sfence.vma zero, zero");
}
pub fn flushTlbPage(vaddr: u32) void {
asm volatile ("sfence.vma %[addr], zero"
:
: [addr] "r" (vaddr),
);
}
pub fn flushTlbAsid(asid: u32) void {
asm volatile ("sfence.vma zero, %[asid]"
:
: [asid] "r" (asid),
);
}
// ─── Switch to a page table ──────────────────────────────────────
pub fn switchToPageTable(root: *PageTable) void {
const satp: u32 = (1 << 31) | (@intFromPtr(root) >> PAGE_SHIFT);
asm volatile ("sfence.vma zero, zero");
asm volatile ("csrw satp, %[val]" :: [val] "r" (satp));
asm volatile ("sfence.vma zero, zero");
}
pub fn disableMmu() void {
asm volatile ("csrw satp, zero");
flushTlbAll();
}
pub fn getCurrentRoot() ?*PageTable {
const satp = asm volatile ("csrr %[out], satp"
: [out] "=r" (-> u32),
);
if ((satp >> 31) == 0) return null;
return @ptrFromInt((satp & 0x3FFFFF) << PAGE_SHIFT);
}