esp_hal/gpio/interrupt.rs
1//! GPIO interrupt handling
2//!
3//! ## Requirements
4//!
5//! - On devices other than the P4, there is a single interrupt handler. GPIO
6//! interrupt handling must not interfere with the async API in this single
7//! handler.
8//! - Async operations take pins by `&mut self`, so they can only be accessed
9//! after the operation is complete, or cancelled. They may be defined to
10//! overwrite the configuration of the manual interrupt API, but not affect
11//! the interrupt handler.
12//! - Manual `listen` operations don't need to be prepared for async operations,
13//! but async operations need to be prepared to handle cases where the pin was
14//! configured to listen for an event - or even that the user unlistened the
15//! pin but left the interrupt status set.
16//!
17//! The user should be careful when using the async API and the manual interrupt
18//! API together. For performance reasons, we will not prevent the user handler
19//! from running in response to an async event.
20//!
21//! ## Single-shot interaction with user interrupt handlers
22//!
23//! The async API disables the pin's interrupt when triggered. This makes async
24//! operations single-shot. If there is no user handler, the other GPIO
25//! interrupts are also single-shot. This is because the user has no way to
26//! handle multiple events in this case, the API only allows querying whether
27//! the interrupt has fired or not. Disabling the interrupt also means that the
28//! interrupt status bits are not cleared, so the `is_interrupt_set` works by
29//! default as expected.
30//!
31//! When the user sets a custom interrupt handler, the built-in interrupt
32//! handler will only disable the async interrupts. The user handler is
33//! responsible for clearing the interrupt status bits or disabling the
34//! interrupts, based on their needs. This is communicated to the user in the
35//! documentation of the `Io::set_interrupt_handler` function.
36//!
37//! ## Critical sections
38//!
39//! The interrupt handler runs in a GPIO-specific critical section. The critical
40//! section is required because interrupts are disabled by modifying the pin
41//! register, which is not an atomic operation. The critical section also
42//! ensures that a higher priority task waken by an async pin event (or the user
43//! handler) will only run after the interrupt handler has finished.
44//!
45//! ## Signaling async completion
46//!
47//! The completion is signalled by clearing a flag in an AtomicU32. This flag is
48//! set at the start of the async operation, and cleared when the interrupt
49//! handler is called. The flag is not accessible by the user, so they can't
50//! force-complete an async operation accidentally from the interrupt handler.
51//!
52//! We could technically use the interrupt status on single-core chips, but it
53//! would be slightly more complicated to prevent the user from breaking things.
54//! (If the user were to clear the interrupt status, we would need to re-enable
55//! it, for PinFuture to detect the completion).
56//!
57//! TODO: currently, direct-binding a GPIO interrupt handler will completely
58//! break the async API. We will need to expose a way to handle async events.
59
60use portable_atomic::{AtomicPtr, Ordering};
61use procmacros::ram;
62use strum::EnumCount;
63
64use crate::{
65 gpio::{GpioBank, InterruptStatusRegisterAccess, asynch, set_int_enable},
66 interrupt::{self, DEFAULT_INTERRUPT_HANDLER, Priority},
67 peripherals::Interrupt,
68 sync::RawMutex,
69};
70
71/// Convenience constant for `Option::None` pin
72pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new();
73
74pub(super) static GPIO_LOCK: RawMutex = RawMutex::new();
75
76pub(super) struct CFnPtr(AtomicPtr<()>);
77impl CFnPtr {
78 pub const fn new() -> Self {
79 Self(AtomicPtr::new(core::ptr::null_mut()))
80 }
81
82 pub fn store(&self, f: extern "C" fn()) {
83 self.0.store(f as *mut (), Ordering::Relaxed);
84 }
85
86 pub fn call(&self) {
87 let ptr = self.0.load(Ordering::Relaxed);
88 if !ptr.is_null() {
89 unsafe { (core::mem::transmute::<*mut (), extern "C" fn()>(ptr))() };
90 }
91 }
92}
93
94pub(crate) fn bind_default_interrupt_handler() {
95 // We first check if a handler is set in the vector table.
96 if let Some(handler) = interrupt::bound_handler(Interrupt::GPIO) {
97 let handler = handler as *const unsafe extern "C" fn();
98
99 // We only allow binding the default handler if nothing else is bound.
100 // This prevents silently overwriting RTIC's interrupt handler, if using GPIO.
101 if !core::ptr::eq(handler, DEFAULT_INTERRUPT_HANDLER.handler() as _) {
102 // The user has configured an interrupt handler they wish to use.
103 info!("Not using default GPIO interrupt handler: already bound in vector table");
104 return;
105 }
106 }
107
108 // The vector table doesn't contain a custom entry. Still, the
109 // peripheral interrupt may already be bound to something else.
110 for cpu in cores() {
111 if interrupt::bound_cpu_interrupt_for(cpu, Interrupt::GPIO).is_some() {
112 info!("Not using default GPIO interrupt handler: peripheral interrupt already in use");
113 return;
114 }
115 }
116
117 unsafe { interrupt::bind_interrupt(Interrupt::GPIO, default_gpio_interrupt_handler) };
118
119 // By default, we use lowest priority
120 set_interrupt_priority(Interrupt::GPIO, Priority::min());
121}
122
123cfg_if::cfg_if! {
124 if #[cfg(esp32)] {
125 // On ESP32, the interrupt fires on the core that started listening for a pin event.
126 fn cores() -> impl Iterator<Item = crate::system::Cpu> {
127 crate::system::Cpu::all()
128 }
129 } else {
130 fn cores() -> [crate::system::Cpu; 1] {
131 [crate::system::Cpu::current()]
132 }
133 }
134}
135
136pub(super) fn set_interrupt_priority(interrupt: Interrupt, priority: Priority) {
137 for cpu in cores() {
138 unwrap!(crate::interrupt::enable_on_cpu(cpu, interrupt, priority));
139 }
140}
141
142/// The default GPIO interrupt handler, when the user has not set one.
143///
144/// This handler will disable all pending interrupts and leave the interrupt
145/// status bits unchanged. This enables functions like `is_interrupt_set` to
146/// work correctly.
147#[ram]
148extern "C" fn default_gpio_interrupt_handler() {
149 GPIO_LOCK.lock(|| {
150 let banks = interrupt_status();
151
152 // Handle the async interrupts
153 for (bank, intrs) in banks {
154 // Get the mask of active async pins and clear the relevant bits to signal
155 // completion. This way the user may clear the interrupt status
156 // without worrying about the async bit being cleared.
157 let async_pins = bank.async_operations().load(Ordering::Relaxed);
158
159 // Wake up the tasks
160 handle_async_pins(bank, async_pins, intrs);
161
162 // Disable the remaining interrupts.
163 let mut intrs = intrs & !async_pins;
164 while intrs != 0 {
165 let pin_pos = intrs.trailing_zeros();
166 intrs -= 1 << pin_pos;
167
168 let pin_nr = pin_pos as u8 + bank.offset();
169
170 // The remaining interrupts are not async, we treat them as single-shot.
171 set_int_enable(pin_nr, Some(0), 0, false);
172 }
173 }
174 });
175}
176
177/// The user GPIO interrupt handler, when the user has set one.
178///
179/// This handler only disables interrupts associated with async pins. The user
180/// handler is responsible for clearing the interrupt status bits or disabling
181/// the interrupts.
182#[ram]
183pub(super) extern "C" fn user_gpio_interrupt_handler() {
184 GPIO_LOCK.lock(|| {
185 // Read interrupt status before the user has a chance to modify them.
186 let banks = interrupt_status();
187
188 // Call the user handler before clearing interrupts. The user can use the enable
189 // bits to determine which interrupts they are interested in. Clearing the
190 // interupt status or enable bits have no effect on the rest of the
191 // interrupt handler.
192 USER_INTERRUPT_HANDLER.call();
193
194 // Handle the async interrupts
195 for (bank, intrs) in banks {
196 // Get the mask of active async pins and clear the relevant bits to signal
197 // completion. This way the user may clear the interrupt status
198 // without worrying about the async bit being cleared.
199 let async_pins = bank.async_operations().load(Ordering::Relaxed);
200
201 // Wake up the tasks
202 handle_async_pins(bank, async_pins, intrs);
203 }
204 });
205}
206
207fn interrupt_status() -> [(GpioBank, u32); GpioBank::COUNT] {
208 let intrs_bank0 = InterruptStatusRegisterAccess::Bank0.interrupt_status_read();
209
210 #[cfg(gpio_bank_1)]
211 let intrs_bank1 = InterruptStatusRegisterAccess::Bank1.interrupt_status_read();
212
213 [
214 (GpioBank::_0, intrs_bank0),
215 #[cfg(gpio_bank_1)]
216 (GpioBank::_1, intrs_bank1),
217 ]
218}
219
220// We have separate variants for single-core and multi-core async pin handling.
221// Single core can be much simpler because no code is running in parallel, so we
222// don't have to be so careful with the order of operations. On multi-core,
223// however, the order can actually break things, regardless of the critical
224// section (because tasks on the other core may be waken in inappropriate
225// times).
226
227#[cfg(single_core)]
228fn handle_async_pins(bank: GpioBank, async_pins: u32, intrs: u32) {
229 let mut async_intrs = async_pins & intrs;
230 while async_intrs != 0 {
231 let pin_pos = async_intrs.trailing_zeros();
232 async_intrs -= 1 << pin_pos;
233
234 let pin_nr = pin_pos as u8 + bank.offset();
235
236 // Disable the interrupt for this pin.
237 set_int_enable(pin_nr, Some(0), 0, false);
238
239 asynch::PIN_WAKERS[pin_nr as usize].wake();
240 }
241
242 // This is an optimization (in case multiple pin interrupts are handled at once)
243 // so that PinFuture doesn't have to clear interrupt status bits one by one
244 // for each pin. We need to clear after disabling the interrupt, so that a pin
245 // event can't re-set the bit.
246 bank.write_interrupt_status_clear(async_pins & intrs);
247
248 // On a single-core chip, the lock around `handle_async_pins` ensures
249 // that the interrupt handler will not be interrupted by other code,
250 // so we can safely write back without an atomic CAS.
251 bank.async_operations()
252 .store(async_pins & !intrs, Ordering::Relaxed);
253}
254
255#[cfg(multi_core)]
256fn handle_async_pins(bank: GpioBank, async_pins: u32, intrs: u32) {
257 // First, disable pin interrupts. If we were to do this after clearing the async
258 // flags, the PinFuture destructor may try to take a critical section which
259 // isn't necessary.
260 let mut async_intrs = async_pins & intrs;
261 while async_intrs != 0 {
262 let pin_pos = async_intrs.trailing_zeros();
263 async_intrs -= 1 << pin_pos;
264
265 let pin_nr = pin_pos as u8 + bank.offset();
266
267 // Disable the interrupt for this pin.
268 set_int_enable(pin_nr, Some(0), 0, false);
269 }
270
271 // This is an optimization (in case multiple pin interrupts are handled at once)
272 // so that PinFuture doesn't have to clear interrupt status bits one by one
273 // for each pin. We need to clear after disabling the interrupt, so that a pin
274 // event can't re-set the bit.
275 bank.write_interrupt_status_clear(async_pins & intrs);
276
277 // Clearing the async bit needs to be the last state change, as this signals
278 // completion.
279 // On multi-core chips, we need to use a CAS to ensure that only
280 // the handled async bits are cleared.
281 bank.async_operations().fetch_and(!intrs, Ordering::Relaxed);
282
283 // Now we can wake the tasks. This needs to happen after completion - waking an
284 // already running task isn't a big issue, but not waking a waiting one is.
285 // Doing it sooner would mean that on a multi-core chip a task could be
286 // waken, but the Future wouldn't actually be resolved, and so it might
287 // never wake again.
288 let mut async_intrs = async_pins & intrs;
289 while async_intrs != 0 {
290 let pin_pos = async_intrs.trailing_zeros();
291 async_intrs -= 1 << pin_pos;
292
293 let pin_nr = pin_pos as u8 + bank.offset();
294
295 asynch::PIN_WAKERS[pin_nr as usize].wake();
296 }
297}