From 7a38a60db7536ed4f3f483dbd1940a47f15bb12e Mon Sep 17 00:00:00 2001 From: Kovacsics Robert Date: Sat, 30 Dec 2023 17:22:20 +0000 Subject: [PATCH] Use generics for reusable timer implementation --- examples/rp2040/minimal/Cargo.toml | 2 +- examples/rp2040/minimal/src/main.rs | 3 +- examples/rp2040/multicore/Cargo.toml | 2 +- examples/rp2040/multicore/src/main.rs | 82 ++--- os/Cargo.toml | 3 +- os/src/exec.rs | 103 ++---- os/src/lib.rs | 2 +- os/src/time.rs | 438 +++++++------------------- os/src/time/systick.rs | 314 ++++++++++++++++++ 9 files changed, 498 insertions(+), 451 deletions(-) create mode 100644 os/src/time/systick.rs diff --git a/examples/rp2040/minimal/Cargo.toml b/examples/rp2040/minimal/Cargo.toml index 0ac47cc..0009f57 100644 --- a/examples/rp2040/minimal/Cargo.toml +++ b/examples/rp2040/minimal/Cargo.toml @@ -17,7 +17,7 @@ default-target = "thumbv6m-none-eabi" [dependencies] cortex-m = "0.7.4" cortex-m-rt = "0.7.1" -lilos = { path = "../../../os", default-features = false, features = ["systick"] } +lilos = { path = "../../../os", default-features = false, features = ["timer", "systick"] } panic-halt = "0.2.0" rp2040-pac = {version = "0.3", features = ["rt"]} rp2040-boot2 = "0.2" diff --git a/examples/rp2040/minimal/src/main.rs b/examples/rp2040/minimal/src/main.rs index 10d4626..c65acf9 100644 --- a/examples/rp2040/minimal/src/main.rs +++ b/examples/rp2040/minimal/src/main.rs @@ -57,7 +57,7 @@ fn main() -> ! { // peripherals `p` from the enclosing stack frame. loop { p.SIO.gpio_out_xor.write(|w| unsafe { w.bits(1 << 25) }); - gate.next_time().await; + gate.next_time(&lilos::time::SysTickTimer).await; } }); @@ -68,5 +68,6 @@ fn main() -> ! { lilos::exec::run_tasks( &mut [blink], // <-- array of tasks lilos::exec::ALL_TASKS, // <-- which to start initially + &lilos::time::SysTickTimer, ) } diff --git a/examples/rp2040/multicore/Cargo.toml b/examples/rp2040/multicore/Cargo.toml index 7924271..71c1aca 100644 --- a/examples/rp2040/multicore/Cargo.toml +++ b/examples/rp2040/multicore/Cargo.toml @@ -17,7 +17,7 @@ default-target = "thumbv6m-none-eabi" [dependencies] cortex-m = "0.7.4" cortex-m-rt = "0.7.1" -lilos = { path = "../../../os", default-features = false, features = ["systick", "2core"] } +lilos = { path = "../../../os", default-features = false, features = ["timer", "2core"] } panic-halt = "0.2.0" rp2040-boot2 = "0.2" rp2040-hal = { version = "0.9.1", features = ["critical-section-impl"] } diff --git a/examples/rp2040/multicore/src/main.rs b/examples/rp2040/multicore/src/main.rs index d658b1d..b6e3c8e 100644 --- a/examples/rp2040/multicore/src/main.rs +++ b/examples/rp2040/multicore/src/main.rs @@ -34,10 +34,11 @@ use core::pin::{pin, Pin}; use cortex_m::peripheral::syst::SystClkSource; use cortex_m_rt::exception; use lilos::list::List; -use lilos::time::TickTime; use embedded_hal::digital::v2::ToggleableOutputPin; +type Instant = hal::fugit::Instant; + // For RP2040, we need to include a bootloader. The general Cargo build process // doesn't have great support for this, so we included it as a binary constant. #[link_section = ".boot_loader"] @@ -51,23 +52,16 @@ fn cpu_core_id() -> u16 { hal::Sio::core() as u16 } -fn tick( -) -> hal::fugit::Duration { +fn tick() -> Instant { let timer = unsafe { &*pac::TIMER::ptr() }; - loop { + Instant::from_ticks(loop { let e = timer.timerawh.read().bits(); let t = timer.timerawl.read().bits(); let e2 = timer.timerawh.read().bits(); if e == e2 { break ((e as u64) << 32) | (t as u64); } - } - .micros() -} - -#[export_name = "lilos::time::now"] -fn now() -> u64 { - tick::<1, 1_000>().to_millis() + }) } /// We mostly just need to not enter an infinite loop, which is what the @@ -85,10 +79,10 @@ fn SysTick() { } } -fn make_idle_task<'a, const NOM: u32, const DENOM: u32>( +fn make_idle_task<'a>( core: &'a mut cortex_m::Peripherals, - timer_list: Pin<&'a List>, - sys_clk: hal::fugit::Rate, + timer_list: Pin<&'a List>, + cycles_per_us: u32, ) -> impl FnMut() + 'a { // Make it so that `wfe` waits for masked interrupts as well as events -- // the problem is that the idle-task is called with interrupts disabled (to @@ -101,24 +95,22 @@ fn make_idle_task<'a, const NOM: u32, const DENOM: u32>( core.SCB.scr.modify(|scr| scr | SEVONPEND); } - let cycles_per_millisecond = sys_clk.to_kHz(); // 24-bit timer - let max_sleep_ms = ((1 << 24) - 1) / cycles_per_millisecond; + let max_sleep_us = ((1 << 24) - 1) / cycles_per_us; core.SYST.set_clock_source(SystClkSource::Core); move || { match timer_list.peek() { - Some(wake_at_ms) => { - let wake_at_ms_u64 = wake_at_ms - .millis_since(TickTime::from_millis_since_boot(0)) - .0; - let now_ms = now(); - if wake_at_ms_u64 > now_ms { - let wake_in_ms = - u64::min(max_sleep_ms as u64, wake_at_ms_u64 - now_ms); - let wake_in_ticks = - wake_in_ms as u32 * cycles_per_millisecond; - // Setting zero to the reload register disables systick + Some(wake_at) => { + let now = tick(); + if wake_at > now { + let wake_in_us = u64::min( + max_sleep_us as u64, + (wake_at - now).to_micros(), + ); + let wake_in_ticks = wake_in_us as u32 * cycles_per_us; + // Setting zero to the reload register disables systick -- + // systick is non-zero due to `wake_at > now` core.SYST.set_reload(wake_in_ticks); core.SYST.clear_current(); core.SYST.enable_interrupt(); @@ -136,7 +128,21 @@ fn make_idle_task<'a, const NOM: u32, const DENOM: u32>( cortex_m::asm::wfe(); } } - timer_list.wake_less_than(TickTime::from_millis_since_boot(now())); + } +} + +struct Timer<'a> { + timer_list: Pin<&'a List>, +} + +impl<'a> lilos::time::Timer for Timer<'a> { + type Instant = Instant; + fn timer_list(&self) -> Pin<&'a List> { + self.timer_list + } + + fn now(&self) -> Self::Instant { + tick() } } @@ -192,10 +198,10 @@ fn main() -> ! { let pac = unsafe { pac::Peripherals::steal() }; let mut sio = hal::Sio::new(pac.SIO); - lilos::create_list!(timer_list); + lilos::create_list!(timer_list, Instant::from_ticks(0)); let timer_list = timer_list.as_ref(); - - let idle_task = make_idle_task(&mut core, timer_list, sys_clk); + let timer = Timer { timer_list }; + let idle_task = make_idle_task(&mut core, timer_list, sys_clk.to_MHz()); fifo::reset_read_fifo(&mut sio.fifo); @@ -205,12 +211,8 @@ fn main() -> ! { // Loop forever, blinking things. Note that this borrows the device // peripherals `p` from the enclosing stack frame. loop { - let delay = sio.fifo.read_async().await; - lilos::time::sleep_for( - timer_list, - lilos::time::Millis(delay as u64), - ) - .await; + let delay = sio.fifo.read_async().await as u64; + lilos::time::sleep_for(&timer, delay.millis()).await; led.toggle().unwrap(); } }); @@ -218,6 +220,7 @@ fn main() -> ! { lilos::exec::run_tasks_with_idle( &mut [blink], // <-- array of tasks lilos::exec::ALL_TASKS, // <-- which to start initially + &timer, 1, idle_task, ) @@ -240,10 +243,15 @@ fn main() -> ! { } }); + lilos::create_list!(timer_list, Instant::from_ticks(0)); + let timer_list = timer_list.as_ref(); + let timer = Timer { timer_list }; + // Set up and run the scheduler with a single task. lilos::exec::run_tasks_with_idle( &mut [compute_delay], // <-- array of tasks lilos::exec::ALL_TASKS, // <-- which to start initially + &timer, 0, // We use `SEV` to signal from the other core that we can send more // data. See also the comment above on SEVONPEND diff --git a/os/Cargo.toml b/os/Cargo.toml index d5cb09f..70c285b 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -15,9 +15,10 @@ all-features = true default-target = "thumbv7em-none-eabihf" [features] -default = ["mutex", "spsc", "systick"] +default = ["mutex", "spsc", "timer", "systick"] mutex = [] spsc = [] +timer = [] systick = [] handoff = ["scopeguard"] 2core = [] diff --git a/os/src/exec.rs b/os/src/exec.rs index 3ceaed7..0f21948 100644 --- a/os/src/exec.rs +++ b/os/src/exec.rs @@ -130,6 +130,9 @@ //! convert your use of `run_tasks` to the more complex form, start by copying //! the code from `run_tasks`. +#[cfg(feature = "timer")] +use crate::time::Timer; + use core::convert::Infallible; use core::future::Future; use core::mem; @@ -140,19 +143,6 @@ use pin_project_lite::pin_project; use portable_atomic::{AtomicUsize, Ordering}; -// Despite the untangling of exec and time that happened in the 1.0 release, we -// still have some intimate dependencies between the modules. You'll see a few -// other cfg(feature = "systick") lines below. -cfg_if::cfg_if! { - if #[cfg(all(feature = "systick", not(feature = "2core")))] { - use portable_atomic::AtomicPtr; - - use crate::cheap_assert; - use crate::list::List; - use crate::time::TickTime; - } -} - /// Accumulates bitmasks from wakers as they are invoked. The executor /// atomically checks and clears this at each iteration. static WAKE_BITS: AtomicUsize = AtomicUsize::new(0); @@ -358,9 +348,10 @@ impl Interrupts { /// until an interrupt arrives. This has the advantages of using less power and /// having more predictable response latency than spinning. If you'd like to /// override this behavior, see [`run_tasks_with_idle`]. -pub fn run_tasks( +pub fn run_tasks<#[cfg(feature = "timer")] T: Timer>( futures: &mut [Pin<&mut dyn Future>], initial_mask: usize, + #[cfg(feature = "timer")] timer: &T, #[cfg(feature = "2core")] core: u8, ) -> ! { // Safety: we're passing Interrupts::Masked, the always-safe option @@ -368,6 +359,8 @@ pub fn run_tasks( run_tasks_with_preemption_and_idle( futures, initial_mask, + #[cfg(feature = "timer")] + timer, #[cfg(feature = "2core")] core, Interrupts::Masked, @@ -398,9 +391,10 @@ pub fn run_tasks( /// WFI yourself from within the implementation of `idle_hook`. /// /// See [`run_tasks`] for more details. -pub fn run_tasks_with_idle( +pub fn run_tasks_with_idle<#[cfg(feature = "timer")] T: Timer>( futures: &mut [Pin<&mut dyn Future>], initial_mask: usize, + #[cfg(feature = "timer")] timer: &T, #[cfg(feature = "2core")] core: u8, idle_hook: impl FnMut(), ) -> ! { @@ -409,6 +403,8 @@ pub fn run_tasks_with_idle( run_tasks_with_preemption_and_idle( futures, initial_mask, + #[cfg(feature = "timer")] + timer, #[cfg(feature = "2core")] core, Interrupts::Masked, @@ -437,9 +433,10 @@ pub fn run_tasks_with_idle( /// Note that none of the top-level functions in this module are safe to use /// from a custom ISR. Only operations on types that are specifically described /// as being ISR safe, such as `Notify::notify`, can be used from ISRs. -pub unsafe fn run_tasks_with_preemption( +pub unsafe fn run_tasks_with_preemption<#[cfg(feature = "timer")] T: Timer>( futures: &mut [Pin<&mut dyn Future>], initial_mask: usize, + #[cfg(feature = "timer")] timer: &T, #[cfg(feature = "2core")] core: u8, interrupts: Interrupts, ) -> ! { @@ -448,6 +445,8 @@ pub unsafe fn run_tasks_with_preemption( run_tasks_with_preemption_and_idle( futures, initial_mask, + #[cfg(feature = "timer")] + timer, #[cfg(feature = "2core")] core, interrupts, @@ -479,9 +478,12 @@ pub unsafe fn run_tasks_with_preemption( /// Note that none of the top-level functions in this module are safe to use /// from a custom ISR. Only operations on types that are specifically described /// as being ISR safe, such as `Notify::notify`, can be used from ISRs. -pub unsafe fn run_tasks_with_preemption_and_idle( +pub unsafe fn run_tasks_with_preemption_and_idle< + #[cfg(feature = "timer")] T: Timer, +>( futures: &mut [Pin<&mut dyn Future>], initial_mask: usize, + #[cfg(feature = "timer")] timer: &T, #[cfg(feature = "2core")] core: u8, interrupts: Interrupts, mut idle_hook: impl FnMut(), @@ -520,28 +522,17 @@ pub unsafe fn run_tasks_with_preemption_and_idle( WAKE_BITS.fetch_or(initial_mask & this_mask, Ordering::SeqCst); // TODO make this list static for more predictable memory usage - #[cfg(all(feature = "systick", not(feature = "2core")))] + #[cfg(all(feature = "timer", feature = "systick", not(feature = "2core")))] { create_list!(timer_list); - let old_list = TIMER_LIST.swap( - // Safety: since we've gotten a &mut, we hold the only reference, so - // it's safe for us to smuggle it through a pointer and reborrow it as - // shared. - unsafe { Pin::get_unchecked_mut(timer_list) }, - Ordering::SeqCst, - ); - - cheap_assert!(old_list.is_null()); + crate::time::SysTickTimer.init(timer_list); } loop { interrupts.scope(|| { - #[cfg(all(feature = "systick", not(feature = "2core")))] - { - // Scan for any expired timers. - with_timer_list(|tl| tl.wake_less_than(TickTime::now())); - } + #[cfg(feature = "timer")] + timer.timer_list().wake_less_than(timer.now()); // Capture and reset wake bits (for the current executor only), // then process any 1s. @@ -923,54 +914,6 @@ pub fn wake_task_by_index(index: usize) { wake_tasks_by_mask(wake_mask_for_index(index)); } -/// Tracks the timer list currently in scope. -#[cfg(all(feature = "systick", not(feature = "2core")))] -static TIMER_LIST: AtomicPtr> = - AtomicPtr::new(core::ptr::null_mut()); - -/// Panics if called from an interrupt service routine (ISR). This is used to -/// prevent OS features that are unavailable to ISRs from being used in ISRs. -#[cfg(all(feature = "systick", not(feature = "2core")))] -fn assert_not_in_isr() { - let psr_value = cortex_m::register::apsr::read().bits(); - // Bottom 9 bits are the exception number, which are 0 in Thread mode. - if psr_value & 0x1FF != 0 { - panic!(); - } -} - -/// Nabs a reference to the current timer list and executes `body`. -/// -/// This provides a safe way to access the timer thread local. -/// -/// # Preconditions -/// -/// - Must not be called from an interrupt. -/// - Must only be called with a timer list available, which is to say, from -/// within a task. -#[cfg(all(feature = "systick", not(feature = "2core")))] -pub(crate) fn with_timer_list( - body: impl FnOnce(Pin<&List>) -> R, -) -> R { - // Prevent this from being used from interrupt context. - assert_not_in_isr(); - - let list_ref = { - let tlptr = TIMER_LIST.load(Ordering::Acquire); - // If this assertion fails, it's a sign that one of the timer-aware OS - // primitives (likely a `sleep_*`) has been used without the OS actually - // running. - cheap_assert!(!tlptr.is_null()); - - // Safety: if it's not null, then it came from a `Pin<&mut>` that we - // have been loaned. We do not treat it as a &mut anywhere, so we can - // safely reborrow it as shared. - unsafe { Pin::new_unchecked(&*tlptr) } - }; - - body(list_ref) -} - /// Returns a future that will be pending exactly once before resolving. /// /// This can be used to give up CPU to any other tasks that are currently ready diff --git a/os/src/lib.rs b/os/src/lib.rs index 2845925..085e878 100644 --- a/os/src/lib.rs +++ b/os/src/lib.rs @@ -178,7 +178,7 @@ pub mod handoff; pub mod mutex; #[cfg(feature = "spsc")] pub mod spsc; -#[cfg(feature = "systick")] +#[cfg(feature = "timer")] pub mod time; // For accessing from macros diff --git a/os/src/time.rs b/os/src/time.rs index e1aef30..01359e6 100644 --- a/os/src/time.rs +++ b/os/src/time.rs @@ -1,274 +1,61 @@ -//! Timekeeping using the SysTick Timer. +//! Timekeeping abstracted over [`Timer`]. //! -//! **Note:** this entire module is only available if the `systick` feature is -//! present; it is on by default. +//! This module provides the trait [`Timer`], which if implemented will make the +//! methods [`sleep_until`], [`sleep_for`], [`with_deadline`], [`with_timeout`] +//! available. //! -//! The OS uses the Cortex-M SysTick Timer to maintain a monotonic counter -//! recording the number of milliseconds ("ticks") since boot. This module -//! provides ways to read that timer, and also to arrange for tasks to be woken -//! at specific times (such as [`sleep_until`] and [`sleep_for`]). +//! To implement [`Timer`], you only need to implement a way to get the current +//! time, the [`Timer::timer_list`] function is there to get the `timer_list` +//! member, which should be a [`crate::list::List`] list which is +//! created using [`crate::create_list!`] at the use-site (i.e. just before +//! calling [`crate::exec::run_tasks_with_idle`] or +//! [`crate::exec::run_tasks_with_preemption_and_idle`] -- you need a custom +//! idle hook, see below). //! -//! To use this facility in an application, you need to call -//! [`initialize_sys_tick`] to inform the OS of the system clock speed. -//! Otherwise, no operations in this module will work properly. +//! *However* to make use of the timer, you also need to put +//! [`List::wake_less_than`] into the +//! `idle_hook` argument of [`crate::exec::run_tasks_with_idle`]. You presumably +//! also want to wait for the timer (or other) interrupts in the idle hook to +//! save power. //! -//! You can get the value of tick counter using [`TickTime::now`]. -//! -//! # Types for describing time -//! -//! This module uses three main types for describing time, in slightly different -//! roles. -//! -//! `TickTime` represents a specific point in time, measured as a number of -//! ticks since boot (or, really, since the executor was started). It's a -//! 64-bit count of milliseconds, which means it overflows every 584 million -//! years. This lets us ignore overflows in timestamps, making everything -//! simpler. `TickTime` is analogous to `std::time::Instant` from the Rust -//! standard library. -//! -//! `Millis` represents a relative time interval in milliseconds. This uses the -//! same representation as `TickTime`, so adding them together is cheap. -//! -//! `core::time::Duration` is similar to `Millis` but with a lot more bells and -//! whistles. It's the type used to measure time intervals in the Rust standard -//! library. It can be used with most time-related API in the OS, but you might -//! not want to do so on a smaller CPU: `Duration` uses a mixed-number-base -//! format internally that means almost all operations require a 64-bit multiply -//! or divide. On machines lacking such instructions, this can become quite -//! costly (in terms of both program size and time required). -//! -//! Cases where the OS won't accept `Duration` are mostly around things like -//! sleeps, where the operation will always be performed in units of whole -//! ticks, so being able to pass (say) nanoseconds is misleading. -//! -//! # Imposing a timeout on an operation -//! -//! If you want to stop a concurrent process if it's not done by a certain time, -//! see the [`with_deadline`] function (and its relative friend, -//! [`with_timeout`]). These let you impose a deadline on any future, such that -//! if it hasn't resolved by a certain time, it will be dropped (cancelled). -//! -//! # Fixing "lost ticks" -//! -//! If the longest sequence in your application between any two `await` points -//! takes less than a millisecond, the standard timer configuration will work -//! fine and keep reliable time. -//! -//! However, if you sometimes need to do more work than that -- or if you're -//! concerned you might do so by accident due to a bug -- the systick IRQ can be -//! configured to preempt task code. The OS is designed to handle this safely. -//! For more information, see -//! [`run_tasks_with_preemption`][crate::exec::run_tasks_with_preemption]. -//! -//! # Getting higher precision -//! -//! For many applications, milliseconds are a fine unit of time, but sometimes -//! you need something more precise. Currently, the easiest way to do this is to -//! enlist a different hardware timer. The `time` module has no special -//! privileges that you can't make use of, and adding your own alternate -//! timekeeping module is explictly supported in the design (this is why the -//! `"systick"` feature exists). -//! -//! This can also be useful on processors like the Nordic nRF52 series, where -//! the best sleep mode to use when idling the CPU also stops the systick timer. -//! On platforms like that, the systick isn't useful as a monotonic clock, and -//! you'll want to use some other vendor-specific timer. -//! -//! Currently there's no example of how to do this in the repo. If you need -//! this, please file an issue. +//! See examples/rp2040/multicore/src/main.rs for an example using a custom +//! timer + +#[cfg(all(feature = "systick", not(feature = "2core")))] +pub mod systick; +#[cfg(all(feature = "systick", not(feature = "2core")))] +// Re-export for compatibility +pub use systick::*; -#[cfg(feature = "2core")] use crate::list::List; use core::future::Future; -use core::ops::{Add, AddAssign}; +use core::ops::Add; use core::pin::Pin; use core::task::{Context, Poll}; -use core::time::Duration; -use cortex_m::peripheral::{syst::SystClkSource, SYST}; -#[cfg(not(feature = "2core"))] -use cortex_m_rt::exception; use pin_project_lite::pin_project; -use portable_atomic::{AtomicU32, Ordering}; - -/// Bottom 32 bits of the tick counter. Updated by ISR. -static TICK: AtomicU32 = AtomicU32::new(0); -/// Top 32 bits of the tick counter. Updated by ISR. -static EPOCH: AtomicU32 = AtomicU32::new(0); - -/// Sets up the tick counter for 1kHz operation, assuming a CPU core clock of -/// `clock_mhz`. +/// Sleep trait, allowing for sleeping until, etc. Implemented for single-core, +/// you will want to implement it for multicore. /// -/// If you use this module in your application, call this before -/// [`run_tasks`][crate::exec::run_tasks] (or a fancier version of `run_tasks`) -/// to set up the timer for monotonic operation. -pub fn initialize_sys_tick(syst: &mut SYST, clock_mhz: u32) { - let cycles_per_millisecond = clock_mhz / 1000; - syst.set_reload(cycles_per_millisecond - 1); - syst.clear_current(); - syst.set_clock_source(SystClkSource::Core); - syst.enable_interrupt(); - syst.enable_counter(); -} - -/// Represents a moment in time by the value of the system tick counter. -/// System-specific analog of `std::time::Instant`. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] -pub struct TickTime(u64); - -impl TickTime { - /// Retrieves the current value of the tick counter. - #[cfg(not(feature = "2core"))] - pub fn now() -> Self { - // This loop will only repeat if e != e2, which means we raced the - // systick ISR. Since that ISR only occurs once per millisecond, this - // loop should repeat at most twice. - loop { - let e = EPOCH.load(Ordering::SeqCst); - let t = TICK.load(Ordering::SeqCst); - let e2 = EPOCH.load(Ordering::SeqCst); - if e == e2 { - break TickTime(((e as u64) << 32) | (t as u64)); - } - } - } - - #[cfg(feature = "2core")] - pub fn now() -> Self { - extern "C" { - #[link_name = "lilos::time::now"] - fn now() -> u64; - } - Self(unsafe { now() }) - } +/// Example implementation for multi-core is available in +/// examples/rp2040/multicore/src/main.rs +pub trait Timer { + /// Type of a point in time + type Instant: PartialOrd; - /// Constructs a `TickTime` value describing a certain number of - /// milliseconds since the executor booted. - pub fn from_millis_since_boot(m: u64) -> Self { - Self(m) - } - - /// Subtracts this time from an earlier time, giving the `Duration` between - /// them. - /// - /// # Panics - /// - /// If this time is not actually `>= earlier`. - pub fn duration_since(self, earlier: TickTime) -> Duration { - Duration::from_millis(self.millis_since(earlier).0) - } - - /// Subtracts this time from an earlier time, giving the amount of time - /// between them measured in `Millis`. - /// - /// # Panics - /// - /// If this time is not actually `>= earlier`. - pub fn millis_since(self, earlier: TickTime) -> Millis { - Millis(self.0.checked_sub(earlier.0).unwrap()) - } + /// Read the current point in time + fn now(&self) -> Self::Instant; - /// Checks the clock to determine how much time has elapsed since the - /// instant recorded by `self`. - pub fn elapsed(self) -> Millis { - Self::now().millis_since(self) - } - - /// Checks the clock to determine how much time has elapsed since the - /// instant recorded by `self`. Convenience version that returns the result - /// as a `Duration`. - pub fn elapsed_duration(self) -> Duration { - Duration::from_millis(self.elapsed().0) - } - - /// Adds some milliseconds to `self`, checking for overflow. Note that since - /// we use 64 bit ticks, overflow is unlikely in practice. - pub fn checked_add(self, millis: Millis) -> Option { - self.0.checked_add(millis.0).map(TickTime) - } - - /// Subtracts some milliseconds from `self`, checking for overflow. Overflow - /// can occur if `millis` is longer than the time from boot to `self`. - pub fn checked_sub(self, millis: Millis) -> Option { - self.0.checked_sub(millis.0).map(TickTime) - } -} - -/// Add a `Duration` to a `Ticks` with normal `+` overflow behavior (i.e. -/// checked in debug builds, optionally not checked in release builds). -impl Add for TickTime { - type Output = Self; - fn add(self, other: Duration) -> Self::Output { - TickTime(self.0 + other.as_millis() as u64) - } -} - -impl AddAssign for TickTime { - fn add_assign(&mut self, other: Duration) { - self.0 += other.as_millis() as u64 - } -} - -impl From for u64 { - fn from(t: TickTime) -> Self { - t.0 - } -} - -/// A period of time measured in milliseconds. -/// -/// This plays a role similar to `core::time::Duration` but is designed to be -/// cheaper to use. In particular, as of this writing, `Duration` insists on -/// converting times to and from a Unix-style (seconds, nanoseconds) -/// representation internally. This means that converting to or from any simple -/// monotonic time -- even in nanoseconds! -- requires a 64-bit division or -/// multiplication. Many useful processors, such as Cortex-M0, don't have 32-bit -/// division, much less 64-bit division. -/// -/// `Millis` wraps a `u64` and records a number of milliseconds. Since -/// milliseconds are `lilos`'s unit used for internal timekeeping, this ensures -/// that a `Millis` can be used for any deadline or timeout computation without -/// any unit conversions or expensive arithmetic operations. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] -pub struct Millis(pub u64); - -/// Adds a number of milliseconds to a `TickTime` with normal `+` overflow -/// behavior (i.e. checked in debug builds, optionally not checked in release -/// builds). -impl Add for TickTime { - type Output = Self; - fn add(self, other: Millis) -> Self::Output { - TickTime(self.0 + other.0) - } -} - -/// Adds a number of milliseconds to a `TickTime` with normal `+=` overflow -/// behavior (i.e. checked in debug builds, optionally not checked in release -/// builds). -impl AddAssign for TickTime { - fn add_assign(&mut self, other: Millis) { - self.0 += other.0; - } -} - -impl From for u64 { - fn from(x: Millis) -> Self { - x.0 - } -} - -impl From for Millis { - fn from(x: u64) -> Self { - Self(x) - } + /// Gets the timer-list of the timer + fn timer_list(&self) -> Pin<&List>; } /// Sleeps until the system time is equal to or greater than `deadline`. /// /// More precisely, `sleep_until(d)` returns a `Future` that will poll as -/// `Pending` until `TickTime::now() >= deadline`; then it will poll `Ready`. +/// `Pending` until `Timer::now() >= deadline`; then it will poll `Ready`. /// /// If `deadline` is already in the past, this will instantly become `Ready`. /// @@ -290,39 +77,41 @@ impl From for Millis { /// **Cancel safety:** Strict. /// /// Dropping this future does nothing in particular. -pub async fn sleep_until( - #[cfg(feature = "2core")] timer_list: Pin<&List>, - deadline: TickTime, -) { +pub async fn sleep_until<'a, I, T>(timer: &'a T, deadline: I) +where + I: PartialOrd + 'a, + T: Timer, +{ // TODO: this early return means we can't simply return the insert_and_wait // future below, which is costing us some bytes of text. - if TickTime::now() >= deadline { - return; + cfg_if::cfg_if! { + if #[cfg(not(feature = "2core"))] { + if timer.now() >= deadline { + return; + } + } else { + if timer.now() >= deadline { + return; + } + } } crate::create_node!(node, deadline, crate::exec::noop_waker()); // Insert our node into the pending timer list. If we get cancelled, the // node will detach itself as it's being dropped. - cfg_if::cfg_if! { - if #[cfg(not(feature = "2core"))] { - crate::exec::with_timer_list(|tl| tl.insert_and_wait(node.as_mut())).await - } else { - timer_list.insert_and_wait(node.as_mut()).await - } - } + timer.timer_list().insert_and_wait(node.as_mut()).await } /// Sleeps until the system time has increased by `d`. /// /// More precisely, `sleep_for(d)` captures the system time, `t`, and returns a -/// `Future` that will poll as `Pending` until `TickTime::now() >= t + d`; then +/// `Future` that will poll as `Pending` until `Timer::now() >= t + d`; then /// it will poll `Ready`. /// /// If `d` is 0, this will instantly become `Ready`. /// -/// `d` can be any type that can be added to a `TickTime`, which in practice -/// means either [`Millis`] or [`Duration`]. +/// `d` can be any type that can be added to `Timer::Instant`. /// /// This function is a thin wrapper around [`sleep_until`]. See that function's /// docs for examples, details, and alternatives. @@ -332,18 +121,16 @@ pub async fn sleep_until( /// **Cancel safety:** Strict. /// /// Dropping this future does nothing in particular. -pub fn sleep_for<'a, D>( - #[cfg(feature = "2core")] timer_list: Pin<&'a List>, +pub fn sleep_for<'a, D, I, T>( + timer: &'a T, d: D, ) -> impl Future + 'a where - TickTime: Add, + I: PartialOrd + Add + 'a, + T: Timer, { - sleep_until( - #[cfg(feature = "2core")] - timer_list, - TickTime::now() + d, - ) + let until = timer.now() + d; + sleep_until(timer, until) } /// Alters a future to impose a deadline on its completion. @@ -365,47 +152,42 @@ where /// In this case, `await` drops the future as soon as it resolves (as always), /// which means the nested `some_operation()` future will be promptly dropped /// when we notice that the deadline has been met or exceeded. -pub fn with_deadline<'a, F>( - #[cfg(feature = "2core")] timer_list: Pin<&'a List>, - deadline: TickTime, +pub fn with_deadline<'a, F, I, T>( + timer: &'a T, + deadline: I, code: F, ) -> impl Future> + 'a where + I: PartialOrd + 'a, + T: Timer, F: Future + 'a, { TimeLimited { - limiter: sleep_until( - #[cfg(feature = "2core")] - timer_list, - deadline, - ), + limiter: sleep_until(timer, deadline), process: code, } } /// Alters a future to impose a timeout on its completion. /// -/// This is equivalent to [`with_deadline`] using a deadline of `TickTime::now() +/// This is equivalent to [`with_deadline`] using a deadline of `Timer::now() /// + timeout`. That is, the current time is captured when `with_timeout` is /// called (_not_ at first poll), the provided timeout is added, and that's used /// as the deadline for the returned future. /// /// See [`with_deadline`] for more details. -pub fn with_timeout<'a, D, F>( - #[cfg(feature = "2core")] timer_list: Pin<&'a List>, +pub fn with_timeout<'a, D, F, I, T>( + timer: &'a T, timeout: D, code: F, ) -> impl Future> + 'a where + I: PartialOrd + Add + 'a, + T: Timer, F: Future + 'a, - TickTime: Add, { - with_deadline( - #[cfg(feature = "2core")] - timer_list, - TickTime::now() + timeout, - code, - ) + let until = timer.now() + timeout; + with_deadline(timer, until, code) } pin_project! { @@ -416,7 +198,7 @@ pin_project! { /// resolves to `None`. If `B` resolves first, its result is produced /// wrapped in `Some`. #[derive(Debug)] - struct TimeLimited { + pub struct TimeLimited { #[pin] limiter: A, #[pin] @@ -475,41 +257,55 @@ where /// - [`sleep_for`] can ensure a minimum delay _between_ operations, which is /// different from `PeriodicGate`'s behavior. #[derive(Debug)] -pub struct PeriodicGate { - interval: Millis, - next: TickTime, +pub struct PeriodicGate +where + I: PartialOrd + Add, +{ + interval: D, + next: I, } -impl From for PeriodicGate { - fn from(d: Duration) -> Self { +#[cfg(all(feature = "systick", not(feature = "2core")))] +impl From for PeriodicGate { + /// Creates a periodic gate that can be used to release execution every + /// `interval`, starting right now. + fn from(interval: Millis) -> Self { PeriodicGate { - interval: Millis(d.as_millis() as u64), + interval, next: TickTime::now(), } } } -impl From for PeriodicGate { +impl PeriodicGate +where + D: Copy, + I: PartialOrd + Add + Copy, +{ /// Creates a periodic gate that can be used to release execution every /// `interval`, starting right now. - fn from(interval: Millis) -> Self { + pub fn new(timer: &T, period: D) -> Self + where + T: Timer, + { PeriodicGate { - interval, - next: TickTime::now(), + interval: period, + next: timer.now(), } } -} -impl PeriodicGate { /// Creates a periodic gate that can be used to release execution every /// `interval`, starting `delay` ticks in the future. /// /// This can be useful for creating multiple periodic gates that operate out /// of phase with respect to each other. - pub fn new_shift(interval: Millis, delay: Millis) -> Self { + pub fn new_shift(timer: &T, interval: D, delay: D) -> Self + where + T: Timer, + { PeriodicGate { interval, - next: TickTime::now() + delay, + next: timer.now() + delay, } } @@ -520,27 +316,11 @@ impl PeriodicGate { /// **Cancel safety:** Strict. /// /// Dropping this future does nothing in particular. - pub async fn next_time( - &mut self, - #[cfg(feature = "2core")] timer_list: Pin<&List>, - ) { - sleep_until( - #[cfg(feature = "2core")] - timer_list, - self.next, - ) - .await; - self.next += self.interval; - } -} - -/// System tick ISR. Advances the tick counter. This doesn't wake any tasks; see -/// code in `exec` for that. -#[doc(hidden)] -#[exception] -#[cfg(all(feature = "systick", not(feature = "2core")))] -fn SysTick() { - if TICK.fetch_add(1, Ordering::Release) == core::u32::MAX { - EPOCH.fetch_add(1, Ordering::Release); + pub async fn next_time(&mut self, timer: &T) + where + T: Timer, + { + sleep_until(timer, self.next).await; + self.next = self.next + self.interval; } } diff --git a/os/src/time/systick.rs b/os/src/time/systick.rs new file mode 100644 index 0000000..25f0185 --- /dev/null +++ b/os/src/time/systick.rs @@ -0,0 +1,314 @@ +//! Timekeeping using the SysTick Timer. +//! +//! **Note:** this entire module is only available if the `systick` feature is +//! present, and the `2core` feature is not; which is the default. +//! +//! The OS uses the Cortex-M SysTick Timer to maintain a monotonic counter +//! recording the number of milliseconds ("ticks") since boot. This module +//! provides ways to read that timer, and also to arrange for tasks to be woken +//! at specific times (such as [`super::sleep_until`] and [`super::sleep_for`]). +//! +//! To use this facility in an application, you need to call +//! [`initialize_sys_tick`] to inform the OS of the system clock speed. +//! Otherwise, no operations in this module will work properly. +//! +//! You can get the value of tick counter using [`TickTime::now`]. +//! +//! # Types for describing time +//! +//! This module uses three main types for describing time, in slightly different +//! roles. +//! +//! `TickTime` represents a specific point in time, measured as a number of +//! ticks since boot (or, really, since the executor was started). It's a +//! 64-bit count of milliseconds, which means it overflows every 584 million +//! years. This lets us ignore overflows in timestamps, making everything +//! simpler. `TickTime` is analogous to `std::time::Instant` from the Rust +//! standard library. +//! +//! `Millis` represents a relative time interval in milliseconds. This uses the +//! same representation as `TickTime`, so adding them together is cheap. +//! +//! `core::time::Duration` is similar to `Millis` but with a lot more bells and +//! whistles. It's the type used to measure time intervals in the Rust standard +//! library. It can be used with most time-related API in the OS, but you might +//! not want to do so on a smaller CPU: `Duration` uses a mixed-number-base +//! format internally that means almost all operations require a 64-bit multiply +//! or divide. On machines lacking such instructions, this can become quite +//! costly (in terms of both program size and time required). +//! +//! Cases where the OS won't accept `Duration` are mostly around things like +//! sleeps, where the operation will always be performed in units of whole +//! ticks, so being able to pass (say) nanoseconds is misleading. +//! +//! # Imposing a timeout on an operation +//! +//! If you want to stop a concurrent process if it's not done by a certain time, +//! see the [`super::with_deadline`] function (and its relative friend, +//! [`super::with_timeout`]). These let you impose a deadline on any future, such that +//! if it hasn't resolved by a certain time, it will be dropped (cancelled). +//! +//! # Fixing "lost ticks" +//! +//! If the longest sequence in your application between any two `await` points +//! takes less than a millisecond, the standard timer configuration will work +//! fine and keep reliable time. +//! +//! However, if you sometimes need to do more work than that -- or if you're +//! concerned you might do so by accident due to a bug -- the systick IRQ can be +//! configured to preempt task code. The OS is designed to handle this safely. +//! For more information, see +//! [`run_tasks_with_preemption`][crate::exec::run_tasks_with_preemption]. +//! +//! # Getting higher precision +//! +//! For many applications, milliseconds are a fine unit of time, but sometimes +//! you need something more precise. Currently, the easiest way to do this is to +//! enlist a different hardware timer. The `time` module has no special +//! privileges that you can't make use of, and adding your own alternate +//! timekeeping module is explictly supported in the design (this is why the +//! `"systick"` feature exists). +//! +//! This can also be useful on processors like the Nordic nRF52 series, where +//! the best sleep mode to use when idling the CPU also stops the systick timer. +//! On platforms like that, the systick isn't useful as a monotonic clock, and +//! you'll want to use some other vendor-specific timer. +//! +//! Currently there's no example of how to do this in the repo. If you need +//! this, please file an issue. + +use crate::cheap_assert; +use crate::list::List; + +use core::ops::{Add, AddAssign}; +use core::pin::Pin; +use core::time::Duration; + +use cortex_m::peripheral::{syst::SystClkSource, SYST}; +use cortex_m_rt::exception; + +use portable_atomic::{AtomicPtr, AtomicU32, Ordering}; + +/// Bottom 32 bits of the tick counter. Updated by ISR. +static TICK: AtomicU32 = AtomicU32::new(0); +/// Top 32 bits of the tick counter. Updated by ISR. +static EPOCH: AtomicU32 = AtomicU32::new(0); + +/// Sets up the tick counter for 1kHz operation, assuming a CPU core clock of +/// `clock_mhz`. +/// +/// If you use this module in your application, call this before +/// [`run_tasks`][crate::exec::run_tasks] (or a fancier version of `run_tasks`) +/// to set up the timer for monotonic operation. +pub fn initialize_sys_tick(syst: &mut SYST, clock_mhz: u32) { + let cycles_per_millisecond = clock_mhz / 1000; + syst.set_reload(cycles_per_millisecond - 1); + syst.clear_current(); + syst.set_clock_source(SystClkSource::Core); + syst.enable_interrupt(); + syst.enable_counter(); +} + +/// Represents a moment in time by the value of the system tick counter. +/// System-specific analog of `std::time::Instant`. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] +pub struct TickTime(u64); + +impl TickTime { + /// Retrieves the current value of the tick counter. + pub fn now() -> Self { + // This loop will only repeat if e != e2, which means we raced the + // systick ISR. Since that ISR only occurs once per millisecond, this + // loop should repeat at most twice. + loop { + let e = EPOCH.load(Ordering::SeqCst); + let t = TICK.load(Ordering::SeqCst); + let e2 = EPOCH.load(Ordering::SeqCst); + if e == e2 { + break TickTime(((e as u64) << 32) | (t as u64)); + } + } + } + + /// Constructs a `TickTime` value describing a certain number of + /// milliseconds since the executor booted. + pub fn from_millis_since_boot(m: u64) -> Self { + Self(m) + } + + /// Subtracts this time from an earlier time, giving the `Duration` between + /// them. + /// + /// # Panics + /// + /// If this time is not actually `>= earlier`. + pub fn duration_since(self, earlier: TickTime) -> Duration { + Duration::from_millis(self.millis_since(earlier).0) + } + + /// Subtracts this time from an earlier time, giving the amount of time + /// between them measured in `Millis`. + /// + /// # Panics + /// + /// If this time is not actually `>= earlier`. + pub fn millis_since(self, earlier: TickTime) -> Millis { + Millis(self.0.checked_sub(earlier.0).unwrap()) + } + + /// Checks the clock to determine how much time has elapsed since the + /// instant recorded by `self`. + pub fn elapsed(self) -> Millis { + Self::now().millis_since(self) + } + + /// Checks the clock to determine how much time has elapsed since the + /// instant recorded by `self`. Convenience version that returns the result + /// as a `Duration`. + pub fn elapsed_duration(self) -> Duration { + Duration::from_millis(self.elapsed().0) + } + + /// Adds some milliseconds to `self`, checking for overflow. Note that since + /// we use 64 bit ticks, overflow is unlikely in practice. + pub fn checked_add(self, millis: Millis) -> Option { + self.0.checked_add(millis.0).map(TickTime) + } + + /// Subtracts some milliseconds from `self`, checking for overflow. Overflow + /// can occur if `millis` is longer than the time from boot to `self`. + pub fn checked_sub(self, millis: Millis) -> Option { + self.0.checked_sub(millis.0).map(TickTime) + } +} + +/// Add a `Duration` to a `Ticks` with normal `+` overflow behavior (i.e. +/// checked in debug builds, optionally not checked in release builds). +impl Add for TickTime { + type Output = Self; + fn add(self, other: Duration) -> Self::Output { + TickTime(self.0 + other.as_millis() as u64) + } +} + +impl AddAssign for TickTime { + fn add_assign(&mut self, other: Duration) { + self.0 += other.as_millis() as u64 + } +} + +impl From for u64 { + fn from(t: TickTime) -> Self { + t.0 + } +} + +/// A period of time measured in milliseconds. +/// +/// This plays a role similar to `core::time::Duration` but is designed to be +/// cheaper to use. In particular, as of this writing, `Duration` insists on +/// converting times to and from a Unix-style (seconds, nanoseconds) +/// representation internally. This means that converting to or from any simple +/// monotonic time -- even in nanoseconds! -- requires a 64-bit division or +/// multiplication. Many useful processors, such as Cortex-M0, don't have 32-bit +/// division, much less 64-bit division. +/// +/// `Millis` wraps a `u64` and records a number of milliseconds. Since +/// milliseconds are `lilos`'s unit used for internal timekeeping, this ensures +/// that a `Millis` can be used for any deadline or timeout computation without +/// any unit conversions or expensive arithmetic operations. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] +pub struct Millis(pub u64); + +/// Adds a number of milliseconds to a `TickTime` with normal `+` overflow +/// behavior (i.e. checked in debug builds, optionally not checked in release +/// builds). +impl Add for TickTime { + type Output = Self; + fn add(self, other: Millis) -> Self::Output { + TickTime(self.0 + other.0) + } +} + +/// Adds a number of milliseconds to a `TickTime` with normal `+=` overflow +/// behavior (i.e. checked in debug builds, optionally not checked in release +/// builds). +impl AddAssign for TickTime { + fn add_assign(&mut self, other: Millis) { + self.0 += other.0; + } +} + +impl From for u64 { + fn from(x: Millis) -> Self { + x.0 + } +} + +impl From for Millis { + fn from(x: u64) -> Self { + Self(x) + } +} + +/// System tick ISR. Advances the tick counter. This doesn't wake any tasks; see +/// code in `exec` for that. +#[doc(hidden)] +#[exception] +fn SysTick() { + if TICK.fetch_add(1, Ordering::Release) == core::u32::MAX { + EPOCH.fetch_add(1, Ordering::Release); + } +} + +static TIMER_LIST: AtomicPtr> = + AtomicPtr::new(core::ptr::null_mut()); + +/// A timer using the SysTick of the core. +#[derive(Debug)] +pub struct SysTickTimer; + +impl SysTickTimer { + pub(crate) fn init(&self, timer_list: Pin<&mut List>) { + let old_list = TIMER_LIST.swap( + // Safety: since we've gotten a &mut, we hold the only reference, so + // it's safe for us to smuggle it through a pointer and reborrow it as + // shared. + unsafe { Pin::get_unchecked_mut(timer_list) }, + Ordering::SeqCst, + ); + + cheap_assert!(old_list.is_null()); + } +} + +impl super::Timer for SysTickTimer { + type Instant = TickTime; + + fn now(&self) -> Self::Instant { + TickTime::now() + } + + /// Nabs a reference to the current timer list and executes `body`. + /// + /// This provides a safe way to access the timer thread local. + /// + /// # Preconditions + /// + /// - Must not be called from an interrupt. + /// - Must only be called with a timer list available, which is to say, from + /// within a task. + fn timer_list(&self) -> Pin<&List> { + let tlptr = TIMER_LIST.load(Ordering::Acquire); + + // If this assertion fails, it's a sign that one of the timer-aware OS + // primitives (likely a `sleep_*`) has been used without the OS actually + // running. + cheap_assert!(!tlptr.is_null()); + + // Safety: if it's not null, then it came from a `Pin<&mut>` that we + // have been loaned. We do not treat it as a &mut anywhere, so we can + // safely reborrow it as shared. + unsafe { Pin::new_unchecked(&*tlptr) } + } +}