Skip to content

Commit

Permalink
Auto-bind the GPIO interrupt handler
Browse files Browse the repository at this point in the history
  • Loading branch information
bugadani committed Nov 7, 2024
1 parent 91dcd58 commit 8b63ede
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 43 deletions.
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The `GpioEtmEventRising`, `GpioEtmEventFalling`, `GpioEtmEventAny` types have been replaced with `Event` (#2427)
- The `TaskSet`, `TaskClear`, `TaskToggle` types have been replaced with `Task` (#2427)
- `{Spi, SpiDma, SpiDmaBus}` configuration methods (#2448)
- `Io::new_with_priority` and `Io::new_no_bind_interrupt`.

## [0.21.1]

Expand Down
115 changes: 78 additions & 37 deletions esp-hal/src/gpio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,18 @@
//! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/
//! [Inverting TX and RX Pins]: crate::uart#inverting-rx-and-tx-pins

use portable_atomic::{AtomicPtr, Ordering};
use portable_atomic::{AtomicPtr, AtomicUsize, Ordering};
use procmacros::ram;

pub use crate::soc::gpio::*;
use crate::{
interrupt::InterruptHandler,
interrupt::{self, InterruptHandler, Priority},
peripheral::{Peripheral, PeripheralRef},
peripherals::{GPIO, IO_MUX},
peripherals::{Interrupt, GPIO, IO_MUX},
private::{self, Sealed},
Cpu,
InterruptConfigurable,
DEFAULT_INTERRUPT_HANDLER,
};

pub mod interconnect;
Expand Down Expand Up @@ -784,6 +786,51 @@ where

impl<const GPIONUM: u8> private::Sealed for GpioPin<GPIONUM> {}

fn ensure_interrupt_handler_bound() {
#[cfg(multi_core)]
{
// On multi-core, we need to call `interrupt::enable` on each core.
// However, we don't want to call it on a core where the handler is already
// bound, otherwise we'd overwrite the priority set for that handler.

// FIXME: handling GPIO interrupts on multiple cores may not be what the users
// want. Only ESP32 can handle a different set of pins on each core. Do
// we want to support this?
static BOUND: AtomicUsize = AtomicUsize::new(0);

let bound = BOUND.load(Ordering::Relaxed);

let cpu_bit = 1 << Cpu::current() as usize;
if bound & cpu_bit != 0 {
// Nothing to do.
return;
}

// A tiny optimization to avoid a CAS in the common case.
if BOUND.fetch_or(cpu_bit, Ordering::Relaxed) & cpu_bit != 0 {
// We have raced with something and lost.
return;
}
}

// We check this by looking up the handler in the vector table.
if let Some(handler) = interrupt::bound_handler(Interrupt::GPIO) {
let handler = handler as *const unsafe extern "C" fn();

// We only allow binding the default handler if nothing else is bound.
// This prevents silently overwriting RTIC's interrupt handler, if using GPIO.
if core::ptr::eq(handler, DEFAULT_INTERRUPT_HANDLER.handler() as _) {
unsafe { interrupt::bind_interrupt(Interrupt::GPIO, gpio_interrupt_handler) };
} else if core::ptr::eq(handler, gpio_interrupt_handler as _) {
warn!("GPIO interrupt handler already bound to a function other than `gpio_interrupt_handler`");
return;
}
}

// By default, we use lowest priority
unwrap!(interrupt::enable(Interrupt::GPIO, Priority::min()));
}

/// General Purpose Input/Output driver
pub struct Io {
_io_mux: IO_MUX,
Expand All @@ -793,39 +840,18 @@ pub struct Io {

impl Io {
/// Initialize the I/O driver.
pub fn new(gpio: GPIO, io_mux: IO_MUX) -> Self {
Self::new_with_priority(gpio, io_mux, crate::interrupt::Priority::min())
}

/// Initialize the I/O driver with a interrupt priority.
///
/// This decides the priority for the interrupt when using async.
pub fn new_with_priority(
mut gpio: GPIO,
io_mux: IO_MUX,
prio: crate::interrupt::Priority,
) -> Self {
gpio.bind_gpio_interrupt(gpio_interrupt_handler);
unwrap!(crate::interrupt::enable(
crate::peripherals::Interrupt::GPIO,
prio
));

Self::new_no_bind_interrupt(gpio, io_mux)
}

/// Initialize the I/O driver without enabling the GPIO interrupt or
/// binding an interrupt handler to it.
///
/// *Note:* You probably don't want to use this, it is intended to be used
/// in very specific use cases. Async GPIO functionality will not work
/// when instantiating `Io` using this constructor.
pub fn new_no_bind_interrupt(_gpio: GPIO, _io_mux: IO_MUX) -> Self {
pub fn new(_gpio: GPIO, _io_mux: IO_MUX) -> Self {
Io {
_io_mux,
pins: unsafe { Pins::steal() },
}
}

/// Set the interrupt priority for GPIO interrupts.
pub fn set_interrupt_priority(&self, prio: Priority) {
ensure_interrupt_handler_bound();
unwrap!(interrupt::enable(crate::peripherals::Interrupt::GPIO, prio));
}
}

impl crate::private::Sealed for Io {}
Expand All @@ -837,18 +863,32 @@ impl InterruptConfigurable for Io {
/// ⚠️ Be careful when using this together with the async API:
///
/// - The async driver will disable interrupts that belong to async pins.
/// - You will not be notified if you make a mistake.
/// - You will not be notified if you handle an interrupt request that is
/// also handled by an async pin.
#[cfg_attr(multi_core, doc = "")]
#[cfg_attr(
multi_core,
doc = " ⚠️ Be careful when using GPIO interrupts on multiple cores:"
)]
#[cfg_attr(multi_core, doc = "")]
#[cfg_attr(
multi_core,
doc = " - This function only enables interrupts for the current core. To enable interrupts for other cores, call this function or [`Io::set_interrupt_priority`] on those cores."
)]
#[cfg_attr(multi_core, doc = " - Both cores will use the same interrupt handler.")]
#[cfg_attr(
all(multi_core, esp32s3),
doc = " - Both cores will handle the interrupt requests, possibly at the same time."
)]
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
unwrap!(crate::interrupt::enable(
crate::peripherals::Interrupt::GPIO,
handler.priority()
));
self.set_interrupt_priority(handler.priority());
USER_INTERRUPT_HANDLER.store(handler.handler());
}
}

/// The built-in interrupt handler.
#[ram]
extern "C" fn gpio_interrupt_handler() {
pub extern "C" fn gpio_interrupt_handler() {
let intrs_bank0 = InterruptStatusRegisterAccess::Bank0.interrupt_status_read();

#[cfg(any(esp32, esp32s2, esp32s3))]
Expand Down Expand Up @@ -1606,6 +1646,7 @@ where
nmi_enable: bool,
wake_up_from_light_sleep: bool,
) {
ensure_interrupt_handler_bound();
if wake_up_from_light_sleep {
match event {
Event::AnyEdge | Event::RisingEdge | Event::FallingEdge => {
Expand Down
18 changes: 15 additions & 3 deletions esp-hal/src/interrupt/riscv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,17 +423,29 @@ mod vectored {
Ok(())
}

/// Bind the given interrupt to the given handler
/// Binds the given interrupt to the given handler.
///
/// # Safety
///
/// This will replace any previously bound interrupt handler
pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn() -> ()) {
pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn()) {
let ptr = &peripherals::__EXTERNAL_INTERRUPTS[interrupt as usize]._handler as *const _
as *mut unsafe extern "C" fn() -> ();
as *mut unsafe extern "C" fn();
ptr.write_volatile(handler);
}

/// Returns the currently bound interrupt handler.
pub fn bound_handler(interrupt: Interrupt) -> Option<unsafe extern "C" fn()> {
unsafe {
let addr = peripherals::__EXTERNAL_INTERRUPTS[interrupt as usize]._handler;
if addr as usize == 0 {
return None;
}

Some(addr)
}
}

#[no_mangle]
#[ram]
unsafe fn handle_interrupts(cpu_intr: CpuInterrupt, context: &mut TrapFrame) {
Expand Down
18 changes: 15 additions & 3 deletions esp-hal/src/interrupt/xtensa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,17 +415,29 @@ mod vectored {
Ok(())
}

/// Bind the given interrupt to the given handler
/// Binds the given interrupt to the given handler.
///
/// # Safety
///
/// This will replace any previously bound interrupt handler
pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn() -> ()) {
pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn()) {
let ptr = &peripherals::__INTERRUPTS[interrupt as usize]._handler as *const _
as *mut unsafe extern "C" fn() -> ();
as *mut unsafe extern "C" fn();
ptr.write_volatile(handler);
}

/// Returns the currently bound interrupt handler.
pub fn bound_handler(interrupt: Interrupt) -> Option<unsafe extern "C" fn()> {
unsafe {
let addr = peripherals::__INTERRUPTS[interrupt as usize]._handler;
if addr as usize == 0 {
return None;
}

Some(addr)
}
}

fn interrupt_level_to_cpu_interrupt(
level: Priority,
is_edge: bool,
Expand Down

0 comments on commit 8b63ede

Please sign in to comment.