aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor/src
diff options
context:
space:
mode:
authorDániel Buga <[email protected]>2023-08-14 15:35:22 +0200
committerDániel Buga <[email protected]>2023-08-14 15:41:53 +0200
commit986a63ebb8611a4dc7c6b14e03146286942ec8e7 (patch)
treef6f963c33fde5397eeeb714d3823a5db089c5991 /embassy-executor/src
parent4c4b12c307bf77516299eb73f9da00ef777b9814 (diff)
Remove the non-specific thread-mode executor
Diffstat (limited to 'embassy-executor/src')
-rw-r--r--embassy-executor/src/arch/cortex_m.rs286
-rw-r--r--embassy-executor/src/arch/riscv32.rs84
-rw-r--r--embassy-executor/src/arch/std.rs68
-rw-r--r--embassy-executor/src/arch/xtensa.rs98
-rw-r--r--embassy-executor/src/interrupt.rs2
-rw-r--r--embassy-executor/src/lib.rs4
-rw-r--r--embassy-executor/src/thread.rs87
7 files changed, 361 insertions, 268 deletions
diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs
index 439db0fc0..2ed70dd1e 100644
--- a/embassy-executor/src/arch/cortex_m.rs
+++ b/embassy-executor/src/arch/cortex_m.rs
@@ -1,122 +1,236 @@
1#[cfg(feature = "executor-thread")] 1const THREAD_PENDER: usize = usize::MAX;
2pub use thread::*;
3
4use crate::raw::PenderContext;
5
6#[cfg(feature = "executor-interrupt")]
7
8/// # Safety
9///
10/// `irq` must be a valid interrupt request number
11unsafe fn nvic_pend(irq: u16) {
12 use cortex_m::interrupt::InterruptNumber;
13
14 #[derive(Clone, Copy)]
15 struct Irq(u16);
16 unsafe impl InterruptNumber for Irq {
17 fn number(self) -> u16 {
18 self.0
19 }
20 }
21 2
22 let irq = Irq(irq);
23
24 // STIR is faster, but is only available in v7 and higher.
25 #[cfg(not(armv6m))]
26 {
27 let mut nvic: cortex_m::peripheral::NVIC = unsafe { core::mem::transmute(()) };
28 nvic.request(irq);
29 }
30
31 #[cfg(armv6m)]
32 cortex_m::peripheral::NVIC::pend(irq);
33}
34
35#[cfg(all(feature = "executor-thread", feature = "executor-interrupt"))]
36#[export_name = "__pender"] 3#[export_name = "__pender"]
37fn __pender(context: PenderContext) { 4#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
5fn __pender(context: crate::raw::PenderContext) {
38 unsafe { 6 unsafe {
39 let context: usize = core::mem::transmute(context);
40 // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt 7 // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt
41 // request number given to `InterruptExecutor::start`. 8 // request number given to `InterruptExecutor::start`.
42 if context as usize == usize::MAX {
43 core::arch::asm!("sev")
44 } else {
45 nvic_pend(context as u16)
46 }
47 }
48}
49
50#[cfg(all(feature = "executor-thread", not(feature = "executor-interrupt")))]
51#[export_name = "__pender"]
52fn __pender(_context: PenderContext) {
53 unsafe { core::arch::asm!("sev") }
54}
55 9
56#[cfg(all(not(feature = "executor-thread"), feature = "executor-interrupt"))]
57#[export_name = "__pender"]
58fn __pender(context: PenderContext) {
59 unsafe {
60 let context: usize = core::mem::transmute(context); 10 let context: usize = core::mem::transmute(context);
61 // Safety: `context` is the same value we passed to `InterruptExecutor::start`, which must 11
62 // be a valid interrupt request number. 12 #[cfg(feature = "executor-thread")]
63 nvic_pend(context as u16) 13 if context == THREAD_PENDER {
14 core::arch::asm!("sev");
15 return;
16 }
17
18 #[cfg(feature = "executor-interrupt")]
19 {
20 use cortex_m::interrupt::InterruptNumber;
21 use cortex_m::peripheral::NVIC;
22
23 #[derive(Clone, Copy)]
24 struct Irq(u16);
25 unsafe impl InterruptNumber for Irq {
26 fn number(self) -> u16 {
27 self.0
28 }
29 }
30
31 let irq = Irq(context as u16);
32
33 // STIR is faster, but is only available in v7 and higher.
34 #[cfg(not(armv6m))]
35 {
36 let mut nvic: NVIC = core::mem::transmute(());
37 nvic.request(irq);
38 }
39
40 #[cfg(armv6m)]
41 NVIC::pend(irq);
42 }
64 } 43 }
65} 44}
66 45
67#[cfg(feature = "executor-thread")] 46#[cfg(feature = "executor-thread")]
47pub use thread::*;
48#[cfg(feature = "executor-thread")]
68mod thread { 49mod thread {
50 use core::arch::asm;
51 use core::marker::PhantomData;
69 52
70 #[cfg(feature = "nightly")] 53 #[cfg(feature = "nightly")]
71 pub use embassy_macros::main_cortex_m as main; 54 pub use embassy_macros::main_cortex_m as main;
72 55
73 use crate::raw::PenderContext; 56 use crate::arch::THREAD_PENDER;
74 use crate::thread::ThreadContext; 57 use crate::{raw, Spawner};
75 58
76 /// TODO 59 /// Thread mode executor, using WFE/SEV.
77 // Name pending 60 ///
78 #[derive(Default)] // Default enables Executor::new 61 /// This is the simplest and most common kind of executor. It runs on
79 pub struct Context; 62 /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
63 /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
64 /// is executed, to make the `WFE` exit from sleep and poll the task.
65 ///
66 /// This executor allows for ultra low power consumption for chips where `WFE`
67 /// triggers low-power sleep without extra steps. If your chip requires extra steps,
68 /// you may use [`raw::Executor`] directly to program custom behavior.
69 pub struct Executor {
70 inner: raw::Executor,
71 not_send: PhantomData<*mut ()>,
72 }
80 73
81 impl ThreadContext for Context { 74 impl Executor {
82 fn context(&self) -> PenderContext { 75 /// Create a new Executor.
83 unsafe { core::mem::transmute(usize::MAX) } 76 pub fn new() -> Self {
77 Self {
78 inner: raw::Executor::new(unsafe { core::mem::transmute(THREAD_PENDER) }),
79 not_send: PhantomData,
80 }
84 } 81 }
85 82
86 fn wait(&mut self) { 83 /// Run the executor.
87 unsafe { core::arch::asm!("wfe") } 84 ///
85 /// The `init` closure is called with a [`Spawner`] that spawns tasks on
86 /// this executor. Use it to spawn the initial task(s). After `init` returns,
87 /// the executor starts running the tasks.
88 ///
89 /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
90 /// for example by passing it as an argument to the initial tasks.
91 ///
92 /// This function requires `&'static mut self`. This means you have to store the
93 /// Executor instance in a place where it'll live forever and grants you mutable
94 /// access. There's a few ways to do this:
95 ///
96 /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
97 /// - a `static mut` (unsafe)
98 /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
99 ///
100 /// This function never returns.
101 pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
102 init(self.inner.spawner());
103
104 loop {
105 unsafe {
106 self.inner.poll();
107 asm!("wfe");
108 };
109 }
88 } 110 }
89 } 111 }
90
91 /// TODO
92 // Type alias for backwards compatibility
93 pub type Executor = crate::thread::ThreadModeExecutor<Context>;
94} 112}
95 113
96#[cfg(feature = "executor-interrupt")] 114#[cfg(feature = "executor-interrupt")]
97pub use interrupt::*; 115pub use interrupt::*;
98#[cfg(feature = "executor-interrupt")] 116#[cfg(feature = "executor-interrupt")]
99mod interrupt { 117mod interrupt {
118 use core::cell::UnsafeCell;
119 use core::mem::MaybeUninit;
120
121 use atomic_polyfill::{AtomicBool, Ordering};
100 use cortex_m::interrupt::InterruptNumber; 122 use cortex_m::interrupt::InterruptNumber;
101 use cortex_m::peripheral::NVIC; 123 use cortex_m::peripheral::NVIC;
102 124
103 use crate::interrupt::InterruptContext; 125 use crate::raw;
104 use crate::raw::PenderContext; 126
127 /// Interrupt mode executor.
128 ///
129 /// This executor runs tasks in interrupt mode. The interrupt handler is set up
130 /// to poll tasks, and when a task is woken the interrupt is pended from software.
131 ///
132 /// This allows running async tasks at a priority higher than thread mode. One
133 /// use case is to leave thread mode free for non-async tasks. Another use case is
134 /// to run multiple executors: one in thread mode for low priority tasks and another in
135 /// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
136 /// priority ones.
137 ///
138 /// It is even possible to run multiple interrupt mode executors at different priorities,
139 /// by assigning different priorities to the interrupts. For an example on how to do this,
140 /// See the 'multiprio' example for 'embassy-nrf'.
141 ///
142 /// To use it, you have to pick an interrupt that won't be used by the hardware.
143 /// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI).
144 /// If this is not the case, you may use an interrupt from any unused peripheral.
145 ///
146 /// It is somewhat more complex to use, it's recommended to use the thread-mode
147 /// [`Executor`] instead, if it works for your use case.
148 pub struct InterruptExecutor {
149 started: AtomicBool,
150 executor: UnsafeCell<MaybeUninit<raw::Executor>>,
151 }
152
153 unsafe impl Send for InterruptExecutor {}
154 unsafe impl Sync for InterruptExecutor {}
155
156 impl InterruptExecutor {
157 /// Create a new, not started `InterruptExecutor`.
158 #[inline]
159 pub const fn new() -> Self {
160 Self {
161 started: AtomicBool::new(false),
162 executor: UnsafeCell::new(MaybeUninit::uninit()),
163 }
164 }
105 165
106 impl<T> InterruptContext for T 166 /// Executor interrupt callback.
107 where 167 ///
108 T: InterruptNumber, 168 /// # Safety
109 { 169 ///
110 fn context(&self) -> PenderContext { 170 /// You MUST call this from the interrupt handler, and from nowhere else.
111 unsafe { core::mem::transmute(self.number() as usize) } 171 pub unsafe fn on_interrupt(&'static self) {
172 let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
173 executor.poll();
112 } 174 }
113 175
114 fn enable(&self) { 176 /// Start the executor.
115 unsafe { NVIC::unmask(*self) } 177 ///
178 /// This initializes the executor, enables the interrupt, and returns.
179 /// The executor keeps running in the background through the interrupt.
180 ///
181 /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
182 /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a
183 /// different "thread" (the interrupt), so spawning tasks on it is effectively
184 /// sending them.
185 ///
186 /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
187 /// a task running in it.
188 ///
189 /// # Interrupt requirements
190 ///
191 /// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt).
192 ///
193 /// This method already enables (unmasks) the interrupt, you must NOT do it yourself.
194 ///
195 /// You must set the interrupt priority before calling this method. You MUST NOT
196 /// do it after.
197 ///
198 pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
199 if self
200 .started
201 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
202 .is_err()
203 {
204 panic!("InterruptExecutor::start() called multiple times on the same executor.");
205 }
206
207 unsafe {
208 let context = core::mem::transmute(irq.number() as usize);
209 (&mut *self.executor.get())
210 .as_mut_ptr()
211 .write(raw::Executor::new(context))
212 }
213
214 let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
215
216 unsafe { NVIC::unmask(irq) }
217
218 executor.spawner().make_send()
116 } 219 }
117 }
118 220
119 /// TODO 221 /// Get a SendSpawner for this executor
120 // Type alias for backwards compatibility 222 ///
121 pub type InterruptExecutor = crate::interrupt::InterruptModeExecutor; 223 /// This returns a [`SendSpawner`] you can use to spawn tasks on this
224 /// executor.
225 ///
226 /// This MUST only be called on an executor that has already been spawned.
227 /// The function will panic otherwise.
228 pub fn spawner(&'static self) -> crate::SendSpawner {
229 if !self.started.load(Ordering::Acquire) {
230 panic!("InterruptExecutor::spawner() called on uninitialized executor.");
231 }
232 let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
233 executor.spawner().make_send()
234 }
235 }
122} 236}
diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs
index f76a4bcf6..551d7527f 100644
--- a/embassy-executor/src/arch/riscv32.rs
+++ b/embassy-executor/src/arch/riscv32.rs
@@ -5,53 +5,77 @@ compile_error!("`executor-interrupt` is not supported with `arch-riscv32`.");
5pub use thread::*; 5pub use thread::*;
6#[cfg(feature = "executor-thread")] 6#[cfg(feature = "executor-thread")]
7mod thread { 7mod thread {
8 use core::marker::PhantomData;
8 use core::sync::atomic::{AtomicBool, Ordering}; 9 use core::sync::atomic::{AtomicBool, Ordering};
9 10
10 #[cfg(feature = "nightly")] 11 #[cfg(feature = "nightly")]
11 pub use embassy_macros::main_riscv as main; 12 pub use embassy_macros::main_riscv as main;
12 13
13 use crate::raw::PenderContext; 14 use crate::{raw, Spawner};
14 use crate::thread::ThreadContext;
15 15
16 /// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV 16 /// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV
17 static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); 17 static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
18 18
19 #[export_name = "__pender"] 19 #[export_name = "__pender"]
20 fn __thread_mode_pender(_context: PenderContext) { 20 fn __thread_mode_pender(_context: crate::raw::PenderContext) {
21 SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); 21 SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
22 } 22 }
23 23
24 /// TODO 24 /// RISCV32 Executor
25 // Name pending 25 pub struct Executor {
26 #[derive(Default)] // Default enables Executor::new 26 inner: raw::Executor,
27 pub struct Context; 27 not_send: PhantomData<*mut ()>,
28 }
28 29
29 impl ThreadContext for Context { 30 impl Executor {
30 fn context(&self) -> PenderContext { 31 /// Create a new Executor.
31 unsafe { core::mem::transmute(0) } 32 pub fn new() -> Self {
33 Self {
34 inner: raw::Executor::new(unsafe { core::mem::transmute(0) }),
35 not_send: PhantomData,
36 }
32 } 37 }
33 38
34 fn wait(&mut self) { 39 /// Run the executor.
35 // We do not care about race conditions between the load and store operations, 40 ///
36 // interrupts will only set this value to true. 41 /// The `init` closure is called with a [`Spawner`] that spawns tasks on
37 critical_section::with(|_| { 42 /// this executor. Use it to spawn the initial task(s). After `init` returns,
38 // if there is work to do, loop back to polling 43 /// the executor starts running the tasks.
39 // TODO can we relax this? 44 ///
40 if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { 45 /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
41 SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); 46 /// for example by passing it as an argument to the initial tasks.
42 } 47 ///
43 // if not, wait for interrupt 48 /// This function requires `&'static mut self`. This means you have to store the
44 else { 49 /// Executor instance in a place where it'll live forever and grants you mutable
45 unsafe { 50 /// access. There's a few ways to do this:
46 core::arch::asm!("wfi"); 51 ///
47 } 52 /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
53 /// - a `static mut` (unsafe)
54 /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
55 ///
56 /// This function never returns.
57 pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
58 init(self.inner.spawner());
59
60 loop {
61 unsafe {
62 self.inner.poll();
63 // we do not care about race conditions between the load and store operations, interrupts
64 //will only set this value to true.
65 critical_section::with(|_| {
66 // if there is work to do, loop back to polling
67 // TODO can we relax this?
68 if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
69 SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
70 }
71 // if not, wait for interrupt
72 else {
73 core::arch::asm!("wfi");
74 }
75 });
76 // if an interrupt occurred while waiting, it will be serviced here
48 } 77 }
49 }); 78 }
50 // if an interrupt occurred while waiting, it will be serviced here
51 } 79 }
52 } 80 }
53
54 /// TODO
55 // Type alias for backwards compatibility
56 pub type Executor = crate::thread::ThreadModeExecutor<Context>;
57} 81}
diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs
index d55de118d..f490084d6 100644
--- a/embassy-executor/src/arch/std.rs
+++ b/embassy-executor/src/arch/std.rs
@@ -5,44 +5,66 @@ compile_error!("`executor-interrupt` is not supported with `arch-std`.");
5pub use thread::*; 5pub use thread::*;
6#[cfg(feature = "executor-thread")] 6#[cfg(feature = "executor-thread")]
7mod thread { 7mod thread {
8 use std::marker::PhantomData;
8 use std::sync::{Condvar, Mutex}; 9 use std::sync::{Condvar, Mutex};
9 10
10 #[cfg(feature = "nightly")] 11 #[cfg(feature = "nightly")]
11 pub use embassy_macros::main_std as main; 12 pub use embassy_macros::main_std as main;
12 13
13 use crate::raw::PenderContext; 14 use crate::{raw, Spawner};
14 use crate::thread::ThreadContext;
15 15
16 /// TODO 16 #[export_name = "__pender"]
17 // Name pending 17 fn __pender(context: crate::raw::PenderContext) {
18 pub struct Context { 18 let signaler: &'static Signaler = unsafe { std::mem::transmute(context) };
19 signaler.signal()
20 }
21
22 /// Single-threaded std-based executor.
23 pub struct Executor {
24 inner: raw::Executor,
25 not_send: PhantomData<*mut ()>,
19 signaler: &'static Signaler, 26 signaler: &'static Signaler,
20 } 27 }
21 28
22 impl Default for Context { 29 impl Executor {
23 fn default() -> Self { 30 /// Create a new Executor.
31 pub fn new() -> Self {
32 let signaler = &*Box::leak(Box::new(Signaler::new()));
24 Self { 33 Self {
25 signaler: &*Box::leak(Box::new(Signaler::new())), 34 inner: raw::Executor::new(unsafe { std::mem::transmute(signaler) }),
35 not_send: PhantomData,
36 signaler,
26 } 37 }
27 } 38 }
28 }
29 39
30 impl ThreadContext for Context { 40 /// Run the executor.
31 fn context(&self) -> PenderContext { 41 ///
32 unsafe { core::mem::transmute(self.signaler) } 42 /// The `init` closure is called with a [`Spawner`] that spawns tasks on
33 } 43 /// this executor. Use it to spawn the initial task(s). After `init` returns,
44 /// the executor starts running the tasks.
45 ///
46 /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
47 /// for example by passing it as an argument to the initial tasks.
48 ///
49 /// This function requires `&'static mut self`. This means you have to store the
50 /// Executor instance in a place where it'll live forever and grants you mutable
51 /// access. There's a few ways to do this:
52 ///
53 /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
54 /// - a `static mut` (unsafe)
55 /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
56 ///
57 /// This function never returns.
58 pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
59 init(self.inner.spawner());
34 60
35 fn wait(&mut self) { 61 loop {
36 self.signaler.wait() 62 unsafe { self.inner.poll() };
63 self.signaler.wait()
64 }
37 } 65 }
38 } 66 }
39 67
40 #[export_name = "__pender"]
41 fn __pender(context: PenderContext) {
42 let signaler: &'static Signaler = unsafe { std::mem::transmute(context) };
43 signaler.signal()
44 }
45
46 struct Signaler { 68 struct Signaler {
47 mutex: Mutex<bool>, 69 mutex: Mutex<bool>,
48 condvar: Condvar, 70 condvar: Condvar,
@@ -70,8 +92,4 @@ mod thread {
70 self.condvar.notify_one(); 92 self.condvar.notify_one();
71 } 93 }
72 } 94 }
73
74 /// TODO
75 // Type alias for backwards compatibility
76 pub type Executor = crate::thread::ThreadModeExecutor<Context>;
77} 95}
diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs
index 1aea9f230..8665a9cb6 100644
--- a/embassy-executor/src/arch/xtensa.rs
+++ b/embassy-executor/src/arch/xtensa.rs
@@ -8,56 +8,80 @@ mod thread {
8 use core::marker::PhantomData; 8 use core::marker::PhantomData;
9 use core::sync::atomic::{AtomicBool, Ordering}; 9 use core::sync::atomic::{AtomicBool, Ordering};
10 10
11 use crate::raw::PenderContext; 11 use crate::{raw, Spawner};
12 use crate::thread::ThreadContext;
13 12
14 /// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa 13 /// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa
15 static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); 14 static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
16 15
17 #[export_name = "__thread_mode_pender"] 16 #[export_name = "__thread_mode_pender"]
18 fn __thread_mode_pender(_context: PenderContext) { 17 fn __thread_mode_pender(_context: crate::raw::PenderContext) {
19 SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); 18 SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
20 } 19 }
21 20
22 /// TODO 21 /// Xtensa Executor
23 // Name pending 22 pub struct Executor {
24 #[derive(Default)] // Default enables Executor::new 23 inner: raw::Executor,
25 pub struct Context; 24 not_send: PhantomData<*mut ()>,
25 }
26 26
27 impl ThreadContext for Context { 27 impl Executor {
28 fn context(&self) -> PenderContext { 28 /// Create a new Executor.
29 unsafe { core::mem::transmute(0) } 29 pub fn new() -> Self {
30 Self {
31 inner: raw::Executor::new(unsafe { core::mem::transmute(0) }),
32 not_send: PhantomData,
33 }
30 } 34 }
31 35
32 fn wait(&mut self) { 36 /// Run the executor.
33 unsafe { 37 ///
34 // Manual critical section implementation that only masks interrupts handlers. 38 /// The `init` closure is called with a [`Spawner`] that spawns tasks on
35 // We must not acquire the cross-core on dual-core systems because that would 39 /// this executor. Use it to spawn the initial task(s). After `init` returns,
36 // prevent the other core from doing useful work while this core is sleeping. 40 /// the executor starts running the tasks.
37 let token: critical_section::RawRestoreState; 41 ///
38 core::arch::asm!("rsil {0}, 5", out(reg) token); 42 /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
39 43 /// for example by passing it as an argument to the initial tasks.
40 // we do not care about race conditions between the load and store operations, 44 ///
41 // interrupts will only set this value to true. 45 /// This function requires `&'static mut self`. This means you have to store the
42 // if there is work to do, loop back to polling 46 /// Executor instance in a place where it'll live forever and grants you mutable
43 if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { 47 /// access. There's a few ways to do this:
44 SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); 48 ///
45 49 /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
46 core::arch::asm!( 50 /// - a `static mut` (unsafe)
47 "wsr.ps {0}", 51 /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
48 "rsync", in(reg) token) 52 ///
49 } else { 53 /// This function never returns.
50 // waiti sets the PS.INTLEVEL when slipping into sleep 54 pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
51 // because critical sections in Xtensa are implemented via increasing 55 init(self.inner.spawner());
52 // PS.INTLEVEL the critical section ends here 56
53 // take care not add code after `waiti` if it needs to be inside the CS 57 loop {
54 core::arch::asm!("waiti 0"); // critical section ends here 58 unsafe {
59 self.inner.poll();
60
61 // Manual critical section implementation that only masks interrupts handlers.
62 // We must not acquire the cross-core on dual-core systems because that would
63 // prevent the other core from doing useful work while this core is sleeping.
64 let token: critical_section::RawRestoreState;
65 core::arch::asm!("rsil {0}, 5", out(reg) token);
66
67 // we do not care about race conditions between the load and store operations, interrupts
68 // will only set this value to true.
69 // if there is work to do, loop back to polling
70 if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
71 SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
72
73 core::arch::asm!(
74 "wsr.ps {0}",
75 "rsync", in(reg) token)
76 } else {
77 // waiti sets the PS.INTLEVEL when slipping into sleep
78 // because critical sections in Xtensa are implemented via increasing
79 // PS.INTLEVEL the critical section ends here
80 // take care not add code after `waiti` if it needs to be inside the CS
81 core::arch::asm!("waiti 0"); // critical section ends here
82 }
55 } 83 }
56 } 84 }
57 } 85 }
58 } 86 }
59
60 /// TODO
61 // Type alias for backwards compatibility
62 pub type Executor = crate::thread::ThreadModeExecutor<Context>;
63} 87}
diff --git a/embassy-executor/src/interrupt.rs b/embassy-executor/src/interrupt.rs
index 28a1cd525..b68754ab2 100644
--- a/embassy-executor/src/interrupt.rs
+++ b/embassy-executor/src/interrupt.rs
@@ -41,7 +41,7 @@ pub trait InterruptContext {
41/// If this is not the case, you may use an interrupt from any unused peripheral. 41/// If this is not the case, you may use an interrupt from any unused peripheral.
42/// 42///
43/// It is somewhat more complex to use, it's recommended to use the 43/// It is somewhat more complex to use, it's recommended to use the
44/// [`crate::thread::ThreadModeExecutor`] instead, if it works for your use case. 44/// thread-mode executor instead, if it works for your use case.
45pub struct InterruptModeExecutor { 45pub struct InterruptModeExecutor {
46 started: AtomicBool, 46 started: AtomicBool,
47 executor: UnsafeCell<MaybeUninit<raw::Executor>>, 47 executor: UnsafeCell<MaybeUninit<raw::Executor>>,
diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs
index ca67c9484..3be32d9c5 100644
--- a/embassy-executor/src/lib.rs
+++ b/embassy-executor/src/lib.rs
@@ -39,8 +39,8 @@ pub mod raw;
39 39
40#[cfg(feature = "executor-interrupt")] 40#[cfg(feature = "executor-interrupt")]
41pub mod interrupt; 41pub mod interrupt;
42#[cfg(feature = "executor-thread")] 42#[cfg(feature = "executor-interrupt")]
43pub mod thread; 43pub use interrupt::*;
44 44
45mod spawner; 45mod spawner;
46pub use spawner::*; 46pub use spawner::*;
diff --git a/embassy-executor/src/thread.rs b/embassy-executor/src/thread.rs
deleted file mode 100644
index 8ff4071da..000000000
--- a/embassy-executor/src/thread.rs
+++ /dev/null
@@ -1,87 +0,0 @@
1//! Thread-mode executor.
2
3use core::marker::PhantomData;
4
5use crate::raw::{self, PenderContext};
6use crate::Spawner;
7
8/// Architecture-specific interface for a thread-mode executor. This trait describes what the
9/// executor should do when idle, and what data should be passed to its pender.
10// TODO: Name pending
11pub trait ThreadContext: Sized {
12 /// A pointer-sized piece of data that is passed to the pender function.
13 ///
14 /// For example, on multi-core systems, this can be used to store the ID of the core that
15 /// should be woken up.
16 fn context(&self) -> PenderContext;
17
18 /// Waits for the executor to be waken.
19 ///
20 /// While it is valid for this function can be empty, it is recommended to use a WFE instruction
21 /// or equivalent to let the CPU sleep.
22 fn wait(&mut self);
23}
24
25/// Thread mode executor, using WFE/SEV.
26///
27/// This is the simplest and most common kind of executor. It runs on
28/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
29/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
30/// is executed, to make the `WFE` exit from sleep and poll the task.
31///
32/// This executor allows for ultra low power consumption for chips where `WFE`
33/// triggers low-power sleep without extra steps. If your chip requires extra steps,
34/// you may use [`raw::Executor`] directly to program custom behavior.
35pub struct ThreadModeExecutor<C: ThreadContext> {
36 inner: raw::Executor,
37 context: C,
38 not_send: PhantomData<*mut ()>,
39}
40
41impl<C: ThreadContext> ThreadModeExecutor<C> {
42 /// Create a new Executor.
43 pub fn new() -> Self
44 where
45 C: Default,
46 {
47 Self::with_context(C::default())
48 }
49
50 /// Create a new Executor using the given thread context.
51 pub fn with_context(context: C) -> Self {
52 Self {
53 inner: raw::Executor::new(context.context()),
54 context,
55 not_send: PhantomData,
56 }
57 }
58
59 /// Run the executor.
60 ///
61 /// The `init` closure is called with a [`Spawner`] that spawns tasks on
62 /// this executor. Use it to spawn the initial task(s). After `init` returns,
63 /// the executor starts running the tasks.
64 ///
65 /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
66 /// for example by passing it as an argument to the initial tasks.
67 ///
68 /// This function requires `&'static mut self`. This means you have to store the
69 /// Executor instance in a place where it'll live forever and grants you mutable
70 /// access. There's a few ways to do this:
71 ///
72 /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
73 /// - a `static mut` (unsafe)
74 /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
75 ///
76 /// This function never returns.
77 pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
78 init(self.inner.spawner());
79
80 loop {
81 unsafe {
82 self.inner.poll();
83 self.context.wait();
84 };
85 }
86 }
87}