esp_rtos/embassy/
mod.rs

1//! OS-aware embassy executors.
2
3use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::Ordering};
4
5use embassy_executor::{SendSpawner, Spawner, raw};
6use esp_hal::interrupt::{InterruptHandler, Priority, software::SoftwareInterrupt};
7#[cfg(multi_core)]
8use esp_hal::system::Cpu;
9use macros::ram;
10use portable_atomic::AtomicPtr;
11
12use crate::task::flags::ThreadFlags;
13
14#[unsafe(export_name = "__pender")]
15#[ram]
16fn __pender(context: *mut ()) {
17    match context as usize {
18        0 => unsafe { SoftwareInterrupt::<0>::steal().raise() },
19        1 => unsafe { SoftwareInterrupt::<1>::steal().raise() },
20        2 => unsafe { SoftwareInterrupt::<2>::steal().raise() },
21        3 => unsafe { SoftwareInterrupt::<3>::steal().raise() },
22        _ => {
23            // This forces us to keep the embassy timer queue separate, otherwise we'd need to
24            // reentrantly lock SCHEDULER.
25            let flags = unwrap!(unsafe { context.cast::<ThreadFlags>().as_ref() });
26            flags.set(1);
27        }
28    }
29}
30
31/// Callbacks to run code before/after polling the task queue.
32pub trait Callbacks {
33    /// Called just before polling the executor.
34    fn before_poll(&mut self);
35
36    /// Called after the executor is polled, if there is no work scheduled.
37    ///
38    /// Note that tasks can become ready at any point during the execution
39    /// of this function.
40    fn on_idle(&mut self);
41}
42
43/// Thread-mode executor.
44///
45/// This executor runs in an OS thread, meaning the scheduler needs to be started before using any
46/// async operations.
47#[cfg_attr(
48    multi_core,
49    doc = r"
50
51If you want to start the executor on the second core, you will need to start the second core using [`crate::start_second_core`].
52If you are looking for a way to run code on the second core without the scheduler, use the [`InterruptExecutor`].
53"
54)]
55pub struct Executor {
56    executor: UnsafeCell<MaybeUninit<raw::Executor>>,
57}
58
59impl Executor {
60    /// Create a new thread-mode executor.
61    pub const fn new() -> Self {
62        Self {
63            executor: UnsafeCell::new(MaybeUninit::uninit()),
64        }
65    }
66
67    /// Run the executor.
68    ///
69    /// The `init` closure is called with a [`Spawner`] that spawns tasks on
70    /// this executor. Use it to spawn the initial task(s). After `init`
71    /// returns, the executor starts running the tasks.
72    ///
73    /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is
74    /// `Copy`), for example by passing it as an argument to the initial
75    /// tasks.
76    ///
77    /// This function requires `&'static mut self`. This means you have to store
78    /// the Executor instance in a place where it'll live forever and grants
79    /// you mutable access. There's a few ways to do this:
80    ///
81    /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
82    /// - a `static mut` (unsafe, not recommended)
83    /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading
84    ///   its lifetime with `transmute`. (unsafe)
85    ///
86    /// This function never returns.
87    pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
88        let flags = ThreadFlags::new();
89        struct NoHooks;
90
91        impl Callbacks for NoHooks {
92            fn before_poll(&mut self) {}
93
94            fn on_idle(&mut self) {}
95        }
96
97        self.run_inner(init, &flags, NoHooks)
98    }
99
100    /// Run the executor with callbacks.
101    ///
102    /// See [Callbacks] on when the callbacks are called.
103    ///
104    /// See [Self::run] for more information about running the executor.
105    ///
106    /// This function never returns.
107    pub fn run_with_callbacks(
108        &'static mut self,
109        init: impl FnOnce(Spawner),
110        callbacks: impl Callbacks,
111    ) -> ! {
112        let flags = ThreadFlags::new();
113        struct Hooks<'a, CB: Callbacks>(CB, &'a ThreadFlags);
114
115        impl<CB: Callbacks> Callbacks for Hooks<'_, CB> {
116            fn before_poll(&mut self) {
117                self.0.before_poll()
118            }
119
120            fn on_idle(&mut self) {
121                // Make sure we only call on_idle if the executor would otherwise go to sleep.
122                if self.1.get() == 0 {
123                    self.0.on_idle();
124                }
125            }
126        }
127
128        self.run_inner(init, &flags, Hooks(callbacks, &flags))
129    }
130
131    fn run_inner(
132        &'static self,
133        init: impl FnOnce(Spawner),
134        flags: &ThreadFlags,
135        mut hooks: impl Callbacks,
136    ) -> ! {
137        let executor = unsafe {
138            (&mut *self.executor.get()).write(raw::Executor::new(
139                (flags as *const ThreadFlags).cast::<()>().cast_mut(),
140            ))
141        };
142
143        #[cfg(multi_core)]
144        if Cpu::current() != Cpu::ProCpu
145            && crate::SCHEDULER
146                .with(|scheduler| !scheduler.per_cpu[Cpu::current() as usize].initialized)
147        {
148            panic!("Executor cannot be started: the scheduler is not running on the current CPU.");
149        }
150
151        init(executor.spawner());
152
153        loop {
154            hooks.before_poll();
155
156            unsafe { executor.poll() };
157
158            hooks.on_idle();
159
160            // Wait for work to become available.
161            flags.wait(1, None);
162        }
163    }
164}
165
166impl Default for Executor {
167    fn default() -> Self {
168        Self::new()
169    }
170}
171
172/// Interrupt mode executor.
173///
174/// This executor runs tasks in interrupt mode. The interrupt handler is set up
175/// to poll tasks, and when a task is woken the interrupt is pended from
176/// software.
177///
178/// Interrupt executors have potentially lower latency than thread-mode executors, but only a
179/// limited number can be created.
180pub struct InterruptExecutor<const SWI: u8> {
181    executor: UnsafeCell<MaybeUninit<raw::Executor>>,
182    interrupt: SoftwareInterrupt<'static, SWI>,
183}
184
185const COUNT: usize = 4;
186static INTERRUPT_EXECUTORS: [InterruptExecutorStorage; COUNT] =
187    [const { InterruptExecutorStorage::new() }; COUNT];
188
189unsafe impl<const SWI: u8> Send for InterruptExecutor<SWI> {}
190unsafe impl<const SWI: u8> Sync for InterruptExecutor<SWI> {}
191
192struct InterruptExecutorStorage {
193    raw_executor: AtomicPtr<raw::Executor>,
194}
195
196impl InterruptExecutorStorage {
197    const fn new() -> Self {
198        Self {
199            raw_executor: AtomicPtr::new(core::ptr::null_mut()),
200        }
201    }
202
203    /// # Safety:
204    ///
205    /// The caller must ensure `set` has been called before.
206    unsafe fn get(&self) -> &raw::Executor {
207        unsafe { &*self.raw_executor.load(Ordering::Relaxed) }
208    }
209
210    fn set(&self, executor: *mut raw::Executor) {
211        self.raw_executor.store(executor, Ordering::Relaxed);
212    }
213}
214
215extern "C" fn handle_interrupt<const NUM: u8>() {
216    let swi = unsafe { SoftwareInterrupt::<NUM>::steal() };
217    swi.reset();
218
219    unsafe {
220        // SAFETY: The executor is always initialized before the interrupt is enabled.
221        let executor = INTERRUPT_EXECUTORS[NUM as usize].get();
222        executor.poll();
223    }
224}
225
226impl<const SWI: u8> InterruptExecutor<SWI> {
227    /// Create a new `InterruptExecutor`.
228    /// This takes the software interrupt to be used internally.
229    #[inline]
230    pub const fn new(interrupt: SoftwareInterrupt<'static, SWI>) -> Self {
231        Self {
232            executor: UnsafeCell::new(MaybeUninit::uninit()),
233            interrupt,
234        }
235    }
236
237    /// Start the executor at the given priority level.
238    ///
239    /// This initializes the executor, enables the interrupt, and returns.
240    /// The executor keeps running in the background through the interrupt.
241    ///
242    /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A
243    /// [`SendSpawner`] is returned instead of a [`Spawner`] because the
244    /// executor effectively runs in a different "thread" (the interrupt),
245    /// so spawning tasks on it is effectively sending them.
246    ///
247    /// To obtain a [`Spawner`] for this executor, use [`Spawner::for_current_executor`]
248    /// from a task running in it.
249    pub fn start(&'static mut self, priority: Priority) -> SendSpawner {
250        unsafe {
251            (*self.executor.get()).write(raw::Executor::new((SWI as usize) as *mut ()));
252
253            INTERRUPT_EXECUTORS[SWI as usize].set((*self.executor.get()).as_mut_ptr());
254        }
255
256        let swi_handler = match SWI {
257            0 => handle_interrupt::<0>,
258            1 => handle_interrupt::<1>,
259            2 => handle_interrupt::<2>,
260            3 => handle_interrupt::<3>,
261            _ => unreachable!(),
262        };
263
264        self.interrupt
265            .set_interrupt_handler(InterruptHandler::new(swi_handler, priority));
266
267        let executor = unsafe { (*self.executor.get()).assume_init_ref() };
268        executor.spawner().make_send()
269    }
270
271    /// Get a SendSpawner for this executor
272    ///
273    /// This returns a [`SendSpawner`] you can use to spawn tasks on this
274    /// executor.
275    ///
276    /// This MUST only be called on an executor that has already been started.
277    /// The function will panic otherwise.
278    pub fn spawner(&'static self) -> SendSpawner {
279        if INTERRUPT_EXECUTORS[SWI as usize]
280            .raw_executor
281            .load(Ordering::Acquire)
282            .is_null()
283        {
284            panic!("InterruptExecutor::spawner() called on uninitialized executor.");
285        }
286        let executor = unsafe { (*self.executor.get()).assume_init_ref() };
287        executor.spawner().make_send()
288    }
289}