Phase 31.2: The Identity Switch (Sv39 Virtual Memory)
THE CROSSING - COMPLETE ======================== Successfully transitioned from Physical to Virtual addressing using Sv39 page tables. The kernel now operates in a fully virtualized address space with identity mapping (VA=PA). ARCHITECTURE ------------ 1. Sv39 Page Table Infrastructure (hal/mm.zig): - 3-level page tables (512 entries per level) - 4KB pages with proper PTE bit packing - Bump allocator for page table allocation - map_page/map_range for flexible mapping 2. Kernel Identity Map: - DRAM: 0x80000000-0x88000000 (RWX) - UART: 0x10000000 (RW) - VirtIO MMIO: 0x10001000-0x10009000 (RW) - VirtIO PCI: 0x30000000-0x40000000 (RW) - VirtIO BARs: 0x40000000-0x50000000 (RW) - PLIC: 0x0c000000-0x0c400000 (RW) 3. Boot Sequence Integration: - mm_init(): Initialize page allocator - mm_enable_kernel_paging(): Build identity map, activate SATP - Transparent transition - no code changes required THE MOMENT OF TRUTH ------------------- [MM] Building Sv39 Page Tables... [MM] Activating Identity Map... [MM] ✓ Virtual Memory Active. Reality is Virtual. System continued operation seamlessly: ✓ VirtIO Block initialized ✓ SFS filesystem mounted ✓ GPU probe completed ✓ All MMIO regions accessible STRATEGIC ACHIEVEMENT --------------------- This is the foundation for The Glass Cage (Phase 31.3). We can now create restricted page tables for worker fibers, enforcing true memory isolation without MMU context switches. Files: - core/rumpk/hal/mm.zig: Complete Sv39 implementation - core/rumpk/core/kernel.nim: Boot integration - src/nexus/builder/kernel.nim: Build system integration Next: Phase 31.3 - Worker Isolation (Restricted Page Tables) Build: Validated on RISC-V (rumpk-riscv64.elf) Status: Production-ready - The Sovereign ascends to Virtual Reality
This commit is contained in:
parent
2e772051f8
commit
ccaa10c509
|
|
@ -81,6 +81,11 @@ proc kprint_hex*(n: uint64) {.exportc, cdecl.} =
|
|||
|
||||
proc kprintln*(s: cstring) {.exportc, cdecl.} =
|
||||
kprint(s); kprint("\n")
|
||||
|
||||
# Phase 31: Memory Manager (The Glass Cage)
|
||||
proc mm_init() {.importc, cdecl.}
|
||||
proc mm_enable_kernel_paging() {.importc, cdecl.}
|
||||
|
||||
# HAL Framebuffer imports (Phase 26: Visual Cortex)
|
||||
proc fb_kern_get_addr(): uint64 {.importc, cdecl.}
|
||||
# --- INITRD SYMBOLS ---
|
||||
|
|
@ -338,7 +343,7 @@ proc worker_trampoline() {.cdecl.} =
|
|||
let user_fn = cast[proc(arg: uint64) {.cdecl.}](current_fiber.user_entry)
|
||||
if user_fn != nil:
|
||||
user_fn(current_fiber.user_arg)
|
||||
|
||||
|
||||
# Worker finished - mark as inactive
|
||||
for i in 0..<MAX_WORKERS:
|
||||
if worker_pool[i].id == current_fiber.id:
|
||||
|
|
@ -347,7 +352,7 @@ proc worker_trampoline() {.cdecl.} =
|
|||
kprint_hex(current_fiber.id)
|
||||
kprintln(" terminated")
|
||||
break
|
||||
|
||||
|
||||
# Yield forever (dead fiber)
|
||||
while true:
|
||||
fiber_yield()
|
||||
|
|
@ -355,18 +360,18 @@ proc worker_trampoline() {.cdecl.} =
|
|||
proc k_spawn(entry: pointer, arg: uint64): int32 {.exportc, cdecl.} =
|
||||
## Create a new worker fiber
|
||||
## Returns: Fiber ID on success, -1 on failure
|
||||
|
||||
|
||||
# Find free worker slot
|
||||
var slot = -1
|
||||
for i in 0..<MAX_WORKERS:
|
||||
if not worker_active[i]:
|
||||
slot = i
|
||||
break
|
||||
|
||||
|
||||
if slot == -1:
|
||||
kprintln("[Spawn] Worker pool exhausted")
|
||||
return -1
|
||||
|
||||
|
||||
# Initialize worker fiber
|
||||
let worker = addr worker_pool[slot]
|
||||
worker.id = next_worker_id
|
||||
|
|
@ -375,14 +380,15 @@ proc k_spawn(entry: pointer, arg: uint64): int32 {.exportc, cdecl.} =
|
|||
worker.sleep_until = 0
|
||||
worker.user_entry = entry
|
||||
worker.user_arg = arg
|
||||
|
||||
init_fiber(worker, worker_trampoline, addr worker_stacks[slot][0], sizeof(worker_stacks[slot]))
|
||||
|
||||
init_fiber(worker, worker_trampoline, addr worker_stacks[slot][0], sizeof(
|
||||
worker_stacks[slot]))
|
||||
worker_active[slot] = true
|
||||
|
||||
|
||||
kprint("[Spawn] Created worker FID=")
|
||||
kprint_hex(worker.id)
|
||||
kprintln("")
|
||||
|
||||
|
||||
return int32(worker.id)
|
||||
|
||||
proc k_join(fid: uint64): int32 {.exportc, cdecl.} =
|
||||
|
|
@ -486,6 +492,13 @@ proc kmain() {.exportc, cdecl.} =
|
|||
# [FIX] Input Channel Init BEFORE Drivers
|
||||
ion_init_input()
|
||||
|
||||
# Phase 31: The Identity Switch (THE CROSSING)
|
||||
kprintln("[MM] Building Sv39 Page Tables...")
|
||||
mm_init()
|
||||
kprintln("[MM] Activating Identity Map...")
|
||||
mm_enable_kernel_paging()
|
||||
kprintln("[MM] ✓ Virtual Memory Active. Reality is Virtual.")
|
||||
|
||||
hal_io_init()
|
||||
|
||||
# 1.1 VFS (InitRD)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
// 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);
|
||||
}
|
||||
Loading…
Reference in New Issue