aboutsummaryrefslogtreecommitdiffstats
path: root/src/handler.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/handler.rs')
-rw-r--r--src/handler.rs185
1 files changed, 185 insertions, 0 deletions
diff --git a/src/handler.rs b/src/handler.rs
new file mode 100644
index 0000000..ec1f22e
--- /dev/null
+++ b/src/handler.rs
@@ -0,0 +1,185 @@
+//! Call closures from interrupt handlers.
+//!
+//! # Motivation
+//!
+//! Existing solutions for interrupt handlers typically revolve around
+//! wrapping resources needed by the handler in an `Option`, wrapped
+//! in a `RefCell` wrapped in an `Mutex`, incurring some run-time
+//! overhead every time the resource is required in the interrupt
+//! handler, in addition to a fair amount of boilerplate. This module
+//! attempts to leverage Rust's borrow checker and move semantics to
+//! allow interrupt handlers to directly use their resources with a
+//! minimum of overhead.
+//!
+//! To accomplish this, we use a closure which is called by an
+//! interrupt handler. Because the closure has access to its
+//! environment, we can use `move`, references, and mutable references
+//! to ensure that variables are available as necessary to the
+//! interrupt handler, while leveraging the borrow checker to ensure
+//! safety at compile time. The only overhead is what it takes to call
+//! the closure itself.
+//!
+//! # Safety
+//!
+//! While this module endeavors to use Rust's safety guarantees to
+//! allow for use of resources inside interrupt handlers, due to
+//! expected use-cases, closure semantics, and how interrupt handlers
+//! are invoked from hardware, certain operations cannot be done
+//! safely at this level.
+//!
+//! Notably, for the handler to be useful when called from interrupt
+//! context, it needs to be stored in a `static mut` variable. This
+//! means that the closure you supply it must also be effectively
+//! `static` or replaced with a longer-lived closure before it goes
+//! out of scope. `Handler::default_handler()` is provided for this
+//! purpose.
+//!
+//! Additionally, replacement of an interrupt handler's closure may
+//! race with the calling of the interrupt handler's closure (i.e.,
+//! `Handler.replace()` may happen concurrently with
+//! `Handler.call()`). You need to avoid this situation however is
+//! appropriate for your code. The expected usage would be replacing
+//! the handler`s closure once, while interrupts are disabled, thus
+//! preventing the simultaneous replace/call problem. As this module
+//! makes no assumptions about the environment in which it will be
+//! used, this cannot be done for you.
+//!
+//! # Examples
+//!
+//! This example for an ARM Cortex-M system demonstrates safe usage by
+//! only replacing the closure for `SYSTICK_HANDLER` inside a critical
+//! section obtained by `cortex_m::interrupt::free()`, and shows how
+//! it is called via the `SysTick()` function, which is called
+//! directly from hardware.
+//!
+//! ``` no_run
+//! use clint::Handler;
+//! use cortex_m_rt::exception;
+//!
+//! static mut SYSTICK_HANDLER: Handler = Handler::new();
+//!
+//! fn main() {
+//! // NB: `closure` is in the lexical scope of `main`, and thus
+//! // cannot go out of scope.
+//! let closure = || {
+//! // Your interrupt handling code.
+//! };
+//! // Replace the handler for SysTick with closure while interrupts are
+//! // disabled.
+//! cortex_m::interrupt::free(|_| {
+//! unsafe { SYSTICK_HANDLER.replace(&closure) };
+//! });
+//!
+//! loop {
+//! // Your main loop.
+//! }
+//! }
+//!
+//! #[exception]
+//! fn SysTick() {
+//! unsafe { SYSTICK_HANDLER.call() };
+//! }
+//! ```
+
+pub struct Handler<'a> {
+ // Handler that will be executed on `call`.
+ h: *const dyn FnMut(),
+ lifetime: core::marker::PhantomData<&'a dyn FnMut()>,
+}
+
+impl<'a> Handler<'a> {
+ /// Returns a new Handler that initially does nothing when
+ /// called. Override its behavior by using `replace`.
+ pub const fn new() -> Self {
+ Self {
+ h: &Self::default_handler,
+ lifetime: core::marker::PhantomData,
+ }
+ }
+
+ /// Replace the behavior of this handler with `f`.
+ ///
+ /// # Safety
+ ///
+ /// There is no exclusion on replacing the handler's behavior
+ /// while it is being executed. It is your responsibility to make
+ /// sure that it's not being executed when you call `replace`.
+ pub unsafe fn replace(&mut self, f: &(dyn FnMut() + Send + 'a)) {
+ self.h = core::mem::transmute::<_, &'a _>(f);
+ }
+
+ /// Execute this handler.
+ ///
+ /// # Safety
+ ///
+ /// This function assumes that a replace is not occurring when the
+ /// closure is being looked up. You need to ensure that `replace`
+ /// and `call` can not occur at the same time.
+ pub unsafe fn call(&self) {
+ let f: &mut dyn FnMut() = &mut *(self.h as *mut dyn FnMut());
+ f();
+ }
+
+ /// Do nothing handler. Needed by `call` until `replace` is used
+ /// to set specific behavior. Can also be used to replace a
+ /// closure that is about to go out of scope.
+ pub fn default_handler() {}
+}
+
+impl<'a> core::fmt::Debug for Handler<'a> {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ let (f0, f1) = unsafe { core::mem::transmute::<_, (usize, usize)>(self.h) };
+ write!(f, "Handler{{ h: (0x{:x}, 0x{:x}) }}", f0, f1)
+ }
+}
+
+// FIXME: This probably shouldn't be Copy/Clone, but it needs to be in
+// order for array initialization to work with [Handler::new(); 32].
+impl<'a> core::marker::Copy for Handler<'a> {}
+impl<'a> core::clone::Clone for Handler<'a> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn replace() {
+ static mut X: usize = 0;
+
+ let mut handler = Handler::new();
+ unsafe {
+ handler.replace(&|| X += 1);
+ assert_eq!(X, 0);
+ handler.call();
+ handler.call();
+ assert_eq!(X, 2);
+ }
+ }
+
+ #[test]
+ fn replace_static() {
+ static mut HANDLER: Handler = Handler::new();
+ static mut X: usize = 0;
+
+ unsafe {
+ HANDLER.replace(&|| X += 1);
+ assert_eq!(X, 0);
+ HANDLER.call();
+ HANDLER.call();
+ assert_eq!(X, 2);
+ }
+ }
+
+ #[test]
+ fn replace_with_default() {
+ let mut handler = Handler::new();
+ unsafe {
+ handler.replace(&Handler::default_handler);
+ handler.call()
+ }
+ }
+}