Skip to content

Add an atomic-check mutex for RTIC #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ overview of what's already available:
| --- | --- | --- | --- |
| `std::sync::Mutex` | [`BusManagerStd`] | [`new_std!()`] | `std` |
| `cortex_m::interrupt::Mutex` | [`BusManagerCortexM`] | [`new_cortexm!()`] | `cortex-m` |
| NA | [`BusManagerAtomicCheck`] | [`new_atomic_check!()`] | `cortex-m` |

# Supported Busses
Currently, the following busses can be shared with _shared-bus_:
Expand All @@ -82,11 +83,13 @@ Currently, the following busses can be shared with _shared-bus_:
[`.acquire_spi()`]: https://docs.rs/shared-bus/latest/shared_bus/struct.BusManager.html#method.acquire_spi
[`BusManagerCortexM`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerCortexM.html
[`BusManagerSimple`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerSimple.html
[`BusManagerAtomicCheck`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerAtomicCheck.html
[`BusManagerStd`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerStd.html
[`BusMutex`]: https://docs.rs/shared-bus/latest/shared_bus/trait.BusMutex.html
[`I2cProxy`]: https://docs.rs/shared-bus/latest/shared_bus/struct.I2cProxy.html
[`SpiProxy`]: https://docs.rs/shared-bus/latest/shared_bus/struct.SpiProxy.html
[`new_cortexm!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_cortexm.html
[`new_atomic_check!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_atomic_check.html
[`new_std!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_std.html
[blog-post]: https://blog.rahix.de/001-shared-bus

Expand Down
55 changes: 52 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
//! | --- | --- | --- | --- |
//! | `std::sync::Mutex` | [`BusManagerStd`] | [`new_std!()`] | `std` |
//! | `cortex_m::interrupt::Mutex` | [`BusManagerCortexM`] | [`new_cortexm!()`] | `cortex-m` |
//! | None (Automatically Managed) | [`BusManagerAtomicCheck`] | [`new_atomic_check!()`] | `cortex-m` |
//!
//! # Supported Busses
//! Currently, the following busses can be shared with _shared-bus_:
Expand All @@ -98,23 +99,24 @@
//! [`.acquire_i2c()`]: ./struct.BusManager.html#method.acquire_i2c
//! [`.acquire_spi()`]: ./struct.BusManager.html#method.acquire_spi
//! [`BusManagerCortexM`]: ./type.BusManagerCortexM.html
//! [`BusManagerAtomicCheck`]: ./type.BusManagerAtomicCheck.html
//! [`BusManagerSimple`]: ./type.BusManagerSimple.html
//! [`BusManagerStd`]: ./type.BusManagerStd.html
//! [`BusMutex`]: ./trait.BusMutex.html
//! [`I2cProxy`]: ./struct.I2cProxy.html
//! [`SpiProxy`]: ./struct.SpiProxy.html
//! [`new_cortexm!()`]: ./macro.new_cortexm.html
//! [`new_std!()`]: ./macro.new_std.html
//! [`new_atomic_check!()`]: ./macro.new_atomic_check.html
//! [blog-post]: https://blog.rahix.de/001-shared-bus
#![doc(html_root_url = "https://docs.rs/shared-bus")]
#![cfg_attr(not(feature = "std"), no_std)]

#![warn(missing_docs)]

mod macros;
mod manager;
mod mutex;
mod proxies;
mod macros;

#[doc(hidden)]
#[cfg(feature = "std")]
Expand All @@ -126,12 +128,15 @@ pub use cortex_m;

pub use manager::BusManager;
pub use mutex::BusMutex;
pub use mutex::NullMutex;
#[cfg(feature = "cortex-m")]
pub use mutex::CortexMMutex;
pub use mutex::NullMutex;
pub use proxies::I2cProxy;
pub use proxies::SpiProxy;

#[cfg(feature = "cortex-m")]
pub use mutex::AtomicCheckMutex;

/// A bus manager for sharing within a single task/thread.
///
/// This is the bus manager with the least overhead; it should always be used when all bus users
Expand Down Expand Up @@ -189,3 +194,47 @@ pub type BusManagerStd<BUS> = BusManager<::std::sync::Mutex<BUS>>;
/// This type is only available with the `cortex-m` feature.
#[cfg(feature = "cortex-m")]
pub type BusManagerCortexM<BUS> = BusManager<CortexMMutex<BUS>>;

/// A bus manager for safely sharing the bus when using concurrency frameworks (such as RTIC).
///
/// This manager relies on RTIC or some other concurrency framework to manage resource
/// contention automatically. As a redundancy, this manager uses an atomic boolean to check
/// whether or not a resource is currently in use. This is purely used as a fail-safe against
/// misuse.
///
/// ## Warning
/// If devices on the same shared bus are not treated as a singular resource, it is possible that
/// pre-emption may occur. In this case, the manger will panic to prevent the race condition.
///
/// ## Usage
/// In order to use this manager with a concurrency framework such as RTIC, all devices on the
/// shared bus must be stored in the same logic resource. The concurrency framework will require a
/// resource lock if pre-emption is possible.
///
/// In order to use this with RTIC (as an example), all devices on the shared bus must be stored in
/// a singular resource:
/// ```rust
/// struct Device<T> { _bus: T };
/// struct OtherDevice<T> { _bus: T };
///
/// type I2C = ();
/// type Proxy = shared_bus::I2cProxy<'static, shared_bus::AtomicCheckMutex<I2C>>;
///
/// struct SharedBusResources {
/// device: Device<Proxy>,
/// other_device: OtherDevice<Proxy>,
/// }
///
/// struct Resources {
/// shared_bus_resources: SharedBusResources,
/// }
/// ```
///
/// Usually, for sharing / between tasks, a manager with `'static` lifetime is needed which can be
/// created using the [`shared_bus::new_atomic_check!()`][new_atomic_check] macro.
///
/// [new_atomic_check]: ./macro.new_atomic_check.html
///
/// This type is only available with the `cortex-m` feature.
#[cfg(feature = "cortex-m")]
pub type BusManagerAtomicCheck<T> = BusManager<AtomicCheckMutex<T>>;
14 changes: 14 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,17 @@ macro_rules! new_cortexm {
m
}};
}

/// Construct a statically allocated bus manager.
#[cfg(feature = "cortex-m")]
#[macro_export]
macro_rules! new_atomic_check {
($bus_type:ty = $bus:expr) => {{
let m: Option<&'static mut _> = $crate::cortex_m::singleton!(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is the only reason that the AtomicCheckMutex is gated by cortex-m. Is there another singleton type that we could use here instead?

: $crate::BusManagerAtomicCheck<$bus_type> =
$crate::BusManagerAtomicCheck::new($bus)
);

m
}};
}
54 changes: 54 additions & 0 deletions src/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,57 @@ mod tests {
});
}
}

/// A simple coherency checker for sharing across multiple tasks/threads.
///
/// This mutex type can be used when all bus users are contained in a single structure, and is
/// intended for use with RTIC. When all bus users are contained within a structure managed by RTIC,
/// RTIC will guarantee that bus collisions do not occur. To protect against accidentaly misuse,
/// this mutex uses an atomic bool to determine when the bus is in use. If a bus collision is
/// detected, the code will panic.
///
/// This mutex type is used with the [`BusManagerAtomicMutex`] type.
///
/// This manager type is explicitly safe to share across threads because it checks to ensure that
/// collisions due to bus sharing do not occur.
///
/// [`BusManagerAtomicMutex`]: ./type.BusManagerAtomicMutex.html
#[cfg(feature = "cortex-m")]
#[derive(Debug)]
pub struct AtomicCheckMutex<BUS> {
bus: core::cell::UnsafeCell<BUS>,
busy: core::sync::atomic::AtomicBool,
}

// It is explicitly safe to share this across threads because there is a coherency check using an
// atomic bool comparison.
#[cfg(feature = "cortex-m")]
unsafe impl<BUS> Sync for AtomicCheckMutex<BUS> {}

#[cfg(feature = "cortex-m")]
impl<BUS> BusMutex for AtomicCheckMutex<BUS> {
type Bus = BUS;

fn create(v: BUS) -> Self {
Self {
bus: core::cell::UnsafeCell::new(v),
busy: core::sync::atomic::AtomicBool::from(false),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this currently won't work for Armv6. We can add another feature armv6 to add a shim layer for atomic operations. An example of this is in https://github.com/ryan-summers/shared-bus-rtic/blob/master/src/lib.rs#L153-L175 and was originally taken from bbqueue.

}
}

fn lock<R, F: FnOnce(&mut Self::Bus) -> R>(&self, f: F) -> R {
self.busy
.compare_exchange(
false,
true,
core::sync::atomic::Ordering::SeqCst,
core::sync::atomic::Ordering::SeqCst,
)
.expect("Bus conflict");
let result = f(unsafe { &mut *self.bus.get() });

self.busy.store(false, core::sync::atomic::Ordering::SeqCst);

result
}
}