Skip to content

Commit cea439d

Browse files
committed
zephyr: sync: Implement SpinMutex
The SpinMutex uses a spinlock to protect a piece of data so that it can be shared safely (with safe Rust code) between different contexts. Signed-off-by: David Brown <[email protected]>
1 parent 46f7f3b commit cea439d

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

zephyr/src/sync.rs

+7
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,10 @@ pub use mutex::{
3737
LockResult,
3838
TryLockResult,
3939
};
40+
41+
mod spinmutex;
42+
43+
pub use spinmutex::{
44+
SpinMutex,
45+
SpinMutexGuard,
46+
};

zephyr/src/sync/spinmutex.rs

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//! Spinlock-based Mutexes
2+
//!
3+
//! The [`sync::Mutex`] type is quite handy in Rust for sharing data between multiple contexts.
4+
//! However, it is not possible to aquire a mutex from interrupt context.
5+
//!
6+
//! This module provides a [`SpinMutex`] which has some similarities to the above, but with some
7+
//! restrictions. In contrast, however, it is usable from interrupt context.
8+
//!
9+
//! It works by use a spinlock to protect the contents of the SpinMutex. This allows for use in
10+
//! user threads as well as from irq context, the spinlock even protecting the data on SMP machines.
11+
//!
12+
//! In contract to [`critical_section::Mutex`], this has an API much closer to [`sync::Mutex`] (and
13+
//! as such to [`std::sync::Mutex`]. In addition, it has slightly less overhead on Zephyr, and
14+
//! since the mutex isn't shared, might allow for slightly better use on SMP systems, when the other
15+
//! CPU(s) don't need access to the SyncMutex.
16+
//!
17+
//! Note that `SpinMutex` doesn't have anything comparable to `Condvar`. Generally, they can be
18+
//! used with a `Semaphore` to allow clients to be waken, but this usage is racey, and if not done
19+
//! carefully can result uses of the semaphore not waking.
20+
21+
use core::{cell::UnsafeCell, convert::Infallible, fmt, marker::PhantomData, ops::{Deref, DerefMut}};
22+
23+
use crate::raw;
24+
25+
/// Result from the lock call. We keep the result for consistency of the API, but these can never
26+
/// fail.
27+
pub type SpinLockResult<Guard> = core::result::Result<Guard, Infallible>;
28+
29+
/// Result from the `try_lock` call. There is only a single type of failure, indicating that this
30+
/// would block.
31+
pub type SpinTryLockResult<Guard> = core::result::Result<Guard, SpinTryLockError>;
32+
33+
/// The single error type that can be returned from `try_lock`.
34+
pub enum SpinTryLockError {
35+
/// The lock could not be acquired at this time because the operation would otherwise block.
36+
WouldBlock,
37+
}
38+
39+
/// A lower-level mutual exclusion primitive for protecting data.
40+
///
41+
/// This is modeled after [`sync::Mutex`] but instead of using `k_mutex` from Zephyr, it uses
42+
/// `k_spinlock`. It's main advantage is that it is usable from IRQ context. However, it also is
43+
/// uninterruptible, and prevents even IRQ handlers from running.
44+
pub struct SpinMutex<T: ?Sized> {
45+
inner: UnsafeCell<raw::k_spinlock>,
46+
data: UnsafeCell<T>,
47+
}
48+
49+
/// As the data is protected by spinlocks, with RAII ensuring the lock is always released, this
50+
/// satisfies Rust's requirements for Send and Sync. The dependency of both on "Send" of the data
51+
/// type is intentional, as it is the Mutex that is providing the Sync semantics. However, it only
52+
/// makes sense for types that are usable from multiple thread contexts.
53+
unsafe impl<T: ?Sized + Send> Send for SpinMutex<T> {}
54+
unsafe impl<T: ?Sized + Send> Sync for SpinMutex<T> {}
55+
56+
impl<T> fmt::Debug for SpinMutex<T> {
57+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58+
write!(f, "Mutex {:?}", self.inner)
59+
}
60+
}
61+
62+
/// An RAII implementation of a "scoped lock" of a SpinMutex. When this structure is dropped (falls
63+
/// out of scope), the lock will be unlocked.
64+
///
65+
/// The data protected by the SpinMutex can be accessed through this guard via it's [`Deref'] and
66+
/// [`DerefMut`] implementations.
67+
///
68+
/// This structure is created by the [`lock`] and [`try_lock`] methods on [`SpinMutex`].
69+
///
70+
/// [`lock`]: SpinMutex::lock
71+
/// [`try_lock`]: SpinMutex::try_lock
72+
///
73+
/// Borrowed largely from std's `MutexGuard`, but adapted to use spinlocks.
74+
pub struct SpinMutexGuard<'a, T: ?Sized + 'a> {
75+
lock: &'a SpinMutex<T>,
76+
key: raw::k_spinlock_key_t,
77+
// Mark as not Send.
78+
_nosend: PhantomData<UnsafeCell<()>>,
79+
}
80+
81+
// Negative trait bounds are unstable, see the _nosend field above.
82+
/// Mark as Sync if the contained data is sync.
83+
unsafe impl<T: ?Sized + Sync> Sync for SpinMutexGuard<'_, T> {}
84+
85+
impl<T> SpinMutex<T> {
86+
/// Construct a new wrapped Mutex.
87+
pub const fn new(t: T) -> SpinMutex<T> {
88+
SpinMutex {
89+
inner: UnsafeCell::new(unsafe { core::mem::zeroed() }),
90+
data: UnsafeCell::new(t),
91+
}
92+
}
93+
}
94+
95+
impl<T: ?Sized> SpinMutex<T> {
96+
/// Acquire a mutex, spinning as needed to aquire the controlling spinlock.
97+
///
98+
/// This function will spin the current thread until it is able to acquire the spinlock.
99+
/// Returns an RAII guard to allow scoped unlock of the lock. When the guard goes out of scope,
100+
/// the SpinMutex will be unlocked.
101+
pub fn lock(&self) -> SpinLockResult<SpinMutexGuard<'_, T>> {
102+
let key = unsafe { raw::k_spin_lock(self.inner.get()) };
103+
unsafe { Ok(SpinMutexGuard::new(self, key)) }
104+
}
105+
106+
/// Attempts to aquire this lock.
107+
///
108+
/// If the lock could not be aquired at this time, then [`Err`] is returned. Otherwise an RAII
109+
/// guard is returned. The lock will be unlocked when the guard is dropped.
110+
///
111+
/// This function does not block.
112+
pub fn try_lock(&self) -> SpinTryLockResult<SpinMutexGuard<'_, T>> {
113+
let mut key = raw::k_spinlock_key_t { key: 0 };
114+
match unsafe { raw::k_spin_trylock(self.inner.get(), &mut key) } {
115+
0 => {
116+
unsafe {
117+
Ok(SpinMutexGuard::new(self, key))
118+
}
119+
}
120+
_ => {
121+
Err(SpinTryLockError::WouldBlock)
122+
}
123+
}
124+
}
125+
}
126+
127+
impl<'mutex, T: ?Sized> SpinMutexGuard<'mutex, T> {
128+
unsafe fn new(lock: &'mutex SpinMutex<T>, key: raw::k_spinlock_key_t) -> SpinMutexGuard<'mutex, T> {
129+
SpinMutexGuard { lock, key, _nosend: PhantomData }
130+
}
131+
}
132+
133+
impl<T: ?Sized> Deref for SpinMutexGuard<'_, T> {
134+
type Target = T;
135+
136+
fn deref(&self) -> &T {
137+
unsafe {
138+
&*self.lock.data.get()
139+
}
140+
}
141+
}
142+
143+
impl<T: ?Sized> DerefMut for SpinMutexGuard<'_, T> {
144+
fn deref_mut(&mut self) -> &mut T {
145+
unsafe { &mut *self.lock.data.get() }
146+
}
147+
}
148+
149+
impl<T: ?Sized> Drop for SpinMutexGuard<'_, T> {
150+
#[inline]
151+
fn drop(&mut self) {
152+
unsafe {
153+
raw::k_spin_unlock(self.lock.inner.get(), self.key);
154+
}
155+
}
156+
}

zephyr/src/sys.rs

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ pub mod critical {
5353
//!
5454
//! Critical sections from Rust are handled with a single Zephyr spinlock. This doesn't allow
5555
//! any nesting, but neither does the `critical-section` crate.
56+
//!
57+
//! This provides the underlying critical section crate, which is useful for external crates
58+
//! that want this interface. However, it isn't a particularly hygienic interface to use. For
59+
//! something a bit nicer, please see [`sync::SpinMutex`].
5660
5761
use core::{ffi::c_int, ptr::addr_of_mut};
5862

0 commit comments

Comments
 (0)