//! Injected hardware exercises the production shared-state and IRQ mechanism. extern crate std; use super::{SharedTimer, TimerBackend}; use std::{ collections::VecDeque, sync::{Arc, Mutex, mpsc}, thread, time::Duration, vec::Vec, }; const CC1IF: u32 = 1 << 1; const UIF: u32 = 1; #[derive(Debug, PartialEq)] enum Access { Counter(u32), Ack, Compare(u32), Enable, Pend, } #[derive(Default)] struct Registers { cnt: u32, flags: u32, compare: u32, enabled: bool, pended: bool, // Advance while writing CCR. `true` means the new compare took effect // before CNT passed it, so hardware raises a fresh CC1IF. write_delays: VecDeque<(u32, bool)>, accesses: Vec, } struct Counter { registers: Arc>, read_gate: Option<(mpsc::Sender<()>, mpsc::Receiver<()>)>, } impl TimerBackend for Counter { fn counter(&mut self) -> u32 { let cnt = { let mut registers = self.registers.lock().unwrap(); let cnt = registers.cnt; registers.accesses.push(Access::Counter(cnt)); cnt }; if let Some((sampled, resume)) = self.read_gate.take() { sampled.send(()).unwrap(); resume.recv().unwrap(); } cnt } fn compare_pending(&mut self) -> bool { self.registers.lock().unwrap().flags & CC1IF != 0 } fn acknowledge_compare(&mut self) { let mut registers = self.registers.lock().unwrap(); registers.flags &= !CC1IF; registers.accesses.push(Access::Ack); } fn set_compare(&mut self, counter: u32) { let mut registers = self.registers.lock().unwrap(); registers.compare = counter; registers.accesses.push(Access::Compare(counter)); if let Some((advance, raises_flag)) = registers.write_delays.pop_front() { registers.cnt = registers.cnt.wrapping_add(advance); if raises_flag { registers.flags |= CC1IF; } } } fn enable_compare_interrupt(&mut self) { let mut registers = self.registers.lock().unwrap(); registers.enabled = true; registers.accesses.push(Access::Enable); } fn pend_interrupt(&mut self) { let mut registers = self.registers.lock().unwrap(); registers.pended = true; registers.accesses.push(Access::Pend); } } fn timer() -> (SharedTimer, Arc>) { let registers = Arc::new(Mutex::new(Registers::default())); let shared = SharedTimer::new(); assert!(shared.initialize(|| Counter { registers: registers.clone(), read_gate: None, })); (shared, registers) } fn tick(shared: &SharedTimer, registers: &Mutex, cnt: u32) -> bool { { let mut registers = registers.lock().unwrap(); registers.cnt = cnt; registers.flags |= CC1IF; } shared.on_interrupt() } #[test] fn one_time_publication_keeps_irq_disabled_until_successful_init_finishes() { let (shared, registers) = timer(); assert!(!shared.initialize(|| panic!("must not reinitialize hardware"))); assert!(!registers.lock().unwrap().enabled); // Other initialization may take time, or fail without ever enabling IRQs. registers.lock().unwrap().cnt = 2_000_000; registers.lock().unwrap().flags = CC1IF | UIF; assert_eq!(shared.now().as_microseconds(), 2_000_000); assert!(!registers.lock().unwrap().enabled); registers.lock().unwrap().accesses.clear(); shared.enable_interrupt(); let registers = registers.lock().unwrap(); assert!(registers.enabled); assert_eq!(registers.flags, UIF); assert_eq!( registers.accesses, [ Access::Ack, Access::Counter(2_000_000), Access::Compare(2_010_000), Access::Counter(2_000_000), Access::Enable, ] ); } #[test] fn readers_and_irq_extend_one_epoch_across_rollover() { let (shared, registers) = timer(); let reader_a = &shared; let reader_b = &shared; registers.lock().unwrap().cnt = u32::MAX - 5; assert_eq!(reader_a.now().as_microseconds(), 4_294_967_290); assert!(tick(&shared, ®isters, 4)); assert_eq!(reader_b.now().as_microseconds(), 4_294_967_300); assert_eq!(registers.lock().unwrap().compare, 2_704); registers.lock().unwrap().cnt = 100; assert_eq!(reader_a.now().as_microseconds(), 4_294_967_396); } #[test] fn future_compare_can_cross_counter_wrap() { let (shared, registers) = timer(); assert!(tick(&shared, ®isters, u32::MAX - 5)); let registers = registers.lock().unwrap(); assert_eq!(registers.compare, 2_704); assert!(!registers.pended); } #[test] fn silent_reader_retains_time_across_multiple_irq_serviced_wraps() { let (shared, registers) = timer(); let silent_reader = &shared; assert_eq!(silent_reader.now().as_microseconds(), 0); // Deliberately delayed service, still below the single-wrap observation // bound. The silent consumer misses more than two whole counter wraps. for second in 1..=10u64 { assert!(tick(&shared, ®isters, (second * 1_000_000_000) as u32)); } assert_eq!(silent_reader.now().as_microseconds(), 10_000_000_000); } #[test] fn on_time_and_two_second_delayed_irq_each_emit_one_event_and_future_compare() { let (shared, registers) = timer(); for (cnt, expected) in [(10_000, 20_000), (2_010_000, 2_020_000)] { registers.lock().unwrap().accesses.clear(); assert!(tick(&shared, ®isters, cnt)); assert_eq!(shared.now().as_microseconds(), u64::from(cnt)); // This represents the consumer callback, after on_interrupt returns. assert_eq!(registers.lock().unwrap().compare, expected); assert!(!shared.on_interrupt()); assert_eq!( registers .lock() .unwrap() .accesses .iter() .filter(|access| matches!(access, Access::Compare(_))) .count(), 1 ); } } #[test] fn stale_nvic_pend_without_cc1if_neither_rearms_nor_notifies() { let (shared, registers) = timer(); registers.lock().unwrap().flags = UIF; assert!(!shared.on_interrupt()); let registers = registers.lock().unwrap(); assert!(registers.accesses.is_empty()); assert_eq!(registers.flags, UIF); } #[test] fn target_passed_during_ccr_write_is_retried_before_callbacks() { let (shared, registers) = timer(); registers .lock() .unwrap() .write_delays .push_back((10_001, false)); assert!(tick(&shared, ®isters, 10_000)); assert_eq!(shared.now().as_microseconds(), 20_001); let registers = registers.lock().unwrap(); assert_eq!(registers.compare, 30_000); assert!(!registers.pended); assert_eq!( registers.accesses, [ Access::Ack, Access::Counter(10_000), Access::Compare(20_000), Access::Counter(20_001), Access::Compare(30_000), Access::Counter(20_001), Access::Counter(20_001), ] ); } #[test] fn repeated_missed_writes_pend_bounded_recovery_without_fabricating_an_event() { let (shared, registers) = timer(); registers.lock().unwrap().write_delays = [(10_001, false), (10_000, false)].into(); assert!(tick(&shared, ®isters, 10_000)); { let mut registers = registers.lock().unwrap(); assert!(registers.pended); assert_eq!(registers.compare, 30_000); assert_eq!(registers.cnt, 30_001); assert_eq!(registers.flags, 0); assert_eq!( registers .accesses .iter() .filter(|a| matches!(a, Access::Compare(_))) .count(), 2 ); registers.pended = false; // NVIC takes the recovery exception. } assert!(!shared.on_interrupt()); assert_eq!(registers.lock().unwrap().compare, 40_000); assert!(!registers.lock().unwrap().pended); assert!(tick(&shared, ®isters, 40_000)); assert_eq!(registers.lock().unwrap().compare, 50_000); } #[test] fn new_compare_flag_during_rearm_and_unrelated_flags_are_preserved() { let (shared, registers) = timer(); registers.lock().unwrap().flags = UIF; registers .lock() .unwrap() .write_delays .push_back((10_000, true)); assert!(tick(&shared, ®isters, 10_000)); assert_eq!(registers.lock().unwrap().flags, UIF | CC1IF); assert_eq!(registers.lock().unwrap().compare, 30_000); assert!(shared.on_interrupt()); assert_eq!(registers.lock().unwrap().flags, UIF); assert!(!shared.on_interrupt()); } #[test] fn irq_cannot_sample_between_a_readers_cnt_read_and_epoch_update() { let registers = Arc::new(Mutex::new(Registers::default())); let shared = SharedTimer::new(); let (sampled_tx, sampled_rx) = mpsc::channel(); let (resume_tx, resume_rx) = mpsc::channel(); assert!(shared.initialize(|| Counter { registers: registers.clone(), read_gate: Some((sampled_tx, resume_rx)), })); registers.lock().unwrap().cnt = 100; thread::scope(|scope| { let reader = scope.spawn(|| shared.now()); sampled_rx.recv().unwrap(); // Hardware continues ticking with PRIMASK set. The IRQ tries to run // while the reader has sampled CNT but has not committed its delta. registers.lock().unwrap().cnt = 150; registers.lock().unwrap().flags = CC1IF; let (attempt_tx, attempt_rx) = mpsc::channel(); let (done_tx, done_rx) = mpsc::channel(); let shared = &shared; let irq = scope.spawn(move || { attempt_tx.send(()).unwrap(); let event = shared.on_interrupt(); done_tx.send(()).unwrap(); event }); attempt_rx.recv().unwrap(); let blocked = done_rx.recv_timeout(Duration::from_millis(20)).is_err(); // Release before assertions so a failing test cannot strand either thread. resume_tx.send(()).unwrap(); assert_eq!(reader.join().unwrap().as_microseconds(), 100); assert!(irq.join().unwrap()); assert!(blocked, "IRQ entered while the reader was extending CNT"); }); assert_eq!(shared.now().as_microseconds(), 150); } #[test] fn full_wrap_blackout_reset_and_stopped_clock_are_not_detectable_from_cnt() { let (shared, registers) = timer(); registers.lock().unwrap().cnt = 100; assert_eq!(shared.now().as_microseconds(), 100); // Stopped/deep-sleep time and a whole unseen wrap both present CNT=100. // No software correction is possible. These modes require a system reset. assert_eq!(shared.now().as_microseconds(), 100); registers.lock().unwrap().cnt = ((1u64 << 32) + 100) as u32; assert_eq!(shared.now().as_microseconds(), 100); // An illicit reset/regression is indistinguishable from a forward wrap. registers.lock().unwrap().cnt = 90; assert_eq!(shared.now().as_microseconds(), 4_294_967_386); }