//! One exclusively owned hardware timer and a bounded logical timer bank. //! Every mutation requires masked IRQs. Callback work is handed to the caller; //! the caller must keep that critical section masked through `dispatch`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Frequency, FrequencyChanged, Overflow, CounterRegression, Past, Owner, Stale, Capacity, Exhausted, Unmasked, Backend, Faulted, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Frequency(u64); impl Frequency { pub fn new(hz: u64) -> Result { if hz == 0 { Err(Error::Frequency) } else { Ok(Self(hz)) } } pub fn hz(self) -> u64 { self.0 } pub fn ticks_for_ns(self, ns: u64) -> Result { ns.checked_mul(self.0) .and_then(|v| v.checked_add(999_999_999)) .map(|v| v / 1_000_000_000) .ok_or(Error::Overflow) } pub fn ns_for_ticks(self, ticks: u64) -> Result { ticks .checked_mul(1_000_000_000) .map(|v| v / self.0) .ok_or(Error::Overflow) } pub fn after(self, now: u64, ns: u64) -> Result { now.checked_add(self.ticks_for_ns(ns)?) .ok_or(Error::Overflow) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Owner { pub id: u64, pub generation: u64, } impl Owner { fn valid(self) -> bool { self.id != 0 && self.generation != 0 } } /// # Safety /// The implementation exclusively owns its physical timer/IRQ registration. /// Reads must observe that counter and frequency. `program` publishes the /// supplied absolute CVAL (or disables the timer) with actual readback/barriers. /// It must not allocate, free, sleep or unmask IRQs. A failed program can have /// changed hardware; Bank then retains every owner and refuses all callbacks. pub unsafe trait Backend { fn masked(&self) -> bool; fn frequency(&self) -> u64; fn counter(&self) -> u64; fn program(&mut self, deadline: Option) -> Result<(), Error>; } #[derive(Debug)] pub struct Handle { bank: u64, slot: usize, owner: Owner, generation: u64, } impl Handle { pub fn slot(&self) -> usize { self.slot } pub fn generation(&self) -> u64 { self.generation } pub fn owner(&self) -> Owner { self.owner } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Delivery { pub owner: Owner, pub scheduled: u64, pub observed: u64, pub elapsed: u64, pub missed: u64, } #[derive(Debug)] pub struct Expiry { bank: u64, slot: usize, generation: u64, serial: u64, delivery: Delivery, } impl Expiry { pub fn delivery(&self) -> Delivery { self.delivery } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum State { Vacant, Armed, Pending(u64), Complete, } #[derive(Clone, Copy)] struct Slot { owner: Owner, generation: u64, deadline: u64, period: Option, state: State, } impl Slot { const EMPTY: Self = Self { owner: Owner { id: 0, generation: 0, }, generation: 0, deadline: 0, period: None, state: State::Vacant, }; } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Statistics { pub deliveries: u64, pub elapsed: u64, pub missed: u64, pub cancelled_expiries: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Snapshot { pub live: usize, pub armed: usize, pub pending: usize, pub retired: usize, pub last_counter: u64, pub faulted: bool, pub statistics: Statistics, } pub struct Bank { backend: B, identity: u64, frequency: Frequency, slots: [Slot; N], last_counter: u64, next_serial: u64, statistics: Statistics, faulted: bool, } #[cfg(not(target_os = "none"))] fn identity() -> Result { use core::sync::atomic::{AtomicU64, Ordering}; static NEXT: AtomicU64 = AtomicU64::new(1); let mut current = NEXT.load(Ordering::Relaxed); loop { let next = current.checked_add(1).ok_or(Error::Exhausted)?; match NEXT.compare_exchange_weak(current, next, Ordering::Relaxed, Ordering::Relaxed) { Ok(value) => return Ok(value), Err(value) => current = value, } } } #[cfg(target_os = "none")] fn identity() -> Result { unsafe extern "C" { fn __os_k960_clock_identity() -> u64; } let value = unsafe { __os_k960_clock_identity() }; if value == 0 { Err(Error::Exhausted) } else { Ok(value) } } impl Bank { pub fn new(mut backend: B) -> Result { if N == 0 || N > 32 { return Err((Error::Capacity, backend)); } if !backend.masked() { return Err((Error::Unmasked, backend)); } let frequency = match Frequency::new(backend.frequency()) { Ok(v) => v, Err(e) => return Err((e, backend)), }; let identity = match identity() { Ok(v) => v, Err(e) => return Err((e, backend)), }; let now = backend.counter(); if backend.program(None).is_err() { return Err((Error::Backend, backend)); } Ok(Self { backend, identity, frequency, slots: [Slot::EMPTY; N], last_counter: now, next_serial: 1, statistics: Statistics::default(), faulted: false, }) } pub fn frequency(&self) -> Frequency { self.frequency } pub fn snapshot(&self) -> Snapshot { Snapshot { live: self .slots .iter() .filter(|s| s.state != State::Vacant) .count(), armed: self .slots .iter() .filter(|s| s.state == State::Armed) .count(), pending: self .slots .iter() .filter(|s| matches!(s.state, State::Pending(_))) .count(), retired: self .slots .iter() .filter(|s| s.state == State::Vacant && s.generation == u64::MAX) .count(), last_counter: self.last_counter, faulted: self.faulted, statistics: self.statistics, } } pub fn observe(&self, callback: impl FnOnce(&B) -> R) -> R { callback(&self.backend) } fn now(&self) -> Result { if self.faulted { return Err(Error::Faulted); } if !self.backend.masked() { return Err(Error::Unmasked); } if self.backend.frequency() != self.frequency.hz() { return Err(Error::FrequencyChanged); } let now = self.backend.counter(); if now < self.last_counter { Err(Error::CounterRegression) } else { Ok(now) } } fn earliest(&self, replacement: Option<(usize, Slot)>) -> Option { self.slots .iter() .enumerate() .filter_map(|(i, old)| { let s = replacement .filter(|(index, _)| *index == i) .map_or(*old, |(_, slot)| slot); (s.state == State::Armed).then_some(s.deadline) }) .min() } fn publish(&mut self, index: usize, slot: Slot, now: u64) -> Result<(), Error> { let deadline = self.earliest(Some((index, slot))); if self.backend.program(deadline).is_err() { self.faulted = true; return Err(Error::Backend); } self.slots[index] = slot; self.last_counter = now; Ok(()) } fn validate(&self, handle: &Handle, owner: Owner) -> Result { if handle.bank != self.identity { return Err(Error::Stale); } if handle.owner != owner { return Err(Error::Owner); } let s = self.slots.get(handle.slot).copied().ok_or(Error::Stale)?; if s.state == State::Vacant || s.generation != handle.generation || s.owner != owner { Err(Error::Stale) } else { Ok(s) } } pub fn arm( &mut self, owner: Owner, deadline: u64, period: Option, ) -> Result { if !owner.valid() { return Err(Error::Owner); } if period == Some(0) { return Err(Error::Overflow); } let index = self .slots .iter() .position(|s| s.state == State::Vacant) .ok_or(Error::Capacity)?; let now = self.now()?; if deadline < now { return Err(Error::Past); } let generation = self.slots[index] .generation .checked_add(1) .ok_or(Error::Exhausted)?; let slot = Slot { owner, generation, deadline, period, state: State::Armed, }; self.publish(index, slot, now)?; Ok(Handle { bank: self.identity, slot: index, owner, generation, }) } pub fn arm_after( &mut self, owner: Owner, ns: u64, period_ns: Option, ) -> Result { let now = self.now()?; let deadline = self.frequency.after(now, ns)?; let period = period_ns .map(|v| self.frequency.ticks_for_ns(v)) .transpose()?; self.arm(owner, deadline, period) } /// Take at most one due timer. Equal deadlines use the stable slot order. /// The private expiry is consumed exactly once by dispatch or invalidated /// by cancellation; polling it again cannot count the same tick twice. pub fn take_due(&mut self) -> Result, Error> { let now = self.now()?; let Some((index, old)) = self .slots .iter() .copied() .enumerate() .filter(|(_, s)| s.state == State::Armed && s.deadline <= now) .min_by_key(|(i, s)| (s.deadline, *i)) else { self.last_counter = now; return Ok(None); }; let serial = self.next_serial; let next_serial = serial.checked_add(1).ok_or(Error::Exhausted)?; let elapsed = match old.period { None => 1, Some(p) => ((now - old.deadline) / p) .checked_add(1) .ok_or(Error::Overflow)?, }; let next = match old.period { None => old.deadline, Some(p) => old .deadline .checked_add(elapsed.checked_mul(p).ok_or(Error::Overflow)?) .ok_or(Error::Overflow)?, }; let slot = Slot { deadline: next, state: State::Pending(serial), ..old }; self.publish(index, slot, now)?; self.next_serial = next_serial; Ok(Some(Expiry { bank: self.identity, slot: index, generation: old.generation, serial, delivery: Delivery { owner: old.owner, scheduled: old.deadline, observed: now, elapsed, missed: elapsed - 1, }, })) } pub fn dispatch( &mut self, expiry: Expiry, callback: impl FnOnce(Delivery), ) -> Result<(), (Error, Expiry)> { let error = (|| { if expiry.bank != self.identity { return Err(Error::Stale); } let old = self.slots.get(expiry.slot).copied().ok_or(Error::Stale)?; if old.generation != expiry.generation || old.owner != expiry.delivery.owner || old.state != State::Pending(expiry.serial) { return Err(Error::Stale); } let now = self.now()?; let mut stats = self.statistics; stats.deliveries = stats.deliveries.checked_add(1).ok_or(Error::Overflow)?; stats.elapsed = stats .elapsed .checked_add(expiry.delivery.elapsed) .ok_or(Error::Overflow)?; stats.missed = stats .missed .checked_add(expiry.delivery.missed) .ok_or(Error::Overflow)?; let next = Slot { state: if old.period.is_some() { State::Armed } else { State::Complete }, ..old }; self.publish(expiry.slot, next, now)?; self.statistics = stats; Ok(()) })(); if let Err(error) = error { return Err((error, expiry)); } callback(expiry.delivery); Ok(()) } pub fn cancel(&mut self, handle: Handle, owner: Owner) -> Result<(), (Error, Handle)> { let result = (|| { let old = self.validate(&handle, owner)?; let now = self.now()?; let cancelled = self .statistics .cancelled_expiries .checked_add(matches!(old.state, State::Pending(_)) as u64) .ok_or(Error::Overflow)?; self.publish( handle.slot, Slot { state: State::Vacant, owner: Owner { id: 0, generation: 0, }, deadline: 0, period: None, ..old }, now, )?; self.statistics.cancelled_expiries = cancelled; Ok(()) })(); result.map_err(|error| (error, handle)) } pub fn into_backend(self) -> Result { if self.faulted || self.slots.iter().any(|s| s.state != State::Vacant) { Err(self) } else { Ok(self.backend) } } } #[cfg(test)] mod exhaustion_tests { use super::*; struct Timer { now: u64, writes: usize, fail: bool, cval: Option, } unsafe impl Backend for Timer { fn masked(&self) -> bool { true } fn frequency(&self) -> u64 { 1 } fn counter(&self) -> u64 { self.now } fn program(&mut self, value: Option) -> Result<(), Error> { self.cval = value; self.writes += 1; if self.fail { Err(Error::Backend) } else { Ok(()) } } } const OWNER: Owner = Owner { id: 1, generation: 1, }; fn bank() -> Bank { match Bank::new(Timer { now: 0, writes: 0, fail: false, cval: None, }) { Ok(v) => v, Err(_) => panic!("constructor"), } } #[test] fn maximum_timer_generation_retires_slot_after_exact_owner_cancel() { let mut b = bank(); b.slots[0].generation = u64::MAX - 1; let handle = b.arm(OWNER, 1, None).unwrap(); assert_eq!(handle.generation(), u64::MAX); b.cancel(handle, OWNER).unwrap(); assert_eq!(b.snapshot().retired, 1); let before = b.snapshot(); let writes = b.backend.writes; assert_eq!(b.arm(OWNER, 1, None).unwrap_err(), Error::Capacity); assert_eq!(b.snapshot(), before); assert_eq!(b.backend.writes, writes); } #[test] fn retired_slot_is_skipped_so_another_owner_can_still_progress() { let mut b = match Bank::<_, 2>::new(Timer { now: 0, writes: 0, fail: false, cval: None, }) { Ok(v) => v, Err(_) => panic!("constructor"), }; b.slots[0].generation = u64::MAX; let handle = b.arm(OWNER, 0, None).unwrap(); assert_eq!(handle.slot(), 1); let expiry = b.take_due().unwrap().unwrap(); b.dispatch(expiry, |_| {}).unwrap(); b.cancel(handle, OWNER).unwrap(); assert_eq!(b.snapshot().retired, 1); assert_eq!(b.snapshot().live, 0); } #[test] fn expiry_serial_exhaustion_retains_armed_timer_without_publication() { let mut b = bank(); let _handle = b.arm(OWNER, 0, None).unwrap(); b.next_serial = u64::MAX; let before = b.snapshot(); let writes = b.backend.writes; assert_eq!(b.take_due().unwrap_err(), Error::Exhausted); assert_eq!(b.snapshot(), before); assert_eq!(b.backend.writes, writes); } #[test] fn statistics_overflow_keeps_actual_pending_expiry_and_never_calls() { for field in 0..3 { let mut b = bank(); let _handle = b.arm(OWNER, 0, Some(1)).unwrap(); b.backend.now = 2; let expiry = b.take_due().unwrap().unwrap(); match field { 0 => b.statistics.deliveries = u64::MAX, 1 => b.statistics.elapsed = u64::MAX, _ => b.statistics.missed = u64::MAX, }; let before = b.snapshot(); let writes = b.backend.writes; let (error, _retained) = b .dispatch(expiry, |_| panic!("overflow callback")) .unwrap_err(); assert_eq!(error, Error::Overflow); assert_eq!(b.snapshot(), before); assert_eq!(b.backend.writes, writes); } } #[test] fn cancelled_expiry_overflow_returns_nonclone_handle_with_owner_intact() { let mut b = bank(); let handle = b.arm(OWNER, 0, None).unwrap(); let _expiry = b.take_due().unwrap().unwrap(); b.statistics.cancelled_expiries = u64::MAX; let before = b.snapshot(); let writes = b.backend.writes; let (error, retained) = b.cancel(handle, OWNER).unwrap_err(); assert_eq!(error, Error::Overflow); assert_eq!(retained.owner(), OWNER); assert_eq!(b.snapshot(), before); assert_eq!(b.backend.writes, writes); } #[test] fn failed_constructor_returns_exclusive_backend_after_partial_write() { let backend = Timer { now: 0, writes: 0, fail: true, cval: Some(9), }; let (error, backend) = match Bank::<_, 1>::new(backend) { Err(v) => v, Ok(_) => panic!("failed constructor"), }; assert_eq!(error, Error::Backend); assert_eq!(backend.writes, 1); assert_eq!(backend.cval, None); } }