Skip to content

Add Duration and Instant support to Rust. #1

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 13 commits into from
Sep 17, 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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ jobs:
run: |
cargo --version

west twister -T samples -v --inline-logs --integration
west twister -T samples -T tests -v --inline-logs --integration
8 changes: 8 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 10 additions & 0 deletions tests/time/CMakeLists.txt
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()
16 changes: 16 additions & 0 deletions tests/time/Cargo.toml
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"
5 changes: 5 additions & 0 deletions tests/time/prj.conf
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
86 changes: 86 additions & 0 deletions tests/time/src/lib.rs
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;
}
98 changes: 98 additions & 0 deletions tests/time/src/times.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* Copyright (c) 2024 Linaro LTD */
/* SPDX-License-Identifier: Apache-2.0 */

#include <zephyr/kernel.h>

/* 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);
}
9 changes: 9 additions & 0 deletions tests/time/testcase.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
common:
filter: CONFIG_RUST_SUPPORTED
tests:
test.rust.time:
harness: console
harness_config:
type: one_line
regex:
- "All tests passed"
3 changes: 3 additions & 0 deletions zephyr-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,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");
Expand Down
3 changes: 3 additions & 0 deletions zephyr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions zephyr/module.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ build:
kconfig: Kconfig
samples:
- samples/hello_world
tests:
- tests
18 changes: 16 additions & 2 deletions zephyr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));

Expand All @@ -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();
}
}

Expand Down
18 changes: 18 additions & 0 deletions zephyr/src/sys.rs
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 };
Loading