diff --git a/Cargo.example.toml b/Cargo.example.toml index f072c2f96..0e9d1e32d 100644 --- a/Cargo.example.toml +++ b/Cargo.example.toml @@ -45,7 +45,6 @@ members = [ #"examples/rp", # std - #"embassy-std", #"examples/std", ] diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index 30461633a..f93ebb54a 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -82,7 +82,7 @@ impl AlarmState { const ALARM_COUNT: usize = 3; -struct State { +struct RtcDriver { /// Number of 2^23 periods elapsed since boot. period: AtomicU32, alarm_count: AtomicU8, @@ -91,13 +91,13 @@ struct State { } const ALARM_STATE_NEW: AlarmState = AlarmState::new(); -static STATE: State = State { +embassy::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { period: AtomicU32::new(0), alarm_count: AtomicU8::new(0), alarms: Mutex::new([ALARM_STATE_NEW; ALARM_COUNT]), -}; +}); -impl State { +impl RtcDriver { fn init(&'static self, irq_prio: crate::interrupt::Priority) { let r = rtc(); r.cc[3].write(|w| unsafe { w.bits(0x800000) }); @@ -159,14 +159,6 @@ impl State { }) } - fn now(&self) -> u64 { - // `period` MUST be read before `counter`, see comment at the top for details. - let period = self.period.load(Ordering::Relaxed); - compiler_fence(Ordering::Acquire); - let counter = rtc().counter.read().bits(); - calc_now(period, counter) - } - fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { // safety: we're allowed to assume the AlarmState is created by us, and // we never create one that's out of bounds. @@ -188,8 +180,18 @@ impl State { let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; f(alarm.ctx.get()); } +} - fn allocate_alarm(&self) -> Option { +impl Driver for RtcDriver { + fn now(&self) -> u64 { + // `period` MUST be read before `counter`, see comment at the top for details. + let period = self.period.load(Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + let counter = rtc().counter.read().bits(); + calc_now(period, counter) + } + + unsafe fn allocate_alarm(&self) -> Option { let id = self .alarm_count .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { @@ -201,7 +203,7 @@ impl State { }); match id { - Ok(id) => Some(unsafe { AlarmHandle::new(id) }), + Ok(id) => Some(AlarmHandle::new(id)), Err(_) => None, } } @@ -263,32 +265,11 @@ impl State { } } -struct RtcDriver; -embassy::time_driver_impl!(RtcDriver); - -impl Driver for RtcDriver { - fn now() -> u64 { - STATE.now() - } - - unsafe fn allocate_alarm() -> Option { - STATE.allocate_alarm() - } - - fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - STATE.set_alarm_callback(alarm, callback, ctx) - } - - fn set_alarm(alarm: AlarmHandle, timestamp: u64) { - STATE.set_alarm(alarm, timestamp) - } -} - #[interrupt] fn RTC1() { - STATE.on_interrupt() + DRIVER.on_interrupt() } pub(crate) fn init(irq_prio: crate::interrupt::Priority) { - STATE.init(irq_prio) + DRIVER.init(irq_prio) } diff --git a/embassy-rp/src/timer.rs b/embassy-rp/src/timer.rs index 71c59ec8d..ed265c47f 100644 --- a/embassy-rp/src/timer.rs +++ b/embassy-rp/src/timer.rs @@ -19,38 +19,40 @@ const DUMMY_ALARM: AlarmState = AlarmState { callback: Cell::new(None), }; -static ALARMS: Mutex<[AlarmState; ALARM_COUNT]> = Mutex::new([DUMMY_ALARM; ALARM_COUNT]); -static NEXT_ALARM: AtomicU8 = AtomicU8::new(0); +struct TimerDriver { + alarms: Mutex<[AlarmState; ALARM_COUNT]>, + next_alarm: AtomicU8, +} -fn now() -> u64 { - loop { - unsafe { - let hi = pac::TIMER.timerawh().read(); - let lo = pac::TIMER.timerawl().read(); - let hi2 = pac::TIMER.timerawh().read(); - if hi == hi2 { - return (hi as u64) << 32 | (lo as u64); +embassy::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{ + alarms: Mutex::new([DUMMY_ALARM; ALARM_COUNT]), + next_alarm: AtomicU8::new(0), +}); + +impl Driver for TimerDriver { + fn now(&self) -> u64 { + loop { + unsafe { + let hi = pac::TIMER.timerawh().read(); + let lo = pac::TIMER.timerawl().read(); + let hi2 = pac::TIMER.timerawh().read(); + if hi == hi2 { + return (hi as u64) << 32 | (lo as u64); + } } } } -} -struct TimerDriver; -embassy::time_driver_impl!(TimerDriver); - -impl Driver for TimerDriver { - fn now() -> u64 { - now() - } - - unsafe fn allocate_alarm() -> Option { - let id = NEXT_ALARM.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { - if x < ALARM_COUNT as u8 { - Some(x + 1) - } else { - None - } - }); + unsafe fn allocate_alarm(&self) -> Option { + let id = self + .next_alarm + .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)), @@ -58,18 +60,18 @@ impl Driver for TimerDriver { } } - fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { + fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { let n = alarm.id() as usize; critical_section::with(|cs| { - let alarm = &ALARMS.borrow(cs)[n]; + let alarm = &self.alarms.borrow(cs)[n]; alarm.callback.set(Some((callback, ctx))); }) } - fn set_alarm(alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { let n = alarm.id() as usize; critical_section::with(|cs| { - let alarm = &ALARMS.borrow(cs)[n]; + let alarm = &self.alarms.borrow(cs)[n]; alarm.timestamp.set(timestamp); // Arm it. @@ -78,44 +80,46 @@ impl Driver for TimerDriver { // it is checked if the alarm time has passed. unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; - let now = now(); + let now = self.now(); // If alarm timestamp has passed, trigger it instantly. // This disarms it. if timestamp <= now { - trigger_alarm(n, cs); + self.trigger_alarm(n, cs); } }) } } -fn check_alarm(n: usize) { - critical_section::with(|cs| { - let alarm = &ALARMS.borrow(cs)[n]; - let timestamp = alarm.timestamp.get(); - if timestamp <= now() { - trigger_alarm(n, cs) - } else { - // Not elapsed, arm it again. - // This can happen if it was set more than 2^32 us in the future. - unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; +impl TimerDriver { + fn check_alarm(&self, n: usize) { + critical_section::with(|cs| { + let alarm = &self.alarms.borrow(cs)[n]; + let timestamp = alarm.timestamp.get(); + if timestamp <= self.now() { + self.trigger_alarm(n, cs) + } else { + // Not elapsed, arm it again. + // This can happen if it was set more than 2^32 us in the future. + unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; + } + }); + + // clear the irq + unsafe { pac::TIMER.intr().write(|w| w.set_alarm(n, true)) } + } + + fn trigger_alarm(&self, n: usize, cs: CriticalSection) { + // disarm + unsafe { pac::TIMER.armed().write(|w| w.set_armed(1 << n)) } + + let alarm = &self.alarms.borrow(cs)[n]; + alarm.timestamp.set(u64::MAX); + + // Call after clearing alarm, so the callback can set another alarm. + if let Some((f, ctx)) = alarm.callback.get() { + f(ctx); } - }); - - // clear the irq - unsafe { pac::TIMER.intr().write(|w| w.set_alarm(n, true)) } -} - -fn trigger_alarm(n: usize, cs: CriticalSection) { - // disarm - unsafe { pac::TIMER.armed().write(|w| w.set_armed(1 << n)) } - - let alarm = &ALARMS.borrow(cs)[n]; - alarm.timestamp.set(u64::MAX); - - // Call after clearing alarm, so the callback can set another alarm. - if let Some((f, ctx)) = alarm.callback.get() { - f(ctx); } } @@ -123,7 +127,7 @@ fn trigger_alarm(n: usize, cs: CriticalSection) { pub unsafe fn init() { // init alarms critical_section::with(|cs| { - let alarms = ALARMS.borrow(cs); + let alarms = DRIVER.alarms.borrow(cs); for a in alarms { a.timestamp.set(u64::MAX); } @@ -144,20 +148,20 @@ pub unsafe fn init() { #[interrupt] unsafe fn TIMER_IRQ_0() { - check_alarm(0) + DRIVER.check_alarm(0) } #[interrupt] unsafe fn TIMER_IRQ_1() { - check_alarm(1) + DRIVER.check_alarm(1) } #[interrupt] unsafe fn TIMER_IRQ_2() { - check_alarm(2) + DRIVER.check_alarm(2) } #[interrupt] unsafe fn TIMER_IRQ_3() { - check_alarm(3) + DRIVER.check_alarm(3) } diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index 91b8525ae..4e1eb7aaa 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -26,12 +26,12 @@ type T = peripherals::TIM3; #[cfg(feature = "time-driver-tim2")] #[interrupt] fn TIM2() { - STATE.on_interrupt() + DRIVER.on_interrupt() } #[cfg(feature = "time-driver-tim3")] #[interrupt] fn TIM3() { - STATE.on_interrupt() + DRIVER.on_interrupt() } // Clock timekeeping works with something we call "periods", which are time intervals @@ -76,7 +76,7 @@ impl AlarmState { } } -struct State { +struct RtcDriver { /// Number of 2^15 periods elapsed since boot. period: AtomicU32, alarm_count: AtomicU8, @@ -85,13 +85,14 @@ struct State { } const ALARM_STATE_NEW: AlarmState = AlarmState::new(); -static STATE: State = State { + +embassy::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { period: AtomicU32::new(0), alarm_count: AtomicU8::new(0), alarms: Mutex::new([ALARM_STATE_NEW; ALARM_COUNT]), -}; +}); -impl State { +impl RtcDriver { fn init(&'static self) { let r = T::regs(); @@ -185,16 +186,6 @@ impl State { }) } - fn now(&self) -> u64 { - let r = T::regs(); - - let period = self.period.load(Ordering::Relaxed); - compiler_fence(Ordering::Acquire); - // NOTE(unsafe) Atomic read with no side-effects - let counter = unsafe { r.cnt().read().cnt() }; - calc_now(period, counter) - } - fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { // safety: we're allowed to assume the AlarmState is created by us, and // we never create one that's out of bounds. @@ -213,8 +204,20 @@ impl State { let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; f(alarm.ctx.get()); } +} - fn allocate_alarm(&self) -> Option { +impl Driver for RtcDriver { + fn now(&self) -> u64 { + let r = T::regs(); + + let period = self.period.load(Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + // NOTE(unsafe) Atomic read with no side-effects + let counter = unsafe { r.cnt().read().cnt() }; + calc_now(period, counter) + } + + unsafe fn allocate_alarm(&self) -> Option { let id = self .alarm_count .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { @@ -226,7 +229,7 @@ impl State { }); match id { - Ok(id) => Some(unsafe { AlarmHandle::new(id) }), + Ok(id) => Some(AlarmHandle::new(id)), Err(_) => None, } } @@ -269,29 +272,8 @@ impl State { } } -struct RtcDriver; -embassy::time_driver_impl!(RtcDriver); - -impl Driver for RtcDriver { - fn now() -> u64 { - STATE.now() - } - - unsafe fn allocate_alarm() -> Option { - STATE.allocate_alarm() - } - - fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - STATE.set_alarm_callback(alarm, callback, ctx) - } - - fn set_alarm(alarm: AlarmHandle, timestamp: u64) { - STATE.set_alarm(alarm, timestamp) - } -} - pub(crate) fn init() { - STATE.init() + DRIVER.init() } // ------------------------------------------------------ diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml index ca7d38a55..0ff71ce1d 100644 --- a/embassy/Cargo.toml +++ b/embassy/Cargo.toml @@ -7,9 +7,17 @@ resolver = "2" [features] default = [] -std = ["futures/std", "embassy-traits/std"] +std = ["futures/std", "embassy-traits/std", "time", "time-tick-1mhz"] +# Enable `embassy::time` module. +# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. +# Enabling it directly without supplying a time driver will fail to link. time = [] + +# 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! time-tick-32768hz = ["time"] time-tick-1000hz = ["time"] time-tick-1mhz = ["time"] diff --git a/embassy/src/executor/arch/std.rs b/embassy/src/executor/arch/std.rs new file mode 100644 index 000000000..fb7880542 --- /dev/null +++ b/embassy/src/executor/arch/std.rs @@ -0,0 +1,67 @@ +use std::marker::PhantomData; +use std::sync::{Condvar, Mutex}; + +use super::{raw, Spawner}; + +pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + signaler: &'static Signaler, +} + +impl Executor { + pub fn new() -> Self { + let signaler = &*Box::leak(Box::new(Signaler::new())); + Self { + inner: raw::Executor::new( + |p| unsafe { + let s = &*(p as *const () as *const Signaler); + s.signal() + }, + signaler as *const _ as _, + ), + not_send: PhantomData, + signaler, + } + } + + /// Runs the executor. + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(unsafe { self.inner.spawner() }); + + loop { + unsafe { self.inner.run_queued() }; + self.signaler.wait() + } + } +} + +struct Signaler { + mutex: Mutex, + condvar: Condvar, +} + +impl Signaler { + fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait(&self) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + signaled = self.condvar.wait(signaled).unwrap(); + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } +} diff --git a/embassy/src/executor/mod.rs b/embassy/src/executor/mod.rs index e0ac566f1..5ffbe689e 100644 --- a/embassy/src/executor/mod.rs +++ b/embassy/src/executor/mod.rs @@ -1,4 +1,5 @@ -#[path = "arch/arm.rs"] +#[cfg_attr(feature = "std", path = "arch/std.rs")] +#[cfg_attr(not(feature = "std"), path = "arch/arm.rs")] mod arch; pub mod raw; mod spawner; diff --git a/embassy/src/io/mod.rs b/embassy/src/io/mod.rs index 8445f6e80..52b050971 100644 --- a/embassy/src/io/mod.rs +++ b/embassy/src/io/mod.rs @@ -1,7 +1,11 @@ mod error; +#[cfg(feature = "std")] +mod std; mod traits; mod util; pub use self::error::*; +#[cfg(feature = "std")] +pub use self::std::*; pub use self::traits::*; pub use self::util::*; diff --git a/embassy/src/io/std.rs b/embassy/src/io/std.rs new file mode 100644 index 000000000..ddec8d56d --- /dev/null +++ b/embassy/src/io/std.rs @@ -0,0 +1,35 @@ +use core::pin::Pin; +use core::task::{Context, Poll}; +use futures::io as std_io; + +use super::{AsyncBufRead, AsyncWrite, Result}; + +pub struct FromStdIo(T); + +impl FromStdIo { + pub fn new(inner: T) -> Self { + Self(inner) + } +} + +impl AsyncBufRead for FromStdIo { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let Self(inner) = unsafe { self.get_unchecked_mut() }; + unsafe { Pin::new_unchecked(inner) } + .poll_fill_buf(cx) + .map_err(|e| e.into()) + } + fn consume(self: Pin<&mut Self>, amt: usize) { + let Self(inner) = unsafe { self.get_unchecked_mut() }; + unsafe { Pin::new_unchecked(inner) }.consume(amt) + } +} + +impl AsyncWrite for FromStdIo { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + let Self(inner) = unsafe { self.get_unchecked_mut() }; + unsafe { Pin::new_unchecked(inner) } + .poll_write(cx, buf) + .map_err(|e| e.into()) + } +} diff --git a/embassy/src/io/traits.rs b/embassy/src/io/traits.rs index b59cdc0d4..8e4a981da 100644 --- a/embassy/src/io/traits.rs +++ b/embassy/src/io/traits.rs @@ -5,9 +5,6 @@ use core::task::{Context, Poll}; #[cfg(feature = "alloc")] use alloc::boxed::Box; -#[cfg(feature = "std")] -use futures::io as std_io; - use super::error::Result; /// Read bytes asynchronously. @@ -159,37 +156,3 @@ where self.get_mut().as_mut().poll_write(cx, buf) } } - -#[cfg(feature = "std")] -pub struct FromStdIo(T); - -#[cfg(feature = "std")] -impl FromStdIo { - pub fn new(inner: T) -> Self { - Self(inner) - } -} - -#[cfg(feature = "std")] -impl AsyncBufRead for FromStdIo { - fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let Self(inner) = unsafe { self.get_unchecked_mut() }; - unsafe { Pin::new_unchecked(inner) } - .poll_fill_buf(cx) - .map_err(|e| e.into()) - } - fn consume(self: Pin<&mut Self>, amt: usize) { - let Self(inner) = unsafe { self.get_unchecked_mut() }; - unsafe { Pin::new_unchecked(inner) }.consume(amt) - } -} - -#[cfg(feature = "std")] -impl AsyncWrite for FromStdIo { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - let Self(inner) = unsafe { self.get_unchecked_mut() }; - unsafe { Pin::new_unchecked(inner) } - .poll_write(cx, buf) - .map_err(|e| e.into()) - } -} diff --git a/embassy/src/time/delay.rs b/embassy/src/time/delay.rs index c97e8b5c2..a46ee3a4f 100644 --- a/embassy/src/time/delay.rs +++ b/embassy/src/time/delay.rs @@ -4,11 +4,10 @@ use super::{Duration, Instant, Timer}; /// Type implementing async delays and blocking `embedded-hal` delays. /// -/// For this interface to work, the Executor's clock must be correctly initialized before using it. /// 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 can be chosen through -/// features flags of this crate. +/// 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; impl crate::traits::delay::Delay for Delay { @@ -58,9 +57,7 @@ impl embedded_hal::blocking::delay::DelayUs for Delay { } } -/// Blocks the cpu for at least `duration`. -/// -/// For this interface to work, the Executor's clock must be correctly initialized before using it. +/// Blocks for at least `duration`. pub fn block_for(duration: Duration) { let expires_at = Instant::now() + duration; while Instant::now() < expires_at {} diff --git a/embassy/src/time/driver.rs b/embassy/src/time/driver.rs index b09cffd8c..1b8949ae1 100644 --- a/embassy/src/time/driver.rs +++ b/embassy/src/time/driver.rs @@ -1,3 +1,60 @@ +//! 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/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_driver_impl!(static DRIVER: MyDriver = MyDriver{}); +//! +//! impl Driver for MyDriver { +//! fn now(&self) -> u64 { +//! todo!() +//! } +//! unsafe fn allocate_alarm(&self) -> Option { +//! 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 { @@ -21,22 +78,22 @@ impl AlarmHandle { } /// Time driver -pub trait Driver { +pub trait Driver: Send + Sync + 'static { /// Return the current timestamp in ticks. /// This is guaranteed to be monotonic, i.e. a call to now() will always return /// a greater or equal value than earler calls. - fn now() -> u64; + 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() -> Option; + unsafe fn allocate_alarm(&self) -> Option; /// 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(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); + fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); /// Sets an alarm at the given timestamp. When the current timestamp reaches that /// timestamp, the provided callback funcion will be called. @@ -44,7 +101,7 @@ pub trait Driver { /// 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. This overwrites any previously-set alarm if any. - fn set_alarm(alarm: AlarmHandle, timestamp: u64); + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64); } extern "Rust" { @@ -70,49 +127,34 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) { /// Set the time Driver implementation. /// -/// # Example -/// -/// ``` -/// struct MyDriver; -/// embassy::time_driver_impl!(MyDriver); -/// -/// unsafe impl embassy::time::driver::Driver for MyDriver { -/// fn now() -> u64 { -/// todo!() -/// } -/// unsafe fn allocate_alarm() -> Option { -/// todo!() -/// } -/// fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { -/// todo!() -/// } -/// fn set_alarm(alarm: AlarmHandle, timestamp: u64) { -/// todo!() -/// } -/// } -/// +/// See the module documentation for an example. #[macro_export] macro_rules! time_driver_impl { - ($t: ty) => { + (static $name:ident: $t: ty = $val:expr) => { + static $name: $t = $val; + #[no_mangle] fn _embassy_time_now() -> u64 { - <$t as $crate::time::driver::Driver>::now() + <$t as $crate::time::driver::Driver>::now(&$name) } + #[no_mangle] - unsafe fn _embassy_time_allocate_alarm() -> Option { - <$t as $crate::time::driver::Driver>::allocate_alarm() + unsafe fn _embassy_time_allocate_alarm() -> Option<$crate::time::driver::AlarmHandle> { + <$t as $crate::time::driver::Driver>::allocate_alarm(&$name) } + #[no_mangle] fn _embassy_time_set_alarm_callback( - alarm: AlarmHandle, + alarm: $crate::time::driver::AlarmHandle, callback: fn(*mut ()), ctx: *mut (), ) { - <$t as $crate::time::driver::Driver>::set_alarm_callback(alarm, callback, ctx) + <$t as $crate::time::driver::Driver>::set_alarm_callback(&$name, alarm, callback, ctx) } + #[no_mangle] - fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64) { - <$t as $crate::time::driver::Driver>::set_alarm(alarm, timestamp) + fn _embassy_time_set_alarm(alarm: $crate::time::driver::AlarmHandle, timestamp: u64) { + <$t as $crate::time::driver::Driver>::set_alarm(&$name, alarm, timestamp) } }; } diff --git a/embassy/src/time/driver_std.rs b/embassy/src/time/driver_std.rs new file mode 100644 index 000000000..29911c4d2 --- /dev/null +++ b/embassy/src/time/driver_std.rs @@ -0,0 +1,208 @@ +use atomic_polyfill::{AtomicU8, Ordering}; +use std::cell::UnsafeCell; +use std::mem; +use std::mem::MaybeUninit; +use std::sync::{Condvar, Mutex, Once}; +use std::time::Duration as StdDuration; +use std::time::Instant as StdInstant; +use std::{ptr, thread}; + +use crate::time::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>, + zero_instant: UninitCell, + signaler: UninitCell, +} + +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() { + 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); + } + } + } + + let until = + unsafe { DRIVER.zero_instant.read() } + StdDuration::from_micros(next_alarm); + + 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 { + 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, + 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(MaybeUninit>); +unsafe impl Send for UninitCell {} +unsafe impl Sync for UninitCell {} + +impl UninitCell { + 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 UninitCell { + pub unsafe fn read(&self) -> T { + ptr::read(self.as_mut_ptr()) + } +} diff --git a/embassy/src/time/duration.rs b/embassy/src/time/duration.rs index 5157450ad..e196a00c7 100644 --- a/embassy/src/time/duration.rs +++ b/embassy/src/time/duration.rs @@ -11,18 +11,27 @@ pub struct Duration { } 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 / TICKS_PER_SECOND } + /// Convert the `Duration` to microseconds, rounding down. pub const fn as_micros(&self) -> u64 { self.ticks * 1_000_000 / TICKS_PER_SECOND } diff --git a/embassy/src/time/instant.rs b/embassy/src/time/instant.rs index 3d430d886..36c2b2dc2 100644 --- a/embassy/src/time/instant.rs +++ b/embassy/src/time/instant.rs @@ -11,7 +11,9 @@ pub struct Instant { } 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. @@ -21,39 +23,38 @@ impl Instant { } } - /// Instant as clock ticks since MCU start. + /// Create an Instant from a tick count since system boot. pub const fn from_ticks(ticks: u64) -> Self { Self { ticks } } - /// Instant as milliseconds since MCU start. + /// Create an Instant from a millisecond count since system boot. pub const fn from_millis(millis: u64) -> Self { Self { - ticks: millis * TICKS_PER_SECOND as u64 / 1000, + ticks: millis * TICKS_PER_SECOND / 1000, } } - /// Instant representing seconds since MCU start. + /// Create an Instant from a second count since system boot. pub const fn from_secs(seconds: u64) -> Self { Self { - ticks: seconds * TICKS_PER_SECOND as u64, + ticks: seconds * TICKS_PER_SECOND, } } - /// Instant as ticks since MCU start. - + /// Tick count since system boot. pub const fn as_ticks(&self) -> u64 { self.ticks } - /// Instant as seconds since MCU start. + /// Seconds since system boot. pub const fn as_secs(&self) -> u64 { - self.ticks / TICKS_PER_SECOND as u64 + self.ticks / TICKS_PER_SECOND } - /// Instant as miliseconds since MCU start. + /// Milliseconds since system boot. pub const fn as_millis(&self) -> u64 { - self.ticks * 1000 / TICKS_PER_SECOND as u64 + self.ticks * 1000 / TICKS_PER_SECOND } /// Duration between this Instant and another Instant @@ -92,11 +93,14 @@ impl Instant { 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 { 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 { self.ticks .checked_sub(duration.ticks) diff --git a/embassy/src/time/mod.rs b/embassy/src/time/mod.rs index f5ad1b3ff..e50742040 100644 --- a/embassy/src/time/mod.rs +++ b/embassy/src/time/mod.rs @@ -1,4 +1,44 @@ -//! Time abstractions +//! 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. mod delay; pub mod driver; @@ -6,16 +46,27 @@ mod duration; mod instant; mod timer; +#[cfg(feature = "std")] +mod driver_std; + pub use delay::{block_for, Delay}; pub use duration::Duration; pub use instant::Instant; pub use timer::{with_timeout, Ticker, TimeoutError, Timer}; #[cfg(feature = "time-tick-1000hz")] -pub const TICKS_PER_SECOND: u64 = 1_000; +const TPS: u64 = 1_000; #[cfg(feature = "time-tick-32768hz")] -pub const TICKS_PER_SECOND: u64 = 32_768; +const TPS: u64 = 32_768; #[cfg(feature = "time-tick-1mhz")] -pub const TICKS_PER_SECOND: u64 = 1_000_000; +const TPS: u64 = 1_000_000; + +/// Ticks per second of the global timebase. +/// +/// This value is specified by the `time-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 `time-tick-*` features for embassy yourself as an end user. +pub const TICKS_PER_SECOND: u64 = TPS; diff --git a/embassy/src/time/timer.rs b/embassy/src/time/timer.rs index 22f1ffe30..d1ed6a512 100644 --- a/embassy/src/time/timer.rs +++ b/embassy/src/time/timer.rs @@ -6,7 +6,13 @@ use futures::{future::select, future::Either, pin_mut, Stream}; use crate::executor::raw; use crate::time::{Duration, Instant}; +/// Error returned by [`with_timeout`] on timeout. 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(timeout: Duration, fut: F) -> Result { let timeout_fut = Timer::after(timeout); pin_mut!(fut); diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 466814850..34b8ebb67 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -5,8 +5,7 @@ name = "embassy-std-examples" version = "0.1.0" [dependencies] -embassy = { version = "0.1.0", path = "../../embassy", features = ["log"] } -embassy-std = { version = "0.1.0", path = "../../embassy-std" } +embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "std", "time"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features=["std", "log", "medium-ethernet", "tcp", "dhcpv4"] } smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", rev="e4241510337e095b9d21136c5f58b2eaa1b78479", default-features = false } diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index 323d711a8..b98b97090 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs @@ -2,11 +2,10 @@ #![allow(incomplete_features)] use clap::{AppSettings, Clap}; -use embassy::executor::Spawner; +use embassy::executor::{Executor, Spawner}; use embassy::io::AsyncWriteExt; use embassy::util::Forever; use embassy_net::*; -use embassy_std::Executor; use heapless::Vec; use log::*; diff --git a/examples/std/src/bin/serial.rs b/examples/std/src/bin/serial.rs index ca596a34c..181c5dfaa 100644 --- a/examples/std/src/bin/serial.rs +++ b/examples/std/src/bin/serial.rs @@ -5,9 +5,9 @@ mod serial_port; use async_io::Async; +use embassy::executor::Executor; use embassy::io::AsyncBufReadExt; use embassy::util::Forever; -use embassy_std::Executor; use log::*; use nix::sys::termios; diff --git a/examples/std/src/bin/tick.rs b/examples/std/src/bin/tick.rs index 16f54b2c6..385b317d4 100644 --- a/examples/std/src/bin/tick.rs +++ b/examples/std/src/bin/tick.rs @@ -1,9 +1,9 @@ #![feature(type_alias_impl_trait)] #![allow(incomplete_features)] +use embassy::executor::Executor; use embassy::time::{Duration, Timer}; use embassy::util::Forever; -use embassy_std::Executor; use log::*; #[embassy::task]