esp_hal_embassy/
time_driver.rs

1//! Embassy time driver implementation
2//!
3//! The time driver is responsible for keeping time, as well as to manage the
4//! wait queue for embassy-time.
5
6#[cfg(not(single_queue))]
7use core::cell::Cell;
8
9use embassy_time_driver::Driver;
10use esp_hal::{
11    interrupt::{InterruptHandler, Priority},
12    sync::Locked,
13    time::{Duration, Instant},
14    timer::OneShotTimer,
15    Blocking,
16};
17
18pub type Timer = OneShotTimer<'static, Blocking>;
19
20/// Alarm handle, assigned by the driver.
21#[derive(Clone, Copy)]
22pub(crate) struct AlarmHandle {
23    id: usize,
24}
25
26impl AlarmHandle {
27    /// Create an AlarmHandle
28    ///
29    /// Safety: May only be called by the current global Driver impl.
30    /// The impl is allowed to rely on the fact that all `AlarmHandle` instances
31    /// are created by itself in unsafe code (e.g. indexing operations)
32    pub unsafe fn new(id: usize) -> Self {
33        Self { id }
34    }
35
36    pub fn update(&self, expiration: u64) -> bool {
37        if expiration == u64::MAX {
38            true
39        } else {
40            DRIVER.set_alarm(*self, expiration)
41        }
42    }
43}
44
45enum AlarmState {
46    Created(extern "C" fn()),
47    Initialized(&'static mut Timer),
48}
49impl AlarmState {
50    fn initialize(timer: &'static mut Timer, interrupt_handler: InterruptHandler) -> AlarmState {
51        // If the driver is initialized, bind the interrupt handler to the
52        // timer. This ensures that alarms allocated after init are correctly
53        // bound to the core that created the executor.
54        timer.set_interrupt_handler(interrupt_handler);
55        timer.enable_interrupt(true);
56        AlarmState::Initialized(timer)
57    }
58}
59
60struct AlarmInner {
61    /// If multiple queues are used, we store the appropriate timer queue here.
62    // FIXME: we currently store the executor, but we could probably avoid an addition by actually
63    // storing a reference to the timer queue.
64    #[cfg(not(single_queue))]
65    pub context: Cell<*const ()>,
66
67    pub state: AlarmState,
68}
69
70struct Alarm {
71    // FIXME: we should be able to use priority-limited locks here, but we can initialize alarms
72    // while running at an arbitrary priority level. We need to rework alarm allocation to only use
73    // a critical section to allocate an alarm, but not when using it.
74    pub inner: Locked<AlarmInner>,
75}
76
77unsafe impl Send for Alarm {}
78
79impl Alarm {
80    pub const fn new(handler: extern "C" fn()) -> Self {
81        Self {
82            inner: Locked::new(AlarmInner {
83                #[cfg(not(single_queue))]
84                context: Cell::new(core::ptr::null_mut()),
85                state: AlarmState::Created(handler),
86            }),
87        }
88    }
89}
90
91/// embassy requires us to implement the [embassy_time_driver::Driver] trait,
92/// which we do here. This trait needs us to be able to tell the current time,
93/// as well as to schedule a wake-up at a certain time.
94///
95/// We are free to choose how we implement these features, and we provide
96/// two options:
97///
98/// - If the `generic` feature is enabled, we implement a single timer queue,
99///   using the implementation provided by embassy-time-queue-driver.
100/// - If the `single-integrated` feature is enabled, we implement a single timer
101///   queue, using our own integrated timer implementation. Our implementation
102///   is a copy of the embassy integrated timer queue, with the addition of
103///   clearing the "owner" information upon dequeueing.
104// TODO: restore this and update the "two options" above:
105// - If the `multiple-integrated` feature is enabled, we provide a separate
106//   timer queue for each executor. We store a separate timer queue for each
107//   executor, and we use the scheduled task's owner to determine which queue to
108//   use. This mode allows us to use less disruptive locks around the timer
109//   queue, but requires more timers - one per timer queue.
110pub(super) struct EmbassyTimer {
111    /// The timer queue, if we use a single one (single-integrated, or generic).
112    #[cfg(single_queue)]
113    pub(crate) inner: crate::timer_queue::TimerQueue,
114
115    alarms: [Alarm; MAX_SUPPORTED_ALARM_COUNT],
116    available_timers: Locked<Option<&'static mut [Timer]>>,
117}
118
119/// Repeats the `Alarm::new` constructor for each alarm, creating an interrupt
120/// handler for each of them.
121macro_rules! alarms {
122    ($($idx:literal),*) => {
123        [$(
124            Alarm::new({
125                // Not #[handler] so we don't have to store the priority - which is constant.
126                extern "C" fn handler() {
127                    DRIVER.on_interrupt($idx);
128                }
129                handler
130            })
131        ),*]
132    };
133}
134
135// TODO: we can reduce this to 1 for single_queue, but that would break current
136// tests. Resolve when tests can use separate configuration sets, or update
137// tests to always pass a single timer.
138const MAX_SUPPORTED_ALARM_COUNT: usize = 7;
139
140embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer {
141    // Single queue, needs maximum priority.
142    #[cfg(single_queue)]
143    inner: crate::timer_queue::TimerQueue::new(Priority::max()),
144    alarms: alarms!(0, 1, 2, 3, 4, 5, 6),
145    available_timers: Locked::new(None),
146});
147
148impl EmbassyTimer {
149    pub(super) fn init(timers: &'static mut [Timer]) {
150        assert!(
151            timers.len() <= MAX_SUPPORTED_ALARM_COUNT,
152            "Maximum {} timers can be used.",
153            MAX_SUPPORTED_ALARM_COUNT
154        );
155
156        // Reset timers
157        timers.iter_mut().for_each(|timer| {
158            timer.enable_interrupt(false);
159            timer.stop();
160        });
161
162        // Store the available timers
163        DRIVER
164            .available_timers
165            .with(|available_timers| *available_timers = Some(timers));
166    }
167
168    #[cfg(not(single_queue))]
169    pub(crate) fn set_callback_ctx(&self, alarm: AlarmHandle, ctx: *const ()) {
170        self.alarms[alarm.id].inner.with(|alarm| {
171            alarm.context.set(ctx.cast_mut());
172        })
173    }
174
175    fn on_interrupt(&self, id: usize) {
176        // On interrupt, we clear the alarm that was triggered...
177        #[cfg_attr(single_queue, allow(clippy::let_unit_value))]
178        let _ctx = self.alarms[id].inner.with(|alarm| {
179            if let AlarmState::Initialized(timer) = &mut alarm.state {
180                timer.clear_interrupt();
181                #[cfg(not(single_queue))]
182                alarm.context.get()
183            } else {
184                unsafe {
185                    // SAFETY: `on_interrupt` is registered right when the alarm is initialized.
186                    core::hint::unreachable_unchecked()
187                }
188            }
189        });
190
191        // ... and process the timer queue if we have one. For multiple queues, the
192        // timer queue is stored in the alarm's context.
193        #[cfg(all(integrated_timers, not(single_queue)))]
194        {
195            let executor = unsafe { &*_ctx.cast::<crate::executor::InnerExecutor>() };
196            executor.timer_queue.dispatch();
197        }
198
199        // If we have a single queue, it lives in this struct.
200        #[cfg(single_queue)]
201        self.inner.dispatch();
202    }
203
204    /// Returns `true` if the timer was armed, `false` if the timestamp is in
205    /// the past.
206    fn arm(timer: &mut Timer, timestamp: u64) -> bool {
207        let now = Instant::now().duration_since_epoch().as_micros();
208
209        if timestamp > now {
210            let timeout = Duration::from_micros(timestamp - now);
211            unwrap!(timer.schedule(timeout));
212            true
213        } else {
214            // If the timestamp is past, we return `false` to ask embassy to poll again
215            // immediately.
216            timer.stop();
217            false
218        }
219    }
220
221    /// Allocate an alarm, if possible.
222    ///
223    /// Returns `None` if there are no available alarms.
224    ///
225    /// When using multiple timer queues, the `priority` parameter indicates the
226    /// priority of the interrupt handler. It is 1 for thread-mode
227    /// executors, or equals to the priority of an interrupt executor.
228    ///
229    /// When using a single timer queue, the `priority` parameter is always the
230    /// highest value possible.
231    pub(crate) unsafe fn allocate_alarm(&self, priority: Priority) -> Option<AlarmHandle> {
232        for (i, alarm) in self.alarms.iter().enumerate() {
233            let handle = alarm.inner.with(|alarm| {
234                let AlarmState::Created(interrupt_handler) = alarm.state else {
235                    return None;
236                };
237
238                let timer = self.available_timers.with(|available_timers| {
239                    if let Some(timers) = available_timers.take() {
240                        // If the driver is initialized, we can allocate a timer.
241                        // If this fails, we can't do anything about it.
242                        let Some((timer, rest)) = timers.split_first_mut() else {
243                            not_enough_timers();
244                        };
245                        *available_timers = Some(rest);
246                        timer
247                    } else {
248                        panic!("schedule_wake called before esp_hal_embassy::init()")
249                    }
250                });
251
252                alarm.state = AlarmState::initialize(
253                    timer,
254                    InterruptHandler::new(interrupt_handler, priority),
255                );
256
257                Some(AlarmHandle::new(i))
258            });
259
260            if handle.is_some() {
261                return handle;
262            }
263        }
264
265        None
266    }
267
268    /// Set an alarm to fire at a certain timestamp.
269    ///
270    /// Returns `false` if the timestamp is in the past.
271    fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
272        let alarm = &self.alarms[alarm.id];
273
274        // The hardware fires the alarm even if timestamp is lower than the current
275        // time. In this case the interrupt handler will pend a wake-up when we exit the
276        // critical section.
277        //
278        // This is correct behavior. See https://docs.rs/embassy-time-driver/0.1.0/embassy_time_driver/trait.Driver.html#tymethod.set_alarm
279        // (... the driver should return true and arrange to call the alarm callback as
280        // soon as possible, but not synchronously.)
281
282        alarm.inner.with(|alarm| {
283            if let AlarmState::Initialized(timer) = &mut alarm.state {
284                Self::arm(timer, timestamp)
285            } else {
286                unsafe {
287                    // SAFETY: We only create `AlarmHandle` instances after the alarm is
288                    // initialized.
289                    core::hint::unreachable_unchecked()
290                }
291            }
292        })
293    }
294}
295
296impl Driver for EmbassyTimer {
297    fn now(&self) -> u64 {
298        Instant::now().duration_since_epoch().as_micros()
299    }
300
301    fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
302        #[cfg(not(single_queue))]
303        unsafe {
304            // If we have multiple queues, we have integrated timers and our own timer queue
305            // implementation.
306            use embassy_executor::raw::Executor as RawExecutor;
307            use portable_atomic::{AtomicPtr, Ordering};
308
309            let task = embassy_executor::raw::task_from_waker(waker);
310
311            // SAFETY: it is impossible to schedule a task that has not yet been spawned,
312            // so the executor is guaranteed to be set to a non-null value.
313            let mut executor = task.executor().unwrap_unchecked() as *const RawExecutor;
314
315            let owner = task
316                .timer_queue_item()
317                .payload
318                .as_ref::<AtomicPtr<RawExecutor>>();
319
320            // Try to take ownership over the timer item.
321            let owner = owner.compare_exchange(
322                core::ptr::null_mut(),
323                executor.cast_mut(),
324                Ordering::AcqRel,
325                Ordering::Acquire,
326            );
327
328            // We can't take ownership, but we may still be able to enqueue the task. Point
329            // at the current owner.
330            if let Err(owner) = owner {
331                executor = owner;
332            };
333
334            // It is possible that the task's owner changes in the mean time. It doesn't
335            // matter, at this point the only interesting question is: can we enqueue in the
336            // currently loaded owner's timer queue?
337
338            // Try to enqueue in the current owner's timer queue. This will fail if the
339            // owner has a lower priority ceiling than the current context.
340
341            // SAFETY: we've exposed provenance in `InnerExecutor::init`, which is called
342            // when the executor is started. Because the executor wasn't running
343            // before init, it is impossible to get a pointer here that has no
344            // provenance exposed.
345            // The cast is then safe, because the RawExecutor is the first field of the
346            // InnerExecutor, and repr(C) guarantees that the fields are laid out in the
347            // order they are defined, and the first field has 0 offset.
348            let executor_addr = executor as usize;
349            let executor = core::ptr::with_exposed_provenance_mut::<crate::executor::InnerExecutor>(
350                executor_addr,
351            );
352            (*executor).timer_queue.schedule_wake(at, waker);
353        }
354
355        #[cfg(single_queue)]
356        self.inner.schedule_wake(at, waker);
357    }
358}
359
360#[cold]
361#[track_caller]
362fn not_enough_timers() -> ! {
363    // This is wrapped in a separate function because rustfmt does not like
364    // extremely long strings. Also, if log is used, this avoids storing the string
365    // twice.
366    panic!("There are not enough timers to allocate a new alarm. Call esp_hal_embassy::init() with the correct number of timers, or consider either using the `single-integrated` or the `generic` timer queue flavors.");
367}
368
369pub(crate) fn set_up_alarm(priority: Priority, _ctx: *mut ()) -> AlarmHandle {
370    let alarm = unsafe {
371        DRIVER
372            .allocate_alarm(priority)
373            .unwrap_or_else(|| not_enough_timers())
374    };
375    #[cfg(not(single_queue))]
376    DRIVER.set_callback_ctx(alarm, _ctx);
377    alarm
378}