time: allow storing state inside the driver struct.

This commit is contained in:
Dario Nieuwenhuis 2021-08-25 18:50:05 +02:00
parent 55b2d7b524
commit 7c0990ad1e
5 changed files with 218 additions and 197 deletions

View file

@ -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<AlarmHandle> {
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<AlarmHandle> {
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<AlarmHandle> {
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)
}

View file

@ -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<AlarmHandle> {
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<AlarmHandle> {
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)
}

View file

@ -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<AlarmHandle> {
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<AlarmHandle> {
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<AlarmHandle> {
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()
}
// ------------------------------------------------------

View file

@ -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<bool>,
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();
}
}

View file

@ -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<AlarmHandle> {
/// 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<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)]
@ -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<AlarmHandle>;
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(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<AlarmHandle> {
/// 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<AlarmHandle> {
<$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)
}
};
}