// Appended only to the exact frozen Context source in an isolated generated // module. Ordinary ContextBank operations are blocked while an owned handoff // is pending; no Rust bank loan spans assembly continuation execution. /// # Safety /// Departure and arrival observe actual source/target CPU execution and frozen /// Table ownership. They never fabricate SP, register bytes or landing episode; /// callbacks keep IRQs masked and perform no allocation or implicit release. pub unsafe trait HandoffBackend: Backend { /// # Safety /// Runs on the real source stack before assembly, while IRQs are masked. /// It publishes only the actual frozen Table yield/sleep (or no source for /// start); failure before any change preserves all state. Any possible /// change reports AfterSwitch, even if the stack branch has not occurred. fn depart( &mut self, prepared: &mut Self::Prepared, source: Option<&Self::Binding>, nonce: u64, disposition: Disposition, ) -> Result<(), Failure>; /// # Safety /// Invoked only after actual target landing. It verifies the live assembly /// landing nonce, retained target generation/root/guard and actual SP, /// activates the frozen Table on that physical stack and preserves the /// actual full register observation recorded before callbacks clobber it. /// Any failure retains staged ownership and reports uncertainty. fn arrive( &mut self, prepared: &mut Self::Prepared, target: &Self::Binding, image: &Image, nonce: u64, ) -> Result<(), Error>; } #[cfg(test)] mod handoff_exhaustion_tests { use super::*; use crate::test_support as saved; unsafe impl HandoffBackend for saved::Backend { fn depart( &mut self, _: &mut Self::Prepared, _: Option<&Self::Binding>, _: u64, _: Disposition, ) -> Result<(), Failure> { Err(Failure { error: Error::Backend, phase: FailurePhase::BeforeSwitch, }) } fn arrive( &mut self, _: &mut Self::Prepared, _: &Self::Binding, _: &Image, _: u64, ) -> Result<(), Error> { Err(Error::Backend) } } fn setup() -> ( AsyncBank, Handle, std::rc::Rc>, ) { let (backend, mut pins, hardware) = saved::backend(2); let mut bank = ContextBank::<_, 4>::new(backend).ok().unwrap(); let a = bank.attach(pins.remove(0)).ok().unwrap(); bank.attach(pins.remove(0)).ok().unwrap(); (AsyncBank::new(bank).ok().unwrap(), a, hardware) } #[test] fn handoff_sequence_max_refuses_without_preparing_or_releasing() { let (mut bank, a, hardware) = setup(); bank.sequence = u64::MAX; let before = bank.bank.snapshot(); assert!(matches!(bank.start(a), Err(Error::Exhausted))); assert_eq!(bank.bank.snapshot(), before); assert!(bank.pending.is_none()); assert_eq!(hardware.borrow().prepares, 0); assert_eq!(hardware.borrow().pins, 2); assert!(bank.quiescent().is_ok()); } #[test] fn foreign_nonce_or_changed_generation_never_departs_actual_prepared() { for field in 0..3 { let (mut bank, a, hardware) = setup(); let mut token = bank.start(a).unwrap(); let prepared = bank.bank.retained_switch.as_ref().unwrap().nonce; match field { 0 => token.bank += 1, 1 => token.nonce += 1, _ => bank.bank.slots[a.slot].generation += 1, } assert!(matches!( bank.depart(token), Err(HandoffFailure::Before(Error::Stale, _)) )); assert_eq!(bank.bank.retained_switch.as_ref().unwrap().nonce, prepared); assert_eq!(bank.stage(), Some(HandoffStage::Staged)); assert!(bank.quiescent().is_err()); assert_eq!(hardware.borrow().pins, 2); assert_eq!(hardware.borrow().aborts, 0); } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum HandoffStage { Staged, Departed, } /// Nonclone lookup authority for one sealed handoff. Every actual binding and /// Prepared remains in the bank; forgetting this token leaves them retained. pub struct OwnedHandoff { bank: u64, nonce: u64, } impl OwnedHandoff { pub fn nonce(&self) -> u64 { self.nonce } } pub enum HandoffFailure { Before(Error, OwnedHandoff), Quarantined(Error), } #[derive(Clone, Copy)] struct HandoffRecord { nonce: u64, stage: HandoffStage, source: Option, target: usize, source_generation: Option, target_generation: u64, capture: Option, image: Image, epoch: u64, old_epoch: u64, consume_deferred: bool, disposition: Disposition, } pub struct AsyncBank { bank: ContextBank, sequence: u64, pending: Option, quarantined: bool, } impl AsyncBank { #[allow(clippy::result_large_err)] pub fn new(bank: ContextBank) -> Result)> { if bank.faulted || bank.retained_switch.is_some() || bank.retained_idle.is_some() { return Err((Error::Quarantined, bank)); } Ok(Self { bank, sequence: 0, pending: None, quarantined: false, }) } pub fn bank(&self) -> &ContextBank { &self.bank } pub fn stage(&self) -> Option { self.pending.map(|p| p.stage) } pub fn quarantined(&self) -> bool { self.quarantined } /// No mutable escape is possible during any part of the staged interval. pub fn quiescent(&mut self) -> Result<&mut ContextBank, Error> { if self.pending.is_some() || self.quarantined || self.bank.faulted { return Err(Error::Pending); } Ok(&mut self.bank) } fn seal(dispatch: Dispatch<'_, B, N>, nonce: u64) -> OwnedHandoff { let mut d = dispatch; let bank_id = d.bank.identity; let record = HandoffRecord { nonce, stage: HandoffStage::Staged, source: d.source, target: d.target, source_generation: d.source.map(|i| d.bank.slots[i].generation), target_generation: d.bank.slots[d.target].generation, capture: d.capture, image: d.image, epoch: d.epoch, old_epoch: d.bank.epoch, consume_deferred: d.consume_deferred, disposition: d.disposition, }; // The wrapper receives this record separately. Prepared and capture // stay physically owned inside the existing frozen bank fields. d.bank.retained_switch = d.prepared.take(); d.bank.uncertain_capture = record.capture; d.bank.faulted = true; OwnedHandoff { bank: bank_id, nonce, } } fn prepare_with( &mut self, make: impl FnOnce(&mut ContextBank) -> Result, Error>, ) -> Result { if self.pending.is_some() || self.quarantined || self.bank.faulted { return Err(Error::Pending); } let nonce = self.sequence.checked_add(1).ok_or(Error::Exhausted)?; let d = make(&mut self.bank)?; // The frozen prepare already validated source capture and target. // Departure repeats before() while retaining the same staged token on // failure, including if a real capture changes after preparation. let record = HandoffRecord { nonce, stage: HandoffStage::Staged, source: d.source, target: d.target, source_generation: d.source.map(|i| d.bank.slots[i].generation), target_generation: d.bank.slots[d.target].generation, capture: d.capture, image: d.image, epoch: d.epoch, old_epoch: d.bank.epoch, consume_deferred: d.consume_deferred, disposition: d.disposition, }; let token = Self::seal(d, nonce); self.sequence = nonce; self.pending = Some(record); Ok(token) } pub fn start(&mut self, target: Handle) -> Result { self.prepare_with(|bank| bank.start(target)) } pub fn cooperate( &mut self, source: Handle, target: Handle, capture: Capture, ) -> Result { self.prepare_with(|bank| bank.cooperate(source, target, capture)) } pub fn sleep_to( &mut self, source: Handle, target: Handle, capture: Capture, ) -> Result { self.prepare_with(|bank| bank.sleep_to(source, target, capture)) } /// Safe-point deferred timer scope only: preserve the original848 IRQ /// witness, and prepare with a fresh640 capture after intervening work. pub fn service_deferred(&mut self, capture: Capture) -> Result { self.prepare_with(|bank| bank.service_deferred(capture)) } pub fn target_image(&self, token: &OwnedHandoff) -> Result { Ok(self.validate_token(token)?.image) } pub fn with_prepared_observation(&self, f: impl FnOnce(Option<&B::Prepared>) -> R) -> R { f(self.bank.retained_switch.as_ref()) } fn validate_token(&self, t: &OwnedHandoff) -> Result { let p = self.pending.ok_or(Error::Pending)?; if self.quarantined || !self.bank.faulted || self.bank.retained_switch.is_none() { return Err(Error::Quarantined); } if t.bank != self.bank.identity || t.nonce != p.nonce || self.bank.epoch != p.old_epoch || self.bank.slots[p.target].generation != p.target_generation || p.source .is_some_and(|i| Some(self.bank.slots[i].generation) != p.source_generation) { return Err(Error::Stale); } Ok(p) } fn before_departure(&mut self, p: HandoffRecord) -> Result<(), Error> { self.bank.faulted = false; let result = { let d = Dispatch { bank: &mut self.bank, source: p.source, target: p.target, capture: p.capture, image: p.image, epoch: p.epoch, prepared: None, consume_deferred: p.consume_deferred, disposition: p.disposition, }; d.before() }; self.bank.faulted = true; result } fn quarantine(&mut self, p: HandoffRecord) { if let Some(i) = p.source { self.bank.slots[i].state = State::Quarantined; } self.bank.slots[p.target].state = State::Quarantined; self.bank.faulted = true; self.quarantined = true; } #[allow(clippy::result_large_err)] pub fn depart(&mut self, token: OwnedHandoff) -> Result { let p = match self.validate_token(&token) { Ok(p) => p, Err(e) => return Err(HandoffFailure::Before(e, token)), }; if p.stage != HandoffStage::Staged { return Err(HandoffFailure::Before(Error::State, token)); } if let Err(e) = self.before_departure(p) { return Err(HandoffFailure::Before(e, token)); } let result = self.bank.backend.depart( self.bank.retained_switch.as_mut().unwrap(), p.source.and_then(|i| self.bank.slots[i].binding.as_ref()), p.nonce, p.disposition, ); if let Err(f) = result { return match f.phase { FailurePhase::BeforeSwitch => Err(HandoffFailure::Before(f.error, token)), FailurePhase::AfterSwitch => { self.quarantine(p); Err(HandoffFailure::Quarantined(f.error)) } }; } self.pending.as_mut().unwrap().stage = HandoffStage::Departed; Ok(token) } pub fn landed(&mut self, token: OwnedHandoff) -> Result<(), HandoffFailure> { let p = match self.validate_token(&token) { Ok(p) => p, Err(e) => return Err(HandoffFailure::Before(e, token)), }; if p.stage != HandoffStage::Departed { return Err(HandoffFailure::Before(Error::State, token)); } let result = self.bank.backend.arrive( self.bank.retained_switch.as_mut().unwrap(), self.bank.slots[p.target].binding.as_ref().unwrap(), &p.image, p.nonce, ); if let Err(e) = result { self.quarantine(p); return Err(HandoffFailure::Quarantined(e)); } self.bank.faulted = false; let checked = { let d = Dispatch { bank: &mut self.bank, source: p.source, target: p.target, capture: p.capture, image: p.image, epoch: p.epoch, prepared: None, consume_deferred: p.consume_deferred, disposition: p.disposition, }; d.completed() }; self.bank.faulted = true; if let Err(e) = checked { self.quarantine(p); return Err(HandoffFailure::Quarantined(e)); } let prepared = self.bank.retained_switch.take().unwrap(); if let Err((e, prepared)) = self.bank.backend.finish(prepared) { self.bank.retained_switch = Some(prepared); self.quarantine(p); return Err(HandoffFailure::Quarantined(e)); } if let Some(i) = p.source { let c = p.capture.unwrap(); self.bank.slots[i].image = c.image; self.bank.slots[i].last_capture = c.episode; self.bank.slots[i].state = State::Ready; } self.bank.slots[p.target].state = State::Active; self.bank.active = Some(self.bank.handle(p.target)); self.bank.epoch = p.epoch; if p.consume_deferred { self.bank.last_deferred = self.bank.deferred_record(); self.bank.deferred = None; } self.bank.uncertain_capture = None; self.bank.faulted = false; self.pending = None; Ok(()) } /// A staged transaction can abort before any Table/CPU change. After /// departure it must remain retained; no rollback or implicit free occurs. pub fn abort(&mut self, token: OwnedHandoff) -> Result<(), HandoffFailure> { let p = match self.validate_token(&token) { Ok(p) => p, Err(e) => return Err(HandoffFailure::Before(e, token)), }; if p.stage != HandoffStage::Staged { return Err(HandoffFailure::Before(Error::State, token)); } let prepared = self.bank.retained_switch.take().unwrap(); if let Err((e, prepared)) = self.bank.backend.abort(prepared) { self.bank.retained_switch = Some(prepared); self.quarantine(p); return Err(HandoffFailure::Quarantined(e)); } self.bank.uncertain_capture = None; self.bank.faulted = false; self.pending = None; Ok(()) } }