embassy_time_driver/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
#![no_std]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
//! ## Implementing a driver
//!
//! - Define a struct `MyDriver`
//! - Implement [`Driver`] for it
//! - Register it as the global driver with [`time_driver_impl`](crate::time_driver_impl).
//!
//! If your driver has a single set tick rate, enable the corresponding [`tick-hz-*`](crate#tick-rate) feature,
//! which will prevent users from needing to configure it themselves (or selecting an incorrect configuration).
//!
//! If your driver supports a small number of set tick rates, expose your own cargo features and have each one
//! enable the corresponding `embassy-time-driver/tick-*`.
//!
//! Otherwise, don’t enable any `tick-hz-*` feature to let the user configure the tick rate themselves by
//! enabling a feature on `embassy-time`.
//!
//! ### Example
//!
//! ```
//! use core::task::Waker;
//!
//! use embassy_time_driver::Driver;
//!
//! struct MyDriver{} // not public!
//!
//! impl Driver for MyDriver {
//! fn now(&self) -> u64 {
//! todo!()
//! }
//!
//! fn schedule_wake(&self, at: u64, waker: &Waker) {
//! todo!()
//! }
//! }
//!
//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
//! ```
//!
//! ## Implementing the timer queue
//!
//! The simplest (but suboptimal) way to implement a timer queue is to define a single queue in the
//! time driver. Declare a field protected by an appropriate mutex (e.g. `critical_section::Mutex`).
//!
//! Then, you'll need to adapt the `schedule_wake` method to use this queue.
//!
//! Note that if you are using multiple queues, you will need to ensure that a single timer
//! queue item is only ever enqueued into a single queue at a time.
//!
//! ```
//! use core::cell::RefCell;
//! use core::task::Waker;
//!
//! use critical_section::{CriticalSection, Mutex};
//! use embassy_time_queue_utils::Queue;
//! use embassy_time_driver::Driver;
//!
//! struct MyDriver {
//! queue: Mutex<RefCell<Queue>>,
//! }
//!
//! impl MyDriver {
//! fn set_alarm(&self, cs: &CriticalSection, at: u64) -> bool {
//! todo!()
//! }
//! }
//!
//! impl Driver for MyDriver {
//! fn now(&self) -> u64 { todo!() }
//!
//! fn schedule_wake(&self, at: u64, waker: &Waker) {
//! critical_section::with(|cs| {
//! let mut queue = self.queue.borrow(cs).borrow_mut();
//! if queue.schedule_wake(at, waker) {
//! let mut next = queue.next_expiration(self.now());
//! while !self.set_alarm(&cs, next) {
//! next = queue.next_expiration(self.now());
//! }
//! }
//! });
//! }
//! }
//! ```
//!
//! # 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 function as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls it.
//! The driver crate defines the function 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.
//! ## Feature flags
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
use core::task::Waker;
mod tick;
/// Ticks per second of the global timebase.
///
/// This value is specified by the [`tick-*` Cargo features](crate#tick-rate)
pub const TICK_HZ: u64 = tick::TICK_HZ;
/// 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 earlier 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;
/// Schedules a waker to be awoken at moment `at`.
/// If this moment is in the past, the waker might be awoken immediately.
fn schedule_wake(&self, at: u64, waker: &Waker);
}
extern "Rust" {
fn _embassy_time_now() -> u64;
fn _embassy_time_schedule_wake(at: u64, waker: &Waker);
}
/// See [`Driver::now`]
pub fn now() -> u64 {
unsafe { _embassy_time_now() }
}
/// Schedule the given waker to be woken at `at`.
pub fn schedule_wake(at: u64, waker: &Waker) {
unsafe { _embassy_time_schedule_wake(at, waker) }
}
/// 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>::now(&$name)
}
#[no_mangle]
fn _embassy_time_schedule_wake(at: u64, waker: &core::task::Waker) {
<$t as $crate::Driver>::schedule_wake(&$name, at, waker);
}
};
}