Skip to content

zephyr: sys: Create Timer #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions tests/timer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
18 changes: 18 additions & 0 deletions tests/timer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
8 changes: 8 additions & 0 deletions tests/timer/prj.conf
Original file line number Diff line number Diff line change
@@ -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
145 changes: 145 additions & 0 deletions tests/timer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Box<CallbackTimer<Arc<AtomicUsize>>>>,
counter: Arc<AtomicUsize>,
}

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<AtomicUsize>) {
data.fetch_add(1, Ordering::Relaxed);
}
}
17 changes: 17 additions & 0 deletions tests/timer/testcase.yaml
Original file line number Diff line number Diff line change
@@ -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"
1 change: 1 addition & 0 deletions zephyr-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#![allow(improper_ctypes)]

#![allow(rustdoc::broken_intra_doc_links)]
#![allow(rustdoc::bare_urls)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

Expand Down
2 changes: 2 additions & 0 deletions zephyr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
2 changes: 2 additions & 0 deletions zephyr/src/sync/spinmutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: ?Sized> {
inner: UnsafeCell<raw::k_spinlock>,
data: UnsafeCell<T>,
Expand Down
2 changes: 2 additions & 0 deletions zephyr/src/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
4 changes: 4 additions & 0 deletions zephyr/src/sys/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(&self, timeout: T) -> *mut c_void
where T: Into<Timeout>
{
Expand Down
Loading