Skip to content

Commit 1540e9a

Browse files
committed
zephyr: Add initial support for embassy
Adds the Cargo.toml framework for initial support of using Embassy's executor on Zephyr. This implements a time driver for Embassy using a `k_timer` from Zephyr. Signed-off-by: David Brown <[email protected]>
1 parent 1503a98 commit 1540e9a

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed

zephyr/Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,30 @@ version = "0.2.2"
4545
# should be safe to build the crate even if the Rust code doesn't use it because of configs.
4646
features = ["alloc"]
4747

48+
[dependencies.embassy-time-driver]
49+
version = "0.2"
50+
# Someone needs to tell embassy what our clock frequency is. Until we have support from cmake for
51+
# this, we'll have to leave this up to the app to provide, since it is heavily board specific.
52+
optional = true
53+
54+
[dependencies.embassy-time-queue-utils]
55+
version = "0.1"
56+
optional = true
57+
58+
[dependencies.embassy-sync]
59+
version = "0.6.2"
60+
optional = true
61+
4862
# These are needed at build time.
4963
# Whether these need to be vendored is an open question. They are not
5064
# used by the core Zephyr tree, but are needed by zephyr applications.
5165
[build-dependencies]
5266
zephyr-build = { version = "0.1.0", path = "../zephyr-build" }
67+
68+
[features]
69+
# Provide an implementation of embassy-time-driver.
70+
time-driver = [
71+
"dep:embassy-time-driver",
72+
"dep:embassy-time-queue-utils",
73+
"dep:embassy-sync",
74+
]

zephyr/src/embassy.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//! Support for Embassy on Rust+Zephyr
2+
//!
3+
//! [Embassy](https://embassy.dev/) is: "The next-generation framework for embedded applications".
4+
//! From a typical RTOS perspective it is perhaps a little difficult to explain what exactly it is,
5+
//! and why it makes sense to discuss it in the context of supporting Rust on Zephyr.
6+
//!
7+
//! At a core level, Embassy is a set of crates that implement various functionality that is used
8+
//! when writing bare metal applications in Rust. Combined, these provide most of the functionality
9+
//! that is needed for an embedded application. However, the crates are largely independent, and as
10+
//! such find use when combined with Zephyr.
11+
//!
12+
//! ## Executor
13+
//!
14+
//! A significant aspect of Embassy's functionality revolves around providing one or more executors
15+
//! for coordinating async code in Rust. The Rust language transforms code annotated with
16+
//! async/await into state machines that allow these operations to be run cooperatively. A bare
17+
//! metal system with one or more of these executors managing async tasks can indeed solve many of
18+
//! the types of scheduling solutions needed for embedded systems.
19+
//!
20+
//! Although Zephyr does have a thread scheduler, there are still some advantages to running an
21+
//! executor on one or more Zephyr threads:
22+
//!
23+
//! - Because the async code is transformed into a state machine, this code only uses stack while
24+
//! evaluating to the next stopping point. This allows a large number of async operations to
25+
//! happen on a single thread, without requiring additional stack. The state machines themselves
26+
//! do take memory, but this usage is known at compile time (with the stable Rust compiler, it is
27+
//! allocated from a pool, and with the nightly compiler, can be completely compile time
28+
//! determined).
29+
//! - Context switches between async threads can be very fast. When running a single executor
30+
//! thread, there is no need for locking for data that is entirely kept within that thread, and
31+
//! these context switches have similar cost to a function call. Even with multiple threads
32+
//! involved, many switches will happen on the same underlying Zephyr thread, reducing the need to
33+
//! reschedule.
34+
//! - Embassy provides a lot of mechanisms for coordinating between these tasks, all that work in
35+
//! the context of async/await. Some may be thought of as redundant with Zephyr primitives, but
36+
//! they serve a different purpose, and provide more streamlined coordination for things entirely
37+
//! within the Rust world.
38+
//!
39+
//! ## Use
40+
//!
41+
//! To best use this module, it is best to look at the various examples under `samples/embassy*` in
42+
//! this repo. Some of the embassy crates, especially embassy-executor have numerous features that
43+
//! must be configured correctly for proper operation. To use the 'executor-thread' feature, it is
44+
//! also necessary to configure embassy for the proper platform. Future versions of the Cmake files
45+
//! for Rust on Zephyr may provide assistance with this, but for now, this does limit a given
46+
//! application to running on a specific architecture. For using the `executor-zephyr` feature
47+
//! provided by this module, it easier to allow the code to run on multiple platforms.
48+
//!
49+
//! The following features in the `zephyr` crate configure what is supported:
50+
//!
51+
//! - **`executor-zephyr`**: This implements an executor that uses Zephyr's thread primitives
52+
//! (`k_thread_suspend` and `k_thread_resume`) to suspend the executor thread when there is no work
53+
//! to perform. This feature is incompatible with either `embassy-thread` or `embassy-interrupt`
54+
//! in the `embassy-executor` crate.
55+
//! - **`embassy-time-driver`**: This feature causes the `zephyr` crate to provide a time driver to
56+
//! Embassy. This driver uses a single `k_timer` in Zephyr to wake async operations that are
57+
//! dependent on time. This enables the `embassy-time` crate's functionality to be used freely
58+
//! within async tasks on Zephyr.
59+
//!
60+
//! Future versions of this support will provide async interfaces to various driver systems in
61+
//! Zephyr, allowing the use of Zephyr drivers freely from async code.
62+
//!
63+
//! It is perfectly permissible to use the `executor-thread` feature from embassy-executor on
64+
//! Zephyr, within the following guidelines:
65+
//!
66+
//! - The executor is incompatible with the async executor provided within [`crate::kio`], and
67+
//! because there are no features to enable this, this functions will still be accessible. Be
68+
//! careful. You should enable `no-kio` in the zephyr crate to hide these functions.
69+
//! - This executor does not coordinate with the scheduler on Zephyr, but uses an
70+
//! architecture-specific mechanmism when there is no work. On Cortex-M, this is the 'wfe'
71+
//! instruction, on riscv32, the 'wfi' instruction. This means that no tasks of lower priority
72+
//! will ever run, so this should only be started from the lowest priority task on the system.
73+
//! - Because the 'idle' thread in Zephyr will never run, some platforms will not enter low power
74+
//! mode, when the system is idle. This is very platform specific.
75+
//!
76+
//! ## Caveats
77+
//!
78+
//! The executor provided by Embassy is fundamentally incompatible with the executor provided by
79+
//! this crate's [`crate::kio`] and [`crate::work::futures`]. Trying to use the functionality
80+
//! provided by operations, such as [`Semaphore::take_async`], will generally result in a panic.
81+
//! These routines are conditionally compiled out when `executor-zephyr` is enabled, but there is no
82+
//! way for this crate to detect the use of embassy's `executor-threaded`. Combining these will
83+
//! result in undefined behavior, likely difficult to debug crashes.
84+
//!
85+
//! [`Semaphore::take_async`]: crate::sys::sync::Semaphore::take_async
86+
87+
#[cfg(feature = "time-driver")]
88+
mod time_driver;

zephyr/src/embassy/time_driver.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! Embassy time driver for Zephyr.
2+
//!
3+
//! Implements the time driver for Embassy using a `k_timer` in Zephyr.
4+
5+
use core::{cell::{RefCell, UnsafeCell}, mem};
6+
7+
use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex};
8+
use embassy_time_driver::Driver;
9+
use embassy_time_queue_utils::Queue;
10+
11+
use crate::raw::{
12+
k_timer,
13+
k_timer_init,
14+
k_timer_start,
15+
k_timeout_t,
16+
};
17+
use crate::sys::K_FOREVER;
18+
19+
embassy_time_driver::time_driver_impl!(static DRIVER: ZephyrTimeDriver = ZephyrTimeDriver {
20+
queue: Mutex::new(RefCell::new(Queue::new())),
21+
timer: Mutex::new(RefCell::new(unsafe { mem::zeroed() })),
22+
});
23+
24+
struct ZephyrTimeDriver {
25+
queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
26+
timer: Mutex<CriticalSectionRawMutex, RefCell<ZTimer>>,
27+
}
28+
29+
/// A wrapper around `k_timer`. In this case, the implementation is a little simpler than the one
30+
/// in the timer module, as we are always called from within a critical section.
31+
struct ZTimer {
32+
item: UnsafeCell<k_timer>,
33+
initialized: bool,
34+
}
35+
36+
impl ZTimer {
37+
fn set_alarm(&mut self, next: u64, now: u64) -> bool {
38+
if next <= now {
39+
return false;
40+
}
41+
42+
// Otherwise, initialize our timer, and handle it.
43+
if !self.initialized {
44+
unsafe { k_timer_init(self.item.get(), Some(Self::timer_tick), None); }
45+
self.initialized = true;
46+
}
47+
48+
// There is a +1 here as the `k_timer_start()` for historical reasons, subtracts one from
49+
// the time, effectively rounding down, whereas we want to wait at least long enough.
50+
let delta = k_timeout_t { ticks: (next - now + 1) as i64 };
51+
let period = K_FOREVER;
52+
unsafe { k_timer_start(self.item.get(), delta, period); }
53+
54+
true
55+
}
56+
57+
unsafe extern "C" fn timer_tick(_k_timer: *mut k_timer) {
58+
DRIVER.check_alarm();
59+
}
60+
}
61+
62+
impl Driver for ZephyrTimeDriver {
63+
fn now(&self) -> u64 {
64+
crate::time::now().ticks()
65+
}
66+
67+
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
68+
critical_section::with(|cs| {
69+
let mut queue = self.queue.borrow(cs).borrow_mut();
70+
let mut timer = self.timer.borrow(cs).borrow_mut();
71+
72+
if queue.schedule_wake(at, waker) {
73+
let mut next = queue.next_expiration(self.now());
74+
while !timer.set_alarm(next, self.now()) {
75+
next = queue.next_expiration(self.now());
76+
}
77+
}
78+
})
79+
}
80+
}
81+
82+
impl ZephyrTimeDriver {
83+
fn check_alarm(&self) {
84+
critical_section::with(|cs| {
85+
let mut queue = self.queue.borrow(cs).borrow_mut();
86+
let mut timer = self.timer.borrow(cs).borrow_mut();
87+
88+
let mut next = queue.next_expiration(self.now());
89+
while !timer.set_alarm(next, self.now()) {
90+
next = queue.next_expiration(self.now());
91+
}
92+
})
93+
}
94+
}
95+
96+
// SAFETY: The timer access is always coordinated through a critical section.
97+
unsafe impl Send for ZTimer { }

zephyr/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878

7979
pub mod align;
8080
pub mod device;
81+
pub mod embassy;
8182
pub mod error;
8283
#[cfg(CONFIG_RUST_ALLOC)]
8384
pub mod kio;

0 commit comments

Comments
 (0)