From c43100337514f98c0eeb202980b42b558d303b3e Mon Sep 17 00:00:00 2001 From: Nathan Lee Date: Tue, 28 Jul 2026 23:25:06 -0500 Subject: rework memory system to use actual Sv32 paging --- src/core.rs | 57 ++++-------- src/main.rs | 140 +++++++++++++++++++++------- src/mem.rs | 263 ++++++++++++++++++++++++++++++++++------------------- src/state.rs | 6 +- wrv32em-common.dts | 9 +- 5 files changed, 299 insertions(+), 176 deletions(-) diff --git a/src/core.rs b/src/core.rs index 6317c9e..2746d1b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -151,74 +151,53 @@ pub fn sltiu(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) { state.registers[rd] = if state.registers[rs1] < imm { 1 } else { 0 }; } pub fn lb(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) { - let byte = state - .memory - .read((state.registers[rs1] + imm) as usize, 99) - .unwrap(); + let byte = state.memory.read(state.registers[rs1] + imm).unwrap(); state.registers[rd] = (byte as i8) as u32; } pub fn lh(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) { - let bytes = state - .memory - .read_multiple_bytes((state.registers[rs1] + imm) as usize, 2, 99) - .unwrap(); - - state.registers[rd] = (((bytes[1] as u16) << 8 | bytes[0] as u16) as i16) as u32; + let halfword = state.memory.read_halfword(state.registers[rs1] + imm).unwrap(); + state.registers[rd] = (halfword as i16) as u32; } pub fn lw(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) { - let addr = (state.registers[rs1].wrapping_add(imm)) as usize; - let slice = state.memory.read_multiple_bytes(addr, 4, 99).unwrap(); - let number = u32::from_le_bytes(slice[0..4].try_into().unwrap()); - - state.registers[rd] = number; + let addr = state.registers[rs1].wrapping_add(imm); + let word = state.memory.read_word(addr).unwrap(); + state.registers[rd] = word; } pub fn lbu(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) { state.registers[rd] = state .memory - .read((state.registers[rs1] + imm) as usize, 99) + .read(state.registers[rs1].wrapping_add(imm)) .unwrap() as u32; } pub fn lhu(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) { - let bytes = state - .memory - .read_multiple_bytes((state.registers[rs1] + imm) as usize, 2, 99) - .unwrap(); - - state.registers[rd] = ((bytes[1] as u16) << 8 | bytes[0] as u16) as u32; + state.registers[rd] = state.memory.read_halfword(state.registers[rs1] + imm).unwrap() as u32; } pub fn sb(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) { - let addr = (state.registers[rs1] + imm) as usize; + let addr = state.registers[rs1] + imm; state .memory - .write(addr, 99, state.registers[rs2] as u8) + .write(addr, state.registers[rs2] as u8) .unwrap(); - state.release_reservations(&[addr]); + state.release_reservations(&[addr as usize]); } pub fn sh(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) { let addr = (state.registers[rs1] + imm) as usize; - let halfword = state.registers[rs2] & 0xFFFF; - - state - .memory - .write_multiple_bytes(addr, &[(halfword & 0xFF) as u8, (halfword >> 8) as u8], 99) - .unwrap(); + let halfword= (state.registers[rs2] & 0xFFFF) as u16; + state.memory.write_halfword(addr as u32, halfword).unwrap(); state.release_reservations(&[addr, addr + 1]); } pub fn sw(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) { let addr = (state.registers[rs1] + imm) as usize; - state - .memory - .write_multiple_bytes(addr, &state.registers[rs2].to_le_bytes(), 99) - .unwrap(); + state.memory.write_word(addr as u32, state.registers[rs2]).unwrap(); state.release_reservations(&[addr, addr + 1, addr + 2, addr + 3]); } @@ -362,14 +341,10 @@ pub fn amominu_w(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) { } fn load_word(state: &CpuState, addr: usize) -> u32 { - let slice = state.memory.read_multiple_bytes(addr, 4, 99).unwrap(); - u32::from_le_bytes(slice[0..4].try_into().unwrap()) + state.memory.read_word(addr as u32).unwrap() } fn store_word(state: &mut CpuState, addr: usize, value: u32) { - state - .memory - .write_multiple_bytes(addr, &value.to_le_bytes(), 99) - .unwrap(); + state.memory.write_word(addr as u32, value).unwrap() } pub fn auipc(state: &mut CpuState, rd: usize, imm: u32) { diff --git a/src/main.rs b/src/main.rs index 45f567f..c276280 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,53 +3,106 @@ pub mod mem; pub mod state; use crate::core::*; -use crate::mem::Memory; +use crate::mem::{CpuMemory, LogicalMemory}; use crate::state::CpuState; -use std::io::Write; const MEMORY_SIZE: usize = 64 * 1024 * 1024; const REGISTER_NAME_WIDTH: u32 = 0b11111; const RO_TARGET: u32 = 0x80000000; -const FDT_LOCATION: u32 = 0x90000000; -const UART_ADDR: u32 = 0x10000000; +const FDT_LOCATION: u32 = 0x83000000; + +const RINGBUF_ADDR: usize = 0x82000000; +const RINGBUF_SIZE: usize = 0x100000; + +fn write_all(memory: &mut CpuMemory, value: &[u8], addr: u32, size: u32) { + let mut remaining_size = size; + let mut curr_addr = addr; + let mut idx = 0; + while remaining_size >= 4 { + memory + .write_word( + curr_addr, + u32::from_le_bytes(value[idx..(idx + 4)].try_into().unwrap()), + ) + .unwrap(); + remaining_size -= 4; + curr_addr += 4; + idx += 4; + } + while remaining_size >= 2 { + memory + .write_halfword( + curr_addr, + u16::from_le_bytes(value[idx..(idx + 2)].try_into().unwrap()), + ) + .unwrap(); + remaining_size -= 2; + curr_addr += 2; + idx += 2; + } + while remaining_size >= 1 { + memory.write(curr_addr, value[idx]).unwrap(); + remaining_size -= 1; + curr_addr += 1; + idx += 1; + } +} + +fn read_all(memory: &CpuMemory, addr: u32, size: u32) -> Vec { + let mut ret = Vec::new(); + for a in addr..addr + size { + ret.push(memory.read(a).unwrap()); + } + ret +} fn main() { let path = std::path::PathBuf::from("wkern.bin"); let f_dat = std::fs::read(path).expect("Unable to read file"); - let dtb_dat = std::fs::read(env!("DTB_PATH")).expect("Unable to read file"); - let memory = Memory::new(MEMORY_SIZE); + let mut memory = CpuMemory { + memory: vec![0u8; MEMORY_SIZE].into_boxed_slice(), + devices: Vec::new(), + }; - let mut state = CpuState::new(memory); + let ram_device = LogicalMemory { + mode: false, + asid: 0, + ppn: 0, + base: 0x80000000, + }; - state - .memory - .write_multiple_bytes(RO_TARGET as usize, f_dat.as_slice(), 99) - .unwrap(); - state - .memory - .write_multiple_bytes(FDT_LOCATION as usize, dtb_dat.as_slice(), 99) - .unwrap(); + memory.register_device( + 0x80000000..0x80000000 + MEMORY_SIZE as u32, + Box::new(ram_device), + ); - state.memory.write(UART_ADDR as usize, 99, 0).unwrap(); - state.pc = RO_TARGET; + let mut state = CpuState::new(memory); + + write_all( + &mut state.memory, + f_dat.as_slice(), + RO_TARGET, + f_dat.len() as u32, + ); + write_all( + &mut state.memory, + dtb_dat.as_slice(), + FDT_LOCATION, + dtb_dat.len() as u32, + ); + + state.pc = 0x80000000; state.registers[10] = 0; state.registers[11] = FDT_LOCATION; + println!("loaded!"); + loop { { handle_instruction(&mut state); - - // simple polling uart - let uart_byte = state.memory.read(UART_ADDR as usize, 99).unwrap(); - if uart_byte != 0 { - print!("{}", uart_byte as char); - } - state.memory.write(UART_ADDR as usize, 99, 0).unwrap(); - std::io::stdout().flush().unwrap(); - state.registers[0] = 0; } } @@ -57,8 +110,7 @@ fn main() { fn handle_instruction(state: &mut CpuState) { let pc = state.pc; - let instruction = state.memory.read_multiple_bytes(pc as usize, 4, 99); - let word = u32::from_le_bytes(instruction.unwrap()[0..4].try_into().unwrap()); + let word = state.memory.read_word(pc).unwrap(); let opcode = word & 0x7f; match opcode { 0b0110011 => { @@ -220,12 +272,15 @@ fn handle_instruction(state: &mut CpuState) { match f3 { 0 => { - eprintln!( - "TRAP! syscall {} with gp state {:?}", - state.registers[17], pc - ); - - // todo: trap to CSR 0x305 + match imm { + 0 => trap_to_kernel(state), // ecall + 258 => return_to_userspace(state), // sret + 261 => { + println!("wfi"); + exit(state); + } + _ => panic!("Illegal instruction: {:x}", word), + } } // ecall 1 => csrrw(state, rd, rs1, imm as usize), 2 => csrrs(state, rd, rs1, imm as usize), @@ -349,8 +404,23 @@ fn handle_instruction(state: &mut CpuState) { let rs1 = ((word >> 15) & REGISTER_NAME_WIDTH) as usize; fcvt_w_s(state, rd, rs1); } - _ => panic!("Illegal instruction or todo: {:x}", word), + _ => panic!("Illegal instruction or todo: {:032b}", word), } state.pc += 4; } + +fn trap_to_kernel(state: &mut CpuState) { + state.csr[0x141 /* sepc */] = state.pc; + state.pc = state.csr[0x105 /* stvec */]; +} + +fn return_to_userspace(state: &mut CpuState) { + state.pc = state.csr[0x141 /* sepc */] + 4; +} + +fn exit(state: &mut CpuState) { + let data = read_all(&state.memory, RINGBUF_ADDR as u32, RINGBUF_SIZE as u32); + std::fs::write("core_dump.txt", data).unwrap(); + std::process::exit(0); +} diff --git a/src/mem.rs b/src/mem.rs index 2043aa1..770659b 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -1,124 +1,201 @@ -const PAGE_SIZE: usize = 4 * 1024; -const PAGE_MASK: usize = !(PAGE_SIZE - 1); - -struct Page { - virtual_addr: usize, - real_addr: usize, - permission: usize, - allocated: usize, +use std::ops::Range; + +pub trait MemoryDevice { + fn read(&self, memory: &CpuMemory, address: u32) -> Result; + fn write(&self, memory: &CpuMemory, address: u32, value: u8) -> Result, ()>; } -pub struct Memory { - memory: Box<[u8]>, - pages: Vec, + +pub struct CpuMemory { + pub memory: Box<[u8]>, + pub devices: Vec<(Range, Box)>, } -impl Memory { - pub fn new(size: usize) -> Memory { - let mut pages: Vec = Vec::with_capacity(size / PAGE_SIZE); - for i in 0..size / PAGE_SIZE { - pages.push(Page { - virtual_addr: usize::MAX, - real_addr: i * PAGE_SIZE, - permission: 0, - allocated: 0, - }); - } - Memory { - memory: vec![0u8; size].into_boxed_slice(), - pages, - } + +impl CpuMemory { + fn get_device(&self, address: u32) -> Option<&Box> { + self.devices + .iter() + .find(|(range, _)| range.contains(&address)) + .map(|(_, device)| device) } - pub fn create_page(&mut self, virtual_addr: usize, permission: usize) -> Option { - // Find first page that isn't allocated and set it to allocated - for (i, page) in self.pages.iter_mut().enumerate() { - if page.allocated == 0 { - page.virtual_addr = virtual_addr; - page.permission = permission; - page.allocated = 1; - return Some(i); - } - } - eprintln!("tried to allocate {:X} but there are no more pages!", virtual_addr); - None + pub fn register_device(&mut self, range: Range, device: Box) { + self.devices.push((range, device)); } - fn lookup_page(&mut self, virtual_addr: usize, permission: usize) -> Option { - for (i, page) in self.pages.iter().enumerate() { - if page.virtual_addr == virtual_addr - && permission >= page.permission - && page.allocated == 1 - { - return Some(i); - } + pub fn read(&self, address: u32) -> Result { + if let Some(device) = self.get_device(address) { + device.read(self, address) + } else { + Err(()) } - self.create_page(virtual_addr, permission) } - pub fn ro_lookup(&self, virtual_addr: usize, permission: usize) -> Option { - for (i, page) in self.pages.iter().enumerate() { - if page.virtual_addr == virtual_addr - && page.permission == permission - && page.allocated == 1 - { - return Some(i); + pub fn write(&mut self, address: u32, value: u8) -> Result<(), ()> { + if let Some(device) = self.get_device(address) { + match device.write(self, address, value) { + Ok(opt_idx) => { + if let Some(idx) = opt_idx { + self.memory[idx as usize] = value; + } + Ok(()) + } + Err(()) => Err(()) } + } else { + Err(()) } - println!("virtual_addr {:X} is not allocated", virtual_addr); - None } - pub fn write(&mut self, address: usize, permission: usize, value: u8) -> Result<(), ()> { - let page = address & PAGE_MASK; - let offset = address - page; + pub fn read_halfword(&self, address: u32) -> Result { + let low = self.read(address)?; + let high = self.read(address + 1)?; + Ok((high as u16) << 8 | low as u16) + } - if let Some(page) = self.lookup_page(page, permission) { - self.memory[self.pages[page].real_addr + offset] = value; - return Ok(()); - } - Err(()) + pub fn write_halfword(&mut self, address: u32, value: u16) -> Result<(), ()> { + self.write(address + 1, (value >> 8) as u8)?; + self.write(address, (value & 0xFF) as u8)?; + Ok(()) } - pub fn read(&self, address: usize, permission: usize) -> Result { - let page = address & PAGE_MASK; - let offset = address - page; + pub fn read_word(&self, address: u32) -> Result { + let low = self.read_halfword(address)?; + let high = self.read_halfword(address + 2)?; + Ok((high as u32) << 16 | low as u32) + } - if let Some(page) = self.ro_lookup(page, permission) { - Ok(self.memory[self.pages[page].real_addr + offset]) - } else { - println!("address {:X} is not allocated", address); + pub fn write_word(&mut self, address: u32, value: u32) -> Result<(), ()> { + self.write_halfword(address + 2, (value >> 16) as u16)?; + self.write_halfword(address, (value & 0xFFFF) as u16) + } +} - Err(()) +pub struct LogicalMemory { + pub mode: bool, + pub asid: u16, + pub ppn: u32, + pub base: u32, +} + +pub struct Sv32Page { + ppn: u32, + _rsw: u8, + _dirty: bool, + _access: bool, + _global: bool, + _user: bool, + _execute: bool, + write: bool, + read: bool, + valid: bool, +} + +impl From for Sv32Page { + fn from(value: u32) -> Sv32Page { + Sv32Page { + ppn: value >> 10, + _rsw: ((value >> 8) & 3) as u8, + _dirty: (value >> 7) & 1 == 0, + _access: (value >> 6) & 1 == 0, + _global: (value >> 5) & 1 == 0, + _user: (value >> 4) & 1 == 0, + _execute: (value >> 3) & 1 == 0, + write: (value >> 2) & 1 == 0, + read: (value >> 1) & 1 == 0, + valid: value & 1 == 0, } } +} - pub fn read_multiple_bytes( - &self, - address: usize, - size: usize, - permission: usize, - ) -> Result, ()> { - let mut ret = vec![0; size]; - for addr in address..address + size { - if let Ok(byte) = self.read(addr, permission) { - ret[addr - address] = byte; - } else { +impl LogicalMemory { + pub fn satp_write(&mut self, satp: u32) { + self.mode = (satp >> 31) & 1 != 0; + self.asid = ((satp >> 22) & 0x1FF) as u16; + self.ppn = satp & 0x3FFFFF; + } + + pub fn lookup_page_entry(&self, memory: &CpuMemory, address: u32) -> Option { + if address - self.base + 4 > memory.memory.len() as u32 { + return None; + } + Some(u32::from_le_bytes( + memory.memory[(address - self.base) as usize..(address - self.base + 4) as usize] + .try_into() + .unwrap(), + )) + } +} + +impl MemoryDevice for LogicalMemory { + fn read(&self, memory: &CpuMemory, address: u32) -> Result { + if self.mode + /* Sv32 */ + { + let root = self.ppn << 12; + let high_page = (address >> 22) & 0x3FF; + + let high_pte = self + .lookup_page_entry(memory, root + high_page * 4) + .ok_or(())?; + let high_pte = Sv32Page::from(high_pte); + if !high_pte.valid { return Err(()); } + + let low_page = (address >> 12) & 0x3FF; + let low_pte = self + .lookup_page_entry(memory, (high_pte.ppn << 12) + low_page * 4) + .ok_or(())?; + let low_pte = Sv32Page::from(low_pte); + + if !low_pte.read || !low_pte.valid { + return Err(()); + } + + Ok(memory.memory[((low_pte.ppn << 12) | (address & 0xfff)) as usize]) + } else + /* bare */ + { + if address - self.base > memory.memory.len() as u32 { + Err(()) + } else { + Ok(memory.memory[(address - self.base) as usize]) + } } - Ok(ret) } + fn write(&self, memory: &CpuMemory, address: u32, _value: u8) -> Result, ()> { + if self.mode + /* Sv32 */ + { + let root = self.ppn << 12; + let high_page = (address >> 22) & 0x3FF; - pub fn write_multiple_bytes( - &mut self, - address: usize, - bytes: &[u8], - permission: usize, - ) -> Result<(), ()> { - for addr in address..address + bytes.len() { - if let Err(_) = self.write(addr, permission, bytes[addr - address]) { + let high_pte = self + .lookup_page_entry(memory, root + high_page * 4) + .ok_or(())?; + let high_pte = Sv32Page::from(high_pte); + if !high_pte.valid { return Err(()); } + + let low_page = (address >> 12) & 0x3FF; + let low_pte = self + .lookup_page_entry(memory, (high_pte.ppn << 12) + low_page * 4) + .ok_or(())?; + let low_pte = Sv32Page::from(low_pte); + + if !low_pte.read || !low_pte.valid { + return Err(()); + } + Ok(Some((low_pte.ppn << 12) | (address & 0xfff))) + } else + /* bare */ + { + if address - self.base > memory.memory.len() as u32 { + Err(()) + } else { + Ok(Some(address - self.base)) + } } - Ok(()) } } diff --git a/src/state.rs b/src/state.rs index d4690bb..fa09e68 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ -use crate::mem::Memory; +use crate::mem::CpuMemory; pub struct CpuState { - pub memory: Memory, + pub memory: CpuMemory, pub registers: [u32; 32], pub f_registers: [f32; 32], pub csr: [u32; 4096], @@ -10,7 +10,7 @@ pub struct CpuState { } impl CpuState { - pub fn new(memory: Memory) -> CpuState { + pub fn new(memory: CpuMemory) -> CpuState { CpuState { memory, registers: [0u32; 32], diff --git a/wrv32em-common.dts b/wrv32em-common.dts index e7c1181..7ba1946 100644 --- a/wrv32em-common.dts +++ b/wrv32em-common.dts @@ -4,10 +4,10 @@ #address-cells = <1>; #size-cells = <1>; - memory@70000000 { + memory@80000000 { device_type = "memory"; status = "okay"; - reg = <0x70000000 + reg = <0x80000000 0x04000000>; }; gpu@60000000 { @@ -15,11 +15,12 @@ device_type = "gpu"; status = "disabled"; reg = <0x60000000 0x04000000>; - } + }; uart@60000000 { compatible = "unix,soc-tty"; device_type = "gpu"; status = "okay"; reg = <0x60000000 0x04000000>; - } + }; + }; -- cgit v1.2.3