diff options
author | Dario Nieuwenhuis <dirbaio@dirbaio.net> | 2022-08-17 23:40:16 +0200 |
---|---|---|
committer | Dario Nieuwenhuis <dirbaio@dirbaio.net> | 2022-08-18 01:22:30 +0200 |
commit | 5daa173ce4b153a532b4daa9e94c7a248231f25b (patch) | |
tree | 2ef0b4d6f9b1c02dac2589e7b57982c20cbc0e66 /embassy-time | |
parent | 1c5b54a4823d596db730eb476c3ab78110557214 (diff) | |
download | embassy-5daa173ce4b153a532b4daa9e94c7a248231f25b.zip |
Split embassy-time from embassy-executor.
Diffstat (limited to 'embassy-time')
-rw-r--r-- | embassy-time/Cargo.toml | 54 | ||||
-rw-r--r-- | embassy-time/src/delay.rs | 98 | ||||
-rw-r--r-- | embassy-time/src/driver.rs | 174 | ||||
-rw-r--r-- | embassy-time/src/driver_std.rs | 208 | ||||
-rw-r--r-- | embassy-time/src/driver_wasm.rs | 134 | ||||
-rw-r--r-- | embassy-time/src/duration.rs | 184 | ||||
-rw-r--r-- | embassy-time/src/fmt.rs | 225 | ||||
-rw-r--r-- | embassy-time/src/instant.rs | 159 | ||||
-rw-r--r-- | embassy-time/src/lib.rs | 99 | ||||
-rw-r--r-- | embassy-time/src/timer.rs | 158 |
10 files changed, 1493 insertions, 0 deletions
diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml new file mode 100644 index 00000000..161c101f --- /dev/null +++ b/embassy-time/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "embassy-time" +version = "0.1.0" +edition = "2021" + + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-v$VERSION/embassy-time/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time/src/" +features = ["nightly", "defmt", "unstable-traits", "std"] +target = "x86_64-unknown-linux-gnu" + +[features] +std = ["tick-1mhz"] +wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-1mhz"] + +# Enable nightly-only features +nightly = ["embedded-hal-async"] + +# Implement embedded-hal 1.0 alpha and embedded-hal-async traits. +# Implement embedded-hal-async traits if `nightly` is set as well. +unstable-traits = ["embedded-hal-1"] + +# Display a timestamp of the number of seconds since startup next to defmt log messages +# To use this you must have a time driver provided. +defmt-timestamp-uptime = ["defmt"] + +# Set the `embassy_time` tick rate. +# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. +# If you're not writing your own driver, check the driver documentation to customize the tick rate. +# If you're writing a driver and your tick rate is not listed here, please add it and send a PR! +tick-32768hz = [] +tick-1000hz = [] +tick-1mhz = [] +tick-16mhz = [] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6" } +embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} +embedded-hal-async = { version = "0.1.0-alpha.1", optional = true} + +futures-util = { version = "0.3.17", default-features = false } +embassy-macros = { version = "0.1.0", path = "../embassy-macros"} +atomic-polyfill = "1.0.1" +critical-section = "1.1" +cfg-if = "1.0.0" + +# WASM dependencies +wasm-bindgen = { version = "0.2.76", features = ["nightly"], optional = true } +js-sys = { version = "0.3", optional = true } +wasm-timer = { version = "0.2.5", optional = true }
\ No newline at end of file diff --git a/embassy-time/src/delay.rs b/embassy-time/src/delay.rs new file mode 100644 index 00000000..d010fff9 --- /dev/null +++ b/embassy-time/src/delay.rs @@ -0,0 +1,98 @@ +use super::{Duration, Instant}; + +/// Blocks for at least `duration`. +pub fn block_for(duration: Duration) { + let expires_at = Instant::now() + duration; + while Instant::now() < expires_at {} +} + +/// Type implementing async delays and blocking `embedded-hal` delays. +/// +/// The delays are implemented in a "best-effort" way, meaning that the cpu will block for at least +/// the amount provided, but accuracy can be affected by many factors, including interrupt usage. +/// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently +/// active driver. +pub struct Delay; + +#[cfg(feature = "unstable-traits")] +mod eh1 { + use super::*; + + impl embedded_hal_1::delay::blocking::DelayUs for Delay { + type Error = core::convert::Infallible; + + fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { + Ok(block_for(Duration::from_micros(us as u64))) + } + + fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { + Ok(block_for(Duration::from_millis(ms as u64))) + } + } +} + +cfg_if::cfg_if! { + if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { + use crate::Timer; + use core::future::Future; + use futures_util::FutureExt; + + impl embedded_hal_async::delay::DelayUs for Delay { + type Error = core::convert::Infallible; + + type DelayUsFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a; + + fn delay_us(&mut self, micros: u32) -> Self::DelayUsFuture<'_> { + Timer::after(Duration::from_micros(micros as _)).map(Ok) + } + + type DelayMsFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a; + + fn delay_ms(&mut self, millis: u32) -> Self::DelayMsFuture<'_> { + Timer::after(Duration::from_millis(millis as _)).map(Ok) + } + } + } +} + +mod eh02 { + use embedded_hal_02::blocking::delay::{DelayMs, DelayUs}; + + use super::*; + + impl DelayMs<u8> for Delay { + fn delay_ms(&mut self, ms: u8) { + block_for(Duration::from_millis(ms as u64)) + } + } + + impl DelayMs<u16> for Delay { + fn delay_ms(&mut self, ms: u16) { + block_for(Duration::from_millis(ms as u64)) + } + } + + impl DelayMs<u32> for Delay { + fn delay_ms(&mut self, ms: u32) { + block_for(Duration::from_millis(ms as u64)) + } + } + + impl DelayUs<u8> for Delay { + fn delay_us(&mut self, us: u8) { + block_for(Duration::from_micros(us as u64)) + } + } + + impl DelayUs<u16> for Delay { + fn delay_us(&mut self, us: u16) { + block_for(Duration::from_micros(us as u64)) + } + } + + impl DelayUs<u32> for Delay { + fn delay_us(&mut self, us: u32) { + block_for(Duration::from_micros(us as u64)) + } + } +} 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) + } + }; +} diff --git a/embassy-time/src/driver_std.rs b/embassy-time/src/driver_std.rs new file mode 100644 index 00000000..2ddb2e60 --- /dev/null +++ b/embassy-time/src/driver_std.rs @@ -0,0 +1,208 @@ +use std::cell::UnsafeCell; +use std::mem::MaybeUninit; +use std::sync::{Condvar, Mutex, Once}; +use std::time::{Duration as StdDuration, Instant as StdInstant}; +use std::{mem, ptr, thread}; + +use atomic_polyfill::{AtomicU8, Ordering}; + +use crate::driver::{AlarmHandle, Driver}; + +const ALARM_COUNT: usize = 4; + +struct AlarmState { + timestamp: u64, + + // This is really a Option<(fn(*mut ()), *mut ())> + // but fn pointers aren't allowed in const yet + callback: *const (), + ctx: *mut (), +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: u64::MAX, + callback: ptr::null(), + ctx: ptr::null_mut(), + } + } +} + +struct TimeDriver { + alarm_count: AtomicU8, + + once: Once, + alarms: UninitCell<Mutex<[AlarmState; ALARM_COUNT]>>, + zero_instant: UninitCell<StdInstant>, + signaler: UninitCell<Signaler>, +} + +const ALARM_NEW: AlarmState = AlarmState::new(); +crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { + alarm_count: AtomicU8::new(0), + + once: Once::new(), + alarms: UninitCell::uninit(), + zero_instant: UninitCell::uninit(), + signaler: UninitCell::uninit(), +}); + +impl TimeDriver { + fn init(&self) { + self.once.call_once(|| unsafe { + self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); + self.zero_instant.write(StdInstant::now()); + self.signaler.write(Signaler::new()); + + thread::spawn(Self::alarm_thread); + }); + } + + fn alarm_thread() { + let zero = unsafe { DRIVER.zero_instant.read() }; + loop { + let now = DRIVER.now(); + + let mut next_alarm = u64::MAX; + { + let alarms = &mut *unsafe { DRIVER.alarms.as_ref() }.lock().unwrap(); + for alarm in alarms { + if alarm.timestamp <= now { + alarm.timestamp = u64::MAX; + + // Call after clearing alarm, so the callback can set another alarm. + + // safety: + // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. + // - other than that we only store valid function pointers into alarm.callback + let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback) }; + f(alarm.ctx); + } else { + next_alarm = next_alarm.min(alarm.timestamp); + } + } + } + + // Ensure we don't overflow + let until = zero + .checked_add(StdDuration::from_micros(next_alarm)) + .unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1)); + + unsafe { DRIVER.signaler.as_ref() }.wait_until(until); + } + } +} + +impl Driver for TimeDriver { + fn now(&self) -> u64 { + self.init(); + + let zero = unsafe { self.zero_instant.read() }; + StdInstant::now().duration_since(zero).as_micros() as u64 + } + + unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> { + let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { + if x < ALARM_COUNT as u8 { + Some(x + 1) + } else { + None + } + }); + + match id { + Ok(id) => Some(AlarmHandle::new(id)), + Err(_) => None, + } + } + + fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { + self.init(); + let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.callback = callback as *const (); + alarm.ctx = ctx; + } + + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + self.init(); + let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.timestamp = timestamp; + unsafe { self.signaler.as_ref() }.signal(); + } +} + +struct Signaler { + mutex: Mutex<bool>, + condvar: Condvar, +} + +impl Signaler { + fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait_until(&self, until: StdInstant) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + let now = StdInstant::now(); + + if now >= until { + break; + } + + let dur = until - now; + let (signaled2, timeout) = self.condvar.wait_timeout(signaled, dur).unwrap(); + signaled = signaled2; + if timeout.timed_out() { + break; + } + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } +} + +pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>); +unsafe impl<T> Send for UninitCell<T> {} +unsafe impl<T> Sync for UninitCell<T> {} + +impl<T> UninitCell<T> { + pub const fn uninit() -> Self { + Self(MaybeUninit::uninit()) + } + + pub unsafe fn as_ptr(&self) -> *const T { + (*self.0.as_ptr()).get() + } + + pub unsafe fn as_mut_ptr(&self) -> *mut T { + (*self.0.as_ptr()).get() + } + + pub unsafe fn as_ref(&self) -> &T { + &*self.as_ptr() + } + + pub unsafe fn write(&self, val: T) { + ptr::write(self.as_mut_ptr(), val) + } +} + +impl<T: Copy> UninitCell<T> { + pub unsafe fn read(&self) -> T { + ptr::read(self.as_mut_ptr()) + } +} diff --git a/embassy-time/src/driver_wasm.rs b/embassy-time/src/driver_wasm.rs new file mode 100644 index 00000000..e4497e6a --- /dev/null +++ b/embassy-time/src/driver_wasm.rs @@ -0,0 +1,134 @@ +use std::cell::UnsafeCell; +use std::mem::MaybeUninit; +use std::ptr; +use std::sync::{Mutex, Once}; + +use atomic_polyfill::{AtomicU8, Ordering}; +use wasm_bindgen::prelude::*; +use wasm_timer::Instant as StdInstant; + +use crate::driver::{AlarmHandle, Driver}; + +const ALARM_COUNT: usize = 4; + +struct AlarmState { + token: Option<f64>, + closure: Option<Closure<dyn FnMut() + 'static>>, +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + token: None, + closure: None, + } + } +} + +#[wasm_bindgen] +extern "C" { + fn setTimeout(closure: &Closure<dyn FnMut()>, millis: u32) -> f64; + fn clearTimeout(token: f64); +} + +struct TimeDriver { + alarm_count: AtomicU8, + + once: Once, + alarms: UninitCell<Mutex<[AlarmState; ALARM_COUNT]>>, + zero_instant: UninitCell<StdInstant>, +} + +const ALARM_NEW: AlarmState = AlarmState::new(); +crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { + alarm_count: AtomicU8::new(0), + once: Once::new(), + alarms: UninitCell::uninit(), + zero_instant: UninitCell::uninit(), +}); + +impl TimeDriver { + fn init(&self) { + self.once.call_once(|| unsafe { + self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); + self.zero_instant.write(StdInstant::now()); + }); + } +} + +impl Driver for TimeDriver { + fn now(&self) -> u64 { + self.init(); + + let zero = unsafe { self.zero_instant.read() }; + StdInstant::now().duration_since(zero).as_micros() as u64 + } + + unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> { + let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { + if x < ALARM_COUNT as u8 { + Some(x + 1) + } else { + None + } + }); + + match id { + Ok(id) => Some(AlarmHandle::new(id)), + Err(_) => None, + } + } + + fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { + self.init(); + let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.closure.replace(Closure::new(move || { + callback(ctx); + })); + } + + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + self.init(); + let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); + let alarm = &mut alarms[alarm.id() as usize]; + let timeout = (timestamp - self.now()) as u32; + if let Some(token) = alarm.token { + clearTimeout(token); + } + alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000)); + } +} + +pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>); +unsafe impl<T> Send for UninitCell<T> {} +unsafe impl<T> Sync for UninitCell<T> {} + +impl<T> UninitCell<T> { + pub const fn uninit() -> Self { + Self(MaybeUninit::uninit()) + } + unsafe fn as_ptr(&self) -> *const T { + (*self.0.as_ptr()).get() + } + + pub unsafe fn as_mut_ptr(&self) -> *mut T { + (*self.0.as_ptr()).get() + } + + pub unsafe fn as_ref(&self) -> &T { + &*self.as_ptr() + } + + pub unsafe fn write(&self, val: T) { + ptr::write(self.as_mut_ptr(), val) + } +} + +impl<T: Copy> UninitCell<T> { + pub unsafe fn read(&self) -> T { + ptr::read(self.as_mut_ptr()) + } +} diff --git a/embassy-time/src/duration.rs b/embassy-time/src/duration.rs new file mode 100644 index 00000000..dc4f16bd --- /dev/null +++ b/embassy-time/src/duration.rs @@ -0,0 +1,184 @@ +use core::fmt; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +use super::{GCD_1K, GCD_1M, TICKS_PER_SECOND}; + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the difference between two [Instant](struct.Instant.html)s +pub struct Duration { + pub(crate) ticks: u64, +} + +impl Duration { + /// The smallest value that can be represented by the `Duration` type. + pub const MIN: Duration = Duration { ticks: u64::MIN }; + /// The largest value that can be represented by the `Duration` type. + pub const MAX: Duration = Duration { ticks: u64::MAX }; + + /// Tick count of the `Duration`. + pub const fn as_ticks(&self) -> u64 { + self.ticks + } + + /// Convert the `Duration` to seconds, rounding down. + pub const fn as_secs(&self) -> u64 { + self.ticks / TICKS_PER_SECOND + } + + /// Convert the `Duration` to milliseconds, rounding down. + pub const fn as_millis(&self) -> u64 { + self.ticks * (1000 / GCD_1K) / (TICKS_PER_SECOND / GCD_1K) + } + + /// Convert the `Duration` to microseconds, rounding down. + pub const fn as_micros(&self) -> u64 { + self.ticks * (1_000_000 / GCD_1M) / (TICKS_PER_SECOND / GCD_1M) + } + + /// Creates a duration from the specified number of clock ticks + pub const fn from_ticks(ticks: u64) -> Duration { + Duration { ticks } + } + + /// Creates a duration from the specified number of seconds, rounding up. + pub const fn from_secs(secs: u64) -> Duration { + Duration { + ticks: secs * TICKS_PER_SECOND, + } + } + + /// Creates a duration from the specified number of milliseconds, rounding up. + pub const fn from_millis(millis: u64) -> Duration { + Duration { + ticks: div_ceil(millis * (TICKS_PER_SECOND / GCD_1K), 1000 / GCD_1K), + } + } + + /// Creates a duration from the specified number of microseconds, rounding up. + /// NOTE: Delays this small may be inaccurate. + pub const fn from_micros(micros: u64) -> Duration { + Duration { + ticks: div_ceil(micros * (TICKS_PER_SECOND / GCD_1M), 1_000_000 / GCD_1M), + } + } + + /// Creates a duration from the specified number of seconds, rounding down. + pub const fn from_secs_floor(secs: u64) -> Duration { + Duration { + ticks: secs * TICKS_PER_SECOND, + } + } + + /// Creates a duration from the specified number of milliseconds, rounding down. + pub const fn from_millis_floor(millis: u64) -> Duration { + Duration { + ticks: millis * (TICKS_PER_SECOND / GCD_1K) / (1000 / GCD_1K), + } + } + + /// Creates a duration from the specified number of microseconds, rounding down. + /// NOTE: Delays this small may be inaccurate. + pub const fn from_micros_floor(micros: u64) -> Duration { + Duration { + ticks: micros * (TICKS_PER_SECOND / GCD_1M) / (1_000_000 / GCD_1M), + } + } + + /// Adds one Duration to another, returning a new Duration or None in the event of an overflow. + pub fn checked_add(self, rhs: Duration) -> Option<Duration> { + self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks }) + } + + /// Subtracts one Duration to another, returning a new Duration or None in the event of an overflow. + pub fn checked_sub(self, rhs: Duration) -> Option<Duration> { + self.ticks.checked_sub(rhs.ticks).map(|ticks| Duration { ticks }) + } + + /// Multiplies one Duration by a scalar u32, returning a new Duration or None in the event of an overflow. + pub fn checked_mul(self, rhs: u32) -> Option<Duration> { + self.ticks.checked_mul(rhs as _).map(|ticks| Duration { ticks }) + } + + /// Divides one Duration a scalar u32, returning a new Duration or None in the event of an overflow. + pub fn checked_div(self, rhs: u32) -> Option<Duration> { + self.ticks.checked_div(rhs as _).map(|ticks| Duration { ticks }) + } +} + +impl Add for Duration { + type Output = Duration; + + fn add(self, rhs: Duration) -> Duration { + self.checked_add(rhs).expect("overflow when adding durations") + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +impl Sub for Duration { + type Output = Duration; + + fn sub(self, rhs: Duration) -> Duration { + self.checked_sub(rhs).expect("overflow when subtracting durations") + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +impl Mul<u32> for Duration { + type Output = Duration; + + fn mul(self, rhs: u32) -> Duration { + self.checked_mul(rhs) + .expect("overflow when multiplying duration by scalar") + } +} + +impl Mul<Duration> for u32 { + type Output = Duration; + + fn mul(self, rhs: Duration) -> Duration { + rhs * self + } +} + +impl MulAssign<u32> for Duration { + fn mul_assign(&mut self, rhs: u32) { + *self = *self * rhs; + } +} + +impl Div<u32> for Duration { + type Output = Duration; + + fn div(self, rhs: u32) -> Duration { + self.checked_div(rhs) + .expect("divide by zero error when dividing duration by scalar") + } +} + +impl DivAssign<u32> for Duration { + fn div_assign(&mut self, rhs: u32) { + *self = *self / rhs; + } +} + +impl<'a> fmt::Display for Duration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ticks", self.ticks) + } +} + +#[inline] +const fn div_ceil(num: u64, den: u64) -> u64 { + (num + den - 1) / den +} diff --git a/embassy-time/src/fmt.rs b/embassy-time/src/fmt.rs new file mode 100644 index 00000000..06697081 --- /dev/null +++ b/embassy-time/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result<Self::Ok, Self::Error>; +} + +impl<T> Try for Option<T> { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result<T, NoneError> { + self.ok_or(NoneError) + } +} + +impl<T, E> Try for Result<T, E> { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} diff --git a/embassy-time/src/instant.rs b/embassy-time/src/instant.rs new file mode 100644 index 00000000..6a4925f4 --- /dev/null +++ b/embassy-time/src/instant.rs @@ -0,0 +1,159 @@ +use core::fmt; +use core::ops::{Add, AddAssign, Sub, SubAssign}; + +use super::{driver, Duration, GCD_1K, GCD_1M, TICKS_PER_SECOND}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// An Instant in time, based on the MCU's clock ticks since startup. +pub struct Instant { + ticks: u64, +} + +impl Instant { + /// The smallest (earliest) value that can be represented by the `Instant` type. + pub const MIN: Instant = Instant { ticks: u64::MIN }; + /// The largest (latest) value that can be represented by the `Instant` type. + pub const MAX: Instant = Instant { ticks: u64::MAX }; + + /// Returns an Instant representing the current time. + pub fn now() -> Instant { + Instant { ticks: driver::now() } + } + + /// Create an Instant from a tick count since system boot. + pub const fn from_ticks(ticks: u64) -> Self { + Self { ticks } + } + + /// Create an Instant from a microsecond count since system boot. + pub const fn from_micros(micros: u64) -> Self { + Self { + ticks: micros * (TICKS_PER_SECOND / GCD_1M) / (1_000_000 / GCD_1M), + } + } + + /// Create an Instant from a millisecond count since system boot. + pub const fn from_millis(millis: u64) -> Self { + Self { + ticks: millis * (TICKS_PER_SECOND / GCD_1K) / (1000 / GCD_1K), + } + } + + /// Create an Instant from a second count since system boot. + pub const fn from_secs(seconds: u64) -> Self { + Self { + ticks: seconds * TICKS_PER_SECOND, + } + } + + /// Tick count since system boot. + pub const fn as_ticks(&self) -> u64 { + self.ticks + } + + /// Seconds since system boot. + pub const fn as_secs(&self) -> u64 { + self.ticks / TICKS_PER_SECOND + } + + /// Milliseconds since system boot. + pub const fn as_millis(&self) -> u64 { + self.ticks * (1000 / GCD_1K) / (TICKS_PER_SECOND / GCD_1K) + } + + /// Microseconds since system boot. + pub const fn as_micros(&self) -> u64 { + self.ticks * (1_000_000 / GCD_1M) / (TICKS_PER_SECOND / GCD_1M) + } + + /// Duration between this Instant and another Instant + /// Panics on over/underflow. + pub fn duration_since(&self, earlier: Instant) -> Duration { + Duration { + ticks: self.ticks.checked_sub(earlier.ticks).unwrap(), + } + } + + /// Duration between this Instant and another Instant + pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> { + if self.ticks < earlier.ticks { + None + } else { + Some(Duration { + ticks: self.ticks - earlier.ticks, + }) + } + } + + /// Returns the duration since the "earlier" Instant. + /// If the "earlier" instant is in the future, the duration is set to zero. + pub fn saturating_duration_since(&self, earlier: Instant) -> Duration { + Duration { + ticks: if self.ticks < earlier.ticks { + 0 + } else { + self.ticks - earlier.ticks + }, + } + } + + /// Duration elapsed since this Instant. + pub fn elapsed(&self) -> Duration { + Instant::now() - *self + } + + /// Adds one Duration to self, returning a new `Instant` or None in the event of an overflow. + pub fn checked_add(&self, duration: Duration) -> Option<Instant> { + self.ticks.checked_add(duration.ticks).map(|ticks| Instant { ticks }) + } + + /// Subtracts one Duration to self, returning a new `Instant` or None in the event of an overflow. + pub fn checked_sub(&self, duration: Duration) -> Option<Instant> { + self.ticks.checked_sub(duration.ticks).map(|ticks| Instant { ticks }) + } +} + +impl Add<Duration> for Instant { + type Output = Instant; + + fn add(self, other: Duration) -> Instant { + self.checked_add(other) + .expect("overflow when adding duration to instant") + } +} + +impl AddAssign<Duration> for Instant { + fn add_assign(&mut self, other: Duration) { + *self = *self + other; + } +} + +impl Sub<Duration> for Instant { + type Output = Instant; + + fn sub(self, other: Duration) -> Instant { + self.checked_sub(other) + .expect("overflow when subtracting duration from instant") + } +} + +impl SubAssign<Duration> for Instant { + fn sub_assign(&mut self, other: Duration) { + *self = *self - other; + } +} + +impl Sub<Instant> for Instant { + type Output = Duration; + + fn sub(self, other: Instant) -> Duration { + self.duration_since(other) + } +} + +impl<'a> fmt::Display for Instant { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ticks", self.ticks) + } +} diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs new file mode 100644 index 00000000..a6454d55 --- /dev/null +++ b/embassy-time/src/lib.rs @@ -0,0 +1,99 @@ +#![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)] +#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))] +#![allow(clippy::new_without_default)] +#![warn(missing_docs)] + +//! Timekeeping, delays and timeouts. +//! +//! Timekeeping is done with elapsed time since system boot. Time is represented in +//! ticks, where the tick rate is defined by the current driver, usually to match +//! the tick rate of the hardware. +//! +//! Tick counts are 64 bits. At the highest supported tick rate of 1Mhz this supports +//! representing time spans of up to ~584558 years, which is big enough for all practical +//! purposes and allows not having to worry about overflows. +//! +//! [`Instant`] represents a given instant of time (relative to system boot), and [`Duration`] +//! represents the duration of a span of time. They implement the math operations you'd expect, +//! like addition and substraction. +//! +//! # Delays and timeouts +//! +//! [`Timer`] allows performing async delays. [`Ticker`] allows periodic delays without drifting over time. +//! +//! An implementation of the `embedded-hal` delay traits is provided by [`Delay`], for compatibility +//! with libraries from the ecosystem. +//! +//! # Wall-clock time +//! +//! The `time` module deals exclusively with a monotonically increasing tick count. +//! Therefore it has no direct support for wall-clock time ("real life" datetimes +//! like `2021-08-24 13:33:21`). +//! +//! If persistence across reboots is not needed, support can be built on top of +//! `embassy_time` by storing the offset between "seconds elapsed since boot" +//! and "seconds since unix epoch". +//! +//! # Time driver +//! +//! The `time` module is backed by a global "time driver" specified at build time. +//! Only one driver can be active in a program. +//! +//! All methods and structs transparently call into the active driver. This makes it +//! possible for libraries to use `embassy_time` in a driver-agnostic way without +//! requiring generic parameters. +//! +//! For more details, check the [`driver`] module. + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +mod delay; +pub mod driver; +mod duration; +mod instant; +mod timer; + +#[cfg(feature = "std")] +mod driver_std; +#[cfg(feature = "wasm")] +mod driver_wasm; + +pub use delay::{block_for, Delay}; +pub use duration::Duration; +pub use instant::Instant; +pub use timer::{with_timeout, Ticker, TimeoutError, Timer}; + +#[cfg(feature = "tick-1000hz")] +const TPS: u64 = 1_000; + +#[cfg(feature = "tick-32768hz")] +const TPS: u64 = 32_768; + +#[cfg(feature = "tick-1mhz")] +const TPS: u64 = 1_000_000; + +#[cfg(feature = "tick-16mhz")] +const TPS: u64 = 16_000_000; + +/// Ticks per second of the global timebase. +/// +/// This value is specified by the `tick-*` Cargo features, which +/// should be set by the time driver. Some drivers support a fixed tick rate, others +/// allow you to choose a tick rate with Cargo features of their own. You should not +/// set the `tick-*` features for embassy yourself as an end user. +pub const TICKS_PER_SECOND: u64 = TPS; + +const fn gcd(a: u64, b: u64) -> u64 { + if b == 0 { + a + } else { + gcd(b, a % b) + } +} + +pub(crate) const GCD_1K: u64 = gcd(TICKS_PER_SECOND, 1_000); +pub(crate) const GCD_1M: u64 = gcd(TICKS_PER_SECOND, 1_000_000); + +#[cfg(feature = "defmt-timestamp-uptime")] +defmt::timestamp! {"{=u64:us}", Instant::now().as_micros() } diff --git a/embassy-time/src/timer.rs b/embassy-time/src/timer.rs new file mode 100644 index 00000000..bd791b81 --- /dev/null +++ b/embassy-time/src/timer.rs @@ -0,0 +1,158 @@ +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll, Waker}; + +use futures_util::future::{select, Either}; +use futures_util::{pin_mut, Stream}; + +use crate::{Duration, Instant}; + +/// Error returned by [`with_timeout`] on timeout. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimeoutError; + +/// Runs a given future with a timeout. +/// +/// If the future completes before the timeout, its output is returned. Otherwise, on timeout, +/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. +pub async fn with_timeout<F: Future>(timeout: Duration, fut: F) -> Result<F::Output, TimeoutError> { + let timeout_fut = Timer::after(timeout); + pin_mut!(fut); + match select(fut, timeout_fut).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } +} + +/// A future that completes at a specified [Instant](struct.Instant.html). +pub struct Timer { + expires_at: Instant, + yielded_once: bool, +} + +impl Timer { + /// Expire at specified [Instant](struct.Instant.html) + pub fn at(expires_at: Instant) -> Self { + Self { + expires_at, + yielded_once: false, + } + } + + /// Expire after specified [Duration](struct.Duration.html). + /// This can be used as a `sleep` abstraction. + /// + /// Example: + /// ``` no_run + /// # #![feature(type_alias_impl_trait)] + /// # + /// # fn foo() {} + /// use embassy_time::{Duration, Timer}; + /// + /// #[embassy_executor::task] + /// async fn demo_sleep_seconds() { + /// // suspend this task for one second. + /// Timer::after(Duration::from_secs(1)).await; + /// } + /// ``` + pub fn after(duration: Duration) -> Self { + Self { + expires_at: Instant::now() + duration, + yielded_once: false, + } + } +} + +impl Unpin for Timer {} + +impl Future for Timer { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { + if self.yielded_once && self.expires_at <= Instant::now() { + Poll::Ready(()) + } else { + schedule_wake(self.expires_at, cx.waker()); + self.yielded_once = true; + Poll::Pending + } + } +} + +/// Asynchronous stream that yields every Duration, indefinitely. +/// +/// This stream will tick at uniform intervals, even if blocking work is performed between ticks. +/// +/// For instance, consider the following code fragment. +/// ``` no_run +/// # #![feature(type_alias_impl_trait)] +/// # +/// use embassy_time::{Duration, Timer}; +/// # fn foo() {} +/// +/// #[embassy_executor::task] +/// async fn ticker_example_0() { +/// loop { +/// foo(); +/// Timer::after(Duration::from_secs(1)).await; +/// } +/// } +/// ``` +/// +/// This fragment will not call `foo` every second. +/// Instead, it will call it every second + the time it took to previously call `foo`. +/// +/// Example using ticker, which will consistently call `foo` once a second. +/// +/// ``` no_run +/// # #![feature(type_alias_impl_trait)] +/// # +/// use embassy_time::{Duration, Ticker}; +/// use futures::StreamExt; +/// # fn foo(){} +/// +/// #[embassy_executor::task] +/// async fn ticker_example_1() { +/// let mut ticker = Ticker::every(Duration::from_secs(1)); +/// loop { +/// foo(); +/// ticker.next().await; +/// } +/// } +/// ``` +pub struct Ticker { + expires_at: Instant, + duration: Duration, +} + +impl Ticker { + /// Creates a new ticker that ticks at the specified duration interval. + pub fn every(duration: Duration) -> Self { + let expires_at = Instant::now() + duration; + Self { expires_at, duration } + } +} + +impl Unpin for Ticker {} + +impl Stream for Ticker { + type Item = (); + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { + if self.expires_at <= Instant::now() { + let dur = self.duration; + self.expires_at += dur; + Poll::Ready(Some(())) + } else { + schedule_wake(self.expires_at, cx.waker()); + Poll::Pending + } + } +} + +extern "Rust" { + fn _embassy_time_schedule_wake(at: Instant, waker: &Waker); +} + +fn schedule_wake(at: Instant, waker: &Waker) { + unsafe { _embassy_time_schedule_wake(at, waker) } +} |