From 7c0990ad1e8d1a455818740973ca0267bb3f5854 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 25 Aug 2021 18:50:05 +0200 Subject: [PATCH] time: allow storing state inside the driver struct. --- embassy-nrf/src/time_driver.rs | 55 +++++-------- embassy-rp/src/timer.rs | 132 ++++++++++++++++--------------- embassy-stm32/src/time_driver.rs | 62 ++++++--------- embassy/src/executor/arch/std.rs | 67 ++++++++++++++++ embassy/src/time/driver.rs | 99 ++++++++++------------- 5 files changed, 218 insertions(+), 197 deletions(-) create mode 100644 embassy/src/executor/arch/std.rs 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/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/time/driver.rs b/embassy/src/time/driver.rs index 21817b92f..1b8949ae1 100644 --- a/embassy/src/time/driver.rs +++ b/embassy/src/time/driver.rs @@ -26,32 +26,34 @@ //! 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. +//! 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 -/// -/// ``` -/// struct MyDriver; // not public! -/// 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!() -/// } -/// } -/// ``` +//! # 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)] @@ -76,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. @@ -99,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" { @@ -125,49 +127,34 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) { /// Set the time Driver implementation. /// -/// # Example -/// -/// ``` -/// struct MyDriver; // not public! -/// 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) } }; }