//! Single CPU context custody; actual assembly and WFI remain trusted backend //! operations. No raw register image, observation or copied handle owns a stack. use core::marker::PhantomData; use core::mem::{align_of, offset_of, size_of}; use turkos_os_exceptions::exceptions::{self as exception, Frame}; use turkos_os_threads::threads::{self as thread, StackObservation, ThreadId, ThreadRecord}; pub const CONTEXT_CANARY: u64 = 0x4b31303430435458; const IRQ: u64 = 1 << 7; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Configuration, Context, Capacity, Exhausted, Stale, State, Stack, Guard, Canary, Entry, Abi, Capture, Simd, Pending, Backend, Quarantined, Active, Wake, } /// Inert lookup identity, independently checked against a retained private slot. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Handle { bank: u64, slot: usize, generation: u64, } impl Handle { pub fn bank(self) -> u64 { self.bank } pub fn slot(self) -> usize { self.slot } pub fn generation(self) -> u64 { self.generation } } /// Full eager SIMD context, even though a cooperative ABI preserves fewer lanes. #[repr(C, align(16))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Cooperative { pub sp: u64, pub x19_x30: [u64; 12], pub padding: u64, pub q: [[u64; 2]; 32], pub fpcr: u32, pub fpsr: u32, pub canary: u64, } impl Cooperative { pub const EMPTY: Self = Self { sp: 0, x19_x30: [0; 12], padding: 0, q: [[0; 2]; 32], fpcr: 0, fpsr: 0, canary: CONTEXT_CANARY, }; } pub const COOP_SP_OFFSET: usize = offset_of!(Cooperative, sp); pub const COOP_X_OFFSET: usize = offset_of!(Cooperative, x19_x30); pub const COOP_PADDING_OFFSET: usize = offset_of!(Cooperative, padding); pub const COOP_Q_OFFSET: usize = offset_of!(Cooperative, q); pub const COOP_FPCR_OFFSET: usize = offset_of!(Cooperative, fpcr); pub const COOP_FPSR_OFFSET: usize = offset_of!(Cooperative, fpsr); pub const COOP_CANARY_OFFSET: usize = offset_of!(Cooperative, canary); pub const COOP_BYTES: usize = size_of::(); const _: () = { assert!(COOP_SP_OFFSET == 0); assert!(COOP_X_OFFSET == 8); assert!(COOP_PADDING_OFFSET == 104); assert!(COOP_Q_OFFSET == 112); assert!(COOP_FPCR_OFFSET == 624); assert!(COOP_FPSR_OFFSET == 628); assert!(COOP_CANARY_OFFSET == 632); assert!(COOP_BYTES == 640); assert!(align_of::() == 16); }; /// Assembly must report exactly these offsets; a byte count alone is inadequate. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Abi { pub offsets: [usize; 7], pub bytes: usize, pub alignment: usize, } impl Abi { pub const COOPERATIVE: Self = Self { offsets: [0, 8, 104, 112, 624, 628, 632], bytes: 640, alignment: 16, }; } // Fixed caller-owned storage; this no_alloc service deliberately retains the full frame. #[allow(clippy::large_enum_variant)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Image { Initial { entry: u64, argument: u64, sp: u64, return_pc: u64, }, Cooperative(Cooperative), Timer(Frame), } impl Image { pub fn sp(self) -> u64 { match self { Self::Initial { sp, .. } => sp, Self::Cooperative(c) => c.sp, Self::Timer(f) => f.interrupted_sp, } } pub fn pc(self) -> u64 { match self { Self::Initial { entry, .. } => entry, Self::Cooperative(c) => c.x19_x30[11], Self::Timer(f) => f.elr, } } pub fn simd(self) -> ([[u64; 2]; 32], u32, u32) { match self { Self::Initial { .. } => ([[0; 2]; 32], 0, 0), Self::Cooperative(c) => (c.q, c.fpcr, c.fpsr), Self::Timer(f) => (f.q, f.fpcr, f.fpsr), } } } /// Raw CPU data. The unsafe backend must separately bind every byte and the /// episode to the current live assembly handoff in this retained thread. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Capture { pub image: Image, pub address: u64, pub abi: Abi, /// An inert observation of a nonzero, never-reused capture episode. This /// value alone does not authorize a capture or establish its freshness. pub episode: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Execution { pub cpu: u32, pub daif: u64, pub in_irq: bool, pub sp: u64, pub root: u64, pub thread: Option, pub fp_enabled: bool, pub critical_depth: u32, pub safe_point: bool, pub tco: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Registers { pub sp: u64, pub x19_x30: [u64; 12], pub q: [[u64; 2]; 32], pub fpcr: u32, pub fpsr: u32, pub pc: u64, pub argument: u64, pub timer: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FailurePhase { BeforeSwitch, AfterSwitch, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Disposition { Ready, Sleeping, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Failure { pub error: Error, pub phase: FailurePhase, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct IdleReceipt { /// The value actually checked atomically at the final mask/WFI handoff. pub checked_epoch: u64, pub work_at_handoff: bool, pub executed_wfi: bool, pub wake_epoch: u64, } /// # Safety /// Backend exclusively owns a frozen threads Table and actual stack/table /// authority. Binding values are nonclone pins for that live ThreadId/stack; /// dropping one never releases pages. bind/release failures return the exact /// resource. capture_matches verifies the current live assembly handoff/IRQ /// episode for this exact live ThreadId, root and retained stack, as well as /// every actual byte at the supplied stack address. Intact bytes from an older /// call on the same live stack must fail even if their address and saved SP are /// valid. Episodes are nonzero and never reused; exhaustion refuses capture. /// Rust callbacks may run below the saved assembly frame, so matching the /// current callback SP to the saved SP is not a freshness check. No caller- /// provided boolean, episode number or raw image manufactures this authority. /// Prepared owns all resources needed by a switch. Preparation failures leave /// the complete resource inventory and thread/register state unchanged; a /// backend whose rollback can fail must retain acquisitions in a successful /// Prepared and report uncertainty through commit. commit reports BeforeSwitch only when no /// hardware, table state or visible resource changed; after any possible change /// it must report AfterSwitch. Observations are real and taken before callback /// code can clobber registers. finish/abort failure returns the same Prepared. /// All operations are bounded and keep IRQs masked while core metadata is /// borrowed. Direct timer commit closes IRQ ownership before changing frozen /// thread state. Eager SIMD/FP load/store includes all32 q registers and FPCR/ /// FPSR. idle_commit performs its epoch/work check atomically at the final /// mask/WFI boundary; an earlier observation is never a substitute. Prepared /// idle resources retain every wake token through failure and spurious wake. pub unsafe trait Backend { type Binding; type Prepared; type IdlePrepared; fn execution(&self) -> Execution; fn record(&self, binding: &Self::Binding) -> Result; fn stack(&self, binding: &Self::Binding) -> Result; fn root(&self, binding: &Self::Binding) -> Result; fn executable(&self, pc: u64) -> bool; fn initial_return_pc(&self, binding: &Self::Binding) -> Result; fn capture_matches(&self, binding: &Self::Binding, capture: &Capture) -> bool; fn registers(&self) -> Registers; fn prepare( &mut self, source: Option<&Self::Binding>, target: &Self::Binding, image: &Image, disposition: Disposition, ) -> Result; fn commit( &mut self, prepared: &mut Self::Prepared, source: Option<&Self::Binding>, target: &Self::Binding, image: &Image, disposition: Disposition, ) -> Result<(), Failure>; fn finish(&mut self, prepared: Self::Prepared) -> Result<(), (Error, Self::Prepared)>; fn abort(&mut self, prepared: Self::Prepared) -> Result<(), (Error, Self::Prepared)>; fn release(&mut self, binding: Self::Binding) -> Result<(), (Error, Self::Binding)>; fn ready_empty(&self) -> bool; fn wake_epoch(&self) -> u64; fn idle_prepare(&mut self, epoch: u64) -> Result; fn idle_commit( &mut self, prepared: &mut Self::IdlePrepared, epoch: u64, ) -> Result; fn idle_finish( &mut self, prepared: Self::IdlePrepared, ) -> Result<(), (Error, Self::IdlePrepared)>; fn idle_abort( &mut self, prepared: Self::IdlePrepared, ) -> Result<(), (Error, Self::IdlePrepared)>; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum State { Vacant, Ready, Active, Quarantined, } struct Slot { generation: u64, last_capture: u64, binding: Option, thread: Option, root: u64, image: Image, state: State, } impl Slot { fn empty() -> Self { Self { generation: 0, last_capture: 0, binding: None, thread: None, root: 0, image: Image::Initial { entry: 0, argument: 0, sp: 0, return_pc: 0, }, state: State::Vacant, } } } struct Deferred { source: Handle, target: Handle, thread: ThreadId, epoch: u64, capture: Capture, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct DeferredRecord { pub source: Handle, pub target: Handle, pub thread: ThreadId, pub epoch: u64, pub capture: Capture, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Record { pub handle: Handle, pub thread: ThreadId, pub state: State, pub root: u64, pub image: Image, pub last_capture: u64, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Snapshot { pub retained: usize, pub retained_bindings: usize, pub active: usize, pub ready: usize, pub quarantined: usize, pub retired: usize, pub deferred_epoch: Option, pub faulted: bool, pub retained_switch: bool, pub retained_idle: bool, pub retained_capture: bool, } pub struct ContextBank { backend: B, identity: u64, cpu: u32, slots: [Slot; N], active: Option, epoch: u64, deferred: Option, last_deferred: Option, faulted: bool, retained_switch: Option, retained_idle: Option, uncertain_capture: Option, _local: PhantomData<*mut ()>, } #[cfg(not(target_os = "none"))] fn identity() -> Result { use core::sync::atomic::{AtomicU64, Ordering}; static NEXT: AtomicU64 = AtomicU64::new(1); NEXT.try_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1)) .map_err(|_| Error::Exhausted) } #[cfg(target_os = "none")] fn identity() -> Result { unsafe extern "C" { fn __os_k1040_context_identity() -> u64; } let n = unsafe { __os_k1040_context_identity() }; if n == 0 { Err(Error::Exhausted) } else { Ok(n) } } impl ContextBank { pub fn new(backend: B) -> Result { let e = backend.execution(); if N == 0 || N > 32 { return Err((Error::Configuration, backend)); } if e.daif & IRQ == 0 || e.daif & !0x3c0 != 0 || e.in_irq || !e.fp_enabled { return Err((Error::Context, backend)); } let id = match identity() { Ok(v) => v, Err(e) => return Err((e, backend)), }; Ok(Self { backend, identity: id, cpu: e.cpu, slots: core::array::from_fn(|_| Slot::empty()), active: None, epoch: 0, deferred: None, last_deferred: None, faulted: false, retained_switch: None, retained_idle: None, uncertain_capture: None, _local: PhantomData, }) } fn execution(&self, irq: bool) -> Result { if self.faulted { return Err(Error::Quarantined); } let e = self.backend.execution(); if e.cpu != self.cpu || e.daif & IRQ == 0 || e.daif & !0x3c0 != 0 || (!irq && e.in_irq) || !e.fp_enabled { Err(Error::Context) } else { Ok(e) } } fn index(&self, h: Handle) -> Result { if h.bank != self.identity || h.slot >= N { return Err(Error::Stale); } let s = &self.slots[h.slot]; if s.state == State::Vacant || s.generation != h.generation { Err(Error::Stale) } else { Ok(h.slot) } } fn handle(&self, i: usize) -> Handle { Handle { bank: self.identity, slot: i, generation: self.slots[i].generation, } } fn binding(&self, i: usize) -> Result<&B::Binding, Error> { self.slots[i].binding.as_ref().ok_or(Error::State) } fn validate(&self, i: usize) -> Result { let s = &self.slots[i]; if s.state == State::Quarantined { return Err(Error::Quarantined); } let b = self.binding(i)?; let r = self.backend.record(b)?; if Some(r.id) != s.thread { return Err(Error::Stale); } if self.backend.root(b)? != s.root { return Err(Error::Stack); } Self::stack_observation(self.backend.stack(b)?, r)?; Ok(r) } fn stack_observation(o: StackObservation, r: ThreadRecord) -> Result<(), Error> { let l = r.layout; if o.layout != l || l.base < 4096 || !l.base.is_multiple_of(4096) || l.physical == 0 || !l.physical.is_multiple_of(4096) || l.bytes < 16384 || l.bytes > 65536 || !l.bytes.is_multiple_of(4096) || l.base.checked_add(l.bytes).is_none() || l.physical.checked_add(l.bytes).is_none() { return Err(Error::Stack); } if o.guard_descriptor != 0 { return Err(Error::Guard); } if o.low_canary != thread::LOW_CANARY || o.high_canary != thread::HIGH_CANARY { return Err(Error::Canary); } Ok(()) } fn usable(r: ThreadRecord, sp: u64) -> bool { sp.is_multiple_of(16) && sp >= r.layout.usable_bottom() && sp <= r.layout.usable_top() } fn validate_image(&self, r: ThreadRecord, image: Image) -> Result<(), Error> { if !Self::usable(r, image.sp()) { return Err(Error::Stack); } if image.pc() == 0 || !image.pc().is_multiple_of(4) || !self.backend.executable(image.pc()) { return Err(Error::Entry); } match image { Image::Initial { entry, argument, sp, return_pc, } => { if entry != r.entry || argument != r.argument || sp != r.layout.usable_top() || return_pc == 0 || !return_pc.is_multiple_of(4) || !self.backend.executable(return_pc) { return Err(Error::Capture); } } Image::Cooperative(c) => { if c.canary != CONTEXT_CANARY || c.padding != 0 { return Err(Error::Canary); } } Image::Timer(_) => {} } Ok(()) } fn current(&self, h: Handle, irq: bool) -> Result<(usize, ThreadRecord, Execution), Error> { let e = self.execution(irq)?; let i = self.index(h)?; if self.active != Some(h) || self.slots[i].state != State::Active { return Err(Error::State); } let r = self.validate(i)?; if r.state != thread::State::Running || e.thread != Some(r.id) || e.root != self.slots[i].root || !Self::usable(r, e.sp) { return Err(Error::State); } Ok((i, r, e)) } fn capture( &self, i: usize, r: ThreadRecord, c: Capture, timer: bool, e: Execution, ) -> Result<(), Error> { if c.episode == 0 || c.episode <= self.slots[i].last_capture { return Err(Error::Capture); } self.validate_image(r, c.image)?; let bytes = if timer { exception::FRAME_BYTES } else { COOP_BYTES }; if !c.address.is_multiple_of(16) || c.address < r.layout.usable_bottom() || c.address .checked_add(bytes as u64) .is_none_or(|end| end > r.layout.usable_top()) { return Err(Error::Stack); } match c.image { Image::Cooperative(f) if !timer => { if c.abi != Abi::COOPERATIVE { return Err(Error::Abi); } if f.sp != c.address + COOP_BYTES as u64 { return Err(Error::Capture); } } Image::Timer(f) if timer => { let stack = exception::Stack::new(r.layout.base, r.layout.base + r.layout.bytes) .map_err(|_| Error::Stack)?; exception::validate_ingress(&f, c.address, stack).map_err(|_| Error::Capture)?; if f.vector_slot != 5 || f.spsr & 31 != 5 || f.spsr & IRQ != 0 || f.spsr & !((15 << 28) | 0x345 | (1 << 25)) != 0 || f.spsr & (1 << 25) != e.tco || e.tco & !(1 << 25) != 0 || f.interrupted_sp != c.address + exception::FRAME_BYTES as u64 { return Err(Error::Capture); } } _ => return Err(Error::Capture), } if false { return Err(Error::Capture); } Ok(()) } /// The caller supplies an actual nonclone pin minted by the exclusive backend. /// A rejected attach returns exactly that resource; no generation is consumed. pub fn attach(&mut self, binding: B::Binding) -> Result { let result = (|| { self.execution(false)?; let i = self .slots .iter() .position(|s| s.state == State::Vacant && s.generation != u64::MAX) .ok_or(Error::Capacity)?; let generation = self.slots[i] .generation .checked_add(1) .ok_or(Error::Exhausted)?; let r = self.backend.record(&binding)?; if r.state != thread::State::Ready { return Err(Error::State); } if self .slots .iter() .any(|s| s.thread == Some(r.id) && s.state != State::Vacant) { return Err(Error::State); } Self::stack_observation(self.backend.stack(&binding)?, r)?; let root = self.backend.root(&binding)?; if root == 0 || !root.is_multiple_of(4096) || root >> 48 != 0 { return Err(Error::Stack); } let image = Image::Initial { entry: r.entry, argument: r.argument, sp: r.layout.usable_top(), return_pc: self.backend.initial_return_pc(&binding)?, }; self.validate_image(r, image)?; Ok((i, generation, r.id, root, image)) })(); match result { Ok((i, generation, id, root, image)) => { self.slots[i] = Slot { generation, last_capture: 0, binding: Some(binding), thread: Some(id), root, image, state: State::Ready, }; Ok(self.handle(i)) } Err(e) => Err((e, binding)), } } fn target(&self, h: Handle) -> Result { let i = self.index(h)?; if self.slots[i].state != State::Ready { return Err(Error::State); } let r = self.validate(i)?; if r.state != thread::State::Ready { return Err(Error::State); } self.validate_image(r, self.slots[i].image)?; Ok(i) } fn dispatch( &mut self, source: Option, target: usize, capture: Option, deferred: bool, disposition: Disposition, ) -> Result, Error> { let image = self.slots[target].image; let epoch = self.epoch.checked_add(1).ok_or(Error::Exhausted)?; let prepared = self.backend.prepare( source.and_then(|i| self.slots[i].binding.as_ref()), self.slots[target].binding.as_ref().ok_or(Error::State)?, &image, disposition, )?; Ok(Dispatch { bank: self, source, target, capture, image, epoch, prepared: Some(prepared), consume_deferred: deferred, disposition, }) } pub fn start(&mut self, target: Handle) -> Result, Error> { let e = self.execution(false)?; if self.active.is_some() || self.deferred.is_some() || e.thread.is_some() { return Err(Error::State); } let i = self.target(target)?; self.dispatch(None, i, None, false, Disposition::Ready) } pub fn cooperate( &mut self, source: Handle, target: Handle, capture: Capture, ) -> Result, Error> { let (i, r, e) = self.current(source, false)?; if e.critical_depth != 0 || !e.safe_point || self.deferred.is_some() { return Err(Error::Context); } if source == target { return Err(Error::State); } self.capture(i, r, capture, false, e)?; let t = self.target(target)?; self.dispatch(Some(i), t, Some(capture), false, Disposition::Ready) } pub fn sleep_to( &mut self, source: Handle, target: Handle, capture: Capture, ) -> Result, Error> { let (i, r, e) = self.current(source, false)?; if e.critical_depth != 0 || !e.safe_point || self.deferred.is_some() { return Err(Error::Context); } if r.wait_epoch == u64::MAX { return Err(Error::Exhausted); } if source == target { return Err(Error::State); } self.capture(i, r, capture, false, e)?; let t = self.target(target)?; self.dispatch(Some(i), t, Some(capture), false, Disposition::Sleeping) } pub fn timer( &mut self, source: Handle, target: Handle, capture: Capture, ) -> Result, Error> { let (i, r, e) = self.current(source, true)?; if !e.in_irq || e.critical_depth != 0 || !e.safe_point || self.deferred.is_some() { return Err(Error::Context); } if source == target { return Err(Error::State); } self.capture(i, r, capture, true, e)?; let t = self.target(target)?; self.dispatch(Some(i), t, Some(capture), false, Disposition::Ready) } pub fn defer_timer( &mut self, source: Handle, target: Handle, capture: Capture, ) -> Result { let (i, r, e) = self.current(source, true)?; if !e.in_irq || e.critical_depth == 0 && e.safe_point { return Err(Error::Context); } if self.deferred.is_some() { return Err(Error::Pending); } if source == target { return Err(Error::State); } self.capture(i, r, capture, true, e)?; self.target(target)?; let epoch = self.epoch.checked_add(1).ok_or(Error::Exhausted)?; self.epoch = epoch; self.deferred = Some(Deferred { source, target, thread: r.id, epoch, capture, }); Ok(epoch) } /// Original IRQ data remains an episode witness. Only this fresh checked /// cooperative capture becomes the later source return context. pub fn service_deferred(&mut self, fresh: Capture) -> Result, Error> { let d = self.deferred.as_ref().ok_or(Error::Pending)?; let (source, target, id) = (d.source, d.target, d.thread); let (i, r, e) = self.current(source, false)?; if r.id != id || e.critical_depth != 0 || !e.safe_point { return Err(Error::Context); } self.capture(i, r, fresh, false, e)?; let t = self.target(target)?; self.dispatch(Some(i), t, Some(fresh), true, Disposition::Ready) } pub fn deferred_record(&self) -> Option { self.deferred.as_ref().map(|d| DeferredRecord { source: d.source, target: d.target, thread: d.thread, epoch: d.epoch, capture: d.capture, }) } pub fn last_deferred_record(&self) -> Option { self.last_deferred } pub fn record(&self, h: Handle) -> Result { let i = self.index(h)?; let s = &self.slots[i]; Ok(Record { handle: h, thread: s.thread.ok_or(Error::State)?, state: s.state, root: s.root, image: s.image, last_capture: s.last_capture, }) } pub fn snapshot(&self) -> Snapshot { let mut r = Snapshot { faulted: self.faulted, deferred_epoch: self.deferred.as_ref().map(|d| d.epoch), retained_switch: self.retained_switch.is_some(), retained_idle: self.retained_idle.is_some(), retained_capture: self.uncertain_capture.is_some(), ..Snapshot::default() }; for s in &self.slots { r.retained_bindings += usize::from(s.binding.is_some()); if s.state == State::Vacant { r.retired += usize::from(s.generation == u64::MAX); } else { r.retained += 1; match s.state { State::Ready => r.ready += 1, State::Active => r.active += 1, State::Quarantined => r.quarantined += 1, State::Vacant => {} } } } r } pub fn retire(&mut self, h: Handle) -> Result<(), Error> { let e = self.execution(false)?; let i = self.index(h)?; if self.active == Some(h) || self .deferred .as_ref() .is_some_and(|d| d.source == h || d.target == h) { return Err(Error::Active); } let r = self.validate(i)?; if r.state != thread::State::Dead || e.thread == Some(r.id) || e.sp >= r.layout.guard() && e.sp <= r.layout.base + r.layout.bytes { return Err(Error::Active); } let b = self.slots[i].binding.take().ok_or(Error::State)?; if let Err((_e, b)) = self.backend.release(b) { self.slots[i].binding = Some(b); self.slots[i].state = State::Quarantined; return Err(Error::Backend); } let generation = self.slots[i].generation; self.slots[i] = Slot::empty(); self.slots[i].generation = generation; Ok(()) } pub fn quarantined_capture(&self) -> Option { self.uncertain_capture } pub fn with_retained_idle_observation( &self, f: impl FnOnce(Option<&B::IdlePrepared>) -> R, ) -> R { f(self.retained_idle.as_ref()) } pub fn with_backend_observation(&self, f: impl FnOnce(&B) -> R) -> R { f(&self.backend) } // Returning the actual retained bank is required on refusal; no heap boxing. #[allow(clippy::result_large_err)] pub fn into_backend(self) -> Result { if self.faulted || self.slots.iter().any(|s| s.state != State::Vacant) || self.deferred.is_some() { return Err((Error::Active, self)); } if let Err(e) = self.execution(false) { return Err((e, self)); } Ok(self.backend) } fn quarantine_switch( &mut self, source: Option, target: usize, prepared: B::Prepared, capture: Option, ) { if let Some(i) = source { self.slots[i].state = State::Quarantined; } self.slots[target].state = State::Quarantined; self.retained_switch = Some(prepared); self.uncertain_capture = capture; self.faulted = true; } pub fn idle(&mut self) -> Result, Error> { let execution = self.execution(false)?; if execution.critical_depth != 0 || !execution.safe_point { return Err(Error::Context); } if let Some(h) = self.active { self.current(h, false)?; } for i in 0..N { if matches!(self.slots[i].state, State::Ready | State::Active) && self.validate(i)?.state == thread::State::Ready { return Err(Error::Pending); } } if self.deferred.is_some() || !self.backend.ready_empty() { return Err(Error::Pending); } let epoch = self.backend.wake_epoch(); if epoch == u64::MAX { return Err(Error::Exhausted); } let prepared = self.backend.idle_prepare(epoch)?; Ok(Idle { bank: self, epoch, prepared: Some(prepared), }) } } /// A retryable pre-switch error returns exactly the dispatch and private staged /// resources. An uncertain post-switch error leaves them retained in the bank. pub enum DispatchError { Before(Error, T), Quarantined(Error), } pub struct Dispatch<'a, B: Backend, const N: usize> { bank: &'a mut ContextBank, source: Option, target: usize, capture: Option, image: Image, epoch: u64, prepared: Option, consume_deferred: bool, disposition: Disposition, } impl Dispatch<'_, B, N> { pub fn image(&self) -> Image { self.image } pub fn with_prepared_observation(&self, f: impl FnOnce(&B::Prepared) -> R) -> R { f(self.prepared.as_ref().unwrap()) } pub fn source_capture(&self) -> Option { self.capture } fn before(&self) -> Result<(), Error> { let timer = self .capture .is_some_and(|c| matches!(c.image, Image::Timer(_))); if let Some(i) = self.source { let (_, r, e) = self.bank.current(self.bank.handle(i), timer)?; if e.in_irq != timer || e.critical_depth != 0 || !e.safe_point { return Err(Error::Context); } self.bank .capture(i, r, self.capture.ok_or(Error::Capture)?, timer, e)?; } else { let e = self.bank.execution(false)?; if self.bank.active.is_some() || e.thread.is_some() || e.critical_depth != 0 || !e.safe_point { return Err(Error::Context); } } self.bank.target(self.bank.handle(self.target))?; if self.bank.slots[self.target].image != self.image { return Err(Error::Capture); } Ok(()) } fn completed(&self) -> Result<(), Error> { let e = self.bank.execution(false)?; let r = self.bank.validate(self.target)?; if r.state != thread::State::Running || e.thread != Some(r.id) || e.root != self.bank.slots[self.target].root || !ContextBank::::usable(r, e.sp) { return Err(Error::State); } if let Some(i) = self.source && self.bank.validate(i)?.state != match self.disposition { Disposition::Ready => thread::State::Ready, Disposition::Sleeping => thread::State::Sleeping, } { return Err(Error::State); } let actual = self.bank.backend.registers(); let (q, fpcr, fpsr) = self.image.simd(); if actual.sp != self.image.sp() || actual.pc != self.image.pc() || actual.q != q || actual.fpcr != fpcr || actual.fpsr != fpsr { return Err(Error::Simd); } match self.image { Image::Initial { argument, return_pc, .. } => { let mut expected = [0; 12]; expected[11] = return_pc; if actual.x19_x30 != expected || actual.argument != argument || actual.timer.is_some() { return Err(Error::Capture); } } Image::Cooperative(c) => { if actual.x19_x30 != c.x19_x30 || actual.timer.is_some() { return Err(Error::Capture); } } Image::Timer(f) => { if actual.x19_x30 != f.x[19..31] || actual.timer != Some(f) { return Err(Error::Capture); } } } Ok(()) } // The same nonclone transaction, including its capture, remains retryable. #[allow(clippy::result_large_err)] pub fn complete(mut self) -> Result<(), DispatchError> { if let Err(e) = self.before() { return Err(DispatchError::Before(e, self)); } let source = self .source .and_then(|i| self.bank.slots[i].binding.as_ref()); let target = self.bank.slots[self.target].binding.as_ref().unwrap(); let prepared = self.prepared.as_mut().unwrap(); if let Err(f) = self.bank .backend .commit(prepared, source, target, &self.image, self.disposition) { return match f.phase { FailurePhase::BeforeSwitch => Err(DispatchError::Before(f.error, self)), FailurePhase::AfterSwitch => { let p = self.prepared.take().unwrap(); self.bank .quarantine_switch(self.source, self.target, p, self.capture); Err(DispatchError::Quarantined(f.error)) } }; } if let Err(e) = self.completed() { let p = self.prepared.take().unwrap(); self.bank .quarantine_switch(self.source, self.target, p, self.capture); return Err(DispatchError::Quarantined(e)); } let prepared = self.prepared.take().unwrap(); if let Err((e, p)) = self.bank.backend.finish(prepared) { self.bank .quarantine_switch(self.source, self.target, p, self.capture); return Err(DispatchError::Quarantined(e)); } if let Some(i) = self.source { self.bank.slots[i].image = self.capture.unwrap().image; self.bank.slots[i].last_capture = self.capture.unwrap().episode; self.bank.slots[i].state = State::Ready; } self.bank.slots[self.target].state = State::Active; self.bank.active = Some(self.bank.handle(self.target)); self.bank.epoch = self.epoch; if self.consume_deferred { self.bank.last_deferred = self.bank.deferred_record(); self.bank.deferred = None; } Ok(()) } pub fn abort(mut self) -> Result<(), Error> { let prepared = self.prepared.take().unwrap(); if let Err((e, p)) = self.bank.backend.abort(prepared) { self.bank .quarantine_switch(self.source, self.target, p, self.capture); return Err(e); } Ok(()) } } impl Drop for Dispatch<'_, B, N> { fn drop(&mut self) { if let Some(p) = self.prepared.take() && let Err((_e, p)) = self.bank.backend.abort(p) { self.bank .quarantine_switch(self.source, self.target, p, self.capture); } } } pub struct Idle<'a, B: Backend, const N: usize> { bank: &'a mut ContextBank, epoch: u64, prepared: Option, } impl Idle<'_, B, N> { pub fn with_prepared_observation(&self, f: impl FnOnce(&B::IdlePrepared) -> R) -> R { f(self.prepared.as_ref().unwrap()) } pub fn epoch(&self) -> u64 { self.epoch } pub fn complete(mut self) -> Result> { let valid = self .bank .execution(false) .and_then(|e| { if e.critical_depth != 0 || !e.safe_point { return Err(Error::Context); } Ok(e) }) .and_then(|_| match self.bank.active { Some(h) => self.bank.current(h, false).map(|_| ()), None => Ok(()), }); if let Err(e) = valid { return Err(DispatchError::Before(e, self)); } let result = self .bank .backend .idle_commit(self.prepared.as_mut().unwrap(), self.epoch); let receipt = match result { Ok(r) => r, Err(f) => { return match f.phase { FailurePhase::BeforeSwitch => Err(DispatchError::Before(f.error, self)), FailurePhase::AfterSwitch => { self.bank.retained_idle = self.prepared.take(); self.bank.faulted = true; Err(DispatchError::Quarantined(f.error)) } }; } }; if receipt.checked_epoch < self.epoch || receipt.wake_epoch < receipt.checked_epoch || receipt.executed_wfi && receipt.work_at_handoff || receipt.executed_wfi && receipt.checked_epoch != self.epoch { self.bank.retained_idle = self.prepared.take(); self.bank.faulted = true; return Err(DispatchError::Quarantined(Error::Wake)); } let p = self.prepared.take().unwrap(); if let Err((e, p)) = self.bank.backend.idle_finish(p) { self.bank.retained_idle = Some(p); self.bank.faulted = true; return Err(DispatchError::Quarantined(e)); } Ok(receipt) } pub fn abort(mut self) -> Result<(), Error> { let p = self.prepared.take().unwrap(); if let Err((e, p)) = self.bank.backend.idle_abort(p) { self.bank.retained_idle = Some(p); self.bank.faulted = true; return Err(e); } Ok(()) } } impl Drop for Idle<'_, B, N> { fn drop(&mut self) { if let Some(p) = self.prepared.take() && let Err((_e, p)) = self.bank.backend.idle_abort(p) { self.bank.retained_idle = Some(p); self.bank.faulted = true; } } } #[cfg(test)] mod exhaustion_tests { use super::*; use crate::test_support::{backend, capture, finish, setup}; #[test] fn retired_slot_is_skipped_and_all_retired_returns_same_binding() { let (b, mut pins, h) = backend(2); let mut bank = ContextBank::<_, 4>::new(b).ok().unwrap(); bank.slots[0].generation = u64::MAX; let first = bank.attach(pins.remove(0)).ok().unwrap(); assert_eq!(first.slot(), 1); for i in [0, 2, 3] { bank.slots[i].generation = u64::MAX; } let binding = pins.remove(0); let id = binding.id; let binding = match bank.attach(binding) { Err((Error::Capacity, b)) => b, _ => panic!(), }; assert_eq!(binding.id, id); assert_eq!(bank.snapshot().retired, 3); assert_eq!(h.borrow().prepares, 0); assert_eq!(h.borrow().pins, 2); } #[test] fn dispatch_epoch_exhaustion_preserves_active_context_and_resources() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); bank.epoch = u64::MAX; let c = capture(&bank, a, &h, 17, false); let before = bank.snapshot(); let image = bank.record(a).unwrap().image; assert!(matches!(bank.cooperate(a, b, c), Err(Error::Exhausted))); assert_eq!(bank.snapshot(), before); assert_eq!(bank.record(a).unwrap().image, image); assert_eq!(h.borrow().prepares, 1); assert_eq!(h.borrow().pins, 2); } #[test] fn deferred_epoch_exhaustion_does_not_publish_episode() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); bank.epoch = u64::MAX; let c = capture(&bank, a, &h, 17, true); h.borrow_mut().critical = 1; let before = bank.snapshot(); assert_eq!(bank.defer_timer(a, b, c), Err(Error::Exhausted)); assert_eq!(bank.snapshot(), before); assert!(bank.deferred_record().is_none()); assert_eq!(h.borrow().pins, 2); } #[test] fn idle_wake_epoch_exhaustion_retains_sleep_episode() { let (mut bank, _, h) = crate::test_support::setup_idle(); h.borrow_mut().wake_epoch = u64::MAX; let before = bank.snapshot(); assert!(matches!(bank.idle(), Err(Error::Exhausted))); assert_eq!(bank.snapshot(), before); assert!(bank.with_backend_observation(|b| b.sleep_wait.is_some())); assert_eq!(h.borrow().pins, 2); } #[test] fn forged_private_generation_and_lost_deferred_thread_identity_refuse() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 17, false); let forged = Handle { generation: a.generation + 1, ..a }; assert!(matches!(bank.cooperate(forged, b, c), Err(Error::Stale))); let c = capture(&bank, a, &h, 17, true); h.borrow_mut().critical = 1; bank.defer_timer(a, b, c).unwrap(); let original = bank.deferred_record(); h.borrow_mut().critical = 0; let fresh = capture(&bank, a, &h, 43, false); bank.slots[a.slot].thread = bank.slots[b.slot].thread; assert!(matches!(bank.service_deferred(fresh), Err(Error::Stale))); assert_eq!(bank.deferred_record(), original); assert_eq!(h.borrow().pins, 2); } }