use portable_atomic::{AtomicPtr, Ordering};
use strum::EnumCount;
use crate::{
gpio::{AnyPin, GPIO_LOCK, GpioBank, InputPin, set_int_enable},
interrupt::Priority,
peripherals::{GPIO, Interrupt},
ram,
};
#[cfg(feature = "rt")]
use crate::{
handler,
interrupt::{self, DEFAULT_INTERRUPT_HANDLER},
};
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;
}
}
for cpu in cores() {
if interrupt::mapped_to(cpu, Interrupt::GPIO).is_some() {
info!("Not using default GPIO interrupt handler: peripheral interrupt already in use");
return;
}
}
interrupt::bind_handler(Interrupt::GPIO, default_gpio_interrupt_handler);
#[cfg(esp32)]
crate::interrupt::enable_on_cpu(
crate::system::Cpu::AppCpu,
Interrupt::GPIO,
Priority::Priority1,
);
}
cfg_if::cfg_if! {
if #[cfg(esp32)] {
fn cores() -> impl Iterator<Item = crate::system::Cpu> {
crate::system::Cpu::all()
}
} else {
fn cores() -> [crate::system::Cpu; 1] {
[crate::system::Cpu::current()]
}
}
}
pub(super) fn set_interrupt_priority(interrupt: Interrupt, priority: Priority) {
for cpu in cores() {
if crate::interrupt::mapped_to(cpu, interrupt).is_some() {
crate::interrupt::enable_on_cpu(cpu, interrupt, priority);
}
}
}
#[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();
set_int_enable(pin_nr, Some(0), 0, false);
}
}
});
}
#[ram]
pub(super) extern "C" fn user_gpio_interrupt_handler() {
GPIO_LOCK.lock(|| {
let banks = interrupt_status();
USER_INTERRUPT_HANDLER.call();
for (bank, intrs) in banks {
let async_pins = bank.async_operations().load(Ordering::Relaxed);
handle_async_pins(bank, async_pins, intrs);
}
});
}
#[derive(Clone, Copy)]
pub(crate) enum InterruptStatusRegisterAccess {
Bank0,
#[cfg(gpio_has_bank_1)]
Bank1,
}
impl InterruptStatusRegisterAccess {
pub(crate) fn interrupt_status_read(self) -> u32 {
cfg_if::cfg_if! {
if #[cfg(esp32)] {
match self {
Self::Bank0 => GPIO::regs().status().read().bits(),
Self::Bank1 => GPIO::regs().status1().read().bits(),
}
} else {
match self {
Self::Bank0 => GPIO::regs().pcpu_int().read().bits(),
#[cfg(gpio_has_bank_1)]
Self::Bank1 => GPIO::regs().pcpu_int1().read().bits(),
}
}
}
}
}
fn interrupt_status() -> [(GpioBank, u32); GpioBank::COUNT] {
let intrs_bank0 = InterruptStatusRegisterAccess::Bank0.interrupt_status_read();
#[cfg(gpio_has_bank_1)]
let intrs_bank1 = InterruptStatusRegisterAccess::Bank1.interrupt_status_read();
[
(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();
}
}