From de4d7f56473df58d9b3fa8ec4917ab86550005ae Mon Sep 17 00:00:00 2001 From: WillaWillNot Date: Fri, 14 Nov 2025 17:00:23 -0500 Subject: Added type-erased AnyBinding for interrupt-handler bindings, and changed Exti driver to accept custom bindings without sacrificing its ability to accept type-erased Channels --- embassy-hal-internal/src/interrupt.rs | 55 +++++++++++++++++++++++++++++++- embassy-stm32/src/exti.rs | 59 +++++++++++++++++++++++++---------- 2 files changed, 97 insertions(+), 17 deletions(-) diff --git a/embassy-hal-internal/src/interrupt.rs b/embassy-hal-internal/src/interrupt.rs index ce6057e2d..c0ea666da 100644 --- a/embassy-hal-internal/src/interrupt.rs +++ b/embassy-hal-internal/src/interrupt.rs @@ -124,6 +124,10 @@ macro_rules! interrupt_mod { /// /// This function must ONLY be called from the interrupt handler for `I`. unsafe fn on_interrupt(); + + /// Source ID of the Handler. No need to override the default outside of internal implementations, + /// where it will always be HandlerType::User. + const SOURCE_ID: HandlerType = HandlerType::User; } /// Compile-time assertion that an interrupt has been bound to a handler. @@ -137,7 +141,56 @@ macro_rules! interrupt_mod { /// to be called every time the `I` interrupt fires. /// /// This allows drivers to check bindings at compile-time. - pub unsafe trait Binding> {} + pub unsafe trait Binding> { + /// Obtain a type-erased Binding. + /// + /// If using the `bind_interrupts!` macro, you will likely have to use a fully qualified path + /// to call this on the output struct: `>::into_any()` + fn into_any() -> AnyBinding { + AnyBinding { + irq: I::IRQ, + handler_source: H::SOURCE_ID, + } + } + } + + #[doc(hidden)] + #[derive(Copy, Clone)] + pub struct PrivateHandlerType { + pub(crate) _private: (), + } + impl PrivateHandlerType { + pub(crate) const fn new() -> Self { + Self { + _private: (), + } + } + } + + /// Driver which defined the Handler. Always User for user-defined handlers. + #[derive(Copy, Clone)] + pub enum HandlerType { + User, + Embassy(PrivateHandlerType), + EmbassyStm32Exti(PrivateHandlerType), + } + + /// Type-erased Binding. + /// + /// Useful for proving a particular binding has been made to a driver which also accepts + /// type-erased peripheral arguments that hide the necessary Interrupt type at compile time. + pub struct AnyBinding { + irq: super::Interrupt, + handler_source: HandlerType, + } + impl AnyBinding { + pub const fn irq(&self) -> super::Interrupt { + self.irq + } + pub const fn source(&self) -> HandlerType { + self.handler_source + } + } } } }; diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index 899d5e677..172435caa 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -10,9 +10,11 @@ use embassy_sync::waitqueue::AtomicWaker; use futures_util::FutureExt; use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin, PinNumber, Pull}; +use crate::interrupt::Interrupt as InterruptEnum; +use crate::interrupt::typelevel::{AnyBinding, HandlerType, Interrupt as InterruptType, PrivateHandlerType}; use crate::pac::EXTI; use crate::pac::exti::regs::Lines; -use crate::{Peri, interrupt, pac, peripherals}; +use crate::{Peri, pac, peripherals}; const EXTI_COUNT: usize = 16; static EXTI_WAKERS: [AtomicWaker; EXTI_COUNT] = [const { AtomicWaker::new() }; EXTI_COUNT]; @@ -106,15 +108,24 @@ impl<'d> Unpin for ExtiInput<'d> {} impl<'d> ExtiInput<'d> { /// Create an EXTI input. - pub fn new(pin: Peri<'d, T>, ch: Peri<'d, T::ExtiChannel>, pull: Pull) -> Self { + /// + /// The interrupt [Binding] must be type-erased to [AnyBinding] via [Binding::into_any()] in order + /// to support type-erased [AnyChannel] arguments. + /// + /// The Binding must bind the Channel's IRQ to [InterruptHandler]. + pub fn new(pin: Peri<'d, T>, ch: Peri<'d, T::ExtiChannel>, pull: Pull, binding: AnyBinding) -> Self { // Needed if using AnyPin+AnyChannel. assert_eq!(pin.pin(), ch.number()); + assert_eq!(ch.irq(), binding.irq()); + assert!(matches!(binding.source(), HandlerType::EmbassyStm32Exti(_))); Self { pin: Input::new(pin, pull), } } + //pub fn new, I: Instance, C: Channel>( + /// Get whether the pin is high. pub fn is_high(&self) -> bool { self.pin.is_high() @@ -328,7 +339,7 @@ macro_rules! foreach_exti_irq { (EXTI15) => { $action!(EXTI15); }; // plus the weird ones - (EXTI0_1) => { $action!( EXTI0_1 ); }; + (EXTI0_1) => { $action!(EXTI0_1); }; (EXTI15_10) => { $action!(EXTI15_10); }; (EXTI15_4) => { $action!(EXTI15_4); }; (EXTI1_0) => { $action!(EXTI1_0); }; @@ -341,18 +352,25 @@ macro_rules! foreach_exti_irq { }; } -macro_rules! impl_irq { - ($e:ident) => { - #[allow(non_snake_case)] - #[cfg(feature = "rt")] - #[interrupt] - unsafe fn $e() { - on_irq() - } - }; +///EXTI interrupt handler. All EXTI interrupt vectors should be bound to this handler. +/// +/// It is generic over the [Interrupt](crate::interrupt::typelevel::Interrupt) rather +/// than the [Instance](crate::exti::Instance) because it should not be bound multiple +/// times to the same vector on chips which multiplex multiple EXTI interrupts into one vector. +// +// It technically doesn't need to be generic at all, except to satisfy the generic argument +// of [Handler](crate::interrupt::typelevel::Handler). All EXTI interrupts eventually +// land in the same on_irq() function. +pub struct InterruptHandler { + _phantom: PhantomData, } -foreach_exti_irq!(impl_irq); +impl crate::interrupt::typelevel::Handler for InterruptHandler { + const SOURCE_ID: HandlerType = HandlerType::EmbassyStm32Exti(PrivateHandlerType::new()); + unsafe fn on_interrupt() { + on_irq() + } +} trait SealedChannel {} @@ -361,6 +379,8 @@ trait SealedChannel {} pub trait Channel: PeripheralType + SealedChannel + Sized { /// Get the EXTI channel number. fn number(&self) -> PinNumber; + /// Get the EXTI IRQ, which may be the same for multiple channels + fn irq(&self) -> InterruptEnum; } /// Type-erased EXTI channel. @@ -368,6 +388,7 @@ pub trait Channel: PeripheralType + SealedChannel + Sized { /// This represents ownership over any EXTI channel, known at runtime. pub struct AnyChannel { number: PinNumber, + irq: InterruptEnum, } impl_peripheral!(AnyChannel); @@ -376,6 +397,9 @@ impl Channel for AnyChannel { fn number(&self) -> PinNumber { self.number } + fn irq(&self) -> InterruptEnum { + self.irq + } } macro_rules! impl_exti { @@ -385,12 +409,15 @@ macro_rules! impl_exti { fn number(&self) -> PinNumber { $number } + fn irq(&self) -> InterruptEnum { + crate::_generated::peripheral_interrupts::EXTI::$type::IRQ + } } - impl From for AnyChannel { - fn from(val: peripherals::$type) -> Self { + fn from(_val: peripherals::$type) -> Self { Self { - number: val.number() as PinNumber, + number: $number, + irq: crate::_generated::peripheral_interrupts::EXTI::$type::IRQ, } } } -- cgit