esp_hal_embassy/executor/
interrupt.rs

1//! Interrupt-mode executor.
2
3use core::{cell::UnsafeCell, mem::MaybeUninit};
4
5use embassy_executor::SendSpawner;
6use esp_hal::{
7    interrupt::{self, InterruptHandler, software::SoftwareInterrupt},
8    system::Cpu,
9};
10use portable_atomic::{AtomicUsize, Ordering};
11
12use super::InnerExecutor;
13
14const COUNT: usize = 3 + cfg!(not(multi_core)) as usize;
15static mut EXECUTORS: [CallbackContext; COUNT] = [const { CallbackContext::new() }; COUNT];
16
17/// Interrupt mode executor.
18///
19/// This executor runs tasks in interrupt mode. The interrupt handler is set up
20/// to poll tasks, and when a task is woken the interrupt is pended from
21/// software.
22pub struct InterruptExecutor<const SWI: u8> {
23    core: AtomicUsize,
24    executor: UnsafeCell<MaybeUninit<InnerExecutor>>,
25    interrupt: SoftwareInterrupt<'static, SWI>,
26}
27
28unsafe impl<const SWI: u8> Send for InterruptExecutor<SWI> {}
29unsafe impl<const SWI: u8> Sync for InterruptExecutor<SWI> {}
30
31struct CallbackContext {
32    raw_executor: UnsafeCell<*mut InnerExecutor>,
33}
34
35impl CallbackContext {
36    const fn new() -> Self {
37        Self {
38            raw_executor: UnsafeCell::new(core::ptr::null_mut()),
39        }
40    }
41
42    /// # Safety:
43    ///
44    /// The caller must ensure `set` has been called before.
45    unsafe fn get(&self) -> &InnerExecutor {
46        unsafe { &**self.raw_executor.get() }
47    }
48
49    fn set(&self, executor: *mut InnerExecutor) {
50        unsafe { self.raw_executor.get().write(executor) };
51    }
52}
53
54extern "C" fn handle_interrupt<const NUM: u8>() {
55    let swi = unsafe { SoftwareInterrupt::<NUM>::steal() };
56    swi.reset();
57
58    unsafe {
59        // SAFETY: The executor is always initialized before the interrupt is enabled.
60        let executor = EXECUTORS[NUM as usize].get();
61        executor.inner.poll();
62    }
63}
64
65impl<const SWI: u8> InterruptExecutor<SWI> {
66    /// Create a new `InterruptExecutor`.
67    /// This takes the software interrupt to be used internally.
68    #[inline]
69    pub const fn new(interrupt: SoftwareInterrupt<'static, SWI>) -> Self {
70        Self {
71            core: AtomicUsize::new(usize::MAX),
72            executor: UnsafeCell::new(MaybeUninit::uninit()),
73            interrupt,
74        }
75    }
76
77    /// Start the executor at the given priority level.
78    ///
79    /// This initializes the executor, enables the interrupt, and returns.
80    /// The executor keeps running in the background through the interrupt.
81    ///
82    /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A
83    /// [`SendSpawner`] is returned instead of a
84    /// [`Spawner`](embassy_executor::Spawner) because the
85    /// executor effectively runs in a different "thread" (the interrupt),
86    /// so spawning tasks on it is effectively sending them.
87    ///
88    /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor,
89    /// use [`Spawner::for_current_executor`](embassy_executor::Spawner::for_current_executor)
90    /// from a task running in it.
91    pub fn start(&'static mut self, priority: interrupt::Priority) -> SendSpawner {
92        if self
93            .core
94            .compare_exchange(
95                usize::MAX,
96                Cpu::current() as usize,
97                Ordering::Acquire,
98                Ordering::Relaxed,
99            )
100            .is_err()
101        {
102            panic!("InterruptExecutor::start() called multiple times on the same executor.");
103        }
104
105        unsafe {
106            (*self.executor.get())
107                .as_mut_ptr()
108                .write(InnerExecutor::new(priority, (SWI as usize) as *mut ()));
109
110            EXECUTORS[SWI as usize].set((*self.executor.get()).as_mut_ptr());
111        }
112
113        let swi_handler = match SWI {
114            0 => handle_interrupt::<0>,
115            1 => handle_interrupt::<1>,
116            2 => handle_interrupt::<2>,
117            #[cfg(not(multi_core))]
118            3 => handle_interrupt::<3>,
119            _ => unreachable!(),
120        };
121
122        self.interrupt
123            .set_interrupt_handler(InterruptHandler::new(swi_handler, priority));
124
125        let executor = unsafe { (*self.executor.get()).assume_init_ref() };
126        executor.init();
127        executor.inner.spawner().make_send()
128    }
129
130    /// Get a SendSpawner for this executor
131    ///
132    /// This returns a [`SendSpawner`] you can use to spawn tasks on this
133    /// executor.
134    ///
135    /// This MUST only be called on an executor that has already been started.
136    /// The function will panic otherwise.
137    pub fn spawner(&'static self) -> SendSpawner {
138        if self.core.load(Ordering::Acquire) == usize::MAX {
139            panic!("InterruptExecutor::spawner() called on uninitialized executor.");
140        }
141        let executor = unsafe { (*self.executor.get()).assume_init_ref() };
142        executor.inner.spawner().make_send()
143    }
144}