This crate provides a method of delegating creation of occupations, and attempts to improve pains associated with creating registrations.
As an end-user of this crate, you will want to use the [take_nvic_interrupt
] and [take_exception
] macros. These generate implementors of the [NvicInterruptRegistration
] and [ExceptionRegistration
] traits respectively, which can be passed to functions that make use of interrupts.
To see information on how you can use [InterruptRegistration
], [NvicInterruptRegistration
] and [ExceptionRegistration
] refer to the docs on those traits.
To demonstrate the problem that this crate tries to solve, we will use an example use case.
To help explain the use case of this crate, we use the following definitions:
- a registration: the function pointer placed in an interrupt vector, the declaration of an interrupt handler.
- an occupation: the code that should run when servicing an interrupt, the body of an interrupt handler.
Our goal with the use case is:
- Configure the
SysTick
interrupt so that it triggers once every1337
cycles. - Increment a counter every time the
SysTick
interrupt occurs.
When using the cortex_m_rt
crate, we would do something like this:
# fn systick_reload(_: u32) {}
# fn setup_systick_exception(_: u32) {}
use cortex_m_rt::{exception, entry};
#[exception]
fn SysTick() { // This is the "registration" (can only
// be provided once in entirety of the
// program, incl. libraries)
static mut COUNTER: u32 = 0; // This is part of the "occupation"
*COUNTER += 1; // This is the "occupation", the
systick_reload(1337); // actual code to be executed
// when handling the SysTick
// interrupt.
}
#[entry]
fn main() {
setup_systick_exception(1337);
loop {}
}
And within the crate providing setup_systick_exception
and systick_reload
:
pub fn setup_systick_exception(reload_value: u32) {
/* Setup systick so that it triggers the SysTick interrupt
after `reload_value` cycles
*/
}
pub fn systick_reload(reload_value: u32) {
// Reload the systick value
}
In this example:
- There is no semantic connection between
setup_systick_exception
and the registration/occupation besides naming and possibly documentation. - The responsibility of adding the correct occupation to the correct registration falls entirely on the person writing the program.
- The maintainer of the crate that provides
setup_systick_exception
andsystick_reload
has no control over the occupation or registration. - There is no need for a trampoline to setup up the interrupt handler.
To represent registrations, the [InterruptRegistration
], [NvicInterruptRegistration
], and [ExceptionRegistration
] traits are provided. They can be used to insert occupations in a more dynamic way.
These traits allow for the following:
- Code that wishes to provide an occupation without directly creating a registration can be written by requiring that user code provides an registration.
- Code that wishes to provide an occupation can verify that a provided registration is correct for the to-be-created occupation.
To alleviate difficulties with creating registrations, the [take_nvic_interrupt
] and [take_exception
] proc-macros are provided. They perform the less self explanatory parts of the setting up a registration, and provide an implementor of [NvicInterruptRegistration
] and [ExceptionRegistration
], respectively.
With these new tools, we can rewrite our code to look as follows:
# fn setup_systick_exception<T: cortex_m_interrupt::InterruptRegistration>(_: u32, _: T, _: fn()) {}
use cortex_m_rt::entry;
use cortex_m::peripheral::scb::Exception::SysTick;
static mut COUNTER: u32 = 0;
fn increase_counter() {
unsafe { COUNTER += 1 };
}
#[entry]
fn main() -> ! {
// We create the registration
let systick_registration = cortex_m_interrupt::take_exception!(SysTick);
// And pass it to some function that will do some configuration and
// provide a occupation for that registration. It also allows us to
// inject our own expansion to the occupation.
setup_systick_exception(1337, systick_registration, increase_counter);
loop {}
}
In the crate providing setup_systick_exception
:
# use cortex_m_interrupt::ExceptionRegistration;
pub fn setup_systick_exception<Registration: ExceptionRegistration>(
reload_value: u32,
registration: Registration,
f: fn(),
) {
// Assert that we've been given a registration of the correct
// exception/interrupt.
assert_eq!(
cortex_m::peripheral::scb::Exception::SysTick,
registration.exception()
);
/* Setup systick so that it triggers the SysTick interrupt
after `reload_value` cycles
*/
use core::mem::MaybeUninit;
static mut USER_HANDLE: MaybeUninit<fn()> = MaybeUninit::uninit();
static mut RELOAD_VALUE: MaybeUninit<u32> = MaybeUninit::uninit();
unsafe { USER_HANDLE.write(f) };
unsafe { RELOAD_VALUE.write(reload_value) };
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Release);
registration.occupy(|| {
systick_reload(unsafe { RELOAD_VALUE.assume_init() });
// Call extra user code
unsafe { (USER_HANDLE.assume_init())(); }
});
}
fn systick_reload(reload_value: u32) {
// Reload the systick value
}
In the revised example:
- There is a more defined semantic connection between the registration and the occupation.
- The implementor of
setup_systick_exception
has full control over the occupation, and can optionally allow user code to perform some extra actions. - The implementor of
setup_systick_exception
can verify that the correct registration is passed to it. - A trampoline is now required in the interrupt handler, adding ~5 cycles of extra processing when an interrupt occurs.
The main differences between the cortex-m-rt
approach and what cortex-m-interrupt
provides are the following:
cortex-m-interrupt
offers a way of creating clearer separation of responsibilities when it comes to registering interrupt handlers.- The traits provided by
cortex-m-interrupt
support a tighter semantic connection between creating a registration and creating an occupation. - The method provided by
cortex-m-rt
has slightly less overhead as it does not require a trampoline.