use super::attrs::{AccessConstraints, MemAttrs, MemOps, MemResult};
use super::region::{AliasId, CombinePolicy, Mapping, RegionKind, RegionRef, RomWrite};
use super::store::{RamStore, RomStore};
use crate::core::error::{BusError, Error};
use crate::core::value::{Endian, Width};
use alloc::collections::BTreeMap;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicU64, Ordering};
const MAX_DEPTH: u32 = 64;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum FlatTarget {
Ram(Arc<RamStore>),
Rom {
store: Arc<RomStore>,
on_write: RomWrite,
},
Io(Arc<dyn MemOps>),
}
#[derive(Debug)]
pub struct FlatLeaf {
target: FlatTarget,
offset: AtomicU64,
fixed: u64,
terms: Vec<(AliasId, Arc<AtomicU64>)>,
period: Option<u64>,
constraints: AccessConstraints,
}
impl FlatLeaf {
#[must_use]
pub fn target(&self) -> &FlatTarget {
&self.target
}
#[inline]
#[must_use]
pub fn offset(&self) -> u64 {
self.offset.load(Ordering::Relaxed)
}
#[inline]
#[must_use]
pub fn constraints(&self) -> AccessConstraints {
self.constraints
}
#[must_use]
pub fn is_rebasable(&self) -> bool {
!self.terms.is_empty()
}
#[inline]
#[must_use]
pub fn period(&self) -> Option<u64> {
self.period
}
#[inline]
#[must_use]
pub fn offset_of(&self, rel: u64) -> u64 {
let raw = self.offset().wrapping_add(rel);
match self.period {
Some(p) => raw % p,
None => raw,
}
}
#[inline]
#[must_use]
pub fn run_len(&self, rel: u64) -> u64 {
match self.period {
Some(p) => p - self.offset_of(rel) % p,
None => u64::MAX,
}
}
#[inline]
pub(super) fn recompute(&self) {
let mut v = self.fixed;
for (_, cell) in &self.terms {
v = v.wrapping_add(cell.load(Ordering::Relaxed));
}
self.offset.store(v, Ordering::Relaxed);
}
#[inline]
fn check(&self, off: u64, len: u64, width: Option<Width>, attrs: MemAttrs) -> MemResult {
match width {
Some(w) => self.constraints.check(off, w, attrs),
None => self.constraints.check_bulk(off, len, attrs),
}
}
#[inline]
pub fn read(
&self,
rel: u64,
dst: &mut [u8],
attrs: MemAttrs,
width: Option<Width>,
) -> MemResult {
let off = self.offset_of(rel);
self.check(off, dst.len() as u64, width, attrs)?;
match &self.target {
FlatTarget::Ram(s) => s.read_at(off, dst),
FlatTarget::Rom { store, .. } => store.read_at(off, dst),
FlatTarget::Io(ops) => ops.read(off, dst, attrs),
}
}
#[inline]
pub fn write(&self, rel: u64, src: &[u8], attrs: MemAttrs, width: Option<Width>) -> MemResult {
let off = self.offset_of(rel);
self.check(off, src.len() as u64, width, attrs)?;
match &self.target {
FlatTarget::Ram(s) => s.write_at(off, src),
FlatTarget::Rom { on_write, .. } => match on_write {
RomWrite::Ignore => Ok(()),
RomWrite::Fault => Err(BusError::BadAccess),
},
FlatTarget::Io(ops) => ops.write(off, src, attrs),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum EntryKind {
Single(FlatLeaf),
Combine {
policy: CombinePolicy,
members: Vec<FlatLeaf>,
},
}
#[derive(Debug)]
pub struct FlatEntry {
start: u64,
len: u64,
kind: EntryKind,
conflicts: AtomicU64,
}
impl FlatEntry {
#[inline]
#[must_use]
pub fn start(&self) -> u64 {
self.start
}
#[inline]
#[must_use]
pub fn len(&self) -> u64 {
self.len
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
#[must_use]
pub fn end(&self) -> u64 {
self.start.wrapping_add(self.len)
}
#[must_use]
pub fn kind(&self) -> &EntryKind {
&self.kind
}
#[inline]
#[must_use]
pub fn leaf(&self) -> Option<&FlatLeaf> {
match &self.kind {
EntryKind::Single(l) => Some(l),
EntryKind::Combine { .. } => None,
}
}
#[inline]
#[must_use]
pub fn endian(&self) -> Endian {
match &self.kind {
EntryKind::Single(l) => l.constraints.endian,
EntryKind::Combine { members, .. } => members
.first()
.map_or(Endian::Little, |m| m.constraints.endian),
}
}
#[inline]
#[must_use]
pub fn drives_data_bus(&self) -> bool {
match &self.kind {
EntryKind::Single(l) => l.constraints.drives_data_bus,
EntryKind::Combine { members, .. } => members
.first()
.is_none_or(|m| m.constraints.drives_data_bus),
}
}
#[inline]
#[must_use]
pub fn is_direct_ram(&self) -> bool {
matches!(&self.kind, EntryKind::Single(l) if matches!(l.target, FlatTarget::Ram(_)))
}
#[inline]
#[must_use]
pub fn run_len(&self, rel: u64) -> u64 {
let avail = self.len.saturating_sub(rel);
let bound = match &self.kind {
EntryKind::Single(l) => l.run_len(rel),
EntryKind::Combine { members, .. } => members
.iter()
.map(|m| m.run_len(rel))
.min()
.unwrap_or(u64::MAX),
};
avail.min(bound)
}
#[must_use]
pub fn conflicts(&self) -> u64 {
self.conflicts.load(Ordering::Relaxed)
}
pub fn read(
&self,
rel: u64,
dst: &mut [u8],
attrs: MemAttrs,
width: Option<Width>,
) -> MemResult {
match &self.kind {
EntryKind::Single(l) => l.read(rel, dst, attrs, width),
EntryKind::Combine { policy, members } => {
let fill = if matches!(policy, CombinePolicy::WiredAnd) {
0xffu8
} else {
0x00
};
dst.fill(fill);
let mut scratch = alloc::vec![0u8; dst.len()];
let mut responders = 0u32;
let mut first_err = None;
for m in members {
match m.read(rel, &mut scratch, attrs, width) {
Ok(()) => {
responders += 1;
for (d, s) in dst.iter_mut().zip(scratch.iter()) {
*d = match policy {
CombinePolicy::WiredAnd => *d & *s,
_ => *d | *s,
};
}
}
Err(BusError::Retry) if responders > 0 => {
return Err(BusError::BadAccess);
}
Err(e) => first_err = first_err.or(Some(e)),
}
}
if responders == 0 {
return Err(first_err.unwrap_or(BusError::Unassigned));
}
if responders > 1 && matches!(policy, CombinePolicy::Conflict) {
self.conflicts.fetch_add(1, Ordering::Relaxed);
}
Ok(())
}
}
}
pub fn write(&self, rel: u64, src: &[u8], attrs: MemAttrs, width: Option<Width>) -> MemResult {
match &self.kind {
EntryKind::Single(l) => l.write(rel, src, attrs, width),
EntryKind::Combine { members, .. } => {
let mut accepted = 0u32;
let mut first_err = None;
for m in members {
match m.write(rel, src, attrs, width) {
Ok(()) => accepted += 1,
Err(BusError::Retry) if accepted > 0 => return Err(BusError::BadAccess),
Err(e) => first_err = first_err.or(Some(e)),
}
}
if accepted == 0 {
return Err(first_err.unwrap_or(BusError::Unassigned));
}
Ok(())
}
}
}
fn for_each_leaf(&self, mut f: impl FnMut(usize, &FlatLeaf)) {
match &self.kind {
EntryKind::Single(l) => f(0, l),
EntryKind::Combine { members, .. } => {
for (i, m) in members.iter().enumerate() {
f(i, m);
}
}
}
}
fn leaf_at(&self, index: usize) -> Option<&FlatLeaf> {
match &self.kind {
EntryKind::Single(l) if index == 0 => Some(l),
EntryKind::Single(_) => None,
EntryKind::Combine { members, .. } => members.get(index),
}
}
}
pub(super) type RebaseIndex = BTreeMap<AliasId, Vec<(u32, u32)>>;
#[derive(Debug, Default)]
pub struct FlatView {
entries: Vec<FlatEntry>,
}
impl FlatView {
pub fn build(
children: &[Mapping],
limit: u64,
combine: CombinePolicy,
) -> Result<(FlatView, RebaseIndex), Error> {
let mut pieces = Vec::new();
resolve_children(children, limit, 0, limit, combine, 0, &mut pieces)?;
let entries: Vec<FlatEntry> = pieces
.into_iter()
.map(|p| FlatEntry {
start: p.start,
len: p.len,
kind: match p.leaves.len() {
1 => EntryKind::Single(p.leaves.into_iter().next().expect("len 1").into_leaf()),
_ => EntryKind::Combine {
policy: p.combine,
members: p.leaves.into_iter().map(LeafSpec::into_leaf).collect(),
},
},
conflicts: AtomicU64::new(0),
})
.collect();
let mut index: RebaseIndex = BTreeMap::new();
for (e, entry) in entries.iter().enumerate() {
entry.for_each_leaf(|m, leaf| {
for (id, _) in &leaf.terms {
index
.entry(*id)
.or_default()
.push((e as u32, u32::try_from(m).unwrap_or(u32::MAX)));
}
});
}
Ok((FlatView { entries }, index))
}
#[must_use]
pub fn entries(&self) -> &[FlatEntry] {
&self.entries
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[inline]
#[must_use]
pub fn find(&self, addr: u64) -> Option<usize> {
let i = self.entries.partition_point(|e| e.start <= addr);
if i == 0 {
return None;
}
let e = &self.entries[i - 1];
if addr < e.end() { Some(i - 1) } else { None }
}
#[inline]
#[must_use]
pub fn entry(&self, index: usize) -> Option<&FlatEntry> {
self.entries.get(index)
}
#[must_use]
pub fn extent(&self) -> u64 {
self.entries.last().map_or(0, FlatEntry::end)
}
pub(super) fn rebase(&self, index: &RebaseIndex, id: AliasId) {
let Some(targets) = index.get(&id) else {
return;
};
for (e, m) in targets {
if let Some(entry) = self.entries.get(*e as usize)
&& let Some(leaf) = entry.leaf_at(*m as usize)
{
leaf.recompute();
}
}
}
}
#[derive(Debug, Clone)]
struct LeafSpec {
target: FlatTarget,
fixed: u64,
terms: Vec<(AliasId, Arc<AtomicU64>)>,
period: Option<u64>,
constraints: AccessConstraints,
}
impl LeafSpec {
fn offset(&self) -> u64 {
let mut v = self.fixed;
for (_, cell) in &self.terms {
v = v.wrapping_add(cell.load(Ordering::Relaxed));
}
v
}
fn into_leaf(self) -> FlatLeaf {
let offset = AtomicU64::new(self.offset());
FlatLeaf {
target: self.target,
offset,
fixed: self.fixed,
terms: self.terms,
period: self.period,
constraints: self.constraints,
}
}
fn continues(&self, next: &LeafSpec, len: u64) -> bool {
if self.period.is_some() || next.period.is_some() {
return false;
}
let same_target = match (&self.target, &next.target) {
(FlatTarget::Ram(a), FlatTarget::Ram(b)) => Arc::ptr_eq(a, b),
(
FlatTarget::Rom {
store: a,
on_write: wa,
},
FlatTarget::Rom {
store: b,
on_write: wb,
},
) => Arc::ptr_eq(a, b) && wa == wb,
(FlatTarget::Io(a), FlatTarget::Io(b)) => Arc::ptr_eq(a, b),
_ => false,
};
same_target
&& self.constraints == next.constraints
&& self.terms.len() == next.terms.len()
&& self
.terms
.iter()
.zip(next.terms.iter())
.all(|(a, b)| a.0 == b.0 && Arc::ptr_eq(&a.1, &b.1))
&& self.fixed.wrapping_add(len) == next.fixed
}
}
#[derive(Debug, Clone)]
struct Piece {
start: u64,
len: u64,
leaves: Vec<LeafSpec>,
combine: CombinePolicy,
}
#[derive(Debug, Clone)]
struct Cand {
start: u64,
len: u64,
priority: i32,
seq: usize,
leaves: Vec<LeafSpec>,
combine: CombinePolicy,
}
fn leaf_target(region: &RegionRef) -> Option<FlatTarget> {
match region.kind() {
RegionKind::Ram(store) => Some(FlatTarget::Ram(store.clone())),
RegionKind::Rom { store, on_write } => Some(FlatTarget::Rom {
store: store.clone(),
on_write: *on_write,
}),
RegionKind::Io(ops) => Some(FlatTarget::Io(ops.clone())),
RegionKind::Alias(_) | RegionKind::Container(_) => None,
}
}
fn resolve_region(
region: &RegionRef,
off: u64,
len: u64,
depth: u32,
out: &mut Vec<Piece>,
) -> Result<(), Error> {
if depth > MAX_DEPTH {
return Err(Error::Config {
at: region.name().to_string(),
message: "region tree nests too deeply".to_string(),
});
}
let avail = region.len().saturating_sub(off);
let len = len.min(avail);
if len == 0 {
return Ok(());
}
let constraints = region.constraints();
match region.kind() {
RegionKind::Ram(store) => out.push(Piece {
start: 0,
len,
leaves: alloc::vec![LeafSpec {
target: FlatTarget::Ram(store.clone()),
fixed: off,
terms: Vec::new(),
period: None,
constraints,
}],
combine: CombinePolicy::Priority,
}),
RegionKind::Rom { store, on_write } => out.push(Piece {
start: 0,
len,
leaves: alloc::vec![LeafSpec {
target: FlatTarget::Rom {
store: store.clone(),
on_write: *on_write,
},
fixed: off,
terms: Vec::new(),
period: None,
constraints,
}],
combine: CombinePolicy::Priority,
}),
RegionKind::Io(ops) => out.push(Piece {
start: 0,
len,
leaves: alloc::vec![LeafSpec {
target: FlatTarget::Io(ops.clone()),
fixed: off,
terms: Vec::new(),
period: None,
constraints,
}],
combine: CombinePolicy::Priority,
}),
RegionKind::Alias(alias) if alias.repeats() => {
let period = alias.period().expect("a repeating alias has a period");
let target = leaf_target(alias.target()).ok_or_else(|| Error::Config {
at: region.name().to_string(),
message: "a repeating window's target must be a leaf".to_string(),
})?;
out.push(Piece {
start: 0,
len,
leaves: alloc::vec![LeafSpec {
target,
fixed: off % period,
terms: Vec::new(),
period: Some(period),
constraints,
}],
combine: CombinePolicy::Priority,
});
}
RegionKind::Alias(alias) => {
let cur = alias.offset();
let mut sub = Vec::new();
resolve_region(
alias.target(),
cur.wrapping_add(off),
len,
depth + 1,
&mut sub,
)?;
if constraints != alias.target().constraints() {
for piece in &mut sub {
for leaf in &mut piece.leaves {
leaf.constraints = constraints;
}
}
}
if alias.is_rebasable() {
for piece in &mut sub {
for leaf in &mut piece.leaves {
leaf.fixed = leaf.fixed.wrapping_sub(cur);
leaf.terms.push((alias.id(), alias.cell().clone()));
}
}
}
out.append(&mut sub);
}
RegionKind::Container(container) => {
resolve_children(
container.children(),
region.len(),
off,
len,
container.combine(),
depth,
out,
)?;
}
}
Ok(())
}
fn resolve_children(
children: &[Mapping],
limit: u64,
off: u64,
len: u64,
combine: CombinePolicy,
depth: u32,
out: &mut Vec<Piece>,
) -> Result<(), Error> {
let want_end = off.saturating_add(len);
let mut cands: Vec<Cand> = Vec::new();
for (seq, m) in children.iter().enumerate() {
let child_start = m.base;
let child_end = m.end().min(limit);
let a = child_start.max(off);
let b = child_end.min(want_end);
if a >= b {
continue;
}
let mut sub = Vec::new();
resolve_region(&m.region, a - child_start, b - a, depth + 1, &mut sub)?;
for p in sub {
cands.push(Cand {
start: a - off + p.start,
len: p.len,
priority: m.priority,
seq,
leaves: p.leaves,
combine: p.combine,
});
}
}
resolve_overlaps(cands, combine, out);
Ok(())
}
fn resolve_overlaps(cands: Vec<Cand>, combine: CombinePolicy, out: &mut Vec<Piece>) {
if cands.is_empty() {
return;
}
let mut bounds: Vec<u64> = Vec::with_capacity(cands.len() * 2);
for c in &cands {
bounds.push(c.start);
bounds.push(c.start.saturating_add(c.len));
}
bounds.sort_unstable();
bounds.dedup();
let mut pieces: Vec<Piece> = Vec::new();
for w in bounds.windows(2) {
let (a, b) = (w[0], w[1]);
let mut covering: Vec<&Cand> = cands
.iter()
.filter(|c| c.start <= a && c.start.saturating_add(c.len) >= b)
.collect();
if covering.is_empty() {
continue;
}
covering.sort_by(|x, y| y.priority.cmp(&x.priority).then_with(|| y.seq.cmp(&x.seq)));
let take = if matches!(combine, CombinePolicy::Priority) {
1
} else {
covering.len()
};
let leaves: Vec<LeafSpec> = covering[..take]
.iter()
.flat_map(|c| {
c.leaves.iter().map(move |leaf| {
let mut leaf = leaf.clone();
leaf.fixed = leaf.fixed.wrapping_add(a - c.start);
leaf
})
})
.collect();
let policy = if matches!(combine, CombinePolicy::Priority) {
covering[0].combine
} else {
combine
};
let piece = Piece {
start: a,
len: b - a,
leaves,
combine: policy,
};
match pieces.last_mut() {
Some(prev)
if prev.start.wrapping_add(prev.len) == piece.start
&& prev.combine == piece.combine
&& prev.leaves.len() == piece.leaves.len()
&& prev
.leaves
.iter()
.zip(piece.leaves.iter())
.all(|(x, y)| x.continues(y, prev.len)) =>
{
prev.len += piece.len;
}
_ => pieces.push(piece),
}
}
out.append(&mut pieces);
}