-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Implement "timeout" support for Rust #78193
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
Changes from all commits
337a2d1
213032f
b5b8d92
e823c22
e9b1585
f8e33c5
ea375ac
e1aa346
9afdb77
f5a4ad9
ec1c9f6
7b168d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
||
//! Zephyr 'sys' module. | ||
//! | ||
//! The `zephyr-sys` crate contains the direct C bindings to the Zephyr API. All of these are | ||
//! unsafe. | ||
//! | ||
//! This module `zephyr::sys` contains thin wrappers to these C bindings, that can be used without | ||
//! unsafe, but as unchanged as possible. | ||
|
||
use zephyr_sys::k_timeout_t; | ||
|
||
// These two constants are not able to be captured by bindgen. It is unlikely that these values | ||
// would change in the Zephyr headers, but there will be an explicit test to make sure they are | ||
// correct. | ||
pub const K_FOREVER: k_timeout_t = k_timeout_t { ticks: -1 }; | ||
pub const K_NO_WAIT: k_timeout_t = k_timeout_t { ticks: 0 }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// Copyright (c) 2024 Linaro LTD | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! Time types designed for Zephyr, inspired by `std::time`. | ||
//! | ||
//! In `std`, there are two primary time types: `Duration`, representing a span of time, and | ||
//! `Instant`, representing a specific point in time. Both have nanosecond precision, which is | ||
//! well-suited to more powerful machines. However, on embedded systems like Zephyr, this precision | ||
//! can lead to performance issues, often requiring divisions whenever timeouts are used. | ||
//! | ||
//! In the Rust embedded ecosystem, the `fugit` crate is commonly used for handling time. It | ||
//! provides both `Duration` and `Instant` types, but with parameters that allow the representation | ||
//! to match the time slice used, enabling compile-time conversion and storing time directly as | ||
//! tick counts. | ||
//! | ||
//! Zephyr manages time in terms of system tick intervals, derived from | ||
//! `sys_clock_hw_cycles_per_sec()`. This model aligns well with `fugit`, especially when the | ||
//! types are properly parameterized. | ||
//! | ||
//! It's important to note that Rust’s `std::Instant` requires time to be monotonically increasing. | ||
//! | ||
//! Zephyr’s `sys/time_units.h` provides a variety of optimized macros for manipulating time | ||
//! values, converting between human-readable units and ticks, and minimizing divisions (especially | ||
//! by non-constant values). Similarly, the `fugit` crate offers constructors that aim to result | ||
//! in constants when possible, avoiding costly division operations. | ||
|
||
use zephyr_sys::{k_timeout_t, k_ticks_t}; | ||
|
||
use core::fmt::Debug; | ||
|
||
// The system ticks, is mostly a constant, but there are some boards that use a dynamic tick | ||
// frequency, and thus need to read this at runtime. | ||
#[cfg(CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME)] | ||
compile_error!("Rust does not (yet) support dynamic frequency timer"); | ||
|
||
// Given the above not defined, the system time base comes from a kconfig. | ||
/// The system time base. The system clock has this many ticks per second. | ||
pub const SYS_FREQUENCY: u32 = crate::kconfig::CONFIG_SYS_CLOCK_TICKS_PER_SEC as u32; | ||
|
||
/// Zephyr can be configured for either 64-bit or 32-bit time values. Use the appropriate type | ||
/// internally to match. This should end up the same size as `k_ticks_t`, but unsigned instead of | ||
/// signed. | ||
#[cfg(CONFIG_TIMEOUT_64BIT)] | ||
pub type Tick = u64; | ||
#[cfg(not(CONFIG_TIMEOUT_64BIT))] | ||
pub type Tick = u32; | ||
|
||
/// Duration appropriate for Zephyr calls that expect `k_timeout_t`. The result will be a time | ||
/// interval from "now" (when the call is made). | ||
pub type Duration = fugit::Duration<Tick, 1, SYS_FREQUENCY>; | ||
|
||
/// An Instant appropriate for Zephyr calls that expect a `k_timeout_t`. The result will be an | ||
/// absolute time in terms of system ticks. | ||
#[cfg(CONFIG_TIMEOUT_64BIT)] | ||
pub type Instant = fugit::Instant<Tick, 1, SYS_FREQUENCY>; | ||
|
||
// The Zephyr `k_timeout_t` represents several different types of intervals, based on the range of | ||
// the value. It is a signed number of the same size as the Tick here, which effectively means it | ||
// is one bit less. | ||
// | ||
// 0: K_NO_WAIT: indicates the operation should not wait. | ||
// 1 .. Max: indicates a duration in ticks of a delay from "now". | ||
// -1: K_FOREVER: a time that never expires. | ||
// MIN .. -2: A wait for an absolute amount of ticks from the start of the system. | ||
// | ||
// The absolute time offset is only implemented when time is a 64-bit value. This also means that | ||
// "Instant" isn't available when time is defined as a 32-bit value. | ||
|
||
// Wrapper around the timeout type, so we can implement From/Info. | ||
pub struct Timeout(pub k_timeout_t); | ||
|
||
// `From` allows methods to take a time of various types and convert it into a Zephyr timeout. | ||
impl From<Duration> for Timeout { | ||
fn from(value: Duration) -> Timeout { | ||
let ticks: k_ticks_t = checked_cast(value.ticks()); | ||
debug_assert_ne!(ticks, crate::sys::K_FOREVER.ticks); | ||
debug_assert_ne!(ticks, crate::sys::K_NO_WAIT.ticks); | ||
Timeout(k_timeout_t { ticks }) | ||
} | ||
} | ||
|
||
#[cfg(CONFIG_TIMEOUT_64BIT)] | ||
impl From<Instant> for Timeout { | ||
fn from(value: Instant) -> Timeout { | ||
let ticks: k_ticks_t = checked_cast(value.ticks()); | ||
debug_assert_ne!(ticks, crate::sys::K_FOREVER.ticks); | ||
debug_assert_ne!(ticks, crate::sys::K_NO_WAIT.ticks); | ||
Timeout(k_timeout_t { ticks: -1 - 1 - ticks }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The overflow checks be done after the offset ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depends on how valuable having a single point of failure is. Integer overflow on the subtraction will also panic when debug assertions are enabled. |
||
} | ||
} | ||
|
||
/// A sleep that waits forever. This is its own type, that is `Into<Timeout>` and can be used | ||
/// anywhere a timeout is needed. | ||
pub struct Forever; | ||
|
||
impl From<Forever> for Timeout { | ||
fn from(_value: Forever) -> Timeout { | ||
Timeout(crate::sys::K_FOREVER) | ||
} | ||
} | ||
|
||
/// A sleep that doesn't ever wait. This is its own type, that is `Info<Timeout>` and can be used | ||
/// anywhere a timeout is needed. | ||
pub struct NoWait; | ||
|
||
impl From<NoWait> for Timeout { | ||
fn from(_valued: NoWait) -> Timeout { | ||
Timeout(crate::sys::K_NO_WAIT) | ||
} | ||
} | ||
|
||
/// Put the current thread to sleep, for the given duration. Uses `k_sleep` for the actual sleep. | ||
/// Returns a duration roughly representing the remaining amount of time if the sleep was woken. | ||
pub fn sleep<T>(timeout: T) -> Duration | ||
where T: Into<Timeout>, | ||
{ | ||
let timeout: Timeout = timeout.into(); | ||
let rest = unsafe { crate::raw::k_sleep(timeout.0) }; | ||
Duration::millis(rest as Tick) | ||
} | ||
|
||
/// Convert from the Tick time type, which is unsigned, to the `k_ticks_t` type. When debug | ||
/// assertions are enabled, it will panic on overflow. | ||
fn checked_cast<I, O>(tick: I) -> O | ||
where | ||
I: TryInto<O>, | ||
I::Error: Debug, | ||
O: Default, | ||
{ | ||
if cfg!(debug_assertions) { | ||
tick.try_into().expect("Overflow in time conversion") | ||
} else { | ||
tick.try_into().unwrap_or(O::default()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
cmake_minimum_required(VERSION 3.20.0) | ||
|
||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) | ||
project(time_rust) | ||
|
||
target_sources(app PRIVATE src/times.c) | ||
|
||
rust_cargo_application() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Copyright (c) 2024 Linaro LTD | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
[package] | ||
# This must be rustapp for now. | ||
name = "rustapp" | ||
version = "0.1.0" | ||
edition = "2021" | ||
description = "Tests of time" | ||
license = "Apache-2.0 or MIT" | ||
|
||
[lib] | ||
crate-type = ["staticlib"] | ||
|
||
[dependencies] | ||
zephyr = "0.1.0" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Copyright (c) 2024 Linaro LTD | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
CONFIG_RUST=y | ||
CONFIG_MAIN_STACK_SIZE=2048 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright (c) 2024 Linaro LTD | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#![no_std] | ||
|
||
use core::ffi::{ | ||
c_char, | ||
CStr, | ||
}; | ||
|
||
use zephyr::printkln; | ||
use zephyr::time::{Duration, Instant, Tick, Timeout}; | ||
use zephyr::raw::k_timeout_t; | ||
|
||
#[no_mangle] | ||
extern "C" fn rust_main() { | ||
printkln!("Tick frequency: {}", zephyr::time::SYS_FREQUENCY); | ||
check_conversions(); | ||
printkln!("All tests passed"); | ||
} | ||
|
||
/// Verify that the conversions are correct. | ||
fn check_conversions() { | ||
let mut index = 0; | ||
loop { | ||
// The entry returns is always valid, so is a valid reference. | ||
let entry = unsafe { &*get_time_entry(index) }; | ||
if entry.name.is_null() { | ||
break; | ||
} | ||
let name = unsafe { | ||
CStr::from_ptr(entry.name).to_str().expect("Invalid C string") | ||
}; | ||
printkln!("Testing: {}", name); | ||
|
||
// The units must match the enum in the C code. | ||
match entry.units { | ||
// UNIT_FOREVER | ||
0 => { | ||
assert_eq!(entry.value.ticks, zephyr::sys::K_FOREVER.ticks); | ||
} | ||
// UNIT_NO_WAIT | ||
1 => { | ||
assert_eq!(entry.value.ticks, zephyr::sys::K_NO_WAIT.ticks); | ||
} | ||
// UNIT_DUR_MS | ||
2 => { | ||
let value = Duration::millis_at_least(entry.uvalue as Tick); | ||
let value: Timeout = value.into(); | ||
assert_eq!(entry.value.ticks, value.0.ticks); | ||
} | ||
// UNIT_INST_MS | ||
3 => { | ||
let base = Instant::from_ticks(0); | ||
let value = Duration::millis_at_least(entry.uvalue as Tick); | ||
let value = base + value; | ||
let value: Timeout = value.into(); | ||
let c_value = unsafe { ms_to_abs_timeout(entry.uvalue) }; | ||
if c_value.ticks != value.0.ticks { | ||
printkln!("Mismatch C: {}, Rust: {}", | ||
c_value.ticks, value.0.ticks); | ||
} | ||
assert_eq!(c_value.ticks, value.0.ticks); | ||
} | ||
_ => { | ||
panic!("Invalid unit enum"); | ||
} | ||
} | ||
|
||
index += 1; | ||
} | ||
} | ||
|
||
/// The time entry information. | ||
#[repr(C)] | ||
struct TimeEntry { | ||
name: *const c_char, | ||
units: u32, | ||
uvalue: i64, | ||
value: k_timeout_t, | ||
} | ||
|
||
extern "C" { | ||
fn get_time_entry(index: usize) -> *const TimeEntry; | ||
fn ms_to_abs_timeout(ms: i64) -> k_timeout_t; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, you can call the argument
_info
and use it in theprintkln!
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is there a typical way this is done? Both seem awkward.