From d9de8284206e2b82eb3fc3e5d6d17db30059e7d9 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 01:48:00 +0200 Subject: [PATCH] feat(adapter-riscv): add Sv39 page tables with identity mapping and MMU activation --- crates/adapter-riscv/src/main.rs | 8 ++ crates/adapter-riscv/src/page_table.rs | 169 +++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 crates/adapter-riscv/src/page_table.rs diff --git a/crates/adapter-riscv/src/main.rs b/crates/adapter-riscv/src/main.rs index a672161..04b9d62 100644 --- a/crates/adapter-riscv/src/main.rs +++ b/crates/adapter-riscv/src/main.rs @@ -1,6 +1,7 @@ #![no_std] #![no_main] +mod page_table; mod uart; use core::arch::global_asm; @@ -22,6 +23,7 @@ spin: unsafe extern "C" { static _bss_start: u8; static _bss_end: u8; + static _heap_start: u8; } fn zero_bss() { @@ -40,6 +42,12 @@ extern "C" fn kmain() -> ! { let uart = Uart::new(); uart.write_str("k-os: booted on RISC-V\n"); + let heap_start = unsafe { &_heap_start as *const u8 as usize }; + page_table::init_page_allocator(heap_start); + let kernel_pt = page_table::init_kernel_page_table(); + page_table::activate(kernel_pt); + uart.write_str("k-os: MMU enabled (Sv39)\n"); + loop { unsafe { core::arch::asm!("wfi") }; } diff --git a/crates/adapter-riscv/src/page_table.rs b/crates/adapter-riscv/src/page_table.rs new file mode 100644 index 0000000..b48bf97 --- /dev/null +++ b/crates/adapter-riscv/src/page_table.rs @@ -0,0 +1,169 @@ +pub const PAGE_SIZE: usize = 4096; +const PTE_V: u64 = 1 << 0; +const PTE_R: u64 = 1 << 1; +const PTE_W: u64 = 1 << 2; +const PTE_X: u64 = 1 << 3; +const PTE_U: u64 = 1 << 4; +const PTE_G: u64 = 1 << 5; +const PTE_A: u64 = 1 << 6; +const PTE_D: u64 = 1 << 7; + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct PageTableEntry(u64); + +impl PageTableEntry { + pub fn empty() -> Self { + Self(0) + } + + pub fn new_leaf(ppn: u64, read: bool, write: bool, exec: bool, user: bool, global: bool) -> Self { + let mut flags = PTE_V | PTE_A | PTE_D; + if read { flags |= PTE_R; } + if write { flags |= PTE_W; } + if exec { flags |= PTE_X; } + if user { flags |= PTE_U; } + if global { flags |= PTE_G; } + Self(flags | (ppn << 10)) + } + + pub fn new_branch(ppn: u64) -> Self { + Self(PTE_V | (ppn << 10)) + } + + pub fn is_valid(&self) -> bool { + self.0 & PTE_V != 0 + } + + pub fn ppn(&self) -> u64 { + (self.0 >> 10) & 0xFFF_FFFF_FFFF + } +} + +#[repr(C, align(4096))] +pub struct PageTable { + pub entries: [PageTableEntry; 512], +} + +impl PageTable { + pub fn zero(&mut self) { + for entry in &mut self.entries { + *entry = PageTableEntry::empty(); + } + } +} + +static mut PAGE_ALLOC_NEXT: usize = 0; + +pub fn init_page_allocator(start: usize) { + unsafe { PAGE_ALLOC_NEXT = start; } +} + +pub fn alloc_page_table() -> &'static mut PageTable { + unsafe { + let addr = PAGE_ALLOC_NEXT; + PAGE_ALLOC_NEXT += PAGE_SIZE; + let table = &mut *(addr as *mut PageTable); + table.zero(); + table + } +} + +fn vpn(va: usize, level: usize) -> usize { + (va >> (12 + 9 * level)) & 0x1FF +} + +fn map_page(root: &mut PageTable, va: usize, pa: usize, read: bool, write: bool, exec: bool, user: bool, global: bool) { + let vpn2 = vpn(va, 2); + let vpn1 = vpn(va, 1); + let vpn0 = vpn(va, 0); + + if !root.entries[vpn2].is_valid() { + let table = alloc_page_table(); + let ppn = (table as *const PageTable as u64) >> 12; + root.entries[vpn2] = PageTableEntry::new_branch(ppn); + } + + let l1_addr = (root.entries[vpn2].ppn() << 12) as *mut PageTable; + let l1 = unsafe { &mut *l1_addr }; + + if !l1.entries[vpn1].is_valid() { + let table = alloc_page_table(); + let ppn = (table as *const PageTable as u64) >> 12; + l1.entries[vpn1] = PageTableEntry::new_branch(ppn); + } + + let l0_addr = (l1.entries[vpn1].ppn() << 12) as *mut PageTable; + let l0 = unsafe { &mut *l0_addr }; + + let ppn = (pa as u64) >> 12; + l0.entries[vpn0] = PageTableEntry::new_leaf(ppn, read, write, exec, user, global); +} + +fn map_range(root: &mut PageTable, va_start: usize, pa_start: usize, pages: usize, read: bool, write: bool, exec: bool, user: bool, global: bool) { + for i in 0..pages { + let offset = i * PAGE_SIZE; + map_page(root, va_start + offset, pa_start + offset, read, write, exec, user, global); + } +} + +pub fn init_kernel_page_table() -> &'static mut PageTable { + let root = alloc_page_table(); + + let ram_start = 0x80000000usize; + let ram_pages = 128 * 1024 * 1024 / PAGE_SIZE; // 128 MiB + map_range(root, ram_start, ram_start, ram_pages, true, true, true, false, true); + + let uart_start = 0x10000000usize; + map_range(root, uart_start, uart_start, 1, true, true, false, false, true); + + let plic_start = 0x0C000000usize; + let plic_pages = 0x400000 / PAGE_SIZE; + map_range(root, plic_start, plic_start, plic_pages, true, true, false, false, true); + + let clint_start = 0x02000000usize; + let clint_pages = 0x10000 / PAGE_SIZE; + map_range(root, clint_start, clint_start, clint_pages, true, true, false, false, true); + + root +} + +pub fn activate(root: &PageTable) { + let ppn = (root as *const PageTable as u64) >> 12; + let satp = (8u64 << 60) | ppn; // mode=8 (Sv39), ASID=0 + unsafe { + core::arch::asm!( + "csrw satp, {0}", + "sfence.vma", + in(reg) satp, + ); + } +} + +pub fn create_user_page_table( + kernel_root: &PageTable, + program_pa: usize, + program_pages: usize, + user_va: usize, +) -> &'static mut PageTable { + let root = alloc_page_table(); + + // Copy kernel mappings (upper half — entries 256-511 cover high VA space) + // For identity mapping, copy all valid entries from kernel root + for i in 0..512 { + if kernel_root.entries[i].is_valid() { + root.entries[i] = kernel_root.entries[i]; + } + } + + // Map user program pages + map_range(root, user_va, program_pa, program_pages, true, false, true, true, false); + + // Map user stack (1 page at user_va - PAGE_SIZE) + let stack_va = user_va.wrapping_sub(PAGE_SIZE); + let stack_table = alloc_page_table(); + let stack_pa = stack_table as *const PageTable as usize; + map_range(root, stack_va, stack_pa, 1, true, true, false, true, false); + + root +}