From 986a63ebb8611a4dc7c6b14e03146286942ec8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 15:35:22 +0200 Subject: [PATCH] Remove the non-specific thread-mode executor --- embassy-executor/src/arch/cortex_m.rs | 296 ++++++++++++++++++-------- embassy-executor/src/arch/riscv32.rs | 92 +++++--- embassy-executor/src/arch/std.rs | 70 +++--- embassy-executor/src/arch/xtensa.rs | 94 +++++--- embassy-executor/src/interrupt.rs | 2 +- embassy-executor/src/lib.rs | 4 +- embassy-executor/src/thread.rs | 87 -------- 7 files changed, 369 insertions(+), 276 deletions(-) delete mode 100644 embassy-executor/src/thread.rs diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 439db0fc..2ed70dd1 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -1,122 +1,236 @@ -#[cfg(feature = "executor-thread")] -pub use thread::*; +const THREAD_PENDER: usize = usize::MAX; -use crate::raw::PenderContext; - -#[cfg(feature = "executor-interrupt")] - -/// # Safety -/// -/// `irq` must be a valid interrupt request number -unsafe fn nvic_pend(irq: u16) { - use cortex_m::interrupt::InterruptNumber; - - #[derive(Clone, Copy)] - struct Irq(u16); - unsafe impl InterruptNumber for Irq { - fn number(self) -> u16 { - self.0 - } - } - - let irq = Irq(irq); - - // STIR is faster, but is only available in v7 and higher. - #[cfg(not(armv6m))] - { - let mut nvic: cortex_m::peripheral::NVIC = unsafe { core::mem::transmute(()) }; - nvic.request(irq); - } - - #[cfg(armv6m)] - cortex_m::peripheral::NVIC::pend(irq); -} - -#[cfg(all(feature = "executor-thread", feature = "executor-interrupt"))] #[export_name = "__pender"] -fn __pender(context: PenderContext) { +#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] +fn __pender(context: crate::raw::PenderContext) { unsafe { - let context: usize = core::mem::transmute(context); // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt // request number given to `InterruptExecutor::start`. - if context as usize == usize::MAX { - core::arch::asm!("sev") - } else { - nvic_pend(context as u16) + + let context: usize = core::mem::transmute(context); + + #[cfg(feature = "executor-thread")] + if context == THREAD_PENDER { + core::arch::asm!("sev"); + return; + } + + #[cfg(feature = "executor-interrupt")] + { + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + + #[derive(Clone, Copy)] + struct Irq(u16); + unsafe impl InterruptNumber for Irq { + fn number(self) -> u16 { + self.0 + } + } + + let irq = Irq(context as u16); + + // STIR is faster, but is only available in v7 and higher. + #[cfg(not(armv6m))] + { + let mut nvic: NVIC = core::mem::transmute(()); + nvic.request(irq); + } + + #[cfg(armv6m)] + NVIC::pend(irq); } } } -#[cfg(all(feature = "executor-thread", not(feature = "executor-interrupt")))] -#[export_name = "__pender"] -fn __pender(_context: PenderContext) { - unsafe { core::arch::asm!("sev") } -} - -#[cfg(all(not(feature = "executor-thread"), feature = "executor-interrupt"))] -#[export_name = "__pender"] -fn __pender(context: PenderContext) { - unsafe { - let context: usize = core::mem::transmute(context); - // Safety: `context` is the same value we passed to `InterruptExecutor::start`, which must - // be a valid interrupt request number. - nvic_pend(context as u16) - } -} - +#[cfg(feature = "executor-thread")] +pub use thread::*; #[cfg(feature = "executor-thread")] mod thread { + use core::arch::asm; + use core::marker::PhantomData; #[cfg(feature = "nightly")] pub use embassy_macros::main_cortex_m as main; - use crate::raw::PenderContext; - use crate::thread::ThreadContext; + use crate::arch::THREAD_PENDER; + use crate::{raw, Spawner}; - /// TODO - // Name pending - #[derive(Default)] // Default enables Executor::new - pub struct Context; - - impl ThreadContext for Context { - fn context(&self) -> PenderContext { - unsafe { core::mem::transmute(usize::MAX) } - } - - fn wait(&mut self) { - unsafe { core::arch::asm!("wfe") } - } + /// Thread mode executor, using WFE/SEV. + /// + /// This is the simplest and most common kind of executor. It runs on + /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction + /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction + /// is executed, to make the `WFE` exit from sleep and poll the task. + /// + /// This executor allows for ultra low power consumption for chips where `WFE` + /// triggers low-power sleep without extra steps. If your chip requires extra steps, + /// you may use [`raw::Executor`] directly to program custom behavior. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, } - /// TODO - // Type alias for backwards compatibility - pub type Executor = crate::thread::ThreadModeExecutor; + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(unsafe { core::mem::transmute(THREAD_PENDER) }), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + asm!("wfe"); + }; + } + } + } } #[cfg(feature = "executor-interrupt")] pub use interrupt::*; #[cfg(feature = "executor-interrupt")] mod interrupt { + use core::cell::UnsafeCell; + use core::mem::MaybeUninit; + + use atomic_polyfill::{AtomicBool, Ordering}; use cortex_m::interrupt::InterruptNumber; use cortex_m::peripheral::NVIC; - use crate::interrupt::InterruptContext; - use crate::raw::PenderContext; + use crate::raw; - impl InterruptContext for T - where - T: InterruptNumber, - { - fn context(&self) -> PenderContext { - unsafe { core::mem::transmute(self.number() as usize) } - } - - fn enable(&self) { - unsafe { NVIC::unmask(*self) } - } + /// Interrupt mode executor. + /// + /// This executor runs tasks in interrupt mode. The interrupt handler is set up + /// to poll tasks, and when a task is woken the interrupt is pended from software. + /// + /// This allows running async tasks at a priority higher than thread mode. One + /// use case is to leave thread mode free for non-async tasks. Another use case is + /// to run multiple executors: one in thread mode for low priority tasks and another in + /// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower + /// priority ones. + /// + /// It is even possible to run multiple interrupt mode executors at different priorities, + /// by assigning different priorities to the interrupts. For an example on how to do this, + /// See the 'multiprio' example for 'embassy-nrf'. + /// + /// To use it, you have to pick an interrupt that won't be used by the hardware. + /// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI). + /// If this is not the case, you may use an interrupt from any unused peripheral. + /// + /// It is somewhat more complex to use, it's recommended to use the thread-mode + /// [`Executor`] instead, if it works for your use case. + pub struct InterruptExecutor { + started: AtomicBool, + executor: UnsafeCell>, } - /// TODO - // Type alias for backwards compatibility - pub type InterruptExecutor = crate::interrupt::InterruptModeExecutor; + unsafe impl Send for InterruptExecutor {} + unsafe impl Sync for InterruptExecutor {} + + impl InterruptExecutor { + /// Create a new, not started `InterruptExecutor`. + #[inline] + pub const fn new() -> Self { + Self { + started: AtomicBool::new(false), + executor: UnsafeCell::new(MaybeUninit::uninit()), + } + } + + /// Executor interrupt callback. + /// + /// # Safety + /// + /// You MUST call this from the interrupt handler, and from nowhere else. + pub unsafe fn on_interrupt(&'static self) { + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.poll(); + } + + /// Start the executor. + /// + /// This initializes the executor, enables the interrupt, and returns. + /// The executor keeps running in the background through the interrupt. + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] + /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a + /// different "thread" (the interrupt), so spawning tasks on it is effectively + /// sending them. + /// + /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from + /// a task running in it. + /// + /// # Interrupt requirements + /// + /// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt). + /// + /// This method already enables (unmasks) the interrupt, you must NOT do it yourself. + /// + /// You must set the interrupt priority before calling this method. You MUST NOT + /// do it after. + /// + pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner { + if self + .started + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + panic!("InterruptExecutor::start() called multiple times on the same executor."); + } + + unsafe { + let context = core::mem::transmute(irq.number() as usize); + (&mut *self.executor.get()) + .as_mut_ptr() + .write(raw::Executor::new(context)) + } + + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + + unsafe { NVIC::unmask(irq) } + + executor.spawner().make_send() + } + + /// Get a SendSpawner for this executor + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on this + /// executor. + /// + /// This MUST only be called on an executor that has already been spawned. + /// The function will panic otherwise. + pub fn spawner(&'static self) -> crate::SendSpawner { + if !self.started.load(Ordering::Acquire) { + panic!("InterruptExecutor::spawner() called on uninitialized executor."); + } + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.spawner().make_send() + } + } } diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index f76a4bcf..551d7527 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`."); pub use thread::*; #[cfg(feature = "executor-thread")] mod thread { + use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; #[cfg(feature = "nightly")] pub use embassy_macros::main_riscv as main; - use crate::raw::PenderContext; - use crate::thread::ThreadContext; + use crate::{raw, Spawner}; /// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); #[export_name = "__pender"] - fn __thread_mode_pender(_context: PenderContext) { + fn __thread_mode_pender(_context: crate::raw::PenderContext) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } - /// TODO - // Name pending - #[derive(Default)] // Default enables Executor::new - pub struct Context; - - impl ThreadContext for Context { - fn context(&self) -> PenderContext { - unsafe { core::mem::transmute(0) } - } - - fn wait(&mut self) { - // We do not care about race conditions between the load and store operations, - // interrupts will only set this value to true. - critical_section::with(|_| { - // if there is work to do, loop back to polling - // TODO can we relax this? - if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { - SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); - } - // if not, wait for interrupt - else { - unsafe { - core::arch::asm!("wfi"); - } - } - }); - // if an interrupt occurred while waiting, it will be serviced here - } + /// RISCV32 Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, } - /// TODO - // Type alias for backwards compatibility - pub type Executor = crate::thread::ThreadModeExecutor; + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(unsafe { core::mem::transmute(0) }), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + // we do not care about race conditions between the load and store operations, interrupts + //will only set this value to true. + critical_section::with(|_| { + // if there is work to do, loop back to polling + // TODO can we relax this? + if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { + SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + } + // if not, wait for interrupt + else { + core::arch::asm!("wfi"); + } + }); + // if an interrupt occurred while waiting, it will be serviced here + } + } + } + } } diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index d55de118..f490084d 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -5,42 +5,64 @@ compile_error!("`executor-interrupt` is not supported with `arch-std`."); pub use thread::*; #[cfg(feature = "executor-thread")] mod thread { + use std::marker::PhantomData; use std::sync::{Condvar, Mutex}; #[cfg(feature = "nightly")] pub use embassy_macros::main_std as main; - use crate::raw::PenderContext; - use crate::thread::ThreadContext; + use crate::{raw, Spawner}; - /// TODO - // Name pending - pub struct Context { + #[export_name = "__pender"] + fn __pender(context: crate::raw::PenderContext) { + let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; + signaler.signal() + } + + /// Single-threaded std-based executor. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, signaler: &'static Signaler, } - impl Default for Context { - fn default() -> Self { + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let signaler = &*Box::leak(Box::new(Signaler::new())); Self { - signaler: &*Box::leak(Box::new(Signaler::new())), + inner: raw::Executor::new(unsafe { std::mem::transmute(signaler) }), + not_send: PhantomData, + signaler, } } - } - impl ThreadContext for Context { - fn context(&self) -> PenderContext { - unsafe { core::mem::transmute(self.signaler) } + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { self.inner.poll() }; + self.signaler.wait() + } } - - fn wait(&mut self) { - self.signaler.wait() - } - } - - #[export_name = "__pender"] - fn __pender(context: PenderContext) { - let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; - signaler.signal() } struct Signaler { @@ -70,8 +92,4 @@ mod thread { self.condvar.notify_one(); } } - - /// TODO - // Type alias for backwards compatibility - pub type Executor = crate::thread::ThreadModeExecutor; } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 1aea9f23..8665a9cb 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -8,56 +8,80 @@ mod thread { use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; - use crate::raw::PenderContext; - use crate::thread::ThreadContext; + use crate::{raw, Spawner}; /// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(_context: PenderContext) { + fn __thread_mode_pender(_context: crate::raw::PenderContext) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } - /// TODO - // Name pending - #[derive(Default)] // Default enables Executor::new - pub struct Context; + /// Xtensa Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } - impl ThreadContext for Context { - fn context(&self) -> PenderContext { - unsafe { core::mem::transmute(0) } + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(unsafe { core::mem::transmute(0) }), + not_send: PhantomData, + } } - fn wait(&mut self) { - unsafe { - // Manual critical section implementation that only masks interrupts handlers. - // We must not acquire the cross-core on dual-core systems because that would - // prevent the other core from doing useful work while this core is sleeping. - let token: critical_section::RawRestoreState; - core::arch::asm!("rsil {0}, 5", out(reg) token); + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); - // we do not care about race conditions between the load and store operations, - // interrupts will only set this value to true. - // if there is work to do, loop back to polling - if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { - SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + loop { + unsafe { + self.inner.poll(); - core::arch::asm!( - "wsr.ps {0}", - "rsync", in(reg) token) - } else { - // waiti sets the PS.INTLEVEL when slipping into sleep - // because critical sections in Xtensa are implemented via increasing - // PS.INTLEVEL the critical section ends here - // take care not add code after `waiti` if it needs to be inside the CS - core::arch::asm!("waiti 0"); // critical section ends here + // Manual critical section implementation that only masks interrupts handlers. + // We must not acquire the cross-core on dual-core systems because that would + // prevent the other core from doing useful work while this core is sleeping. + let token: critical_section::RawRestoreState; + core::arch::asm!("rsil {0}, 5", out(reg) token); + + // we do not care about race conditions between the load and store operations, interrupts + // will only set this value to true. + // if there is work to do, loop back to polling + if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { + SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + + core::arch::asm!( + "wsr.ps {0}", + "rsync", in(reg) token) + } else { + // waiti sets the PS.INTLEVEL when slipping into sleep + // because critical sections in Xtensa are implemented via increasing + // PS.INTLEVEL the critical section ends here + // take care not add code after `waiti` if it needs to be inside the CS + core::arch::asm!("waiti 0"); // critical section ends here + } } } } } - - /// TODO - // Type alias for backwards compatibility - pub type Executor = crate::thread::ThreadModeExecutor; } diff --git a/embassy-executor/src/interrupt.rs b/embassy-executor/src/interrupt.rs index 28a1cd52..b68754ab 100644 --- a/embassy-executor/src/interrupt.rs +++ b/embassy-executor/src/interrupt.rs @@ -41,7 +41,7 @@ pub trait InterruptContext { /// If this is not the case, you may use an interrupt from any unused peripheral. /// /// It is somewhat more complex to use, it's recommended to use the -/// [`crate::thread::ThreadModeExecutor`] instead, if it works for your use case. +/// thread-mode executor instead, if it works for your use case. pub struct InterruptModeExecutor { started: AtomicBool, executor: UnsafeCell>, diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index ca67c948..3be32d9c 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -39,8 +39,8 @@ pub mod raw; #[cfg(feature = "executor-interrupt")] pub mod interrupt; -#[cfg(feature = "executor-thread")] -pub mod thread; +#[cfg(feature = "executor-interrupt")] +pub use interrupt::*; mod spawner; pub use spawner::*; diff --git a/embassy-executor/src/thread.rs b/embassy-executor/src/thread.rs deleted file mode 100644 index 8ff4071d..00000000 --- a/embassy-executor/src/thread.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Thread-mode executor. - -use core::marker::PhantomData; - -use crate::raw::{self, PenderContext}; -use crate::Spawner; - -/// Architecture-specific interface for a thread-mode executor. This trait describes what the -/// executor should do when idle, and what data should be passed to its pender. -// TODO: Name pending -pub trait ThreadContext: Sized { - /// A pointer-sized piece of data that is passed to the pender function. - /// - /// For example, on multi-core systems, this can be used to store the ID of the core that - /// should be woken up. - fn context(&self) -> PenderContext; - - /// Waits for the executor to be waken. - /// - /// While it is valid for this function can be empty, it is recommended to use a WFE instruction - /// or equivalent to let the CPU sleep. - fn wait(&mut self); -} - -/// Thread mode executor, using WFE/SEV. -/// -/// This is the simplest and most common kind of executor. It runs on -/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction -/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction -/// is executed, to make the `WFE` exit from sleep and poll the task. -/// -/// This executor allows for ultra low power consumption for chips where `WFE` -/// triggers low-power sleep without extra steps. If your chip requires extra steps, -/// you may use [`raw::Executor`] directly to program custom behavior. -pub struct ThreadModeExecutor { - inner: raw::Executor, - context: C, - not_send: PhantomData<*mut ()>, -} - -impl ThreadModeExecutor { - /// Create a new Executor. - pub fn new() -> Self - where - C: Default, - { - Self::with_context(C::default()) - } - - /// Create a new Executor using the given thread context. - pub fn with_context(context: C) -> Self { - Self { - inner: raw::Executor::new(context.context()), - context, - not_send: PhantomData, - } - } - - /// Run the executor. - /// - /// The `init` closure is called with a [`Spawner`] that spawns tasks on - /// this executor. Use it to spawn the initial task(s). After `init` returns, - /// the executor starts running the tasks. - /// - /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), - /// for example by passing it as an argument to the initial tasks. - /// - /// This function requires `&'static mut self`. This means you have to store the - /// Executor instance in a place where it'll live forever and grants you mutable - /// access. There's a few ways to do this: - /// - /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) - /// - a `static mut` (unsafe) - /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) - /// - /// This function never returns. - pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(self.inner.spawner()); - - loop { - unsafe { - self.inner.poll(); - self.context.wait(); - }; - } - } -}