587 lines
19 KiB
Rust
587 lines
19 KiB
Rust
// Copyright (c) 2025 taitep
|
|
// SPDX-License-Identifier: BSD-2-Clause
|
|
//
|
|
// 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::sync::{
|
|
Arc,
|
|
atomic::{AtomicU8, AtomicU16, AtomicU32, AtomicU64, Ordering::Relaxed},
|
|
};
|
|
|
|
use memmap2::MmapMut;
|
|
|
|
use crate::{
|
|
consts::{Addr, Byte, DWord, HWord, Word},
|
|
exceptions::{MemoryException, MemoryExceptionType},
|
|
};
|
|
|
|
pub type PageNum = usize;
|
|
|
|
pub const RAM_START: Addr = 0x8000_0000;
|
|
|
|
#[derive(Clone)]
|
|
pub struct MemConfig {
|
|
pub ram: Arc<Ram>,
|
|
pub mmio_root: MmioRoot,
|
|
}
|
|
|
|
impl MemConfig {
|
|
pub fn memory_mapping_type(&self, addr: Addr) -> Option<MemoryMappingType> {
|
|
if addr >= RAM_START {
|
|
Some(MemoryMappingType::RAM)
|
|
} else {
|
|
self.mmio_root
|
|
.get_device(addr)
|
|
.map(|_| MemoryMappingType::MMIO)
|
|
}
|
|
}
|
|
|
|
pub fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryException> {
|
|
if addr >= RAM_START {
|
|
self.ram.read_dword(addr - RAM_START)
|
|
} else {
|
|
if !addr.is_multiple_of(8) && self.mmio_root.crosses_boundary(addr, 8) {
|
|
return Err(MemoryException {
|
|
type_: MemoryExceptionType::AddressMisaligned,
|
|
addr,
|
|
});
|
|
}
|
|
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?;
|
|
|
|
interface.read_dword(addr)
|
|
}
|
|
}
|
|
pub fn read_word(&self, addr: Addr) -> Result<Word, MemoryException> {
|
|
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(MemoryException {
|
|
type_: MemoryExceptionType::AddressMisaligned,
|
|
addr,
|
|
});
|
|
}
|
|
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?;
|
|
|
|
interface.read_word(addr)
|
|
}
|
|
}
|
|
pub fn read_hword(&self, addr: Addr) -> Result<HWord, MemoryException> {
|
|
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(MemoryException {
|
|
type_: MemoryExceptionType::AddressMisaligned,
|
|
addr,
|
|
});
|
|
}
|
|
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?;
|
|
interface.read_hword(addr)
|
|
}
|
|
}
|
|
pub fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryException> {
|
|
if addr >= RAM_START {
|
|
self.ram.read_byte(addr - RAM_START)
|
|
} else {
|
|
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?;
|
|
interface.read_byte(addr)
|
|
}
|
|
}
|
|
|
|
pub fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryException> {
|
|
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(MemoryException {
|
|
type_: MemoryExceptionType::AddressMisaligned,
|
|
addr,
|
|
});
|
|
}
|
|
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?;
|
|
interface.write_dword(addr, value)
|
|
}
|
|
}
|
|
pub fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryException> {
|
|
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(MemoryException {
|
|
type_: MemoryExceptionType::AddressMisaligned,
|
|
addr,
|
|
});
|
|
}
|
|
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?;
|
|
interface.write_word(addr, value)
|
|
}
|
|
}
|
|
pub fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryException> {
|
|
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(MemoryException {
|
|
type_: MemoryExceptionType::AddressMisaligned,
|
|
addr,
|
|
});
|
|
}
|
|
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?;
|
|
interface.write_hword(addr, value)
|
|
}
|
|
}
|
|
pub fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryException> {
|
|
if addr >= RAM_START {
|
|
self.ram.write_byte(addr - RAM_START, value)
|
|
} else {
|
|
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?;
|
|
interface.write_byte(addr, value)
|
|
}
|
|
}
|
|
|
|
pub fn get_atomic_dword(&self, addr: Addr) -> Result<&AtomicU64, MemoryException> {
|
|
if !addr.is_multiple_of(8) {
|
|
return Err(MemoryException {
|
|
type_: MemoryExceptionType::AddressMisaligned,
|
|
addr,
|
|
});
|
|
}
|
|
|
|
let index = ((addr - RAM_START) / 8) as usize;
|
|
unsafe {
|
|
self.ram
|
|
.buf_transmuted::<AtomicU64>()
|
|
.get(index)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
}
|
|
pub fn get_atomic_word(&self, addr: Addr) -> Result<&AtomicU32, MemoryException> {
|
|
if !addr.is_multiple_of(4) {
|
|
return Err(MemoryException {
|
|
type_: MemoryExceptionType::AddressMisaligned,
|
|
addr,
|
|
});
|
|
}
|
|
|
|
if addr < RAM_START {
|
|
return Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
});
|
|
}
|
|
|
|
let index = ((addr - RAM_START) / 4) as usize;
|
|
unsafe {
|
|
self.ram
|
|
.buf_transmuted::<AtomicU32>()
|
|
.get(index)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum MemoryMappingType {
|
|
MMIO,
|
|
RAM,
|
|
}
|
|
|
|
pub struct Ram {
|
|
buf: MmapMut,
|
|
}
|
|
|
|
#[cfg(target_endian = "big")]
|
|
compile_error!("Current RAM implementation requires a little-endian host.");
|
|
|
|
impl Ram {
|
|
pub fn try_new(size: usize) -> Result<Self, std::io::Error> {
|
|
if !size.is_multiple_of(8) {
|
|
return Err(std::io::Error::other("ram size must be a multiple of 8"));
|
|
}
|
|
Ok(Self {
|
|
buf: MmapMut::map_anon(size)?,
|
|
})
|
|
}
|
|
|
|
pub fn buf_mut(&mut self) -> &mut [u8] {
|
|
self.buf.as_mut()
|
|
}
|
|
|
|
/// # Safety
|
|
/// Safe if the size of the memory in bytes is divisible by the size of T
|
|
/// Assuming try_new is used, RAM size is guaranteed to be a multiple of 8
|
|
/// meaning anything with size 1, 2, 4, or 8 bytes is valid.
|
|
/// It must also be known that the contents of RAM are made up of naturally
|
|
/// aligned valid instances of T.
|
|
#[inline]
|
|
unsafe fn buf_transmuted<T>(&self) -> &[T] {
|
|
debug_assert!(self.buf.len().is_multiple_of(std::mem::size_of::<T>()));
|
|
unsafe {
|
|
std::slice::from_raw_parts(
|
|
self.buf.as_ptr() as *const T,
|
|
self.buf.len() / std::mem::size_of::<T>(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn buf_atomic(&self) -> &[AtomicU8] {
|
|
unsafe { std::slice::from_raw_parts(self.buf.as_ptr() as *const AtomicU8, self.buf.len()) }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryException> {
|
|
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::<AtomicU64>()
|
|
.get(index)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}?
|
|
.load(Relaxed))
|
|
}
|
|
#[inline]
|
|
pub fn read_word(&self, addr: Addr) -> Result<Word, MemoryException> {
|
|
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::<AtomicU32>()
|
|
.get(index)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}?
|
|
.load(Relaxed))
|
|
}
|
|
#[inline]
|
|
pub fn read_hword(&self, addr: Addr) -> Result<HWord, MemoryException> {
|
|
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::<AtomicU16>()
|
|
.get(index)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}?
|
|
.load(Relaxed))
|
|
}
|
|
#[inline]
|
|
pub fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryException> {
|
|
Ok(self
|
|
.buf_atomic()
|
|
.get(addr as usize)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?
|
|
.load(Relaxed))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryException> {
|
|
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::<AtomicU64>()
|
|
.get(index)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}?
|
|
.store(value, Relaxed);
|
|
Ok(())
|
|
}
|
|
#[inline]
|
|
pub fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryException> {
|
|
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::<AtomicU32>()
|
|
.get(index)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}?
|
|
.store(value, Relaxed);
|
|
Ok(())
|
|
}
|
|
#[inline]
|
|
pub fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryException> {
|
|
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::<AtomicU16>()
|
|
.get(index)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}?
|
|
.store(value, Relaxed);
|
|
Ok(())
|
|
}
|
|
#[inline]
|
|
pub fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryException> {
|
|
self.buf_atomic()
|
|
.get(addr as usize)
|
|
.ok_or(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})?
|
|
.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 MmioRoot(Box<[Option<MmioSecondLevel>; MMIO_ROOT_ENTRIES]>);
|
|
|
|
impl MmioRoot {
|
|
pub fn insert(&mut self, base_addr: Addr, interface: Arc<dyn MemDeviceInterface>) {
|
|
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<dyn MemDeviceInterface>) {
|
|
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<dyn MemDeviceInterface>, 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<Arc<dyn MemDeviceInterface>>; MMIO_SECOND_LEVEL_ENTRIES]>),
|
|
Interface(Arc<dyn MemDeviceInterface>),
|
|
}
|
|
|
|
impl MmioSecondLevel {
|
|
fn get_device(&self, addr: Addr) -> Option<(Arc<dyn MemDeviceInterface>, 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, addr: Addr, value: DWord) -> Result<(), MemoryException> {
|
|
Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryException> {
|
|
Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryException> {
|
|
Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryException> {
|
|
Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
|
|
fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryException> {
|
|
Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
fn read_word(&self, addr: Addr) -> Result<Word, MemoryException> {
|
|
Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
fn read_hword(&self, addr: Addr) -> Result<HWord, MemoryException> {
|
|
Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryException> {
|
|
Err(MemoryException {
|
|
type_: MemoryExceptionType::AccessFault,
|
|
addr,
|
|
})
|
|
}
|
|
}
|