zephyr/
embassy.rs

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

#[cfg(feature = "time-driver")]
mod time_driver;

#[cfg(feature = "executor-zephyr")]
pub use executor::Executor;
#[cfg(feature = "executor-zephyr")]
mod executor;