esp-hal 1.2.0

Bare-metal HAL for Espressif devices
Documentation
//! GPIO interrupt handling
//!
//! ## Requirements
//!
//! - On devices other than the P4, there is a single interrupt handler. GPIO interrupt handling
//!   must not interfere with the async API in this single handler.
//! - Async operations take pins by `&mut self`, so they can only be accessed after the operation is
//!   complete, or cancelled. They may be defined to overwrite the configuration of the manual
//!   interrupt API, but not affect the interrupt handler.
//! - Manual `listen` operations don't need to be prepared for async operations, but async
//!   operations need to be prepared to handle cases where the pin was configured to listen for an
//!   event - or even that the user unlistened the pin but left the interrupt status set.
//!
//! The user should be careful when using the async API and the manual interrupt
//! API together. For performance reasons, we will not prevent the user handler
//! from running in response to an async event.
//!
//! ## Single-shot interaction with user interrupt handlers
//!
//! The async API disables the pin's interrupt when triggered. This makes async
//! operations single-shot. If there is no user handler, the other GPIO
//! interrupts are also single-shot. This is because the user has no way to
//! handle multiple events in this case, the API only allows querying whether
//! the interrupt has fired or not. Disabling the interrupt also means that the
//! interrupt status bits are not cleared, so the `is_interrupt_set` works by
//! default as expected.
//!
//! When the user sets a custom interrupt handler, the built-in interrupt
//! handler will only disable the async interrupts. The user handler is
//! responsible for clearing the interrupt status bits or disabling the
//! interrupts, based on their needs. This is communicated to the user in the
//! documentation of the `Io::set_interrupt_handler` function.
//!
//! ## Critical sections
//!
//! The interrupt handler runs in a GPIO-specific critical section. The critical
//! section is required because interrupts are disabled by modifying the pin
//! register, which is not an atomic operation. The critical section also
//! ensures that a higher priority task waken by an async pin event (or the user
//! handler) will only run after the interrupt handler has finished.
//!
//! ## Signaling async completion
//!
//! The completion is signaled by clearing a flag in an AtomicU32. This flag is
//! set at the start of the async operation, and cleared when the interrupt
//! handler is called. The flag is not accessible by the user, so they can't
//! force-complete an async operation accidentally from the interrupt handler.
//!
//! We could technically use the interrupt status on single-core chips, but it
//! would be slightly more complicated to prevent the user from breaking things.
//! (If the user were to clear the interrupt status, we would need to re-enable
//! it, for PinFuture to detect the completion).

use portable_atomic::{AtomicPtr, Ordering};
use strum::EnumCount;

#[cfg(feature = "rt")]
use crate::{
    gpio::low_level::disable_cpu_interrupt,
    handler,
    interrupt::{self, DEFAULT_INTERRUPT_HANDLER},
    peripherals::Interrupt,
    system::Cpu,
};
use crate::{
    gpio::{AnyPin, GPIO_LOCK, GpioBank, InputPin, low_level::set_int_enable},
    ram,
};

/// Convenience constant for `Option::None` pin
pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new();

pub(super) struct CFnPtr(AtomicPtr<()>);
impl CFnPtr {
    pub const fn new() -> Self {
        Self(AtomicPtr::new(core::ptr::null_mut()))
    }

    pub fn store(&self, f: extern "C" fn()) {
        self.0.store(f as *mut (), Ordering::Relaxed);
    }

    pub fn call(&self) {
        let ptr = self.0.load(Ordering::Relaxed);
        if !ptr.is_null() {
            unsafe { (core::mem::transmute::<*mut (), extern "C" fn()>(ptr))() };
        }
    }
}

#[cfg(feature = "rt")]
pub(crate) fn bind_default_interrupt_handler() {
    // We first check if a handler is set in the vector table.
    if let Some(handler) = interrupt::bound_handler(Interrupt::GPIO) {
        // We only allow binding the default handler if nothing else is bound.
        // This prevents silently overwriting RTIC's interrupt handler, if using GPIO.
        if handler != DEFAULT_INTERRUPT_HANDLER.handler() {
            // The user has configured an interrupt handler they wish to use.
            info!("Not using default GPIO interrupt handler: already bound in vector table");
            return;
        }
    }

    // The vector table doesn't contain a custom entry. Still, the
    // peripheral interrupt may already be bound to something else.
    let mut is_mapped = false;
    for cpu in Cpu::all() {
        if interrupt::mapped_to(cpu, Interrupt::GPIO).is_some() {
            is_mapped = true;
        }
    }

    if is_mapped {
        info!("Not using default GPIO interrupt handler: peripheral interrupt already in use");
        return;
    }

    super::low_level::enable_interrupt(default_gpio_interrupt_handler);
}

/// The default GPIO interrupt handler, used when no custom handler is set.
///
/// This handler disables all pending interrupts and leaves the interrupt
/// status bits unchanged. This enables functions like `is_interrupt_set` to
/// work correctly.
#[ram]
#[handler]
#[cfg(feature = "rt")]
fn default_gpio_interrupt_handler() {
    GPIO_LOCK.lock(|| {
        let banks = interrupt_status();

        // Handle the async interrupts
        for (bank, intrs) in banks {
            // Get the mask of active async pins and clear the relevant bits to signal
            // completion. This way the user may clear the interrupt status
            // without worrying about the async bit being cleared.
            let async_pins = bank.async_operations().load(Ordering::Relaxed);

            // Wake up the tasks
            handle_async_pins(bank, async_pins, intrs);

            // Disable the remaining interrupts.
            let mut intrs = intrs & !async_pins;
            while intrs != 0 {
                let pin_pos = intrs.trailing_zeros();
                intrs -= 1 << pin_pos;

                let pin_nr = pin_pos as u8 + bank.offset();

                // The remaining interrupts are not async, we treat them as single-shot.
                disable_cpu_interrupt(pin_nr);
            }
        }
    });
}

/// The custom GPIO interrupt handler, used when one is set.
///
/// This handler only disables interrupts associated with async pins. The custom
/// handler is responsible for clearing the interrupt status bits or disabling
/// the interrupts.
#[ram]
pub(super) extern "C" fn user_gpio_interrupt_handler() {
    GPIO_LOCK.lock(|| {
        // Read interrupt status before the user has a chance to modify them.
        let banks = interrupt_status();

        // Call the user handler before clearing interrupts. The user can use the enable
        // bits to determine which interrupts they are interested in. Clearing the
        // interrupt status or enable bits have no effect on the rest of the
        // interrupt handler.
        USER_INTERRUPT_HANDLER.call();

        process_async_banks(banks);
    });
}

/// Internal entry point for [`crate::gpio::handle_gpio_interrupt`].
///
/// Kept here (rather than at the public callsite) because this module owns
/// the GPIO ISR critical section and the per-bank dispatch routine, both of
/// which are private to this module.
///
/// # Safety
///
/// Must be called from a GPIO interrupt context.
#[ram]
pub(super) unsafe fn handle_gpio_interrupt_impl() {
    GPIO_LOCK.lock(|| {
        let banks = interrupt_status();
        process_async_banks(banks);
    });
}

#[inline]
pub(super) fn process_async_banks(banks: [(GpioBank, u32); GpioBank::COUNT]) {
    for (bank, intrs) in banks {
        let async_pins = bank.async_operations().load(Ordering::Relaxed);

        handle_async_pins(bank, async_pins, intrs);
    }
}

/// Internal entry point for [`crate::gpio::wake_pin`].
///
/// # Safety
///
/// Must be called from a GPIO interrupt context.
#[ram]
pub(super) unsafe fn wake_pin_impl(pin: u8) {
    GPIO_LOCK.lock(|| {
        let any = unsafe { AnyPin::steal(pin) };
        let bank = any.bank();
        let mask = any.mask();

        // Only act if this pin is registered as an async waiter.
        let async_pins = bank.async_operations().load(Ordering::Relaxed);
        if async_pins & mask == 0 {
            return;
        }

        handle_async_pins(bank, mask, mask);
    });
}

fn interrupt_status() -> [(GpioBank, u32); GpioBank::COUNT] {
    let intrs_bank0 = GpioBank::_0.read_interrupt_status_of_current_cpu();

    #[cfg(gpio_has_bank_1)]
    let intrs_bank1 = GpioBank::_1.read_interrupt_status_of_current_cpu();

    [
        (GpioBank::_0, intrs_bank0),
        #[cfg(gpio_has_bank_1)]
        (GpioBank::_1, intrs_bank1),
    ]
}

// We have separate variants for single-core and multi-core async pin handling.
// Single core can be much simpler because no code is running in parallel, so we
// don't have to be so careful with the order of operations. On multi-core,
// however, the order can actually break things, regardless of the critical
// section (because tasks on the other core may be waken in inappropriate
// times).

#[cfg(single_core)]
fn handle_async_pins(bank: GpioBank, async_pins: u32, intrs: u32) {
    let mut async_intrs = async_pins & intrs;
    while async_intrs != 0 {
        let pin_pos = async_intrs.trailing_zeros();
        async_intrs -= 1 << pin_pos;

        let pin_nr = pin_pos as u8 + bank.offset();

        // Disable the interrupt for this pin.
        set_int_enable(pin_nr, Some(0), 0, false);

        unsafe { AnyPin::steal(pin_nr) }.waker().wake();
    }

    // This is an optimization (in case multiple pin interrupts are handled at once)
    // so that PinFuture doesn't have to clear interrupt status bits one by one
    // for each pin. We need to clear after disabling the interrupt, so that a pin
    // event can't re-set the bit.
    bank.write_interrupt_status_clear(async_pins & intrs);

    // On a single-core chip, the lock around `handle_async_pins` ensures
    // that the interrupt handler will not be interrupted by other code,
    // so we can safely write back without an atomic CAS.
    bank.async_operations()
        .store(async_pins & !intrs, Ordering::Relaxed);
}

#[cfg(multi_core)]
fn handle_async_pins(bank: GpioBank, async_pins: u32, intrs: u32) {
    // First, disable pin interrupts. If we were to do this after clearing the async
    // flags, the PinFuture destructor may try to take a critical section which
    // isn't necessary.
    let mut async_intrs = async_pins & intrs;
    while async_intrs != 0 {
        let pin_pos = async_intrs.trailing_zeros();
        async_intrs -= 1 << pin_pos;

        let pin_nr = pin_pos as u8 + bank.offset();

        // Disable the interrupt for this pin.
        set_int_enable(pin_nr, Some(0), 0, false);
    }

    // This is an optimization (in case multiple pin interrupts are handled at once)
    // so that PinFuture doesn't have to clear interrupt status bits one by one
    // for each pin. We need to clear after disabling the interrupt, so that a pin
    // event can't re-set the bit.
    bank.write_interrupt_status_clear(async_pins & intrs);

    // Clearing the async bit needs to be the last state change, as this signals
    // completion.
    // On multi-core chips, we need to use a CAS to ensure that only
    // the handled async bits are cleared.
    bank.async_operations().fetch_and(!intrs, Ordering::Relaxed);

    // Now we can wake the tasks. This needs to happen after completion - waking an
    // already running task isn't a big issue, but not waking a waiting one is.
    // Doing it sooner would mean that on a multi-core chip a task could be
    // waken, but the Future wouldn't actually be resolved, and so it might
    // never wake again.
    let mut async_intrs = async_pins & intrs;
    while async_intrs != 0 {
        let pin_pos = async_intrs.trailing_zeros();
        async_intrs -= 1 << pin_pos;

        let pin_nr = pin_pos as u8 + bank.offset();

        unsafe { AnyPin::steal(pin_nr) }.waker().wake();
    }
}