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> {}