Skip to main content

esp_radio_rtos_driver/
timer.rs

1//! Timers (callbacks scheduled to run in the future)
2//!
3//! ## Implementation
4//!
5//! Implement the `TimerImplementation` trait for an object, and use the
6//! `register_timer_implementation` to register that implementation for esp-radio.
7//!
8//! See the [`TimerImplementation`] documentation for more information.
9//!
10//! As an alternative, you may use the `CompatTimer` structure as your timer implementation.
11//!
12//! ## Usage
13//!
14//! Users should use [`TimerHandle`] to interact with timers created by the driver implementation.
15//!
16//! > Note that the only expected user of this crate is esp-radio.
17
18use core::{ffi::c_void, ptr::NonNull};
19
20/// Pointer to an opaque timer created by the driver implementation.
21pub type TimerPtr = NonNull<()>;
22
23unsafe extern "Rust" {
24    fn esp_rtos_timer_create(
25        function: unsafe extern "C" fn(*mut c_void),
26        data: *mut c_void,
27    ) -> TimerPtr;
28    fn esp_rtos_timer_delete(timer: TimerPtr);
29
30    fn esp_rtos_timer_arm(timer: TimerPtr, timeout: u64, periodic: bool);
31    fn esp_rtos_timer_is_active(timer: TimerPtr) -> bool;
32    fn esp_rtos_timer_disarm(timer: TimerPtr);
33}
34
35/// A timer implementation.
36///
37/// The following snippet demonstrates the boilerplate necessary to implement a timer using the
38/// `TimerImplementation` trait:
39///
40/// ```rust,no_run
41/// use esp_radio_rtos_driver::{
42///     register_timer_implementation,
43///     timer::{TimerImplementation, TimerPtr},
44/// };
45///
46/// struct MyTimer {
47///     // Timer implementation details
48/// }
49///
50/// impl TimerImplementation for MyTimer {
51///     fn create(function: unsafe extern "C" fn(*mut c_void), data: *mut c_void) -> TimerPtr {
52///         unimplemented!()
53///     }
54///
55///     unsafe fn delete(mutex: MutexPtr) {
56///         unimplemented!()
57///     }
58///
59///     unsafe fn arm(timer: TimerPtr, timeout: u64, periodic: bool) {
60///         unimplemented!()
61///     }
62///
63///     unsafe fn is_active(timer: TimerPtr) -> bool {
64///         unimplemented!()
65///     }
66///
67///     unsafe fn disarm(timer: TimerPtr) -> bool {
68///         unimplemented!()
69///     }
70/// }
71///
72/// register_timer_implementation!(MyTimer);
73/// ```
74pub trait TimerImplementation {
75    /// Creates a new timer instance from the given callback.
76    fn create(function: unsafe extern "C" fn(*mut c_void), data: *mut c_void) -> TimerPtr;
77
78    /// Deletes a timer instance.
79    ///
80    /// # Safety
81    ///
82    /// `timer` must be a pointer returned from [`Self::create`].
83    unsafe fn delete(timer: TimerPtr);
84
85    /// Configures the timer to be triggered after the given timeout.
86    ///
87    /// The timeout is specified in microsecond. If the timer is set to be periodic,
88    /// the timer will be triggered with a constant frequency.
89    ///
90    /// # Safety
91    ///
92    /// `timer` must be a pointer returned from [`Self::create`].
93    unsafe fn arm(timer: TimerPtr, timeout: u64, periodic: bool);
94
95    /// Checks if the timer is currently active.
96    ///
97    /// # Safety
98    ///
99    /// `timer` must be a pointer returned from [`Self::create`].
100    unsafe fn is_active(timer: TimerPtr) -> bool;
101
102    /// Stops the timer.
103    ///
104    /// # Safety
105    ///
106    /// `timer` must be a pointer returned from [`Self::create`].
107    unsafe fn disarm(timer: TimerPtr);
108}
109
110#[macro_export]
111macro_rules! register_timer_implementation {
112    ($t: ty) => {
113        #[unsafe(no_mangle)]
114        #[inline]
115        fn esp_rtos_timer_create(
116            function: unsafe extern "C" fn(*mut ::core::ffi::c_void),
117            data: *mut ::core::ffi::c_void,
118        ) -> $crate::timer::TimerPtr {
119            <$t as $crate::timer::TimerImplementation>::create(function, data)
120        }
121
122        #[unsafe(no_mangle)]
123        #[inline]
124        fn esp_rtos_timer_delete(timer: $crate::timer::TimerPtr) {
125            unsafe { <$t as $crate::timer::TimerImplementation>::delete(timer) }
126        }
127
128        #[unsafe(no_mangle)]
129        #[inline]
130        fn esp_rtos_timer_arm(timer: $crate::timer::TimerPtr, timeout: u64, periodic: bool) {
131            unsafe { <$t as $crate::timer::TimerImplementation>::arm(timer, timeout, periodic) }
132        }
133
134        #[unsafe(no_mangle)]
135        #[inline]
136        fn esp_rtos_timer_is_active(timer: $crate::timer::TimerPtr) -> bool {
137            unsafe { <$t as $crate::timer::TimerImplementation>::is_active(timer) }
138        }
139
140        #[unsafe(no_mangle)]
141        #[inline]
142        fn esp_rtos_timer_disarm(timer: $crate::timer::TimerPtr) {
143            unsafe { <$t as $crate::timer::TimerImplementation>::disarm(timer) }
144        }
145    };
146}
147
148/// A timer handle.
149///
150/// This handle is used to interact with timers created by the driver implementation.
151#[repr(transparent)]
152pub struct TimerHandle(TimerPtr);
153impl TimerHandle {
154    /// Creates a new timer instance from the given callback.
155    ///
156    /// # Safety
157    ///
158    /// - The callback and its data must be valid for the lifetime of the timer.
159    /// - The callback and its data need to be able to be sent across threads.
160    #[inline]
161    pub unsafe fn new(function: unsafe extern "C" fn(*mut c_void), data: *mut c_void) -> Self {
162        Self(unsafe { esp_rtos_timer_create(function, data) })
163    }
164
165    /// Converts this object into a pointer without dropping it.
166    #[inline]
167    pub fn leak(self) -> TimerPtr {
168        let ptr = self.0;
169        core::mem::forget(self);
170        ptr
171    }
172
173    /// Recovers the object from a leaked pointer.
174    ///
175    /// # Safety
176    ///
177    /// - The caller must only use pointers created using [`Self::leak`].
178    /// - The caller must ensure the pointer is not shared.
179    #[inline]
180    pub unsafe fn from_ptr(ptr: TimerPtr) -> Self {
181        Self(ptr)
182    }
183
184    /// Creates a reference to this object from a leaked pointer.
185    ///
186    /// This function is used in the esp-radio code to interact with the timer.
187    ///
188    /// # Safety
189    ///
190    /// - The caller must only use pointers created using [`Self::leak`].
191    #[inline]
192    pub unsafe fn ref_from_ptr(ptr: &TimerPtr) -> &Self {
193        unsafe { core::mem::transmute(ptr) }
194    }
195
196    /// Configures the timer to be triggered after the given timeout.
197    ///
198    /// The timeout is specified in microsecond. If the timer is set to be periodic,
199    /// the timer will be triggered with a constant frequency.
200    #[inline]
201    pub fn arm(&self, timeout: u64, periodic: bool) {
202        unsafe { esp_rtos_timer_arm(self.0, timeout, periodic) }
203    }
204
205    /// Checks if the timer is currently active.
206    #[inline]
207    pub fn is_active(&self) -> bool {
208        unsafe { esp_rtos_timer_is_active(self.0) }
209    }
210
211    /// Stops the timer.
212    #[inline]
213    pub fn disarm(&self) {
214        unsafe { esp_rtos_timer_disarm(self.0) }
215    }
216}
217
218impl Drop for TimerHandle {
219    #[inline]
220    fn drop(&mut self) {
221        unsafe { esp_rtos_timer_delete(self.0) };
222    }
223}
224
225#[cfg(feature = "ipc-implementations")]
226mod implementation {
227    use alloc::{boxed::Box, vec::Vec};
228    use core::{
229        cell::{RefCell, UnsafeCell},
230        ptr::NonNull,
231        sync::atomic::Ordering,
232    };
233
234    use esp_sync::NonReentrantMutex;
235    use portable_atomic::AtomicPtr;
236
237    use super::*;
238    use crate::semaphore::{SemaphoreHandle, SemaphoreKind, SemaphorePtr};
239
240    // The following code implements a timer queue based solely on portable_atomic and blocks
241    // defined in this crate.
242
243    struct TimerQueueInner {
244        // A linked list of active timers
245        head: Option<NonNull<CompatTimer>>,
246        next_wakeup: u64,
247        semaphore: SemaphorePtr,
248        processing: bool,
249        scheduled_for_drop: Vec<TimerPtr>,
250    }
251
252    unsafe impl Send for TimerQueueInner {}
253
254    impl TimerQueueInner {
255        fn new() -> Self {
256            Self {
257                head: None,
258                next_wakeup: u64::MAX,
259                semaphore: SemaphoreHandle::new(SemaphoreKind::Counting { max: 1, initial: 0 })
260                    .leak(),
261                processing: false,
262                scheduled_for_drop: Vec::new(),
263            }
264        }
265
266        /// Returns the Semaphore that should be given.
267        fn enqueue(&mut self, timer: &CompatTimer) -> Option<SemaphorePtr> {
268            let head = self.head;
269            let props = timer.properties(self);
270            let due = props.next_due;
271
272            if !props.enqueued {
273                trace!("Enqueueing timer {:x}", timer as *const _ as usize);
274                props.enqueued = true;
275
276                props.next = head;
277                self.head = Some(NonNull::from(timer));
278            } else {
279                trace!("Already enqueued timer {:x}", timer as *const _ as usize);
280            }
281
282            // If the timer is due before the next wakeup, wake the thread so it can put itself back
283            // to sleep with the right deadline.
284            if due < self.next_wakeup {
285                self.next_wakeup = due;
286                Some(self.semaphore)
287            } else {
288                None
289            }
290        }
291
292        fn dequeue(&mut self, timer: &CompatTimer) -> bool {
293            let mut current = self.head;
294            let mut prev: Option<NonNull<CompatTimer>> = None;
295
296            // Scan through the queue until we find the timer
297            while let Some(current_timer) = current {
298                if core::ptr::eq(current_timer.as_ptr(), timer) {
299                    // If we find the timer, remove it from the queue by bypassing it in the linked
300                    // list. The previous element, if any, will point at the next element.
301
302                    let timer_props = timer.properties(self);
303                    let next = timer_props.next.take();
304                    timer_props.enqueued = false;
305
306                    if let Some(mut p) = prev {
307                        unsafe { p.as_mut().properties(self).next = next };
308                    } else {
309                        self.head = next;
310                    }
311                    return true;
312                }
313
314                prev = current;
315                current = unsafe { current_timer.as_ref().properties(self).next };
316            }
317
318            false
319        }
320    }
321
322    struct CompatTimerQueue {
323        inner: NonReentrantMutex<TimerQueueInner>,
324    }
325
326    unsafe impl Send for CompatTimerQueue {}
327
328    impl CompatTimerQueue {
329        /// Ensures that the timer queue is initialized, then provides a reference to it.
330        fn ensure_initialized<'a>() -> &'a CompatTimerQueue {
331            static TIMER_QUEUE: AtomicPtr<CompatTimerQueue> = AtomicPtr::new(core::ptr::null_mut());
332
333            #[cold]
334            #[inline(never)]
335            fn initialize<'a>() -> &'a CompatTimerQueue {
336                trace!("Trying to initialize timer queue");
337                let boxed = Box::new(CompatTimerQueue {
338                    inner: NonReentrantMutex::new(TimerQueueInner::new()),
339                });
340                let queue_ptr = NonNull::from(boxed.as_ref());
341
342                let mut forget = false;
343                let queue_ptr = loop {
344                    match TIMER_QUEUE.compare_exchange(
345                        core::ptr::null_mut(),
346                        queue_ptr.as_ptr(),
347                        Ordering::SeqCst,
348                        Ordering::SeqCst,
349                    ) {
350                        Ok(_) => {
351                            // We're using our queue, forget it so we don't drop it.
352                            trace!("Successfully initialized timer queue");
353                            forget = true;
354
355                            // The winner also creates the timer task.
356                            let semaphore = boxed.with(|inner| inner.semaphore);
357                            unsafe {
358                                // It's okay to drop the thread pointer, the timer queue cannot be
359                                // stopped.
360                                crate::task_create(
361                                    "timer",
362                                    timer_task,
363                                    semaphore.as_ptr().cast(),
364                                    2,
365                                    None,
366                                    8192,
367                                );
368                            }
369
370                            break queue_ptr;
371                        }
372                        Err(queue) => {
373                            // In case the queue is somehow still null, we will re-attempt storing
374                            // our own pointer.
375                            if let Some(queue_ptr) = NonNull::new(queue) {
376                                trace!("Timer queue already initialized");
377                                break queue_ptr;
378                            }
379                            trace!("Retrying initialization");
380                        }
381                    }
382                };
383
384                if forget {
385                    core::mem::forget(boxed);
386                }
387
388                unsafe { queue_ptr.as_ref() }
389            }
390
391            if let Some(queue) = NonNull::new(TIMER_QUEUE.load(Ordering::Acquire)) {
392                unsafe { queue.as_ref() }
393            } else {
394                initialize()
395            }
396        }
397
398        /// Calls a closure with a mutable reference to the global timer queue.
399        ///
400        /// If the queue is not initialized, it will be initialized first.
401        fn with_global<F, R>(f: F) -> R
402        where
403            F: FnOnce(&mut TimerQueueInner) -> R,
404        {
405            let queue = Self::ensure_initialized();
406            queue.with(f)
407        }
408
409        /// Calls a closure with a mutable reference to the timer queue, in case you already have a
410        /// reference to it.
411        fn with<F, R>(&self, f: F) -> R
412        where
413            F: FnOnce(&mut TimerQueueInner) -> R,
414        {
415            self.inner.with(f)
416        }
417
418        /// Trigger due timers.
419        ///
420        /// The timer queue needs to be re-processed when a new timer is armed, because the new
421        /// timer may need to be triggered before the next scheduled wakeup.
422        fn process(&self, semaphore: &SemaphoreHandle) {
423            debug!("Processing timer queue");
424            let mut timers = self.with(|q| {
425                q.processing = true;
426                q.next_wakeup = u64::MAX;
427                q.head.take()
428            });
429
430            while let Some(current) = timers {
431                trace!("Checking timer: {:x}", current.addr());
432                let current_timer = unsafe { current.as_ref() };
433
434                let run_callback = self.with(|q| {
435                    let props = current_timer.properties(q);
436
437                    // Remove current timer from the list.
438                    timers = props.next.take();
439                    props.enqueued = false;
440
441                    if !props.is_active {
442                        trace!(
443                            "Timer {:x} is inactive or dropped",
444                            current_timer as *const _ as usize
445                        );
446                        return false;
447                    }
448
449                    if props.next_due > crate::now() {
450                        // Not our time yet.
451                        trace!(
452                            "Timer {:x} is not due yet",
453                            current_timer as *const _ as usize
454                        );
455                        return false;
456                    }
457
458                    // Re-arm periodic timer
459                    if props.periodic {
460                        props.next_due += props.period;
461                    }
462                    props.is_active = props.periodic;
463                    true
464                });
465
466                if run_callback {
467                    debug!("Triggering timer: {:x}", current_timer as *const _ as usize);
468                    (current_timer.callback.borrow_mut())();
469                }
470
471                self.with(|q| {
472                    let props = current_timer.properties(q);
473
474                    if props.is_active {
475                        q.enqueue(current_timer);
476                    } else {
477                        trace!("Timer {:x} inactive", current_timer as *const _ as usize);
478                    }
479                });
480            }
481
482            let next_wakeup = self.with(|q| {
483                while let Some(timer) = q.scheduled_for_drop.pop() {
484                    trace!("Dropping timer {:x}", timer.as_ptr() as usize);
485                    let timer = unsafe { Box::from_raw(timer.cast::<CompatTimer>().as_ptr()) };
486                    q.dequeue(&timer);
487                    core::mem::drop(timer);
488                }
489
490                q.processing = false;
491                q.next_wakeup
492            });
493
494            debug!("Timer queue next_wakeup: {}", next_wakeup);
495            semaphore.take_with_deadline(Some(next_wakeup));
496        }
497    }
498
499    struct TimerProperties {
500        is_active: bool,
501        next_due: u64,
502        period: u64,
503        periodic: bool,
504
505        enqueued: bool,
506        next: Option<NonNull<CompatTimer>>,
507    }
508
509    struct TimerQueueCell<T>(UnsafeCell<T>);
510
511    impl<T> TimerQueueCell<T> {
512        const fn new(inner: T) -> Self {
513            Self(UnsafeCell::new(inner))
514        }
515
516        fn get_mut<'a>(&'a self, _q: &'a mut TimerQueueInner) -> &'a mut T {
517            unsafe { &mut *self.0.get() }
518        }
519    }
520
521    /// A timer implementation that uses a background thread.
522    ///
523    /// This implementation uses thread and Semaphore APIs that the RTOS implementation provides.
524    ///
525    /// To use this implementation, add `register_timer_implementation!(CompatTimer)` to your
526    /// implementation.
527    pub struct CompatTimer {
528        callback: RefCell<Box<dyn FnMut() + Send>>,
529        // Timer properties, not available in `callback` due to how the timer is constructed.
530        timer_properties: TimerQueueCell<TimerProperties>,
531    }
532
533    impl CompatTimer {
534        pub fn new(callback: Box<dyn FnMut() + Send>) -> Self {
535            CompatTimer {
536                callback: RefCell::new(callback),
537                timer_properties: TimerQueueCell::new(TimerProperties {
538                    is_active: false,
539                    next_due: 0,
540                    period: 0,
541                    periodic: false,
542
543                    enqueued: false,
544                    next: None,
545                }),
546            }
547        }
548
549        unsafe fn from_ptr<'a>(ptr: TimerPtr) -> &'a Self {
550            unsafe { ptr.cast::<Self>().as_mut() }
551        }
552
553        fn arm(
554            &self,
555            q: &mut TimerQueueInner,
556            timeout: u64,
557            periodic: bool,
558        ) -> Option<SemaphorePtr> {
559            let next_due = crate::now() + timeout;
560
561            let props = self.properties(q);
562            props.is_active = true;
563            props.next_due = next_due;
564            props.period = timeout;
565            props.periodic = periodic;
566
567            q.enqueue(self)
568        }
569
570        fn is_active(&self, q: &mut TimerQueueInner) -> bool {
571            self.properties(q).is_active
572        }
573
574        fn disarm(&self, q: &mut TimerQueueInner) {
575            self.properties(q).is_active = false;
576
577            // We don't dequeue the timer - processing the queue will just skip it. If we re-arm,
578            // the timer may already be in the queue.
579        }
580
581        fn properties<'a>(&'a self, q: &'a mut TimerQueueInner) -> &'a mut TimerProperties {
582            self.timer_properties.get_mut(q)
583        }
584    }
585
586    impl TimerImplementation for CompatTimer {
587        fn create(func: unsafe extern "C" fn(*mut c_void), data: *mut c_void) -> TimerPtr {
588            // TODO: get rid of the inner box (or its heap allocation) somehow
589            struct CCallback {
590                func: unsafe extern "C" fn(*mut c_void),
591                data: *mut c_void,
592            }
593            unsafe impl Send for CCallback {}
594
595            impl CCallback {
596                unsafe fn call(&mut self) {
597                    unsafe { (self.func)(self.data) }
598                }
599            }
600
601            let mut callback = CCallback { func, data };
602
603            let timer = Box::new(CompatTimer::new(Box::new(move || unsafe {
604                callback.call()
605            })));
606            NonNull::from(Box::leak(timer)).cast()
607        }
608
609        unsafe fn delete(timer: TimerPtr) {
610            let mut semaphore_to_give = None;
611            CompatTimerQueue::with_global(|q| {
612                // we don't drop the timer right now, since it might be
613                // processed currently
614                q.scheduled_for_drop.push(timer);
615
616                // make sure the queue will get processed soon
617                // and cleanup will happen
618                if !q.processing && q.next_wakeup == u64::MAX {
619                    q.next_wakeup = 0;
620
621                    semaphore_to_give = Some(q.semaphore);
622                }
623
624                let timer = unsafe { CompatTimer::from_ptr(timer) };
625                timer.properties(q).is_active = false;
626            });
627
628            if let Some(semaphore_ptr) = semaphore_to_give {
629                let semaphore = unsafe { SemaphoreHandle::ref_from_ptr(&semaphore_ptr) };
630                semaphore.give();
631            }
632        }
633
634        unsafe fn arm(timer: TimerPtr, timeout: u64, periodic: bool) {
635            let timer = unsafe { CompatTimer::from_ptr(timer) };
636            if let Some(semaphore_ptr) =
637                CompatTimerQueue::with_global(|q| timer.arm(q, timeout, periodic))
638            {
639                let semaphore = unsafe { SemaphoreHandle::ref_from_ptr(&semaphore_ptr) };
640                semaphore.give();
641            }
642        }
643
644        unsafe fn is_active(timer: TimerPtr) -> bool {
645            let timer = unsafe { CompatTimer::from_ptr(timer) };
646            CompatTimerQueue::with_global(|q| timer.is_active(q))
647        }
648
649        unsafe fn disarm(timer: TimerPtr) {
650            let timer = unsafe { CompatTimer::from_ptr(timer) };
651            CompatTimerQueue::with_global(|q| timer.disarm(q))
652        }
653    }
654
655    /// Entry point for the timer task responsible for handling scheduled timer
656    /// events.
657    ///
658    /// The timer task is created when the first timer is armed.
659    pub(crate) extern "C" fn timer_task(semaphore_ptr: *mut c_void) {
660        let semaphore_ptr = NonNull::new(semaphore_ptr).unwrap().cast();
661        let semaphore = unsafe { SemaphoreHandle::ref_from_ptr(&semaphore_ptr) };
662
663        // Wait for the semaphore to be signaled, there is nothing to do until then.
664        semaphore.take(None);
665
666        let queue = CompatTimerQueue::ensure_initialized();
667
668        loop {
669            queue.process(semaphore);
670        }
671    }
672}
673
674#[cfg(feature = "ipc-implementations")]
675pub use implementation::CompatTimer;