From 2b4c4083339e347b1f3b3eecb44a69ad91cc84a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Quentin?= Date: Wed, 13 Mar 2024 16:45:34 +0100 Subject: [PATCH] Runtime interrupt binding (#1231) * Interrupt runtime binding * Add `set_interrupt_handler` for GPIO * Fix * Fix typo * Make sure to produce a warning for an unused `#[handler]` * Simplify GPIO interrupt handling * Appease Clippy * Make sure to patch the PACS in esp-hal * Use latest PAC commit * CHANGELOG.md entry --- esp-hal-procmacros/src/lib.rs | 66 ++++++++++++++++++++++++++ esp-hal/CHANGELOG.md | 1 + esp-hal/Cargo.toml | 10 ++++ esp-hal/src/gpio.rs | 48 +++++++++++++------ esp-hal/src/interrupt/riscv.rs | 7 +++ esp-hal/src/interrupt/xtensa.rs | 7 +++ esp-hal/src/peripheral.rs | 17 ++++++- esp-hal/src/soc/esp32/peripherals.rs | 2 +- esp-hal/src/soc/esp32c2/peripherals.rs | 2 +- esp-hal/src/soc/esp32c3/peripherals.rs | 2 +- esp-hal/src/soc/esp32c6/peripherals.rs | 2 +- esp-hal/src/soc/esp32h2/peripherals.rs | 2 +- esp-hal/src/soc/esp32p4/peripherals.rs | 2 +- esp-hal/src/soc/esp32s2/peripherals.rs | 2 +- esp-hal/src/soc/esp32s3/peripherals.rs | 2 +- examples/Cargo.toml | 10 ++++ examples/src/bin/gpio_interrupt.rs | 7 +-- 17 files changed, 163 insertions(+), 26 deletions(-) diff --git a/esp-hal-procmacros/src/lib.rs b/esp-hal-procmacros/src/lib.rs index c62061c25..de69cab27 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 acce91d46..24407772b 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 62fbe07fa..5077a0832 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -221,3 +221,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 2a20f69a2..8008f9a6c 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 7e2416ada..da1366058 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 776168389..285dfe400 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 e6ef8d904..a89c57ea7 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 87d4be457..b56d41cd4 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 6b60c1318..072a7e80a 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 0a744d6ae..5d1c9e229 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 c522f2eb5..11360622b 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 daf2febb8..251fc4929 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 9d5359817..0c1a4a6bc 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 37978c817..f8c04dc54 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 123f65708..aa93b1736 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 91ab16cba..7f9d7075b 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 a2303b51f..37984fae1 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 {}",