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,
};
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() {
if let Some(handler) = interrupt::bound_handler(Interrupt::GPIO) {
if handler != DEFAULT_INTERRUPT_HANDLER.handler() {
info!("Not using default GPIO interrupt handler: already bound in vector table");
return;
}
}
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);
}
#[ram]
#[handler]
#[cfg(feature = "rt")]
fn default_gpio_interrupt_handler() {
GPIO_LOCK.lock(|| {
let banks = interrupt_status();
for (bank, intrs) in banks {
let async_pins = bank.async_operations().load(Ordering::Relaxed);
handle_async_pins(bank, async_pins, intrs);
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();
disable_cpu_interrupt(pin_nr);
}
}
});
}
#[ram]
pub(super) extern "C" fn user_gpio_interrupt_handler() {
GPIO_LOCK.lock(|| {
let banks = interrupt_status();
USER_INTERRUPT_HANDLER.call();
process_async_banks(banks);
});
}
#[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);
}
}
#[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();
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),
]
}
#[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();
set_int_enable(pin_nr, Some(0), 0, false);
unsafe { AnyPin::steal(pin_nr) }.waker().wake();
}
bank.write_interrupt_status_clear(async_pins & intrs);
bank.async_operations()
.store(async_pins & !intrs, Ordering::Relaxed);
}
#[cfg(multi_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();
set_int_enable(pin_nr, Some(0), 0, false);
}
bank.write_interrupt_status_clear(async_pins & intrs);
bank.async_operations().fetch_and(!intrs, Ordering::Relaxed);
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();
}
}