#[path = "../../os-context-switch/tests/support/mod.rs"] #[allow(dead_code)] mod saved; use std::{cell::RefCell, rc::Rc}; use turkos_os_context_switch::context::*; use turkos_os_threads::threads::{StackObservation, ThreadRecord}; #[derive(Default)] struct Cpu { nonce: u64, registers: Option, depart_fail: Option, arrive_fail: bool, departures: u64, arrivals: u64, } struct Device { inner: saved::Backend, cpu: Rc>, } unsafe impl Backend for Device { type Binding = saved::Binding; type Prepared = saved::Prepared; type IdlePrepared = saved::IdlePrepared; fn execution(&self) -> Execution { self.inner.execution() } fn record(&self, b: &Self::Binding) -> Result { self.inner.record(b) } fn stack(&self, b: &Self::Binding) -> Result { self.inner.stack(b) } fn root(&self, b: &Self::Binding) -> Result { self.inner.root(b) } fn executable(&self, pc: u64) -> bool { self.inner.executable(pc) } fn initial_return_pc(&self, b: &Self::Binding) -> Result { self.inner.initial_return_pc(b) } fn capture_matches(&self, b: &Self::Binding, c: &Capture) -> bool { self.inner.capture_matches(b, c) } fn registers(&self) -> Registers { self.cpu.borrow().registers.unwrap() } fn prepare( &mut self, s: Option<&Self::Binding>, t: &Self::Binding, i: &Image, d: Disposition, ) -> Result { self.inner.prepare(s, t, i, d) } fn commit( &mut self, _: &mut Self::Prepared, _: Option<&Self::Binding>, _: &Self::Binding, _: &Image, _: Disposition, ) -> Result<(), Failure> { panic!("synchronous commit must never span an asynchronous handoff") } fn finish(&mut self, p: Self::Prepared) -> Result<(), (Error, Self::Prepared)> { self.inner.finish(p) } fn abort(&mut self, p: Self::Prepared) -> Result<(), (Error, Self::Prepared)> { self.inner.abort(p) } fn release(&mut self, p: Self::Binding) -> Result<(), (Error, Self::Binding)> { self.inner.release(p) } fn ready_empty(&self) -> bool { self.inner.ready_empty() } fn wake_epoch(&self) -> u64 { self.inner.wake_epoch() } fn idle_prepare(&mut self, e: u64) -> Result { self.inner.idle_prepare(e) } fn idle_commit(&mut self, p: &mut Self::IdlePrepared, e: u64) -> Result { self.inner.idle_commit(p, e) } fn idle_finish(&mut self, p: Self::IdlePrepared) -> Result<(), (Error, Self::IdlePrepared)> { self.inner.idle_finish(p) } fn idle_abort(&mut self, p: Self::IdlePrepared) -> Result<(), (Error, Self::IdlePrepared)> { self.inner.idle_abort(p) } } unsafe impl HandoffBackend for Device { fn depart( &mut self, p: &mut Self::Prepared, source: Option<&Self::Binding>, _: u64, d: Disposition, ) -> Result<(), Failure> { assert!(p.nonce > 0); let failure = self.cpu.borrow().depart_fail; if failure == Some(FailurePhase::BeforeSwitch) { return Err(Failure { error: Error::Backend, phase: FailurePhase::BeforeSwitch, }); } if let Some(source) = source { match d { Disposition::Ready => self .inner .table .borrow_mut() .yield_current(source.id) .unwrap(), Disposition::Sleeping => { self.inner.sleep_wait = Some( self.inner .table .borrow_mut() .sleep_current(source.id) .unwrap(), ); } } } self.inner.hardware.borrow_mut().current = None; self.cpu.borrow_mut().departures += 1; if failure == Some(FailurePhase::AfterSwitch) { return Err(Failure { error: Error::Backend, phase: FailurePhase::AfterSwitch, }); } Ok(()) } fn arrive( &mut self, _: &mut Self::Prepared, target: &Self::Binding, _: &Image, nonce: u64, ) -> Result<(), Error> { let cpu = self.cpu.borrow(); if cpu.nonce != nonce || cpu.registers.is_none() || cpu.arrive_fail { return Err(Error::Capture); } drop(cpu); self.inner .table .borrow_mut() .activate(target.id) .map_err(|_| Error::State)?; self.inner.hardware.borrow_mut().current = Some(target.id); self.cpu.borrow_mut().arrivals += 1; Ok(()) } } type Setup = ( AsyncBank, [Handle; 2], Rc>, Rc>, ); fn setup() -> Setup { let (b, pins, h) = saved::backend(2); let cpu = Rc::new(RefCell::new(Cpu::default())); let mut bank = ContextBank::<_, 4>::new(Device { inner: b, cpu: cpu.clone(), }) .ok() .unwrap(); let mut handles = pins.into_iter().map(|p| bank.attach(p).ok().unwrap()); let a = handles.next().unwrap(); let b = handles.next().unwrap(); (AsyncBank::new(bank).ok().unwrap(), [a, b], h, cpu) } fn physical_step(bank: &AsyncBank, token: &OwnedHandoff) { let image = bank.target_image(token).unwrap(); bank.bank().with_backend_observation(|d| { let actual = saved::Backend::expected(image); d.inner.hardware.borrow_mut().sp = image.sp() - 64; let mut cpu = d.cpu.borrow_mut(); cpu.nonce = token.nonce(); cpu.registers = Some(actual); }); } fn land(bank: &mut AsyncBank, token: OwnedHandoff) { let token = bank.depart(token).ok().unwrap(); physical_step(bank, &token); assert!(bank.landed(token).is_ok()); } fn capture( bank: &AsyncBank, source: Handle, h: &Rc>, value: u64, timer: bool, ) -> Capture { let r = bank.bank().record(source).unwrap(); let image = if timer { let mut f = turkos_os_exceptions::exceptions::Frame::EMPTY; f.interrupted_sp = 0x80004000 + r.thread.slot() as u64 * 0x20000; f.elr = saved::ENTRY + 0x100; f.spsr = 0x305; f.vector_slot = 5; f.current_el = 4; f.canary = turkos_os_exceptions::exceptions::FRAME_CANARY; Image::Timer(f) } else { let mut f = Cooperative::EMPTY; f.sp = 0x80003000 + r.thread.slot() as u64 * 0x20000; f.x19_x30 = core::array::from_fn(|i| value + i as u64); f.x19_x30[11] = saved::ENTRY + 0x200 + value % 64 * 4; f.q = core::array::from_fn(|i| [value * 100 + i as u64, value * 200 + i as u64]); f.fpcr = value as u32; f.fpsr = value as u32 + 1; Image::Cooperative(f) }; let episode = h.borrow().capture_episode + 1; let c = Capture { image, address: image.sp() - if timer { 848 } else { 640 }, abi: Abi::COOPERATIVE, episode, }; let mut bytes = Vec::new(); let mut word = |v: u64| bytes.extend_from_slice(&v.to_le_bytes()); match image { Image::Cooperative(f) => { word(f.sp); for v in f.x19_x30 { word(v) } word(f.padding); for q in f.q { for v in q { word(v) } } word(u64::from(f.fpcr) | u64::from(f.fpsr) << 32); word(f.canary) } Image::Timer(f) => { for v in f.x { word(v) } for v in [ f.interrupted_sp, f.sp_el0, f.elr, f.spsr, f.esr, f.far, f.vector_slot, ] { word(v) } for q in f.q { for v in q { word(v) } } word(u64::from(f.fpcr) | u64::from(f.fpsr) << 32); for v in [f.current_el, f.canary, f.padding] { word(v) } } _ => panic!(), } bank.bank().with_backend_observation(|d| { let record = d.inner.table.borrow().record(r.thread).unwrap(); let offset = (c.address - record.layout.base) as usize; d.inner.stacks[r.thread.slot()].borrow_mut()[offset..offset + bytes.len()] .copy_from_slice(&bytes); }); let mut hw = h.borrow_mut(); hw.sp = c.address - 64; hw.capture = Some(c); hw.capture_episode = episode; hw.in_irq = timer; c } #[test] fn staged_departed_landed_a_b_a_has_no_bank_loan_across_transfer() { let (mut bank, [a, b], h, _) = setup(); let t = bank.start(a).unwrap(); assert!(bank.quiescent().is_err()); land(&mut bank, t); let ca = capture(&bank, a, &h, 19, false); let t = bank.cooperate(a, b, ca).unwrap(); assert!(bank.quiescent().is_err()); let old = bank.bank().record(a).unwrap(); let t = bank.depart(t).ok().unwrap(); assert!(bank.quiescent().is_err()); assert_eq!(bank.bank().record(a).unwrap(), old); physical_step(&bank, &t); assert!(bank.landed(t).is_ok()); assert_eq!(bank.bank().record(a).unwrap().last_capture, ca.episode); let cb = capture(&bank, b, &h, 43, false); let t = bank.cooperate(b, a, cb).unwrap(); land(&mut bank, t); assert_eq!( bank.bank().with_backend_observation(|d| d.registers()), saved::Backend::expected(ca.image) ); assert_eq!(h.borrow().pins, 2); assert_eq!(h.borrow().stack_pages, 8); } #[test] fn before_departure_failure_returns_same_staged_nonce_and_prepared() { let (mut bank, [a, _], _, cpu) = setup(); let t = bank.start(a).unwrap(); let nonce = t.nonce(); let prepared = bank.with_prepared_observation(|p| p.unwrap().nonce); cpu.borrow_mut().depart_fail = Some(FailurePhase::BeforeSwitch); let t = match bank.depart(t) { Err(HandoffFailure::Before(Error::Backend, t)) => t, _ => panic!(), }; assert_eq!(t.nonce(), nonce); assert_eq!( bank.with_prepared_observation(|p| p.unwrap().nonce), prepared ); assert_eq!(bank.stage(), Some(HandoffStage::Staged)); assert!(!bank.quarantined()); cpu.borrow_mut().depart_fail = None; land(&mut bank, t); } #[test] fn changed_live_capture_keeps_same_handoff_before_any_source_change() { let (mut bank, [a, b], h, _) = setup(); let t = bank.start(a).unwrap(); land(&mut bank, t); let c = capture(&bank, a, &h, 19, false); let t = bank.cooperate(a, b, c).unwrap(); let nonce = t.nonce(); h.borrow_mut().capture = None; let t = match bank.depart(t) { Err(HandoffFailure::Before(Error::Capture, t)) => t, _ => panic!(), }; assert_eq!(t.nonce(), nonce); assert_eq!(bank.bank().record(a).unwrap().last_capture, 0); h.borrow_mut().capture = Some(c); land(&mut bank, t); } #[test] fn departed_failure_retains_real_prepared_source_capture_and_both_bindings() { let (mut bank, [a, b], h, cpu) = setup(); let t = bank.start(a).unwrap(); land(&mut bank, t); let c = capture(&bank, a, &h, 19, false); let t = bank.cooperate(a, b, c).unwrap(); cpu.borrow_mut().depart_fail = Some(FailurePhase::AfterSwitch); assert!(matches!( bank.depart(t), Err(HandoffFailure::Quarantined(Error::Backend)) )); assert!(bank.quarantined()); assert!(bank.quiescent().is_err()); let s = bank.bank().snapshot(); assert!(s.retained_switch && s.retained_capture && s.faulted); assert_eq!(s.retained_bindings, 2); assert_eq!(bank.bank().quarantined_capture(), Some(c)); assert_eq!(bank.bank().record(a).unwrap().last_capture, 0); assert_eq!(h.borrow().pins, 2); } #[test] fn landing_nonce_and_actual_register_corruption_never_publish() { for bad in 0..3 { let (mut bank, [a, _], h, cpu) = setup(); let t = bank.start(a).unwrap(); let t = bank.depart(t).ok().unwrap(); physical_step(&bank, &t); { let mut c = cpu.borrow_mut(); match bad { 0 => c.nonce += 1, 1 => c.registers.as_mut().unwrap().q[31][1] ^= 1, _ => c.registers.as_mut().unwrap().fpsr ^= 1, } } assert!(matches!( bank.landed(t), Err(HandoffFailure::Quarantined(_)) )); assert!(bank.quarantined()); assert_eq!(bank.bank().snapshot().retained_bindings, 2); assert!(bank.bank().snapshot().retained_switch); assert_eq!(h.borrow().pins, 2); } } #[test] fn finish_and_abort_failures_retain_exact_owned_prepared() { for after in [false, true] { let (mut bank, [a, _], h, _) = setup(); let t = bank.start(a).unwrap(); let prepared = bank.with_prepared_observation(|p| p.unwrap().nonce); if after { let t = bank.depart(t).ok().unwrap(); physical_step(&bank, &t); h.borrow_mut().finish_fail = true; assert!(matches!( bank.landed(t), Err(HandoffFailure::Quarantined(Error::Backend)) )); } else { h.borrow_mut().abort_fail = true; assert!(matches!( bank.abort(t), Err(HandoffFailure::Quarantined(Error::Backend)) )); } assert!(bank.quarantined()); assert_eq!( bank.with_prepared_observation(|p| p.unwrap().nonce), prepared ); assert_eq!(h.borrow().pins, 2); } } // Dropping this inert token must retain bank custody. #[test] #[allow(clippy::drop_non_drop)] fn forget_token_and_wrong_stage_retain_authority_and_block_ordinary_ops() { let (mut bank, [a, _], h, _) = setup(); let t = bank.start(a).unwrap(); let t = match bank.landed(t) { Err(HandoffFailure::Before(Error::State, t)) => t, _ => panic!(), }; let t = bank.depart(t).ok().unwrap(); let t = match bank.abort(t) { Err(HandoffFailure::Before(Error::State, t)) => t, _ => panic!(), }; drop(t); assert_eq!(bank.stage(), Some(HandoffStage::Departed)); assert!(bank.quiescent().is_err()); assert!(bank.bank().snapshot().retained_switch); assert_eq!(h.borrow().pins, 2); } #[test] fn deferred_original848_survives_intervening_work_and_fresh640_publication() { let (mut bank, [a, b], h, _) = setup(); let t = bank.start(a).unwrap(); land(&mut bank, t); let original = capture(&bank, a, &h, 19, true); h.borrow_mut().critical = 1; let bank_mut = bank.quiescent().unwrap(); bank_mut.defer_timer(a, b, original).unwrap(); h.borrow_mut().in_irq = false; h.borrow_mut().critical = 0; let fresh = capture(&bank, a, &h, 43, false); let t = bank.service_deferred(fresh).unwrap(); assert_eq!(bank.bank().deferred_record().unwrap().capture, original); land(&mut bank, t); assert_eq!( bank.bank().last_deferred_record().unwrap().capture, original ); assert_eq!(bank.bank().record(a).unwrap().image, fresh.image); assert_eq!(bank.bank().record(a).unwrap().last_capture, fresh.episode); assert!(bank.bank().deferred_record().is_none()); } #[test] fn maximum_capture_episode_consumes_only_after_successful_landing_finish() { for finish_failure in [false, true] { let (mut bank, [a, b], h, _) = setup(); let t = bank.start(a).unwrap(); land(&mut bank, t); h.borrow_mut().capture_episode = u64::MAX - 1; let c = capture(&bank, a, &h, 19, false); assert_eq!(c.episode, u64::MAX); let t = bank.cooperate(a, b, c).unwrap(); assert_eq!(bank.bank().record(a).unwrap().last_capture, 0); let t = bank.depart(t).ok().unwrap(); physical_step(&bank, &t); assert_eq!(bank.bank().record(a).unwrap().last_capture, 0); h.borrow_mut().finish_fail = finish_failure; let result = bank.landed(t); if finish_failure { assert!(matches!( result, Err(HandoffFailure::Quarantined(Error::Backend)) )); assert_eq!(bank.bank().record(a).unwrap().last_capture, 0); assert_eq!(bank.bank().quarantined_capture(), Some(c)); } else { assert!(result.is_ok()); assert_eq!(bank.bank().record(a).unwrap().last_capture, u64::MAX); assert!(bank.bank().quarantined_capture().is_none()); } assert_eq!(h.borrow().pins, 2); } }