#![expect(
unsafe_code,
reason = "the core keeps pointers into the regions, so they're reached through raw pointers only"
)]
use std::ptr::NonNull;
use crate::emulator::Alias;
use crate::soc::{Endian, Region, Width};
#[derive(Debug)]
struct Bytes {
base: u64,
aliases: Vec<u64>,
writable: bool,
contents: NonNull<u8>,
size: usize,
}
unsafe impl Send for Bytes {}
impl Drop for Bytes {
fn drop(&mut self) {
if self.size > 0 {
unsafe { std::alloc::dealloc(self.contents.as_ptr(), layout(self.size)) }
}
}
}
fn layout(size: usize) -> std::alloc::Layout {
std::alloc::Layout::from_size_align(size, PAGE_ALIGNMENT).expect("a region fits in memory")
}
pub(crate) const PAGE_ALIGNMENT: usize = 4096;
impl Bytes {
fn zeroed(base: u64, aliases: Vec<u64>, writable: bool, size: usize) -> Self {
let contents = if size == 0 {
NonNull::dangling()
} else {
NonNull::new(unsafe { std::alloc::alloc_zeroed(layout(size)) })
.unwrap_or_else(|| std::alloc::handle_alloc_error(layout(size)))
};
Self {
base,
aliases,
writable,
contents,
size,
}
}
fn offset(&self, address: u64, length: u64) -> Option<usize> {
let size = self.size as u64;
self.starts()
.find(|&base| {
address >= base
&& address
.checked_add(length)
.zip(base.checked_add(size))
.is_some_and(|(end, limit)| end <= limit)
})
.map(|base| usize::try_from(address - base).expect("inside a region in memory"))
}
fn starts(&self) -> impl Iterator<Item = u64> {
std::iter::once(self.base).chain(self.aliases.iter().copied())
}
fn at(&self, offset: usize) -> *mut u8 {
self.contents.as_ptr().wrapping_add(offset)
}
fn copy_out(&self, offset: usize, into: &mut [u8]) {
assert!(offset + into.len() <= self.size, "inside the region");
unsafe { std::ptr::copy_nonoverlapping(self.at(offset), into.as_mut_ptr(), into.len()) }
}
fn copy_in(&mut self, offset: usize, bytes: &[u8]) {
assert!(offset + bytes.len() <= self.size, "inside the region");
unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), self.at(offset), bytes.len()) }
}
}
#[derive(Debug)]
pub(crate) struct Memory {
regions: Vec<Bytes>,
endian: Endian,
}
#[derive(Debug)]
pub(crate) struct Mapped(Memory);
impl Mapped {
pub(crate) fn new(memory: Memory) -> Self {
Self(memory)
}
pub(crate) fn place(&mut self, address: u64, bytes: &[u8]) -> bool {
self.0.place(address, bytes)
}
pub(crate) fn write(&mut self, address: u64, width: Width, value: u64) -> bool {
self.0.write(address, width, value)
}
}
impl std::ops::Deref for Mapped {
type Target = Memory;
fn deref(&self) -> &Memory {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Mapping {
pub(crate) start: u64,
pub(crate) length: u64,
pub(crate) writable: bool,
}
pub(crate) fn to_target(value: u64, count: usize, endian: Endian) -> [u8; 8] {
let mut bytes = [0; 8];
match endian {
Endian::Little => bytes[..count].copy_from_slice(&value.to_le_bytes()[..count]),
Endian::Big => bytes[..count].copy_from_slice(&value.to_be_bytes()[8 - count..]),
}
bytes
}
pub(crate) fn from_target(bytes: &[u8], endian: Endian) -> u64 {
let mut word = [0; 8];
match endian {
Endian::Little => {
word[..bytes.len()].copy_from_slice(bytes);
u64::from_le_bytes(word)
}
Endian::Big => {
word[8 - bytes.len()..].copy_from_slice(bytes);
u64::from_be_bytes(word)
}
}
}
impl Memory {
pub(crate) fn new(map: &[Region], aliases: &[Alias], endian: Endian) -> Self {
Self {
regions: map
.iter()
.map(|region| {
Bytes::zeroed(
region.base,
aliases
.iter()
.filter(|alias| alias.region == region.name)
.map(|alias| alias.at)
.collect(),
region.kind.is_writable(),
usize::try_from(region.size).expect("a region fits in memory"),
)
})
.collect(),
endian,
}
}
pub(crate) fn endian(&self) -> Endian {
self.endian
}
#[cfg(not(emulator_absent))]
pub(crate) unsafe fn map_into(&self, processor: &mut dyn super::Processor) {
for region in &self.regions {
for start in region.starts() {
unsafe { processor.map(start, region.size as u64, region.at(0), region.writable) };
}
}
}
pub(crate) fn mappings(&self) -> Vec<Mapping> {
self.regions
.iter()
.flat_map(|region| {
region.starts().map(|start| Mapping {
start,
length: region.size as u64,
writable: region.writable,
})
})
.collect()
}
pub(crate) fn partial_page(&self, page: u64) -> Option<Mapping> {
self.mappings().into_iter().find(|mapping| {
mapping.length == 0
|| !mapping.start.is_multiple_of(page)
|| !mapping.length.is_multiple_of(page)
})
}
#[cfg(test)]
fn host(&self, address: u64) -> Option<*mut u8> {
self.regions.iter().find_map(|region| {
let offset = region.offset(address, 1)?;
Some(region.at(offset))
})
}
pub(crate) fn contents(&self, read_only: bool) -> Vec<(u64, Vec<u8>)> {
self.regions
.iter()
.filter(|region| read_only || region.writable)
.map(|region| {
let mut bytes = vec![0; region.size];
region.copy_out(0, &mut bytes);
(region.base, bytes)
})
.collect()
}
pub(crate) fn read_only(&self, address: u64) -> bool {
self.regions
.iter()
.any(|region| !region.writable && region.offset(address, 1).is_some())
}
pub(crate) fn read(&self, address: u64, width: Width) -> Option<u64> {
let bytes = usize::try_from(width.bytes()).expect("a width fits");
self.regions.iter().find_map(|region| {
let offset = region.offset(address, bytes as u64)?;
let mut word = [0; 8];
region.copy_out(offset, &mut word[..bytes]);
Some(from_target(&word[..bytes], self.endian))
})
}
pub(crate) fn write(&mut self, address: u64, width: Width, value: u64) -> bool {
let bytes = usize::try_from(width.bytes()).expect("a width fits");
self.place(address, &to_target(value, bytes, self.endian)[..bytes])
}
pub(crate) fn place(&mut self, address: u64, bytes: &[u8]) -> bool {
self.regions.iter_mut().any(|region| {
let Some(offset) = region.offset(address, bytes.len() as u64) else {
return false;
};
region.copy_in(offset, bytes);
true
})
}
pub(crate) fn writable(&self, address: u64) -> bool {
self.regions
.iter()
.any(|region| region.writable && region.offset(address, 1).is_some())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn memory() -> Memory {
Memory::new(
&[
Region::flash("flash", 0x0800_0000, 0x1000),
Region::ram("sram", 0x2000_0000, 0x100),
],
&[Alias {
region: "flash",
at: 0,
}],
Endian::Little,
)
}
#[test]
fn contents_copies_out_each_region_whole() {
let mut memory = memory();
assert!(memory.place(0x0800_0004, &[1, 2]));
assert!(memory.place(0x2000_00FE, &[3, 4]));
let writable = memory.contents(false);
assert_eq!(writable.len(), 1);
let (base, ram) = &writable[0];
assert_eq!((*base, ram.len()), (0x2000_0000, 0x100));
assert_eq!(&ram[0xFE..], [3, 4]);
let every = memory.contents(true);
assert_eq!(
every
.iter()
.map(|(base, bytes)| (*base, bytes.len()))
.collect::<Vec<_>>(),
[(0x0800_0000, 0x1000), (0x2000_0000, 0x100)]
);
assert_eq!(&every[0].1[4..6], [1, 2]);
}
#[test]
fn an_alias_is_the_same_bytes() {
let mut memory = memory();
assert!(memory.write(0x0800_0010, Width::W32, 0xDEAD_BEEF));
assert_eq!(memory.read(0x10, Width::W32), Some(0xDEAD_BEEF));
assert_eq!(memory.host(0x10), memory.host(0x0800_0010));
}
#[test]
fn every_region_is_mapped_at_its_address_and_its_aliases() {
let mappings = memory().mappings();
assert_eq!(
mappings,
[
Mapping {
start: 0x0800_0000,
length: 0x1000,
writable: false
},
Mapping {
start: 0,
length: 0x1000,
writable: false
},
Mapping {
start: 0x2000_0000,
length: 0x100,
writable: true
},
]
);
}
#[test]
fn a_region_that_ends_inside_a_page_is_found() {
assert_eq!(memory().partial_page(0x100), None);
assert_eq!(
memory().partial_page(0x400).map(|mapping| mapping.start),
Some(0x2000_0000)
);
}
#[test]
fn a_pointer_the_core_keeps_and_the_runtimes_own_access_share_the_bytes() {
let mut memory = memory();
let kept = memory.host(0x2000_0010).unwrap();
assert!(memory.place(0x2000_0010, &[1, 2]));
assert_eq!(unsafe { kept.read() }, 1);
unsafe { kept.write(9) };
assert_eq!(memory.read(0x2000_0010, Width::W8), Some(9));
assert!(memory.write(0x2000_0010, Width::W8, 7));
assert_eq!(unsafe { kept.read() }, 7);
}
#[test]
fn an_access_that_runs_off_a_region_is_nothing() {
let memory = memory();
assert_eq!(memory.read(0x2000_00FE, Width::W32), None);
assert_eq!(memory.read(0x3000_0000, Width::W8), None);
assert!(memory.read_only(0x10));
assert!(!memory.read_only(0x2000_0000));
assert!(memory.writable(0x2000_0000));
}
}