diff --git a/embassy-cortex-m/src/interrupt.rs b/embassy-cortex-m/src/interrupt.rs index ead9d52f..3a82726d 100644 --- a/embassy-cortex-m/src/interrupt.rs +++ b/embassy-cortex-m/src/interrupt.rs @@ -13,6 +13,36 @@ pub mod _export { pub use embassy_macros::{cortex_m_interrupt as interrupt, cortex_m_interrupt_declare as declare}; } +/// Interrupt handler trait. +/// +/// Drivers that need to handle interrupts implement this trait. +/// The user must ensure `on_interrupt()` is called every time the interrupt fires. +/// Drivers must use use [`Binding`] to assert at compile time that the user has done so. +pub trait Handler { + /// Interrupt handler function. + /// + /// Must be called every time the `I` interrupt fires, synchronously from + /// the interrupt handler context. + /// + /// # Safety + /// + /// This function must ONLY be called from the interrupt handler for `I`. + unsafe fn on_interrupt(); +} + +/// Compile-time assertion that an interrupt has been bound to a handler. +/// +/// For the vast majority of cases, you should use the `bind_interrupts!` +/// macro instead of writing `unsafe impl`s of this trait. +/// +/// # Safety +/// +/// By implementing this trait, you are asserting that you have arranged for `H::on_interrupt()` +/// to be called every time the `I` interrupt fires. +/// +/// This allows drivers to check bindings at compile-time. +pub unsafe trait Binding> {} + /// Implementation detail, do not use outside embassy crates. #[doc(hidden)] pub struct DynHandler { diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 3c5db5c7..446b1f41 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -95,14 +95,39 @@ pub mod wdt; #[cfg_attr(feature = "_nrf9160", path = "chips/nrf9160.rs")] mod chip; -pub use chip::EASY_DMA_SIZE; - pub mod interrupt { - //! nRF interrupts for cortex-m devices. + //! Interrupt definitions and macros to bind them. pub use cortex_m::interrupt::{CriticalSection, Mutex}; pub use embassy_cortex_m::interrupt::*; pub use crate::chip::irqs::*; + + /// Macro to bind interrupts to handlers. + /// + /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) + /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to + /// prove at compile-time that the right interrupts have been bound. + // developer note: this macro can't be in `embassy-cortex-m` due to the use of `$crate`. + #[macro_export] + macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + unsafe extern "C" fn $irq() { + $( + <$handler as $crate::interrupt::Handler<$crate::interrupt::$irq>>::on_interrupt(); + )* + } + + $( + unsafe impl $crate::interrupt::Binding<$crate::interrupt::$irq, $handler> for $name {} + )* + )* + }; + } } // Reexports @@ -111,7 +136,7 @@ pub mod interrupt { pub use chip::pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use chip::pac; -pub use chip::{peripherals, Peripherals}; +pub use chip::{peripherals, Peripherals, EASY_DMA_SIZE}; pub use embassy_cortex_m::executor; pub use embassy_cortex_m::interrupt::_export::interrupt; pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};