aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor/src/interrupt.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-executor/src/interrupt.rs')
-rw-r--r--embassy-executor/src/interrupt.rs127
1 files changed, 127 insertions, 0 deletions
diff --git a/embassy-executor/src/interrupt.rs b/embassy-executor/src/interrupt.rs
new file mode 100644
index 000000000..f8b0809da
--- /dev/null
+++ b/embassy-executor/src/interrupt.rs
@@ -0,0 +1,127 @@
1//! Interrupt-mode executor.
2
3use core::cell::UnsafeCell;
4use core::mem::MaybeUninit;
5
6use atomic_polyfill::{AtomicBool, Ordering};
7
8use crate::raw::{self, OpaqueInterruptContext, Pender, PenderInner};
9
10/// An interrupt source that can be used to drive an [`InterruptExecutor`].
11// Name pending
12pub trait InterruptContext {
13 /// Creates an opaque identifier for this interrupt.
14 fn context(&self) -> OpaqueInterruptContext;
15
16 /// Sets up the interrupt request.
17 fn enable(&self);
18}
19
20/// Interrupt mode executor.
21///
22/// This executor runs tasks in interrupt mode. The interrupt handler is set up
23/// to poll tasks, and when a task is woken the interrupt is pended from software.
24///
25/// This allows running async tasks at a priority higher than thread mode. One
26/// use case is to leave thread mode free for non-async tasks. Another use case is
27/// to run multiple executors: one in thread mode for low priority tasks and another in
28/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
29/// priority ones.
30///
31/// It is even possible to run multiple interrupt mode executors at different priorities,
32/// by assigning different priorities to the interrupts. For an example on how to do this,
33/// See the 'multiprio' example for 'embassy-nrf'.
34///
35/// To use it, you have to pick an interrupt that won't be used by the hardware.
36/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI).
37/// If this is not the case, you may use an interrupt from any unused peripheral.
38///
39/// It is somewhat more complex to use, it's recommended to use the thread-mode
40/// [`Executor`] instead, if it works for your use case.
41pub struct InterruptModeExecutor {
42 started: AtomicBool,
43 executor: UnsafeCell<MaybeUninit<raw::Executor>>,
44}
45
46unsafe impl Send for InterruptModeExecutor {}
47unsafe impl Sync for InterruptModeExecutor {}
48
49impl InterruptModeExecutor {
50 /// Create a new, not started `InterruptExecutor`.
51 #[inline]
52 pub const fn new() -> Self {
53 Self {
54 started: AtomicBool::new(false),
55 executor: UnsafeCell::new(MaybeUninit::uninit()),
56 }
57 }
58
59 /// Executor interrupt callback.
60 ///
61 /// # Safety
62 ///
63 /// You MUST call this from the interrupt handler, and from nowhere else.
64 pub unsafe fn on_interrupt(&'static self) {
65 let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
66 executor.poll();
67 }
68
69 /// Start the executor.
70 ///
71 /// This initializes the executor, enables the interrupt, and returns.
72 /// The executor keeps running in the background through the interrupt.
73 ///
74 /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
75 /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a
76 /// different "thread" (the interrupt), so spawning tasks on it is effectively
77 /// sending them.
78 ///
79 /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
80 /// a task running in it.
81 ///
82 /// # Interrupt requirements
83 ///
84 /// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt).
85 ///
86 /// This method already enables (unmasks) the interrupt, you must NOT do it yourself.
87 ///
88 /// You must set the interrupt priority before calling this method. You MUST NOT
89 /// do it after.
90 ///
91 pub fn start(&'static self, irq: impl InterruptContext) -> crate::SendSpawner {
92 if self
93 .started
94 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
95 .is_err()
96 {
97 panic!("InterruptExecutor::start() called multiple times on the same executor.");
98 }
99
100 unsafe {
101 (&mut *self.executor.get())
102 .as_mut_ptr()
103 .write(raw::Executor::new(Pender(PenderInner::Interrupt(irq.context()))))
104 }
105
106 let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
107
108 irq.enable();
109
110 executor.spawner().make_send()
111 }
112
113 /// Get a SendSpawner for this executor
114 ///
115 /// This returns a [`SendSpawner`] you can use to spawn tasks on this
116 /// executor.
117 ///
118 /// This MUST only be called on an executor that has already been spawned.
119 /// The function will panic otherwise.
120 pub fn spawner(&'static self) -> crate::SendSpawner {
121 if !self.started.load(Ordering::Acquire) {
122 panic!("InterruptExecutor::spawner() called on uninitialized executor.");
123 }
124 let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
125 executor.spawner().make_send()
126 }
127}