use super::attrs::{AccessConstraints, MemAttrs, MemOps, MemResult, Perms};
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};
pub(super) 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: u64,
constraints: AccessConstraints,
perms: Perms,
}
impl FlatLeaf {
#[must_use]
pub fn target(&self) -> &FlatTarget {
&self.target
}
#[inline]
#[must_use]
pub fn perms(&self) -> Perms {
self.perms
}
#[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 != 0).then_some(self.period)
}
#[inline]
#[must_use]
pub fn offset_of(&self, rel: u64) -> u64 {
let raw = self.offset().wrapping_add(rel);
if self.period == 0 {
raw
} else {
raw % self.period
}
}
#[inline]
#[must_use]
pub fn run_len(&self, rel: u64) -> u64 {
if self.period == 0 {
u64::MAX
} else {
self.period - self.offset_of(rel) % self.period
}
}
#[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 {
if !self.perms.contains(Perms::READ) {
return Err(BusError::Protected);
}
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 {
if !self.perms.contains(Perms::WRITE) {
return Err(BusError::Protected);
}
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,
write_to: Option<alloc::boxed::Box<FlatLeaf>>,
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 {
self.write_to.is_none()
&& matches!(&self.kind, EntryKind::Single(l) if matches!(l.target, FlatTarget::Ram(_)))
}
#[inline]
#[must_use]
pub fn run_len(&self, rel: u64) -> u64 {
self.read_run_len(rel).min(self.write_run_len(rel))
}
#[inline]
#[must_use]
pub fn read_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)
}
#[inline]
#[must_use]
pub fn write_run_len(&self, rel: u64) -> u64 {
match &self.write_to {
Some(l) => self.len.saturating_sub(rel).min(l.run_len(rel)),
None => self.read_run_len(rel),
}
}
#[inline]
#[must_use]
pub fn write_to(&self) -> Option<&FlatLeaf> {
self.write_to.as_deref()
}
#[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 {
if let Some(l) = &self.write_to {
return l.write(rel, src, attrs, width);
}
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);
}
}
}
if let Some(l) = &self.write_to {
f(WRITE_SIDE, l);
}
}
fn leaf_at(&self, index: usize) -> Option<&FlatLeaf> {
if index == WRITE_SIDE {
return self.write_to.as_deref();
}
match &self.kind {
EntryKind::Single(l) if index == 0 => Some(l),
EntryKind::Single(_) => None,
EntryKind::Combine { members, .. } => members.get(index),
}
}
}
const WRITE_SIDE: usize = u32::MAX as usize;
impl FlatEntry {
fn from_piece(p: Piece) -> FlatEntry {
let mut leaves = p.leaves.into_iter();
let (kind, write_to) = match (p.directed, leaves.len()) {
(true, 2) => {
let read = leaves.next().expect("len 2").into_leaf();
let write = leaves.next().expect("len 2").into_leaf();
(EntryKind::Single(read), Some(alloc::boxed::Box::new(write)))
}
(_, 1) => (
EntryKind::Single(leaves.next().expect("len 1").into_leaf()),
None,
),
_ => (
EntryKind::Combine {
policy: p.combine,
members: leaves.map(LeafSpec::into_leaf).collect(),
},
None,
),
};
FlatEntry {
start: p.start,
len: p.len,
kind,
write_to,
conflicts: AtomicU64::new(0),
}
}
}
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,
Descent {
depth: 0,
perms: Perms::RWX,
},
&mut pieces,
)?;
let entries: Vec<FlatEntry> = pieces.into_iter().map(FlatEntry::from_piece).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: u64,
constraints: AccessConstraints,
perms: Perms,
}
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,
perms: self.perms,
}
}
fn continues(&self, next: &LeafSpec, len: u64) -> bool {
if self.period != 0 || next.period != 0 {
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.perms == next.perms
&& 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,
directed: bool,
}
#[derive(Debug, Clone)]
struct Cand {
start: u64,
len: u64,
priority: i32,
seq: usize,
leaves: Vec<LeafSpec>,
combine: CombinePolicy,
directed: bool,
}
impl Cand {
fn end(&self) -> u64 {
self.start.saturating_add(self.len)
}
fn answers(&self, want: Perms) -> bool {
self.leaves.iter().any(|l| l.perms.contains(want))
}
}
#[derive(Debug, Clone, Copy)]
struct Descent {
depth: u32,
perms: Perms,
}
impl Descent {
fn into_child(self, perms: Perms) -> Descent {
Descent {
depth: self.depth + 1,
perms: self.perms.intersect(perms),
}
}
}
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,
d: Descent,
out: &mut Vec<Piece>,
) -> Result<(), Error> {
let perms = d.perms;
if d.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: 0,
constraints,
perms,
}],
combine: CombinePolicy::Priority,
directed: false,
}),
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: 0,
constraints,
perms,
}],
combine: CombinePolicy::Priority,
directed: false,
}),
RegionKind::Io(ops) => out.push(Piece {
start: 0,
len,
leaves: alloc::vec![LeafSpec {
target: FlatTarget::Io(ops.clone()),
fixed: off,
terms: Vec::new(),
period: 0,
constraints,
perms,
}],
combine: CombinePolicy::Priority,
directed: false,
}),
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,
constraints,
perms,
}],
combine: CombinePolicy::Priority,
directed: false,
});
}
RegionKind::Alias(alias) => {
let cur = alias.offset();
let mut sub = Vec::new();
resolve_region(
alias.target(),
cur.wrapping_add(off),
len,
d.into_child(Perms::RWX),
&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(),
d,
out,
)?;
}
}
Ok(())
}
fn resolve_children(
children: &[Mapping],
limit: u64,
off: u64,
len: u64,
combine: CombinePolicy,
d: Descent,
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,
d.into_child(m.perms),
&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,
directed: p.directed,
});
}
}
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.end());
}
bounds.sort_unstable();
bounds.dedup();
let mut arrivals: Vec<usize> = (0..cands.len()).collect();
arrivals.sort_by_key(|&i| cands[i].start);
let mut arrived = 0usize;
let mut active: Vec<usize> = Vec::new();
let mut pieces: Vec<Piece> = Vec::new();
for w in bounds.windows(2) {
let (a, b) = (w[0], w[1]);
while arrived < arrivals.len() && cands[arrivals[arrived]].start <= a {
let i = arrivals[arrived];
arrived += 1;
let rank = |j: usize| {
use core::cmp::Reverse;
(
Reverse(cands[j].priority),
Reverse(cands[j].seq),
Reverse(j),
)
};
let at = active.partition_point(|&j| rank(j) < rank(i));
active.insert(at, i);
}
active.retain(|&i| cands[i].end() >= b);
if active.is_empty() {
continue;
}
let winner = |want: Perms| active.iter().copied().find(|&i| cands[i].answers(want));
let (read_win, write_win) = (winner(Perms::READ), winner(Perms::WRITE));
let cut = |i: usize| {
let c = &cands[i];
c.leaves.iter().map(move |leaf| {
let mut leaf = leaf.clone();
leaf.fixed = leaf.fixed.wrapping_add(a - c.start);
leaf
})
};
let combining = !matches!(combine, CombinePolicy::Priority);
let split = !combining
&& match (read_win, write_win) {
(Some(r), Some(w)) => {
r != w && cands[r].leaves.len() == 1 && cands[w].leaves.len() == 1
}
_ => false,
};
let (leaves, policy, directed) = if combining {
let leaves: Vec<LeafSpec> = active.iter().copied().flat_map(cut).collect();
(leaves, combine, false)
} else if split {
let (r, w) = (read_win.expect("split"), write_win.expect("split"));
let mut leaves: Vec<LeafSpec> = cut(r).collect();
leaves.extend(cut(w));
(leaves, CombinePolicy::Priority, true)
} else {
let i = read_win.or(write_win).unwrap_or(active[0]);
(cut(i).collect(), cands[i].combine, cands[i].directed)
};
let piece = Piece {
start: a,
len: b - a,
leaves,
combine: policy,
directed,
};
match pieces.last_mut() {
Some(prev)
if prev.start.wrapping_add(prev.len) == piece.start
&& prev.combine == piece.combine
&& prev.directed == piece.directed
&& 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);
}