blob: b4318ee2706905ae74f7d77ae0fb9ee531cf50f3 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! Register-field mux mechanism: inline `FieldWrite`s coalesced to one RMW per register at compile time.
use crate::resource::FieldWrite;
use core::marker::PhantomData;
/// Fixed-size compile-time coalescing scratch; narrowed to the exact live count before apply, so it never reaches the binary.
pub(crate) const MAX_REGS: usize = 16;
/// Central batch of register writes, coalesced to one op per distinct offset: `[..len]` of `ops` live.
#[derive(Clone, Copy)]
pub struct RegBatch {
/// Register ops, one per distinct offset; only `[..len]` are valid.
ops: [FieldWrite; MAX_REGS],
/// Number of live ops.
len: usize,
}
impl RegBatch {
/// An empty accumulator.
#[must_use]
pub const fn new() -> Self {
Self {
ops: [FieldWrite {
offset: 0,
set: 0,
clr: 0,
}; MAX_REGS],
len: 0,
}
}
/// Coalesce one write into the batch, grouped by register offset via [`find`](Self::find).
const fn push(&mut self, w: FieldWrite) {
let j = self.find(w.offset);
if j == self.len {
assert!(self.len < MAX_REGS, "MAX_REGS too small");
self.ops[self.len] = w;
self.len += 1;
} else {
self.ops[j].coalesce(w);
}
}
///merges via `FieldWrite::coalesce`. Crate-private, can only be called by fns with pin token authority provided.
pub(crate) const fn append(&mut self, writes: &[FieldWrite]) {
let mut i = 0;
while i < writes.len() {
self.push(writes[i]);
i += 1;
}
}
/// Merge another batch's live ops in, coalescing per register — the tuple/handle-list fold step.
pub(crate) const fn append_batch(&mut self, other: &RegBatch) {
let mut i = 0;
while i < other.len {
self.push(other.ops[i]);
i += 1;
}
}
/// Live op count — the exact `N` [`into_ops`](Self::into_ops) narrows to so the fixed scratch tail never reaches the binary.
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
/// True if no route has been appended yet.
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
/// Consume the batch into its exact `[FieldWrite; N]` ops (no zeroed tail); const-panics unless `N == len`.
#[must_use]
pub const fn into_ops<const N: usize>(self) -> [FieldWrite; N] {
assert!(self.len == N, "into_ops: N must equal batch len");
let mut out = [FieldWrite {
offset: 0,
set: 0,
clr: 0,
}; N];
let mut k = 0;
while k < N {
out[k] = self.ops[k];
k += 1;
}
out
}
// HACK: the MAX_REGS-wide `ops` rides to rodata; `into_ops` trims it but needs generic_const_exprs.
/// The live ops as a tight slice — `[..len]` of the fixed scratch.
#[must_use]
pub const fn as_slice(&self) -> &[FieldWrite] {
self.ops.split_at(self.len).0
}
/// Index of `offset` in `ops[..len]`, or `len` if absent — group-by-register for [`append`].
const fn find(&self, offset: u32) -> usize {
let mut j = 0;
while j < self.len {
if self.ops[j].offset == offset {
return j;
}
j += 1;
}
self.len
}
}
impl Default for RegBatch {
fn default() -> Self {
Self::new()
}
}
/// A register block a coalesced mux batch can be applied to: one read + one write per offset.
/// A target impls these two methods once; `apply` replaces a per-offset match in the applier.
pub trait RegBlock {
/// Read the 32-bit register at `offset`.
fn read_reg(&self, offset: u32) -> u32;
/// Write `val` to the 32-bit register at `offset`.
fn write_reg(&self, offset: u32, val: u32);
}
/// Apply an exact-N mux batch to a block: one RMW (`reg = (reg & !clr) | set`) per register. Panic-free — a flat walk over the tight slice, no indexing.
pub fn apply(block: &impl RegBlock, batch: &[FieldWrite]) {
for op in batch {
block.write_reg(op.offset, (block.read_reg(op.offset) & !op.clr) | op.set);
}
}
/// Coalesce a list of per-pin routes into one batch (one RMW per register) — the compile-time fold.
#[must_use]
pub const fn coalesce_routes(routes: &[&[FieldWrite]]) -> RegBatch {
let mut b = RegBatch::new();
let mut i = 0;
while i < routes.len() {
b.append(routes[i]);
i += 1;
}
b
}
/// A bound handle (or tuple of handles) whose SCU mux routes are folded to one RMW per register at
/// compile time in [`COALESCED`](MuxRoutes::COALESCED); the applier just flushes that const.
pub trait MuxRoutes {
/// This handle's routes, coalesced to one op per register — folded per monomorphization at compile time.
const COALESCED: RegBatch;
/// Distinct-register count of [`COALESCED`](Self::COALESCED) — the exact array length to narrow to before apply.
const LEN: usize = Self::COALESCED.len();
}
/// Route a handle through a shared reference: the applier reads only the type, so a borrow folds identically — non-Copy owners route without being consumed.
impl<H: MuxRoutes> MuxRoutes for &H {
const COALESCED: RegBatch = H::COALESCED;
}
/// Impl [`MuxRoutes`] for every tuple arity by folding each member's `COALESCED` into one batch at
/// compile time; recurses on the tail, so one invocation covers all arities down to the 1-tuple.
macro_rules! impl_muxroutes_tuple {
() => {};
($head:ident $(, $tail:ident)*) => {
impl<$head: MuxRoutes $(, $tail: MuxRoutes)*> MuxRoutes for ($head, $($tail,)*) {
const COALESCED: RegBatch = {
let mut out = RegBatch::new();
out.append_batch(&$head::COALESCED);
$( out.append_batch(&$tail::COALESCED); )*
out
};
}
impl_muxroutes_tuple!($($tail),*);
};
}
// HACK: tuple arity caps pins-per-apply at this ident count (no variadic generics); widen the list if needed.
impl_muxroutes_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
/// A silicon-fixed singleton register block (e.g. the SCU): `BASE` names it, no pin token carries it.
///
/// # Safety
/// `BASE` must name a valid, `'static`, aligned register block usable for `u32` volatile access at every offset its driver touches.
#[allow(unsafe_code)]
pub unsafe trait Block {
/// The silicon-fixed base of this singleton register block.
const BASE: *const u8;
}
/// Confined MMIO applier over block `B`, phantom-typed so one block's applier can't stand in for another's.
pub struct Mmio<B> {
base: *const u8,
_block: PhantomData<B>,
}
impl<B> Clone for Mmio<B> {
fn clone(&self) -> Self {
*self
}
}
impl<B> Copy for Mmio<B> {}
impl<B> Mmio<B> {
/// HAL-internal ctor: base always rides on authored `DATA` or a `Block::BASE`, whose validity was promised upstream.
#[must_use]
pub(crate) const fn from_raw(base: *const u8) -> Self {
Self {
base,
_block: PhantomData,
}
}
/// Read the 32-bit register at byte `offset`.
#[must_use]
pub fn read_reg(&self, offset: u32) -> u32 {
// SAFETY: `base` names a valid register block sized for `u32` access at every offset used.
#[allow(unsafe_code)]
// nosemgrep
unsafe {
self.base.add(offset as usize).cast::<u32>().read_volatile()
}
}
/// Write `val` to the 32-bit register at byte `offset`.
pub fn write_reg(&self, offset: u32, val: u32) {
// SAFETY: `base` names a valid register block sized for `u32` access at every offset used.
#[allow(unsafe_code)]
// nosemgrep
unsafe {
(self.base.add(offset as usize) as *mut u32).write_volatile(val)
}
}
/// True if `bit` is set in the register at byte `offset`.
#[must_use]
pub fn read_bit(&self, offset: u32, bit: u8) -> bool {
self.read_reg(offset) & (1 << bit) != 0
}
/// Read-modify-write a `mask`-wide field at `shift` in the register at `offset` to `val`.
pub fn modify_field(&self, offset: u32, shift: u8, mask: u32, val: u32) {
let cur = self.read_reg(offset);
self.write_reg(offset, (cur & !(mask << shift)) | ((val & mask) << shift));
}
}
impl<B: Block> Mmio<B> {
/// Mint a token-less singleton's applier from its `Block::BASE`; safe, the `unsafe impl Block` made the promise.
#[must_use]
pub const fn block() -> Self {
Self::from_raw(B::BASE)
}
}
impl<B> RegBlock for Mmio<B> {
fn read_reg(&self, offset: u32) -> u32 {
Mmio::read_reg(self, offset)
}
fn write_reg(&self, offset: u32, val: u32) {
Mmio::write_reg(self, offset, val)
}
}