aboutsummaryrefslogtreecommitdiff
path: root/rtic-timer/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rtic-timer/src/lib.rs')
-rw-r--r--rtic-timer/src/lib.rs390
1 files changed, 294 insertions, 96 deletions
diff --git a/rtic-timer/src/lib.rs b/rtic-timer/src/lib.rs
index e7051d27..d7faa07f 100644
--- a/rtic-timer/src/lib.rs
+++ b/rtic-timer/src/lib.rs
@@ -1,138 +1,336 @@
+//! Crate
+
#![no_std]
+#![no_main]
+#![deny(missing_docs)]
+#![allow(incomplete_features)]
+#![feature(async_fn_in_trait)]
+
+pub mod monotonic;
+
+use core::future::{poll_fn, Future};
+use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+use core::task::{Poll, Waker};
+use futures_util::{
+ future::{select, Either},
+ pin_mut,
+};
+pub use monotonic::Monotonic;
-use core::sync::atomic::{AtomicU32, Ordering};
-use core::{cmp::Ordering, task::Waker};
-use cortex_m::peripheral::{syst::SystClkSource, SYST};
-pub use fugit::{self, ExtU64};
-pub use rtic_monotonic::Monotonic;
+mod linked_list;
-mod sll;
-use sll::{IntrusiveSortedLinkedList, Min as IsslMin, Node as IntrusiveNode};
+use linked_list::{Link, LinkedList};
-pub struct Timer {
- cnt: AtomicU32,
- // queue: IntrusiveSortedLinkedList<'static, WakerNotReady<Mono>, IsslMin>,
+/// Holds a waker and at which time instant this waker shall be awoken.
+struct WaitingWaker<Mono: Monotonic> {
+ waker: Waker,
+ release_at: Mono::Instant,
+}
+
+impl<Mono: Monotonic> Clone for WaitingWaker<Mono> {
+ fn clone(&self) -> Self {
+ Self {
+ waker: self.waker.clone(),
+ release_at: self.release_at,
+ }
+ }
}
-#[allow(non_snake_case)]
-#[no_mangle]
-fn SysTick() {
- // ..
- let cnt = unsafe {
- static mut CNT: u32 = 0;
- &mut CNT
- };
+impl<Mono: Monotonic> PartialEq for WaitingWaker<Mono> {
+ fn eq(&self, other: &Self) -> bool {
+ self.release_at == other.release_at
+ }
+}
- *cnt = cnt.wrapping_add(1);
+impl<Mono: Monotonic> PartialOrd for WaitingWaker<Mono> {
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ self.release_at.partial_cmp(&other.release_at)
+ }
}
-/// Systick implementing `rtic_monotonic::Monotonic` which runs at a
-/// settable rate using the `TIMER_HZ` parameter.
-pub struct Systick<const TIMER_HZ: u32> {
- systick: SYST,
- cnt: u64,
+/// A generic timer queue for async executors.
+///
+/// # Blocking
+///
+/// The internal priority queue uses global critical sections to manage access. This means that
+/// `await`ing a delay will cause a lock of the entire system for O(n) time. In practice the lock
+/// duration is ~10 clock cycles per element in the queue.
+///
+/// # Safety
+///
+/// This timer queue is based on an intrusive linked list, and by extension the links are strored
+/// on the async stacks of callers. The links are deallocated on `drop` or when the wait is
+/// complete.
+///
+/// Do not call `mem::forget` on an awaited future, or there will be dragons!
+pub struct TimerQueue<Mono: Monotonic> {
+ queue: LinkedList<WaitingWaker<Mono>>,
+ initialized: AtomicBool,
}
-impl<const TIMER_HZ: u32> Systick<TIMER_HZ> {
- /// Provide a new `Monotonic` based on SysTick.
+/// This indicates that there was a timeout.
+pub struct TimeoutError;
+
+impl<Mono: Monotonic> TimerQueue<Mono> {
+ /// Make a new queue.
+ pub const fn new() -> Self {
+ Self {
+ queue: LinkedList::new(),
+ initialized: AtomicBool::new(false),
+ }
+ }
+
+ /// Forwards the `Monotonic::now()` method.
+ #[inline(always)]
+ pub fn now(&self) -> Mono::Instant {
+ Mono::now()
+ }
+
+ /// Takes the initialized monotonic to initialize the TimerQueue.
+ pub fn initialize(&self, monotonic: Mono) {
+ self.initialized.store(true, Ordering::SeqCst);
+
+ // Don't run drop on `Mono`
+ core::mem::forget(monotonic);
+ }
+
+ /// Call this in the interrupt handler of the hardware timer supporting the `Monotonic`
///
- /// The `sysclk` parameter is the speed at which SysTick runs at. This value should come from
- /// the clock generation function of the used HAL.
+ /// # Safety
///
- /// Notice that the actual rate of the timer is a best approximation based on the given
- /// `sysclk` and `TIMER_HZ`.
- pub fn new(mut systick: SYST, sysclk: u32) -> Self {
- // + TIMER_HZ / 2 provides round to nearest instead of round to 0.
- // - 1 as the counter range is inclusive [0, reload]
- let reload = (sysclk + TIMER_HZ / 2) / TIMER_HZ - 1;
+ /// It's always safe to call, but it must only be called from the interrupt of the
+ /// monotonic timer for correct operation.
+ pub unsafe fn on_monotonic_interrupt(&self) {
+ Mono::clear_compare_flag();
+ Mono::on_interrupt();
- assert!(reload <= 0x00ff_ffff);
- assert!(reload > 0);
+ loop {
+ let mut release_at = None;
+ let head = self.queue.pop_if(|head| {
+ release_at = Some(head.release_at);
- systick.disable_counter();
- systick.set_clock_source(SystClkSource::Core);
- systick.set_reload(reload);
+ Mono::now() >= head.release_at
+ });
- Systick { systick, cnt: 0 }
- }
-}
+ match (head, release_at) {
+ (Some(link), _) => {
+ link.waker.wake();
+ }
+ (None, Some(instant)) => {
+ Mono::enable_timer();
+ Mono::set_compare(instant);
-impl<const TIMER_HZ: u32> Monotonic for Systick<TIMER_HZ> {
- const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false;
+ if Mono::now() >= instant {
+ // The time for the next instant passed while handling it,
+ // continue dequeueing
+ continue;
+ }
- type Instant = fugit::TimerInstantU64<TIMER_HZ>;
- type Duration = fugit::TimerDurationU64<TIMER_HZ>;
+ break;
+ }
+ (None, None) => {
+ // Queue is empty
+ Mono::disable_timer();
- fn now(&mut self) -> Self::Instant {
- if self.systick.has_wrapped() {
- self.cnt = self.cnt.wrapping_add(1);
+ break;
+ }
+ }
}
-
- Self::Instant::from_ticks(self.cnt)
}
- unsafe fn reset(&mut self) {
- self.systick.clear_current();
- self.systick.enable_counter();
- }
+ /// Timeout at a specific time.
+ pub async fn timeout_at<F: Future>(
+ &self,
+ instant: Mono::Instant,
+ future: F,
+ ) -> Result<F::Output, TimeoutError> {
+ let delay = self.delay_until(instant);
- #[inline(always)]
- fn set_compare(&mut self, _val: Self::Instant) {
- // No need to do something here, we get interrupts anyway.
+ pin_mut!(future);
+ pin_mut!(delay);
+
+ match select(future, delay).await {
+ Either::Left((r, _)) => Ok(r),
+ Either::Right(_) => Err(TimeoutError),
+ }
}
- #[inline(always)]
- fn clear_compare_flag(&mut self) {
- // NOOP with SysTick interrupt
+ /// Timeout after a specific duration.
+ #[inline]
+ pub async fn timeout_after<F: Future>(
+ &self,
+ duration: Mono::Duration,
+ future: F,
+ ) -> Result<F::Output, TimeoutError> {
+ self.timeout_at(Mono::now() + duration, future).await
}
- #[inline(always)]
- fn zero() -> Self::Instant {
- Self::Instant::from_ticks(0)
+ /// Delay for some duration of time.
+ #[inline]
+ pub async fn delay(&self, duration: Mono::Duration) {
+ let now = Mono::now();
+
+ self.delay_until(now + duration).await;
}
- #[inline(always)]
- fn on_interrupt(&mut self) {
- if self.systick.has_wrapped() {
- self.cnt = self.cnt.wrapping_add(1);
+ /// Delay to some specific time instant.
+ pub async fn delay_until(&self, instant: Mono::Instant) {
+ if !self.initialized.load(Ordering::Relaxed) {
+ panic!(
+ "The timer queue is not initialized with a monotonic, you need to run `initialize`"
+ );
}
+
+ let mut first_run = true;
+ let queue = &self.queue;
+ let mut link = Link::new(WaitingWaker {
+ waker: poll_fn(|cx| Poll::Ready(cx.waker().clone())).await,
+ release_at: instant,
+ });
+
+ let marker = &AtomicUsize::new(0);
+
+ let dropper = OnDrop::new(|| {
+ queue.delete(marker.load(Ordering::Relaxed));
+ });
+
+ poll_fn(|_| {
+ if Mono::now() >= instant {
+ return Poll::Ready(());
+ }
+
+ if first_run {
+ first_run = false;
+ let (was_empty, addr) = queue.insert(&mut link);
+ marker.store(addr, Ordering::Relaxed);
+
+ if was_empty {
+ // Pend the monotonic handler if the queue was empty to setup the timer.
+ Mono::pend_interrupt();
+ }
+ }
+
+ Poll::Pending
+ })
+ .await;
+
+ // Make sure that our link is deleted from the list before we drop this stack
+ drop(dropper);
}
}
-struct WakerNotReady<Mono>
-where
- Mono: Monotonic,
-{
- pub waker: Waker,
- pub instant: Mono::Instant,
- pub marker: u32,
+struct OnDrop<F: FnOnce()> {
+ f: core::mem::MaybeUninit<F>,
}
-impl<Mono> Eq for WakerNotReady<Mono> where Mono: Monotonic {}
-
-impl<Mono> Ord for WakerNotReady<Mono>
-where
- Mono: Monotonic,
-{
- fn cmp(&self, other: &Self) -> Ordering {
- self.instant.cmp(&other.instant)
+impl<F: FnOnce()> OnDrop<F> {
+ pub fn new(f: F) -> Self {
+ Self {
+ f: core::mem::MaybeUninit::new(f),
+ }
}
-}
-impl<Mono> PartialEq for WakerNotReady<Mono>
-where
- Mono: Monotonic,
-{
- fn eq(&self, other: &Self) -> bool {
- self.instant == other.instant
+ #[allow(unused)]
+ pub fn defuse(self) {
+ core::mem::forget(self)
}
}
-impl<Mono> PartialOrd for WakerNotReady<Mono>
-where
- Mono: Monotonic,
-{
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
+impl<F: FnOnce()> Drop for OnDrop<F> {
+ fn drop(&mut self) {
+ unsafe { self.f.as_ptr().read()() }
}
}
+
+// -------- Test program ---------
+//
+//
+// use systick_monotonic::{Systick, TimerQueue};
+//
+// // same panicking *behavior* as `panic-probe` but doesn't print a panic message
+// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked
+// #[defmt::panic_handler]
+// fn panic() -> ! {
+// cortex_m::asm::udf()
+// }
+//
+// /// Terminates the application and makes `probe-run` exit with exit-code = 0
+// pub fn exit() -> ! {
+// loop {
+// cortex_m::asm::bkpt();
+// }
+// }
+//
+// defmt::timestamp!("{=u64:us}", {
+// let time_us: fugit::MicrosDurationU32 = MONO.now().duration_since_epoch().convert();
+//
+// time_us.ticks() as u64
+// });
+//
+// make_systick_timer_queue!(MONO, Systick<1_000>);
+//
+// #[rtic::app(
+// device = nrf52832_hal::pac,
+// dispatchers = [SWI0_EGU0, SWI1_EGU1, SWI2_EGU2, SWI3_EGU3, SWI4_EGU4, SWI5_EGU5],
+// )]
+// mod app {
+// use super::{Systick, MONO};
+// use fugit::ExtU32;
+//
+// #[shared]
+// struct Shared {}
+//
+// #[local]
+// struct Local {}
+//
+// #[init]
+// fn init(cx: init::Context) -> (Shared, Local) {
+// defmt::println!("init");
+//
+// let systick = Systick::start(cx.core.SYST, 64_000_000);
+//
+// defmt::println!("initializing monotonic");
+//
+// MONO.initialize(systick);
+//
+// async_task::spawn().ok();
+// async_task2::spawn().ok();
+// async_task3::spawn().ok();
+//
+// (Shared {}, Local {})
+// }
+//
+// #[idle]
+// fn idle(_: idle::Context) -> ! {
+// defmt::println!("idle");
+//
+// loop {
+// core::hint::spin_loop();
+// }
+// }
+//
+// #[task]
+// async fn async_task(_: async_task::Context) {
+// loop {
+// defmt::println!("async task waiting for 1 second");
+// MONO.delay(1.secs()).await;
+// }
+// }
+//
+// #[task]
+// async fn async_task2(_: async_task2::Context) {
+// loop {
+// defmt::println!(" async task 2 waiting for 0.5 second");
+// MONO.delay(500.millis()).await;
+// }
+// }
+//
+// #[task]
+// async fn async_task3(_: async_task3::Context) {
+// loop {
+// defmt::println!(" async task 3 waiting for 0.2 second");
+// MONO.delay(200.millis()).await;
+// }
+// }
+// }
+//