From 2085ee663411649193fa33fd276ac79f72965a65 Mon Sep 17 00:00:00 2001 From: Rahix Date: Wed, 1 May 2019 21:27:35 +0200 Subject: [PATCH] WIP: Add mutex traits Signed-off-by: Rahix --- Cargo.toml | 1 + src/lib.rs | 1 + src/mutex.rs | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 src/mutex.rs diff --git a/Cargo.toml b/Cargo.toml index 341fe2e5d..ad7d17ad7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ version = "0.1.1" [dev-dependencies] stm32f30x = "0.6.0" +cortex-m = "0.6.0" futures = "0.1.17" [features] diff --git a/src/lib.rs b/src/lib.rs index 1a903081a..6ce3a1bcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -694,6 +694,7 @@ pub mod adc; pub mod blocking; pub mod digital; pub mod fmt; +pub mod mutex; pub mod prelude; pub mod serial; pub mod spi; diff --git a/src/mutex.rs b/src/mutex.rs new file mode 100644 index 000000000..9790aac94 --- /dev/null +++ b/src/mutex.rs @@ -0,0 +1,234 @@ +//! Generic mutex traits +//! +//! The traits in this module allow code to be generic over the mutex type used. +//! The types implementing these traits must guarantee that access is always +//! exclusive, even for a `RoMutex`. +//! +//! ## Example Implementation +//! +//! ### Std +//! The following code snippet is a possible implementation of the mutex traits +//! for `std`'s `Mutex`: +//! ``` +//! # use embedded_hal::mutex; +//! pub struct StdMutex(std::sync::Mutex); +//! +//! impl mutex::Mutex for StdMutex { +//! type CreationError = void::Void; +//! +//! fn create(v: T) -> Result { +//! Ok(StdMutex(std::sync::Mutex::new(v))) +//! } +//! } +//! +//! impl mutex::RwMutex for StdMutex { +//! type Error = (); +//! +//! fn lock_mut(&self, f: impl FnOnce(&mut T) -> R) -> Result { +//! // Erase the error type in this example for simplicity +//! let mut v = self.0.try_lock().or(Err(()))?; +//! Ok(f(&mut v)) +//! } +//! } +//! +//! // RoMutex is implemented automatically by adding the following: +//! impl mutex::default::DefaultRo for StdMutex { } +//! +//! # fn main() { +//! // Make use of the mutex: +//! use embedded_hal::mutex::{Mutex, RoMutex, RwMutex}; +//! +//! let m = StdMutex::create(123).unwrap(); +//! m.lock_mut(|v| { +//! assert_eq!(*v, 123); +//! *v = 321; +//! }).unwrap(); +//! m.lock(|v| +//! assert_eq!(*v, 321) +//! ).unwrap(); +//! # } +//! ``` +//! +//! ### `cortex-m` +//! `cortex-m` uses the `bare-metal` mutex type, an implementation might look +//! like this: +//! ``` +//! # use embedded_hal::mutex; +//! pub struct CortexMMutex(cortex_m::interrupt::Mutex); +//! +//! impl mutex::Mutex for CortexMMutex { +//! type CreationError = void::Void; +//! +//! fn create(v: T) -> Result { +//! Ok(CortexMMutex(cortex_m::interrupt::Mutex::new(v))) +//! } +//! } +//! +//! impl mutex::RoMutex for CortexMMutex { +//! type Error = void::Void; +//! +//! fn lock(&self, f: impl FnOnce(&T) -> R) -> Result { +//! Ok(cortex_m::interrupt::free(|cs| { +//! let v = self.0.borrow(cs); +//! f(v) +//! })) +//! } +//! } +//! +//! // Implement RwMutex for CortexMMutex> automatically: +//! impl mutex::default::RefCellRw for CortexMMutex { } +//! +//! // Add a type alias for convenience +//! type CortexMMutexRw = CortexMMutex>; +//! # +//! # // Check that implementations actually exist +//! # fn is_mu>() { } +//! # fn is_ro>() { } +//! # fn is_rw>() { } +//! # +//! # is_mu::<(), CortexMMutex<()>>(); +//! # is_ro::<(), CortexMMutex<()>>(); +//! # is_mu::<(), CortexMMutexRw<()>>(); +//! # is_rw::<(), CortexMMutexRw<()>>(); +//! ``` + +/// A generic mutex abstraction. +/// +/// This trait by itself is not that useful, `RoMutex` and `RwMutex` have this +/// as their common requirement. See the module root for more info. +#[cfg(feature = "unproven")] +pub trait Mutex: Sized { + /// Creation Error + type CreationError; + + /// Create a new mutex of this type. + fn create(v: T) -> Result; +} + +/// A read-only (immutable) mutex. +/// +/// This means, the value it shares is immutable, but only a single context may +/// have exclusive access. +/// +/// `RwMutex`es can implement this trait automatically using +/// ``` +/// # use embedded_hal::mutex; +/// # struct MyMutex(T); +/// impl mutex::default::DefaultRo for MyMutex { } +/// ``` +#[cfg(feature = "unproven")] +pub trait RoMutex: Mutex { + /// Locking error + type Error; + + /// Lock the mutex for the duration of a closure + /// + /// `lock` will call a closure with an immutable reference to the unlocked + /// mutex's value. + fn lock(&self, f: impl FnOnce(&T) -> R) -> Result; +} + +/// A read-write (mutable) mutex. +/// +/// This mutex type is similar to the Mutex from `std`. When you lock it, you +/// get access to a mutable reference. +/// +/// This trait can automatically be implemented for `RoMutex>` by using +/// ``` +/// # use embedded_hal::mutex; +/// # struct MyMutex(T); +/// impl mutex::default::RefCellRw for MyMutex { } +/// ``` +#[cfg(feature = "unproven")] +pub trait RwMutex: Mutex { + /// Locking error + type Error; + + /// Lock the mutex for the duration of a closure + /// + /// `lock_mut` will call a closure with a mutable reference to the unlocked + /// mutex's value. + fn lock_mut(&self, f: impl FnOnce(&mut T) -> R) -> Result; +} + +/// Blanket implementations for `RoMutex` and `RwMutex` +/// +/// Any `RwMutex` can trivially implement `RoMutex` as well. To enable this, +/// add a line like +/// ``` +/// # use embedded_hal::mutex; +/// # struct MyMutex(T); +/// impl mutex::default::DefaultRo for MyMutex { } +/// ``` +/// to your mutex definition. +/// +/// Similarly, a `RoMutex` and a `RefCell` can be used to implement `RwMutex`. +/// The blanket implementation can be enabled using +/// ``` +/// # use embedded_hal::mutex; +/// # struct MyMutex(T); +/// impl mutex::default::RefCellRw for MyMutex { } +/// ``` +#[cfg(feature = "unproven")] +pub mod default { + use super::*; + use core::cell::RefCell; + + /// Marker trait to enable the default `RoMutex` implementation. + /// + /// Your mutex type must implement `RwMutex` for this to have an effect! + pub trait DefaultRo {} + + // Blanket impl: + // Every read-write mutex is also read-only. Don't confuse this with an + // RwLock where multiple reads are allowed simultaneously! + impl RoMutex for M + where + M: DefaultRo + RwMutex, + { + type Error = >::Error; + + fn lock(&self, f: impl FnOnce(&T) -> R) -> Result { + self.lock_mut(|v| f(v)) + } + } + + /// Marker trait to enable an implementation of `RwMutex` using `RefCell`s + /// + /// Your mutex type must implement `RoMutex` for this to have an effect! + pub trait RefCellRw {} + + // Blanket impl: + // You can use a RefCell to make an RoMutex read-write. This means, the + // bare-metal mutex type (which is read-only) can easily be used for + // creating a read-write mutex! + // + // This is the wrapper for creation of such a mutex. + impl Mutex for M + where + M: RefCellRw + RoMutex>, + { + type CreationError = >>::CreationError; + + fn create(v: T) -> Result { + >>::create(RefCell::new(v)) + } + } + + // Blanket impl: + // You can use a RefCell to make an RoMutex read-write. This means, the + // bare-metal mutex type (which is read-only) can easily be used for + // creating a read-write mutex! + // + // This is the actual RwMutex implementation. + impl RwMutex for M + where + M: RefCellRw + RoMutex> + Mutex, + { + type Error = >>::Error; + + fn lock_mut(&self, f: impl FnOnce(&mut T) -> R) -> Result { + self.lock(|v| f(&mut v.borrow_mut())) + } + } +}