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}