90 lines
3.5 KiB
Rust
90 lines
3.5 KiB
Rust
//! Executor specific to cortex-m devices.
|
|
use core::marker::PhantomData;
|
|
|
|
pub use embassy::executor::*;
|
|
|
|
use crate::interrupt::{Interrupt, InterruptExt};
|
|
|
|
fn pend_by_number(n: u16) {
|
|
#[derive(Clone, Copy)]
|
|
struct N(u16);
|
|
unsafe impl cortex_m::interrupt::InterruptNumber for N {
|
|
fn number(self) -> u16 {
|
|
self.0
|
|
}
|
|
}
|
|
cortex_m::peripheral::NVIC::pend(N(n))
|
|
}
|
|
|
|
/// 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<I: Interrupt> {
|
|
irq: I,
|
|
inner: raw::Executor,
|
|
not_send: PhantomData<*mut ()>,
|
|
}
|
|
|
|
impl<I: Interrupt> InterruptExecutor<I> {
|
|
/// Create a new Executor.
|
|
pub fn new(irq: I) -> Self {
|
|
let ctx = irq.number() as *mut ();
|
|
Self {
|
|
irq,
|
|
inner: raw::Executor::new(|ctx| pend_by_number(ctx as u16), ctx),
|
|
not_send: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Start the executor.
|
|
///
|
|
/// This initializes the executor, configures and 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.
|
|
///
|
|
/// 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 [Forever](embassy::util::Forever) (safe)
|
|
/// - a `static mut` (unsafe)
|
|
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
|
pub fn start(&'static mut self) -> SendSpawner {
|
|
self.irq.disable();
|
|
|
|
self.irq.set_handler(|ctx| unsafe {
|
|
let executor = &*(ctx as *const raw::Executor);
|
|
executor.poll();
|
|
});
|
|
self.irq.set_handler_context(&self.inner as *const _ as _);
|
|
self.irq.enable();
|
|
|
|
self.inner.spawner().make_send()
|
|
}
|
|
}
|