diff --git a/lib/rust/main.c b/lib/rust/main.c index a5fa648cbd1dc..ba75f92ed4de3 100644 --- a/lib/rust/main.c +++ b/lib/rust/main.c @@ -16,4 +16,12 @@ int main(void) return 0; } +/* On most arches, panic is entirely macros resulting in some kind of inline assembly. Create this + * wrapper so the Rust panic handler can call the same kind of panic. + */ +void rust_panic_wrap(void) +{ + k_panic(); +} + #endif diff --git a/lib/rust/zephyr-sys/build.rs b/lib/rust/zephyr-sys/build.rs index 19714ae985dac..3d987a12581f8 100644 --- a/lib/rust/zephyr-sys/build.rs +++ b/lib/rust/zephyr-sys/build.rs @@ -67,6 +67,9 @@ fn main() -> Result<()> { .derive_copy(false) .allowlist_function("k_.*") .allowlist_function("gpio_.*") + .allowlist_function("sys_.*") + // Deprecated + .blocklist_function("sys_clock_timeout_end_calc") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate() .expect("Unable to generate bindings"); diff --git a/lib/rust/zephyr/Cargo.toml b/lib/rust/zephyr/Cargo.toml index 7005f52ab55b6..977aea210e343 100644 --- a/lib/rust/zephyr/Cargo.toml +++ b/lib/rust/zephyr/Cargo.toml @@ -12,6 +12,9 @@ Functionality for Rust-based applications that run on Zephyr. [dependencies] zephyr-sys = { version = "0.1.0", path = "../zephyr-sys" } +[dependencies.fugit] +version = "0.3.7" + # These are needed at build time. # Whether these need to be vendored is an open question. They are not # used by the core Zephyr tree, but are needed by zephyr applications. diff --git a/lib/rust/zephyr/src/lib.rs b/lib/rust/zephyr/src/lib.rs index 1440213ec9576..ce71e46f93aae 100644 --- a/lib/rust/zephyr/src/lib.rs +++ b/lib/rust/zephyr/src/lib.rs @@ -9,6 +9,9 @@ #![no_std] #![allow(unexpected_cfgs)] +pub mod sys; +pub mod time; + // Bring in the generated kconfig module include!(concat!(env!("OUT_DIR"), "/kconfig.rs")); @@ -24,8 +27,19 @@ use core::panic::PanicInfo; /// Override rust's panic. This simplistic initial version just hangs in a loop. #[panic_handler] -fn panic(_ :&PanicInfo) -> ! { - loop { +fn panic(info :&PanicInfo) -> ! { + #[cfg(CONFIG_PRINTK)] + { + printkln!("panic: {}", info); + } + let _ = info; + + // Call into the wrapper for the system panic function. + unsafe { + extern "C" { + fn rust_panic_wrap() -> !; + } + rust_panic_wrap(); } } diff --git a/lib/rust/zephyr/src/sys.rs b/lib/rust/zephyr/src/sys.rs new file mode 100644 index 0000000000000..d7342a6d9eea1 --- /dev/null +++ b/lib/rust/zephyr/src/sys.rs @@ -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 }; diff --git a/lib/rust/zephyr/src/time.rs b/lib/rust/zephyr/src/time.rs new file mode 100644 index 0000000000000..7e6228e7421c8 --- /dev/null +++ b/lib/rust/zephyr/src/time.rs @@ -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; + +/// 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; + +// 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 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 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 }) + } +} + +/// A sleep that waits forever. This is its own type, that is `Into` and can be used +/// anywhere a timeout is needed. +pub struct Forever; + +impl From 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` and can be used +/// anywhere a timeout is needed. +pub struct NoWait; + +impl From 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(timeout: T) -> Duration + where T: Into, +{ + 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(tick: I) -> O +where + I: TryInto, + 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()) + } +} diff --git a/tests/lib/rust/time/CMakeLists.txt b/tests/lib/rust/time/CMakeLists.txt new file mode 100644 index 0000000000000..ae269f27eb701 --- /dev/null +++ b/tests/lib/rust/time/CMakeLists.txt @@ -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() diff --git a/tests/lib/rust/time/Cargo.toml b/tests/lib/rust/time/Cargo.toml new file mode 100644 index 0000000000000..360788e3ec63b --- /dev/null +++ b/tests/lib/rust/time/Cargo.toml @@ -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" diff --git a/tests/lib/rust/time/prj.conf b/tests/lib/rust/time/prj.conf new file mode 100644 index 0000000000000..d9bbcfe972f87 --- /dev/null +++ b/tests/lib/rust/time/prj.conf @@ -0,0 +1,5 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_RUST=y +CONFIG_MAIN_STACK_SIZE=2048 diff --git a/tests/lib/rust/time/src/lib.rs b/tests/lib/rust/time/src/lib.rs new file mode 100644 index 0000000000000..e8d1602d338d8 --- /dev/null +++ b/tests/lib/rust/time/src/lib.rs @@ -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; +} diff --git a/tests/lib/rust/time/src/times.c b/tests/lib/rust/time/src/times.c new file mode 100644 index 0000000000000..ac23d9e7ead67 --- /dev/null +++ b/tests/lib/rust/time/src/times.c @@ -0,0 +1,98 @@ +/* Copyright (c) 2024 Linaro LTD */ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include + +/* Rather than trying to get C enums to match in size with Rust ones, just use a known integet type. + */ +enum units { + UNIT_FOREVER, + UNIT_NO_WAIT, + UNIT_DUR_MSEC, + UNIT_INST_MSEC, +}; + +/* Data handed back from C containing processed time constant values. + */ +struct time_entry { + const char *name; + + uint32_t units; + + /* Value in the given units. */ + int64_t uvalue; + + /* Value in ticks. */ + k_timeout_t value; +}; + +const struct time_entry time_entries[] = { + /* For the constants, only the `.value` gets used by the test. */ + { + .name = "K_FOREVER", + .units = UNIT_FOREVER, + .value = K_FOREVER, + }, + { + .name = "K_NO_WAIT", + .units = UNIT_NO_WAIT, + .value = K_NO_WAIT, + }, +#define DUR_TEST(unit, n) \ + { \ + .name = "Duration " #unit " " #n, \ + .units = UNIT_DUR_ ## unit, \ + .uvalue = n, \ + .value = K_ ## unit(n), \ + } + /* Test various values near typical clock boundaries. */ + DUR_TEST(MSEC, 1), + DUR_TEST(MSEC, 2), + DUR_TEST(MSEC, 99), + DUR_TEST(MSEC, 100), + DUR_TEST(MSEC, 101), + DUR_TEST(MSEC, 999), + DUR_TEST(MSEC, 1000), + DUR_TEST(MSEC, 1001), + DUR_TEST(MSEC, 32767), + DUR_TEST(MSEC, 32768), + DUR_TEST(MSEC, 32769), + /* The Instance tests don't set the `.value` because it isn't constant, and the test code + * will calculate the value at runtime, using the conversion functions below. + */ +#define INST_TEST(unit, n) \ + { \ + .name = "Instant " #unit " " #n, \ + .units = UNIT_INST_ ## unit, \ + .uvalue = n, \ + } + INST_TEST(MSEC, 1), + INST_TEST(MSEC, 2), + INST_TEST(MSEC, 99), + INST_TEST(MSEC, 100), + INST_TEST(MSEC, 101), + INST_TEST(MSEC, 999), + INST_TEST(MSEC, 1000), + INST_TEST(MSEC, 1001), + INST_TEST(MSEC, 32767), + INST_TEST(MSEC, 32768), + INST_TEST(MSEC, 32769), + { + .name = 0, + }, +}; + +/* Return the indexed time entry. It is up to the Rust code to detect the null name, and handle it + * properly. + */ +const struct time_entry *get_time_entry(uintptr_t index) +{ + return &time_entries[index]; +} + +/* The abs timeout is not constant, so provide this wrapper function. + */ +const k_timeout_t ms_to_abs_timeout(int64_t ms) +{ + return K_TIMEOUT_ABS_MS(ms); +} diff --git a/tests/lib/rust/time/testcase.yaml b/tests/lib/rust/time/testcase.yaml new file mode 100644 index 0000000000000..b28c72a89c88b --- /dev/null +++ b/tests/lib/rust/time/testcase.yaml @@ -0,0 +1,9 @@ +common: + filter: CONFIG_RUST_SUPPORTED +tests: + test.rust.time: + harness: console + harness_config: + type: one_line + regex: + - "All tests passed"