diff --git a/tests/timer/CMakeLists.txt b/tests/timer/CMakeLists.txt new file mode 100644 index 00000000..0f240c95 --- /dev/null +++ b/tests/timer/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(timer_rust) + +rust_cargo_application() diff --git a/tests/timer/Cargo.toml b/tests/timer/Cargo.toml new file mode 100644 index 00000000..52edcd20 --- /dev/null +++ b/tests/timer/Cargo.toml @@ -0,0 +1,18 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +[package] +# This must be rustapp for now. +name = "rustapp" +version = "3.7.0" +edition = "2021" +description = "Tests of timeers" +license = "Apache-2.0 or MIT" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +rand = { version = "0.8", default-features = false } +rand_pcg = { version = "0.3.1", default-features = false } +zephyr = "3.7.0" diff --git a/tests/timer/prj.conf b/tests/timer/prj.conf new file mode 100644 index 00000000..295d9195 --- /dev/null +++ b/tests/timer/prj.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_RUST=y +CONFIG_MAIN_STACK_SIZE=2048 + +# Timers need alloc +CONFIG_RUST_ALLOC=y diff --git a/tests/timer/src/lib.rs b/tests/timer/src/lib.rs new file mode 100644 index 00000000..8676286a --- /dev/null +++ b/tests/timer/src/lib.rs @@ -0,0 +1,145 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +extern crate alloc; + +use core::{pin::Pin, sync::atomic::Ordering}; + +use alloc::{boxed::Box, vec::Vec}; +use rand::Rng; +use rand_pcg::Pcg32; +use zephyr::{ + printkln, sync::{atomic::AtomicUsize, Arc}, time::{Duration, NoWait, Tick}, timer::{Callback, CallbackTimer, SimpleTimer, StoppedTimer} +}; + +// Test the timers interface. There are a couple of things this tries to test: +// 1. Do timers dynamically allocated and dropped work. +// 2. Can simple timers count properly. +// 3. Can we wait on a Simple timer. +// 4. Do callbacks work with messages and semaphores. + +#[no_mangle] +extern "C" fn rust_main() { + printkln!("Tick frequency: {}", zephyr::time::SYS_FREQUENCY); + timer_test(); + printkln!("All tests passed"); +} + +fn timer_test() { + let mut rng = Pcg32::new(1, 1); + + // Track a global "stop" time when the entire test should be shut down. + // let mut total_test = StoppedTimer::new().start_simple(Duration::secs_at_least(5), NoWait); + let mut total_test = StoppedTimer::new().start_simple(Duration::secs_at_least(5), NoWait); + + // This simple timer lets us pause periodically to allow other timers to build up. + let mut period = StoppedTimer::new().start_simple( + Duration::millis_at_least(100), + Duration::millis_at_least(100), + ); + + let mut simples: Vec<_> = (0..10).map(|_| TestSimple::new(&mut rng)).collect(); + let atomics: Vec<_> = (0..10).map(|_| TestAtomic::new(&mut rng)).collect(); + + let mut count = 0; + loop { + // Wait for the period timer. + let num = period.read_count_wait(); + + if num > 1 { + // Getting this is actually a good indicator that we've overwhelmed ourselves with + // timers, and are stress testing things. + printkln!("Note: Missed period ticks"); + } + + count += 1; + + if count % 10 == 0 { + printkln!("Ticks {}", count); + } + + if total_test.read_count() > 0 { + break; + } + + simples.iter_mut().for_each(|m| m.update()); + } + + // Collect all of the times they fired. + let simple_count: usize = simples.iter().map(|s| s.count).sum(); + printkln!("Simple fired {} times", simple_count); + let atomic_count: usize = atomics.iter().map(|s| s.count()).sum(); + printkln!("Atomics fired {} times", atomic_count); + + printkln!("Period ticks: {}", count); + + // Now that everything is done and cleaned up, allow a little time to pass to make sure there + // are no stray timers. We can re-use the total test timer. + let mut total_test = total_test.stop().start_simple(Duration::millis_at_least(1), NoWait); + total_test.read_count_wait(); +} + +/// Test a SimpleTimer. +/// +/// This allocates a simple timer, and starts it with a small somewhat random period. It will track +/// the total number of times that it fires when checked. +struct TestSimple { + timer: SimpleTimer, + _delay: Tick, + count: usize, +} + +impl TestSimple { + fn new(rng: &mut impl Rng) -> TestSimple { + let delay = rng.gen_range(2..16); + TestSimple { + timer: StoppedTimer::new() + .start_simple(Duration::from_ticks(delay), Duration::from_ticks(delay)), + _delay: delay, + count: 0, + } + } + + /// Update from the total count from the timer itself. + fn update(&mut self) { + self.count += self.timer.read_count() as usize; + } +} + +/// Test a callback using an atomic counter. +/// +/// This allocates a Callback timer, and uses the callback to increment an atomic value. +struct TestAtomic { + _timer: Pin>>>, + counter: Arc, +} + +impl TestAtomic { + fn new(rng: &mut impl Rng) -> TestAtomic { + let delay = rng.gen_range(2..16); + let counter = Arc::new(AtomicUsize::new(0)); + TestAtomic { + _timer: StoppedTimer::new().start_callback( + Callback { + call: Self::expiry, + data: counter.clone(), + }, + Duration::from_ticks(delay), + Duration::from_ticks(delay), + ), + counter: counter.clone(), + } + } + + // Read the atomic count. + fn count(&self) -> usize { + self.counter.load(Ordering::Acquire) + } + + /// Expire the function + fn expiry(data: &Arc) { + data.fetch_add(1, Ordering::Relaxed); + } +} diff --git a/tests/timer/testcase.yaml b/tests/timer/testcase.yaml new file mode 100644 index 00000000..eb08eec7 --- /dev/null +++ b/tests/timer/testcase.yaml @@ -0,0 +1,17 @@ +common: + filter: CONFIG_RUST_SUPPORTED + platform_allow: + - qemu_cortex_m0 + - qemu_cortex_m3 + - qemu_riscv32 + - qemu_riscv32/qemu_virt_riscv32/smp + - qemu_riscv64 + - qemu_riscv64/qemu_virt_riscv64/smp + - nrf52840dk/nrf52840 +tests: + test.rust.timer: + harness: console + harness_config: + type: one_line + regex: + - "All tests passed" diff --git a/zephyr-sys/src/lib.rs b/zephyr-sys/src/lib.rs index ee43a123..5f317d8c 100644 --- a/zephyr-sys/src/lib.rs +++ b/zephyr-sys/src/lib.rs @@ -18,6 +18,7 @@ #![allow(improper_ctypes)] #![allow(rustdoc::broken_intra_doc_links)] +#![allow(rustdoc::bare_urls)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index aca2bd60..c92ceb4c 100644 --- a/zephyr/src/lib.rs +++ b/zephyr/src/lib.rs @@ -17,6 +17,8 @@ pub mod object; pub mod sync; pub mod sys; pub mod time; +#[cfg(CONFIG_RUST_ALLOC)] +pub mod timer; pub use error::{Error, Result}; diff --git a/zephyr/src/sync/spinmutex.rs b/zephyr/src/sync/spinmutex.rs index 3de22618..49ee444f 100644 --- a/zephyr/src/sync/spinmutex.rs +++ b/zephyr/src/sync/spinmutex.rs @@ -41,6 +41,8 @@ pub enum SpinTryLockError { /// This is modeled after [`sync::Mutex`] but instead of using `k_mutex` from Zephyr, it uses /// `k_spinlock`. It's main advantage is that it is usable from IRQ context. However, it also is /// uninterruptible, and prevents even IRQ handlers from running. +/// +/// [`sync::Mutex`]: crate::sync::Mutex pub struct SpinMutex { inner: UnsafeCell, data: UnsafeCell, diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index e17733d9..f85069a1 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -50,6 +50,8 @@ pub mod critical { //! This provides the underlying critical section crate, which is useful for external crates //! that want this interface. However, it isn't a particularly hygienic interface to use. For //! something a bit nicer, please see [`sync::SpinMutex`]. + //! + //! [`sync::SpinMutex`]: crate::sync::SpinMutex use core::{ffi::c_int, ptr::addr_of_mut}; diff --git a/zephyr/src/sys/queue.rs b/zephyr/src/sys/queue.rs index a2c69752..943733f0 100644 --- a/zephyr/src/sys/queue.rs +++ b/zephyr/src/sys/queue.rs @@ -65,6 +65,10 @@ impl Queue { /// The timeout value can be [`Forever`] to block until there is a message, [`NoWait`] to check /// and immediately return if there is no message, or a [`Duration`] to indicate a specific /// timeout. + /// + /// [`Forever`]: crate::time::Forever + /// [`NoWait`]: crate::time::NoWait + /// [`Duration`]: crate::time::Duration pub unsafe fn recv(&self, timeout: T) -> *mut c_void where T: Into { diff --git a/zephyr/src/timer.rs b/zephyr/src/timer.rs new file mode 100644 index 00000000..658911bd --- /dev/null +++ b/zephyr/src/timer.rs @@ -0,0 +1,385 @@ +// Copyright (c) 2024 EOVE SAS +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +//! Zephyr timers +//! +//! This provides a relatively high-level and almost safe interface to Zephyr's underlying +//! `k_timer`. +//! +//! Every timer starts as a [`StoppedTimer`], which has been allocated, but is not tracking any +//! time. These can either be created through [`StoppedTimer::new`], or by calling `.init_once(())` on a +//! StaticStoppedTimer declared within the `kobject_define!` macro, from [`object`]. +//! +//! The `StoppedTimer` has two methods of interest here: +//! +//! - [`start_simple`]: which starts the timer. This timer has methods for robustly getting counts +//! of the number of times it has fired, as well as blocking the current thread for the timer to +//! expire. +//! - [`start_callback`]: which starts the timer, registering a callback handler. This timer will +//! (unsafely) call the callback function, from IRQ context, every time the timer expires. +//! +//! Both of these returned timer types [`SimpleTimer`] and [`CallbackTimer`] have a [`stop`] method +//! that will stop the timer, and give back the original `StoppedTimer`. +//! +//! All of the types implement `Drop` and can dynamic timers can be safely dropped. It is safe to +//! drop a timer allocated through the static object system, but it will then not be possible to +//! re-use that timer. +//! +//! [`object`]: crate::object +//! [`start_simple`]: StoppedTimer::start_simple +//! [`start_callback`]: StoppedTimer::start_callback +//! [`stop`]: SimpleTimer::stop + +extern crate alloc; + +#[cfg(CONFIG_RUST_ALLOC)] +use alloc::boxed::Box; + +use core::ffi::c_void; +use core::marker::PhantomPinned; +use core::pin::Pin; +use core::{fmt, mem}; + +use crate::object::{Fixed, StaticKernelObject, Wrapped}; +use crate::raw::{ + k_timer, + k_timer_init, + k_timer_start, + k_timer_status_get, + k_timer_status_sync, + k_timer_stop, + k_timer_user_data_get, + k_timer_user_data_set, +}; +use crate::time::Timeout; + +/// A Zephyr timer that is not running. +/// +/// A basic timer, allocated, but not running. +pub struct StoppedTimer { + /// The underlying Zephyr timer. + item: Fixed, +} + +impl StoppedTimer { + /// Construct a new timer. + /// + /// Allocates a dynamically allocate timer. The time will not be running. + #[cfg(CONFIG_RUST_ALLOC)] + pub fn new() -> Self { + let item: Fixed = Fixed::new(unsafe { mem::zeroed() }); + unsafe { + // SAFETY: The `Fixed` type takes care of ensuring the timer is allocate at a fixed or + // pinned address. + k_timer_init(item.get(), None, None); + } + StoppedTimer { item } + } + + /// Start the timer, in "simple" mode. + /// + /// Returns the [`SimpleTimer`] representing the running timer. The `delay` specifies the + /// amount of time before the first expiration happens. `period` gives the time of subsequent + /// timer expirations. If `period` is [`NoWait`] or [`Forever`], then the timer will be + /// one-shot + /// + /// [`NoWait`]: crate::time::NoWait + /// [`Forever`]: crate::time::Forever + pub fn start_simple(self, delay: impl Into, period: impl Into) -> SimpleTimer { + unsafe { + // SAFETY: The timer will be registered with Zephyr, using fields within the struct. + // The `Fixed` type takes care of ensuring that the memory is not used. Drop will call + // `stop` to ensure that the timer is unregistered before the memory is returned. + k_timer_start(self.item.get(), delay.into().0, period.into().0); + } + + SimpleTimer { item: Some(self.item) } + } + + /// Start the timer in "callback" mode. + /// + /// Returns the [`CallbackTimer`] representing the running timer. The `delay` specifies the + /// amount of time before the first expiration happens. `period` gives the time of subsequent + /// timer expirations. If `period` is [`NoWait`] or [`Forever`], then the timer will be one + /// shot. + /// + /// Each time the timer expires, The callback function given by the `Callback` will be called + /// from IRQ context. Much of Zephyr's API is unavailable from within IRQ context. Some useful + /// things to use are data that is wrapped in a [`SpinMutex`], a channel [`Sender`] from a + /// bounded channel, or a [`Semaphore`], which can has it's `give` method available from IRQ + /// context. + /// + /// Because the callback is registered with Zephyr, the resulting CallbackTimer must be pinned. + /// + /// [`NoWait`]: crate::time::NoWait + /// [`Forever`]: crate::time::Forever + /// [`SpinMutex`]: crate::sync::SpinMutex + /// [`Semaphore`]: crate::sys::sync::Semaphore + /// [`Sender`]: crate::sync::channel::Sender + pub fn start_callback(self, + callback: Callback, + delay: impl Into, + period: impl Into, + ) -> Pin>> + where T: Send + Sync + { + CallbackTimer::new(self, callback, delay, period) + } + +} + +impl fmt::Debug for StoppedTimer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "StoppedTimer {:?}", self.item.get()) + } +} + +/// A statically allocated `k_timer` (StoppedTimer). +/// +/// This is intended to be used from within the `kobj_define!` macro. It declares a static +/// `k_timer` that will be properly registered with the Zephyr object system (and can be used from +/// userspace). Call `[init_once`] to get the `StoppedTimer` that it represents. +/// +/// [`init_once`]: StaticStoppedTimer::init_once +pub type StaticStoppedTimer = StaticKernelObject; + +// SAFETY: The timer itself is not associated with any particular thread, but it is unclear if they +// are safe to use from multiple threads. As such, we'll declare this as Send, !Sync. +unsafe impl Send for StoppedTimer {} + +impl Wrapped for StaticKernelObject { + type T = StoppedTimer; + + /// No initializers. + type I = (); + + fn get_wrapped(&self, _arg: Self::I) -> StoppedTimer { + let ptr = self.value.get(); + unsafe { + // SAFETY: The ptr is static, so it is safe to have Zephyr initialize. The callback is + // safe as it checks if the user data has been set. The callback is needed for the + // callback version of the timer. + k_timer_init(ptr, None, None); + } + StoppedTimer { + item: Fixed::Static(ptr), + } + } +} + +/// A simple timer. +/// +/// A SimpleTimer represents a running Zephyr `k_timer` that does not have a callback registered. +/// It can only be created by calling [`StoppedTimer::start_simple`]. +pub struct SimpleTimer { + /// The underlying Zephyr timer. Option is needed to coordinate 'stop' and 'drop'. + item: Option>, +} + +impl SimpleTimer { + /// Read the count from the timer. + /// + /// Returns the number of times the timer has fired since the last time either this method or + /// [`read_count_wait`] was called. + /// + /// This works via an internal counter, that is atomically reset to zero when the current value + /// of the counter is read. + /// + /// [`read_count_wait`]: Self::read_count_wait + pub fn read_count(&mut self) -> u32 { + unsafe { + // SAFETY: As long as the timer's data is allocated, this call is safe in Zephyr. + k_timer_status_get(self.item_ptr()) + } + } + + /// Read the count from the timer, waiting for it to become non-zero. + /// + /// Blocks the current thread until the timer has fired at least once since the last call to + /// this method or [`read_count`]. Once it has fired, will return the count. This will return + /// immediately if the timer has already fired once since the last time. + /// + /// [`read_count`]: Self::read_count + pub fn read_count_wait(&mut self) -> u32 { + unsafe { + // SAFETY: As long as the timer's data is allocated, this call is safe in Zephyr. + k_timer_status_sync(self.item_ptr()) + } + } + + /// Restart the current timer. + /// + /// This resets the fired counter back to zero, and sets a new `delay` and `period` for the + /// timer. It is mostly equivalent to `self.stop().start_simple(delay, period)`, but saves the + /// step of having to stop the timer. + pub fn restart(&mut self, delay: impl Into, period: impl Into) { + unsafe { + // SAFETY: According to zephyr docs, it is safe to `start` a running timer, and the + // behavior is as described here. + k_timer_start(self.item_ptr(), delay.into().0, period.into().0); + } + } + + /// Get the item pointer, assuming it is still present. + fn item_ptr(&self) -> *mut k_timer { + self.item.as_ref().expect("Use of SimpleTimer after stop").get() + } + + /// Stop the timer. + /// + /// Stops the timer, so that it will not fire any more, converting the timer back into a + /// StoppedTimer. + pub fn stop(mut self) -> StoppedTimer { + // Actually do the stop. + let item = self.raw_stop(); + + let item = item.expect("Error in stop/drop interaction"); + + StoppedTimer { item } + } + + /// Attempt to stop the timer, if it is still present. Returns the possible inner item. + fn raw_stop(&mut self) -> Option> { + let item = self.item.take(); + if let Some(ref item) = item { + unsafe { + // SAFETY: This call, in Zephyr, removes the timer from any queues. There must also + // not be any threads blocked on `read_count_wait`, which will be the case because + // this is `self` and there can be no other references to the timer in Rust. + k_timer_stop(item.get()) + } + } + item + } +} + +impl Drop for SimpleTimer { + fn drop(&mut self) { + // Stop the timer, discarding the inner item. + let _ = self.raw_stop(); + } +} + +/// A timer callback. The function will be called in IRQ context being passed the given data. +/// Note that this handler owns the data, but passes a reference to the handler. This will +/// typically be a `SpinMutex` to allow for proper sharing with IRQ context. +pub struct Callback { + /// The callback function. + pub call: fn(data: &T), + /// The data passed into the callback. + pub data: T, +} + +/// A zephyr timer that calls a callback each time the timer expires. +/// +/// Each time the timer fires, the callback will be called. It is important to note the data +/// associated with the timer must be both `Send` and `Sync`. As the callback will be called from +/// interrupt context, a normal `Mutex` cannot be used. For this purpose, there is a [`SpinMutex`] +/// type that protects the data with a spin lock. Other useful things a pass as data to the +/// callback are [`Sender`] from a bounded channel, and a [`Semaphore`]. +/// +/// [`SpinMutex`]: crate::sync::SpinMutex +/// [`Sender`]: crate::sync::channel::Sender +/// [`Semaphore`]: crate::sys::sync::Semaphore +pub struct CallbackTimer { + /// The underlying Zephyr timer. + item: Option>, + + /// The callback used for expiry. + expiry: Callback, + + /// Marker to prevent unpinning. + _marker: PhantomPinned, +} + +impl CallbackTimer { + fn new(item: StoppedTimer, + callback: Callback, + delay: impl Into, + period: impl Into, + ) -> Pin>> { + let this = Box::pin(CallbackTimer { + item: Some(item.item), + expiry: callback, + _marker: PhantomPinned, + }); + + // Set the timer's expiry function. + unsafe { + // SAFETY: The timer is not running as this came from a stopped timer. Therefore there + // are no races with timers potentially using the callback function. + // + // After we set the expiry function, the timer will be started with `k_timer_start`, + // which includes the necessary memory barrier to that the timer irq will see the updated + // callback function and user data. + let item_ptr = this.item_ptr(); + (*item_ptr).expiry_fn = Some(Self::timer_expiry); + let raw = &this.expiry as *const _ as *const c_void; + k_timer_user_data_set(item_ptr, raw as *mut c_void); + + k_timer_start(item_ptr, delay.into().0, period.into().0); + } + + this + } + + /// The timer callback. Called in IRQ context, by Zephyr. + unsafe extern "C" fn timer_expiry(ktimer: *mut k_timer) { + // The user data comes back from Zephyr as a `* mut`, even though that is not sound. + let data = unsafe { + // SAFETY: The user data pointer was set above to the pinned expiry. It will be + // unregistered, as set as null when drop is called. Although the timer will also be + // stopped, the callback should be safe as this function checks. + k_timer_user_data_get(ktimer) + }; + if data.is_null() { + return; + } + let cb: &Callback = &*(data as *const Callback); + (cb.call)(&cb.data); + } + + /// Get the item pointer, assuming it is still present. + fn item_ptr(&self) -> *mut k_timer { + self.item.as_ref().expect("Use of SimpleTimer after stop").get() + } + + /// Stop the timer. + /// + /// Stops the timer, so that it will not fire any more, converting the timer back into a + /// StoppedTimer. + pub fn stop(mut self) -> StoppedTimer { + // Actually do the stop. + let item = self.raw_stop(); + + let item = item.expect("Error in stop/drop interaction"); + + StoppedTimer { item } + } + + /// Stop the timer. Returns the inner item. + fn raw_stop(&mut self) -> Option> { + let item = self.item.take(); + if let Some(ref item) = item { + unsafe { + // SAFETY: Stopping the timer removes it from any queues. There must not be threads + // blocked, which is enforced by this only being called from either `stop` or + // `drop`. Once this has been stopped, it is then safe to remove the callback. As + // there will be no more timer operations until the timer is restarted, which will + // have a barrier, this is also safe. + let raw_item = item.get(); + k_timer_stop(raw_item); + (*raw_item).expiry_fn = None; + } + } + item + } +} + +impl Drop for CallbackTimer { + fn drop(&mut self) { + // Stop the timer, discarding the inner item. + let _ = self.raw_stop(); + } +}