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 01/16] 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(); + }; + } + } +} From fbf50cdae899dc1cd2f232b880e096d0fc51f49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 12 Aug 2023 22:05:19 +0200 Subject: [PATCH 02/16] Remove Pender wrapper --- embassy-executor/src/arch/wasm.rs | 6 ++-- embassy-executor/src/interrupt.rs | 4 +-- embassy-executor/src/raw/mod.rs | 46 +++++++++++++++---------------- embassy-executor/src/thread.rs | 4 +-- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index 4f5ce9c9..e244c0b3 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -17,7 +17,7 @@ mod thread { use wasm_bindgen::prelude::*; use crate::raw::util::UninitCell; - use crate::raw::{OpaqueThreadContext, Pender, PenderInner}; + use crate::raw::{OpaqueThreadContext, Pender}; use crate::{raw, Spawner}; #[export_name = "__thread_mode_pender"] @@ -52,9 +52,7 @@ mod thread { pub fn new() -> Self { let ctx = &*Box::leak(Box::new(WasmContext::new())); Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(OpaqueThreadContext( - ctx as *const _ as usize, - )))), + inner: raw::Executor::new(Pender::Thread(OpaqueThreadContext(ctx as *const _ as usize))), ctx, not_send: PhantomData, } diff --git a/embassy-executor/src/interrupt.rs b/embassy-executor/src/interrupt.rs index f8b0809d..c1084ea7 100644 --- a/embassy-executor/src/interrupt.rs +++ b/embassy-executor/src/interrupt.rs @@ -5,7 +5,7 @@ use core::mem::MaybeUninit; use atomic_polyfill::{AtomicBool, Ordering}; -use crate::raw::{self, OpaqueInterruptContext, Pender, PenderInner}; +use crate::raw::{self, OpaqueInterruptContext, Pender}; /// An interrupt source that can be used to drive an [`InterruptExecutor`]. // Name pending @@ -100,7 +100,7 @@ impl InterruptModeExecutor { unsafe { (&mut *self.executor.get()) .as_mut_ptr() - .write(raw::Executor::new(Pender(PenderInner::Interrupt(irq.context())))) + .write(raw::Executor::new(Pender::Interrupt(irq.context()))) } let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index b4d70b1e..7fd29db4 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -308,19 +308,6 @@ pub struct OpaqueThreadContext(pub(crate) usize); #[repr(transparent)] pub struct OpaqueInterruptContext(pub(crate) usize); -#[derive(Clone, Copy)] -pub(crate) enum PenderInner { - #[cfg(feature = "executor-thread")] - Thread(OpaqueThreadContext), - #[cfg(feature = "executor-interrupt")] - Interrupt(OpaqueInterruptContext), - #[cfg(feature = "pender-callback")] - Callback { func: fn(*mut ()), context: *mut () }, -} - -unsafe impl Send for PenderInner {} -unsafe impl Sync for PenderInner {} - /// Platform/architecture-specific action executed when an executor has pending work. /// /// When a task within an executor is woken, the `Pender` is called. This does a @@ -328,7 +315,23 @@ unsafe impl Sync for PenderInner {} /// When this happens, you must arrange for [`Executor::poll`] to be called. /// /// You can think of it as a waker, but for the whole executor. -pub struct Pender(pub(crate) PenderInner); +#[derive(Clone, Copy)] +pub enum Pender { + /// Pender for a thread-mode executor. + #[cfg(feature = "executor-thread")] + Thread(OpaqueThreadContext), + + /// Pender for an interrupt-mode executor. + #[cfg(feature = "executor-interrupt")] + Interrupt(OpaqueInterruptContext), + + /// Arbitrary, dynamically dispatched pender. + #[cfg(feature = "pender-callback")] + Callback { func: fn(*mut ()), context: *mut () }, +} + +unsafe impl Send for Pender {} +unsafe impl Sync for Pender {} impl Pender { /// Create a `Pender` that will call an arbitrary function pointer. @@ -339,32 +342,29 @@ impl Pender { /// - `context`: Opaque context pointer, that will be passed to the function pointer. #[cfg(feature = "pender-callback")] pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self { - Self(PenderInner::Callback { - func, - context: context.into(), - }) + Self::Callback { func, context } } } impl Pender { - pub(crate) fn pend(&self) { - match self.0 { + pub(crate) fn pend(self) { + match self { #[cfg(feature = "executor-thread")] - PenderInner::Thread(core_id) => { + Pender::Thread(core_id) => { extern "Rust" { fn __thread_mode_pender(core_id: OpaqueThreadContext); } unsafe { __thread_mode_pender(core_id) }; } #[cfg(feature = "executor-interrupt")] - PenderInner::Interrupt(interrupt) => { + Pender::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), + Pender::Callback { func, context } => func(context), } } } diff --git a/embassy-executor/src/thread.rs b/embassy-executor/src/thread.rs index 9bbe2950..2d2c6daa 100644 --- a/embassy-executor/src/thread.rs +++ b/embassy-executor/src/thread.rs @@ -2,7 +2,7 @@ use core::marker::PhantomData; -use crate::raw::{OpaqueThreadContext, Pender, PenderInner}; +use crate::raw::{OpaqueThreadContext, Pender}; use crate::{raw, Spawner}; /// TODO @@ -43,7 +43,7 @@ impl ThreadModeExecutor { /// Create a new Executor. pub fn with_context(context: C) -> Self { Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(context.context()))), + inner: raw::Executor::new(Pender::Thread(context.context())), context, not_send: PhantomData, } From bce250bbdc18f025547f59c30a7bec24826b3aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 12 Aug 2023 22:08:46 +0200 Subject: [PATCH 03/16] Remove unnecessary !Send markers --- embassy-executor/src/arch/cortex_m.rs | 4 +--- embassy-executor/src/arch/riscv32.rs | 5 +---- embassy-executor/src/arch/std.rs | 6 +----- embassy-executor/src/arch/xtensa.rs | 11 ++--------- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index ca1675c0..355a0f08 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -18,9 +18,7 @@ mod thread { /// TODO // Name pending #[derive(Default)] // Default enables Executor::new - pub struct CortexMThreadContext { - _not_send: core::marker::PhantomData<*mut ()>, - } + pub struct CortexMThreadContext; impl ThreadContext for CortexMThreadContext { #[cfg(feature = "thread-context")] diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index 5f766442..becc0245 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -8,7 +8,6 @@ compile_error!("`thread-context` 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")] @@ -28,9 +27,7 @@ mod thread { /// TODO // Name pending #[derive(Default)] // Default enables Executor::new - pub struct RiscVThreadContext { - _not_send: PhantomData<*mut ()>, - } + pub struct RiscVThreadContext; impl ThreadContext for RiscVThreadContext { fn context(&self) -> OpaqueThreadContext { diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 28e25fbd..b08974a0 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -8,7 +8,6 @@ compile_error!("`arch-std` requires `thread-context`."); pub use thread::*; #[cfg(feature = "executor-thread")] mod thread { - use std::marker::PhantomData; use std::sync::{Condvar, Mutex}; #[cfg(feature = "nightly")] @@ -20,16 +19,13 @@ mod thread { /// TODO // Name pending pub struct StdThreadCtx { - _not_send: PhantomData<*mut ()>, signaler: &'static Signaler, } impl Default for StdThreadCtx { fn default() -> Self { - let signaler = &*Box::leak(Box::new(Signaler::new())); Self { - _not_send: PhantomData, - signaler, + signaler: &*Box::leak(Box::new(Signaler::new())), } } } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 8e1b917d..6357bfef 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -27,15 +27,8 @@ mod thread { /// TODO // Name pending - pub struct XtensaThreadContext { - _not_send: PhantomData<*mut ()>, - } - - impl Default for XtensaThreadContext { - fn default() -> Self { - Self { _not_send: PhantomData } - } - } + #[derive(Default)] // Default enables Executor::new + pub struct XtensaThreadContext; impl ThreadContext for XtensaThreadContext { fn context(&self) -> OpaqueThreadContext { From d5e66f6f87222de65ac575c4b923b2fee5487388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 12 Aug 2023 22:20:11 +0200 Subject: [PATCH 04/16] Lift thread-context feature restrictions --- embassy-executor/src/arch/riscv32.rs | 10 +++++++--- embassy-executor/src/arch/xtensa.rs | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index becc0245..c4e772e3 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -1,9 +1,6 @@ #[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")] @@ -30,6 +27,13 @@ mod thread { pub struct RiscVThreadContext; impl ThreadContext for RiscVThreadContext { + #[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(()) } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 6357bfef..1097bff8 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -1,12 +1,6 @@ #[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")] @@ -17,7 +11,7 @@ mod thread { 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 + /// 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"] @@ -31,6 +25,13 @@ mod thread { pub struct XtensaThreadContext; impl ThreadContext for XtensaThreadContext { + #[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(()) } @@ -43,8 +44,8 @@ mod thread { 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. + // 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); From 6ab0d71d9246cdc65f392212d03d639a51d21098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 12 Aug 2023 22:42:50 +0200 Subject: [PATCH 05/16] Tweak identifiers and comments --- embassy-executor/src/arch/cortex_m.rs | 13 ++++-------- embassy-executor/src/arch/riscv32.rs | 13 ++++-------- embassy-executor/src/arch/std.rs | 12 +++++------ embassy-executor/src/arch/xtensa.rs | 13 ++++-------- embassy-executor/src/interrupt.rs | 16 ++++++++------ embassy-executor/src/thread.rs | 30 ++++++++++++++++++++------- 6 files changed, 50 insertions(+), 47 deletions(-) diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 355a0f08..6c1300ae 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -11,27 +11,22 @@ mod thread { use crate::thread::ThreadContext; #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(_core_id: OpaqueThreadContext) { + fn __thread_mode_pender(_context: OpaqueThreadContext) { unsafe { core::arch::asm!("sev") } } /// TODO // Name pending #[derive(Default)] // Default enables Executor::new - pub struct CortexMThreadContext; + pub struct Context; - impl ThreadContext for CortexMThreadContext { + impl ThreadContext for Context { #[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") } } @@ -39,7 +34,7 @@ mod thread { /// TODO // Type alias for backwards compatibility - pub type Executor = crate::thread::ThreadModeExecutor; + pub type Executor = crate::thread::ThreadModeExecutor; } // None of this has to be public, I guess? diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index c4e772e3..08720400 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -17,27 +17,22 @@ mod thread { static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(_core_id: OpaqueThreadContext) { + fn __thread_mode_pender(_context: OpaqueThreadContext) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } /// TODO // Name pending #[derive(Default)] // Default enables Executor::new - pub struct RiscVThreadContext; + pub struct Context; - impl ThreadContext for RiscVThreadContext { + impl ThreadContext for Context { #[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) { // We do not care about race conditions between the load and store operations, // interrupts will only set this value to true. @@ -60,5 +55,5 @@ mod thread { /// TODO // Type alias for backwards compatibility - pub type Executor = crate::thread::ThreadModeExecutor; + pub type Executor = crate::thread::ThreadModeExecutor; } diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index b08974a0..2731e275 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -18,11 +18,11 @@ mod thread { /// TODO // Name pending - pub struct StdThreadCtx { + pub struct Context { signaler: &'static Signaler, } - impl Default for StdThreadCtx { + impl Default for Context { fn default() -> Self { Self { signaler: &*Box::leak(Box::new(Signaler::new())), @@ -30,7 +30,7 @@ mod thread { } } - impl ThreadContext for StdThreadCtx { + impl ThreadContext for Context { fn context(&self) -> OpaqueThreadContext { OpaqueThreadContext(self.signaler as *const _ as usize) } @@ -41,8 +41,8 @@ mod thread { } #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(core_id: OpaqueThreadContext) { - let signaler: &'static Signaler = unsafe { std::mem::transmute(core_id) }; + fn __thread_mode_pender(context: OpaqueThreadContext) { + let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; signaler.signal() } @@ -76,5 +76,5 @@ mod thread { /// TODO // Type alias for backwards compatibility - pub type Executor = crate::thread::ThreadModeExecutor; + pub type Executor = crate::thread::ThreadModeExecutor; } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 1097bff8..54c84202 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -15,27 +15,22 @@ mod thread { static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(_core_id: OpaqueThreadContext) { + fn __thread_mode_pender(_context: OpaqueThreadContext) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } /// TODO // Name pending #[derive(Default)] // Default enables Executor::new - pub struct XtensaThreadContext; + pub struct Context; - impl ThreadContext for XtensaThreadContext { + impl ThreadContext for Context { #[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 { // Manual critical section implementation that only masks interrupts handlers. @@ -66,5 +61,5 @@ mod thread { /// TODO // Type alias for backwards compatibility - pub type Executor = crate::thread::ThreadModeExecutor; + pub type Executor = crate::thread::ThreadModeExecutor; } diff --git a/embassy-executor/src/interrupt.rs b/embassy-executor/src/interrupt.rs index c1084ea7..6f310651 100644 --- a/embassy-executor/src/interrupt.rs +++ b/embassy-executor/src/interrupt.rs @@ -7,13 +7,17 @@ use atomic_polyfill::{AtomicBool, Ordering}; use crate::raw::{self, OpaqueInterruptContext, Pender}; -/// An interrupt source that can be used to drive an [`InterruptExecutor`]. -// Name pending +/// Architecture-specific interface for an interrupt-mode executor. This trait describes what data +/// should be passed to the [`InterruptExecutor`]'s pender, and how to enable the interrupt that +/// triggers polling the executor. +// TODO: Name pending pub trait InterruptContext { - /// Creates an opaque identifier for this interrupt. + /// A pointer-sized piece of data that is passed to the pender function. + /// + /// Usually, the context contains the interrupt that should be used to wake the executor. fn context(&self) -> OpaqueInterruptContext; - /// Sets up the interrupt request. + /// Enabled the interrupt request. fn enable(&self); } @@ -36,8 +40,8 @@ pub trait InterruptContext { /// 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. +/// It is somewhat more complex to use, it's recommended to use the +/// [`crate::thread::ThreadModeExecutor`] instead, if it works for your use case. pub struct InterruptModeExecutor { started: AtomicBool, executor: UnsafeCell>, diff --git a/embassy-executor/src/thread.rs b/embassy-executor/src/thread.rs index 2d2c6daa..f977d41e 100644 --- a/embassy-executor/src/thread.rs +++ b/embassy-executor/src/thread.rs @@ -5,13 +5,21 @@ use core::marker::PhantomData; use crate::raw::{OpaqueThreadContext, Pender}; use crate::{raw, Spawner}; -/// TODO -// Name pending +/// 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 { - /// TODO + /// 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. + #[cfg(feature = "thread-context")] fn context(&self) -> OpaqueThreadContext; - /// TODO + /// 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); } @@ -40,11 +48,17 @@ impl ThreadModeExecutor { Self::with_context(C::default()) } - /// Create a new Executor. - pub fn with_context(context: C) -> Self { + /// Create a new Executor using the given thread context. + pub fn with_context(thread_context: C) -> Self { + #[cfg(not(feature = "thread-context"))] + let context = OpaqueThreadContext(()); + + #[cfg(feature = "thread-context")] + let context = thread_context.context(); + Self { - inner: raw::Executor::new(Pender::Thread(context.context())), - context, + inner: raw::Executor::new(Pender::Thread(context)), + context: thread_context, not_send: PhantomData, } } From ec6bd27df6101bc5f77fa4eace0e8963970231ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 08:22:22 +0200 Subject: [PATCH 06/16] Remove thread-context feature --- embassy-executor/Cargo.toml | 7 ++----- embassy-executor/src/arch/cortex_m.rs | 2 -- embassy-executor/src/arch/riscv32.rs | 2 -- embassy-executor/src/arch/std.rs | 3 --- embassy-executor/src/arch/wasm.rs | 3 --- embassy-executor/src/arch/xtensa.rs | 2 -- embassy-executor/src/raw/mod.rs | 7 ------- embassy-executor/src/thread.rs | 13 +++---------- 8 files changed, 5 insertions(+), 34 deletions(-) diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 182dd693..ce5e2741 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", "thread-context"] +arch-std = ["_arch", "critical-section/std"] arch-cortex-m = ["_arch", "dep:cortex-m"] arch-xtensa = ["_arch"] arch-riscv32 = ["_arch"] -arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "thread-context"] +arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] # Enable creating a `Pender` from an arbitrary function pointer callback. pender-callback = [] @@ -45,9 +45,6 @@ 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 6c1300ae..a29e5b23 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -21,9 +21,7 @@ mod thread { pub struct Context; impl ThreadContext for Context { - #[cfg(feature = "thread-context")] fn context(&self) -> OpaqueThreadContext { - // Enabling thread-context is not incorrect, just wasteful. OpaqueThreadContext(0) } diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index 08720400..976e7bcb 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -27,9 +27,7 @@ mod thread { pub struct Context; impl ThreadContext for Context { - #[cfg(feature = "thread-context")] fn context(&self) -> OpaqueThreadContext { - // Enabling thread-context is not incorrect, just wasteful. OpaqueThreadContext(0) } diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 2731e275..ceaa5c7a 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -1,9 +1,6 @@ #[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")] diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index e244c0b3..c393722b 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -1,9 +1,6 @@ #[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")] diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 54c84202..28abf352 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -25,9 +25,7 @@ mod thread { pub struct Context; impl ThreadContext for Context { - #[cfg(feature = "thread-context")] fn context(&self) -> OpaqueThreadContext { - // Enabling thread-context is not incorrect, just wasteful. OpaqueThreadContext(0) } diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 7fd29db4..7795f1e4 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -292,13 +292,6 @@ 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); diff --git a/embassy-executor/src/thread.rs b/embassy-executor/src/thread.rs index f977d41e..ef703003 100644 --- a/embassy-executor/src/thread.rs +++ b/embassy-executor/src/thread.rs @@ -13,7 +13,6 @@ pub trait ThreadContext: Sized { /// /// For example, on multi-core systems, this can be used to store the ID of the core that /// should be woken up. - #[cfg(feature = "thread-context")] fn context(&self) -> OpaqueThreadContext; /// Waits for the executor to be waken. @@ -49,16 +48,10 @@ impl ThreadModeExecutor { } /// Create a new Executor using the given thread context. - pub fn with_context(thread_context: C) -> Self { - #[cfg(not(feature = "thread-context"))] - let context = OpaqueThreadContext(()); - - #[cfg(feature = "thread-context")] - let context = thread_context.context(); - + pub fn with_context(context: C) -> Self { Self { - inner: raw::Executor::new(Pender::Thread(context)), - context: thread_context, + inner: raw::Executor::new(Pender::Thread(context.context())), + context, not_send: PhantomData, } } From 454a7cbf4c0eb3a4e651e7da5512ec49ff7d4050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 08:32:26 +0200 Subject: [PATCH 07/16] Remove pender-callback --- embassy-executor/Cargo.toml | 7 ++----- embassy-executor/src/raw/mod.rs | 19 ------------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index ce5e2741..d190c95a 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -14,7 +14,7 @@ categories = [ [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" -features = ["nightly", "defmt", "pender-callback"] +features = ["nightly", "defmt"] flavors = [ { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] }, { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] }, @@ -25,7 +25,7 @@ flavors = [ [package.metadata.docs.rs] default-target = "thumbv7em-none-eabi" targets = ["thumbv7em-none-eabi"] -features = ["nightly", "defmt", "pender-callback", "arch-cortex-m", "executor-thread", "executor-interrupt"] +features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] [features] @@ -37,9 +37,6 @@ arch-xtensa = ["_arch"] arch-riscv32 = ["_arch"] arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] -# Enable creating a `Pender` from an arbitrary function pointer callback. -pender-callback = [] - # Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) executor-thread = [] # Enable the interrupt-mode executor (available in Cortex-M only) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 7795f1e4..81ad1e53 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -317,28 +317,11 @@ pub enum Pender { /// Pender for an interrupt-mode executor. #[cfg(feature = "executor-interrupt")] Interrupt(OpaqueInterruptContext), - - /// Arbitrary, dynamically dispatched pender. - #[cfg(feature = "pender-callback")] - Callback { func: fn(*mut ()), context: *mut () }, } unsafe impl Send for Pender {} unsafe impl Sync for Pender {} -impl Pender { - /// Create a `Pender` that will call an arbitrary function pointer. - /// - /// # Arguments - /// - /// - `func`: The function pointer to call. - /// - `context`: Opaque context pointer, that will be passed to the function pointer. - #[cfg(feature = "pender-callback")] - pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self { - Self::Callback { func, context } - } -} - impl Pender { pub(crate) fn pend(self) { match self { @@ -356,8 +339,6 @@ impl Pender { } unsafe { __interrupt_mode_pender(interrupt) }; } - #[cfg(feature = "pender-callback")] - Pender::Callback { func, context } => func(context), } } } From f6007869bffd3ed4f48e74222dc40d11c7c87ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 08:57:14 +0200 Subject: [PATCH 08/16] Remove the Pender enum --- embassy-executor/src/arch/cortex_m.rs | 103 +++++++++++++++++--------- embassy-executor/src/arch/riscv32.rs | 10 +-- embassy-executor/src/arch/std.rs | 10 +-- embassy-executor/src/arch/wasm.rs | 6 +- embassy-executor/src/arch/xtensa.rs | 8 +- embassy-executor/src/interrupt.rs | 6 +- embassy-executor/src/raw/mod.rs | 48 ++---------- embassy-executor/src/thread.rs | 8 +- 8 files changed, 98 insertions(+), 101 deletions(-) diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index a29e5b23..7f8a97ef 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -1,28 +1,84 @@ #[cfg(feature = "executor-thread")] pub use thread::*; +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) { + unsafe { + // 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) + } + } +} + +#[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 { + // 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")] mod thread { #[cfg(feature = "nightly")] pub use embassy_macros::main_cortex_m as main; - use crate::raw::OpaqueThreadContext; + use crate::raw::PenderContext; use crate::thread::ThreadContext; - #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(_context: OpaqueThreadContext) { - unsafe { core::arch::asm!("sev") } - } - /// TODO // Name pending #[derive(Default)] // Default enables Executor::new pub struct Context; impl ThreadContext for Context { - fn context(&self) -> OpaqueThreadContext { - OpaqueThreadContext(0) + fn context(&self) -> PenderContext { + usize::MAX } fn wait(&mut self) { @@ -35,7 +91,6 @@ mod thread { 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")] @@ -44,23 +99,14 @@ mod interrupt { use cortex_m::peripheral::NVIC; use crate::interrupt::InterruptContext; - use crate::raw::OpaqueInterruptContext; - - #[derive(Clone, Copy)] - struct CortexMInterruptContext(u16); - - unsafe impl cortex_m::interrupt::InterruptNumber for CortexMInterruptContext { - fn number(self) -> u16 { - self.0 - } - } + use crate::raw::PenderContext; impl InterruptContext for T where T: InterruptNumber, { - fn context(&self) -> OpaqueInterruptContext { - OpaqueInterruptContext(self.number() as usize) + fn context(&self) -> PenderContext { + self.number() as usize } fn enable(&self) { @@ -68,21 +114,6 @@ mod interrupt { } } - #[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 976e7bcb..886056e8 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -10,14 +10,14 @@ mod thread { #[cfg(feature = "nightly")] pub use embassy_macros::main_riscv as main; - use crate::raw::OpaqueThreadContext; + use crate::raw::PenderContext; 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); - #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(_context: OpaqueThreadContext) { + #[export_name = "__pender"] + fn __thread_mode_pender(_context: PenderContext) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } @@ -27,8 +27,8 @@ mod thread { pub struct Context; impl ThreadContext for Context { - fn context(&self) -> OpaqueThreadContext { - OpaqueThreadContext(0) + fn context(&self) -> PenderContext { + 0 } fn wait(&mut self) { diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index ceaa5c7a..d2a069d1 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -10,7 +10,7 @@ mod thread { #[cfg(feature = "nightly")] pub use embassy_macros::main_std as main; - use crate::raw::OpaqueThreadContext; + use crate::raw::PenderContext; use crate::thread::ThreadContext; /// TODO @@ -28,8 +28,8 @@ mod thread { } impl ThreadContext for Context { - fn context(&self) -> OpaqueThreadContext { - OpaqueThreadContext(self.signaler as *const _ as usize) + fn context(&self) -> PenderContext { + self.signaler as *const _ as usize } fn wait(&mut self) { @@ -37,8 +37,8 @@ mod thread { } } - #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(context: OpaqueThreadContext) { + #[export_name = "__pender"] + fn __pender(context: PenderContext) { let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; signaler.signal() } diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index c393722b..634f48d1 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -14,11 +14,11 @@ mod thread { use wasm_bindgen::prelude::*; use crate::raw::util::UninitCell; - use crate::raw::{OpaqueThreadContext, Pender}; + use crate::raw::PenderContext; use crate::{raw, Spawner}; #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(context: OpaqueThreadContext) { + fn __thread_mode_pender(context: PenderContext) { let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); } @@ -49,7 +49,7 @@ mod thread { pub fn new() -> Self { let ctx = &*Box::leak(Box::new(WasmContext::new())); Self { - inner: raw::Executor::new(Pender::Thread(OpaqueThreadContext(ctx as *const _ as usize))), + inner: raw::Executor::new(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 28abf352..3986c6c1 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -8,14 +8,14 @@ mod thread { use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; - use crate::raw::OpaqueThreadContext; + use crate::raw::PenderContext; use crate::thread::ThreadContext; /// 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: OpaqueThreadContext) { + fn __thread_mode_pender(_context: PenderContext) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } @@ -25,8 +25,8 @@ mod thread { pub struct Context; impl ThreadContext for Context { - fn context(&self) -> OpaqueThreadContext { - OpaqueThreadContext(0) + fn context(&self) -> PenderContext { + 0 } fn wait(&mut self) { diff --git a/embassy-executor/src/interrupt.rs b/embassy-executor/src/interrupt.rs index 6f310651..28a1cd52 100644 --- a/embassy-executor/src/interrupt.rs +++ b/embassy-executor/src/interrupt.rs @@ -5,7 +5,7 @@ use core::mem::MaybeUninit; use atomic_polyfill::{AtomicBool, Ordering}; -use crate::raw::{self, OpaqueInterruptContext, Pender}; +use crate::raw::{self, PenderContext}; /// Architecture-specific interface for an interrupt-mode executor. This trait describes what data /// should be passed to the [`InterruptExecutor`]'s pender, and how to enable the interrupt that @@ -15,7 +15,7 @@ pub trait InterruptContext { /// A pointer-sized piece of data that is passed to the pender function. /// /// Usually, the context contains the interrupt that should be used to wake the executor. - fn context(&self) -> OpaqueInterruptContext; + fn context(&self) -> PenderContext; /// Enabled the interrupt request. fn enable(&self); @@ -104,7 +104,7 @@ impl InterruptModeExecutor { unsafe { (&mut *self.executor.get()) .as_mut_ptr() - .write(raw::Executor::new(Pender::Interrupt(irq.context()))) + .write(raw::Executor::new(irq.context())) } let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 81ad1e53..a0a940e2 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -292,54 +292,20 @@ impl TaskPool { } /// Context given to the thread-mode executor's pender. -#[repr(transparent)] -#[derive(Clone, Copy)] -pub struct OpaqueThreadContext(pub(crate) usize); +pub type PenderContext = usize; -/// Context given to the interrupt-mode executor's pender. #[derive(Clone, Copy)] -#[repr(transparent)] -pub struct OpaqueInterruptContext(pub(crate) usize); - -/// Platform/architecture-specific action executed when an executor has pending work. -/// -/// When a task within an executor is woken, the `Pender` is called. This does a -/// platform/architecture-specific action to signal there is pending work in the executor. -/// When this happens, you must arrange for [`Executor::poll`] to be called. -/// -/// You can think of it as a waker, but for the whole executor. -#[derive(Clone, Copy)] -pub enum Pender { - /// Pender for a thread-mode executor. - #[cfg(feature = "executor-thread")] - Thread(OpaqueThreadContext), - - /// Pender for an interrupt-mode executor. - #[cfg(feature = "executor-interrupt")] - Interrupt(OpaqueInterruptContext), -} +pub(crate) struct Pender(PenderContext); unsafe impl Send for Pender {} unsafe impl Sync for Pender {} impl Pender { pub(crate) fn pend(self) { - match self { - #[cfg(feature = "executor-thread")] - Pender::Thread(core_id) => { - extern "Rust" { - fn __thread_mode_pender(core_id: OpaqueThreadContext); - } - unsafe { __thread_mode_pender(core_id) }; - } - #[cfg(feature = "executor-interrupt")] - Pender::Interrupt(interrupt) => { - extern "Rust" { - fn __interrupt_mode_pender(interrupt: OpaqueInterruptContext); - } - unsafe { __interrupt_mode_pender(interrupt) }; - } + extern "Rust" { + fn __pender(context: PenderContext); } + unsafe { __pender(self.0) }; } } @@ -499,9 +465,9 @@ impl Executor { /// When the executor has work to do, it will call the [`Pender`]. /// /// See [`Executor`] docs for details on `Pender`. - pub fn new(pender: Pender) -> Self { + pub fn new(context: PenderContext) -> Self { Self { - inner: SyncExecutor::new(pender), + inner: SyncExecutor::new(Pender(context)), _not_sync: PhantomData, } } diff --git a/embassy-executor/src/thread.rs b/embassy-executor/src/thread.rs index ef703003..8ff4071d 100644 --- a/embassy-executor/src/thread.rs +++ b/embassy-executor/src/thread.rs @@ -2,8 +2,8 @@ use core::marker::PhantomData; -use crate::raw::{OpaqueThreadContext, Pender}; -use crate::{raw, Spawner}; +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. @@ -13,7 +13,7 @@ pub trait ThreadContext: Sized { /// /// 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) -> OpaqueThreadContext; + fn context(&self) -> PenderContext; /// Waits for the executor to be waken. /// @@ -50,7 +50,7 @@ impl ThreadModeExecutor { /// Create a new Executor using the given thread context. pub fn with_context(context: C) -> Self { Self { - inner: raw::Executor::new(Pender::Thread(context.context())), + inner: raw::Executor::new(context.context()), context, not_send: PhantomData, } From 4c4b12c307bf77516299eb73f9da00ef777b9814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 15:16:40 +0200 Subject: [PATCH 09/16] Make PenderContext opaque --- embassy-executor/src/arch/cortex_m.rs | 6 ++++-- embassy-executor/src/arch/riscv32.rs | 2 +- embassy-executor/src/arch/std.rs | 2 +- embassy-executor/src/arch/wasm.rs | 2 +- embassy-executor/src/arch/xtensa.rs | 2 +- embassy-executor/src/raw/mod.rs | 25 ++++++++++++++++++++++--- 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 7f8a97ef..439db0fc 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -36,6 +36,7 @@ unsafe fn nvic_pend(irq: u16) { #[export_name = "__pender"] fn __pender(context: 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 { @@ -56,6 +57,7 @@ fn __pender(_context: PenderContext) { #[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) @@ -78,7 +80,7 @@ mod thread { impl ThreadContext for Context { fn context(&self) -> PenderContext { - usize::MAX + unsafe { core::mem::transmute(usize::MAX) } } fn wait(&mut self) { @@ -106,7 +108,7 @@ mod interrupt { T: InterruptNumber, { fn context(&self) -> PenderContext { - self.number() as usize + unsafe { core::mem::transmute(self.number() as usize) } } fn enable(&self) { diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index 886056e8..f76a4bcf 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -28,7 +28,7 @@ mod thread { impl ThreadContext for Context { fn context(&self) -> PenderContext { - 0 + unsafe { core::mem::transmute(0) } } fn wait(&mut self) { diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index d2a069d1..d55de118 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -29,7 +29,7 @@ mod thread { impl ThreadContext for Context { fn context(&self) -> PenderContext { - self.signaler as *const _ as usize + unsafe { core::mem::transmute(self.signaler) } } fn wait(&mut self) { diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index 634f48d1..452c3e39 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -49,7 +49,7 @@ mod thread { pub fn new() -> Self { let ctx = &*Box::leak(Box::new(WasmContext::new())); Self { - inner: raw::Executor::new(ctx as *const _ as usize), + inner: raw::Executor::new(unsafe { core::mem::transmute(ctx) }), ctx, not_send: PhantomData, } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 3986c6c1..1aea9f23 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -26,7 +26,7 @@ mod thread { impl ThreadContext for Context { fn context(&self) -> PenderContext { - 0 + unsafe { core::mem::transmute(0) } } fn wait(&mut self) { diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index a0a940e2..4a6e4553 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -292,10 +292,29 @@ impl TaskPool { } /// Context given to the thread-mode executor's pender. -pub type PenderContext = usize; - +#[repr(transparent)] #[derive(Clone, Copy)] -pub(crate) struct Pender(PenderContext); +pub struct PenderContext(usize); + +/// Platform/architecture-specific action executed when an executor has pending work. +/// +/// When a task within an executor is woken, the `Pender` is called. This does a +/// platform/architecture-specific action to signal there is pending work in the executor. +/// When this happens, you must arrange for [`Executor::poll`] to be called. +/// +/// You can think of it as a waker, but for the whole executor. +/// +/// Platform/architecture implementations must provide a function that can be referred to as: +/// +/// ```rust +/// use embassy_executor::raw::PenderContext; +/// +/// extern "Rust" { +/// fn __pender(context: PenderContext); +/// } +/// ``` +#[derive(Clone, Copy)] +pub struct Pender(PenderContext); unsafe impl Send for Pender {} unsafe impl Sync for Pender {} 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 10/16] 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(); - }; - } - } -} From 3a51e2d9cae6fad2fd903c07634b4a66de59b3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 15:45:43 +0200 Subject: [PATCH 11/16] Make PenderContext actually pointer-size --- embassy-executor/src/raw/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 4a6e4553..2bbbb132 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -294,7 +294,7 @@ impl TaskPool { /// Context given to the thread-mode executor's pender. #[repr(transparent)] #[derive(Clone, Copy)] -pub struct PenderContext(usize); +pub struct PenderContext(*mut ()); /// Platform/architecture-specific action executed when an executor has pending work. /// From 995434614384bc5c218a16a026ce7c06737ca860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 15:59:47 +0200 Subject: [PATCH 12/16] Remove interrupt executor, remove PenderContext --- embassy-executor/src/arch/cortex_m.rs | 9 +- embassy-executor/src/arch/riscv32.rs | 4 +- embassy-executor/src/arch/std.rs | 6 +- embassy-executor/src/arch/wasm.rs | 7 +- embassy-executor/src/arch/xtensa.rs | 4 +- embassy-executor/src/interrupt.rs | 131 -------------------------- embassy-executor/src/lib.rs | 5 - embassy-executor/src/raw/mod.rs | 17 +--- 8 files changed, 19 insertions(+), 164 deletions(-) delete mode 100644 embassy-executor/src/interrupt.rs diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 2ed70dd1..8fe5644d 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -2,12 +2,12 @@ const THREAD_PENDER: usize = usize::MAX; #[export_name = "__pender"] #[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] -fn __pender(context: crate::raw::PenderContext) { +fn __pender(context: *mut ()) { unsafe { // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt // request number given to `InterruptExecutor::start`. - let context: usize = core::mem::transmute(context); + let context = context as usize; #[cfg(feature = "executor-thread")] if context == THREAD_PENDER { @@ -75,7 +75,7 @@ mod thread { /// Create a new Executor. pub fn new() -> Self { Self { - inner: raw::Executor::new(unsafe { core::mem::transmute(THREAD_PENDER) }), + inner: raw::Executor::new(THREAD_PENDER as *mut ()), not_send: PhantomData, } } @@ -205,10 +205,9 @@ mod interrupt { } unsafe { - let context = core::mem::transmute(irq.number() as usize); (&mut *self.executor.get()) .as_mut_ptr() - .write(raw::Executor::new(context)) + .write(raw::Executor::new(irq.number() as *mut ())) } let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index 551d7527..ce78bc25 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -17,7 +17,7 @@ mod thread { static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); #[export_name = "__pender"] - fn __thread_mode_pender(_context: crate::raw::PenderContext) { + fn __thread_mode_pender(_context: *mut ()) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } @@ -31,7 +31,7 @@ mod thread { /// Create a new Executor. pub fn new() -> Self { Self { - inner: raw::Executor::new(unsafe { core::mem::transmute(0) }), + inner: raw::Executor::new(core::ptr::null_mut()), not_send: PhantomData, } } diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index f490084d..5b2f7e2e 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -14,7 +14,7 @@ mod thread { use crate::{raw, Spawner}; #[export_name = "__pender"] - fn __pender(context: crate::raw::PenderContext) { + fn __pender(context: *mut ()) { let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; signaler.signal() } @@ -29,9 +29,9 @@ mod thread { impl Executor { /// Create a new Executor. pub fn new() -> Self { - let signaler = &*Box::leak(Box::new(Signaler::new())); + let signaler = Box::leak(Box::new(Signaler::new())); Self { - inner: raw::Executor::new(unsafe { std::mem::transmute(signaler) }), + inner: raw::Executor::new(signaler as *mut Signaler as *mut ()), not_send: PhantomData, signaler, } diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index 452c3e39..5f9b2e70 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -14,11 +14,10 @@ mod thread { use wasm_bindgen::prelude::*; use crate::raw::util::UninitCell; - use crate::raw::PenderContext; use crate::{raw, Spawner}; #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(context: PenderContext) { + fn __thread_mode_pender(context: *mut ()) { let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); } @@ -47,9 +46,9 @@ mod thread { impl Executor { /// Create a new Executor. pub fn new() -> Self { - let ctx = &*Box::leak(Box::new(WasmContext::new())); + let ctx = Box::leak(Box::new(WasmContext::new())); Self { - inner: raw::Executor::new(unsafe { core::mem::transmute(ctx) }), + inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()), ctx, not_send: PhantomData, } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 8665a9cb..66b3351c 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -14,7 +14,7 @@ mod thread { static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(_context: crate::raw::PenderContext) { + fn __thread_mode_pender(_context: *mut ()) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } @@ -28,7 +28,7 @@ mod thread { /// Create a new Executor. pub fn new() -> Self { Self { - inner: raw::Executor::new(unsafe { core::mem::transmute(0) }), + inner: raw::Executor::new(core::ptr::null_mut()), not_send: PhantomData, } } diff --git a/embassy-executor/src/interrupt.rs b/embassy-executor/src/interrupt.rs deleted file mode 100644 index b68754ab..00000000 --- a/embassy-executor/src/interrupt.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Interrupt-mode executor. - -use core::cell::UnsafeCell; -use core::mem::MaybeUninit; - -use atomic_polyfill::{AtomicBool, Ordering}; - -use crate::raw::{self, PenderContext}; - -/// Architecture-specific interface for an interrupt-mode executor. This trait describes what data -/// should be passed to the [`InterruptExecutor`]'s pender, and how to enable the interrupt that -/// triggers polling the executor. -// TODO: Name pending -pub trait InterruptContext { - /// A pointer-sized piece of data that is passed to the pender function. - /// - /// Usually, the context contains the interrupt that should be used to wake the executor. - fn context(&self) -> PenderContext; - - /// Enabled 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(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 3be32d9c..3ce687eb 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -37,11 +37,6 @@ pub use arch::*; pub mod raw; -#[cfg(feature = "executor-interrupt")] -pub mod interrupt; -#[cfg(feature = "executor-interrupt")] -pub use interrupt::*; - mod spawner; pub use spawner::*; diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 2bbbb132..aa99b4cf 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -291,11 +291,6 @@ impl TaskPool { } } -/// Context given to the thread-mode executor's pender. -#[repr(transparent)] -#[derive(Clone, Copy)] -pub struct PenderContext(*mut ()); - /// Platform/architecture-specific action executed when an executor has pending work. /// /// When a task within an executor is woken, the `Pender` is called. This does a @@ -306,15 +301,13 @@ pub struct PenderContext(*mut ()); /// /// Platform/architecture implementations must provide a function that can be referred to as: /// -/// ```rust -/// use embassy_executor::raw::PenderContext; -/// +/// ```rust/// /// extern "Rust" { -/// fn __pender(context: PenderContext); +/// fn __pender(context: *mut ()); /// } /// ``` #[derive(Clone, Copy)] -pub struct Pender(PenderContext); +pub struct Pender(*mut ()); unsafe impl Send for Pender {} unsafe impl Sync for Pender {} @@ -322,7 +315,7 @@ unsafe impl Sync for Pender {} impl Pender { pub(crate) fn pend(self) { extern "Rust" { - fn __pender(context: PenderContext); + fn __pender(context: *mut ()); } unsafe { __pender(self.0) }; } @@ -484,7 +477,7 @@ impl Executor { /// When the executor has work to do, it will call the [`Pender`]. /// /// See [`Executor`] docs for details on `Pender`. - pub fn new(context: PenderContext) -> Self { + pub fn new(context: *mut ()) -> Self { Self { inner: SyncExecutor::new(Pender(context)), _not_sync: PhantomData, From da4f15d94492667dd33087f95e28747b74b07811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 16:33:49 +0200 Subject: [PATCH 13/16] Fix fn name --- embassy-executor/src/arch/riscv32.rs | 2 +- embassy-executor/src/arch/wasm.rs | 4 ++-- embassy-executor/src/arch/xtensa.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index ce78bc25..40c6877e 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -17,7 +17,7 @@ mod thread { static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); #[export_name = "__pender"] - fn __thread_mode_pender(_context: *mut ()) { + fn __pender(_context: *mut ()) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index 5f9b2e70..934fd69e 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -16,8 +16,8 @@ mod thread { use crate::raw::util::UninitCell; use crate::{raw, Spawner}; - #[export_name = "__thread_mode_pender"] - fn __thread_mode_pender(context: *mut ()) { + #[export_name = "__pender"] + fn __pender(context: *mut ()) { let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 66b3351c..601d8500 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -13,8 +13,8 @@ mod thread { /// 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: *mut ()) { + #[export_name = "__pender"] + fn __pender(_context: *mut ()) { SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); } From 07c36001271ab0a033a08a6535719729efb677c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 16:35:12 +0200 Subject: [PATCH 14/16] Hide Pender --- embassy-executor/src/raw/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index aa99b4cf..06483102 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -301,13 +301,13 @@ impl TaskPool { /// /// Platform/architecture implementations must provide a function that can be referred to as: /// -/// ```rust/// +/// ```rust /// extern "Rust" { /// fn __pender(context: *mut ()); /// } /// ``` #[derive(Clone, Copy)] -pub struct Pender(*mut ()); +pub(crate) struct Pender(*mut ()); unsafe impl Send for Pender {} unsafe impl Sync for Pender {} From e4f3979ec8dd12fef1f44e78733917980045def0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 16:46:02 +0200 Subject: [PATCH 15/16] Don't check context if only thread-mode is enabled --- embassy-executor/src/arch/cortex_m.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 8fe5644d..0806a22a 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -10,7 +10,8 @@ fn __pender(context: *mut ()) { let context = context as usize; #[cfg(feature = "executor-thread")] - if context == THREAD_PENDER { + // Try to make Rust optimize the branching away if we only use thread mode. + if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { core::arch::asm!("sev"); return; } From 890f29ccfe129f3205cf835c7131862c579d9349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 14 Aug 2023 17:53:42 +0200 Subject: [PATCH 16/16] Update docs --- embassy-executor/CHANGELOG.md | 4 +++ embassy-executor/src/raw/mod.rs | 49 +++++++++++++++++---------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 86a47758..e2e7bce3 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Replaced Pender. Implementations now must define an extern function called `__pender`. + ## 0.2.1 - 2023-08-10 - Avoid calling `pend()` when waking expired timers diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 06483102..7caa3302 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -291,21 +291,6 @@ impl TaskPool { } } -/// Platform/architecture-specific action executed when an executor has pending work. -/// -/// When a task within an executor is woken, the `Pender` is called. This does a -/// platform/architecture-specific action to signal there is pending work in the executor. -/// When this happens, you must arrange for [`Executor::poll`] to be called. -/// -/// You can think of it as a waker, but for the whole executor. -/// -/// Platform/architecture implementations must provide a function that can be referred to as: -/// -/// ```rust -/// extern "Rust" { -/// fn __pender(context: *mut ()); -/// } -/// ``` #[derive(Clone, Copy)] pub(crate) struct Pender(*mut ()); @@ -451,15 +436,31 @@ impl SyncExecutor { /// /// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks /// that "want to run"). -/// - You must supply a [`Pender`]. The executor will call it to notify you it has work -/// to do. You must arrange for `poll()` to be called as soon as possible. +/// - You must supply a pender function, as shown below. The executor will call it to notify you +/// it has work to do. You must arrange for `poll()` to be called as soon as possible. +/// - Enabling `arch-xx` features will define a pender function for you. This means that you +/// are limited to using the executors provided to you by the architecture/platform +/// implementation. If you need a different executor, you must not enable `arch-xx` features. /// -/// The [`Pender`] can be called from *any* context: any thread, any interrupt priority +/// The pender can be called from *any* context: any thread, any interrupt priority /// level, etc. It may be called synchronously from any `Executor` method call as well. /// You must deal with this correctly. /// /// In particular, you must NOT call `poll` directly from the pender callback, as this violates /// the requirement for `poll` to not be called reentrantly. +/// +/// The pender function must be exported with the name `__pender` and have the following signature: +/// +/// ```rust +/// #[export_name = "__pender"] +/// fn pender(context: *mut ()) { +/// // schedule `poll()` to be called +/// } +/// ``` +/// +/// The `context` argument is a piece of arbitrary data the executor will pass to the pender. +/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example, +/// differentiate between executors, or to pass a pointer to a callback that should be called. #[repr(transparent)] pub struct Executor { pub(crate) inner: SyncExecutor, @@ -474,9 +475,9 @@ impl Executor { /// Create a new executor. /// - /// When the executor has work to do, it will call the [`Pender`]. + /// When the executor has work to do, it will call the pender function and pass `context` to it. /// - /// See [`Executor`] docs for details on `Pender`. + /// See [`Executor`] docs for details on the pender. pub fn new(context: *mut ()) -> Self { Self { inner: SyncExecutor::new(Pender(context)), @@ -502,16 +503,16 @@ impl Executor { /// This loops over all tasks that are queued to be polled (i.e. they're /// freshly spawned or they've been woken). Other tasks are not polled. /// - /// You must call `poll` after receiving a call to the [`Pender`]. It is OK - /// to call `poll` even when not requested by the `Pender`, but it wastes + /// You must call `poll` after receiving a call to the pender. It is OK + /// to call `poll` even when not requested by the pender, but it wastes /// energy. /// /// # Safety /// /// You must NOT call `poll` reentrantly on the same executor. /// - /// In particular, note that `poll` may call the `Pender` synchronously. Therefore, you - /// must NOT directly call `poll()` from the `Pender` callback. Instead, the callback has to + /// In particular, note that `poll` may call the pender synchronously. Therefore, you + /// must NOT directly call `poll()` from the pender callback. Instead, the callback has to /// somehow schedule for `poll()` to be called later, at a time you know for sure there's /// no `poll()` already running. pub unsafe fn poll(&'static self) {