ruspiro_lock/sync/mutex.rs
1/***********************************************************************************************************************
2 * Copyright (c) 2020 by the authors
3 *
4 * Author: André Borrmann <pspwizard@gmx.de>
5 * License: Apache License 2.0 / MIT
6 **********************************************************************************************************************/
7
8//! # Mutex
9//!
10//! Enable exclusive access to data guarded by a cross core atomic lock. In contrast to a ``Singleton``
11//! the data access lock could also be non-blocking and might fail. But exclusive access is guaranteed
12//! across cores if the lock could be aquired.
13//!
14//! # Example
15//! ```
16//! use ruspiro_lock::sync::Mutex;
17//!
18//! static DATA: Mutex<u32> = Mutex::new(0);
19//!
20//! fn main() {
21//! if let Some(mut data) = DATA.try_lock() {
22//! *data = 20;
23//! }
24//! // once the data goes ot of scope the lock will be released
25//! if let Some(data) = DATA.try_lock() {
26//! println!("data: {}", *data);
27//!
28//! // another lock should fail inside this scope
29//! assert!(DATA.try_lock().is_none());
30//! }
31//! }
32//! ```
33//! This example uses a ``static`` variable to define a lock that shall be available across cores.
34//! The data might also be wrapped in an ``Arc<Mutex<T>>`` and shared between cores using clones
35//! of the ``Arc``.
36//!
37
38use core::arch::asm;
39use core::cell::UnsafeCell;
40use core::fmt;
41use core::ops::{Deref, DerefMut};
42use core::sync::atomic::{AtomicBool, Ordering};
43
44/// An mutual exclusive access lock for the interior data
45#[repr(C, align(16))]
46pub struct Mutex<T: ?Sized> {
47 locked: AtomicBool,
48 data: UnsafeCell<T>,
49}
50
51/// The MutexGuard is the result of successfully aquiring the mutual exclusive lock for the interior
52/// data. If this guard goes ot of scope the lock will be released
53pub struct MutexGuard<'a, T: ?Sized + 'a> {
54 _data: &'a Mutex<T>,
55}
56
57impl<T> Mutex<T> {
58 /// Create a new data access guarding lock
59 pub const fn new(value: T) -> Self {
60 Mutex {
61 locked: AtomicBool::new(false),
62 data: UnsafeCell::new(value),
63 }
64 }
65}
66
67impl<T: ?Sized> Mutex<T> {
68 /// Try to lock the interior data for mutual exclusive access. Returns ``None`` if the lock failes
69 /// or ``Some(MutexGuard)``. The actual data, the MutexGuard wraps could be conviniently accessed by
70 /// dereferencing it.
71 ///
72 /// # Example
73 /// ```
74 /// # use ruspiro_lock::sync::Mutex;
75 /// static DATA: Mutex<u32> = Mutex::new(10);
76 /// # fn main() {
77 /// if let Some(data) = DATA.try_lock() {
78 /// // do something with data
79 /// }
80 /// # }
81 /// ```
82 pub fn try_lock(&self) -> Option<MutexGuard<T>> {
83 // do the atomic operation to set the lock
84 if !self.locked.swap(true, Ordering::Acquire) {
85 // has been false previously means we now have the lock
86
87 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
88 unsafe {
89 // dmb required before allow access to the protected resource, see:
90 // http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf
91 asm!("dmb sy");
92 }
93
94 Some(MutexGuard { _data: self })
95 } else {
96 // we couldn't set the lock
97 None
98 }
99 }
100
101 /// Lock the guarded data for mutual exclusive access. This blocks until the data could be
102 /// successfully locked. The locked data will be returned as ``MutexGuard``. Simply dereferencing
103 /// this allows access to the contained data value.
104 ///
105 /// # Example
106 /// ```
107 /// # use ruspiro_lock::sync::Mutex;
108 /// static DATA: Mutex<u32> = Mutex::new(10);
109 /// # fn main() {
110 /// let mut data = DATA.lock();
111 /// // do something with data
112 /// *data = 15;
113 ///
114 /// # }
115 /// ```
116 pub fn lock(&self) -> MutexGuard<T> {
117 loop {
118 if let Some(data) = self.try_lock() {
119 return data;
120 }
121 // to save energy and cpu consumption we can wait for an event beeing raised that indicates that the
122 // mutex lock have liekly been released
123 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
124 unsafe {
125 asm!("wfe");
126 }
127 }
128 }
129
130 /// Consume the Mutex and return the inner value
131 pub fn into_inner(self) -> T
132 where
133 T: Sized,
134 {
135 self.data.into_inner()
136 }
137}
138
139impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 let mut dbg = f.debug_struct("Mutex");
142 match self.try_lock() {
143 Some(guard) => {
144 dbg.field("Value", &&*guard);
145 }
146 _ => {
147 dbg.field("Value", &"unable to lock");
148 }
149 }
150 dbg.finish_non_exhaustive()
151 }
152}
153
154// when the MutexGuard is dropped release the owning lock
155impl<T: ?Sized> Drop for MutexGuard<'_, T> {
156 fn drop(&mut self) {
157 self._data.locked.swap(false, Ordering::Release);
158
159 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
160 unsafe {
161 // dmb required before allow access to the protected resource, see:
162 // http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf
163 asm!("dmb sy");
164 // also raise a signal to indicate the mutex has been changed (this trigger all WFE's to continue
165 // processing) but do data syncronisation barrier upfront to ensure any data updates has been finished
166 asm!(
167 "dsb sy
168 sev"
169 );
170 }
171 }
172}
173
174// Dereferencing the value contained in the [MutexGuard]
175// this ok as the MutexGuard does only exist if the exclusive access to the data could
176// be ensured. Therefore also only one `MutexGuard` could ever exist for one specific ``Mutex``, which makes it
177// safe to return immutable and mutable references.
178impl<T: ?Sized> Deref for MutexGuard<'_, T> {
179 type Target = T;
180
181 fn deref(&self) -> &T {
182 unsafe { &*self._data.data.get() }
183 }
184}
185
186impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
187 fn deref_mut(&mut self) -> &mut T {
188 unsafe { &mut *self._data.data.get() }
189 }
190}
191
192/// implement debug trait to forward to the type wrapped within the guard
193impl<T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'_, T> {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 fmt::Debug::fmt(&**self, f)
196 }
197}
198
199/// The Mutex is always `Sync`, to make it `Send` as well it need to be wrapped into an `Arc`.
200unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}