rumpk/hal/mm.zig

234 lines
6.7 KiB
Zig

// Phase 31: The Glass Cage - Sv39 Virtual Memory Isolation
// Memory Manager: Page Table Infrastructure
const std = @import("std");
// Sv39 Constants
pub const PAGE_SIZE: usize = 4096;
pub const PAGE_SHIFT: u6 = 12;
pub const PTE_PER_PAGE: usize = 512;
pub const LEVELS: u8 = 3;
// Physical memory layout (RISC-V QEMU virt)
pub const DRAM_BASE: u64 = 0x80000000;
pub const DRAM_SIZE: u64 = 128 * 1024 * 1024; // 128MB default
// MMIO regions
pub const UART_BASE: u64 = 0x10000000;
pub const VIRTIO_BASE: u64 = 0x10001000;
pub const PLIC_BASE: u64 = 0x0c000000;
// PTE Flags
pub const PTE_V: u64 = 1 << 0; // Valid
pub const PTE_R: u64 = 1 << 1; // Read
pub const PTE_W: u64 = 1 << 2; // Write
pub const PTE_X: u64 = 1 << 3; // Execute
pub const PTE_U: u64 = 1 << 4; // User
pub const PTE_G: u64 = 1 << 5; // Global
pub const PTE_A: u64 = 1 << 6; // Accessed
pub const PTE_D: u64 = 1 << 7; // Dirty
// Page Table Entry
pub const PageTableEntry = packed struct {
flags: u10,
ppn0: u9,
ppn1: u9,
ppn2: u26,
reserved: u10,
pub fn init(pa: u64, flags: u64) PageTableEntry {
const ppn = pa >> PAGE_SHIFT;
return PageTableEntry{
.flags = @truncate(flags),
.ppn0 = @truncate(ppn & 0x1FF),
.ppn1 = @truncate((ppn >> 9) & 0x1FF),
.ppn2 = @truncate((ppn >> 18) & 0x3FFFFFF),
.reserved = 0,
};
}
pub fn to_u64(self: PageTableEntry) u64 {
return @bitCast(self);
}
pub fn get_pa(self: PageTableEntry) u64 {
const ppn: u64 = (@as(u64, self.ppn2) << 18) |
(@as(u64, self.ppn1) << 9) |
@as(u64, self.ppn0);
return ppn << PAGE_SHIFT;
}
pub fn is_valid(self: PageTableEntry) bool {
return (self.flags & PTE_V) != 0;
}
pub fn is_leaf(self: PageTableEntry) bool {
return (self.flags & (PTE_R | PTE_W | PTE_X)) != 0;
}
};
// Page Table (512 entries)
pub const PageTable = struct {
entries: [PTE_PER_PAGE]PageTableEntry align(PAGE_SIZE),
pub fn init() PageTable {
return PageTable{
.entries = [_]PageTableEntry{PageTableEntry.init(0, 0)} ** PTE_PER_PAGE,
};
}
pub fn get_entry(self: *PageTable, index: usize) *PageTableEntry {
return &self.entries[index];
}
};
// Simple bump allocator for page tables
var page_alloc_base: u64 = 0;
var page_alloc_offset: u64 = 0;
pub fn init_page_allocator(base: u64, size: u64) void {
_ = size; // Reserved for bounds checking
page_alloc_base = base;
page_alloc_offset = 0;
}
pub fn alloc_page_table() ?*PageTable {
if (page_alloc_offset + PAGE_SIZE > DRAM_SIZE) {
return null;
}
const addr = page_alloc_base + page_alloc_offset;
page_alloc_offset += PAGE_SIZE;
const pt: *PageTable = @ptrFromInt(addr);
pt.* = PageTable.init();
return pt;
}
// Extract VPN from virtual address
pub fn vpn(va: u64, level: u8) usize {
const shift: u6 = @intCast(PAGE_SHIFT + (9 * level));
return @truncate((va >> shift) & 0x1FF);
}
// Map a single page
pub fn map_page(root: *PageTable, va: u64, pa: u64, flags: u64) !void {
var pt = root;
var level: u8 = 2;
while (level > 0) : (level -= 1) {
const idx = vpn(va, level);
var pte = pt.get_entry(idx);
if (!pte.is_valid()) {
// Allocate intermediate page table
const new_pt = alloc_page_table() orelse return error.OutOfMemory;
pte.* = PageTableEntry.init(@intFromPtr(new_pt), PTE_V);
}
if (pte.is_leaf()) {
return error.AlreadyMapped;
}
pt = @ptrFromInt(pte.get_pa());
}
// Map leaf entry
const idx = vpn(va, 0);
var pte = pt.get_entry(idx);
if (pte.is_valid()) {
return error.AlreadyMapped;
}
pte.* = PageTableEntry.init(pa, flags | PTE_V);
}
// Map a range of pages (identity or custom)
pub fn map_range(root: *PageTable, va_start: u64, pa_start: u64, size: u64, flags: u64) !void {
var offset: u64 = 0;
while (offset < size) : (offset += PAGE_SIZE) {
try map_page(root, va_start + offset, pa_start + offset, flags);
}
}
// Create kernel identity map
pub fn create_kernel_identity_map() !*PageTable {
const root = alloc_page_table() orelse return error.OutOfMemory;
// Map DRAM (identity: VA = PA)
try map_range(root, DRAM_BASE, DRAM_BASE, DRAM_SIZE, PTE_R | PTE_W | PTE_X);
// Map UART (MMIO)
try map_range(root, UART_BASE, UART_BASE, PAGE_SIZE, PTE_R | PTE_W);
// Map VirtIO (MMIO) - Expanded to cover all devices
try map_range(root, 0x10001000, 0x10001000, 0x8000, PTE_R | PTE_W);
// Map VirtIO PCI (MMIO) - CRITICAL for PCI probe
try map_range(root, 0x30000000, 0x30000000, 0x10000000, PTE_R | PTE_W);
// Map VirtIO BAR region (dynamic PCI BAR assignments)
try map_range(root, 0x40000000, 0x40000000, 0x10000000, PTE_R | PTE_W);
// Map PLIC (MMIO)
try map_range(root, PLIC_BASE, PLIC_BASE, 0x400000, PTE_R | PTE_W);
return root;
}
// Create restricted worker map
pub fn create_worker_map(stack_base: u64, stack_size: u64, packet_addr: u64) !*PageTable {
const root = alloc_page_table() orelse return error.OutOfMemory;
// Map kernel code (RX) - identity map for simplicity
// TODO: Split into proper RX/RW regions
try map_range(root, DRAM_BASE, DRAM_BASE, 16 * 1024 * 1024, PTE_R | PTE_X);
// Map worker stack (RW)
try map_range(root, stack_base, stack_base, stack_size, PTE_R | PTE_W);
// Map shared packet (RW)
const packet_page = packet_addr & ~@as(u64, PAGE_SIZE - 1);
try map_range(root, packet_page, packet_page, PAGE_SIZE, PTE_R | PTE_W);
// Map UART for debugging (RW)
try map_range(root, UART_BASE, UART_BASE, PAGE_SIZE, PTE_R | PTE_W);
return root;
}
// Convert page table to SATP value
pub fn make_satp(root: *PageTable) u64 {
const ppn = @intFromPtr(root) >> PAGE_SHIFT;
const mode: u64 = 8; // Sv39
return (mode << 60) | ppn;
}
// Activate page table
pub fn activate_pagetable(satp_val: u64) void {
asm volatile (
\\csrw satp, %[satp]
\\sfence.vma zero, zero
:
: [satp] "r" (satp_val),
);
}
// Export for kernel
pub export fn mm_init() callconv(.c) void {
// Initialize page allocator at end of kernel
// Kernel ends at ~16MB, allocate page tables after
const pt_base = DRAM_BASE + (16 * 1024 * 1024);
init_page_allocator(pt_base, 8 * 1024 * 1024); // 8MB for page tables
}
pub export fn mm_enable_kernel_paging() callconv(.c) void {
const root = create_kernel_identity_map() catch {
// Can't use console here, just halt
while (true) {}
};
const satp_val = make_satp(root);
activate_pagetable(satp_val);
}