aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor/src/time/driver.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-executor/src/time/driver.rs')
-rw-r--r--embassy-executor/src/time/driver.rs170
1 files changed, 170 insertions, 0 deletions
diff --git a/embassy-executor/src/time/driver.rs b/embassy-executor/src/time/driver.rs
new file mode 100644
index 000000000..48e2f1c7d
--- /dev/null
+++ b/embassy-executor/src/time/driver.rs
@@ -0,0 +1,170 @@
1//! Time driver interface
2//!
3//! This module defines the interface a driver needs to implement to power the `embassy_executor::time` module.
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`].
10//! - Enable the Cargo features `embassy-executor/time` and one of `embassy-executor/time-tick-*` corresponding to the
11//! tick rate of your driver.
12//!
13//! If you wish to make the tick rate configurable by the end user, you should do so by exposing your own
14//! Cargo features and having each enable the corresponding `embassy-executor/time-tick-*`.
15//!
16//! # Linkage details
17//!
18//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions.
19//!
20//! `embassy` internally defines the driver functions as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls them.
21//! The driver crate defines the functions as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the
22//! calls from the `embassy` crate to call into the driver crate.
23//!
24//! If there is none or multiple drivers in the crate tree, linking will fail.
25//!
26//! This method has a few key advantages for something as foundational as timekeeping:
27//!
28//! - The time driver is available everywhere easily, without having to thread the implementation
29//! through generic parameters. This is especially helpful for libraries.
30//! - It means comparing `Instant`s will always make sense: if there were multiple drivers
31//! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which
32//! would yield incorrect results.
33//!
34//! # Example
35//!
36//! ```
37//! use embassy_executor::time::driver::{Driver, AlarmHandle};
38//!
39//! struct MyDriver{}; // not public!
40//! embassy_executor::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
41//!
42//! impl Driver for MyDriver {
43//! fn now(&self) -> u64 {
44//! todo!()
45//! }
46//! unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
47//! todo!()
48//! }
49//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
50//! todo!()
51//! }
52//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
53//! todo!()
54//! }
55//! }
56//! ```
57
58/// Alarm handle, assigned by the driver.
59#[derive(Clone, Copy)]
60pub struct AlarmHandle {
61 id: u8,
62}
63
64impl AlarmHandle {
65 /// Create an AlarmHandle
66 ///
67 /// Safety: May only be called by the current global Driver impl.
68 /// The impl is allowed to rely on the fact that all `AlarmHandle` instances
69 /// are created by itself in unsafe code (e.g. indexing operations)
70 pub unsafe fn new(id: u8) -> Self {
71 Self { id }
72 }
73
74 /// Get the ID of the AlarmHandle.
75 pub fn id(&self) -> u8 {
76 self.id
77 }
78}
79
80/// Time driver
81pub trait Driver: Send + Sync + 'static {
82 /// Return the current timestamp in ticks.
83 ///
84 /// Implementations MUST ensure that:
85 /// - This is guaranteed to be monotonic, i.e. a call to now() will always return
86 /// a greater or equal value than earler calls. Time can't "roll backwards".
87 /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say
88 /// in 10_000 years (Human civilization is likely to already have self-destructed
89 /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers
90 /// you MUST extend them to 64-bit, for example by counting overflows in software,
91 /// or chaining multiple timers together.
92 fn now(&self) -> u64;
93
94 /// Try allocating an alarm handle. Returns None if no alarms left.
95 /// Initially the alarm has no callback set, and a null `ctx` pointer.
96 ///
97 /// # Safety
98 /// It is UB to make the alarm fire before setting a callback.
99 unsafe fn allocate_alarm(&self) -> Option<AlarmHandle>;
100
101 /// Sets the callback function to be called when the alarm triggers.
102 /// The callback may be called from any context (interrupt or thread mode).
103 fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
104
105 /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm
106 /// timestamp, the provided callback function will be called.
107 ///
108 /// If `timestamp` is already in the past, the alarm callback must be immediately fired.
109 /// In this case, it is allowed (but not mandatory) to call the alarm callback synchronously from `set_alarm`.
110 ///
111 /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp.
112 ///
113 /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any.
114 fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64);
115}
116
117extern "Rust" {
118 fn _embassy_time_now() -> u64;
119 fn _embassy_time_allocate_alarm() -> Option<AlarmHandle>;
120 fn _embassy_time_set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
121 fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64);
122}
123
124pub(crate) fn now() -> u64 {
125 unsafe { _embassy_time_now() }
126}
127/// Safety: it is UB to make the alarm fire before setting a callback.
128pub(crate) unsafe fn allocate_alarm() -> Option<AlarmHandle> {
129 _embassy_time_allocate_alarm()
130}
131pub(crate) fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
132 unsafe { _embassy_time_set_alarm_callback(alarm, callback, ctx) }
133}
134pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
135 unsafe { _embassy_time_set_alarm(alarm, timestamp) }
136}
137
138/// Set the time Driver implementation.
139///
140/// See the module documentation for an example.
141#[macro_export]
142macro_rules! time_driver_impl {
143 (static $name:ident: $t: ty = $val:expr) => {
144 static $name: $t = $val;
145
146 #[no_mangle]
147 fn _embassy_time_now() -> u64 {
148 <$t as $crate::time::driver::Driver>::now(&$name)
149 }
150
151 #[no_mangle]
152 unsafe fn _embassy_time_allocate_alarm() -> Option<$crate::time::driver::AlarmHandle> {
153 <$t as $crate::time::driver::Driver>::allocate_alarm(&$name)
154 }
155
156 #[no_mangle]
157 fn _embassy_time_set_alarm_callback(
158 alarm: $crate::time::driver::AlarmHandle,
159 callback: fn(*mut ()),
160 ctx: *mut (),
161 ) {
162 <$t as $crate::time::driver::Driver>::set_alarm_callback(&$name, alarm, callback, ctx)
163 }
164
165 #[no_mangle]
166 fn _embassy_time_set_alarm(alarm: $crate::time::driver::AlarmHandle, timestamp: u64) {
167 <$t as $crate::time::driver::Driver>::set_alarm(&$name, alarm, timestamp)
168 }
169 };
170}