mod attrs;
mod dispatch;
mod flat;
mod region;
mod store;
#[cfg(test)]
mod tests;
pub use attrs::{AccessConstraints, MemAttrs, MemOps, MemResult, RequesterId};
pub use dispatch::{Dispatch, DispatchEntry, DispatchPolicy};
pub use flat::{EntryKind, FlatEntry, FlatLeaf, FlatTarget, FlatView};
pub use region::{
Alias, AliasId, CombinePolicy, Container, Mapping, MappingId, Region, RegionKind, RegionRef,
RomWrite,
};
pub use store::{DEFAULT_PAGE_BITS, RamStore, RomStore};
use crate::core::error::{BusError, Error};
use crate::core::sync::{LockRank, RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::core::value::{Endian, Width};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::sync::atomic::{AtomicU64, Ordering};
use flat::RebaseIndex;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum UnassignedAction {
#[default]
Fault,
ReadAsOnes,
ReadAsZeros,
OpenBus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub struct UnassignedPolicy {
pub action: UnassignedAction,
pub log: bool,
}
impl UnassignedPolicy {
pub const FAULT: UnassignedPolicy = UnassignedPolicy {
action: UnassignedAction::Fault,
log: false,
};
pub const ONES: UnassignedPolicy = UnassignedPolicy {
action: UnassignedAction::ReadAsOnes,
log: false,
};
pub const ZEROS: UnassignedPolicy = UnassignedPolicy {
action: UnassignedAction::ReadAsZeros,
log: false,
};
pub const OPEN_BUS: UnassignedPolicy = UnassignedPolicy {
action: UnassignedAction::OpenBus,
log: false,
};
#[must_use]
pub const fn logged(mut self) -> Self {
self.log = true;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct UnassignedLog {
pub count: u64,
pub last_addr: u64,
pub last_was_write: bool,
}
#[derive(Debug)]
struct Topology {
root: Vec<(MappingId, Mapping)>,
next_id: u64,
flat: FlatView,
dispatch: Option<Dispatch>,
rebase_index: RebaseIndex,
}
#[derive(Debug)]
pub struct AddressSpace {
name: String,
bits: u32,
endian: Endian,
unassigned: UnassignedPolicy,
combine: CombinePolicy,
dispatch_policy: DispatchPolicy,
topo: RwLock<Topology>,
generation: AtomicU64,
unassigned_count: AtomicU64,
unassigned_last: AtomicU64,
unassigned_last_write: AtomicU64,
}
impl AddressSpace {
#[must_use]
pub fn new(name: impl Into<String>, bits: u32) -> Self {
assert!(bits > 0 && bits <= 64, "address width out of range");
AddressSpace {
name: name.into(),
bits,
endian: Endian::Little,
unassigned: UnassignedPolicy::FAULT,
combine: CombinePolicy::Priority,
dispatch_policy: DispatchPolicy::Flat,
topo: RwLock::with_rank(
LockRank::TOPOLOGY,
Topology {
root: Vec::new(),
next_id: 1,
flat: FlatView::default(),
dispatch: None,
rebase_index: RebaseIndex::new(),
},
),
generation: AtomicU64::new(1),
unassigned_count: AtomicU64::new(0),
unassigned_last: AtomicU64::new(0),
unassigned_last_write: AtomicU64::new(0),
}
}
#[must_use]
pub fn with_unassigned(mut self, policy: UnassignedPolicy) -> Self {
self.unassigned = policy;
self
}
#[must_use]
pub fn with_endian(mut self, endian: Endian) -> Self {
self.endian = endian;
self
}
#[must_use]
pub fn with_combine(mut self, combine: CombinePolicy) -> Self {
self.combine = combine;
self
}
#[must_use]
pub fn with_dispatch(mut self, policy: DispatchPolicy) -> Self {
self.dispatch_policy = policy;
self
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
#[must_use]
pub fn bits(&self) -> u32 {
self.bits
}
#[inline]
#[must_use]
pub fn size(&self) -> u64 {
if self.bits >= 64 {
u64::MAX
} else {
1u64 << self.bits
}
}
#[inline]
#[must_use]
pub fn generation(&self) -> u64 {
self.generation.load(Ordering::Relaxed)
}
#[must_use]
pub fn unassigned_log(&self) -> UnassignedLog {
UnassignedLog {
count: self.unassigned_count.load(Ordering::Relaxed),
last_addr: self.unassigned_last.load(Ordering::Relaxed),
last_was_write: self.unassigned_last_write.load(Ordering::Relaxed) != 0,
}
}
#[must_use]
pub fn topology(&self) -> TopologyGuard<'_> {
TopologyGuard {
space: self,
topo: self.topo.write(),
}
}
#[must_use]
pub fn try_topology(&self) -> Option<TopologyGuard<'_>> {
Some(TopologyGuard {
space: self,
topo: self.topo.try_write()?,
})
}
#[must_use]
pub fn view(&self) -> SpaceView<'_> {
self.try_view()
.expect("address space is being retopologised; use `read`/`try_view` on an access path")
}
#[must_use]
pub fn try_view(&self) -> Option<SpaceView<'_>> {
Some(SpaceView {
space: self,
topo: self.topo.try_read()?,
})
}
pub fn rebase(&self, region: &RegionRef, offset: u64) -> Result<(), Error> {
self.try_view()
.ok_or(Error::Bus(BusError::Retry))?
.rebase(region, offset)
}
#[inline]
#[must_use]
pub fn locate(&self, addr: u64) -> Option<usize> {
self.view().locate(addr)
}
#[inline]
#[must_use]
pub fn endian_at(&self, addr: u64) -> Endian {
self.view().endian_at(addr)
}
#[inline]
pub fn read(&self, addr: u64, width: Width, attrs: MemAttrs) -> MemResult<u64> {
self.try_view()
.ok_or(BusError::Retry)?
.read(addr, width, attrs)
}
#[inline]
pub fn read_driven(&self, addr: u64, width: Width, attrs: MemAttrs) -> MemResult<(u64, bool)> {
self.try_view()
.ok_or(BusError::Retry)?
.read_driven(addr, width, attrs)
}
#[inline]
pub fn write(&self, addr: u64, width: Width, value: u64, attrs: MemAttrs) -> MemResult {
self.try_view()
.ok_or(BusError::Retry)?
.write(addr, width, value, attrs)
}
pub fn read_bytes(&self, addr: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
self.try_view()
.ok_or(BusError::Retry)?
.read_bytes(addr, dst, attrs)
}
pub fn write_bytes(&self, addr: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
self.try_view()
.ok_or(BusError::Retry)?
.write_bytes(addr, src, attrs)
}
fn note_unassigned(&self, addr: u64, is_write: bool, attrs: MemAttrs) {
if !self.unassigned.log || attrs.debug {
return;
}
self.unassigned_count.fetch_add(1, Ordering::Relaxed);
self.unassigned_last.store(addr, Ordering::Relaxed);
self.unassigned_last_write
.store(u64::from(is_write), Ordering::Relaxed);
}
fn unassigned_read(&self, addr: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
self.note_unassigned(addr, false, attrs);
match self.unassigned.action {
UnassignedAction::Fault => Err(BusError::Unassigned),
UnassignedAction::ReadAsOnes => {
dst.fill(0xff);
Ok(())
}
UnassignedAction::ReadAsZeros => {
dst.fill(0x00);
Ok(())
}
UnassignedAction::OpenBus => {
dst.fill(attrs.bus);
Ok(())
}
}
}
fn unassigned_write(&self, addr: u64, attrs: MemAttrs) -> MemResult {
self.note_unassigned(addr, true, attrs);
match self.unassigned.action {
UnassignedAction::Fault => Err(BusError::Unassigned),
UnassignedAction::ReadAsOnes
| UnassignedAction::ReadAsZeros
| UnassignedAction::OpenBus => Ok(()),
}
}
fn check_fits(&self, mapping: &Mapping) -> Result<(), Error> {
let fits = mapping
.base
.checked_add(mapping.region.len())
.is_some_and(|e| e <= self.size());
if !fits {
return Err(Error::Config {
at: self.name.clone(),
message: alloc::format!(
"region `{}` at {:#x} (+{:#x}) does not fit in a {}-bit space",
mapping.region.name(),
mapping.base,
mapping.region.len(),
self.bits
),
});
}
Ok(())
}
}
#[derive(Debug)]
pub struct SpaceView<'a> {
space: &'a AddressSpace,
topo: RwLockReadGuard<'a, Topology>,
}
impl SpaceView<'_> {
#[inline]
#[must_use]
pub fn space(&self) -> &AddressSpace {
self.space
}
#[inline]
#[must_use]
pub fn flat_view(&self) -> &FlatView {
&self.topo.flat
}
#[inline]
#[must_use]
pub fn dispatch(&self) -> Option<&Dispatch> {
self.topo.dispatch.as_ref()
}
pub fn mappings(&self) -> impl Iterator<Item = (MappingId, &Mapping)> {
self.topo.root.iter().map(|(id, m)| (*id, m))
}
#[inline]
#[must_use]
pub fn locate(&self, addr: u64) -> Option<usize> {
if let Some(d) = &self.topo.dispatch {
match d.lookup(addr) {
Some(DispatchEntry::Unassigned) => return None,
Some(DispatchEntry::Mapped(i) | DispatchEntry::Direct(i)) => {
return Some(i as usize);
}
Some(DispatchEntry::SubPage) | None => {}
}
}
self.topo.flat.find(addr)
}
#[inline]
#[must_use]
pub fn endian_at(&self, addr: u64) -> Endian {
self.locate(addr)
.and_then(|i| self.topo.flat.entry(i))
.map_or(self.space.endian, FlatEntry::endian)
}
pub fn rebase(&self, region: &RegionRef, offset: u64) -> Result<(), Error> {
let Some(alias) = region.as_alias() else {
return Err(Error::Config {
at: region.name().to_string(),
message: "not an alias".to_string(),
});
};
if !alias.is_rebasable() {
return Err(Error::Config {
at: region.name().to_string(),
message: "alias targets a container; sliding it is a retopology".to_string(),
});
}
let end = offset.checked_add(region.len());
if end.is_none_or(|e| e > alias.target().len()) {
return Err(Error::Config {
at: region.name().to_string(),
message: alloc::format!(
"offset {offset:#x} (+{:#x}) runs off the end of `{}`",
region.len(),
alias.target().name()
),
});
}
alias.cell().store(offset, Ordering::Relaxed);
self.topo.flat.rebase(&self.topo.rebase_index, alias.id());
Ok(())
}
#[inline]
pub fn read(&self, addr: u64, width: Width, attrs: MemAttrs) -> MemResult<u64> {
let n = width.bytes() as usize;
let mut buf = [0u8; 8];
let endian = self.read_span(addr, &mut buf[..n], attrs, Some(width))?;
endian.load(&buf[..n], width)
}
pub fn read_driven(&self, addr: u64, width: Width, attrs: MemAttrs) -> MemResult<(u64, bool)> {
let n = width.bytes() as usize;
let mut buf = [0u8; 8];
let mut driven = true;
let endian = self.read_span_driven(addr, &mut buf[..n], attrs, Some(width), &mut driven)?;
Ok((endian.load(&buf[..n], width)?, driven))
}
#[inline]
pub fn write(&self, addr: u64, width: Width, value: u64, attrs: MemAttrs) -> MemResult {
let n = width.bytes() as usize;
let mut buf = [0u8; 8];
self.endian_at(addr).store(&mut buf[..n], width, value)?;
self.write_span(addr, &buf[..n], attrs, Some(width))
}
pub fn read_bytes(&self, addr: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
self.read_span(addr, dst, attrs, None).map(|_| ())
}
pub fn write_bytes(&self, addr: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
self.write_span(addr, src, attrs, None)
}
fn gap_len(&self, addr: u64, max: u64) -> u64 {
let entries = self.topo.flat.entries();
let i = entries.partition_point(|e| e.start() <= addr);
match entries.get(i) {
Some(e) => (e.start() - addr).min(max),
None => max,
}
}
fn read_span(
&self,
addr: u64,
dst: &mut [u8],
attrs: MemAttrs,
width: Option<Width>,
) -> MemResult<Endian> {
let mut driven = true;
self.read_span_driven(addr, dst, attrs, width, &mut driven)
}
fn read_span_driven(
&self,
addr: u64,
dst: &mut [u8],
attrs: MemAttrs,
width: Option<Width>,
driven: &mut bool,
) -> MemResult<Endian> {
let total = dst.len() as u64;
if total == 0 {
return Ok(self.space.endian);
}
addr.checked_add(total - 1).ok_or(BusError::BadAccess)?;
let mut endian = None;
let mut done = 0u64;
let mut committed = false;
while done < total {
let a = addr + done;
let remaining = total - done;
let (n, res) = match self.locate(a) {
Some(i) => {
let e = self.topo.flat.entry(i).expect("index came from locate");
let rel = a - e.start();
let n = e.run_len(rel).min(remaining);
if endian.is_none() {
endian = Some(e.endian());
}
let piece = &mut dst[usize_of(done)..usize_of(done + n)];
let w = if n == total { width } else { None };
*driven &= e.drives_data_bus();
(n, e.read(rel, piece, attrs, w))
}
None => {
let n = self.gap_len(a, remaining);
let piece = &mut dst[usize_of(done)..usize_of(done + n)];
*driven = false;
(n, self.space.unassigned_read(a, piece, attrs))
}
};
match res {
Err(BusError::Retry) if committed => return Err(BusError::BadAccess),
Err(e) => return Err(e),
Ok(()) => {}
}
committed = true;
done += n;
}
Ok(endian.unwrap_or(self.space.endian))
}
fn write_span(
&self,
addr: u64,
src: &[u8],
attrs: MemAttrs,
width: Option<Width>,
) -> MemResult {
let total = src.len() as u64;
if total == 0 {
return Ok(());
}
addr.checked_add(total - 1).ok_or(BusError::BadAccess)?;
let mut done = 0u64;
let mut committed = false;
while done < total {
let a = addr + done;
let remaining = total - done;
let (n, res) = match self.locate(a) {
Some(i) => {
let e = self.topo.flat.entry(i).expect("index came from locate");
let rel = a - e.start();
let n = e.run_len(rel).min(remaining);
let piece = &src[usize_of(done)..usize_of(done + n)];
let w = if n == total { width } else { None };
(n, e.write(rel, piece, attrs, w))
}
None => {
let n = self.gap_len(a, remaining);
(n, self.space.unassigned_write(a, attrs))
}
};
match res {
Err(BusError::Retry) if committed => return Err(BusError::BadAccess),
Err(e) => return Err(e),
Ok(()) => {}
}
committed = true;
done += n;
}
Ok(())
}
}
#[derive(Debug)]
pub struct TopologyGuard<'a> {
space: &'a AddressSpace,
topo: RwLockWriteGuard<'a, Topology>,
}
impl TopologyGuard<'_> {
#[inline]
#[must_use]
pub fn space(&self) -> &AddressSpace {
self.space
}
#[inline]
#[must_use]
pub fn flat_view(&self) -> &FlatView {
&self.topo.flat
}
#[inline]
#[must_use]
pub fn dispatch(&self) -> Option<&Dispatch> {
self.topo.dispatch.as_ref()
}
pub fn mappings(&self) -> impl Iterator<Item = (MappingId, &Mapping)> {
self.topo.root.iter().map(|(id, m)| (*id, m))
}
pub fn map(&mut self, region: impl Into<RegionRef>, base: u64) -> Result<MappingId, Error> {
self.map_with(Mapping::new(region, base))
}
pub fn map_with_priority(
&mut self,
region: impl Into<RegionRef>,
base: u64,
priority: i32,
) -> Result<MappingId, Error> {
self.map_with(Mapping::new(region, base).with_priority(priority))
}
pub fn map_with(&mut self, mapping: Mapping) -> Result<MappingId, Error> {
self.space.check_fits(&mapping)?;
let id = MappingId(self.topo.next_id);
self.topo.next_id += 1;
self.topo.root.push((id, mapping));
match self.rebuild() {
Ok(()) => Ok(id),
Err(e) => {
self.topo.root.pop();
let _ = self.rebuild();
Err(e)
}
}
}
pub fn unmap(&mut self, id: MappingId) -> Result<(), Error> {
let Some(pos) = self.topo.root.iter().position(|(i, _)| *i == id) else {
return Err(self.no_such_mapping(id));
};
self.topo.root.remove(pos);
self.rebuild()
}
pub fn remap(&mut self, id: MappingId, base: u64) -> Result<(), Error> {
let Some(pos) = self.topo.root.iter().position(|(i, _)| *i == id) else {
return Err(self.no_such_mapping(id));
};
let old = self.topo.root[pos].1.base;
self.topo.root[pos].1.base = base;
let mapping = self.topo.root[pos].1.clone();
if let Err(e) = self.space.check_fits(&mapping) {
self.topo.root[pos].1.base = old;
return Err(e);
}
self.rebuild()
}
pub fn rebuild(&mut self) -> Result<(), Error> {
let children: Vec<Mapping> = self.topo.root.iter().map(|(_, m)| m.clone()).collect();
let (flat, index) = FlatView::build(&children, self.space.size(), self.space.combine)?;
self.topo.dispatch = Dispatch::build(&flat, self.space.dispatch_policy);
self.topo.flat = flat;
self.topo.rebase_index = index;
self.space.generation.fetch_add(1, Ordering::Relaxed);
Ok(())
}
fn no_such_mapping(&self, id: MappingId) -> Error {
Error::Config {
at: self.space.name.clone(),
message: alloc::format!("no mapping {id:?} in this space"),
}
}
}
#[inline]
fn usize_of(v: u64) -> usize {
v as usize
}