|
| 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 | +} |
0 commit comments