From 2a72a4e00fb4f756ea33e197c42b7b8c4ef24e43 Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Fri, 26 Jan 2024 15:31:31 +0000 Subject: [PATCH] Add types which help you move data into an interrupt. --- Cargo.toml | 7 +++ src/irq_sharing.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 113 insertions(+) create mode 100644 src/irq_sharing.rs diff --git a/Cargo.toml b/Cargo.toml index 1917449..055dc04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,13 @@ documentation = "https://docs.rs/grounded/" version = "1.3" default-features = false +[dependencies.critical-section] +version = "1.1" + +[dev-dependencies.critical-section] +version = "1.1" +features = ["std"] + [features] default = [] # components that require compare-and-swap operations diff --git a/src/irq_sharing.rs b/src/irq_sharing.rs new file mode 100644 index 0000000..b5ac65e --- /dev/null +++ b/src/irq_sharing.rs @@ -0,0 +1,105 @@ +//! Types for handling data that needs to be moved into an interrupt handler or +//! thread. +//! +//! Written by Ferrous Systems. +//! +//! They are designed to work with the +//! [`cortex-m`](https://crates.io/crates/cortex-m) `#[interrupt]` macro. +//! +//! ```rust, ignore +//! use grounded::irq_sharing::{Global, Local}; +//! static GLOBAL_UART0: Global = Global::empty(); +//! +//! #[entry] +//! fn main() -> { +//! let p = hal::init(); +//! GLOBAL_UART0.load(p.uart); +//! p.enable_uart_interrupt(); +//! loop { +//! // Do your main thread stuff +//! } +//! } +//! +//! #[interrupt] +//! fn UART0 { +//! // This is re-written to be safe by the #[interrupt] attribute +//! static mut UART0: Local = Local::empty(); +//! +//! let uart0 = UART0.get_or_init(&GLOBAL_UART0); +//! +//! // can use uart0 here safely, knowing no other code has access +//! // to this object. +//! } +//! ``` + +/// The global type for sharing things with an interrupt handler +pub struct Global { + inner: critical_section::Mutex>>, +} + +impl Global { + /// Create a new, empty, object + pub const fn empty() -> Global { + Global { + inner: critical_section::Mutex::new(core::cell::RefCell::new(None)), + } + } + + /// Load a value into the global + /// + /// Returns the old value, if any + pub fn load(&self, value: T) -> Option { + critical_section::with(|cs| self.inner.borrow(cs).replace(Some(value))) + } +} + +/// The local type for sharing things with an interrupt handler +pub struct Interrupt { + inner: Option, +} + +impl Interrupt { + /// Create a new, empty, object + pub const fn empty() -> Interrupt { + Interrupt { inner: None } + } + + /// Grab a mutable reference to the contents. + /// + /// If the value is empty, the contents are taken from a mutex-locked global + /// variable. That global must have been initialised before calling this + /// function. If not, this function panics. + pub fn get_or_init_with(&mut self, global: &Global) -> &mut T { + let result = self.inner.get_or_insert_with(|| { + critical_section::with(|cs| global.inner.borrow(cs).replace(None).unwrap()) + }); + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn send_not_sync() { + // Is Send but !Sync + struct Racy { + inner: core::cell::Cell, + } + + // Initialisation + static GLOBAL_TEST: Global = Global::empty(); + let racy = Racy { + inner: core::cell::Cell::new(0), + }; + GLOBAL_TEST.load(racy); + // Usage - we don't have the static mut re-write here so use a stack + // variable + let mut local: Interrupt = Interrupt::empty(); + let local_ref = local.get_or_init_with(&GLOBAL_TEST); + assert_eq!(local_ref.inner.get(), 0); + } +} + +// End of file diff --git a/src/lib.rs b/src/lib.rs index 1ced464..606282f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ #![doc = include_str!("../README.md")] pub mod const_init; +pub mod irq_sharing; pub mod uninit; #[cfg(feature = "cas")]