diff options
| author | Dániel Buga <[email protected]> | 2023-08-14 15:35:22 +0200 |
|---|---|---|
| committer | Dániel Buga <[email protected]> | 2023-08-14 15:41:53 +0200 |
| commit | 986a63ebb8611a4dc7c6b14e03146286942ec8e7 (patch) | |
| tree | f6f963c33fde5397eeeb714d3823a5db089c5991 | |
| parent | 4c4b12c307bf77516299eb73f9da00ef777b9814 (diff) | |
Remove the non-specific thread-mode executor
| -rw-r--r-- | embassy-executor/src/arch/cortex_m.rs | 286 | ||||
| -rw-r--r-- | embassy-executor/src/arch/riscv32.rs | 84 | ||||
| -rw-r--r-- | embassy-executor/src/arch/std.rs | 68 | ||||
| -rw-r--r-- | embassy-executor/src/arch/xtensa.rs | 98 | ||||
| -rw-r--r-- | embassy-executor/src/interrupt.rs | 2 | ||||
| -rw-r--r-- | embassy-executor/src/lib.rs | 4 | ||||
| -rw-r--r-- | embassy-executor/src/thread.rs | 87 |
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")] | 1 | const THREAD_PENDER: usize = usize::MAX; |
| 2 | pub use thread::*; | ||
| 3 | |||
| 4 | use crate::raw::PenderContext; | ||
| 5 | |||
| 6 | #[cfg(feature = "executor-interrupt")] | ||
| 7 | |||
| 8 | /// # Safety | ||
| 9 | /// | ||
| 10 | /// `irq` must be a valid interrupt request number | ||
| 11 | unsafe 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"] |
| 37 | fn __pender(context: PenderContext) { | 4 | #[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] |
| 5 | fn __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"] | ||
| 52 | fn __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"] | ||
| 58 | fn __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")] |
| 47 | pub use thread::*; | ||
| 48 | #[cfg(feature = "executor-thread")] | ||
| 68 | mod thread { | 49 | mod 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")] |
| 97 | pub use interrupt::*; | 115 | pub use interrupt::*; |
| 98 | #[cfg(feature = "executor-interrupt")] | 116 | #[cfg(feature = "executor-interrupt")] |
| 99 | mod interrupt { | 117 | mod 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`."); | |||
| 5 | pub use thread::*; | 5 | pub use thread::*; |
| 6 | #[cfg(feature = "executor-thread")] | 6 | #[cfg(feature = "executor-thread")] |
| 7 | mod thread { | 7 | mod 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`."); | |||
| 5 | pub use thread::*; | 5 | pub use thread::*; |
| 6 | #[cfg(feature = "executor-thread")] | 6 | #[cfg(feature = "executor-thread")] |
| 7 | mod thread { | 7 | mod 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. |
| 45 | pub struct InterruptModeExecutor { | 45 | pub 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")] |
| 41 | pub mod interrupt; | 41 | pub mod interrupt; |
| 42 | #[cfg(feature = "executor-thread")] | 42 | #[cfg(feature = "executor-interrupt")] |
| 43 | pub mod thread; | 43 | pub use interrupt::*; |
| 44 | 44 | ||
| 45 | mod spawner; | 45 | mod spawner; |
| 46 | pub use spawner::*; | 46 | pub 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 | |||
| 3 | use core::marker::PhantomData; | ||
| 4 | |||
| 5 | use crate::raw::{self, PenderContext}; | ||
| 6 | use 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 | ||
| 11 | pub 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. | ||
| 35 | pub struct ThreadModeExecutor<C: ThreadContext> { | ||
| 36 | inner: raw::Executor, | ||
| 37 | context: C, | ||
| 38 | not_send: PhantomData<*mut ()>, | ||
| 39 | } | ||
| 40 | |||
| 41 | impl<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 | } | ||
