// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: Apache-2.0
//! The static description of a system on chip, and the [`Soc`] that gives its peripherals behavior.
//!
//! The description is what `rigless svd generate` emits from the vendor's SVD: a [`Cpu`], every
//! [`Peripheral`] instance at its address, and one [`RegisterSpec`] table per layout, with a
//! [`Layout`] enum naming each table's registers. A hand-written memory map of [`Region`]s
//! completes it. [`Soc::builder`] assembles all of that with the SoC's corrections, reset
//! values and write rules both, and its bound [`Device`]s, checks it once, and produces a
//! [`Soc`] the bus can read and write.
//!
//! A [`Soc`] is also its peripherals in virtual time: it holds their event queue, so
//! [`Soc::next_event`] is the instant the engine next has to stop at, [`Soc::advance_to`] fires
//! what is due in order, and [`Soc::drain_interrupts`] hands over what the models raised.
//! [`Soc::model`] and [`Soc::drive`] reach a bound model by name and type, which is how a test
//! feeds it an input the bus does not carry.
//!
//! Nothing here knows which processor family the SoC belongs to: addresses and values are
//! 64-bit, and a [`Cpu`] is a name, an FPU flag, and an interrupt count.
//!
//! A model raises its interrupt by position in its instance's table, `context.raise(UPDATE)` with `const UPDATE: usize = 0`, because a layout is shared by every instance built on it and only the instance knows its number; the `Soc` resolves the position and reports the change as the instance's name, the number, and the level. A position the table does not list is a bug in the model and panics with the instance's name. The `Soc` holds the queue and the changes: `next_event` is the instant the engine next has to stop at, `advance_to` fires every event due by an instant, earliest first, and `drain_interrupts` hands the session what the models set since the last drain.
#[cfg(feature = "stm32f427")]
pub mod stm32f427;
use std::fmt;
use crate::device::{Device, DeviceContext, ErasedDevice, InterruptChange, RegisterFile};
use crate::time::{EventQueue, VirtualNanos};
/// The processor core as the SVD describes it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cpu {
/// The core name as the vendor spells it.
pub name: &'static str,
/// Whether the core has a floating-point unit.
pub fpu: bool,
/// Number of external interrupt lines the interrupt controller has.
pub interrupt_lines: u16,
}
/// One peripheral instance at its address.
///
/// Instances the SVD derives from a sibling share that sibling's table, so every instance of one
/// kind points at one generated `REGISTERS`. An SoC module corrects an instance the vendor's file
/// got wrong with struct update syntax: `Peripheral { interrupts: &[87], ..generated::AN_INSTANCE }`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Peripheral {
/// The instance name as the vendor spells it, such as `SPI1`.
pub name: &'static str,
/// Absolute address of the first register.
pub base: u64,
/// Bytes the instance occupies on the bus.
pub size: u64,
/// Interrupt lines the instance raises, ascending.
pub interrupts: &'static [u16],
/// The register table, ordered by offset.
pub registers: &'static [RegisterSpec],
}
impl Peripheral {
/// Whether the address falls inside the instance.
pub const fn contains(&self, address: u64) -> bool {
address >= self.base && address < self.base + self.size
}
}
/// One register of a table: where it is, what it holds after reset, and how the bus may treat it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegisterSpec {
/// The register name as the vendor spells it.
pub name: &'static str,
/// Offset from the instance base.
pub offset: u32,
/// Width on the bus.
pub width: Width,
/// Value after reset.
pub reset: u64,
/// Which directions the bus allows.
pub access: Access,
/// What a read does to the register.
pub read: ReadAction,
/// What arithmetic a write performs before the value lands.
pub write: WriteModify,
/// The bit fields, ordered by position.
pub fields: &'static [FieldSpec],
}
impl RegisterSpec {
/// One past the last byte of the register, as an offset from the instance base.
pub const fn end(&self) -> u32 {
self.offset + self.width.bytes()
}
}
/// One bit field of a register.
///
/// The generator emits one constant per field, so a model reads a bit by name:
/// `cr1::SPE.is_set(value)`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldSpec {
/// The field name as the vendor spells it.
pub name: &'static str,
/// Position of the least significant bit.
pub bit: u8,
/// Width in bits.
pub width: u8,
/// Which directions the bus allows; the register's unless the SVD says otherwise.
pub access: Access,
/// What a read does to the field; the register's unless the SVD says otherwise.
pub read: ReadAction,
/// What arithmetic a write performs; the register's unless the SVD says otherwise.
pub write: WriteModify,
}
impl FieldSpec {
/// The bits the field occupies within the register.
pub const fn mask(&self) -> u64 {
if self.width >= Width::W64.bits() {
u64::MAX
} else {
((1u64 << self.width) - 1) << self.bit
}
}
/// The field's value within a register value.
pub const fn get(&self, register_value: u64) -> u64 {
(register_value & self.mask()) >> self.bit
}
/// The register value with this field replaced.
pub const fn set(&self, register_value: u64, field_value: u64) -> u64 {
(register_value & !self.mask()) | ((field_value << self.bit) & self.mask())
}
/// Whether any bit of the field is set in the register value.
pub const fn is_set(&self, register_value: u64) -> bool {
register_value & self.mask() != 0
}
}
/// Width of a register, or of a bus access, in bits.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Width {
/// Eight bits.
W8,
/// Sixteen bits.
W16,
/// Thirty-two bits.
W32,
/// Sixty-four bits.
W64,
}
impl Width {
/// The width in bits.
pub const fn bits(self) -> u8 {
match self {
Self::W8 => 8,
Self::W16 => 16,
Self::W32 => 32,
Self::W64 => 64,
}
}
/// The width in bytes.
pub const fn bytes(self) -> u32 {
self.bits() as u32 / 8
}
/// A mask of the bits a value of this width holds.
pub const fn mask(self) -> u64 {
match self {
Self::W64 => u64::MAX,
narrower => (1u64 << narrower.bits()) - 1,
}
}
}
/// Which directions the bus allows, as the CMSIS-SVD `access` element names them.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Access {
/// Reads return the value; writes are ignored.
ReadOnly,
/// Writes land; reads return zero.
WriteOnly,
/// Both directions.
ReadWrite,
/// Only the first write after reset lands; reads return zero.
WriteOnce,
/// Only the first write after reset lands; reads return the value.
ReadWriteOnce,
}
impl Access {
/// Whether the bus may read the value.
pub const fn is_readable(self) -> bool {
!matches!(self, Self::WriteOnly | Self::WriteOnce)
}
/// Whether the bus may write the value, at least once.
pub const fn is_writable(self) -> bool {
!matches!(self, Self::ReadOnly)
}
/// Whether only the first write after reset lands.
pub const fn is_write_once(self) -> bool {
matches!(self, Self::WriteOnce | Self::ReadWriteOnce)
}
}
/// What a read does to a register, as the CMSIS-SVD `readAction` element names it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReadAction {
/// Nothing.
None,
/// The register clears.
Clear,
/// The register sets.
Set,
/// The register changes in a way only the model knows.
Modify,
/// Another register changes.
ModifyExternal,
}
impl ReadAction {
/// The value the register holds after a read of `value`, within `mask`.
pub const fn apply(self, value: u64, mask: u64) -> u64 {
match self {
Self::Clear => value & !mask,
Self::Set => value | mask,
Self::None | Self::Modify | Self::ModifyExternal => value,
}
}
}
/// What arithmetic a write performs before the value lands, as the CMSIS-SVD `modifiedWriteValues` element names it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteModify {
/// The written value lands.
None,
/// Written ones clear their bits.
OneToClear,
/// Written ones set their bits.
OneToSet,
/// Written ones toggle their bits.
OneToToggle,
/// Written zeros clear their bits.
ZeroToClear,
/// Written zeros set their bits.
ZeroToSet,
/// Written zeros toggle their bits.
ZeroToToggle,
/// Any write clears the register.
Clear,
/// Any write sets the register.
Set,
/// Any write changes the register in a way only the model knows.
Modify,
}
impl WriteModify {
/// The value that lands when `written` arrives over `old`.
pub const fn apply(self, old: u64, written: u64) -> u64 {
match self {
Self::None => written,
Self::OneToClear => old & !written,
Self::OneToSet => old | written,
Self::OneToToggle => old ^ written,
Self::ZeroToClear => old & written,
Self::ZeroToSet => old | !written,
Self::ZeroToToggle => old ^ !written,
Self::Clear => 0,
Self::Set => u64::MAX,
Self::Modify => old,
}
}
/// The written value that leaves `old` unchanged, for the lanes a narrow access does not touch.
pub const fn neutral(self, old: u64) -> u64 {
match self {
Self::OneToClear | Self::OneToSet | Self::OneToToggle => 0,
Self::ZeroToClear | Self::ZeroToSet | Self::ZeroToToggle => u64::MAX,
Self::None | Self::Clear | Self::Set | Self::Modify => old,
}
}
}
/// A register table's names as a type, so a model matches on registers instead of strings.
///
/// The generator emits one enum implementing this per table; a variant's index is its row in
/// [`Layout::REGISTERS`].
pub trait Layout: Copy + fmt::Debug {
/// The table this layout names.
const REGISTERS: &'static [RegisterSpec];
/// The register at this row of the table.
fn from_index(index: usize) -> Option<Self>;
/// This register's row in the table.
fn index(self) -> usize;
/// This register's row of the table.
fn spec(self) -> &'static RegisterSpec {
&Self::REGISTERS[self.index()]
}
}
/// What a memory region holds.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegionKind {
/// Code and constant data, loaded from the firmware image.
Flash,
/// Read-write memory.
Ram,
/// Vendor read-only data such as the unique device ID.
System,
}
/// One memory region of the SoC.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Region {
/// The region name, such as `sram`.
pub name: &'static str,
/// Absolute address of the first byte.
pub base: u64,
/// Size in bytes.
pub size: u64,
/// What the region holds.
pub kind: RegionKind,
}
impl Region {
/// A flash region.
pub const fn flash(name: &'static str, base: u64, size: u64) -> Self {
Self {
name,
base,
size,
kind: RegionKind::Flash,
}
}
/// A RAM region.
pub const fn ram(name: &'static str, base: u64, size: u64) -> Self {
Self {
name,
base,
size,
kind: RegionKind::Ram,
}
}
/// A system memory region.
pub const fn system(name: &'static str, base: u64, size: u64) -> Self {
Self {
name,
base,
size,
kind: RegionKind::System,
}
}
}
/// Why an SoC description does not hold together.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// Two memory regions share addresses.
#[error("memory regions {lower} and {upper} overlap")]
OverlappingRegions {
/// The region with the lower base.
lower: &'static str,
/// The region that starts inside it.
upper: &'static str,
},
/// Two peripheral instances share addresses.
#[error("peripheral instances {lower} and {upper} overlap")]
OverlappingInstances {
/// The instance with the lower base.
lower: &'static str,
/// The instance that starts inside it.
upper: &'static str,
},
/// An instance raises an interrupt line the CPU does not have.
#[error("{instance} raises interrupt {line} but the CPU has {lines} lines")]
InterruptOutOfRange {
/// The instance.
instance: &'static str,
/// The interrupt line.
line: u16,
/// Lines the CPU has.
lines: u16,
},
/// A binding or correction names an instance the SoC does not have.
#[error("{instance} is not an instance of this SoC")]
UnknownInstance {
/// The name given.
instance: &'static str,
},
/// A correction names a register the instance does not have.
#[error("{instance} has no register {register}")]
UnknownRegister {
/// The instance.
instance: &'static str,
/// The name given.
register: &'static str,
},
/// A corrected reset value has bits beyond the register's width.
#[error("reset value {value:#x} does not fit {instance}.{register}")]
ResetTooWide {
/// The instance.
instance: &'static str,
/// The register.
register: &'static str,
/// The value given.
value: u64,
},
/// A device written for one register layout was bound to an instance of another.
#[error("{instance} does not have the register layout the device was written for")]
LayoutMismatch {
/// The instance.
instance: &'static str,
},
/// Two devices were bound to one instance.
#[error("{instance} is bound twice")]
BoundTwice {
/// The instance.
instance: &'static str,
},
}
/// Why a bus access could not complete.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Fault {
/// No register covers the address.
#[error("no register at {address:#x}")]
Unmapped {
/// The address accessed.
address: u64,
},
/// The access runs past the end of the register that covers the address.
#[error("a {}-bit access at {address:#x} runs past its register", width.bits())]
Misaligned {
/// The address accessed.
address: u64,
/// The width of the access.
width: Width,
},
}
/// One instance of the assembled SoC: its description, its values, and its device if one is bound.
struct Instance {
peripheral: Peripheral,
registers: RegisterFile,
device: Option<Box<dyn ErasedDevice + Send>>,
}
impl fmt::Debug for Instance {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Instance")
.field("peripheral", &self.peripheral.name)
.field("registers", &self.registers)
.field("bound", &self.device.is_some())
.finish()
}
}
/// A named span of addresses, for the overlap checks.
struct Extent {
name: &'static str,
base: u64,
size: u64,
}
impl Extent {
const fn end(&self) -> u64 {
self.base.saturating_add(self.size)
}
}
/// Assembles an SoC from its description, the corrections, and the devices, checking everything once in [`Builder::build`].
#[must_use = "a builder does nothing until built"]
pub struct Builder {
cpu: Cpu,
memory: &'static [Region],
peripherals: Vec<Peripheral>,
reset_corrections: Vec<ResetCorrection>,
write_corrections: Vec<WriteCorrection>,
devices: Vec<BoundDevice>,
}
struct ResetCorrection {
instance: &'static str,
register: &'static str,
value: u64,
}
struct WriteCorrection {
instance: &'static str,
register: &'static str,
rule: WriteModify,
}
struct BoundDevice {
instance: &'static str,
layout: &'static [RegisterSpec],
device: Box<dyn ErasedDevice + Send>,
}
impl fmt::Debug for Builder {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let bound: Vec<&str> = self.devices.iter().map(|bound| bound.instance).collect();
formatter
.debug_struct("Builder")
.field("cpu", &self.cpu)
.field("reset_corrections", &self.reset_corrections.len())
.field("write_corrections", &self.write_corrections.len())
.field("devices", &bound)
.finish_non_exhaustive()
}
}
impl Builder {
/// Overrides a reset value where the SVD disagrees with the vendor's documentation of the SoC.
pub fn reset(mut self, instance: &'static str, register: &'static str, value: u64) -> Self {
self.reset_corrections.push(ResetCorrection {
instance,
register,
value,
});
self
}
/// Overrides the write rule of a register and of every field in it where the SVD disagrees with the
/// vendor's documentation of the SoC, a status register whose flags clear on a written zero for one.
pub fn write_modify(
mut self,
instance: &'static str,
register: &'static str,
rule: WriteModify,
) -> Self {
self.write_corrections.push(WriteCorrection {
instance,
register,
rule,
});
self
}
/// Gives an instance behavior; the device's layout must be the instance's, which [`Builder::build`] checks.
pub fn bind<D: Device + Send + 'static>(mut self, instance: &'static str, device: D) -> Self {
self.devices.push(BoundDevice {
instance,
layout: D::Register::REGISTERS,
device: Box::new(device),
});
self
}
/// Checks the description and produces the SoC, naming what is at fault otherwise.
pub fn build(self) -> Result<Soc, Error> {
let regions = self.memory.iter().map(|region| Extent {
name: region.name,
base: region.base,
size: region.size,
});
if let Some((lower, upper)) = first_overlap(regions) {
return Err(Error::OverlappingRegions { lower, upper });
}
let spans = self.peripherals.iter().map(|peripheral| Extent {
name: peripheral.name,
base: peripheral.base,
size: peripheral.size,
});
if let Some((lower, upper)) = first_overlap(spans) {
return Err(Error::OverlappingInstances { lower, upper });
}
check_interrupts(&self.peripherals, self.cpu.interrupt_lines)?;
let mut instances: Vec<Instance> = self
.peripherals
.into_iter()
.map(|peripheral| Instance {
peripheral,
registers: RegisterFile::new(peripheral.registers),
device: None,
})
.collect();
instances.sort_by_key(|instance| instance.peripheral.base);
for correction in self.reset_corrections {
let instance = instance_named(&mut instances, correction.instance)?;
let (row, register) = instance
.peripheral
.registers
.iter()
.enumerate()
.find(|(_, register)| register.name == correction.register)
.ok_or(Error::UnknownRegister {
instance: correction.instance,
register: correction.register,
})?;
if correction.value & !register.width.mask() != 0 {
return Err(Error::ResetTooWide {
instance: correction.instance,
register: correction.register,
value: correction.value,
});
}
instance.registers.correct_reset(row, correction.value);
}
for correction in self.write_corrections {
let instance = instance_named(&mut instances, correction.instance)?;
let row = instance
.peripheral
.registers
.iter()
.position(|register| register.name == correction.register)
.ok_or(Error::UnknownRegister {
instance: correction.instance,
register: correction.register,
})?;
instance.registers.correct_write(row, correction.rule);
}
for bound in self.devices {
let instance = instance_named(&mut instances, bound.instance)?;
if !std::ptr::eq(bound.layout, instance.peripheral.registers) {
return Err(Error::LayoutMismatch {
instance: bound.instance,
});
}
if instance.device.replace(bound.device).is_some() {
return Err(Error::BoundTwice {
instance: bound.instance,
});
}
}
Ok(Soc {
cpu: self.cpu,
memory: self.memory,
instances,
events: EventQueue::default(),
interrupt_changes: Vec::new(),
})
}
}
fn instance_named<'a>(
instances: &'a mut [Instance],
name: &'static str,
) -> Result<&'a mut Instance, Error> {
instances
.iter_mut()
.find(|instance| instance.peripheral.name == name)
.ok_or(Error::UnknownInstance { instance: name })
}
/// The first two spans that share an address, by ascending base.
fn first_overlap(spans: impl Iterator<Item = Extent>) -> Option<(&'static str, &'static str)> {
let mut sorted: Vec<Extent> = spans.collect();
sorted.sort_by_key(|span| span.base);
sorted
.windows(2)
.find(|pair| pair[1].base < pair[0].end())
.map(|pair| (pair[0].name, pair[1].name))
}
fn check_interrupts(peripherals: &[Peripheral], lines: u16) -> Result<(), Error> {
for peripheral in peripherals {
if let Some(&line) = peripheral.interrupts.iter().find(|&&line| line >= lines) {
return Err(Error::InterruptOutOfRange {
instance: peripheral.name,
line,
lines,
});
}
}
Ok(())
}
/// A checked SoC: the CPU, the memory map, and every peripheral instance with its register file and its device, if any.
#[derive(Debug)]
pub struct Soc {
cpu: Cpu,
memory: &'static [Region],
instances: Vec<Instance>,
events: EventQueue,
interrupt_changes: Vec<InterruptChange>,
}
impl Soc {
/// Starts assembling an SoC from its CPU, memory map, and peripheral instances.
pub fn builder(
cpu: Cpu,
memory: &'static [Region],
peripherals: impl IntoIterator<Item = Peripheral>,
) -> Builder {
Builder {
cpu,
memory,
peripherals: peripherals.into_iter().collect(),
reset_corrections: Vec::new(),
write_corrections: Vec::new(),
devices: Vec::new(),
}
}
/// The CPU.
pub fn cpu(&self) -> Cpu {
self.cpu
}
/// The memory regions.
pub fn memory(&self) -> &'static [Region] {
self.memory
}
/// Every peripheral instance, by ascending base address.
pub fn peripherals(&self) -> impl Iterator<Item = &Peripheral> {
self.instances.iter().map(|instance| &instance.peripheral)
}
/// The instance with this name.
pub fn peripheral(&self, name: &str) -> Option<&Peripheral> {
self.peripherals()
.find(|peripheral| peripheral.name == name)
}
/// The register file of the instance with this name.
pub fn registers(&self, name: &str) -> Option<&RegisterFile> {
self.instances
.iter()
.find(|instance| instance.peripheral.name == name)
.map(|instance| &instance.registers)
}
/// The model bound to an instance, as its own type, with the instance's registers, for reading what it holds.
/// `None` when no model of that type is bound there.
pub fn model<D: Device + 'static>(
&mut self,
name: &str,
) -> Option<(&mut D, &mut RegisterFile)> {
let instance = self
.instances
.iter_mut()
.find(|instance| instance.peripheral.name == name)?;
let device = instance.device.as_mut()?.as_any_mut().downcast_mut::<D>()?;
Some((device, &mut instance.registers))
}
/// Drives an input of the model bound to an instance at `at`, an input the bus does not carry: the model gets
/// its registers and a context as on a bus access, so it may schedule and raise. `None` when no model of that
/// type is bound there.
pub fn drive<D: Device + 'static, T>(
&mut self,
at: VirtualNanos,
name: &str,
input: impl FnOnce(&mut D, &mut RegisterFile, &mut DeviceContext<'_>) -> T,
) -> Option<T> {
let index = self
.instances
.iter()
.position(|instance| instance.peripheral.name == name)?;
let (instance, mut context) = self.call(at, index);
let device = instance.device.as_mut()?.as_any_mut().downcast_mut::<D>()?;
Some(input(device, &mut instance.registers, &mut context))
}
/// A read from the firmware of `width` bits at `address`, which may be part of a wider register.
pub fn read(&mut self, at: VirtualNanos, address: u64, width: Width) -> Result<u64, Fault> {
let located = self.locate(address, width, Access::is_readable)?;
let (instance, mut context) = self.call(at, located.instance);
let answered = instance
.device
.as_mut()
.and_then(|device| device.read(located.row, &mut instance.registers, &mut context));
let value = answered.unwrap_or_else(|| instance.registers.bus_read(located.row));
Ok((value >> located.lane.shift) & width.mask())
}
/// A write over the bus at `at` on the virtual clock: the register file applies the rules, then the bound model
/// sees what landed.
pub fn write(
&mut self,
at: VirtualNanos,
address: u64,
width: Width,
value: u64,
) -> Result<(), Fault> {
let located = self.locate(address, width, Access::is_writable)?;
let (instance, mut context) = self.call(at, located.instance);
let register = &instance.peripheral.registers[located.row];
// A write-only register stores nothing, so the lanes a narrow write leaves alone carry no action.
let neutral = if register.access.is_readable() {
instance
.registers
.write_rule(located.row)
.neutral(instance.registers.get_row(located.row))
} else {
0
};
let untouched = neutral & !(width.mask() << located.lane.shift);
let written = untouched | ((value & width.mask()) << located.lane.shift);
instance.registers.bus_write(located.row, written);
if let Some(device) = instance.device.as_mut() {
device.write(located.row, written, &mut instance.registers, &mut context);
}
Ok(())
}
/// Fires every event due by `now`, earliest first, each to the instance that scheduled it; an event scheduled
/// meanwhile at `now` fires in the same pass.
pub fn advance_to(&mut self, now: VirtualNanos) {
while let Some((event, owner)) = self.events.pop_due(now) {
let (instance, mut context) = self.call(now, owner);
if let Some(device) = instance.device.as_mut() {
device.on_event(event, &mut instance.registers, &mut context);
}
}
}
/// The instant of the earliest pending event, which is when the engine next needs to stop.
#[must_use]
pub fn next_event(&self) -> Option<VirtualNanos> {
self.events.next_at()
}
/// Takes the interrupt levels the models set since the last drain, in the order they set them.
pub fn drain_interrupts(&mut self) -> impl Iterator<Item = InterruptChange> + '_ {
self.interrupt_changes.drain(..)
}
fn call(&mut self, at: VirtualNanos, index: usize) -> (&mut Instance, DeviceContext<'_>) {
let Self {
instances,
events,
interrupt_changes,
..
} = self;
let instance = &mut instances[index];
let context = DeviceContext::new(at, index, instance.peripheral, events, interrupt_changes);
(instance, context)
}
fn locate(
&self,
address: u64,
width: Width,
allows: fn(Access) -> bool,
) -> Result<Located, Fault> {
let candidates = self
.instances
.partition_point(|instance| instance.peripheral.base <= address);
let Some(index) = candidates.checked_sub(1) else {
return Err(Fault::Unmapped { address });
};
let instance = &self.instances[index];
if !instance.peripheral.contains(address) {
return Err(Fault::Unmapped { address });
}
let offset = u32::try_from(address - instance.peripheral.base)
.map_err(|_too_far| Fault::Unmapped { address })?;
let row = instance
.registers
.row_covering(offset, allows)
.ok_or(Fault::Unmapped { address })?;
let register = &instance.peripheral.registers[row];
let byte_in_register = offset - register.offset;
if byte_in_register + width.bytes() > register.width.bytes() {
return Err(Fault::Misaligned { address, width });
}
Ok(Located {
instance: index,
row,
lane: Lane {
shift: byte_in_register * u32::from(Width::W8.bits()),
},
})
}
}
/// Where one bus access lands: the instance, the register row, and the lane inside the register.
struct Located {
instance: usize,
row: usize,
lane: Lane,
}
struct Lane {
shift: u32,
}