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
This commit is contained in:
Björn Quentin 2024-03-13 16:45:34 +01:00 committed by GitHub
parent 1f129744fd
commit 2b4c408333
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 163 additions and 26 deletions

View File

@ -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

View File

@ -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

View File

@ -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" }

View File

@ -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<Unknown>;
/// Convenience constant for `Option::None` pin
pub const NO_PIN: Option<NoPinType> = None;
static USER_INTERRUPT_HANDLER: Mutex<Cell<Option<unsafe extern "C" fn()>>> =
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))]

View File

@ -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.
///

View File

@ -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,

View File

@ -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 [<bind_ $interrupt:lower _interrupt >](&mut self, handler: unsafe extern "C" fn() -> ()) {
unsafe { $crate::interrupt::bind_interrupt($crate::peripherals::Interrupt::$interrupt, handler); }
}
}
)*
}
)*
)*
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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" }

View File

@ -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 {}",