use super::attrs::{AccessConstraints, MemOps};
use super::store::{RamStore, RomStore};
use crate::core::error::Error;
use crate::core::value::Endian;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicU64, Ordering};
pub type RegionRef = Arc<Region>;
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AliasId(pub u64);
static NEXT_ALIAS_ID: AtomicU64 = AtomicU64::new(1);
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MappingId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum RomWrite {
#[default]
Ignore,
Fault,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum CombinePolicy {
#[default]
Priority,
WiredOr,
WiredAnd,
Conflict,
}
#[derive(Debug)]
pub struct Alias {
target: RegionRef,
offset: Arc<AtomicU64>,
id: AliasId,
rebasable: bool,
repeat: bool,
}
impl Alias {
#[must_use]
pub fn target(&self) -> &RegionRef {
&self.target
}
#[inline]
#[must_use]
pub fn offset(&self) -> u64 {
self.offset.load(Ordering::Relaxed)
}
#[must_use]
pub fn id(&self) -> AliasId {
self.id
}
#[must_use]
pub fn is_rebasable(&self) -> bool {
self.rebasable
}
#[must_use]
pub fn repeats(&self) -> bool {
self.repeat
}
#[must_use]
pub fn period(&self) -> Option<u64> {
if self.repeat && !self.target.is_empty() {
Some(self.target.len())
} else {
None
}
}
pub(super) fn cell(&self) -> &Arc<AtomicU64> {
&self.offset
}
}
#[derive(Debug, Clone)]
pub struct Mapping {
pub region: RegionRef,
pub base: u64,
pub priority: i32,
}
impl Mapping {
#[must_use]
pub fn new(region: impl Into<RegionRef>, base: u64) -> Self {
Mapping {
region: region.into(),
base,
priority: 0,
}
}
#[must_use]
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
#[must_use]
pub fn end(&self) -> u64 {
self.base.saturating_add(self.region.len())
}
}
#[derive(Debug)]
pub struct Container {
children: Vec<Mapping>,
combine: CombinePolicy,
}
impl Container {
#[must_use]
pub fn children(&self) -> &[Mapping] {
&self.children
}
#[must_use]
pub fn combine(&self) -> CombinePolicy {
self.combine
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum RegionKind {
Ram(Arc<RamStore>),
Rom {
store: Arc<RomStore>,
on_write: RomWrite,
},
Io(Arc<dyn MemOps>),
Alias(Alias),
Container(Container),
}
#[derive(Debug)]
pub struct Region {
name: String,
len: u64,
constraints: AccessConstraints,
kind: RegionKind,
}
#[derive(Debug)]
struct Split {
reads: Arc<dyn MemOps>,
writes: Arc<dyn MemOps>,
constraints: AccessConstraints,
}
impl MemOps for Split {
fn read(&self, offset: u64, dst: &mut [u8], attrs: super::attrs::MemAttrs) -> super::MemResult {
self.reads.read(offset, dst, attrs)
}
fn write(&self, offset: u64, src: &[u8], attrs: super::attrs::MemAttrs) -> super::MemResult {
self.writes.write(offset, src, attrs)
}
fn constraints(&self) -> AccessConstraints {
self.constraints
}
}
impl Region {
pub fn split(
name: impl Into<String>,
reads: impl Into<RegionRef>,
writes: impl Into<RegionRef>,
) -> Result<Self, Error> {
let name = name.into();
let reads = reads.into();
let writes = writes.into();
let ops = |side: &RegionRef, which: &str| match side.kind() {
RegionKind::Io(ops) => Ok(Arc::clone(ops)),
_ => Err(Error::Config {
at: name.clone(),
message: alloc::format!(
"the {which} side of a split must be a plain I/O region, and `{}` is not",
side.name()
),
}),
};
let read_ops = ops(&reads, "read")?;
let write_ops = ops(&writes, "write")?;
if reads.len() != writes.len() {
return Err(Error::Config {
at: name,
message: alloc::format!(
"a split's two sides must be the same size: `{}` is {:#x} bytes and `{}` is {:#x}",
reads.name(),
reads.len(),
writes.name(),
writes.len()
),
});
}
let constraints = reads.constraints;
if constraints.min != writes.constraints.min || constraints.max != writes.constraints.max {
return Err(Error::Config {
at: name,
message: alloc::format!(
"a split's two sides must accept the same access widths: `{}` and `{}` do not",
reads.name(),
writes.name()
),
});
}
let len = reads.len();
Ok(Region {
name,
len,
constraints,
kind: RegionKind::Io(Arc::new(Split {
reads: read_ops,
writes: write_ops,
constraints,
})),
})
}
#[must_use]
pub fn ram(name: impl Into<String>, store: Arc<RamStore>) -> Self {
let len = store.len();
Region {
name: name.into(),
len,
constraints: AccessConstraints::ANY,
kind: RegionKind::Ram(store),
}
}
#[must_use]
pub fn rom(name: impl Into<String>, store: Arc<RomStore>, on_write: RomWrite) -> Self {
let len = store.len();
Region {
name: name.into(),
len,
constraints: AccessConstraints::ANY,
kind: RegionKind::Rom { store, on_write },
}
}
#[must_use]
pub fn io(name: impl Into<String>, len: u64, ops: Arc<dyn MemOps>) -> Self {
let constraints = ops.constraints();
Region {
name: name.into(),
len,
constraints,
kind: RegionKind::Io(ops),
}
}
pub fn alias(
name: impl Into<String>,
target: impl Into<RegionRef>,
offset: u64,
len: u64,
) -> Result<Self, Error> {
let name = name.into();
let target = target.into();
let end = offset.checked_add(len).ok_or_else(|| Error::Config {
at: name.clone(),
message: "alias window overflows".to_string(),
})?;
if end > target.len() {
return Err(Error::Config {
at: name,
message: alloc::format!(
"alias window {offset:#x}..{end:#x} does not fit in target `{}` of {:#x} bytes",
target.name(),
target.len()
),
});
}
let rebasable = target.resolves_to_leaf();
let constraints = target.constraints;
Ok(Region {
name,
len,
constraints,
kind: RegionKind::Alias(Alias {
target,
offset: Arc::new(AtomicU64::new(offset)),
id: AliasId(NEXT_ALIAS_ID.fetch_add(1, Ordering::Relaxed)),
rebasable,
repeat: false,
}),
})
}
pub fn mirror(
name: impl Into<String>,
target: impl Into<RegionRef>,
len: u64,
) -> Result<Self, Error> {
let name = name.into();
let target = target.into();
if target.is_empty() {
return Err(Error::Config {
at: name,
message: "cannot mirror a zero-sized region".to_string(),
});
}
if !matches!(
target.kind(),
RegionKind::Ram(_) | RegionKind::Rom { .. } | RegionKind::Io(_)
) {
return Err(Error::Config {
at: name,
message: "a repeating window's target must be RAM, ROM, or I/O".to_string(),
});
}
let constraints = target.constraints;
Ok(Region {
name,
len,
constraints,
kind: RegionKind::Alias(Alias {
target,
offset: Arc::new(AtomicU64::new(0)),
id: AliasId(NEXT_ALIAS_ID.fetch_add(1, Ordering::Relaxed)),
rebasable: false,
repeat: true,
}),
})
}
#[must_use]
pub fn container(name: impl Into<String>, len: u64, children: Vec<Mapping>) -> Self {
Self::container_with(name, len, children, CombinePolicy::Priority)
}
#[must_use]
pub fn container_with(
name: impl Into<String>,
len: u64,
children: Vec<Mapping>,
combine: CombinePolicy,
) -> Self {
Region {
name: name.into(),
len,
constraints: AccessConstraints::ANY,
kind: RegionKind::Container(Container { children, combine }),
}
}
#[must_use]
pub fn with_constraints(mut self, constraints: AccessConstraints) -> Self {
self.constraints = constraints;
self
}
#[must_use]
pub fn with_endian(mut self, endian: Endian) -> Self {
self.constraints = self.constraints.with_endian(endian);
self
}
#[must_use]
pub fn with_len(mut self, len: u64) -> Self {
self.len = len;
self
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
#[must_use]
pub fn len(&self) -> u64 {
self.len
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn kind(&self) -> &RegionKind {
&self.kind
}
#[inline]
#[must_use]
pub fn constraints(&self) -> AccessConstraints {
self.constraints
}
#[must_use]
pub fn as_alias(&self) -> Option<&Alias> {
match &self.kind {
RegionKind::Alias(a) => Some(a),
_ => None,
}
}
#[must_use]
pub fn as_container(&self) -> Option<&Container> {
match &self.kind {
RegionKind::Container(c) => Some(c),
_ => None,
}
}
fn resolves_to_leaf(&self) -> bool {
match &self.kind {
RegionKind::Ram(_) | RegionKind::Rom { .. } | RegionKind::Io(_) => true,
RegionKind::Alias(a) => a.rebasable && !a.repeat,
RegionKind::Container(_) => false,
}
}
}