time: replace dyn clock/alarm with a global Driver trait

This commit is contained in:
Dario Nieuwenhuis
2021-08-03 22:08:13 +02:00
parent a4c0ee6df7
commit 0ea6a2d890
47 changed files with 663 additions and 814 deletions

View File

@ -109,18 +109,13 @@ pub struct Executor {
}
impl Executor {
pub const fn new() -> Self {
pub fn new() -> Self {
Self {
inner: raw::Executor::new(|_| cortex_m::asm::sev(), ptr::null_mut()),
not_send: PhantomData,
}
}
#[cfg(feature = "time")]
pub fn set_alarm(&mut self, alarm: &'static dyn crate::time::Alarm) {
self.inner.set_alarm(alarm);
}
/// Runs the executor.
///
/// This function never returns.
@ -161,11 +156,6 @@ impl<I: Interrupt> InterruptExecutor<I> {
}
}
#[cfg(feature = "time")]
pub fn set_alarm(&mut self, alarm: &'static dyn crate::time::Alarm) {
self.inner.set_alarm(alarm);
}
/// Start the executor.
///
/// `init` is called in the interrupt context, then the interrupt is

View File

@ -15,7 +15,9 @@ use super::SpawnToken;
#[cfg(feature = "time")]
use super::timer_queue::{TimerQueue, TimerQueueItem};
#[cfg(feature = "time")]
use crate::time::{Alarm, Instant};
use crate::time::driver::{self, AlarmHandle};
#[cfg(feature = "time")]
use crate::time::Instant;
/// Task is spawned (has a future)
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
@ -169,11 +171,16 @@ pub struct Executor {
#[cfg(feature = "time")]
timer_queue: TimerQueue,
#[cfg(feature = "time")]
alarm: Option<&'static dyn Alarm>,
alarm: AlarmHandle,
}
impl Executor {
pub const fn new(signal_fn: fn(*mut ()), signal_ctx: *mut ()) -> Self {
pub fn new(signal_fn: fn(*mut ()), signal_ctx: *mut ()) -> Self {
#[cfg(feature = "time")]
let alarm = unsafe { unwrap!(driver::allocate_alarm()) };
#[cfg(feature = "time")]
driver::set_alarm_callback(alarm, signal_fn, signal_ctx);
Self {
run_queue: RunQueue::new(),
signal_fn,
@ -182,15 +189,10 @@ impl Executor {
#[cfg(feature = "time")]
timer_queue: TimerQueue::new(),
#[cfg(feature = "time")]
alarm: None,
alarm,
}
}
#[cfg(feature = "time")]
pub fn set_alarm(&mut self, alarm: &'static dyn Alarm) {
self.alarm = Some(alarm);
}
pub fn set_signal_ctx(&mut self, signal_ctx: *mut ()) {
self.signal_ctx = signal_ctx;
}
@ -209,11 +211,9 @@ impl Executor {
pub unsafe fn run_queued(&'static self) {
#[cfg(feature = "time")]
if self.alarm.is_some() {
self.timer_queue.dequeue_expired(Instant::now(), |p| {
p.as_ref().enqueue();
});
}
self.timer_queue.dequeue_expired(Instant::now(), |p| {
p.as_ref().enqueue();
});
self.run_queue.dequeue_all(|p| {
let task = p.as_ref();
@ -239,13 +239,12 @@ impl Executor {
self.timer_queue.update(p);
});
// If this is in the past, set_alarm will immediately trigger the alarm,
// which will make the wfe immediately return so we do another loop iteration.
#[cfg(feature = "time")]
if let Some(alarm) = self.alarm {
{
// If this is in the past, set_alarm will immediately trigger the alarm,
// which will make the wfe immediately return so we do another loop iteration.
let next_expiration = self.timer_queue.next_expiration();
alarm.set_callback(self.signal_fn, self.signal_ctx);
alarm.set(next_expiration.as_ticks());
driver::set_alarm(self.alarm, next_expiration.as_ticks());
}
}

118
embassy/src/time/driver.rs Normal file
View File

@ -0,0 +1,118 @@
/// 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 {
/// 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;
/// 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>;
/// 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 ());
/// Sets an alarm at the given timestamp. When the current timestamp reaches that
/// timestamp, the provided callback funcion will be called.
///
/// 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);
}
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);
}
pub(crate) fn now() -> u64 {
unsafe { _embassy_time_now() }
}
/// Safety: it is UB to make the alarm fire before setting a callback.
pub(crate) unsafe fn allocate_alarm() -> Option<AlarmHandle> {
_embassy_time_allocate_alarm()
}
pub(crate) fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
unsafe { _embassy_time_set_alarm_callback(alarm, callback, ctx) }
}
pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
unsafe { _embassy_time_set_alarm(alarm, timestamp) }
}
/// 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<AlarmHandle> {
/// todo!()
/// }
/// fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
/// todo!()
/// }
/// fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
/// todo!()
/// }
/// }
///
#[macro_export]
macro_rules! time_driver_impl {
($t: ty) => {
#[no_mangle]
fn _embassy_time_now() -> u64 {
<$t as $crate::time::driver::Driver>::now()
}
#[no_mangle]
unsafe fn _embassy_time_allocate_alarm() -> Option<AlarmHandle> {
<$t as $crate::time::driver::Driver>::allocate_alarm()
}
#[no_mangle]
fn _embassy_time_set_alarm_callback(
alarm: AlarmHandle,
callback: fn(*mut ()),
ctx: *mut (),
) {
<$t as $crate::time::driver::Driver>::set_alarm_callback(alarm, callback, ctx)
}
#[no_mangle]
fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64) {
<$t as $crate::time::driver::Driver>::set_alarm(alarm, timestamp)
}
};
}

View File

@ -1,8 +1,7 @@
use core::fmt;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use super::TICKS_PER_SECOND;
use super::{now, Duration};
use super::{driver, Duration, TICKS_PER_SECOND};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -17,7 +16,9 @@ impl Instant {
/// Returns an Instant representing the current time.
pub fn now() -> Instant {
Instant { ticks: now() }
Instant {
ticks: driver::now(),
}
}
/// Instant as clock ticks since MCU start.

View File

@ -1,17 +1,15 @@
//! Time abstractions
//! To use these abstractions, first call `set_clock` with an instance of an [Clock](trait.Clock.html).
//!
mod delay;
pub mod driver;
mod duration;
mod instant;
mod timer;
mod traits;
pub use delay::{block_for, Delay};
pub use duration::Duration;
pub use instant::Instant;
pub use timer::{with_timeout, Ticker, TimeoutError, Timer};
pub use traits::*;
#[cfg(feature = "time-tick-1000hz")]
pub const TICKS_PER_SECOND: u64 = 1_000;
@ -21,19 +19,3 @@ pub const TICKS_PER_SECOND: u64 = 32_768;
#[cfg(feature = "time-tick-1mhz")]
pub const TICKS_PER_SECOND: u64 = 1_000_000;
static mut CLOCK: Option<&'static dyn Clock> = None;
/// Sets the clock used for the timing abstractions
///
/// Safety: Sets a mutable global.
pub unsafe fn set_clock(clock: &'static dyn Clock) {
CLOCK = Some(clock);
}
/// 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.
pub(crate) fn now() -> u64 {
unsafe { unwrap!(CLOCK, "No clock set").now() }
}

View File

@ -1,32 +0,0 @@
/// Monotonic clock
pub trait Clock {
/// 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(&self) -> u64;
}
impl<T: Clock + ?Sized> Clock for &T {
fn now(&self) -> u64 {
T::now(self)
}
}
/// Trait to register a callback at a given timestamp.
pub trait Alarm {
/// 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_callback(&self, callback: fn(*mut ()), ctx: *mut ());
/// Sets an alarm at the given timestamp. When the clock reaches that
/// timestamp, the provided callback funcion will be called.
///
/// 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(&self, timestamp: u64);
/// Clears the previously-set alarm.
/// If no alarm was set, this is a noop.
fn clear(&self);
}