From 675b7fb6056d8c3dfaca759b7cd373e2f4a0e111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 12 Aug 2023 16:00:18 +0200 Subject: [PATCH] POC: allow custom executors --- embassy-executor/Cargo.toml | 7 +- embassy-executor/src/arch/cortex_m.rs | 252 +++++++------------------- embassy-executor/src/arch/riscv32.rs | 100 ++++------ embassy-executor/src/arch/std.rs | 76 +++----- embassy-executor/src/arch/wasm.rs | 37 ++-- embassy-executor/src/arch/xtensa.rs | 117 +++++------- embassy-executor/src/interrupt.rs | 127 +++++++++++++ embassy-executor/src/lib.rs | 5 + embassy-executor/src/raw/mod.rs | 35 +++- embassy-executor/src/thread.rs | 80 ++++++++ 10 files changed, 448 insertions(+), 388 deletions(-) create mode 100644 embassy-executor/src/interrupt.rs create mode 100644 embassy-executor/src/thread.rs diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index ce5e2741..182dd693 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -31,11 +31,11 @@ features = ["nightly", "defmt", "pender-callback", "arch-cortex-m", "executor-th # Architecture _arch = [] # some arch was picked -arch-std = ["_arch", "critical-section/std"] +arch-std = ["_arch", "critical-section/std", "thread-context"] arch-cortex-m = ["_arch", "dep:cortex-m"] arch-xtensa = ["_arch"] arch-riscv32 = ["_arch"] -arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] +arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "thread-context"] # Enable creating a `Pender` from an arbitrary function pointer callback. pender-callback = [] @@ -45,6 +45,9 @@ executor-thread = [] # Enable the interrupt-mode executor (available in Cortex-M only) executor-interrupt = [] +# Pass a context to the thread-mode executor. +thread-context = [] + # Enable nightly-only features nightly = [] diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 94c8134d..ca1675c0 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -1,224 +1,98 @@ #[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::{Pender, PenderInner}; - use crate::{raw, Spawner}; + use crate::raw::OpaqueThreadContext; + use crate::thread::ThreadContext; - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender; + #[export_name = "__thread_mode_pender"] + fn __thread_mode_pender(_core_id: OpaqueThreadContext) { + unsafe { core::arch::asm!("sev") } + } - impl ThreadPender { - pub(crate) fn pend(self) { - unsafe { core::arch::asm!("sev") } + /// TODO + // Name pending + #[derive(Default)] // Default enables Executor::new + pub struct CortexMThreadContext { + _not_send: core::marker::PhantomData<*mut ()>, + } + + impl ThreadContext for CortexMThreadContext { + #[cfg(feature = "thread-context")] + fn context(&self) -> OpaqueThreadContext { + // Enabling thread-context is not incorrect, just wasteful. + OpaqueThreadContext(0) + } + + #[cfg(not(feature = "thread-context"))] + fn context(&self) -> OpaqueThreadContext { + OpaqueThreadContext(()) + } + + 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 ()>, - } - - impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), - 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"); - }; - } - } - } + /// TODO + // Type alias for backwards compatibility + pub type Executor = crate::thread::ThreadModeExecutor; } +// None of this has to be public, I guess? #[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::raw::{self, Pender, PenderInner}; + use crate::interrupt::InterruptContext; + use crate::raw::OpaqueInterruptContext; #[derive(Clone, Copy)] - pub(crate) struct InterruptPender(u16); + struct CortexMInterruptContext(u16); - impl InterruptPender { - pub(crate) fn pend(self) { - // 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(self); - } - - #[cfg(armv6m)] - cortex_m::peripheral::NVIC::pend(self); - } - } - - unsafe impl cortex_m::interrupt::InterruptNumber for InterruptPender { + unsafe impl cortex_m::interrupt::InterruptNumber for CortexMInterruptContext { fn number(self) -> u16 { self.0 } } - /// 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>, - } - - 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()), - } + impl InterruptContext for T + where + T: InterruptNumber, + { + fn context(&self) -> OpaqueInterruptContext { + OpaqueInterruptContext(self.number() as usize) } - /// 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 { - (&mut *self.executor.get()) - .as_mut_ptr() - .write(raw::Executor::new(Pender(PenderInner::Interrupt(InterruptPender( - irq.number(), - ))))) - } - - 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() + fn enable(&self) { + unsafe { NVIC::unmask(*self) } } } + + #[export_name = "__interrupt_mode_pender"] + fn __interrupt_mode_pender(interrupt: OpaqueInterruptContext) { + let interrupt = CortexMInterruptContext(unsafe { core::mem::transmute::<_, usize>(interrupt) as u16 }); + + // STIR is faster, but is only available in v7 and higher. + #[cfg(not(armv6m))] + { + let mut nvic: NVIC = unsafe { core::mem::transmute(()) }; + nvic.request(interrupt); + } + + #[cfg(armv6m)] + NVIC::pend(interrupt); + } + + /// TODO + // Type alias for backwards compatibility + pub type InterruptExecutor = crate::interrupt::InterruptModeExecutor; } diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index ff7ec157..5f766442 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -1,6 +1,9 @@ #[cfg(feature = "executor-interrupt")] compile_error!("`executor-interrupt` is not supported with `arch-riscv32`."); +#[cfg(feature = "thread-context")] +compile_error!("`thread-context` is not supported with `arch-riscv32`."); + #[cfg(feature = "executor-thread")] pub use thread::*; #[cfg(feature = "executor-thread")] @@ -11,77 +14,50 @@ mod thread { #[cfg(feature = "nightly")] pub use embassy_macros::main_riscv as main; - use crate::raw::{Pender, PenderInner}; - use crate::{raw, Spawner}; - - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender; - - impl ThreadPender { - #[allow(unused)] - pub(crate) fn pend(self) { - SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); - } - } + use crate::raw::OpaqueThreadContext; + use crate::thread::ThreadContext; /// 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); - /// RISCV32 Executor - pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, + #[export_name = "__thread_mode_pender"] + fn __thread_mode_pender(_core_id: OpaqueThreadContext) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } - impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), - not_send: PhantomData, - } + /// TODO + // Name pending + #[derive(Default)] // Default enables Executor::new + pub struct RiscVThreadContext { + _not_send: PhantomData<*mut ()>, + } + + impl ThreadContext for RiscVThreadContext { + fn context(&self) -> OpaqueThreadContext { + OpaqueThreadContext(()) } - /// 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 + 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 } } + + /// TODO + // Type alias for backwards compatibility + pub type Executor = crate::thread::ThreadModeExecutor; } diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 4e4a178f..28e25fbd 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -1,6 +1,9 @@ #[cfg(feature = "executor-interrupt")] compile_error!("`executor-interrupt` is not supported with `arch-std`."); +#[cfg(not(feature = "thread-context"))] +compile_error!("`arch-std` requires `thread-context`."); + #[cfg(feature = "executor-thread")] pub use thread::*; #[cfg(feature = "executor-thread")] @@ -11,63 +14,40 @@ mod thread { #[cfg(feature = "nightly")] pub use embassy_macros::main_std as main; - use crate::raw::{Pender, PenderInner}; - use crate::{raw, Spawner}; + use crate::raw::OpaqueThreadContext; + use crate::thread::ThreadContext; - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender(&'static Signaler); - - impl ThreadPender { - #[allow(unused)] - pub(crate) fn pend(self) { - self.0.signal() - } - } - - /// Single-threaded std-based executor. - pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, + /// TODO + // Name pending + pub struct StdThreadCtx { + _not_send: PhantomData<*mut ()>, signaler: &'static Signaler, } - impl Executor { - /// Create a new Executor. - pub fn new() -> Self { + impl Default for StdThreadCtx { + fn default() -> Self { let signaler = &*Box::leak(Box::new(Signaler::new())); Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(signaler)))), - not_send: PhantomData, + _not_send: PhantomData, 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() - } + impl ThreadContext for StdThreadCtx { + fn context(&self) -> OpaqueThreadContext { + OpaqueThreadContext(self.signaler as *const _ as usize) } + + fn wait(&mut self) { + self.signaler.wait() + } + } + + #[export_name = "__thread_mode_pender"] + fn __thread_mode_pender(core_id: OpaqueThreadContext) { + let signaler: &'static Signaler = unsafe { std::mem::transmute(core_id) }; + signaler.signal() } struct Signaler { @@ -97,4 +77,8 @@ 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/wasm.rs b/embassy-executor/src/arch/wasm.rs index 08ab16b9..4f5ce9c9 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -1,6 +1,9 @@ #[cfg(feature = "executor-interrupt")] compile_error!("`executor-interrupt` is not supported with `arch-wasm`."); +#[cfg(not(feature = "thread-context"))] +compile_error!("`arch-wasm` requires `thread-context`."); + #[cfg(feature = "executor-thread")] pub use thread::*; #[cfg(feature = "executor-thread")] @@ -14,14 +17,13 @@ mod thread { use wasm_bindgen::prelude::*; use crate::raw::util::UninitCell; - use crate::raw::{Pender, PenderInner}; + use crate::raw::{OpaqueThreadContext, Pender, PenderInner}; use crate::{raw, Spawner}; - /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. - pub struct Executor { - inner: raw::Executor, - ctx: &'static WasmContext, - not_send: PhantomData<*mut ()>, + #[export_name = "__thread_mode_pender"] + fn __thread_mode_pender(context: OpaqueThreadContext) { + let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; + let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); } pub(crate) struct WasmContext { @@ -29,16 +31,6 @@ mod thread { closure: UninitCell>, } - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender(&'static WasmContext); - - impl ThreadPender { - #[allow(unused)] - pub(crate) fn pend(self) { - let _ = self.0.promise.then(unsafe { self.0.closure.as_mut() }); - } - } - impl WasmContext { pub fn new() -> Self { Self { @@ -48,14 +40,23 @@ mod thread { } } + /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. + pub struct Executor { + inner: raw::Executor, + ctx: &'static WasmContext, + not_send: PhantomData<*mut ()>, + } + impl Executor { /// Create a new Executor. pub fn new() -> Self { let ctx = &*Box::leak(Box::new(WasmContext::new())); Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(ctx)))), - not_send: PhantomData, + inner: raw::Executor::new(Pender(PenderInner::Thread(OpaqueThreadContext( + ctx as *const _ as usize, + )))), ctx, + not_send: PhantomData, } } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 017b2c52..8e1b917d 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -1,6 +1,12 @@ #[cfg(feature = "executor-interrupt")] compile_error!("`executor-interrupt` is not supported with `arch-xtensa`."); +#[cfg(feature = "thread-context")] +compile_error!( + "`thread-context` is not supported with `arch-xtensa`.\ + Use a multicore-safe executor from esp-hal instead." // obviously, this is too specific to ESP32 +); + #[cfg(feature = "executor-thread")] pub use thread::*; #[cfg(feature = "executor-thread")] @@ -8,86 +14,63 @@ mod thread { use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; - use crate::raw::{Pender, PenderInner}; - use crate::{raw, Spawner}; + use crate::raw::OpaqueThreadContext; + use crate::thread::ThreadContext; - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender; - - impl ThreadPender { - #[allow(unused)] - pub(crate) fn pend(self) { - SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); - } - } - - /// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa + /// 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); - /// Xtensa Executor - pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, + #[export_name = "__thread_mode_pender"] + fn __thread_mode_pender(_core_id: OpaqueThreadContext) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } - impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), - not_send: PhantomData, - } + /// TODO + // Name pending + pub struct XtensaThreadContext { + _not_send: PhantomData<*mut ()>, + } + + impl Default for XtensaThreadContext { + fn default() -> Self { + Self { _not_send: PhantomData } + } + } + + impl ThreadContext for XtensaThreadContext { + fn context(&self) -> OpaqueThreadContext { + OpaqueThreadContext(()) } - /// 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()); + 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); - 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. + // 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); - // 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!( + 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 - } + } 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 new file mode 100644 index 00000000..f8b0809d --- /dev/null +++ b/embassy-executor/src/interrupt.rs @@ -0,0 +1,127 @@ +//! Interrupt-mode executor. + +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; + +use atomic_polyfill::{AtomicBool, Ordering}; + +use crate::raw::{self, OpaqueInterruptContext, Pender, PenderInner}; + +/// An interrupt source that can be used to drive an [`InterruptExecutor`]. +// Name pending +pub trait InterruptContext { + /// Creates an opaque identifier for this interrupt. + fn context(&self) -> OpaqueInterruptContext; + + /// Sets up the interrupt request. + fn enable(&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 InterruptModeExecutor { + started: AtomicBool, + executor: UnsafeCell>, +} + +unsafe impl Send for InterruptModeExecutor {} +unsafe impl Sync for InterruptModeExecutor {} + +impl InterruptModeExecutor { + /// 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 InterruptContext) -> 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 { + (&mut *self.executor.get()) + .as_mut_ptr() + .write(raw::Executor::new(Pender(PenderInner::Interrupt(irq.context())))) + } + + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + + irq.enable(); + + 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/lib.rs b/embassy-executor/src/lib.rs index 3ce687eb..ca67c948 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -37,6 +37,11 @@ pub use arch::*; pub mod raw; +#[cfg(feature = "executor-interrupt")] +pub mod interrupt; +#[cfg(feature = "executor-thread")] +pub mod thread; + mod spawner; pub use spawner::*; diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 25c2ab0d..b4d70b1e 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -291,12 +291,29 @@ impl TaskPool { } } +/// Context given to the thread-mode executor's pender. +#[cfg(all(feature = "executor-thread", not(feature = "thread-context")))] +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct OpaqueThreadContext(pub(crate) ()); + +/// Context given to the thread-mode executor's pender. +#[cfg(all(feature = "executor-thread", feature = "thread-context"))] +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct OpaqueThreadContext(pub(crate) usize); + +/// Context given to the interrupt-mode executor's pender. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct OpaqueInterruptContext(pub(crate) usize); + #[derive(Clone, Copy)] pub(crate) enum PenderInner { #[cfg(feature = "executor-thread")] - Thread(crate::arch::ThreadPender), + Thread(OpaqueThreadContext), #[cfg(feature = "executor-interrupt")] - Interrupt(crate::arch::InterruptPender), + Interrupt(OpaqueInterruptContext), #[cfg(feature = "pender-callback")] Callback { func: fn(*mut ()), context: *mut () }, } @@ -333,9 +350,19 @@ impl Pender { pub(crate) fn pend(&self) { match self.0 { #[cfg(feature = "executor-thread")] - PenderInner::Thread(x) => x.pend(), + PenderInner::Thread(core_id) => { + extern "Rust" { + fn __thread_mode_pender(core_id: OpaqueThreadContext); + } + unsafe { __thread_mode_pender(core_id) }; + } #[cfg(feature = "executor-interrupt")] - PenderInner::Interrupt(x) => x.pend(), + PenderInner::Interrupt(interrupt) => { + extern "Rust" { + fn __interrupt_mode_pender(interrupt: OpaqueInterruptContext); + } + unsafe { __interrupt_mode_pender(interrupt) }; + } #[cfg(feature = "pender-callback")] PenderInner::Callback { func, context } => func(context), } diff --git a/embassy-executor/src/thread.rs b/embassy-executor/src/thread.rs new file mode 100644 index 00000000..9bbe2950 --- /dev/null +++ b/embassy-executor/src/thread.rs @@ -0,0 +1,80 @@ +//! Thread-mode executor. + +use core::marker::PhantomData; + +use crate::raw::{OpaqueThreadContext, Pender, PenderInner}; +use crate::{raw, Spawner}; + +/// TODO +// Name pending +pub trait ThreadContext: Sized { + /// TODO + fn context(&self) -> OpaqueThreadContext; + + /// TODO + 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. + pub fn with_context(context: C) -> Self { + Self { + inner: raw::Executor::new(Pender(PenderInner::Thread(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(); + }; + } + } +}