diff --git a/esp-hal-procmacros/src/lib.rs b/esp-hal-procmacros/src/lib.rs index c62061c25fc..de69cab27ce 100644 --- a/esp-hal-procmacros/src/lib.rs +++ b/esp-hal-procmacros/src/lib.rs @@ -359,6 +359,72 @@ pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { .into() } +/// Mark a function as an interrupt handler. +/// +/// This is really just a nicer looking way to make a function `unsafe extern +/// "C"` +#[cfg(feature = "interrupt")] +#[proc_macro_attribute] +pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream { + use darling::ast::NestedMeta; + use proc_macro::Span; + use proc_macro_error::abort; + use syn::{parse::Error as ParseError, spanned::Spanned, ItemFn, ReturnType, Type}; + + use self::interrupt::{check_attr_whitelist, WhiteListCaller}; + + let mut f: ItemFn = syn::parse(input).expect("`#[handler]` must be applied to a function"); + let original_span = f.span(); + + let attr_args = match NestedMeta::parse_meta_list(args.into()) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(darling::Error::from(e).write_errors()); + } + }; + + if attr_args.len() > 0 { + abort!(Span::call_site(), "This attribute accepts no arguments") + } + + // XXX should we blacklist other attributes? + + if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) { + return error; + } + + let valid_signature = f.sig.constness.is_none() + && f.sig.abi.is_none() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + } + && f.sig.inputs.len() <= 1; + + if !valid_signature { + return ParseError::new( + f.span(), + "`#[handler]` handlers must have signature `[unsafe] fn([&mut Context]) [-> !]`", + ) + .to_compile_error() + .into(); + } + + f.sig.abi = syn::parse_quote_spanned!(original_span => extern "C"); + + quote::quote_spanned!( original_span => + #f + ) + .into() +} + /// Create an enum for erased GPIO pins, using the enum-dispatch pattern /// /// Only used internally diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index acce91d468d..24407772bec 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Prefer mutable references over moving for DMA transactions (#1238) +- Support runtime interrupt binding, adapt GPIO driver (#1231) ### Removed diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index 912185c06fe..24742b3f14b 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -220,3 +220,13 @@ opsram-16m = [] [lints.clippy] mixed_attributes_style = "allow" + +[patch.crates-io] +esp32 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32s2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32s3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32c2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32c3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32c6 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32h2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32p4 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } diff --git a/esp-hal/src/gpio.rs b/esp-hal/src/gpio.rs index 2a20f69a2b2..8008f9a6c10 100644 --- a/esp-hal/src/gpio.rs +++ b/esp-hal/src/gpio.rs @@ -22,7 +22,10 @@ //! //! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/ -use core::{convert::Infallible, marker::PhantomData}; +use core::{cell::Cell, convert::Infallible, marker::PhantomData}; + +use critical_section::Mutex; +use procmacros::interrupt; #[cfg(any(adc, dac))] pub(crate) use crate::analog; @@ -38,6 +41,9 @@ pub type NoPinType = Gpio0; /// Convenience constant for `Option::None` pin pub const NO_PIN: Option = None; +static USER_INTERRUPT_HANDLER: Mutex>> = + Mutex::new(Cell::new(None)); + #[derive(Copy, Clone)] pub enum Event { RisingEdge = 1, @@ -1854,6 +1860,32 @@ impl IO { pins, } } + + /// Install the given interrupt handler replacing any previously set + /// handler. + /// + /// When the async feature is enabled the handler will be called first and + /// the internal async handler will run after. In that case it's + /// important to not reset the interrupt status when mixing sync and + /// async (i.e. using async wait) interrupt handling. + pub fn set_interrupt_handler(&mut self, handler: unsafe extern "C" fn() -> ()) { + critical_section::with(|cs| { + USER_INTERRUPT_HANDLER.borrow(cs).set(Some(handler)); + }); + } +} + +#[interrupt] +unsafe fn GPIO() { + if let Some(user_handler) = critical_section::with(|cs| USER_INTERRUPT_HANDLER.borrow(cs).get()) + { + unsafe { + user_handler(); + } + } + + #[cfg(feature = "async")] + asynch::handle_gpio_interrupt(); } pub trait GpioProperties { @@ -3149,19 +3181,7 @@ mod asynch { }); } - #[cfg(not(any(esp32p4)))] - #[interrupt] - unsafe fn GPIO() { - handle_gpio_interrupt(); - } - - #[cfg(esp32p4)] - #[interrupt] - unsafe fn GPIO_INT0() { - handle_gpio_interrupt(); - } - - fn handle_gpio_interrupt() { + pub(super) fn handle_gpio_interrupt() { let intrs_bank0 = InterruptStatusRegisterAccessBank0::interrupt_status_read(); #[cfg(any(esp32, esp32s2, esp32s3, esp32p4))] diff --git a/esp-hal/src/interrupt/riscv.rs b/esp-hal/src/interrupt/riscv.rs index 7e2416ada27..da13660588b 100644 --- a/esp-hal/src/interrupt/riscv.rs +++ b/esp-hal/src/interrupt/riscv.rs @@ -199,6 +199,13 @@ mod vectored { Ok(()) } + /// Bind the given interrupt to the given handler + pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn() -> ()) { + let ptr = &peripherals::__EXTERNAL_INTERRUPTS[interrupt as usize]._handler as *const _ + as *mut unsafe extern "C" fn() -> (); + ptr.write_volatile(handler); + } + /// Enables an interrupt at a given priority, maps it to the given CPU /// interrupt and assigns the given priority. /// diff --git a/esp-hal/src/interrupt/xtensa.rs b/esp-hal/src/interrupt/xtensa.rs index 77616838985..285dfe40042 100644 --- a/esp-hal/src/interrupt/xtensa.rs +++ b/esp-hal/src/interrupt/xtensa.rs @@ -311,6 +311,13 @@ mod vectored { Ok(()) } + /// Bind the given interrupt to the given handler + pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn() -> ()) { + let ptr = &peripherals::__INTERRUPTS[interrupt as usize]._handler as *const _ + as *mut unsafe extern "C" fn() -> (); + ptr.write_volatile(handler); + } + fn interrupt_level_to_cpu_interrupt( level: Priority, is_edge: bool, diff --git a/esp-hal/src/peripheral.rs b/esp-hal/src/peripheral.rs index e6ef8d904ce..a89c57ea740 100644 --- a/esp-hal/src/peripheral.rs +++ b/esp-hal/src/peripheral.rs @@ -221,7 +221,7 @@ mod peripheral_macros { #[doc(hidden)] #[macro_export] macro_rules! peripherals { - ($($(#[$cfg:meta])? $name:ident <= $from_pac:tt),*$(,)?) => { + ($($(#[$cfg:meta])? $name:ident <= $from_pac:tt $(($($interrupt:ident),*))? ),*$(,)?) => { /// Contains the generated peripherals which implement [`Peripheral`] mod peripherals { @@ -278,6 +278,21 @@ mod peripheral_macros { $( pub use peripherals::$name; )* + + $( + $( + impl peripherals::$name { + $( + paste::paste!{ + pub fn [](&mut self, handler: unsafe extern "C" fn() -> ()) { + unsafe { $crate::interrupt::bind_interrupt($crate::peripherals::Interrupt::$interrupt, handler); } + } + } + )* + } + )* + )* + } } diff --git a/esp-hal/src/soc/esp32/peripherals.rs b/esp-hal/src/soc/esp32/peripherals.rs index 87d4be45726..b56d41cd4b1 100644 --- a/esp-hal/src/soc/esp32/peripherals.rs +++ b/esp-hal/src/soc/esp32/peripherals.rs @@ -32,7 +32,7 @@ crate::peripherals! { EFUSE <= EFUSE, FLASH_ENCRYPTION <= FLASH_ENCRYPTION, FRC_TIMER <= FRC_TIMER, - GPIO <= GPIO, + GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HINF <= HINF, I2C0 <= I2C0, diff --git a/esp-hal/src/soc/esp32c2/peripherals.rs b/esp-hal/src/soc/esp32c2/peripherals.rs index 6b60c13183d..072a7e80a4a 100644 --- a/esp-hal/src/soc/esp32c2/peripherals.rs +++ b/esp-hal/src/soc/esp32c2/peripherals.rs @@ -28,7 +28,7 @@ crate::peripherals! { ECC <= ECC, EFUSE <= EFUSE, EXTMEM <= EXTMEM, - GPIO <= GPIO, + GPIO <= GPIO (GPIO,GPIO_NMI), I2C0 <= I2C0, INTERRUPT_CORE0 <= INTERRUPT_CORE0, IO_MUX <= IO_MUX, diff --git a/esp-hal/src/soc/esp32c3/peripherals.rs b/esp-hal/src/soc/esp32c3/peripherals.rs index 0a744d6aec4..5d1c9e229aa 100644 --- a/esp-hal/src/soc/esp32c3/peripherals.rs +++ b/esp-hal/src/soc/esp32c3/peripherals.rs @@ -30,7 +30,7 @@ crate::peripherals! { DS <= DS, EFUSE <= EFUSE, EXTMEM <= EXTMEM, - GPIO <= GPIO, + GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HMAC <= HMAC, I2C0 <= I2C0, diff --git a/esp-hal/src/soc/esp32c6/peripherals.rs b/esp-hal/src/soc/esp32c6/peripherals.rs index c522f2eb58a..11360622b80 100644 --- a/esp-hal/src/soc/esp32c6/peripherals.rs +++ b/esp-hal/src/soc/esp32c6/peripherals.rs @@ -30,7 +30,7 @@ crate::peripherals! { ECC <= ECC, EFUSE <= EFUSE, EXTMEM <= EXTMEM, - GPIO <= GPIO, + GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HINF <= HINF, HMAC <= HMAC, diff --git a/esp-hal/src/soc/esp32h2/peripherals.rs b/esp-hal/src/soc/esp32h2/peripherals.rs index daf2febb8dc..251fc492934 100644 --- a/esp-hal/src/soc/esp32h2/peripherals.rs +++ b/esp-hal/src/soc/esp32h2/peripherals.rs @@ -28,7 +28,7 @@ crate::peripherals! { DS <= DS, ECC <= ECC, EFUSE <= EFUSE, - GPIO <= GPIO, + GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HMAC <= HMAC, HP_APM <= HP_APM, diff --git a/esp-hal/src/soc/esp32p4/peripherals.rs b/esp-hal/src/soc/esp32p4/peripherals.rs index 9d5359817a0..0c1a4a6bc62 100644 --- a/esp-hal/src/soc/esp32p4/peripherals.rs +++ b/esp-hal/src/soc/esp32p4/peripherals.rs @@ -32,7 +32,7 @@ crate::peripherals! { ECC <= ECC, ECDSA <= ECDSA, EFUSE <= EFUSE, - GPIO <= GPIO, + GPIO <= GPIO (GPIO,GPIO_INT1,GPIO_INT2,GPIO_INT3), GPIO_SD <= GPIO_SD, H264 <= H264, H264_DMA <= H264_DMA, diff --git a/esp-hal/src/soc/esp32s2/peripherals.rs b/esp-hal/src/soc/esp32s2/peripherals.rs index 37978c817f9..f8c04dc5478 100644 --- a/esp-hal/src/soc/esp32s2/peripherals.rs +++ b/esp-hal/src/soc/esp32s2/peripherals.rs @@ -30,7 +30,7 @@ crate::peripherals! { DS <= DS, EFUSE <= EFUSE, EXTMEM <= EXTMEM, - GPIO <= GPIO, + GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HMAC <= HMAC, I2C0 <= I2C0, diff --git a/esp-hal/src/soc/esp32s3/peripherals.rs b/esp-hal/src/soc/esp32s3/peripherals.rs index 123f65708f8..aa93b173659 100644 --- a/esp-hal/src/soc/esp32s3/peripherals.rs +++ b/esp-hal/src/soc/esp32s3/peripherals.rs @@ -30,7 +30,7 @@ crate::peripherals! { DS <= DS, EFUSE <= EFUSE, EXTMEM <= EXTMEM, - GPIO <= GPIO, + GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HMAC <= HMAC, I2C0 <= I2C0, diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 91ab16cbaad..7f9d7075b92 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -70,3 +70,13 @@ psram-2m = ["esp-hal/psram-2m"] [profile.release] debug = true + +[patch.crates-io] +esp32 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32s2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32s3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32c2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32c3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32c6 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32h2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } +esp32p4 = { git = "https://github.com/esp-rs/esp-pacs", rev = "963c280621f0b7ec26546a5eff24a5032305437f" } diff --git a/examples/src/bin/gpio_interrupt.rs b/examples/src/bin/gpio_interrupt.rs index a2303b51f6c..37984fae13e 100644 --- a/examples/src/bin/gpio_interrupt.rs +++ b/examples/src/bin/gpio_interrupt.rs @@ -36,7 +36,8 @@ fn main() -> ! { let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); // Set GPIO2 as an output, and set its state high initially. - let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let mut io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + io.set_interrupt_handler(handler); let mut led = io.pins.gpio2.into_push_pull_output(); #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] @@ -61,9 +62,9 @@ fn main() -> ! { } } +#[handler] #[ram] -#[interrupt] -fn GPIO() { +fn handler() { #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] esp_println::println!( "GPIO Interrupt with priority {}",