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<Timer>>,
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: &Timer) -> 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: &Timer) -> bool {
293            let mut current = self.head;
294            let mut prev: Option<NonNull<Timer>> = 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>(task_priority: u32) -> &'a CompatTimerQueue {
331            static TIMER_QUEUE: AtomicPtr<CompatTimerQueue> = AtomicPtr::new(core::ptr::null_mut());
332
333            #[cold]
334            #[inline(never)]
335            fn initialize<'a>(task_priority: u32) -> &'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                            unsafe {
357                                // It's okay to drop the thread pointer, the timer queue cannot be
358                                // stopped.
359                                crate::task_create(
360                                    "timer",
361                                    timer_task,
362                                    queue_ptr.as_ptr().cast(),
363                                    task_priority,
364                                    None,
365                                    8192,
366                                );
367                            }
368
369                            break queue_ptr;
370                        }
371                        Err(queue) => {
372                            // In case the queue is somehow still null, we will re-attempt storing
373                            // our own pointer.
374                            if let Some(queue_ptr) = NonNull::new(queue) {
375                                trace!("Timer queue already initialized");
376                                break queue_ptr;
377                            }
378                            trace!("Retrying initialization");
379                        }
380                    }
381                };
382
383                if forget {
384                    core::mem::forget(boxed);
385                }
386
387                unsafe { queue_ptr.as_ref() }
388            }
389
390            if let Some(queue) = NonNull::new(TIMER_QUEUE.load(Ordering::Acquire)) {
391                unsafe { queue.as_ref() }
392            } else {
393                initialize(task_priority)
394            }
395        }
396
397        /// Calls a closure with a mutable reference to the global timer queue.
398        ///
399        /// If the queue is not initialized, it will be initialized first.
400        fn with_global<F, R>(task_priority: u32, f: F) -> R
401        where
402            F: FnOnce(&mut TimerQueueInner) -> R,
403        {
404            let queue = Self::ensure_initialized(task_priority);
405            queue.with(f)
406        }
407
408        /// Calls a closure with a mutable reference to the timer queue, in case you already have a
409        /// reference to it.
410        fn with<F, R>(&self, f: F) -> R
411        where
412            F: FnOnce(&mut TimerQueueInner) -> R,
413        {
414            self.inner.with(f)
415        }
416
417        /// Trigger due timers.
418        ///
419        /// The timer queue needs to be re-processed when a new timer is armed, because the new
420        /// timer may need to be triggered before the next scheduled wakeup.
421        fn process(&self, semaphore: &SemaphoreHandle) {
422            debug!("Processing timer queue");
423            let mut timers = self.with(|q| {
424                q.processing = true;
425                q.next_wakeup = u64::MAX;
426                q.head.take()
427            });
428
429            while let Some(current) = timers {
430                trace!("Checking timer: {:x}", current.addr());
431                let current_timer = unsafe { current.as_ref() };
432
433                let run_callback = self.with(|q| {
434                    let props = current_timer.properties(q);
435
436                    // Remove current timer from the list.
437                    timers = props.next.take();
438                    props.enqueued = false;
439
440                    if !props.is_active {
441                        trace!(
442                            "Timer {:x} is inactive or dropped",
443                            current_timer as *const _ as usize
444                        );
445                        return false;
446                    }
447
448                    if props.next_due > crate::now() {
449                        // Not our time yet.
450                        trace!(
451                            "Timer {:x} is not due yet",
452                            current_timer as *const _ as usize
453                        );
454                        return false;
455                    }
456
457                    // Re-arm periodic timer
458                    if props.periodic {
459                        props.next_due += props.period;
460                    }
461                    props.is_active = props.periodic;
462                    true
463                });
464
465                if run_callback {
466                    debug!("Triggering timer: {:x}", current_timer as *const _ as usize);
467                    (current_timer.callback.borrow_mut())();
468                }
469
470                self.with(|q| {
471                    let props = current_timer.properties(q);
472
473                    if props.is_active {
474                        q.enqueue(current_timer);
475                    } else {
476                        trace!("Timer {:x} inactive", current_timer as *const _ as usize);
477                    }
478                });
479            }
480
481            let next_wakeup = self.with(|q| {
482                while let Some(timer) = q.scheduled_for_drop.pop() {
483                    trace!("Dropping timer {:x}", timer.as_ptr() as usize);
484                    let timer = unsafe { Box::from_raw(timer.cast::<Timer>().as_ptr()) };
485                    q.dequeue(&timer);
486                    core::mem::drop(timer);
487                }
488
489                q.processing = false;
490                q.next_wakeup
491            });
492
493            debug!("Timer queue next_wakeup: {}", next_wakeup);
494            semaphore.take_with_deadline(Some(next_wakeup));
495        }
496    }
497
498    struct TimerProperties {
499        is_active: bool,
500        next_due: u64,
501        period: u64,
502        periodic: bool,
503
504        enqueued: bool,
505        next: Option<NonNull<Timer>>,
506    }
507
508    struct TimerQueueCell<T>(UnsafeCell<T>);
509
510    impl<T> TimerQueueCell<T> {
511        const fn new(inner: T) -> Self {
512            Self(UnsafeCell::new(inner))
513        }
514
515        fn get_mut<'a>(&'a self, _q: &'a mut TimerQueueInner) -> &'a mut T {
516            unsafe { &mut *self.0.get() }
517        }
518    }
519
520    struct Timer {
521        callback: RefCell<Box<dyn FnMut() + Send>>,
522        // Timer properties, not available in `callback` due to how the timer is constructed.
523        timer_properties: TimerQueueCell<TimerProperties>,
524    }
525
526    impl Timer {
527        fn new(callback: Box<dyn FnMut() + Send>) -> Self {
528            Self {
529                callback: RefCell::new(callback),
530                timer_properties: TimerQueueCell::new(TimerProperties {
531                    is_active: false,
532                    next_due: 0,
533                    period: 0,
534                    periodic: false,
535
536                    enqueued: false,
537                    next: None,
538                }),
539            }
540        }
541
542        unsafe fn from_ptr<'a>(ptr: TimerPtr) -> &'a Self {
543            unsafe { ptr.cast::<Self>().as_mut() }
544        }
545
546        fn arm(
547            &self,
548            q: &mut TimerQueueInner,
549            timeout: u64,
550            periodic: bool,
551        ) -> Option<SemaphorePtr> {
552            let next_due = crate::now() + timeout;
553
554            let props = self.properties(q);
555            props.is_active = true;
556            props.next_due = next_due;
557            props.period = timeout;
558            props.periodic = periodic;
559
560            q.enqueue(self)
561        }
562
563        fn is_active(&self, q: &mut TimerQueueInner) -> bool {
564            self.properties(q).is_active
565        }
566
567        fn disarm(&self, q: &mut TimerQueueInner) {
568            self.properties(q).is_active = false;
569
570            // We don't dequeue the timer - processing the queue will just skip it. If we re-arm,
571            // the timer may already be in the queue.
572        }
573
574        fn properties<'a>(&'a self, q: &'a mut TimerQueueInner) -> &'a mut TimerProperties {
575            self.timer_properties.get_mut(q)
576        }
577    }
578
579    /// A timer implementation that uses a background thread.
580    ///
581    /// This implementation uses thread and Semaphore APIs that the RTOS implementation provides.
582    ///
583    /// To use this implementation, add `register_timer_implementation!(CompatTimer)` to your
584    /// implementation. The background task defaults to priority 2; select another priority with,
585    /// for example, `register_timer_implementation!(CompatTimer<1>)`. Priorities above the
586    /// scheduler's maximum supported priority are clamped to that maximum.
587    pub struct CompatTimer<const TASK_PRIORITY: u32 = 2>;
588
589    impl<const TASK_PRIORITY: u32> TimerImplementation for CompatTimer<TASK_PRIORITY> {
590        fn create(func: unsafe extern "C" fn(*mut c_void), data: *mut c_void) -> TimerPtr {
591            // TODO: get rid of the inner box (or its heap allocation) somehow
592            struct CCallback {
593                func: unsafe extern "C" fn(*mut c_void),
594                data: *mut c_void,
595            }
596            unsafe impl Send for CCallback {}
597
598            impl CCallback {
599                unsafe fn call(&mut self) {
600                    unsafe { (self.func)(self.data) }
601                }
602            }
603
604            let mut callback = CCallback { func, data };
605
606            let timer = Box::new(Timer::new(Box::new(move || unsafe { callback.call() })));
607            NonNull::from(Box::leak(timer)).cast()
608        }
609
610        unsafe fn delete(timer: TimerPtr) {
611            let mut semaphore_to_give = None;
612            CompatTimerQueue::with_global(TASK_PRIORITY, |q| {
613                // we don't drop the timer right now, since it might be
614                // processed currently
615                q.scheduled_for_drop.push(timer);
616
617                // make sure the queue will get processed soon
618                // and cleanup will happen
619                if !q.processing && q.next_wakeup == u64::MAX {
620                    q.next_wakeup = 0;
621
622                    semaphore_to_give = Some(q.semaphore);
623                }
624
625                let timer = unsafe { Timer::from_ptr(timer) };
626                timer.properties(q).is_active = false;
627            });
628
629            if let Some(semaphore_ptr) = semaphore_to_give {
630                let semaphore = unsafe { SemaphoreHandle::ref_from_ptr(&semaphore_ptr) };
631                semaphore.give();
632            }
633        }
634
635        unsafe fn arm(timer: TimerPtr, timeout: u64, periodic: bool) {
636            let timer = unsafe { Timer::from_ptr(timer) };
637            if let Some(semaphore_ptr) =
638                CompatTimerQueue::with_global(TASK_PRIORITY, |q| timer.arm(q, timeout, periodic))
639            {
640                let semaphore = unsafe { SemaphoreHandle::ref_from_ptr(&semaphore_ptr) };
641                semaphore.give();
642            }
643        }
644
645        unsafe fn is_active(timer: TimerPtr) -> bool {
646            let timer = unsafe { Timer::from_ptr(timer) };
647            CompatTimerQueue::with_global(TASK_PRIORITY, |q| timer.is_active(q))
648        }
649
650        unsafe fn disarm(timer: TimerPtr) {
651            let timer = unsafe { Timer::from_ptr(timer) };
652            CompatTimerQueue::with_global(TASK_PRIORITY, |q| timer.disarm(q))
653        }
654    }
655
656    /// Entry point for the timer task responsible for handling scheduled timer
657    /// events.
658    ///
659    /// The timer task is created when the first timer is armed.
660    pub(crate) extern "C" fn timer_task(queue_ptr: *mut c_void) {
661        let queue = unsafe {
662            NonNull::new(queue_ptr)
663                .unwrap()
664                .cast::<CompatTimerQueue>()
665                .as_ref()
666        };
667        let semaphore_ptr = queue.with(|inner| inner.semaphore);
668        let semaphore = unsafe { SemaphoreHandle::ref_from_ptr(&semaphore_ptr) };
669
670        // Wait for the semaphore to be signaled, there is nothing to do until then.
671        semaphore.take(None);
672
673        loop {
674            queue.process(semaphore);
675        }
676    }
677}
678
679#[cfg(feature = "ipc-implementations")]
680pub use implementation::CompatTimer;