summaryrefslogtreecommitdiff
path: root/embassy-time/src/driver.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-time/src/driver.rs')
-rw-r--r--embassy-time/src/driver.rs174
1 files changed, 174 insertions, 0 deletions
diff --git a/embassy-time/src/driver.rs b/embassy-time/src/driver.rs
new file mode 100644
index 00000000..216b2740
--- /dev/null
+++ b/embassy-time/src/driver.rs
@@ -0,0 +1,174 @@
+//! Time driver interface
+//!
+//! This module defines the interface a driver needs to implement to power the `embassy_time` module.
+//!
+//! # Implementing a driver
+//!
+//! - Define a struct `MyDriver`
+//! - Implement [`Driver`] for it
+//! - Register it as the global driver with [`time_driver_impl`].
+//! - Enable the Cargo features `embassy-executor/time` and one of `embassy-time/tick-*` corresponding to the
+//! tick rate of your driver.
+//!
+//! If you wish to make the tick rate configurable by the end user, you should do so by exposing your own
+//! Cargo features and having each enable the corresponding `embassy-time/tick-*`.
+//!
+//! # Linkage details
+//!
+//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions.
+//!
+//! `embassy` internally defines the driver functions as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls them.
+//! The driver crate defines the functions as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the
+//! calls from the `embassy` crate to call into the driver crate.
+//!
+//! If there is none or multiple drivers in the crate tree, linking will fail.
+//!
+//! This method has a few key advantages for something as foundational as timekeeping:
+//!
+//! - The time driver is available everywhere easily, without having to thread the implementation
+//! through generic parameters. This is especially helpful for libraries.
+//! - It means comparing `Instant`s will always make sense: if there were multiple drivers
+//! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which
+//! would yield incorrect results.
+//!
+//! # Example
+//!
+//! ```
+//! use embassy_time::driver::{Driver, AlarmHandle};
+//!
+//! struct MyDriver{}; // not public!
+//! embassy_time::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
+//!
+//! impl Driver for MyDriver {
+//! fn now(&self) -> u64 {
+//! todo!()
+//! }
+//! unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
+//! todo!()
+//! }
+//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
+//! todo!()
+//! }
+//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
+//! todo!()
+//! }
+//! }
+//! ```
+
+/// Alarm handle, assigned by the driver.
+#[derive(Clone, Copy)]
+pub struct AlarmHandle {
+ id: u8,
+}
+
+impl AlarmHandle {
+ /// Create an AlarmHandle
+ ///
+ /// Safety: May only be called by the current global Driver impl.
+ /// The impl is allowed to rely on the fact that all `AlarmHandle` instances
+ /// are created by itself in unsafe code (e.g. indexing operations)
+ pub unsafe fn new(id: u8) -> Self {
+ Self { id }
+ }
+
+ /// Get the ID of the AlarmHandle.
+ pub fn id(&self) -> u8 {
+ self.id
+ }
+}
+
+/// Time driver
+pub trait Driver: Send + Sync + 'static {
+ /// Return the current timestamp in ticks.
+ ///
+ /// Implementations MUST ensure that:
+ /// - This is guaranteed to be monotonic, i.e. a call to now() will always return
+ /// a greater or equal value than earler calls. Time can't "roll backwards".
+ /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say
+ /// in 10_000 years (Human civilization is likely to already have self-destructed
+ /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers
+ /// you MUST extend them to 64-bit, for example by counting overflows in software,
+ /// or chaining multiple timers together.
+ fn now(&self) -> u64;
+
+ /// Try allocating an alarm handle. Returns None if no alarms left.
+ /// Initially the alarm has no callback set, and a null `ctx` pointer.
+ ///
+ /// # Safety
+ /// It is UB to make the alarm fire before setting a callback.
+ unsafe fn allocate_alarm(&self) -> Option<AlarmHandle>;
+
+ /// Sets the callback function to be called when the alarm triggers.
+ /// The callback may be called from any context (interrupt or thread mode).
+ fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
+
+ /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm
+ /// timestamp, the provided callback function will be called.
+ ///
+ /// If `timestamp` is already in the past, the alarm callback must be immediately fired.
+ /// In this case, it is allowed (but not mandatory) to call the alarm callback synchronously from `set_alarm`.
+ ///
+ /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp.
+ ///
+ /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any.
+ fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64);
+}
+
+extern "Rust" {
+ fn _embassy_time_now() -> u64;
+ fn _embassy_time_allocate_alarm() -> Option<AlarmHandle>;
+ fn _embassy_time_set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
+ fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64);
+}
+
+/// See [`Driver::now`]
+pub fn now() -> u64 {
+ unsafe { _embassy_time_now() }
+}
+
+/// See [`Driver::allocate_alarm`]
+///
+/// Safety: it is UB to make the alarm fire before setting a callback.
+pub unsafe fn allocate_alarm() -> Option<AlarmHandle> {
+ _embassy_time_allocate_alarm()
+}
+
+/// See [`Driver::set_alarm_callback`]
+pub fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
+ unsafe { _embassy_time_set_alarm_callback(alarm, callback, ctx) }
+}
+
+/// See [`Driver::set_alarm`]
+pub fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
+ unsafe { _embassy_time_set_alarm(alarm, timestamp) }
+}
+
+/// Set the time Driver implementation.
+///
+/// See the module documentation for an example.
+#[macro_export]
+macro_rules! time_driver_impl {
+ (static $name:ident: $t: ty = $val:expr) => {
+ static $name: $t = $val;
+
+ #[no_mangle]
+ fn _embassy_time_now() -> u64 {
+ <$t as $crate::driver::Driver>::now(&$name)
+ }
+
+ #[no_mangle]
+ unsafe fn _embassy_time_allocate_alarm() -> Option<$crate::driver::AlarmHandle> {
+ <$t as $crate::driver::Driver>::allocate_alarm(&$name)
+ }
+
+ #[no_mangle]
+ fn _embassy_time_set_alarm_callback(alarm: $crate::driver::AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
+ <$t as $crate::driver::Driver>::set_alarm_callback(&$name, alarm, callback, ctx)
+ }
+
+ #[no_mangle]
+ fn _embassy_time_set_alarm(alarm: $crate::driver::AlarmHandle, timestamp: u64) {
+ <$t as $crate::driver::Driver>::set_alarm(&$name, alarm, timestamp)
+ }
+ };
+}