rpi_hal_embassy/executor.rs
1//! Thread-mode executor, idling in `wfe` and woken by `sev`.
2//!
3//! `embassy-executor`'s scheduler is portable; what it needs per platform
4//! is two things — a `__pender` that makes an idle core look at the run
5//! queue again, and an idle instruction to spend the time in between.
6//! This module supplies both, identically for AArch32 and AArch64.
7//!
8//! # Why not `embassy-executor`'s own backend
9//!
10//! `embassy-executor` has a `platform-cortex-ar` feature whose executor is
11//! `sev` to pend and `wfe` to idle — exactly the sequence below, which is
12//! good evidence this is the blessed approach for the architecture rather
13//! than an invention here. It is AArch32 only, though (it depends on
14//! `aarch32-cpu`), and there is no AArch64 backend, which would leave the
15//! 64-bit build on `platform-spin`: a busy-loop that never idles the CPU
16//! at all.
17//!
18//! Two executors with two different idle behaviors, in a crate whose point
19//! is that both word sizes behave the same, is a worse trade than the
20//! sixty lines here. `embassy_executor::raw` is public precisely so that a
21//! HAL can do this.
22//!
23//! # Using it
24//!
25//! The executor has to outlive `run`, which is why it takes
26//! `&'static mut self`. `static_cell` is the usual way to get one safely;
27//! a `fn kmain() -> !` that never returns can also transmute a local's
28//! lifetime, which is what `#[embassy_executor::main]` expands to.
29//!
30//! That attribute works here, in the form
31//! `#[embassy_executor::main(executor = "rpi_hal_embassy::Executor")]` —
32//! with no `platform-*` feature selected it resolves to the macro's
33//! architecture-agnostic variant, which takes the executor path as an
34//! argument. It also emits a plain `fn main`, though, and `rpi-hal`'s boot
35//! code calls `kmain`, so it needs an `entry` attribute macro that
36//! `rpi-hal` does not currently provide. Until then, call
37//! [`Executor::run`](crate::executor::Executor::run) from `kmain`
38//! directly — which is all the macro does anyway.
39
40use core::marker::PhantomData;
41
42use embassy_executor::{Spawner, raw};
43
44/// Wakes a core that may be sitting in `wfe`.
45///
46/// `embassy-executor` calls this after putting a task on the run queue.
47/// The context pointer distinguishes executors when there is more than
48/// one; with a single thread-mode executor there is nothing to
49/// distinguish, so it is ignored.
50#[unsafe(export_name = "__pender")]
51fn __pender(_context: *mut ()) {
52 // SAFETY: `dsb`/`sev` touch no memory and have no operands; both are
53 // unprivileged and valid on ARMv6K, ARMv7-A and ARMv8-A alike.
54 //
55 // `dsb ish` before `sev` is ARM's prescribed order for signalling
56 // another core: the run-queue stores this event is announcing have to
57 // be observable before the event that sends someone looking for them.
58 // Strictly redundant while only one core runs an executor — program
59 // order already covers a core waking itself — but this is the
60 // sequence that stays correct when a second one does, and it is
61 // cheap next to the queue's own atomics.
62 #[cfg(not(armv6))]
63 unsafe {
64 core::arch::asm!("dsb ish", "sev", options(nomem, nostack))
65 };
66
67 // ARMv6 (the ARM1176 in a Pi 1 or Pi Zero) has the same barrier only
68 // as a CP15 operation, and only in the full-system flavour — there are
69 // no domain-qualified forms to narrow it to the inner-shareable
70 // domain, so this is a superset of what the line above asks for. `sev`
71 // itself is unchanged.
72 //
73 // `cfg(armv6)` comes from `build.rs`, which derives it from the
74 // target triple — see there for why the obvious `target_feature =
75 // "v7"` test cannot be used, in source or out of it.
76 #[cfg(armv6)]
77 unsafe {
78 core::arch::asm!(
79 "mcr p15, 0, {0}, c7, c10, 4",
80 "sev",
81 in(reg) 0u32,
82 options(nomem, nostack),
83 )
84 };
85}
86
87/// Thread-mode executor: runs tasks on the main context, and idles the
88/// core in `wfe` when none are ready.
89///
90/// `wfe` parks the core until an event or an interrupt arrives, so an
91/// idle program draws no more power than a `wfi` loop — unlike a spinning
92/// executor. Interrupts wake it regardless of the event register, which
93/// is what lets the timer interrupt drive `embassy-time` deadlines.
94///
95/// There is no lost-wake-up race between polling and sleeping. `sev` sets
96/// a sticky per-core event register, so a wake-up that lands in the window
97/// after the last `poll` and before the `wfe` makes that `wfe` return
98/// immediately rather than sleeping through it.
99pub struct Executor {
100 inner: raw::Executor,
101 /// The executor must stay on the core that created it: `run` polls
102 /// from one context, and the pender's `wfe`/`sev` pairing is per-core.
103 not_send: PhantomData<*mut ()>,
104}
105
106// `Default` is not offered alongside `new`: an `Executor` is only usable
107// through `&'static mut`, so a value produced by `Default::default()` in
108// an expression position could never be run, and offering it would suggest
109// otherwise.
110#[allow(clippy::new_without_default)]
111impl Executor {
112 /// Creates an executor with no tasks.
113 pub fn new() -> Self {
114 Self {
115 // No context to pass: this crate has one pender and one
116 // executor, so there is nothing for the pointer to select
117 // between. Per-core executors would give each its own.
118 inner: raw::Executor::new(core::ptr::null_mut()),
119 not_send: PhantomData,
120 }
121 }
122
123 /// Spawns the initial tasks and then runs them forever.
124 ///
125 /// `init` receives a [`Spawner`] for this executor and is called once,
126 /// before the first poll; keep a copy of the `Spawner` (it is `Copy`)
127 /// to spawn more later from inside a task.
128 ///
129 /// Never returns. Between polls the core sits in `wfe`, so anything
130 /// that should wake it — including the `embassy-time` deadline
131 /// interrupt — must be enabled before this is called.
132 pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
133 init(self.inner.spawner());
134
135 loop {
136 // SAFETY: `poll` must not be re-entered. This is the only call
137 // site, it is not reachable from a task, and `Executor` is
138 // `!Send`, so no other core can be polling this one.
139 unsafe { self.inner.poll() };
140
141 // SAFETY: `wfe` is unprivileged and touches no memory. A
142 // pending event or interrupt makes it return at once, so this
143 // cannot park a core that has work waiting.
144 unsafe { core::arch::asm!("wfe", options(nomem, nostack)) };
145 }
146 }
147}