diff --git a/Kconfig b/Kconfig index 0d99cb40..9a3ed888 100644 --- a/Kconfig +++ b/Kconfig @@ -19,4 +19,16 @@ config RUST help This option enables the use of applications written in Rust. +if RUST + +config RUST_ALLOC + bool "Support an allocator in Rust code" + help + If enabled, the Rust zephyr support library will include support for + an allocator. This allocator will use the currently configured + Zephyr allocator (malloc/free). This this enabled, Rust + applications can use the `alloc` crate. + +endif # RUST + endmenu diff --git a/README.rst b/README.rst index 21fa7e69..2900a59b 100644 --- a/README.rst +++ b/README.rst @@ -70,7 +70,7 @@ your application directory, with options set so that it can find the Zephyr supp that the output will be contained within the Zephyr build directory. The :file:`Cargo.toml` will need to have a ``[lib]`` section that sets ``crate-type = -["staticlib"]``, and will need to include ``zephyr = "0.1.0"`` as a dependency. You can use +["staticlib"]``, and will need to include ``zephyr = "3.7.0"`` as a dependency. You can use crates.io and the Crate ecosystem to include any other dependencies you need. Just make sure that you use crates that support building with no-std. @@ -115,7 +115,7 @@ To your ``Cargo.toml`` file, add the following: .. code-block:: toml [build-dependencies] - zephyr-build = "0.1.0" + zephyr-build = "3.7.0" Then, you will need a ``build.rs`` file to call the support function. The following will work: diff --git a/docgen/.cargo/config.toml b/docgen/.cargo/config.toml new file mode 120000 index 00000000..f26aaf03 --- /dev/null +++ b/docgen/.cargo/config.toml @@ -0,0 +1 @@ +../../../../../zephyr/build/rust/sample-cargo-config.toml \ No newline at end of file diff --git a/docgen/.gitignore b/docgen/.gitignore new file mode 100644 index 00000000..80e243ad --- /dev/null +++ b/docgen/.gitignore @@ -0,0 +1,7 @@ +# In this case, we do want the symlink checked in. We'll assume we have the module in the standard +# module place. +# +# On Windows, this symlink will just get checked out as a regular file and will have to be replaced +# with a copy (or real symlinks enabled). But, this shouldn't affect CI runs of the documentation, +# which are done on Linux. +!.cargo/ diff --git a/docgen/CMakeLists.txt b/docgen/CMakeLists.txt new file mode 100644 index 00000000..3030520d --- /dev/null +++ b/docgen/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(hello_rust_world) +rust_cargo_application() diff --git a/docgen/Cargo.toml b/docgen/Cargo.toml new file mode 100644 index 00000000..5588550d --- /dev/null +++ b/docgen/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 = "3.7.0" +edition = "2021" +description = "A small application to generate documentation" +license = "Apache-2.0 or MIT" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +zephyr = "3.7.0" diff --git a/docgen/prj.conf b/docgen/prj.conf new file mode 100644 index 00000000..930445c6 --- /dev/null +++ b/docgen/prj.conf @@ -0,0 +1,5 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_RUST=y +CONFIG_RUST_ALLOC=y diff --git a/docgen/src/lib.rs b/docgen/src/lib.rs new file mode 100644 index 00000000..b29be52b --- /dev/null +++ b/docgen/src/lib.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +use zephyr::printkln; + +// Reference the Zephyr crate so that the panic handler gets used. This is only needed if no +// symbols from the crate are directly used. +extern crate zephyr; + +#[no_mangle] +extern "C" fn rust_main() { + printkln!("Hello world from Rust on {}", + zephyr::kconfig::CONFIG_BOARD); +} diff --git a/samples/hello_world/Cargo.toml b/samples/hello_world/Cargo.toml index 4058e2d9..dbf54f57 100644 --- a/samples/hello_world/Cargo.toml +++ b/samples/hello_world/Cargo.toml @@ -4,7 +4,7 @@ [package] # This must be rustapp for now. name = "rustapp" -version = "0.1.0" +version = "3.7.0" edition = "2021" description = "A sample hello world application in Rust" license = "Apache-2.0 or MIT" @@ -13,4 +13,4 @@ license = "Apache-2.0 or MIT" crate-type = ["staticlib"] [dependencies] -zephyr = "0.1.0" +zephyr = "3.7.0" diff --git a/samples/philosophers/CMakeLists.txt b/samples/philosophers/CMakeLists.txt new file mode 100644 index 00000000..88561604 --- /dev/null +++ b/samples/philosophers/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(philosophers) + +rust_cargo_application() diff --git a/samples/philosophers/Cargo.toml b/samples/philosophers/Cargo.toml new file mode 100644 index 00000000..bb0cdf30 --- /dev/null +++ b/samples/philosophers/Cargo.toml @@ -0,0 +1,25 @@ +# 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 = "A sample hello world application in Rust" +license = "Apache-2.0 or MIT" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +zephyr = "3.7.0" + +# Dependencies that are used by build.rs. +[build-dependencies] +zephyr-build = "3.7.0" + +[profile.release] +debug-assertions = true +overflow-checks = true +debug = true diff --git a/samples/philosophers/Kconfig b/samples/philosophers/Kconfig new file mode 100644 index 00000000..2e481703 --- /dev/null +++ b/samples/philosophers/Kconfig @@ -0,0 +1,18 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "Rust Dining Philosphers" + +source "Kconfig.zephyr" + +choice + prompt "Select Synchronization implementation" + default SYNC_SYS_SEMAPHORE + + config SYNC_SYS_SEMAPHORE + bool "Use sys::Semaphore to synchronize forks" + help + Use to have the dining philosophers sample use sys::Semaphore, with one per form, + to synchronize. + +endchoice diff --git a/samples/philosophers/boards/rpi_pico.conf b/samples/philosophers/boards/rpi_pico.conf new file mode 100644 index 00000000..94c0843a --- /dev/null +++ b/samples/philosophers/boards/rpi_pico.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +# This board doesn't have a serial console, so use RTT. +CONFIG_UART_CONSOLE=n +CONFIG_RTT_CONSOLE=y +CONFIG_USE_SEGGER_RTT=y diff --git a/samples/philosophers/build.rs b/samples/philosophers/build.rs new file mode 100644 index 00000000..22233f15 --- /dev/null +++ b/samples/philosophers/build.rs @@ -0,0 +1,9 @@ +// Copyright (c) 2023 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +// This crate needs access to kconfig variables. This is an example of how to do that. The +// zephyr-build must be a build dependency. + +fn main() { + zephyr_build::export_bool_kconfig(); +} diff --git a/samples/philosophers/prj.conf b/samples/philosophers/prj.conf new file mode 100644 index 00000000..5dfcb890 --- /dev/null +++ b/samples/philosophers/prj.conf @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_RUST=y +CONFIG_RUST_ALLOC=y +CONFIG_MAIN_STACK_SIZE=8192 + +# CONFIG_USERSPACE=y + +# Some debugging +CONFIG_THREAD_MONITOR=y +CONFIG_THREAD_ANALYZER=y +CONFIG_THREAD_ANALYZER_USE_PRINTK=y +CONFIG_THREAD_ANALYZER_AUTO=n +# CONFIG_THREAD_ANALYZER_AUTO_INTERVAL=15 diff --git a/samples/philosophers/sample.yaml b/samples/philosophers/sample.yaml new file mode 100644 index 00000000..cfd1eb47 --- /dev/null +++ b/samples/philosophers/sample.yaml @@ -0,0 +1,22 @@ +sample: + description: Philosphers, in Rust + name: philosophers rust +common: + harness: console + harness_config: + type: one_line + regex: + # Match the statistics, and make sure that each philosopher has at least 10 (two digits) + # meals. + # - "^\\[\\d{2,}, \\d{2,}, \\d{2,}, \\d{2,}, \\d{2,}, \\d{2,}\\]" + # + # Until the stastics have been implemented, just match on one of the children thinking + - "^Child 5 thinking \\(\\d+ ticks.*" + tags: rust + filter: CONFIG_RUST_SUPPORTED +tests: + sample.rust.philosopher.semaphore: + tags: introduction + min_ram: 32 + extra_configs: + - CONFIG_SYNC_SYS_SEMAPHORE=y diff --git a/samples/philosophers/src/lib.rs b/samples/philosophers/src/lib.rs new file mode 100644 index 00000000..7e514305 --- /dev/null +++ b/samples/philosophers/src/lib.rs @@ -0,0 +1,134 @@ +// Copyright (c) 2023 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +// Cargo tries to detect configs that have typos in them. Unfortunately, the Zephyr Kconfig system +// uses a large number of Kconfigs and there is no easy way to know which ones might conceivably be +// valid. This prevents a warning about each cfg that is used. +#![allow(unexpected_cfgs)] + +extern crate alloc; + +#[allow(unused_imports)] +use alloc::boxed::Box; +use alloc::vec::Vec; +use zephyr::time::{Duration, sleep, Tick}; +use zephyr::{ + printkln, + kobj_define, + sys::uptime_get, + sync::Arc, +}; + +// These are optional, based on Kconfig, so allow them to be unused. +#[allow(unused_imports)] +use crate::semsync::semaphore_sync; + +mod semsync; + +/// How many philosophers. There will be the same number of forks. +const NUM_PHIL: usize = 6; + +/// How much stack should each philosopher thread get. Worst case I've seen is riscv64, with 3336 +/// bytes, when printing messages. Make a bit larger to work. +const PHIL_STACK_SIZE: usize = 4096; + +// The dining philosophers problem is a simple example of cooperation between multiple threads. +// This implementation use one of several different underlying mechanism to support this cooperation. + +// This example uses dynamic dispatch to allow multiple implementations. The intent is to be able +// to periodically shut down all of the philosphers and start them up with a differernt sync +// mechanism. This isn't implemented yet. + +/// The philosophers use a fork synchronization mechanism. Essentially, this is 6 locks, and will be +/// implemented in a few different ways to demonstrate/test different mechanmism in Rust. All of +/// them implement The ForkSync trait which provides this mechanism. +trait ForkSync: core::fmt::Debug + Sync + Send { + /// Take the given fork. The are indexed the same as the philosopher index number. This will + /// block until the fork is released. + fn take(&self, index: usize); + + /// Release the given fork. Index is the same as take. + fn release(&self, index: usize); +} + +#[no_mangle] +extern "C" fn rust_main() { + printkln!("Hello world from Rust on {}", + zephyr::kconfig::CONFIG_BOARD); + printkln!("Time tick: {}", zephyr::time::SYS_FREQUENCY); + + let syncers = get_syncer(); + + printkln!("Pre fork"); + + for (i, syncer) in (0..NUM_PHIL).zip(syncers.into_iter()) { + let thread = PHIL_THREADS[i].init_once(PHIL_STACKS[i].init_once(()).unwrap()).unwrap(); + thread.spawn(move || { + phil_thread(i, syncer); + }); + } + + let delay = Duration::secs_at_least(10); + loop { + // Periodically, printout the stats. + zephyr::time::sleep(delay); + } +} + +#[cfg(CONFIG_SYNC_SYS_SEMAPHORE)] +fn get_syncer() -> Vec> { + semaphore_sync() +} + +fn phil_thread(n: usize, syncer: Arc) { + printkln!("Child {} started: {:?}", n, syncer); + + // Determine our two forks. + let forks = if n == NUM_PHIL - 1 { + // Per Dijkstra, the last phyilosopher needs to reverse forks, or we deadlock. + (0, n) + } else { + (n, n+1) + }; + + loop { + { + printkln!("Child {} hungry", n); + printkln!("Child {} take left fork", n); + syncer.take(forks.0); + printkln!("Child {} take right fork", n); + syncer.take(forks.1); + + let delay = get_random_delay(n, 25); + printkln!("Child {} eating ({} ms)", n, delay); + sleep(delay); + // stats.lock().unwrap().record_eat(n, delay); + + // Release the forks. + printkln!("Child {} giving up forks", n); + syncer.release(forks.1); + syncer.release(forks.0); + + let delay = get_random_delay(n, 25); + printkln!("Child {} thinking ({} ms)", n, delay); + sleep(delay); + // stats.lock().unwrap().record_think(n, delay); + } + } +} + +/// Get a random delay, based on the ID of this user, and the current uptime. +fn get_random_delay(id: usize, period: usize) -> Duration { + let tick = (uptime_get() & (usize::MAX as i64)) as usize; + let delay = (tick / 100 * (id + 1)) & 0x1f; + + // Use one greater to be sure to never get a delay of zero. + Duration::millis_at_least(((delay + 1) * period) as Tick) +} + +kobj_define! { + static PHIL_THREADS: [StaticThread; NUM_PHIL]; + static PHIL_STACKS: [ThreadStack; NUM_PHIL]; +} diff --git a/samples/philosophers/src/semsync.rs b/samples/philosophers/src/semsync.rs new file mode 100644 index 00000000..03bf8ce9 --- /dev/null +++ b/samples/philosophers/src/semsync.rs @@ -0,0 +1,58 @@ +// Copyright (c) 2023 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +//! Semaphore based sync. +//! +//! This is the simplest type of sync, which uses a single semaphore per fork. + +extern crate alloc; + +use alloc::vec::Vec; +use alloc::boxed::Box; + +use zephyr::{ + kobj_define, + sync::Arc, + time::Forever, +}; + +use crate::{ForkSync, NUM_PHIL}; + +#[derive(Debug)] +pub struct SemSync { + /// The forks for this philosopher. This is a big excessive, as we really don't need all of + /// them, but the ForSync code uses the index here. + forks: [zephyr::sys::sync::Semaphore; NUM_PHIL], +} + +impl ForkSync for SemSync { + fn take(&self, index: usize) { + self.forks[index].take(Forever).unwrap(); + } + + fn release(&self, index: usize) { + self.forks[index].give(); + } +} + +#[allow(dead_code)] +pub fn semaphore_sync() -> Vec> { + let forks = SEMS.each_ref().map(|m| { + // Each fork starts as taken. + m.init_once((1, 1)).unwrap() + }); + + let syncers = (0..NUM_PHIL).map(|_| { + let syncer = SemSync { + forks: forks.clone(), + }; + let item = Box::new(syncer) as Box; + Arc::from(item) + }).collect(); + + syncers +} + +kobj_define! { + static SEMS: [StaticSemaphore; NUM_PHIL]; +} diff --git a/tests/time/Cargo.toml b/tests/time/Cargo.toml index 360788e3..ffcba92b 100644 --- a/tests/time/Cargo.toml +++ b/tests/time/Cargo.toml @@ -4,7 +4,7 @@ [package] # This must be rustapp for now. name = "rustapp" -version = "0.1.0" +version = "3.7.0" edition = "2021" description = "Tests of time" license = "Apache-2.0 or MIT" @@ -13,4 +13,4 @@ license = "Apache-2.0 or MIT" crate-type = ["staticlib"] [dependencies] -zephyr = "0.1.0" +zephyr = "3.7.0" diff --git a/zephyr-build/Cargo.toml b/zephyr-build/Cargo.toml index c73a95e6..a31eaa8e 100644 --- a/zephyr-build/Cargo.toml +++ b/zephyr-build/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "zephyr-build" -version = "0.1.0" +version = "3.7.0" edition = "2021" description = """ Build-time support for Rust-based applications that run on Zephyr. diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index 285262a7..bec71347 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -54,24 +54,22 @@ pub fn build_kconfig_mod() { let gen_path = Path::new(&outdir).join("kconfig.rs"); let mut f = File::create(&gen_path).unwrap(); - writeln!(&mut f, "pub mod kconfig {{").unwrap(); let file = File::open(&dotconfig).expect("Unable to open dotconfig"); for line in BufReader::new(file).lines() { let line = line.expect("reading line from dotconfig"); if let Some(caps) = config_hex.captures(&line) { - writeln!(&mut f, " #[allow(dead_code)]").unwrap(); - writeln!(&mut f, " pub const {}: usize = {};", + writeln!(&mut f, "#[allow(dead_code)]").unwrap(); + writeln!(&mut f, "pub const {}: usize = {};", &caps[1], &caps[2]).unwrap(); } else if let Some(caps) = config_int.captures(&line) { - writeln!(&mut f, " #[allow(dead_code)]").unwrap(); - writeln!(&mut f, " pub const {}: isize = {};", + writeln!(&mut f, "#[allow(dead_code)]").unwrap(); + writeln!(&mut f, "pub const {}: isize = {};", &caps[1], &caps[2]).unwrap(); } else if let Some(caps) = config_str.captures(&line) { - writeln!(&mut f, " #[allow(dead_code)]").unwrap(); - writeln!(&mut f, " pub const {}: &'static str = {};", + writeln!(&mut f, "#[allow(dead_code)]").unwrap(); + writeln!(&mut f, "pub const {}: &'static str = {};", &caps[1], &caps[2]).unwrap(); } } - writeln!(&mut f, "}}").unwrap(); } diff --git a/zephyr-sys/Cargo.toml b/zephyr-sys/Cargo.toml index 62e2256d..385dcd5f 100644 --- a/zephyr-sys/Cargo.toml +++ b/zephyr-sys/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "zephyr-sys" -version = "0.1.0" +version = "3.7.0" edition = "2021" description = """ Zephyr low-level API bindings. @@ -15,4 +15,4 @@ Zephyr low-level API bindings. [build-dependencies] anyhow = "1.0" bindgen = { version = "0.69.4", features = ["experimental"] } -# zephyr-build = { version = "0.1.0", path = "../zephyr-build" } +# zephyr-build = { version = "3.7.0", path = "../zephyr-build" } diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index dffac136..7107369a 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -71,6 +71,9 @@ fn main() -> Result<()> { .allowlist_function("k_.*") .allowlist_function("gpio_.*") .allowlist_function("sys_.*") + .allowlist_item("E.*") + .allowlist_item("K_.*") + .allowlist_item("ZR_.*") // Deprecated .blocklist_function("sys_clock_timeout_end_calc") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) diff --git a/zephyr-sys/src/lib.rs b/zephyr-sys/src/lib.rs index 3338adaf..8934367b 100644 --- a/zephyr-sys/src/lib.rs +++ b/zephyr-sys/src/lib.rs @@ -17,4 +17,6 @@ // Note, however, that this suppresses any warnings in the bindings about improper C types. #![allow(improper_ctypes)] +#![allow(rustdoc::broken_intra_doc_links)] + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index f620d4b0..b68b7fab 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -31,4 +31,15 @@ extern int errno; #endif #include +#include #include + +/* + * bindgen will output #defined constant that resolve to simple numbers. There are some symbols + * that we want exported that, at least in some situations, are more complex, usually with a type + * case. + * + * We'll use the prefix "ZR_" to avoid conflicts with other symbols. + */ +const uintptr_t ZR_STACK_ALIGN = Z_KERNEL_STACK_OBJ_ALIGN; +const uintptr_t ZR_STACK_RESERVED = K_KERNEL_STACK_RESERVED; diff --git a/zephyr/Cargo.toml b/zephyr/Cargo.toml index 977aea21..3775e278 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -3,20 +3,43 @@ [package] name = "zephyr" -version = "0.1.0" +version = "3.7.0" edition = "2021" description = """ Functionality for Rust-based applications that run on Zephyr. """ [dependencies] -zephyr-sys = { version = "0.1.0", path = "../zephyr-sys" } +zephyr-sys = { version = "3.7.0", path = "../zephyr-sys" } + +# Although paste is brought in, it is a compile-time macro, and is not linked into the application. +paste = "1.0" [dependencies.fugit] version = "0.3.7" +[dependencies.critical-section] +version = "1.1.2" +features = ["restore-state-u32"] + +[dependencies.portable-atomic] +version = "1.7.0" +# We assume that the instances where portable atomic must provide its own implementation (target +# does not have atomic instructions), are only single CPU. This is the case currently with Zephyr, +# so this should only match the same implementation. +# features = ["fallback", "unsafe-assume-single-core"] +features = ["fallback", "critical-section"] + +# Provides an implementation of Arc that either directs to 'alloc', or implements using portable +# atomic. +[dependencies.portable-atomic-util] +version = "0.2.2" +# Technically, this should only be used if the Rust feature for allocation is defined. But, it +# should be safe to build the crate even if the Rust code doesn't use it because of configs. +features = ["alloc"] + # 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. [build-dependencies] -zephyr-build = { version = "0.1.0", path = "../zephyr-build" } +zephyr-build = { version = "3.7.0", path = "../zephyr-build" } diff --git a/zephyr/module.yml b/zephyr/module.yml index ef97cdf7..2bf1c9f6 100644 --- a/zephyr/module.yml +++ b/zephyr/module.yml @@ -4,6 +4,6 @@ build: cmake: . kconfig: Kconfig samples: - - samples/hello_world + - samples tests: - tests diff --git a/zephyr/src/align.rs b/zephyr/src/align.rs new file mode 100644 index 00000000..8247d7a8 --- /dev/null +++ b/zephyr/src/align.rs @@ -0,0 +1,49 @@ +//! Alignment +//! +//! Natively, the align attribute in rust does not allow anything other than an integer literal. +//! However, Zephyr will define the external alignment based on numeric constants. This defines a +//! bit of a trick to enforce alignment of structs to values by defined constants. +//! +//! Thanks to Chayim Refael Friedman for help with this. + +#[doc(hidden)] +pub struct AlignAsStruct; + +#[doc(hidden)] +pub trait AlignAsTrait { + type Aligned; +} + +macro_rules! impl_alignas { + ( $($align:literal),* $(,)? ) => { + $( + const _: () = { + #[repr(align($align))] + pub struct Aligned; + impl AlignAsTrait<$align> for AlignAsStruct { + type Aligned = Aligned; + } + }; + )* + }; +} +// This can be expanded as needed. +impl_alignas!(1, 2, 4, 8, 16, 32, 64, 128, 256); + +/// Align a given struct to a given alignment. To use this, just include `AlignAs` as the first +/// member of the struct. +#[repr(transparent)] +pub struct AlignAs([>::Aligned; 0]) + where + AlignAsStruct: AlignAsTrait; + +impl AlignAs + where AlignAsStruct: AlignAsTrait +{ + /// Construct a new AlignAs. + /// + /// It is zero bytes, but needs a constructor as the field is private. + pub const fn new() -> AlignAs { + AlignAs([]) + } +} diff --git a/zephyr/src/alloc_impl.rs b/zephyr/src/alloc_impl.rs new file mode 100644 index 00000000..359dde6d --- /dev/null +++ b/zephyr/src/alloc_impl.rs @@ -0,0 +1,92 @@ +//! A Rust global allocator that uses the stdlib allocator in Zephyr +//! +//! The zephyr runtime is divided into three crates: +//! - [core](https://doc.rust-lang.org/stable/core/) is the "dependency-free" foundation of the +//! standard library. It is all of the parts of the standard library that have no dependencies +//! outside of the language and the architecture itself. +//! - [alloc](https://doc.rust-lang.org/stable/alloc/) provides the parts of the standard library +//! that depend on memory allocation. This includes various types of smart pointers, atomically +//! referenced counted pointers, and various collections. This depends on the platform providing +//! an allocator. +//! - [std](https://doc.rust-lang.org/stable/std/) is the rest of the standard library. It include +//! both core and alloc, and then everthing else, including filesystem access, networking, etc. It +//! is notable, however, that the Rust standard library is fairly minimal. A lot of functionality +//! that other languages might include will be relegated to other crates, and the ecosystem and +//! tooling around cargo make it as easy to use these as the standard library. +//! +//! For running application code on Zephyr, the core library (mostly) just works (the a caveat of a +//! little work needed to use atomics on platforms Zephyr supports but don't have atomic +//! instructions). The std library is somewhat explicitly _not_ supported. Although the intent is +//! to provide much of the functionality from std, Zephyr is different enough from the conventional +//! operating system std was built around that just porting it doesn't really give practical +//! results. The result is either to complicated to make work, or too different from what is +//! typically done on Zephyr. Supporting std could be a future project. +//! +//! This leaves alloc, which is mostly independent but is required to know about an allocator to +//! use. This module provides an allocator for Rust that uses the underlying memory allocator +//! configured into Zephyr. +//! +//! Because a given embedded application may or may not want memory allocation, this is controlled +//! by the `CONFIG_RUST_ALLOC` Kconfig. When this config is enabled, the alloc crate becomes +//! available to applications. +//! +//! Since alloc is typically used on Rust as a part of the std library, building in a no-std +//! environment requires that it be access explicitly. Generally, alloc must be explicitly added +//! to every module that needs it. +//! +//! ``` +//! extern crate alloc; +//! +//! use alloc::boxed::Box; +//! +//! let item = Box::new(5); +//! printkln!("box value {}", item); +//! ``` +//! +//! The box holding the value 5 will be allocated by `Box::new`, and freed when the `item` goes out +//! of scope. + +// This entire module is only used if CONFIG_RUST_ALLOC is enabled. +extern crate alloc; + +use core::alloc::{GlobalAlloc, Layout}; + +use alloc::alloc::handle_alloc_error; + +/// Define size_t, as it isn't defined within the FFI. +#[allow(non_camel_case_types)] +type c_size_t = usize; + +extern "C" { + fn malloc(size: c_size_t) -> *mut u8; + fn free(ptr: *mut u8); +} + +/// An allocator that uses Zephyr's allocation primitives. +/// +/// This is exported for documentation purposes, this module does contain an instance of the +/// allocator as well. +pub struct ZephyrAllocator; + +unsafe impl GlobalAlloc for ZephyrAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let size = layout.size(); + let align = layout.align(); + + // The C allocation library assumes an alignment of 8. For now, just panic if this cannot + // be satistifed. + if align > 8 { + handle_alloc_error(layout); + } + + malloc(size) + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + free(ptr) + } +} + +/// The global allocator built around the Zephyr malloc/free. +#[global_allocator] +pub static ZEPHYR_ALLOCATOR: ZephyrAllocator = ZephyrAllocator; diff --git a/zephyr/src/error.rs b/zephyr/src/error.rs new file mode 100644 index 00000000..1cd2b826 --- /dev/null +++ b/zephyr/src/error.rs @@ -0,0 +1,56 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +//! # Zephyr errors +//! +//! This module contains an `Error` and `Result` type for use in wrapped Zephyr calls. Many +//! operations in Zephyr return an int result where negative values correspond with errnos. +//! Convert those to a `Result` type where the `Error` condition maps to errnos. +//! +//! Initially, this will just simply wrap the numeric error code, but it might make sense to make +//! this an enum itself, however, it would probably be better to auto-generate this enum instead of +//! trying to maintain the list manually. + +use core::ffi::c_int; +use core::fmt; + +// This is a little messy because the constants end up as u32 from bindgen, although the values are +// negative. + +/// A Zephyr error. +/// +/// Represents an error result returned within Zephyr. +pub struct Error(pub u32); + +impl core::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "zephyr error errno:{}", self.0) + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "zephyr error errno:{}", self.0) + } +} + +/// Wraps a value with a possible Zephyr error. +pub type Result = core::result::Result; + +/// Map a return result from Zephyr into an Result. +/// +/// Negative return results being considered errors. +pub fn to_result(code: c_int) -> Result { + if code < 0 { + Err(Error(-code as u32)) + } else { + Ok(code) + } +} + +/// Map a return result, with a void result. +pub fn to_result_void(code: c_int) -> Result<()> { + to_result(code).map(|_| ()) +} diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index ce71e46f..85b68924 100644 --- a/zephyr/src/lib.rs +++ b/zephyr/src/lib.rs @@ -8,12 +8,35 @@ #![no_std] #![allow(unexpected_cfgs)] +#![deny(missing_docs)] +pub mod align; +pub mod error; +pub mod object; +pub mod sync; pub mod sys; pub mod time; +pub use error::{Error, Result}; + +/// Re-exported for local macro use. +pub use paste::paste; + // Bring in the generated kconfig module -include!(concat!(env!("OUT_DIR"), "/kconfig.rs")); +pub mod kconfig { + //! Zephyr Kconfig values. + //! + //! This module contains an auto-generated set of constants corresponding to the values of + //! various Kconfig values during the build. + //! + //! **Note**: Unless you are viewing docs generated for a specific build, the values below are + //! unlikely to directly correspond to those in a given build. + + // Don't enforce doc comments on the bindgen, as it isn't enforced within Zephyr. + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/kconfig.rs")); +} // Ensure that Rust is enabled. #[cfg(not(CONFIG_RUST))] @@ -52,4 +75,14 @@ pub mod raw { #[doc(hidden)] pub mod _export { pub use core::format_args; + + use crate::{object::StaticKernelObject, sys::thread::StaticThreadStack}; + + /// Type alias for the thread stack kernel object. + pub type KStaticThreadStack = StaticKernelObject; } + +// Mark this as `pub` so the docs can be read. +// If allocation has been requested, provide the allocator. +#[cfg(CONFIG_RUST_ALLOC)] +pub mod alloc_impl; diff --git a/zephyr/src/object.rs b/zephyr/src/object.rs new file mode 100644 index 00000000..417c1bd3 --- /dev/null +++ b/zephyr/src/object.rs @@ -0,0 +1,318 @@ +//! # Zephyr Kernel Objects +//! +//! Zephyr has a concept of a 'kernel object' that is handled a bit magically. In kernel mode +//! threads, these are just pointers to the data structures that Zephyr uses to manage that item. +//! In userspace, they are still pointers, but those data structures aren't accessible to the +//! thread. When making syscalls, the kernel validates that the objects are both valid kernel +//! objects and that the are supposed to be accessible to this thread. +//! +//! In many Zephyr apps, the kernel objects in the app are defined as static, using special macros. +//! These macros make sure that the objects get registered so that they are accessible to userspace +//! (at least after that access is granted). +//! +//! There are also kernel objects that are synthesized as part of the build. Most notably, there +//! are ones generated by the device tree. +//! +//! ## Safety +//! +//! Zephyr has traditionally not focused on safety. Early versions of project goals, in fact, +//! emphasized performance and small code size as priorities over runtime checking of safety. Over +//! the years, this focus has changed a bit, and Zephyr does contain some additional checking, some +//! of which is optional. +//! +//! Zephyr is still constrained at compile time to checks that can be performed with the limits +//! of the C language. With Rust, we have a much greater ability to enforce many aspects of safety +//! at compile time. However, there is some complexity to doing this at the interface between the C +//! world and Rust. +//! +//! There are two types of kernel objects we deal with. There are kernel objects that are allocated +//! by C code (often auto-generated) that should be accessible to Rust. These are mostly `struct +//! device` values, and will be handled in a devices module. The other type are objects that +//! application code wishes to declare statically, and use from Rust code. That is the +//! responsibility of this module. (There will also be support for more dynamic management of +//! kernel objects, but this will be handled later). +//! +//! Static kernel objects in Zephyr are declared as C top-level variables (where the keyword static +//! means something different). It is the responsibility of the calling code to initialize these +//! items, make sure they are only initialized once, and to ensure that sharing of the object is +//! handled properly. All of these are concerns we can handle in Rust. +//! +//! To handle initialization, we pair each kernel object with a single atomic value, whose zero +//! value indicates [`KOBJ_UNINITIALIZED`]. There are a few instances of values that can be placed +//! into uninitialized memory in a C declaration that will need to be zero initialized as a Rust +//! static. The case of thread stacks is handled as a special case, where the initialization +//! tracking is kept separate so that the stack can still be placed in initialized memory. +//! +//! This state goes through two more values as the item is initialized, one indicating the +//! initialization is happening, and another indicating it has finished. +//! +//! For each kernel object, there will be two types. One, having a name of the form StaticThing, +//! and the other having the form Thing. The StaticThing will be used in a static declaration. +//! There is a [`kobj_define!`] macro that matches declarations of these values and adds the +//! necessary linker declarations to place these in the correct linker sections. This is the +//! equivalent of the set of macros in C, such as `K_SEM_DEFINE`. +//! +//! This StaticThing will have a single method [`init_once`] which accepts a single argument of a +//! type defined by the object. For most objects, it will just be an empty tuple `()`, but it can +//! be whatever initializer is needed for that type by Zephyr. Semaphores, for example, take the +//! initial value and the limit. Threads take as an initializer the stack to be used. +//! +//! This `init_once` will initialize the Zephyr object and return the `Thing` item that will have +//! the methods on it to use the object. Attributes such as `Sync`, and `Clone` will be defined +//! appropriately so as to match the semantics of the underlying Zephyr kernel object. Generally +//! this `Thing` type will simply be a container for a direct pointer, and thus using and storing +//! these will have the same characteristics as it would from C. +//! +//! Rust has numerous strict rules about mutable references, namely that it is not safe to have more +//! than one mutable reference. The language does allow multiple `*mut ktype` references, and their +//! safety depends on the semantics of what is pointed to. In the case of Zephyr, some of these are +//! intentionally thread safe (for example, things like `k_sem` which have the purpose of +//! synchronizing between threads). Others are not, and that is mirrored in Rust by whether or not +//! `Clone` and/or `Sync` are implemented. Please see the documentation of individual entities for +//! details for that object. +//! +//! In general, methods on `Thing` will require `&mut self` if there is any state to manage. Those +//! that are built around synchronization primitives, however, will generally use `&self`. In +//! general, objects that implement `Clone` will use `&self` because there would be no benefit to +//! mutable self when the object could be cloned. +//! +//! [`kobj_define!`]: crate::kobj_define +//! [`init_once`]: StaticKernelObject::init_once + +use core::{cell::UnsafeCell, mem}; + +use crate::sync::atomic::{AtomicUsize, Ordering}; + +// The kernel object itself must be wrapped in `UnsafeCell` in Rust. This does several thing, but +// the primary feature that we want to declare to the Rust compiler is that this item has "interior +// mutability". One impact will be that the default linker section will be writable, even though +// the object will not be declared as mutable. It also affects the compiler as it will avoid things +// like aliasing and such on the data, as it will know that it is potentially mutable. In our case, +// the mutations happen from C code, so this is less important than the data being placed in the +// proper section. Many will have the link section overridden by the `kobj_define` macro. + +/// A kernel object represented statically in Rust code. +/// +/// These should not be declared directly by the user, as they generally need linker decorations to +/// be properly registered in Zephyr as kernel objects. The object has the underlying Zephyr type +/// T, and the wrapper type W. +/// +/// Kernel objects will have their `StaticThing` implemented as `StaticKernelObject` where +/// `kobj` is the type of the underlying Zephyr object. `Thing` will usually be a struct with a +/// single field, which is a `*mut kobj`. +/// +/// TODO: Can we avoid the public fields with a const new method? +/// +/// TODO: Handling const-defined alignment for these. +pub struct StaticKernelObject { + #[allow(dead_code)] + /// The underlying zephyr kernel object. + pub value: UnsafeCell, + /// Initialization status of this object. Most objects will start uninitialized and be + /// initialized manually. + pub init: AtomicUsize, +} + +/// Define the Wrapping of a kernel object. +/// +/// This trait defines the association between a static kernel object and the two associated Rust +/// types: `StaticThing` and `Thing`. In the general case: there should be: +/// ``` +/// impl Wrapped for StaticKernelObject { +/// type T = Thing, +/// type I = (), +/// fn get_wrapped(&self, args: Self::I) -> Self::T { +/// let ptr = self.value.get(); +/// // Initizlie the kobj using ptr and possible the args. +/// Thing { ptr } +/// } +/// } +/// ``` +pub trait Wrapped { + /// The wrapped type. This is what `init_once()` on the StaticKernelObject will return after + /// initialization. + type T; + + /// The wrapped type also requires zero or more initializers. Which are represented by this + /// type. + type I; + + /// Initialize this kernel object, and return the wrapped pointer. + fn get_wrapped(&self, args: Self::I) -> Self::T; +} + +/// A state indicating an uninitialized kernel object. +/// +/// This must be zero, as kernel objects will +/// be represetned as zero-initialized memory. +pub const KOBJ_UNINITIALIZED: usize = 0; + +/// A state indicating a kernel object that is being initialized. +pub const KOBJ_INITING: usize = 1; + +/// A state indicating a kernel object that has completed initialization. This also means that the +/// take has been called. And shouldn't be allowed additional times. +pub const KOBJ_INITIALIZED: usize = 2; + +impl StaticKernelObject +where + StaticKernelObject: Wrapped, +{ + /// Construct an empty of these objects, with the zephyr data zero-filled. This is safe in the + /// sense that Zephyr we track the initialization, they start in the uninitialized state, and + /// the zero value of the initialize atomic indicates that it is uninitialized. + pub const fn new() -> StaticKernelObject { + StaticKernelObject { + value: unsafe { mem::zeroed() }, + init: AtomicUsize::new(KOBJ_UNINITIALIZED), + } + } + + /// Get the instance of the kernel object. + /// + /// Will return a single wrapped instance of this object. This will invoke the initialization, + /// and return `Some` for the wrapped containment type. + /// + /// If it is called an additional time, it will return None. + pub fn init_once(&self, args: ::I) -> Option<::T> { + if let Err(_) = self.init.compare_exchange( + KOBJ_UNINITIALIZED, + KOBJ_INITING, + Ordering::AcqRel, + Ordering::Acquire) + { + return None; + } + let result = self.get_wrapped(args); + self.init.store(KOBJ_INITIALIZED, Ordering::Release); + Some(result) + } +} + +/// Declare a static kernel object. This helps declaring static values of Zephyr objects. +/// +/// This can typically be used as: +/// ``` +/// kobj_define! { +/// static A_MUTEX: StaticMutex; +/// static MUTEX_ARRAY: [StaticMutex; 4]; +/// } +/// ``` +#[macro_export] +macro_rules! kobj_define { + ($v:vis static $name:ident: $type:tt; $($rest:tt)*) => { + $crate::_kobj_rule!($v, $name, $type); + $crate::kobj_define!($($rest)*); + }; + ($v:vis static $name:ident: $type:tt<$size:ident>; $($rest:tt)*) => { + $crate::_kobj_rule!($v, $name, $type<$size>); + $crate::kobj_define!($($rest)*); + }; + ($v:vis static $name:ident: $type:tt<$size:literal>; $($rest:tt)*) => { + $crate::_kobj_rule!($v, $name, $type<$size>); + $crate::kobj_define!($($rest)*); + }; + ($v:vis static $name:ident: $type:tt<{$size:expr}>; $($rest:tt)*) => { + $crate::_kobj_rule!($v, $name, $type<{$size}>); + $crate::kobj_define!($($rest)*); + }; + () => {}; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! _kobj_rule { + // static NAME: StaticSemaphore; + ($v:vis, $name:ident, StaticSemaphore) => { + #[link_section = concat!("._k_sem.static.", stringify!($name), ".", file!(), line!())] + $v static $name: $crate::sys::sync::StaticSemaphore = + unsafe { ::core::mem::zeroed() }; + }; + + // static NAMES: [StaticSemaphore; COUNT]; + ($v:vis, $name:ident, [StaticSemaphore; $size:expr]) => { + #[link_section = concat!("._k_sem.static.", stringify!($name), ".", file!(), line!())] + $v static $name: [$crate::sys::sync::StaticSemaphore; $size] = + unsafe { ::core::mem::zeroed() }; + }; + + // static THREAD: staticThread; + ($v:vis, $name:ident, StaticThread) => { + // Since the static object has an atomic that we assume is initialized, we cannot use the + // default linker section Zephyr uses for Thread, as that is uninitialized. This will put + // it in .bss, where it is zero initialized. + $v static $name: $crate::sys::thread::StaticThread = + unsafe { ::core::mem::zeroed() }; + }; + + // static THREAD: [staticThread; COUNT]; + ($v:vis, $name:ident, [StaticThread; $size:expr]) => { + // Since the static object has an atomic that we assume is initialized, we cannot use the + // default linker section Zephyr uses for Thread, as that is uninitialized. This will put + // it in .bss, where it is zero initialized. + $v static $name: [$crate::sys::thread::StaticThread; $size] = + unsafe { ::core::mem::zeroed() }; + }; + + // Use indirection on stack initializers to handle some different cases in the Rust syntax. + ($v:vis, $name:ident, ThreadStack<$size:literal>) => { + $crate::_kobj_stack!($v, $name, $size); + }; + ($v:vis, $name:ident, ThreadStack<$size:ident>) => { + $crate::_kobj_stack!($v, $name, $size); + }; + ($v:vis, $name:ident, ThreadStack<{$size:expr}>) => { + $crate::_kobj_stack!($v, $name, $size); + }; + + // Array of stack object versions. + ($v:vis, $name:ident, [ThreadStack<$size:literal>; $asize:expr]) => { + $crate::_kobj_stack!($v, $name, $size, $asize); + }; + ($v:vis, $name:ident, [ThreadStack<$size:ident>; $asize:expr]) => { + $crate::_kobj_stack!($v, $name, $size, $asize); + }; + ($v:vis, $name:ident, [ThreadStack<{$size:expr}>; $asize:expr]) => { + $crate::_kobj_stack!($v, $name, $size, $asize); + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! _kobj_stack { + ($v:vis, $name: ident, $size:expr) => { + $crate::paste! { + // The actual stack itself goes into the no-init linker section. We'll use the user_name, + // with _REAL appended, to indicate the real stack. + #[link_section = concat!(".noinit.", stringify!($name), ".", file!(), line!())] + $v static [< $name _REAL >]: $crate::sys::thread::RealStaticThreadStack<{$crate::sys::thread::stack_len($size)}> = + unsafe { ::core::mem::zeroed() }; + + // The proxy object used to ensure initialization is placed in initialized memory. + $v static $name: $crate::_export::KStaticThreadStack = + $crate::_export::KStaticThreadStack::new_from(&[< $name _REAL >]); + } + }; + + // This initializer needs to have the elements of the array initialized to fixed elements of the + // `RealStaticThreadStack`. Unfortunately, methods such as [`each_ref`] on the array are not + // const and can't be used in a static initializer. We could use a recursive macro definition + // to perform the initialization, but this would require the array size to only be an integer + // literal (constants aren't calculated until after macro expansion). It may also be possible + // to write a constructor for the array as a const fn, which would greatly simplify the + // initialization here. + ($v:vis, $name: ident, $size:expr, $asize:expr) => { + $crate::paste! { + // The actual stack itself goes into the no-init linker section. We'll use the user_name, + // with _REAL appended, to indicate the real stack. + #[link_section = concat!(".noinit.", stringify!($name), ".", file!(), line!())] + $v static [< $name _REAL >]: + [$crate::sys::thread::RealStaticThreadStack<{$crate::sys::thread::stack_len($size)}>; $asize] = + unsafe { ::core::mem::zeroed() }; + + $v static $name: + [$crate::_export::KStaticThreadStack; $asize] = + $crate::_export::KStaticThreadStack::new_from_array(&[< $name _REAL >]); + } + }; +} diff --git a/zephyr/src/printk.rs b/zephyr/src/printk.rs index a8183aef..8a281df5 100644 --- a/zephyr/src/printk.rs +++ b/zephyr/src/printk.rs @@ -12,6 +12,15 @@ use core::fmt::{ write, }; +/// Print to Zephyr's console, without a newline. +/// +/// This macro uses the same syntax as std's +/// [`format!`](https://doc.rust-lang.org/stable/std/fmt/index.html), but writes to the Zephyr +/// console instead. +/// +/// if `CONFIG_PRINTK_SYNC` is enabled, this locks during printing. However, to avoid allocation, +/// and due to private accessors in the Zephyr printk implementation, the lock is only over groups +/// of a small buffer size. This buffer must be kept fairly small, as it resides on the stack. #[macro_export] macro_rules! printk { ($($arg:tt)*) => {{ @@ -19,6 +28,17 @@ macro_rules! printk { }}; } +/// Print to Zephyr's console, with a newline. +/// +/// This macro uses the same syntax as std's +/// [`format!`](https://doc.rust-lang.org/stable/std/fmt/index.html), but writes to the Zephyr +/// console instead. +/// +/// If `CONFIG_PRINTK_SYNC` is enabled, this locks during printing. However, to avoid allocation, +/// and due to private accessors in the Zephyr printk implementation, the lock is only over groups +/// of a small buffer size. This buffer must be kept fairly small, as it resides on the stack. +/// +/// [`format!`]: alloc::format #[macro_export] macro_rules! printkln { ($($arg:tt)*) => {{ @@ -93,6 +113,7 @@ impl Write for Context { } } +#[doc(hidden)] pub fn printk(args: Arguments<'_>) { let mut context = Context { count: 0, @@ -102,6 +123,7 @@ pub fn printk(args: Arguments<'_>) { context.flush(); } +#[doc(hidden)] pub fn printkln(args: Arguments<'_>) { let mut context = Context { count: 0, diff --git a/zephyr/src/sync.rs b/zephyr/src/sync.rs new file mode 100644 index 00000000..bdce4e20 --- /dev/null +++ b/zephyr/src/sync.rs @@ -0,0 +1,24 @@ +//! Higher level synchronization primitives. +//! +//! These are modeled after the synchronization primitives in +//! [`std::sync`](https://doc.rust-lang.org/stable/std/sync/index.html) and those from +//! [`crossbeam-channel`](https://docs.rs/crossbeam-channel/latest/crossbeam_channel/), in as much +//! as it makes sense. + +pub mod atomic { + //! Re-export portable atomic. + //! + //! Although `core` contains a + //! [`sync::atomic`](https://doc.rust-lang.org/stable/core/sync/atomic/index.html) module, + //! these are dependent on the target having atomic instructions, and the types are missing + //! when the platform cannot support them. Zephyr, however, does provide atomics on platforms + //! that don't support atomic instructions, using spinlocks. In the Rust-embedded world, this + //! is done through the [`portable-atomic`](https://crates.io/crates/portable-atomic) crate, + //! which will either just re-export the types from core, or provide an implementation using + //! spinlocks when those aren't available. + + pub use portable_atomic::*; +} + +#[cfg(CONFIG_RUST_ALLOC)] +pub use portable_atomic_util::Arc; diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index d7342a6d..dfb16e4d 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -11,8 +11,63 @@ use zephyr_sys::k_timeout_t; +pub mod sync; +pub mod thread; + // 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. + +/// Represents a timeout with an infinite delay. +/// +/// Low-level Zephyr constant. Calls using this value will wait as long as necessary to perform +/// the requested operation. pub const K_FOREVER: k_timeout_t = k_timeout_t { ticks: -1 }; + +/// Represents a null timeout delay. +/// +/// Low-level Zephyr Constant. Calls using this value will not wait if the operation cannot be +/// performed immediately. pub const K_NO_WAIT: k_timeout_t = k_timeout_t { ticks: 0 }; + +/// Return the current uptime of the system in ms. +/// +/// Direct Zephyr call. Precision is limited by the system tick timer. +#[inline] +pub fn uptime_get() -> i64 { + unsafe { + crate::raw::k_uptime_get() + } +} + +pub mod critical { + //! Zephyr implementation of critical sections. + //! + //! Critical sections from Rust are handled with a single Zephyr spinlock. This doesn't allow + //! any nesting, but neither does the `critical-section` crate. + + use core::{ffi::c_int, ptr::addr_of_mut}; + + use critical_section::RawRestoreState; + use zephyr_sys::{k_spinlock, k_spin_lock, k_spin_unlock, k_spinlock_key_t}; + + struct ZephyrCriticalSection; + critical_section::set_impl!(ZephyrCriticalSection); + + // The critical section shares a single spinlock. + static mut LOCK: k_spinlock = unsafe { core::mem::zeroed() }; + + unsafe impl critical_section::Impl for ZephyrCriticalSection { + unsafe fn acquire() -> RawRestoreState { + let res = k_spin_lock(addr_of_mut!(LOCK)); + res.key as RawRestoreState + } + + unsafe fn release(token: RawRestoreState) { + k_spin_unlock(addr_of_mut!(LOCK), + k_spinlock_key_t { + key: token as c_int, + }); + } + } +} diff --git a/zephyr/src/sys/sync.rs b/zephyr/src/sys/sync.rs new file mode 100644 index 00000000..b19b593f --- /dev/null +++ b/zephyr/src/sys/sync.rs @@ -0,0 +1,146 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +//! # Zephyr low-level synchronization primities. +//! +//! The `zephyr-sys` crate contains direct calls into the Zephyr C API. This interface, however, +//! cannot be used from safe Rust. This crate attempts to be as direct an interface to some of +//! these synchronization mechanisms, but without the need for unsafe. The other module +//! `crate::sync` provides higher level interfaces that help manage synchronization in coordination +//! with Rust's borrowing and sharing rules, and will generally provide much more usable +//! interfaces. +//! +//! # Kernel objects +//! +//! Zephyr's primitives work with the concept of a kernel object. These are the data structures +//! that are used by the Zephyr kernel to coordinate the operation of the primitives. In addition, +//! they are where the protection barrier provided by `CONFIG_USERSPACE` is implemented. In order +//! to use these primitives from a userspace thread two things must happen: +//! +//! - The kernel objects must be specially declared. All kernel objects in Zephyr will be built, +//! at compile time, into a perfect hash table that is used to validate them. The special +//! declaration will take care of this. +//! - The objects must be granted permission to be used by the userspace thread. This can be +//! managed either by specifically granting permission, or by using inheritance when creating the +//! thread. +//! +//! At this time, only the first mechanism is implemented, and all kernel objects should be +//! declared using the `crate::kobj_define!` macro. These then must be initialized, and then the +//! special method `.get()` called, to retrieve the Rust-style value that is used to manage them. +//! Later, there will be a pool mechanism to allow these kernel objects to be allocated and freed +//! from a pool, although the objects will still be statically allocated. + +use core::ffi::c_uint; +use core::fmt; + +use crate::{ + error::{Result, to_result_void}, + object::{StaticKernelObject, Wrapped}, + raw::{ + k_sem, + k_sem_init, + k_sem_take, + k_sem_give, + k_sem_reset, + k_sem_count_get, + }, + time::Timeout, +}; + +pub use crate::raw::K_SEM_MAX_LIMIT; + +/// A zephyr `k_sem` usable from safe Rust code. +#[derive(Clone)] +pub struct Semaphore { + /// The raw Zephyr `k_sem`. + item: *mut k_sem, +} + +/// By nature, Semaphores are both Sync and Send. Safety is handled by the underlying Zephyr +/// implementation (which is why Clone is also implemented). +unsafe impl Sync for Semaphore {} +unsafe impl Send for Semaphore {} + +impl Semaphore { + /// Take a semaphore. + /// + /// Can be called from ISR if called with [`NoWait`]. + /// + /// [`NoWait`]: crate::time::NoWait + pub fn take(&self, timeout: T) -> Result<()> + where T: Into, + { + let timeout: Timeout = timeout.into(); + let ret = unsafe { + k_sem_take(self.item, timeout.0) + }; + to_result_void(ret) + } + + /// Give a semaphore. + /// + /// This routine gives to the semaphore, unless the semaphore is already at its maximum + /// permitted count. + pub fn give(&self) { + unsafe { + k_sem_give(self.item) + } + } + + /// Resets a semaphor's count to zero. + /// + /// This resets the count to zero. Any outstanding [`take`] calls will be aborted with + /// `Error(EAGAIN)`. + /// + /// [`take`]: Self::take + pub fn reset(&mut self) { + unsafe { + k_sem_reset(self.item) + } + } + + /// Get a semaphore's count. + /// + /// Returns the current count. + pub fn count_get(&mut self) -> usize { + unsafe { + k_sem_count_get(self.item) as usize + } + } +} + +/// A static Zephyr `k_sem`. +/// +/// This is intended to be used from within the `kobj_define!` macro. It declares a static ksem +/// that will be properly registered with the Zephyr kernel object system. Call [`init_once`] to +/// get the [`Semaphore`] that is represents. +/// +/// [`init_once`]: StaticKernelObject::init_once +pub type StaticSemaphore = StaticKernelObject; + +unsafe impl Sync for StaticSemaphore {} + +impl Wrapped for StaticKernelObject { + type T = Semaphore; + + /// The initializer for Semaphores is the initial count, and the count limit (which can be + /// K_SEM_MAX_LIMIT, re-exported here. + type I = (c_uint, c_uint); + + // TODO: Thoughts about how to give parameters to the initialzation. + fn get_wrapped(&self, arg: Self::I) -> Semaphore { + let ptr = self.value.get(); + unsafe { + k_sem_init(ptr, arg.0, arg.1); + } + Semaphore { + item: ptr, + } + } +} + +impl fmt::Debug for Semaphore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "sys::Semaphore") + } +} diff --git a/zephyr/src/sys/thread.rs b/zephyr/src/sys/thread.rs new file mode 100644 index 00000000..4e9725f8 --- /dev/null +++ b/zephyr/src/sys/thread.rs @@ -0,0 +1,479 @@ +//! Zephyr low level threads +//! +//! This is a fairly low level (but still safe) interface to Zephyr threads. This is intended to +//! work the same way as threads are typically done on Zephyr systems, where the threads and their +//! stacks are statically allocated, a code is called to initialize them. +//! +//! In addition, there are some convenience operations available that require allocation to be +//! available. +//! +//! ## Usage +//! +//! Each thread needs a stack associated with it. The stack and the thread should be defined as +//! follows: +//! ``` +//! kobj_defined! { +//! static MY_THREAD: StaticThread; +//! static MY_THREAD_STACK: StaticThreadStack<2048>; +//! } +//! ``` +//! +//! Each of these has a [`init_once`] method that returns the single usable instance. The +//! StaticThread takes the stack retrieved by take as its argument. This will return a +//! ThreadStarter, where various options can be set on the thread, and then it started with one of +//! `spawn`, or `simple_spawn` (spawn requires `CONFIG_RUST_ALLOC`). +//! +//! Provided that `CONFIG_RUST_ALLOC` has been enabled (recommended): the read can be initialized as +//! follows: +//! ``` +//! let mut thread = MY_THREAD.init_once(MY_THREAD_STACK.init_once(()).unwrap()).unwrap(); +//! thread.set_priority(5); +//! let child = thread.spawn(|| move { +//! // thread code... +//! }); +//! ``` +//! +//! [`init_once`]: StaticKernelObject::init_once + +#[cfg(CONFIG_RUST_ALLOC)] +extern crate alloc; + +#[cfg(CONFIG_RUST_ALLOC)] +use alloc::boxed::Box; +use core::{cell::UnsafeCell, ffi::{c_int, c_void}, mem}; + +use zephyr_sys::{ + k_thread, + k_thread_entry_t, + k_thread_create, + z_thread_stack_element, + ZR_STACK_ALIGN, + ZR_STACK_RESERVED, +}; +use super::K_NO_WAIT; + +use crate::{align::AlignAs, object::{StaticKernelObject, Wrapped}, sync::atomic::AtomicUsize}; + +/// Adjust the stack size for alignment. Note that, unlike the C code, we don't include the +/// reservation in this, as it has its own fields in the struct. +pub const fn stack_len(size: usize) -> usize { + size.next_multiple_of(ZR_STACK_ALIGN) +} + +#[doc(hidden)] +/// A Zephyr stack declaration. +/// +/// It isn't meant to be used directly, as it needs additional decoration about linker sections and +/// such. Unlike the C declaration, the reservation is a separate field. As long as the SIZE is +/// properly aligned, this should work without padding between the fields. +pub struct RealStaticThreadStack { + #[allow(dead_code)] + align: AlignAs, + #[allow(dead_code)] + pub data: UnsafeCell<[z_thread_stack_element; SIZE]>, + #[allow(dead_code)] + extra: [z_thread_stack_element; ZR_STACK_RESERVED], +} + +unsafe impl Sync for RealStaticThreadStack {} + +/// The dynamic stack value, which wraps the underlying stack. +/// +/// TODO: constructor instead of private. +pub struct ThreadStack { + /// Private + pub base: *mut z_thread_stack_element, + /// Private + pub size: usize, +} + +#[doc(hidden)] +pub struct StaticThreadStack { + pub base: *mut z_thread_stack_element, + pub size: usize, +} + +unsafe impl Sync for StaticKernelObject {} + +/* +// Let's make sure I can declare some of this. +pub static TEST_STACK: RealStaticThreadStack<1024> = unsafe { ::core::mem::zeroed() }; +pub static TEST: StaticKernelObject = StaticKernelObject { + value: UnsafeCell::new(StaticThreadStack { + base: TEST_STACK.data.get() as *mut z_thread_stack_element, + size: 1024, + }), + init: AtomicUsize::new(0), +}; + +pub fn doit() { + TEST.init_once(()); +} +*/ + +impl Wrapped for StaticKernelObject { + type T = ThreadStack; + type I = (); + fn get_wrapped(&self, _args: Self::I) -> Self::T { + // This is a bit messy. Whee. + let stack = self.value.get(); + let stack = unsafe { &*stack }; + ThreadStack { + base: stack.base, + size: stack.size, + } + } +} + +impl StaticKernelObject { + /// Construct a StaticThreadStack object. + /// + /// This is not intended to be directly called, but is used by the [`kobj_define`] macro. + #[doc(hidden)] + pub const fn new_from(real: &RealStaticThreadStack) -> Self { + Self { + value: UnsafeCell::new(StaticThreadStack { + base: real.data.get() as *mut z_thread_stack_element, + size: SZ, + }), + init: AtomicUsize::new(0), + } + } + + /// Construct an array of StaticThreadStack kernel objects, based on the same sized array of the + /// RealStaticThreadStack objects. + /// + /// This is not intended to be directly called, but is used by the [`kobj_define`] macro. + #[doc(hidden)] + pub const fn new_from_array( + real: &[RealStaticThreadStack; N], + ) -> [Self; N] { + // Rustc currently doesn't support iterators in constant functions, but it does allow simple + // looping. Since the value is not Copy, we need to use the MaybeUninit with a bit of + // unsafe. This initialization is safe, as we loop through all of the entries, giving them + // a value. + // + // In addition, MaybeUninit::uninit_array is not stable, so do this the old unsafe way. + // let mut res: [MaybeUninit; N] = MaybeUninit::uninit_array(); + // Note that `mem::uninitialized` in addition to being deprecated, isn't const. But, since + // this is a const computation, zero-filling shouldn't hurt anything. + let mut res: [Self; N] = unsafe { mem::zeroed() }; + let mut i = 0; + while i < N { + res[i] = Self::new_from(&real[i]); + i += 1; + } + res + } +} + +/// A single Zephyr thread. +/// +/// This wraps a `k_thread` type within Rust. This value is returned from +/// [`StaticThread::init_once`] and represents an initialized thread that hasn't been started. +pub struct Thread { + raw: *mut k_thread, + stack: ThreadStack, + + /// The initial priority of this thread. + priority: c_int, + /// Options given to thread creation. + options: u32, +} + +/// A statically defined thread. +pub type StaticThread = StaticKernelObject; + +unsafe impl Sync for StaticThread {} + +impl Wrapped for StaticKernelObject { + type T = Thread; + type I = ThreadStack; + fn get_wrapped(&self, stack: Self::I) -> Self::T { + Thread { + raw: self.value.get(), + stack, + priority: 0, + options: 0, + } + } +} + +impl Thread { + /// Set the priority the thread will be created at. + pub fn set_priority(&mut self, priority: c_int) { + self.priority = priority; + } + + /// Set the value of the options passed to thread creation. + pub fn set_options(&mut self, options: u32) { + self.options = options; + } + + /// Simple thread spawn. This is unsafe because of the raw values being used. This can be + /// useful in systems without an allocator defined. + pub unsafe fn simple_spawn(self, + child: k_thread_entry_t, + p1: *mut c_void, + p2: *mut c_void, + p3: *mut c_void) + { + k_thread_create( + self.raw, + self.stack.base, + self.stack.size, + child, + p1, + p2, + p3, + self.priority, + self.options, + K_NO_WAIT); + } + + #[cfg(CONFIG_RUST_ALLOC)] + /// Spawn a thread, with a closure. + /// + /// This requires allocation to be able to safely pass the closure to the other thread. + pub fn spawn(&self, child: F) { + use core::ptr::null_mut; + + let child: closure::Closure = Box::new(child); + let child = Box::into_raw(Box::new(closure::ThreadData { + closure: child, + })); + unsafe { + k_thread_create( + self.raw, + self.stack.base, + self.stack.size, + Some(closure::child), + child as *mut c_void, + null_mut(), + null_mut(), + self.priority, + self.options, + K_NO_WAIT); + } + } +} + +/* +use zephyr_sys::{ + k_thread, k_thread_create, k_thread_start, z_thread_stack_element, ZR_STACK_ALIGN, ZR_STACK_RESERVED +}; + +use core::{cell::UnsafeCell, ffi::c_void, ptr::null_mut}; + +use crate::{align::AlignAs, object::{KobjInit, StaticKernelObject}}; + +#[cfg(CONFIG_RUST_ALLOC)] +extern crate alloc; +#[cfg(CONFIG_RUST_ALLOC)] +use alloc::boxed::Box; +#[cfg(CONFIG_RUST_ALLOC)] +use core::mem::ManuallyDrop; + +use super::K_FOREVER; + +/// Adjust the stack size for alignment. Note that, unlike the C code, we don't include the +/// reservation in this, as it has its own fields in the struct. +pub const fn stack_len(size: usize) -> usize { + size.next_multiple_of(ZR_STACK_ALIGN) +} + +/// A Zephyr stack declaration. It isn't meant to be used directly, as it needs additional +/// decoration about linker sections and such. Unlike the C declaration, the reservation is a +/// separate field. As long as the SIZE is properly aligned, this should work without padding +/// between the fields. +pub struct ThreadStack { + #[allow(dead_code)] + align: AlignAs, + data: UnsafeCell<[z_thread_stack_element; SIZE]>, + #[allow(dead_code)] + extra: [z_thread_stack_element; ZR_STACK_RESERVED], +} + +unsafe impl Sync for ThreadStack {} + +impl ThreadStack { + /// Get the size of this stack. This is the size, minus any reservation. This is called `size` + /// to avoid any confusion with `len` which might return the actual size of the stack. + pub fn size(&self) -> usize { + SIZE + } + + /// Return the stack base needed as the argument to various zephyr calls. + pub fn base(&self) -> *mut z_thread_stack_element { + self.data.get() as *mut z_thread_stack_element + } + + /// Return the token information for this stack, which is a base and size. + pub fn token(&self) -> StackToken { + StackToken { base: self.base(), size: self.size() } + } +} + +/// Declare a variable, of a given name, representing the stack for a thread. +#[macro_export] +macro_rules! kernel_stack_define { + ($name:ident, $size:expr) => { + #[link_section = concat!(".noinit.", stringify!($name), ".", file!(), line!())] + static $name: $crate::sys::thread::ThreadStack<{$crate::sys::thread::stack_len($size)}> + = unsafe { ::core::mem::zeroed() }; + }; +} + +/// A single Zephyr thread. +/// +/// This wraps a `k_thread` type within Zephyr. This value is returned +/// from the `StaticThread::spawn` method, to allow control over the start +/// of the thread. The [`start`] method should be used to start the +/// thread. +/// +/// [`start`]: Thread::start +pub struct Thread { + raw: *mut k_thread, +} + +unsafe impl Sync for StaticKernelObject { } + +impl KobjInit for StaticKernelObject { + fn wrap(ptr: *mut k_thread) -> Thread { + Thread { raw: ptr } + } +} + +// Public interface to threads. +impl Thread { + /// Start execution of the given thread. + pub fn start(&self) { + unsafe { k_thread_start(self.raw) } + } +} + +/// Declare a global static representing a thread variable. +#[macro_export] +macro_rules! kernel_thread_define { + ($name:ident) => { + // Since the static object has an atomic that we assume is initialized, let the compiler put + // this in the data section it finds appropriate (probably .bss if it is initialized to zero). + // This only matters when the objects are being checked. + // TODO: This doesn't seem to work with the config. + // #[cfg_attr(not(CONFIG_RUST_CHECK_KOBJ_INIT), + // link_section = concat!(".noinit.", stringify!($name), ".", file!(), line!()))] + static $name: $crate::object::StaticKernelObject<$crate::raw::k_thread> = + $crate::object::StaticKernelObject::new(); + // static $name: $crate::sys::thread::Thread = unsafe { ::core::mem::zeroed() }; + }; +} + +/// For now, this "token" represents the somewhat internal information about thread. +/// What we really want is to make sure that stacks and threads go together. +pub struct StackToken { + base: *mut z_thread_stack_element, + size: usize, +} + +// This isn't really safe at all, as these can be initialized. It is unclear how, if even if it is +// possible to implement safe static threads and other data structures in Zephyr. + +/// A Statically defined Zephyr `k_thread` object to be used from Rust. +/// +/// This should be used in a manner similar to: +/// ``` +/// const MY_STACK_SIZE: usize = 4096; +/// +/// kobj_define! { +/// static MY_THREAD: StaticThread; +/// static MY_STACK: ThreadStack; +/// } +/// +/// let thread = MY_THREAD.spawn(MY_STACK.token(), move || { +/// // Body of thread. +/// }); +/// thread.start(); +/// ``` +pub type StaticThread = StaticKernelObject; + +// The thread itself assumes we've already initialized, so this method is on the wrapper. +impl StaticThread { + /// Spawn this thread to the given external function. This is a simplified version that doesn't + /// take any arguments. The child runs immediately. + pub fn simple_spawn(&self, stack: StackToken, child: fn() -> ()) -> Thread { + self.init_help(|raw| { + unsafe { + k_thread_create( + raw, + stack.base, + stack.size, + Some(simple_child), + child as *mut c_void, + null_mut(), + null_mut(), + 5, + 0, + K_FOREVER, + ); + } + }); + self.get() + } + + #[cfg(CONFIG_RUST_ALLOC)] + /// Spawn a thread, running a closure. The closure will be boxed to give to the new thread. + /// The new thread runs immediately. + pub fn spawn(&self, stack: StackToken, child: F) -> Thread { + let child: closure::Closure = Box::new(child); + let child = Box::into_raw(Box::new(closure::ThreadData { + closure: ManuallyDrop::new(child), + })); + self.init_help(move |raw| { + unsafe { + k_thread_create( + raw, + stack.base, + stack.size, + Some(closure::child), + child as *mut c_void, + null_mut(), + null_mut(), + 5, + 0, + K_FOREVER, + ); + } + }); + self.get() + } +} + +unsafe extern "C" fn simple_child( + arg: *mut c_void, + _p2: *mut c_void, + _p3: *mut c_void, +) { + let child: fn() -> () = core::mem::transmute(arg); + (child)(); +} +*/ + +#[cfg(CONFIG_RUST_ALLOC)] +/// Handle the closure case. This invokes a double box to rid us of the fat pointer. I'm not sure +/// this is actually necessary. +mod closure { + use super::Box; + use core::ffi::c_void; + + pub type Closure = Box; + + pub struct ThreadData { + pub closure: Closure, + } + + pub unsafe extern "C" fn child(child: *mut c_void, _p2: *mut c_void, _p3: *mut c_void) { + let thread_data: Box = unsafe { Box::from_raw(child as *mut ThreadData) }; + let closure = (*thread_data).closure; + closure(); + } +} diff --git a/zephyr/src/time.rs b/zephyr/src/time.rs index 7e6228e7..e1d6eafb 100644 --- a/zephyr/src/time.rs +++ b/zephyr/src/time.rs @@ -66,7 +66,11 @@ pub type Instant = fugit::Instant; // 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. +/// Wrapper around the timeout type, so we can implement From/Info. +/// +/// This wrapper allows us to implement `From` and `Info` from the Fugit types into the Zephyr +/// types. This allows various methods to accept either value, as well as the `Forever` and +/// `NoWait` values defined here. pub struct Timeout(pub k_timeout_t); // `From` allows methods to take a time of various types and convert it into a Zephyr timeout.