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 | |
108 | use core::task::Waker; |
109 | |
110 | mod tick; |
111 | |
112 | /// Ticks per second of the global timebase. |
113 | /// |
114 | /// This value is specified by the [`tick-*` Cargo features](crate#tick-rate) |
115 | pub const TICK_HZ: u64 = tick::TICK_HZ; |
116 | |
117 | /// Time driver |
118 | pub 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 | |
136 | unsafeextern "Rust" { |
137 | unsafefn _embassy_time_now() -> u64; |
138 | unsafefn _embassy_time_schedule_wake(at: u64, waker: &Waker); |
139 | } |
140 | |
141 | /// See [`Driver::now`] |
142 | pub fn now() -> u64 { |
143 | unsafe { _embassy_time_now() } |
144 | } |
145 | |
146 | /// Schedule the given waker to be woken at `at`. |
147 | pub 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 ] |
155 | macro_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 | } |
170 | |