From 528b519ce98c21049705526442cbde1672495b49 Mon Sep 17 00:00:00 2001 From: taitep Date: Fri, 26 Dec 2025 14:20:27 +0100 Subject: [PATCH] (BIG CHANGE) memory handling has changed, MMIO is now a 2 level page table, misaligned access supported, addresses not internally split to page and offset immediately, all load/store instructions implemented. Might still have bugs --- README.md | 4 +- echo.S | 2 +- src/basic_uart.rs | 77 +---- src/core.rs | 12 +- src/exceptions.rs | 34 ++- src/instructions.rs | 3 +- src/instructions/rvi/mem.rs | 131 ++++---- src/main.rs | 83 ++--- src/mem.rs | 588 +++++++++++++++++++++--------------- 9 files changed, 478 insertions(+), 456 deletions(-) diff --git a/README.md b/README.md index 43087bc..06b640e 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ The emulator will load a raw binary image or static ELF executable from a file s which starts at 0x80000000 and is currently 16MiB, and start execution at the start of the image/ram or the ELF entry point. -There is also a debug out page starting at `0x00000000`-`0x00001000`. +There is also a debug out section at `0x00000000`-`0x00010000`. Anything written to it will be logged out in hex. -There is also a UART at `0x00001000`-`0x00002000`, the interface is quite simple: +There is also a UART at `0x00010000`-`0x00010002`, the interface is quite simple: - byte `0`: Data. When written, writes out the character When read, reads a character from the buffer, or 0 if empty. - byte `1`: Status. Read-only. Least significant bit is `TX_READY` and indicates whether diff --git a/echo.S b/echo.S index 7c29fec..337c26b 100644 --- a/echo.S +++ b/echo.S @@ -7,7 +7,7 @@ .equ UART_TX_READY, 0b01 _start: - li a0, 0x1000 + li a0, 0x10000 loop: lbu t0, UART_STATUS(a0) diff --git a/src/basic_uart.rs b/src/basic_uart.rs index ec687e3..4632b11 100644 --- a/src/basic_uart.rs +++ b/src/basic_uart.rs @@ -13,9 +13,9 @@ use std::time::Duration; use nix::fcntl::fcntl; use nix::fcntl::{FcntlArg, OFlag}; -use trve::consts::{Byte, DWord, HWord, Word}; -use trve::exceptions::ExceptionType; -use trve::mem::{MemDeviceInterface, PageNum}; +use trve::consts::{Addr, Byte}; +use trve::exceptions::MemoryExceptionType; +use trve::mem::MemDeviceInterface; /// byte 0: rx/tx /// byte 1: status (------rt, r=rxready, t=txready)/none @@ -78,79 +78,20 @@ impl BasicUart { } impl MemDeviceInterface for BasicUart { - fn write_dword( - &self, - _page: PageNum, - _offset: u16, - _value: DWord, - ) -> Result<(), ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) - } - - fn write_word(&self, _page: PageNum, _offset: u16, _value: Word) -> Result<(), ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) - } - - fn write_hword( - &self, - _page: PageNum, - _offset: u16, - _value: HWord, - ) -> Result<(), ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) - } - - fn write_byte(&self, page: PageNum, offset: u16, value: Byte) -> Result<(), ExceptionType> { - if page > 0 { - return Err(ExceptionType::StoreAmoAccessFault); - } - - match offset { + fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> { + match addr { 0 => { self.write(value); Ok(()) } - _ => Err(ExceptionType::StoreAmoAccessFault), + _ => Err(MemoryExceptionType::AccessFault), } } - - fn read_dword(&self, _page: PageNum, _offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) - } - - fn read_word(&self, _page: PageNum, _offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) - } - - fn read_hword(&self, _page: PageNum, _offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) - } - - fn read_byte(&self, page: PageNum, offset: u16) -> Result { - if page > 0 { - return Err(ExceptionType::LoadAccessFault); - } - - match offset { + fn read_byte(&self, addr: Addr) -> Result { + match addr { 0 => Ok(self.read()), 1 => Ok(1 | (self.can_read() as u8) << 1), - _ => Err(ExceptionType::LoadAccessFault), + _ => Err(MemoryExceptionType::AccessFault), } } - - fn get_atomic_word( - &self, - _page: PageNum, - _offset: u16, - ) -> Result<&std::sync::atomic::AtomicU32, ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) - } - - fn get_atomic_dword( - &self, - _page: PageNum, - _offset: u16, - ) -> Result<&std::sync::atomic::AtomicU64, ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) - } } diff --git a/src/core.rs b/src/core.rs index d5b254d..f46fd54 100644 --- a/src/core.rs +++ b/src/core.rs @@ -4,6 +4,8 @@ // This file is part of TRVE (https://gitea.taitep.se/taitep/trve) // See LICENSE file in the project root for full license text. +use std::fmt::format; + use crate::{ consts::{Addr, RegId, RegValue}, decode::Instruction, @@ -29,17 +31,15 @@ impl Core { pub fn run(&mut self) { loop { - let page = (self.pc / 4096) as usize; - let offset = (self.pc % 4096 / 4) as u16; if !self.pc.is_multiple_of(4) { - self.throw_exception(ExceptionType::InstructionAccessMisaligned); + self.throw_exception(ExceptionType::InstructionAddressMisaligned); break; } - let instr = match self.mem.read_word(page, offset) { + let instr = match self.mem.read_word(self.pc) { Ok(i) => i, - Err(_) => { - self.throw_exception(ExceptionType::InstructionAccessFault); + Err(e) => { + self.throw_exception(e.to_exception_instr()); break; } }; diff --git a/src/exceptions.rs b/src/exceptions.rs index 2929d65..7f00e5c 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -10,7 +10,7 @@ use int_enum::IntEnum; #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq, IntEnum)] pub enum ExceptionType { - InstructionAccessMisaligned = 0, + InstructionAddressMisaligned = 0, InstructionAccessFault = 1, IllegalInstruction = 2, Breakpoint = 3, @@ -28,3 +28,35 @@ pub enum ExceptionType { SoftwareCheck = 18, HardwareError = 19, } + +pub enum MemoryExceptionType { + AddressMisaligned, + AccessFault, + PageFault, +} + +impl MemoryExceptionType { + pub(crate) fn to_exception_store(&self) -> ExceptionType { + match self { + Self::AddressMisaligned => ExceptionType::StoreAmoAddressMisaligned, + Self::AccessFault => ExceptionType::StoreAmoAccessFault, + Self::PageFault => ExceptionType::StoreAmoPageFault, + } + } + + pub(crate) fn to_exception_instr(&self) -> ExceptionType { + match self { + Self::AddressMisaligned => ExceptionType::InstructionAddressMisaligned, + Self::AccessFault => ExceptionType::InstructionAccessFault, + Self::PageFault => ExceptionType::InstructionPageFault, + } + } + + pub(crate) fn to_exception_load(&self) -> ExceptionType { + match self { + Self::AddressMisaligned => ExceptionType::LoadAddressMisaligned, + Self::AccessFault => ExceptionType::LoadAccessFault, + Self::PageFault => ExceptionType::LoadPageFault, + } + } +} diff --git a/src/instructions.rs b/src/instructions.rs index 482ea5d..5e3f39c 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -58,7 +58,9 @@ pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), E 0b000 => rvi::lb(core, instr), 0b100 => rvi::lbu(core, instr), 0b001 => rvi::lh(core, instr), + 0b101 => rvi::lhu(core, instr), 0b010 => rvi::lw(core, instr), + 0b110 => rvi::lwu(core, instr), 0b011 => rvi::ld(core, instr), _ => Err(IllegalInstruction), }, @@ -74,7 +76,6 @@ pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), E 0b01101 => rvi::lui(core, instr), 0b00101 => rvi::auipc(core, instr), 0b11011 => rvi::jal(core, instr), - // 0b11001 => (instr.funct3() == 0).then(|| rvi::jalr(core, instr)), 0b11001 => { if instr.funct3() == 0 { rvi::jalr(core, instr) diff --git a/src/instructions/rvi/mem.rs b/src/instructions/rvi/mem.rs index ad938cb..bc02b7f 100644 --- a/src/instructions/rvi/mem.rs +++ b/src/instructions/rvi/mem.rs @@ -5,75 +5,63 @@ // See LICENSE file in the project root for full license text. use crate::{ - consts::{Addr, Byte, DWord, HWord, Word}, + consts::{Byte, DWord, HWord, Word}, core::Core, exceptions::ExceptionType, instructions::Instruction, - mem::PageNum, }; -// TODO: Support misaligned memory access pub fn sd(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s()); - - if !addr.is_multiple_of(std::mem::size_of::() as Addr) { - return Err(ExceptionType::StoreAmoAddressMisaligned); - } - - let page = (addr / 4096) as PageNum; - let offset = (addr / 8 & ((4096 / 8 as Addr) - 1)) as u16; let value = core.reg_read(instr.rs2()); - - core.mem.write_dword(page, offset, value)?; + core.mem + .write_dword(addr, value) + .map_err(|e| e.to_exception_store())?; core.advance_pc(); - Ok(()) } pub fn ld(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()); - - if !addr.is_multiple_of(std::mem::size_of::() as Addr) { - return Err(ExceptionType::LoadAddressMisaligned); - } - - let page = (addr / 4096) as PageNum; - let offset = (addr / 8 & ((4096 / 8 as Addr) - 1)) as u16; - - core.reg_write(instr.rd(), core.mem.read_dword(page, offset)?); + core.reg_write( + instr.rd(), + core.mem + .read_dword(addr) + .map_err(|e| e.to_exception_load())?, + ); core.advance_pc(); Ok(()) } pub fn sw(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s()); - - if !addr.is_multiple_of(std::mem::size_of::() as Addr) { - return Err(ExceptionType::StoreAmoAddressMisaligned); - } - - let page = (addr / 4096) as PageNum; - let offset = (addr / 4 & ((4096 / 4 as Addr) - 1)) as u16; let value = core.reg_read(instr.rs2()) as Word; - - core.mem.write_word(page, offset, value)?; + core.mem + .write_word(addr, value) + .map_err(|e| e.to_exception_store())?; core.advance_pc(); Ok(()) } pub fn lw(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()); - - if !addr.is_multiple_of(std::mem::size_of::() as Addr) { - return Err(ExceptionType::LoadAddressMisaligned); - } - - let page = (addr / 4096) as PageNum; - let offset = (addr / 4 & ((4096 / 4 as Addr) - 1)) as u16; - core.reg_write( instr.rd(), - core.mem.read_word(page, offset)? as i32 as i64 as DWord, + core.mem + .read_word(addr) + .map_err(|e| e.to_exception_load())? as i32 as i64 as DWord, + ); + core.advance_pc(); + Ok(()) +} + +pub fn lwu(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { + let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()); + core.reg_write( + instr.rd(), + core.mem + .read_word(addr) + .map_err(|e| e.to_exception_load())? as DWord, ); core.advance_pc(); Ok(()) @@ -81,33 +69,33 @@ pub fn lw(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { pub fn sh(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s()); - - if !addr.is_multiple_of(std::mem::size_of::() as Addr) { - return Err(ExceptionType::StoreAmoAddressMisaligned); - } - - let page = (addr / 4096) as PageNum; - let offset = (addr / 2 & ((4096 / 2 as Addr) - 1)) as u16; let value = core.reg_read(instr.rs2()) as HWord; - - core.mem.write_hword(page, offset, value)?; + core.mem + .write_hword(addr, value) + .map_err(|e| e.to_exception_store())?; core.advance_pc(); Ok(()) } pub fn lh(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()); - - if !addr.is_multiple_of(std::mem::size_of::() as Addr) { - return Err(ExceptionType::LoadAddressMisaligned); - } - - let page = (addr / 4096) as PageNum; - let offset = (addr / 2 & ((4096 / 2 as Addr) - 1)) as u16; - core.reg_write( instr.rd(), - core.mem.read_hword(page, offset)? as i16 as i64 as DWord, + core.mem + .read_hword(addr) + .map_err(|e| e.to_exception_load())? as i16 as i64 as DWord, + ); + core.advance_pc(); + Ok(()) +} + +pub fn lhu(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { + let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()); + core.reg_write( + instr.rd(), + core.mem + .read_hword(addr) + .map_err(|e| e.to_exception_load())? as DWord, ); core.advance_pc(); Ok(()) @@ -115,25 +103,21 @@ pub fn lh(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { pub fn sb(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s()); - - let page = (addr / 4096) as PageNum; - let offset = (addr & (4096 as Addr - 1)) as u16; let value = core.reg_read(instr.rs2()) as Byte; - - core.mem.write_byte(page, offset, value)?; + core.mem + .write_byte(addr, value) + .map_err(|e| e.to_exception_store())?; core.advance_pc(); Ok(()) } pub fn lb(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()); - - let page = (addr / 4096) as PageNum; - let offset = (addr & (4096 as Addr - 1)) as u16; - core.reg_write( instr.rd(), - core.mem.read_byte(page, offset)? as i8 as i64 as DWord, + core.mem + .read_byte(addr) + .map_err(|e| e.to_exception_load())? as i8 as i64 as DWord, ); core.advance_pc(); Ok(()) @@ -141,11 +125,12 @@ pub fn lb(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { pub fn lbu(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> { let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()); - - let page = (addr / 4096) as PageNum; - let offset = (addr & (4096 as Addr - 1)) as u16; - - core.reg_write(instr.rd(), core.mem.read_byte(page, offset)? as DWord); + core.reg_write( + instr.rd(), + core.mem + .read_byte(addr) + .map_err(|e| e.to_exception_load())? as DWord, + ); core.advance_pc(); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 615f3bf..b3393d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,10 @@ use std::{env, sync::Arc, time::Duration}; use trve::{ - consts::{Byte, DWord, HWord, Word}, + consts::{Addr, Byte, DWord, HWord, Word}, core::Core, - exceptions::ExceptionType, - mem::{DeviceEntry, MemConfig, MemDeviceInterface, PageNum, Ram}, + exceptions::MemoryExceptionType, + mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram}, }; use anyhow::{Result, bail}; @@ -20,7 +20,7 @@ use crate::basic_uart::BasicUart; mod execload; fn main() -> Result<()> { - let mut ram = Ram::try_new(16 * 1024 * 1024 / 4096)?; + let mut ram = Ram::try_new(16 * 1024 * 1024)?; let buf = ram.buf_mut(); let args: Vec = env::args().collect(); @@ -32,24 +32,16 @@ fn main() -> Result<()> { let entry_point = execload::load(&args[1], buf, 0x8000_0000)?; + let mut mmio_root = MmioRoot::default(); + mmio_root.insert(0, Arc::new(DbgOut)); + let uart = BasicUart::new(); let uart = uart.spawn_poller(Duration::from_millis(10)); + mmio_root.insert(0x10000, uart); let mem_cfg = MemConfig { ram: Arc::new(ram), - ram_start: 0x8000_0000 / 4096, - devices: Box::new([ - DeviceEntry { - base: 0, - size: 1, - interface: Arc::new(DbgOut), - }, - DeviceEntry { - base: 1, - size: 1, - interface: uart, - }, - ]), + mmio_root, }; let mut core = Core::new(mem_cfg); @@ -64,64 +56,23 @@ mod basic_uart; struct DbgOut; impl MemDeviceInterface for DbgOut { - fn write_dword(&self, page: PageNum, offset: u16, value: DWord) -> Result<(), ExceptionType> { - eprintln!( - "Wrote DWord {value:016x} to Debug-Out page {page}, offset {offset} (byte {})", - offset * 8 - ); + fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryExceptionType> { + eprintln!("Wrote DWord {value:016x} to Debug-Out address {addr:x}"); Ok(()) } - fn write_word(&self, page: PageNum, offset: u16, value: Word) -> Result<(), ExceptionType> { - eprintln!( - "Wrote Word {value:08x} to Debug-Out page {page}, offset {offset} (byte {})", - offset * 4 - ); + fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryExceptionType> { + eprintln!("Wrote Word {value:08x} to Debug-Out address {addr:x}"); Ok(()) } - fn write_hword(&self, page: PageNum, offset: u16, value: HWord) -> Result<(), ExceptionType> { - eprintln!( - "Wrote HWord {value:04x} to Debug-Out page {page}, offset {offset} (byte {})", - offset * 2 - ); + fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryExceptionType> { + eprintln!("Wrote HWord {value:04x} to Debug-Out address {addr:x}"); Ok(()) } - fn write_byte(&self, page: PageNum, offset: u16, value: Byte) -> Result<(), ExceptionType> { - eprintln!("Wrote Byte {value:02x} to Debug-Out page {page}, offset {offset}"); + fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> { + eprintln!("Wrote Byte {value:02x} to Debug-Out address {addr:x}"); Ok(()) } - - fn read_dword(&self, _page: PageNum, _offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) - } - - fn read_word(&self, _page: PageNum, _offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) - } - - fn read_hword(&self, _page: PageNum, _offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) - } - - fn read_byte(&self, _page: PageNum, _offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) - } - - fn get_atomic_word( - &self, - _page: PageNum, - _offset: u16, - ) -> Result<&std::sync::atomic::AtomicU32, ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) - } - - fn get_atomic_dword( - &self, - _page: PageNum, - _offset: u16, - ) -> Result<&std::sync::atomic::AtomicU64, ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) - } } diff --git a/src/mem.rs b/src/mem.rs index 5576905..45ee356 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -12,184 +12,171 @@ use std::sync::{ use memmap2::MmapMut; use crate::{ - consts::{Byte, DWord, HWord, Word}, - exceptions::ExceptionType, + consts::{Addr, Byte, DWord, HWord, Word}, + exceptions::MemoryExceptionType, }; pub type PageNum = usize; -const PAGE_SIZE: usize = 4096; +pub const RAM_START: Addr = 0x8000_0000; #[derive(Clone)] pub struct MemConfig { pub ram: Arc, - pub ram_start: PageNum, - pub devices: Box<[DeviceEntry]>, + pub mmio_root: MmioRoot, } impl MemConfig { - #[allow(clippy::needless_borrow)] - pub fn find_device_by_page(&self, page: PageNum) -> Option<&DeviceEntry> { - for entry in self.devices.iter() { - if page_in_range(page, entry.base, entry.size) { - return Some(&entry); - } - } - - None - } - - pub fn memory_mapping_type(&self, page: PageNum) -> Option { - if page_in_range(page, self.ram_start, self.ram.pages) { + pub fn memory_mapping_type(&self, addr: Addr) -> Option { + if addr >= RAM_START { Some(MemoryMappingType::RAM) } else { - self.find_device_by_page(page) - .map(|_x| MemoryMappingType::MMIO) + self.mmio_root + .get_device(addr) + .map(|_| MemoryMappingType::MMIO) } } - pub fn read_dword(&self, page: PageNum, offset: u16) -> Result { - if page_in_range(page, self.ram_start, self.ram.pages) { - self.ram.read_dword(page - self.ram_start, offset) + pub fn read_dword(&self, addr: Addr) -> Result { + if addr >= RAM_START { + self.ram.read_dword(addr - RAM_START) } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::LoadAccessFault)?; - - entry.interface.read_dword(page - entry.base, offset) - } - } - pub fn read_word(&self, page: PageNum, offset: u16) -> Result { - if page_in_range(page, self.ram_start, self.ram.pages) { - self.ram.read_word(page - self.ram_start, offset) - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::LoadAccessFault)?; - - entry.interface.read_word(page - entry.base, offset) - } - } - pub fn read_hword(&self, page: PageNum, offset: u16) -> Result { - if page_in_range(page, self.ram_start, self.ram.pages) { - self.ram.read_hword(page - self.ram_start, offset) - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::LoadAccessFault)?; - - entry.interface.read_hword(page - entry.base, offset) - } - } - pub fn read_byte(&self, page: PageNum, offset: u16) -> Result { - if page_in_range(page, self.ram_start, self.ram.pages) { - self.ram.read_byte(page - self.ram_start, offset) - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::LoadAccessFault)?; - - entry.interface.read_byte(page - entry.base, offset) - } - } - - pub fn write_dword( - &self, - page: PageNum, - offset: u16, - value: DWord, - ) -> Result<(), ExceptionType> { - if page_in_range(page, self.ram_start, self.ram.pages) { - self.ram.write_dword(page - self.ram_start, offset, value) - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::StoreAmoAccessFault)?; - entry - .interface - .write_dword(page - entry.base, offset, value) - } - } - pub fn write_word(&self, page: PageNum, offset: u16, value: Word) -> Result<(), ExceptionType> { - if page_in_range(page, self.ram_start, self.ram.pages) { - self.ram.write_word(page - self.ram_start, offset, value) - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::StoreAmoAccessFault)?; - entry.interface.write_word(page - entry.base, offset, value) - } - } - pub fn write_hword( - &self, - page: PageNum, - offset: u16, - value: HWord, - ) -> Result<(), ExceptionType> { - if page_in_range(page, self.ram_start, self.ram.pages) { - self.ram.write_hword(page - self.ram_start, offset, value) - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::StoreAmoAccessFault)?; - entry - .interface - .write_hword(page - entry.base, offset, value) - } - } - pub fn write_byte(&self, page: PageNum, offset: u16, value: Byte) -> Result<(), ExceptionType> { - if page_in_range(page, self.ram_start, self.ram.pages) { - self.ram.write_byte(page - self.ram_start, offset, value) - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::StoreAmoAccessFault)?; - entry.interface.write_byte(page - entry.base, offset, value) - } - } - - pub fn get_atomic_dword( - &self, - page: PageNum, - offset: u16, - ) -> Result<&AtomicU64, ExceptionType> { - if page_in_range(page, self.ram_start, self.ram.pages) { - debug_assert!(((offset * 8) as usize) < PAGE_SIZE); - let index = page * (PAGE_SIZE / 8) + (offset as usize); - unsafe { - self.ram - .buf_transmuted::() - .get(index) - .ok_or(ExceptionType::HardwareError) + if !addr.is_multiple_of(8) && self.mmio_root.crosses_boundary(addr, 8) { + return Err(MemoryExceptionType::AddressMisaligned); } - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::StoreAmoAccessFault)?; - entry.interface.get_atomic_dword(page - entry.base, offset) - } - } - pub fn get_atomic_word(&self, page: PageNum, offset: u16) -> Result<&AtomicU32, ExceptionType> { - if page_in_range(page, self.ram_start, self.ram.pages) { - debug_assert!(((offset * 4) as usize) < PAGE_SIZE); - let index = page * (PAGE_SIZE / 4) + (offset as usize); - unsafe { - self.ram - .buf_transmuted::() - .get(index) - .ok_or(ExceptionType::HardwareError) - } - } else { - let entry = self - .find_device_by_page(page) - .ok_or(ExceptionType::StoreAmoAccessFault)?; - entry.interface.get_atomic_word(page - entry.base, offset) - } - } -} + let (interface, addr) = self + .mmio_root + .get_device(addr) + .ok_or(MemoryExceptionType::AccessFault)?; -fn page_in_range(page: PageNum, start: PageNum, pages: PageNum) -> bool { - page >= start && page - start < pages + interface.read_dword(addr) + } + } + pub fn read_word(&self, addr: Addr) -> Result { + if addr >= RAM_START { + self.ram.read_word(addr - RAM_START) + } else { + if !addr.is_multiple_of(4) && self.mmio_root.crosses_boundary(addr, 4) { + return Err(MemoryExceptionType::AddressMisaligned); + } + let (interface, addr) = self + .mmio_root + .get_device(addr) + .ok_or(MemoryExceptionType::AccessFault)?; + + interface.read_word(addr) + } + } + pub fn read_hword(&self, addr: Addr) -> Result { + if addr >= RAM_START { + self.ram.read_hword(addr - RAM_START) + } else { + if !addr.is_multiple_of(2) && self.mmio_root.crosses_boundary(addr, 2) { + return Err(MemoryExceptionType::AddressMisaligned); + } + let (interface, addr) = self + .mmio_root + .get_device(addr) + .ok_or(MemoryExceptionType::AccessFault)?; + interface.read_hword(addr) + } + } + pub fn read_byte(&self, addr: Addr) -> Result { + if addr >= RAM_START { + self.ram.read_byte(addr - RAM_START) + } else { + let (interface, addr) = self + .mmio_root + .get_device(addr) + .ok_or(MemoryExceptionType::AccessFault)?; + interface.read_byte(addr) + } + } + + pub fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryExceptionType> { + if addr >= RAM_START { + self.ram.write_dword(addr - RAM_START, value) + } else { + if !addr.is_multiple_of(8) && self.mmio_root.crosses_boundary(addr, 8) { + return Err(MemoryExceptionType::AddressMisaligned); + } + let (interface, addr) = self + .mmio_root + .get_device(addr) + .ok_or(MemoryExceptionType::AccessFault)?; + interface.write_dword(addr, value) + } + } + pub fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryExceptionType> { + if addr >= RAM_START { + self.ram.write_word(addr - RAM_START, value) + } else { + if !addr.is_multiple_of(4) && self.mmio_root.crosses_boundary(addr, 4) { + return Err(MemoryExceptionType::AddressMisaligned); + } + let (interface, addr) = self + .mmio_root + .get_device(addr) + .ok_or(MemoryExceptionType::AccessFault)?; + interface.write_word(addr, value) + } + } + pub fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryExceptionType> { + if addr >= RAM_START { + self.ram.write_hword(addr - RAM_START, value) + } else { + if !addr.is_multiple_of(2) && self.mmio_root.crosses_boundary(addr, 2) { + return Err(MemoryExceptionType::AddressMisaligned); + } + let (interface, addr) = self + .mmio_root + .get_device(addr) + .ok_or(MemoryExceptionType::AccessFault)?; + interface.write_hword(addr, value) + } + } + pub fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> { + if addr >= RAM_START { + self.ram.write_byte(addr - RAM_START, value) + } else { + let (interface, addr) = self + .mmio_root + .get_device(addr) + .ok_or(MemoryExceptionType::AccessFault)?; + interface.write_byte(addr, value) + } + } + + pub fn get_atomic_dword(&self, addr: Addr) -> Result<&AtomicU64, MemoryExceptionType> { + if !addr.is_multiple_of(8) { + return Err(MemoryExceptionType::AddressMisaligned); + } + + let index = ((addr - RAM_START) / 8) as usize; + unsafe { + self.ram + .buf_transmuted::() + .get(index) + .ok_or(MemoryExceptionType::AccessFault) + } + } + pub fn get_atomic_word(&self, addr: Addr) -> Result<&AtomicU32, MemoryExceptionType> { + if !addr.is_multiple_of(4) { + return Err(MemoryExceptionType::AddressMisaligned); + } + + if addr < RAM_START { + return Err(MemoryExceptionType::AccessFault); + } + + let index = ((addr - RAM_START) / 4) as usize; + unsafe { + self.ram + .buf_transmuted::() + .get(index) + .ok_or(MemoryExceptionType::AccessFault) + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -200,17 +187,15 @@ pub enum MemoryMappingType { pub struct Ram { buf: MmapMut, - pages: PageNum, } #[cfg(target_endian = "big")] compile_error!("Current RAM implementation requires a little-endian host."); impl Ram { - pub fn try_new(pages: PageNum) -> Result { + pub fn try_new(size: usize) -> Result { Ok(Self { - buf: MmapMut::map_anon(pages * PAGE_SIZE)?, - pages, + buf: MmapMut::map_anon(size)?, }) } @@ -218,10 +203,6 @@ impl Ram { self.buf.as_mut() } - pub fn pages(&self) -> PageNum { - self.pages - } - /// # Safety /// Safe if T has a size divisible by page size (4kb) (or is known to have a size divisible by the full ram size) and you know that the RAM is made up of valid naturally aligned values of T #[inline] @@ -241,146 +222,277 @@ impl Ram { } #[inline] - pub fn read_dword(&self, page: PageNum, offset: u16) -> Result { - debug_assert!(((offset * 8) as usize) < PAGE_SIZE); - let index = page * (PAGE_SIZE / 8) + (offset as usize); + pub fn read_dword(&self, addr: Addr) -> Result { + if !addr.is_multiple_of(8) { + let high_word_addr = addr.wrapping_add(4); + + let low_word = self.read_byte(addr)?; + let high_word = self.read_byte(high_word_addr)?; + + return Ok((low_word as DWord) | (high_word as DWord) << 32); + } + + let index = (addr / 8) as usize; Ok(unsafe { self.buf_transmuted::() .get(index) - .ok_or(ExceptionType::LoadAccessFault) + .ok_or(MemoryExceptionType::AccessFault) }? .load(Relaxed)) } #[inline] - pub fn read_word(&self, page: PageNum, offset: u16) -> Result { - debug_assert!(((offset * 4) as usize) < PAGE_SIZE); - let index = page * (PAGE_SIZE / 4) + (offset as usize); + pub fn read_word(&self, addr: Addr) -> Result { + if !addr.is_multiple_of(4) { + let high_hword_addr = addr.wrapping_add(2); + + let low_hword = self.read_hword(addr)?; + let high_hword = self.read_hword(high_hword_addr)?; + + return Ok((low_hword as Word) | (high_hword as Word) << 16); + } + + let index = (addr / 4) as usize; Ok(unsafe { self.buf_transmuted::() .get(index) - .ok_or(ExceptionType::LoadAccessFault) + .ok_or(MemoryExceptionType::AccessFault) }? .load(Relaxed)) } #[inline] - pub fn read_hword(&self, page: PageNum, offset: u16) -> Result { - debug_assert!(((offset * 2) as usize) < PAGE_SIZE); - let index = page * (PAGE_SIZE / 2) + (offset as usize); + pub fn read_hword(&self, addr: Addr) -> Result { + if !addr.is_multiple_of(2) { + let high_byte_addr = addr.wrapping_add(1); + + let low_byte = self.read_byte(addr)?; + let high_byte = self.read_byte(high_byte_addr)?; + + return Ok((low_byte as HWord) | (high_byte as HWord) << 8); + } + + let index = (addr / 2) as usize; Ok(unsafe { self.buf_transmuted::() .get(index) - .ok_or(ExceptionType::LoadAccessFault) + .ok_or(MemoryExceptionType::AccessFault) }? .load(Relaxed)) } #[inline] - pub fn read_byte(&self, page: PageNum, offset: u16) -> Result { - debug_assert!((offset as usize) < PAGE_SIZE); - let index = page * PAGE_SIZE + (offset as usize); + pub fn read_byte(&self, addr: Addr) -> Result { Ok(self .buf_atomic() - .get(index) - .ok_or(ExceptionType::LoadAccessFault)? + .get(addr as usize) + .ok_or(MemoryExceptionType::AccessFault)? .load(Relaxed)) } #[inline] - pub fn write_dword( - &self, - page: PageNum, - offset: u16, - value: DWord, - ) -> Result<(), ExceptionType> { - debug_assert!(((offset * 8) as usize) < PAGE_SIZE); - let index = page * (PAGE_SIZE / 8) + (offset as usize); + pub fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryExceptionType> { + if !addr.is_multiple_of(8) { + let low_word = value as Word; + let high_word = (value >> 32) as Word; + + let high_word_address = addr.wrapping_add(4); + + self.write_word(addr, low_word)?; + self.write_word(high_word_address, high_word)?; + return Ok(()); + } + + let index = (addr / 8) as usize; unsafe { self.buf_transmuted::() .get(index) - .ok_or(ExceptionType::StoreAmoAccessFault) + .ok_or(MemoryExceptionType::AccessFault) }? .store(value, Relaxed); Ok(()) } #[inline] - pub fn write_word(&self, page: PageNum, offset: u16, value: Word) -> Result<(), ExceptionType> { - debug_assert!(((offset * 4) as usize) < PAGE_SIZE); - let index = page * (PAGE_SIZE / 4) + (offset as usize); + pub fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryExceptionType> { + if !addr.is_multiple_of(4) { + let low_hword = value as HWord; + let high_hword = (value >> 16) as HWord; + + let high_hword_address = addr.wrapping_add(2); + + self.write_hword(addr, low_hword)?; + self.write_hword(high_hword_address, high_hword)?; + return Ok(()); + } + + let index = (addr / 4) as usize; unsafe { self.buf_transmuted::() .get(index) - .ok_or(ExceptionType::StoreAmoAccessFault) + .ok_or(MemoryExceptionType::AccessFault) }? .store(value, Relaxed); Ok(()) } #[inline] - pub fn write_hword( - &self, - page: PageNum, - offset: u16, - value: HWord, - ) -> Result<(), ExceptionType> { - debug_assert!(((offset * 2) as usize) < PAGE_SIZE); - let index = page * (PAGE_SIZE / 2) + (offset as usize); + pub fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryExceptionType> { + if !addr.is_multiple_of(2) { + let low_byte = value as Byte; + let high_byte = (value >> 8) as Byte; + + let high_byte_address = addr.wrapping_add(1); + + self.write_byte(addr, low_byte)?; + self.write_byte(high_byte_address, high_byte)?; + return Ok(()); + } + + let index = (addr / 2) as usize; unsafe { self.buf_transmuted::() .get(index) - .ok_or(ExceptionType::StoreAmoAccessFault) + .ok_or(MemoryExceptionType::AccessFault) }? .store(value, Relaxed); Ok(()) } #[inline] - pub fn write_byte(&self, page: PageNum, offset: u16, value: Byte) -> Result<(), ExceptionType> { - debug_assert!((offset as usize) < PAGE_SIZE); - let index = page * PAGE_SIZE + (offset as usize); + pub fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> { self.buf_atomic() - .get(index) - .ok_or(ExceptionType::StoreAmoAccessFault)? + .get(addr as usize) + .ok_or(MemoryExceptionType::AccessFault)? .store(value, Relaxed); Ok(()) } } +pub const MMIO_SECOND_LEVEL_PAGE_SIZE: usize = 64 * 1024; +pub const MMIO_ROOT_PAGE_SIZE: usize = MMIO_SECOND_LEVEL_PAGE_SIZE * 64; + +const MMIO_ROOT_ENTRIES: usize = RAM_START as usize / MMIO_ROOT_PAGE_SIZE; +const MMIO_SECOND_LEVEL_ENTRIES: usize = MMIO_ROOT_PAGE_SIZE / MMIO_SECOND_LEVEL_PAGE_SIZE; + #[derive(Clone)] -pub struct DeviceEntry { - pub base: PageNum, - pub size: PageNum, - pub interface: Arc, +pub struct MmioRoot(Box<[Option; MMIO_ROOT_ENTRIES]>); + +impl MmioRoot { + pub fn insert(&mut self, base_addr: Addr, interface: Arc) { + assert!(base_addr.is_multiple_of(MMIO_SECOND_LEVEL_PAGE_SIZE as u64)); + assert!(base_addr < RAM_START); + + let page_id = base_addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE; + let root_page_id = page_id / MMIO_SECOND_LEVEL_ENTRIES; + let second_level_page_id = page_id % MMIO_SECOND_LEVEL_ENTRIES; + + let second_level = self.0[root_page_id].get_or_insert_default(); + + if let MmioSecondLevel::SubTable(t) = second_level { + t[second_level_page_id] = Some(interface); + } + } + + pub fn insert_full(&mut self, base_addr: Addr, interface: Arc) { + assert!(base_addr.is_multiple_of(MMIO_ROOT_PAGE_SIZE as u64)); + assert!(base_addr < RAM_START); + + let page_id = base_addr as usize / MMIO_ROOT_PAGE_SIZE; + + self.0[page_id] = Some(MmioSecondLevel::Interface(interface)); + } + + fn get_device(&self, addr: Addr) -> Option<(Arc, Addr)> { + debug_assert!(addr < RAM_START); + + let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE; + let root_page_id = page_id / MMIO_SECOND_LEVEL_ENTRIES; + + self.0[root_page_id] + .as_ref() + .and_then(|s| s.get_device(addr % MMIO_ROOT_PAGE_SIZE as Addr)) + } + + fn crosses_boundary(&self, addr: Addr, size: Addr) -> bool { + if addr >= RAM_START { + return false; + } + + if addr + size > RAM_START { + return true; + } + + let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE; + let root_page_id = page_id / MMIO_SECOND_LEVEL_ENTRIES; + let end = addr + size - 1; + + match self.0[root_page_id].as_ref() { + Some(s) => match s { + MmioSecondLevel::SubTable(_) => { + let end_page_id = end as usize / MMIO_SECOND_LEVEL_PAGE_SIZE; + page_id != end_page_id + } + MmioSecondLevel::Interface(_) => { + let end_root_page_id = end as usize / MMIO_ROOT_PAGE_SIZE; + root_page_id != end_root_page_id + } + }, + None => false, + } + } +} + +impl Default for MmioRoot { + fn default() -> Self { + Self(Box::new([(); MMIO_ROOT_ENTRIES].map(|_| None))) + } +} + +#[derive(Clone)] +enum MmioSecondLevel { + SubTable(Box<[Option>; MMIO_SECOND_LEVEL_ENTRIES]>), + Interface(Arc), +} + +impl MmioSecondLevel { + fn get_device(&self, addr: Addr) -> Option<(Arc, Addr)> { + let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE; + match self { + Self::SubTable(t) => t[page_id] + .as_ref() + .map(|i| (i.clone(), addr % MMIO_SECOND_LEVEL_PAGE_SIZE as Addr)), + + Self::Interface(i) => Some((i.clone(), addr)), + } + } +} + +impl Default for MmioSecondLevel { + fn default() -> Self { + Self::SubTable(Box::new([(); MMIO_SECOND_LEVEL_ENTRIES].map(|_| None))) + } } #[allow(unused_variables)] pub trait MemDeviceInterface { - fn write_dword(&self, page: PageNum, offset: u16, value: DWord) -> Result<(), ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) + fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryExceptionType> { + Err(MemoryExceptionType::AccessFault) } - fn write_word(&self, page: PageNum, offset: u16, value: Word) -> Result<(), ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) + fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryExceptionType> { + Err(MemoryExceptionType::AccessFault) } - fn write_hword(&self, page: PageNum, offset: u16, value: HWord) -> Result<(), ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) + fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryExceptionType> { + Err(MemoryExceptionType::AccessFault) } - fn write_byte(&self, page: PageNum, offset: u16, value: Byte) -> Result<(), ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) + fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> { + Err(MemoryExceptionType::AccessFault) } - fn read_dword(&self, page: PageNum, offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) + fn read_dword(&self, addr: Addr) -> Result { + Err(MemoryExceptionType::AccessFault) } - fn read_word(&self, page: PageNum, offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) + fn read_word(&self, addr: Addr) -> Result { + Err(MemoryExceptionType::AccessFault) } - fn read_hword(&self, page: PageNum, offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) + fn read_hword(&self, addr: Addr) -> Result { + Err(MemoryExceptionType::AccessFault) } - fn read_byte(&self, page: PageNum, offset: u16) -> Result { - Err(ExceptionType::LoadAccessFault) - } - - fn get_atomic_word(&self, page: PageNum, offset: u16) -> Result<&AtomicU32, ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) - } - fn get_atomic_dword(&self, page: PageNum, offset: u16) -> Result<&AtomicU64, ExceptionType> { - Err(ExceptionType::StoreAmoAccessFault) + fn read_byte(&self, addr: Addr) -> Result { + Err(MemoryExceptionType::AccessFault) } }