embassy_time_driver/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#![warn(missing_docs)]
4
5//! ## Implementing a driver
6//!
7//! - Define a struct `MyDriver`
8//! - Implement [`Driver`] for it
9//! - Register it as the global driver with [`time_driver_impl`](crate::time_driver_impl).
10//!
11//! If your driver has a single set tick rate, enable the corresponding [`tick-hz-*`](crate#tick-rate) feature,
12//! which will prevent users from needing to configure it themselves (or selecting an incorrect configuration).
13//!
14//! If your driver supports a small number of set tick rates, expose your own cargo features and have each one
15//! enable the corresponding `embassy-time-driver/tick-*`.
16//!
17//! Otherwise, don’t enable any `tick-hz-*` feature to let the user configure the tick rate themselves by
18//! enabling a feature on `embassy-time`.
19//!
20//! ### Example
21//!
22//! ```
23//! use core::task::Waker;
24//!
25//! use embassy_time_driver::Driver;
26//!
27//! struct MyDriver{} // not public!
28//!
29//! impl Driver for MyDriver {
30//!     fn now(&self) -> u64 {
31//!         todo!()
32//!     }
33//!
34//!     fn schedule_wake(&self, at: u64, waker: &Waker) {
35//!         todo!()
36//!     }
37//! }
38//!
39//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
40//! ```
41//!
42//! ## Implementing the timer queue
43//!
44//! The simplest (but suboptimal) way to implement a timer queue is to define a single queue in the
45//! time driver. Declare a field protected by an appropriate mutex (e.g. `critical_section::Mutex`).
46//!
47//! Then, you'll need to adapt the `schedule_wake` method to use this queue.
48//!
49//! Note that if you are using multiple queues, you will need to ensure that a single timer
50//! queue item is only ever enqueued into a single queue at a time.
51//!
52//! ```
53//! use core::cell::RefCell;
54//! use core::task::Waker;
55//!
56//! use critical_section::{CriticalSection, Mutex};
57//! use embassy_time_queue_utils::Queue;
58//! use embassy_time_driver::Driver;
59//!
60//! struct MyDriver {
61//!     queue: Mutex<RefCell<Queue>>,
62//! }
63//!
64//! impl MyDriver {
65//!    fn set_alarm(&self, cs: &CriticalSection, at: u64) -> bool {
66//!        todo!()
67//!    }
68//! }
69//!
70//! impl Driver for MyDriver {
71//!     fn now(&self) -> u64 { todo!() }
72//!
73//!     fn schedule_wake(&self, at: u64, waker: &Waker) {
74//!         critical_section::with(|cs| {
75//!             let mut queue = self.queue.borrow(cs).borrow_mut();
76//!             if queue.schedule_wake(at, waker) {
77//!                 let mut next = queue.next_expiration(self.now());
78//!                 while !self.set_alarm(&cs, next) {
79//!                     next = queue.next_expiration(self.now());
80//!                 }
81//!             }
82//!         });
83//!     }
84//! }
85//! ```
86//!
87//! # Linkage details
88//!
89//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions.
90//!
91//! `embassy` internally defines the driver function as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls it.
92//! The driver crate defines the function as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the
93//! calls from the `embassy` crate to call into the driver crate.
94//!
95//! If there is none or multiple drivers in the crate tree, linking will fail.
96//!
97//! This method has a few key advantages for something as foundational as timekeeping:
98//!
99//! - The time driver is available everywhere easily, without having to thread the implementation
100//!   through generic parameters. This is especially helpful for libraries.
101//! - It means comparing `Instant`s will always make sense: if there were multiple drivers
102//!   active, one could compare an `Instant` from driver A to an `Instant` from driver B, which
103//!   would yield incorrect results.
104
105//! ## Feature flags
106#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
107
108use core::task::Waker;
109
110mod tick;
111
112/// Ticks per second of the global timebase.
113///
114/// This value is specified by the [`tick-*` Cargo features](crate#tick-rate)
115pub const TICK_HZ: u64 = tick::TICK_HZ;
116
117/// Time driver
118pub trait Driver: Send + Sync + 'static {
119    /// Return the current timestamp in ticks.
120    ///
121    /// Implementations MUST ensure that:
122    /// - This is guaranteed to be monotonic, i.e. a call to now() will always return
123    ///   a greater or equal value than earlier calls. Time can't "roll backwards".
124    /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say
125    ///   in 10_000 years (Human civilization is likely to already have self-destructed
126    ///   10_000 years from now.). This means if your hardware only has 16bit/32bit timers
127    ///   you MUST extend them to 64-bit, for example by counting overflows in software,
128    ///   or chaining multiple timers together.
129    fn now(&self) -> u64;
130
131    /// Schedules a waker to be awoken at moment `at`.
132    /// If this moment is in the past, the waker might be awoken immediately.
133    fn schedule_wake(&self, at: u64, waker: &Waker);
134}
135
136extern "Rust" {
137    fn _embassy_time_now() -> u64;
138    fn _embassy_time_schedule_wake(at: u64, waker: &Waker);
139}
140
141/// See [`Driver::now`]
142pub fn now() -> u64 {
143    unsafe { _embassy_time_now() }
144}
145
146/// Schedule the given waker to be woken at `at`.
147pub fn schedule_wake(at: u64, waker: &Waker) {
148    unsafe { _embassy_time_schedule_wake(at, waker) }
149}
150
151/// Set the time Driver implementation.
152///
153/// See the module documentation for an example.
154#[macro_export]
155macro_rules! time_driver_impl {
156    (static $name:ident: $t: ty = $val:expr) => {
157        static $name: $t = $val;
158
159        #[no_mangle]
160        fn _embassy_time_now() -> u64 {
161            <$t as $crate::Driver>::now(&$name)
162        }
163
164        #[no_mangle]
165        fn _embassy_time_schedule_wake(at: u64, waker: &core::task::Waker) {
166            <$t as $crate::Driver>::schedule_wake(&$name, at, waker);
167        }
168    };
169}