aboutsummaryrefslogtreecommitdiff
path: root/embassy-time-driver/src/lib.rs
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-01-11 16:38:44 +0100
committerDario Nieuwenhuis <[email protected]>2024-01-11 16:56:08 +0100
commit15f94fb0fc70463b9a50c997083fee3f5758b17a (patch)
tree17ccb5d369a9e7b0d2dbe0c7e39804b7f1f65458 /embassy-time-driver/src/lib.rs
parentdcffad6b05a06bc5f4d09a35184d5ffe158e02cf (diff)
time: split driver into a separate embassy-time-driver crate.
Diffstat (limited to 'embassy-time-driver/src/lib.rs')
-rw-r--r--embassy-time-driver/src/lib.rs200
1 files changed, 200 insertions, 0 deletions
diff --git a/embassy-time-driver/src/lib.rs b/embassy-time-driver/src/lib.rs
new file mode 100644
index 000000000..39a772aa5
--- /dev/null
+++ b/embassy-time-driver/src/lib.rs
@@ -0,0 +1,200 @@
1#![no_std]
2#![doc = include_str!("../README.md")]
3#![warn(missing_docs)]
4
5//! Time driver interface
6//!
7//! This module defines the interface a driver needs to implement to power the `embassy_time` module.
8//!
9//! # Implementing a driver
10//!
11//! - Define a struct `MyDriver`
12//! - Implement [`Driver`] for it
13//! - Register it as the global driver with [`time_driver_impl`](crate::time_driver_impl).
14//! - Enable the Cargo feature `embassy-executor/time`
15//!
16//! If your driver has a single set tick rate, enable the corresponding [`tick-hz-*`](crate#tick-rate) feature,
17//! which will prevent users from needing to configure it themselves (or selecting an incorrect configuration).
18//!
19//! If your driver supports a small number of set tick rates, expose your own cargo features and have each one
20//! enable the corresponding `embassy-time/tick-*`.
21//!
22//! Otherwise, don’t enable any `tick-hz-*` feature to let the user configure the tick rate themselves by
23//! enabling a feature on `embassy-time`.
24//!
25//! # Linkage details
26//!
27//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions.
28//!
29//! `embassy` internally defines the driver functions as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls them.
30//! The driver crate defines the functions as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the
31//! calls from the `embassy` crate to call into the driver crate.
32//!
33//! If there is none or multiple drivers in the crate tree, linking will fail.
34//!
35//! This method has a few key advantages for something as foundational as timekeeping:
36//!
37//! - The time driver is available everywhere easily, without having to thread the implementation
38//! through generic parameters. This is especially helpful for libraries.
39//! - It means comparing `Instant`s will always make sense: if there were multiple drivers
40//! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which
41//! would yield incorrect results.
42//!
43//! # Example
44//!
45//! ```
46//! use embassy_time::driver::{Driver, AlarmHandle};
47//!
48//! struct MyDriver{} // not public!
49//!
50//! impl Driver for MyDriver {
51//! fn now(&self) -> u64 {
52//! todo!()
53//! }
54//! unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
55//! todo!()
56//! }
57//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
58//! todo!()
59//! }
60//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
61//! todo!()
62//! }
63//! }
64//! ```
65//! ```ignore
66//! embassy_time::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
67//! ```
68
69//! ## Feature flags
70#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
71
72mod tick;
73
74/// Ticks per second of the global timebase.
75///
76/// This value is specified by the [`tick-*` Cargo features](crate#tick-rate)
77pub const TICK_HZ: u64 = tick::TICK_HZ;
78
79/// Alarm handle, assigned by the driver.
80#[derive(Clone, Copy)]
81pub struct AlarmHandle {
82 id: u8,
83}
84
85impl AlarmHandle {
86 /// Create an AlarmHandle
87 ///
88 /// Safety: May only be called by the current global Driver impl.
89 /// The impl is allowed to rely on the fact that all `AlarmHandle` instances
90 /// are created by itself in unsafe code (e.g. indexing operations)
91 pub unsafe fn new(id: u8) -> Self {
92 Self { id }
93 }
94
95 /// Get the ID of the AlarmHandle.
96 pub fn id(&self) -> u8 {
97 self.id
98 }
99}
100
101/// Time driver
102pub trait Driver: Send + Sync + 'static {
103 /// Return the current timestamp in ticks.
104 ///
105 /// Implementations MUST ensure that:
106 /// - This is guaranteed to be monotonic, i.e. a call to now() will always return
107 /// a greater or equal value than earler calls. Time can't "roll backwards".
108 /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say
109 /// in 10_000 years (Human civilization is likely to already have self-destructed
110 /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers
111 /// you MUST extend them to 64-bit, for example by counting overflows in software,
112 /// or chaining multiple timers together.
113 fn now(&self) -> u64;
114
115 /// Try allocating an alarm handle. Returns None if no alarms left.
116 /// Initially the alarm has no callback set, and a null `ctx` pointer.
117 ///
118 /// # Safety
119 /// It is UB to make the alarm fire before setting a callback.
120 unsafe fn allocate_alarm(&self) -> Option<AlarmHandle>;
121
122 /// Sets the callback function to be called when the alarm triggers.
123 /// The callback may be called from any context (interrupt or thread mode).
124 fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
125
126 /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm
127 /// timestamp, the provided callback function will be called.
128 ///
129 /// The `Driver` implementation should guarantee that the alarm callback is never called synchronously from `set_alarm`.
130 /// Rather - if `timestamp` is already in the past - `false` should be returned and alarm should not be set,
131 /// or alternatively, the driver should return `true` and arrange to call the alarm callback as soon as possible, but not synchronously.
132 /// There is a rare third possibility that the alarm was barely in the future, and by the time it was enabled, it had slipped into the
133 /// past. This is can be detected by double-checking that the alarm is still in the future after enabling it; if it isn't, `false`
134 /// should also be returned to indicate that the callback may have been called already by the alarm, but it is not guaranteed, so the
135 /// caller should also call the callback, just like in the more common `false` case. (Note: This requires idempotency of the callback.)
136 ///
137 /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp.
138 ///
139 /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any.
140 fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool;
141}
142
143extern "Rust" {
144 fn _embassy_time_now() -> u64;
145 fn _embassy_time_allocate_alarm() -> Option<AlarmHandle>;
146 fn _embassy_time_set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
147 fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool;
148}
149
150/// See [`Driver::now`]
151pub fn now() -> u64 {
152 unsafe { _embassy_time_now() }
153}
154
155/// See [`Driver::allocate_alarm`]
156///
157/// Safety: it is UB to make the alarm fire before setting a callback.
158pub unsafe fn allocate_alarm() -> Option<AlarmHandle> {
159 _embassy_time_allocate_alarm()
160}
161
162/// See [`Driver::set_alarm_callback`]
163pub fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
164 unsafe { _embassy_time_set_alarm_callback(alarm, callback, ctx) }
165}
166
167/// See [`Driver::set_alarm`]
168pub fn set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool {
169 unsafe { _embassy_time_set_alarm(alarm, timestamp) }
170}
171
172/// Set the time Driver implementation.
173///
174/// See the module documentation for an example.
175#[macro_export]
176macro_rules! time_driver_impl {
177 (static $name:ident: $t: ty = $val:expr) => {
178 static $name: $t = $val;
179
180 #[no_mangle]
181 fn _embassy_time_now() -> u64 {
182 <$t as $crate::Driver>::now(&$name)
183 }
184
185 #[no_mangle]
186 unsafe fn _embassy_time_allocate_alarm() -> Option<$crate::AlarmHandle> {
187 <$t as $crate::Driver>::allocate_alarm(&$name)
188 }
189
190 #[no_mangle]
191 fn _embassy_time_set_alarm_callback(alarm: $crate::AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
192 <$t as $crate::Driver>::set_alarm_callback(&$name, alarm, callback, ctx)
193 }
194
195 #[no_mangle]
196 fn _embassy_time_set_alarm(alarm: $crate::AlarmHandle, timestamp: u64) -> bool {
197 <$t as $crate::Driver>::set_alarm(&$name, alarm, timestamp)
198 }
199 };
200}