Make interrupt module more standard.

- Move typelevel interrupts to a special-purpose mod: `embassy_xx::interrupt::typelevel`.
- Reexport the PAC interrupt enum in `embassy_xx::interrupt`.

This has a few advantages:
- The `embassy_xx::interrupt` module is now more "standard".
  - It works with `cortex-m` functions for manipulating interrupts, for example.
  - It works with RTIC.
- the interrupt enum allows holding value that can be "any interrupt at runtime", this can't be done with typelevel irqs.
- When "const-generics on enums" is stable, we can remove the typelevel interrupts without disruptive changes to `embassy_xx::interrupt`.
This commit is contained in:
Dario Nieuwenhuis
2023-06-08 16:08:40 +02:00
parent 87ad66f2b4
commit 921780e6bf
72 changed files with 845 additions and 930 deletions

View File

@ -2,120 +2,207 @@
use core::mem;
use core::sync::atomic::{compiler_fence, Ordering};
use cortex_m::interrupt::InterruptNumber;
use cortex_m::peripheral::NVIC;
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
#[doc(hidden)]
pub mod _export {
pub use atomic_polyfill as atomic;
pub use embassy_macros::{cortex_m_interrupt as interrupt, cortex_m_interrupt_declare as declare};
}
/// Generate a standard `mod interrupt` for a HAL.
#[macro_export]
macro_rules! interrupt_mod {
($($irqs:ident),* $(,)?) => {
pub use cortex_m_rt::interrupt;
/// 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<I: Interrupt> {
/// 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();
}
/// Interrupt definitions.
pub mod interrupt {
pub use embassy_cortex_m::interrupt::{InterruptExt, Priority};
pub use crate::pac::interrupt::*;
pub use crate::pac::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<I: Interrupt, H: Handler<I>> {}
/// Type-level interrupt infrastructure.
///
/// This module contains one *type* per interrupt. This is used for checking at compile time that
/// the interrupts are correctly bound to HAL drivers.
///
/// As an end user, you shouldn't need to use this module directly. Use the [`crate::bind_interrupts!`] macro
/// to bind interrupts, and the [`crate::interrupt`] module to manually register interrupt handlers and manipulate
/// interrupts directly (pending/unpending, enabling/disabling, setting the priority, etc...)
pub mod typelevel {
use super::InterruptExt;
#[derive(Clone, Copy)]
pub(crate) struct NrWrap(pub(crate) u16);
unsafe impl cortex_m::interrupt::InterruptNumber for NrWrap {
fn number(self) -> u16 {
self.0
}
mod sealed {
pub trait Interrupt {}
}
/// Type-level interrupt.
///
/// This trait is implemented for all typelevel interrupt types in this module.
pub trait Interrupt: sealed::Interrupt {
/// Interrupt enum variant.
///
/// This allows going from typelevel interrupts (one type per interrupt) to
/// non-typelevel interrupts (a single `Interrupt` enum type, with one variant per interrupt).
const IRQ: super::Interrupt;
/// Enable the interrupt.
#[inline]
unsafe fn enable() {
Self::IRQ.enable()
}
/// Disable the interrupt.
#[inline]
fn disable() {
Self::IRQ.disable()
}
/// Check if interrupt is enabled.
#[inline]
fn is_enabled() -> bool {
Self::IRQ.is_enabled()
}
/// Check if interrupt is pending.
#[inline]
fn is_pending() -> bool {
Self::IRQ.is_pending()
}
/// Set interrupt pending.
#[inline]
fn pend() {
Self::IRQ.pend()
}
/// Unset interrupt pending.
#[inline]
fn unpend() {
Self::IRQ.unpend()
}
/// Get the priority of the interrupt.
#[inline]
fn get_priority() -> crate::interrupt::Priority {
Self::IRQ.get_priority()
}
/// Set the interrupt priority.
#[inline]
fn set_priority(prio: crate::interrupt::Priority) {
Self::IRQ.set_priority(prio)
}
}
$(
#[allow(non_camel_case_types)]
#[doc=stringify!($irqs)]
#[doc=" typelevel interrupt."]
pub enum $irqs {}
impl sealed::Interrupt for $irqs{}
impl Interrupt for $irqs {
const IRQ: super::Interrupt = super::Interrupt::$irqs;
}
)*
/// 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<I: Interrupt> {
/// 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<I: Interrupt, H: Handler<I>> {}
}
}
};
}
/// Represents an interrupt type that can be configured by embassy to handle
/// interrupts.
pub unsafe trait Interrupt {
/// Return the NVIC interrupt number for this interrupt.
fn number() -> u16;
pub unsafe trait InterruptExt: InterruptNumber + Copy {
/// Enable the interrupt.
#[inline]
unsafe fn enable() {
unsafe fn enable(self) {
compiler_fence(Ordering::SeqCst);
NVIC::unmask(NrWrap(Self::number()))
NVIC::unmask(self)
}
/// Disable the interrupt.
#[inline]
fn disable() {
NVIC::mask(NrWrap(Self::number()));
fn disable(self) {
NVIC::mask(self);
compiler_fence(Ordering::SeqCst);
}
/// Check if interrupt is being handled.
#[inline]
#[cfg(not(armv6m))]
fn is_active() -> bool {
NVIC::is_active(NrWrap(Self::number()))
fn is_active(self) -> bool {
NVIC::is_active(self)
}
/// Check if interrupt is enabled.
#[inline]
fn is_enabled() -> bool {
NVIC::is_enabled(NrWrap(Self::number()))
fn is_enabled(self) -> bool {
NVIC::is_enabled(self)
}
/// Check if interrupt is pending.
#[inline]
fn is_pending() -> bool {
NVIC::is_pending(NrWrap(Self::number()))
fn is_pending(self) -> bool {
NVIC::is_pending(self)
}
/// Set interrupt pending.
#[inline]
fn pend() {
NVIC::pend(NrWrap(Self::number()))
fn pend(self) {
NVIC::pend(self)
}
/// Unset interrupt pending.
#[inline]
fn unpend() {
NVIC::unpend(NrWrap(Self::number()))
fn unpend(self) {
NVIC::unpend(self)
}
/// Get the priority of the interrupt.
#[inline]
fn get_priority() -> Priority {
Priority::from(NVIC::get_priority(NrWrap(Self::number())))
fn get_priority(self) -> Priority {
Priority::from(NVIC::get_priority(self))
}
/// Set the interrupt priority.
#[inline]
fn set_priority(prio: Priority) {
fn set_priority(self, prio: Priority) {
critical_section::with(|_| unsafe {
let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(());
nvic.set_priority(NrWrap(Self::number()), prio.into())
nvic.set_priority(self, prio.into())
})
}
}
unsafe impl<T: InterruptNumber + Copy> InterruptExt for T {}
impl From<u8> for Priority {
fn from(priority: u8) -> Self {
unsafe { mem::transmute(priority & PRIO_MASK) }