Skip to main content

systick_timer/
embassy_driver.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use core::cell::RefCell;
4use cortex_m::{
5    interrupt::{self, Mutex},
6    peripheral::SYST,
7};
8
9struct Wakeup {
10    wakeup_at: u64,
11    waker: core::task::Waker,
12}
13
14/// Core scheduling logic extracted for testability.
15/// Returns true if the wakeup was scheduled (or already exists), false if no slots available.
16fn schedule_wake_into_slots<const N: usize>(
17    slots: &mut [Option<Wakeup>; N],
18    at: u64,
19    waker: &core::task::Waker,
20) -> bool {
21    let mut empty_idx: Option<usize> = None;
22
23    // Single pass: scan all slots for duplicates while tracking first empty
24    for (i, slot) in slots.iter().enumerate() {
25        if let Some(wakeup) = slot {
26            if wakeup.waker.will_wake(waker) && wakeup.wakeup_at == at {
27                // Duplicate found - already scheduled
28                return true;
29            }
30        } else if empty_idx.is_none() {
31            empty_idx = Some(i);
32        }
33    }
34
35    // No duplicate found - insert into first empty slot
36    if let Some(i) = empty_idx {
37        slots[i] = Some(Wakeup {
38            wakeup_at: at,
39            waker: waker.clone(),
40        });
41        true
42    } else {
43        false
44    }
45}
46
47/// Very basic Embassy time driver that uses the SysTick timer.
48///
49/// Wakeups are stored in a fixed-size array
50///
51/// The driver has to be a static instance, create it with:
52///
53/// ```
54/// embassy_time_driver::time_driver_impl!(static DRIVER: SystickDriver<4>
55///     = SystickDriver::new(48_000_000, 47999));
56/// ```
57///
58pub struct SystickDriver<const N: usize> {
59    wakeup_at: Mutex<RefCell<[Option<Wakeup>; N]>>,
60    timer: crate::Timer,
61}
62
63impl<const N: usize> SystickDriver<N> {
64    /// SystickDriver constructor.
65    ///
66    /// # Arguments
67    ///
68    /// * `systick_freq` - The frequency of the SysTick timer in Hz.
69    /// * `reload_value` - The reload value for the SysTick timer.
70    ///
71    ///  Note the tick frequency is configured to embassy_time_driver::TICK_HZ.
72    ///
73    pub const fn new(systick_freq: u64, reload_value: u32) -> Self {
74        let timer = crate::Timer::new(embassy_time_driver::TICK_HZ, reload_value, systick_freq);
75        Self {
76            wakeup_at: Mutex::new(RefCell::new([const { None }; N])),
77            timer: timer,
78        }
79    }
80
81    /// Create a new SystickDriver with a default reload value that matches
82    /// the interrupt frequency to embassy_time_driver::TICK_HZ.
83    ///
84    /// The default reload value is calculated as (systick_freq / embassy_time_driver::TICK_HZ) - 1.
85    ///
86    /// # Arguments
87    ///
88    /// * `systick_freq` - The frequency of the SysTick timer in Hz.
89    ///
90    pub const fn new_default(systick_freq: u64) -> Self {
91        let reload = (systick_freq / embassy_time_driver::TICK_HZ) - 1;
92        Self::new(systick_freq, reload as u32)
93    }
94
95    fn maybe_wake(&self) {
96        interrupt::free(|cs| {
97            let mutex_borrow = &self.wakeup_at.borrow(cs);
98            for slot in mutex_borrow.borrow_mut().iter_mut() {
99                let mut cleared = false;
100                if let Some(wakeup) = slot {
101                    if self.timer.now() >= wakeup.wakeup_at {
102                        wakeup.waker.wake_by_ref();
103                        cleared = true;
104                    }
105                }
106                if cleared {
107                    *slot = None;
108                }
109            }
110        })
111    }
112
113    pub fn start(&self, syst: &mut SYST) {
114        self.timer.start(syst);
115    }
116
117    /// Call this from the SysTick interrupt handler.
118    pub fn systick_interrupt(&self) {
119        self.timer.systick_handler();
120        self.maybe_wake();
121    }
122}
123
124impl<const N: usize> embassy_time_driver::Driver for SystickDriver<N> {
125    fn now(&self) -> u64 {
126        self.timer.now()
127    }
128
129    fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
130        interrupt::free(|cs| {
131            let mutex_borrow = self.wakeup_at.borrow(cs);
132            let mut wakeups = mutex_borrow.borrow_mut();
133            if !schedule_wake_into_slots(&mut *wakeups, at, waker) {
134                panic!("No free wakeup slots");
135            }
136        })
137    }
138}
139
140#[cfg(feature = "embassy-defaults")]
141embassy_time_driver::time_driver_impl!(static DRIVER: SystickDriver<4> = SystickDriver::new(8_000_000, 7_999));
142
143#[cfg(feature = "embassy-defaults")]
144#[cortex_m_rt::exception]
145fn SysTick() {
146    DRIVER.systick_interrupt();
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use std::sync::Arc;
153    use std::task::Wake;
154
155    /// A simple Wake implementation for testing
156    struct TestWake;
157
158    impl Wake for TestWake {
159        fn wake(self: Arc<Self>) {}
160    }
161
162    fn make_waker() -> core::task::Waker {
163        Arc::new(TestWake).into()
164    }
165
166    fn make_distinct_waker() -> core::task::Waker {
167        // Each call creates a new Arc, so wakers won't be equal
168        Arc::new(TestWake).into()
169    }
170
171    fn count_occupied<const N: usize>(slots: &[Option<Wakeup>; N]) -> usize {
172        slots.iter().filter(|s| s.is_some()).count()
173    }
174
175    #[test]
176    fn test_basic_insert() {
177        let mut slots: [Option<Wakeup>; 4] = [const { None }; 4];
178        let waker = make_waker();
179
180        let result = schedule_wake_into_slots(&mut slots, 100, &waker);
181        assert!(result);
182        assert_eq!(count_occupied(&slots), 1);
183    }
184
185    #[test]
186    fn test_duplicate_detection_same_waker_same_time() {
187        let mut slots: [Option<Wakeup>; 4] = [const { None }; 4];
188        let waker = make_waker();
189
190        // First insert succeeds
191        assert!(schedule_wake_into_slots(&mut slots, 100, &waker));
192        assert_eq!(count_occupied(&slots), 1);
193
194        // Second insert with same waker and time - duplicate detected, still returns true
195        assert!(schedule_wake_into_slots(&mut slots, 100, &waker));
196        assert_eq!(count_occupied(&slots), 1); // Still only one slot used
197    }
198
199    #[test]
200    fn test_different_times_same_waker_creates_separate_slots() {
201        let mut slots: [Option<Wakeup>; 4] = [const { None }; 4];
202        let waker = make_waker();
203
204        schedule_wake_into_slots(&mut slots, 100, &waker);
205        schedule_wake_into_slots(&mut slots, 200, &waker);
206
207        assert_eq!(count_occupied(&slots), 2);
208    }
209
210    #[test]
211    fn test_different_wakers_same_time_creates_separate_slots() {
212        let mut slots: [Option<Wakeup>; 4] = [const { None }; 4];
213        let waker1 = make_distinct_waker();
214        let waker2 = make_distinct_waker();
215
216        schedule_wake_into_slots(&mut slots, 100, &waker1);
217        schedule_wake_into_slots(&mut slots, 100, &waker2);
218
219        assert_eq!(count_occupied(&slots), 2);
220    }
221
222    /// CRITICAL TEST: This is the bug that the original PR fix missed.
223    /// When slots = [None, Some(existing)], scheduling the same wakeup again
224    /// should detect the duplicate in slot[1], NOT fill slot[0].
225    #[test]
226    fn test_duplicate_detection_with_hole_before_existing() {
227        let mut slots: [Option<Wakeup>; 4] = [const { None }; 4];
228        let waker = make_waker();
229
230        // Manually create a "hole" scenario: [None, Some(wakeup), None, None]
231        slots[1] = Some(Wakeup {
232            wakeup_at: 100,
233            waker: waker.clone(),
234        });
235        assert_eq!(count_occupied(&slots), 1);
236
237        // Try to schedule the same waker/time - should detect duplicate
238        let result = schedule_wake_into_slots(&mut slots, 100, &waker);
239        assert!(result); // Returns true (already scheduled)
240        assert_eq!(count_occupied(&slots), 1); // Must NOT have added another slot
241
242        // Verify slot[0] is still empty (the flawed implementation would fill it)
243        assert!(slots[0].is_none());
244    }
245
246    /// Another hole scenario: duplicate exists at the end
247    #[test]
248    fn test_duplicate_detection_with_multiple_holes() {
249        let mut slots: [Option<Wakeup>; 4] = [const { None }; 4];
250        let waker = make_waker();
251
252        // Create: [None, None, None, Some(wakeup)]
253        slots[3] = Some(Wakeup {
254            wakeup_at: 100,
255            waker: waker.clone(),
256        });
257
258        let result = schedule_wake_into_slots(&mut slots, 100, &waker);
259        assert!(result);
260        assert_eq!(count_occupied(&slots), 1);
261    }
262
263    /// Test that holes get filled correctly when no duplicate exists
264    #[test]
265    fn test_fills_first_available_hole() {
266        let mut slots: [Option<Wakeup>; 4] = [const { None }; 4];
267        let waker1 = make_distinct_waker();
268        let waker2 = make_distinct_waker();
269
270        // Create: [None, Some(waker1@100), None, None]
271        slots[1] = Some(Wakeup {
272            wakeup_at: 100,
273            waker: waker1.clone(),
274        });
275
276        // Insert different waker - should fill slot[0]
277        let result = schedule_wake_into_slots(&mut slots, 100, &waker2);
278        assert!(result);
279        assert_eq!(count_occupied(&slots), 2);
280        assert!(slots[0].is_some()); // First empty slot was filled
281    }
282
283    #[test]
284    fn test_returns_false_when_full() {
285        let mut slots: [Option<Wakeup>; 2] = [const { None }; 2];
286        let waker1 = make_distinct_waker();
287        let waker2 = make_distinct_waker();
288        let waker3 = make_distinct_waker();
289
290        assert!(schedule_wake_into_slots(&mut slots, 100, &waker1));
291        assert!(schedule_wake_into_slots(&mut slots, 100, &waker2));
292        assert!(!schedule_wake_into_slots(&mut slots, 100, &waker3)); // Returns false
293    }
294
295    /// Simulate the select() polling scenario that triggered the original bug
296    #[test]
297    fn test_repeated_polling_scenario() {
298        let mut slots: [Option<Wakeup>; 4] = [const { None }; 4];
299        let waker = make_waker();
300
301        // Simulate multiple polls with same waker/time (what select() does)
302        for _ in 0..100 {
303            schedule_wake_into_slots(&mut slots, 1000, &waker);
304        }
305
306        // Should only have used ONE slot, not exhausted all slots
307        assert_eq!(count_occupied(&slots), 1);
308    }
309}