//! Minimal interrupt helpers mirroring embassy-imxrt style for OS_EVENT and LPUART2. //! Type-level interrupt traits and bindings are provided by the //! `embassy_hal_internal::interrupt_mod!` macro via the generated module below. // TODO(AJM): As of 2025-11-13, we need to do a pass to ensure safety docs // are complete prior to release. #![allow(clippy::missing_safety_doc)] mod generated { embassy_hal_internal::interrupt_mod!( OS_EVENT, RTC, ADC1, GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPUART0, LPUART1, LPUART2, LPUART3, LPUART4, LPUART5, ); } use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; pub use generated::interrupt::{typelevel, Priority}; use crate::pac::Interrupt; /// Trait for configuring and controlling interrupts. /// /// This trait provides a consistent interface for interrupt management across /// different interrupt sources, similar to embassy-imxrt's InterruptExt. pub trait InterruptExt { /// Clear any pending interrupt in NVIC. fn unpend(&self); /// Set NVIC priority for this interrupt. fn set_priority(&self, priority: Priority); /// Enable this interrupt in NVIC. /// /// # Safety /// This function is unsafe because it can enable interrupts that may not be /// properly configured, potentially leading to undefined behavior. unsafe fn enable(&self); /// Disable this interrupt in NVIC. /// /// # Safety /// This function is unsafe because disabling interrupts may leave the system /// in an inconsistent state if the interrupt was expected to fire. unsafe fn disable(&self); /// Check if the interrupt is pending in NVIC. fn is_pending(&self) -> bool; } #[derive(Clone, Copy, Debug, Default)] pub struct DefaultHandlerSnapshot { pub vector: u16, pub count: u32, pub cfsr: u32, pub hfsr: u32, pub stacked_pc: u32, pub stacked_lr: u32, pub stacked_sp: u32, } static LAST_DEFAULT_VECTOR: AtomicU16 = AtomicU16::new(0); static LAST_DEFAULT_COUNT: AtomicU32 = AtomicU32::new(0); static LAST_DEFAULT_CFSR: AtomicU32 = AtomicU32::new(0); static LAST_DEFAULT_HFSR: AtomicU32 = AtomicU32::new(0); static LAST_DEFAULT_PC: AtomicU32 = AtomicU32::new(0); static LAST_DEFAULT_LR: AtomicU32 = AtomicU32::new(0); static LAST_DEFAULT_SP: AtomicU32 = AtomicU32::new(0); #[inline] pub fn default_handler_snapshot() -> DefaultHandlerSnapshot { DefaultHandlerSnapshot { vector: LAST_DEFAULT_VECTOR.load(Ordering::Relaxed), count: LAST_DEFAULT_COUNT.load(Ordering::Relaxed), cfsr: LAST_DEFAULT_CFSR.load(Ordering::Relaxed), hfsr: LAST_DEFAULT_HFSR.load(Ordering::Relaxed), stacked_pc: LAST_DEFAULT_PC.load(Ordering::Relaxed), stacked_lr: LAST_DEFAULT_LR.load(Ordering::Relaxed), stacked_sp: LAST_DEFAULT_SP.load(Ordering::Relaxed), } } #[inline] pub fn clear_default_handler_snapshot() { LAST_DEFAULT_VECTOR.store(0, Ordering::Relaxed); LAST_DEFAULT_COUNT.store(0, Ordering::Relaxed); LAST_DEFAULT_CFSR.store(0, Ordering::Relaxed); LAST_DEFAULT_HFSR.store(0, Ordering::Relaxed); LAST_DEFAULT_PC.store(0, Ordering::Relaxed); LAST_DEFAULT_LR.store(0, Ordering::Relaxed); LAST_DEFAULT_SP.store(0, Ordering::Relaxed); } /// OS_EVENT interrupt helper with methods similar to embassy-imxrt's InterruptExt. pub struct OsEvent; pub const OS_EVENT: OsEvent = OsEvent; impl InterruptExt for OsEvent { /// Clear any pending OS_EVENT in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::OS_EVENT); } /// Set NVIC priority for OS_EVENT. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::OS_EVENT, u8::from(priority)); } } /// Enable OS_EVENT in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::OS_EVENT); } /// Disable OS_EVENT in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::OS_EVENT); } /// Check if OS_EVENT is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::OS_EVENT) } } impl OsEvent { /// Configure OS_EVENT interrupt for timer operation. /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. /// Also performs a software event to wake any pending WFE. pub fn configure_for_timer(&self, priority: Priority) { // Configure NVIC self.unpend(); self.set_priority(priority); unsafe { self.enable(); } // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. unsafe { cortex_m::interrupt::enable(); } // Wake any executor WFE that might be sleeping when we armed the first deadline cortex_m::asm::sev(); } } /// LPUART2 interrupt helper with methods similar to embassy-imxrt's InterruptExt. pub struct Lpuart2; pub const LPUART2: Lpuart2 = Lpuart2; impl InterruptExt for Lpuart2 { /// Clear any pending LPUART2 in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::LPUART2); } /// Set NVIC priority for LPUART2. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::LPUART2, u8::from(priority)); } } /// Enable LPUART2 in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::LPUART2); } /// Disable LPUART2 in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::LPUART2); } /// Check if LPUART2 is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::LPUART2) } } impl Lpuart2 { /// Configure LPUART2 interrupt for UART operation. /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. pub fn configure_for_uart(&self, priority: Priority) { // Configure NVIC self.unpend(); self.set_priority(priority); unsafe { self.enable(); } // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. unsafe { cortex_m::interrupt::enable(); } } /// Install LPUART2 handler into the RAM vector table. /// Safety: See `install_irq_handler`. pub unsafe fn install_handler(&self, handler: unsafe extern "C" fn()) { install_irq_handler(Interrupt::LPUART2, handler); } } pub struct Rtc; pub const RTC: Rtc = Rtc; impl InterruptExt for Rtc { /// Clear any pending RTC in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::RTC); } /// Set NVIC priority for RTC. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::RTC, u8::from(priority)); } } /// Enable RTC in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::RTC); } /// Disable RTC in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::RTC); } /// Check if RTC is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::RTC) } } pub struct Adc; pub const ADC1: Adc = Adc; impl InterruptExt for Adc { /// Clear any pending ADC1 in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::ADC1); } /// Set NVIC priority for ADC1. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::ADC1, u8::from(priority)); } } /// Enable ADC1 in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::ADC1); } /// Disable ADC1 in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::ADC1); } /// Check if ADC1 is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::ADC1) } } pub struct Gpio0; pub const GPIO0: Gpio0 = Gpio0; impl InterruptExt for Gpio0 { /// Clear any pending GPIO0 in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO0); } /// Set NVIC priority for GPIO0. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::GPIO0, u8::from(priority)); } } /// Enable GPIO0 in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO0); } /// Disable GPIO0 in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::GPIO0); } /// Check if GPIO0 is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO0) } } pub struct Gpio1; pub const GPIO1: Gpio1 = Gpio1; impl InterruptExt for Gpio1 { /// Clear any pending GPIO1 in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO1); } /// Set NVIC priority for GPIO1. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::GPIO1, u8::from(priority)); } } /// Enable GPIO1 in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO1); } /// Disable GPIO1 in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::GPIO1); } /// Check if GPIO1 is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO1) } } pub struct Gpio2; pub const GPIO2: Gpio2 = Gpio2; impl InterruptExt for Gpio2 { /// Clear any pending GPIO2 in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO2); } /// Set NVIC priority for GPIO2. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::GPIO2, u8::from(priority)); } } /// Enable GPIO2 in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO2); } /// Disable GPIO2 in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::GPIO2); } /// Check if GPIO2 is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO2) } } pub struct Gpio3; pub const GPIO3: Gpio3 = Gpio3; impl InterruptExt for Gpio3 { /// Clear any pending GPIO3 in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO3); } /// Set NVIC priority for GPIO3. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::GPIO3, u8::from(priority)); } } /// Enable GPIO3 in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO3); } /// Disable GPIO3 in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::GPIO3); } /// Check if GPIO3 is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO3) } } pub struct Gpio4; pub const GPIO4: Gpio4 = Gpio4; impl InterruptExt for Gpio4 { /// Clear any pending GPIO4 in NVIC. #[inline] fn unpend(&self) { cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO4); } /// Set NVIC priority for GPIO4. #[inline] fn set_priority(&self, priority: Priority) { unsafe { let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; nvic.set_priority(Interrupt::GPIO4, u8::from(priority)); } } /// Enable GPIO4 in NVIC. #[inline] unsafe fn enable(&self) { cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO4); } /// Disable GPIO4 in NVIC. #[inline] unsafe fn disable(&self) { cortex_m::peripheral::NVIC::mask(Interrupt::GPIO4); } /// Check if GPIO4 is pending in NVIC. #[inline] fn is_pending(&self) -> bool { cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO4) } } /// Set VTOR (Vector Table Offset) to a RAM-based vector table. /// Pass a pointer to the first word in the RAM table (stack pointer slot 0). /// Safety: Caller must ensure the RAM table is valid and aligned as required by the core. pub unsafe fn vtor_set_ram_vector_base(base: *const u32) { core::ptr::write_volatile(0xE000_ED08 as *mut u32, base as u32); } /// Install an interrupt handler into the current VTOR-based vector table. /// This writes the function pointer at index 16 + irq number. /// Safety: Caller must ensure VTOR points at a writable RAM table and that `handler` /// has the correct ABI and lifetime. pub unsafe fn install_irq_handler(irq: Interrupt, handler: unsafe extern "C" fn()) { let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; let idx = 16 + (irq as isize as usize); core::ptr::write_volatile(vtor_base.add(idx), handler as usize as u32); } impl OsEvent { /// Convenience to install the OS_EVENT handler into the RAM vector table. /// Safety: See `install_irq_handler`. pub unsafe fn install_handler(&self, handler: extern "C" fn()) { install_irq_handler(Interrupt::OS_EVENT, handler); } } /// Install OS_EVENT handler by raw address. Useful to avoid fn pointer type mismatches. /// Safety: Caller must ensure the address is a valid `extern "C" fn()` handler. pub unsafe fn os_event_install_handler_raw(handler_addr: usize) { let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; let idx = 16 + (Interrupt::OS_EVENT as isize as usize); core::ptr::write_volatile(vtor_base.add(idx), handler_addr as u32); } /// Provide a conservative default IRQ handler that avoids wedging the system. /// It clears all NVIC pending bits and returns, so spurious or reserved IRQs /// don’t trap the core in an infinite WFI loop during bring-up. #[no_mangle] pub unsafe extern "C" fn DefaultHandler() { let active = core::ptr::read_volatile(0xE000_ED04 as *const u32) & 0x1FF; let cfsr = core::ptr::read_volatile(0xE000_ED28 as *const u32); let hfsr = core::ptr::read_volatile(0xE000_ED2C as *const u32); let sp = cortex_m::register::msp::read(); let stacked = sp as *const u32; // Stacked registers follow ARMv8-M procedure call standard order let stacked_pc = unsafe { stacked.add(6).read() }; let stacked_lr = unsafe { stacked.add(5).read() }; LAST_DEFAULT_VECTOR.store(active as u16, Ordering::Relaxed); LAST_DEFAULT_CFSR.store(cfsr, Ordering::Relaxed); LAST_DEFAULT_HFSR.store(hfsr, Ordering::Relaxed); LAST_DEFAULT_COUNT.fetch_add(1, Ordering::Relaxed); LAST_DEFAULT_PC.store(stacked_pc, Ordering::Relaxed); LAST_DEFAULT_LR.store(stacked_lr, Ordering::Relaxed); LAST_DEFAULT_SP.store(sp, Ordering::Relaxed); // Do nothing here: on some MCUs/TrustZone setups, writing NVIC from a spurious // handler can fault if targeting the Secure bank. Just return. cortex_m::asm::dsb(); cortex_m::asm::isb(); }