Skip to content

Commit 27a4c53

Browse files
committed
Allow hooking into the executor
1 parent a7798f6 commit 27a4c53

File tree

3 files changed

+87
-28
lines changed

3 files changed

+87
-28
lines changed

esp-hal-embassy/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- `Executor::run_with_callbacks` and the associated `Callbacks` trait (#3737)
1213

1314
### Changed
1415

esp-hal-embassy/src/executor/thread.rs

Lines changed: 85 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use embassy_executor::Spawner;
66
#[cfg(all(low_power_wait, multi_core))]
77
use esp_hal::interrupt::software::SoftwareInterrupt;
88
use esp_hal::{interrupt::Priority, system::Cpu};
9-
#[cfg(low_power_wait)]
109
use portable_atomic::{AtomicBool, Ordering};
1110

1211
use super::InnerExecutor;
@@ -15,28 +14,36 @@ pub(crate) const THREAD_MODE_CONTEXT: usize = 16;
1514

1615
/// global atomic used to keep track of whether there is work to do since sev()
1716
/// is not available on either Xtensa or RISC-V
18-
#[cfg(low_power_wait)]
1917
static SIGNAL_WORK_THREAD_MODE: [AtomicBool; Cpu::COUNT] =
2018
[const { AtomicBool::new(false) }; Cpu::COUNT];
2119

22-
pub(crate) fn pend_thread_mode(_core: usize) {
23-
#[cfg(low_power_wait)]
24-
{
25-
// Signal that there is work to be done.
26-
SIGNAL_WORK_THREAD_MODE[_core].store(true, Ordering::Relaxed);
27-
28-
// If we are pending a task on the current core, we're done. Otherwise, we
29-
// need to make sure the other core wakes up.
30-
#[cfg(multi_core)]
31-
if _core != Cpu::current() as usize {
32-
// We need to clear the interrupt from software. We don't actually
33-
// need it to trigger and run the interrupt handler, we just need to
34-
// kick waiti to return.
35-
unsafe { SoftwareInterrupt::<3>::steal().raise() };
36-
}
20+
pub(crate) fn pend_thread_mode(core: usize) {
21+
// Signal that there is work to be done.
22+
SIGNAL_WORK_THREAD_MODE[core].store(true, Ordering::Relaxed);
23+
24+
// If we are pending a task on the current core, we're done. Otherwise, we
25+
// need to make sure the other core wakes up.
26+
#[cfg(all(low_power_wait, multi_core))]
27+
if core != Cpu::current() as usize {
28+
// We need to clear the interrupt from software. We don't actually
29+
// need it to trigger and run the interrupt handler, we just need to
30+
// kick waiti to return.
31+
unsafe { SoftwareInterrupt::<3>::steal().raise() };
3732
}
3833
}
3934

35+
/// Callbacks to run code before/after polling the task queue.
36+
pub trait Callbacks {
37+
/// Called just before polling the executor.
38+
fn before_poll(&mut self);
39+
40+
/// Called after the executor is polled, if there is no work scheduled.
41+
///
42+
/// Note that tasks can become ready at any point during the execution
43+
/// of this function.
44+
fn on_idle(&mut self);
45+
}
46+
4047
/// Thread mode executor.
4148
///
4249
/// This is the simplest and most common kind of executor. It runs on thread
@@ -49,6 +56,7 @@ create one instance per core. The executors don't steal tasks from each other."
4956
)]
5057
pub struct Executor {
5158
inner: InnerExecutor,
59+
cpu: Cpu,
5260
not_send: PhantomData<*mut ()>,
5361
}
5462

@@ -61,14 +69,16 @@ impl Executor {
6169
This will use software-interrupt 3 which isn't available for anything else to wake the other core(s)."#
6270
)]
6371
pub fn new() -> Self {
72+
let cpu = Cpu::current();
6473
Self {
6574
inner: InnerExecutor::new(
6675
// Priority 1 means the timer queue can be accessed at interrupt priority 1 - for
6776
// the thread mode executor it needs to be one higher than the base run level, to
6877
// allow alarm interrupts to be handled.
6978
Priority::Priority1,
70-
(THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut (),
79+
(THREAD_MODE_CONTEXT + cpu as usize) as *mut (),
7180
),
81+
cpu,
7282
not_send: PhantomData,
7383
}
7484
}
@@ -94,6 +104,58 @@ This will use software-interrupt 3 which isn't available for anything else to wa
94104
///
95105
/// This function never returns.
96106
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
107+
struct NoHooks(usize);
108+
109+
impl Callbacks for NoHooks {
110+
fn before_poll(&mut self) {
111+
#[cfg(low_power_wait)]
112+
SIGNAL_WORK_THREAD_MODE[self.0].store(false, Ordering::Relaxed);
113+
}
114+
115+
fn on_idle(&mut self) {}
116+
}
117+
118+
self.run_inner(init, NoHooks(self.cpu as usize))
119+
}
120+
121+
/// Run the executor with callbacks.
122+
///
123+
/// See [Callbacks] on when the callbacks are called.
124+
///
125+
/// See [Self::run] for more information about running the executor.
126+
///
127+
/// This function never returns.
128+
pub fn run_with_callbacks(
129+
&'static mut self,
130+
init: impl FnOnce(Spawner),
131+
callbacks: impl Callbacks,
132+
) -> ! {
133+
struct Hooks<'a, CB: Callbacks>(CB, &'a AtomicBool);
134+
135+
impl<CB: Callbacks> Callbacks for Hooks<'_, CB> {
136+
fn before_poll(&mut self) {
137+
// Clear the flag unconditionally since we'll use it to decide
138+
// if on_idle should be called.
139+
self.1.store(false, Ordering::Relaxed);
140+
141+
self.0.before_poll()
142+
}
143+
144+
fn on_idle(&mut self) {
145+
// Make sure we only call on_idle if the executor would otherwise go to sleep.
146+
if !self.1.load(Ordering::Acquire) {
147+
self.0.on_idle();
148+
}
149+
}
150+
}
151+
152+
self.run_inner(
153+
init,
154+
Hooks(callbacks, &SIGNAL_WORK_THREAD_MODE[self.cpu as usize]),
155+
)
156+
}
157+
158+
fn run_inner(&'static mut self, init: impl FnOnce(Spawner), mut hooks: impl Callbacks) -> ! {
97159
#[cfg(all(multi_core, low_power_wait))]
98160
unwrap!(esp_hal::interrupt::enable(
99161
esp_hal::peripherals::Interrupt::FROM_CPU_INTR3,
@@ -104,14 +166,15 @@ This will use software-interrupt 3 which isn't available for anything else to wa
104166

105167
init(self.inner.inner.spawner());
106168

107-
#[cfg(low_power_wait)]
108-
let cpu = Cpu::current() as usize;
109-
110169
loop {
170+
hooks.before_poll();
171+
111172
unsafe { self.inner.inner.poll() };
112173

174+
hooks.on_idle();
175+
113176
#[cfg(low_power_wait)]
114-
Self::wait_impl(cpu);
177+
Self::wait_impl(self.cpu as usize);
115178
}
116179
}
117180

@@ -154,8 +217,6 @@ This will use software-interrupt 3 which isn't available for anything else to wa
154217
_ => unsafe { core::arch::asm!("waiti 5") },
155218
}
156219
}
157-
// If this races and some waker sets the signal, we'll reset it, but still poll.
158-
SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::Relaxed);
159220
}
160221

161222
#[cfg(all(riscv, low_power_wait))]
@@ -169,9 +230,6 @@ This will use software-interrupt 3 which isn't available for anything else to wa
169230
unsafe { core::arch::asm!("wfi") };
170231
}
171232
});
172-
// if an interrupt occurred while waiting, it will be serviced here
173-
// If this races and some waker sets the signal, we'll reset it, but still poll.
174-
SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::Relaxed);
175233
}
176234
}
177235

esp-hal-embassy/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use esp_hal::timer::{AnyTimer, timg::Timer as TimgTimer};
5656
pub use macros::embassy_main as main;
5757

5858
#[cfg(feature = "executors")]
59-
pub use self::executor::{Executor, InterruptExecutor};
59+
pub use self::executor::{Callbacks, Executor, InterruptExecutor};
6060
use self::time_driver::{EmbassyTimer, Timer};
6161

6262
#[cfg(feature = "executors")]

0 commit comments

Comments
 (0)