aarch64_std/sync/mutex.rs
1use super::poison::*;
2use core::{
3 arch::asm,
4 cell::UnsafeCell,
5 marker::PhantomData,
6 ops::{Deref, DerefMut},
7};
8
9/// A mutual exclusion primitive useful for protecting shared data
10///
11/// This mutex will block threads waiting for the lock to become available. The
12/// mutex can be created via a [`new`] constructor. Each mutex has a type parameter
13/// which represents the data that it is protecting. The data can only be accessed
14/// through the RAII guards returned from [`lock`] and [`try_lock`], which
15/// guarantees that the data is only ever accessed when the mutex is locked.
16///
17/// # Poisoning
18///
19/// Standard library mutexes can become poisoned when the holder panics. However, this crate's
20/// mutexes do not become poisoned as there's currently no reliable way to detect panics. Poisoning
21/// may be added in the future if it becomes possible.
22#[derive(Debug, Default)]
23pub struct Mutex<T: ?Sized> {
24 is_locked: u32,
25 inner: UnsafeCell<T>,
26}
27
28impl<T> Mutex<T> {
29 /// Creates a new mutex in an unlocked state ready for use.
30 pub const fn new(t: T) -> Self {
31 Mutex {
32 is_locked: 0,
33 inner: UnsafeCell::new(t),
34 }
35 }
36}
37
38unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
39unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
40
41impl<T: ?Sized> Mutex<T> {
42 /// Acquires a mutex, blocking the current thread until it is able to do so.
43 ///
44 /// This function will block the local thread until it is available to acquire
45 /// the mutex. Upon returning, the thread is the only thread with the lock
46 /// held. An RAII guard is returned to allow scoped unlock of the lock. When
47 /// the guard goes out of scope, the mutex will be unlocked.
48 ///
49 /// The exact behavior on locking a mutex in the thread which already holds
50 /// the lock is left unspecified. However, this function will not return on
51 /// the second call (it might panic or deadlock, for example).
52 ///
53 /// # Errors
54 ///
55 /// Currently this function cannot fail. The standard library's Mutex may fail if there is a
56 /// panic while the lock is held, but without the standard library we currently have no good
57 /// way to detect panics. Poisoning may be added at a later time.
58 ///
59 /// # Panics
60 ///
61 /// This function might panic when called if the lock is already held by
62 /// the current thread.
63 pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
64 self.lock_impl(true)
65 }
66
67 pub(crate) fn lock_impl(&self, _yield_on_fail: bool) -> LockResult<MutexGuard<'_, T>> {
68 aarch64_cpu::asm::sevl();
69 loop {
70 aarch64_cpu::asm::wfe();
71 match self.try_lock() {
72 Ok(g) => return Ok(g),
73 Err(TryLockError::WouldBlock) => {
74 #[cfg(feature = "alloc")]
75 if _yield_on_fail {
76 crate::thread::yield_now();
77 }
78 continue;
79 }
80 }
81 }
82 }
83
84 /// Attempts to acquire this lock.
85 ///
86 /// If the lock could not be acquired at this time, then [`Err`] is returned.
87 /// Otherwise, an RAII guard is returned. The lock will be unlocked when the
88 /// guard is dropped.
89 ///
90 /// This function does not block.
91 ///
92 /// # Errors
93 ///
94 /// If the mutex could not be acquired because it is already locked, then
95 /// this call will return the [`WouldBlock`] error.
96 pub fn try_lock(&self) -> TryLockResult<MutexGuard<'_, T>> {
97 let mut result: u32;
98 unsafe {
99 asm!(
100 "ldaxr {result:w}, [{is_locked_addr}]",
101 "cmp {result:w}, 0",
102 "bne 1f",
103 "mov {tmp:w}, 1",
104 "stlxr {result:w}, {tmp:w}, [{is_locked_addr}]",
105 "1:",
106 is_locked_addr = in(reg) &self.is_locked as *const u32 as u64,
107 tmp = out(reg) _,
108 result = out(reg) result,
109 options(nostack),
110 );
111 }
112 if result == 0 {
113 Ok(MutexGuard {
114 lock: self,
115 _make_unsend: PhantomData,
116 })
117 } else {
118 Err(TryLockError::WouldBlock)
119 }
120 }
121
122 /// Consumes this mutex, returning the underlying data.
123 ///
124 /// # Errors
125 ///
126 /// If another user of this mutex panicked while holding the mutex, then
127 /// this call will return an error instead.
128 pub fn into_inner(self) -> LockResult<T>
129 where
130 T: Sized,
131 {
132 Ok(self.inner.into_inner())
133 }
134
135 /// Returns a mutable reference to the underlying data.
136 ///
137 /// Since this call borrows the `Mutex` mutably, no actual locking needs to
138 /// take place -- the mutable borrow statically guarantees no locks exist.
139 ///
140 /// # Errors
141 ///
142 /// If another user of this mutex panicked while holding the mutex, then
143 /// this call will return an error instead.
144 pub fn get_mut(&mut self) -> LockResult<&mut T> {
145 Ok(self.inner.get_mut())
146 }
147}
148
149/// An RAII implementation of a "scoped lock" of a mutex. When this structure is
150/// dropped (falls out of scope), the lock will be unlocked.
151///
152/// The data protected by the mutex can be accessed through this guard via its
153/// [`Deref`] and [`DerefMut`] implementations.
154///
155/// This structure is created by the [`lock`] and [`try_lock`] methods on
156/// [`Mutex`].
157///
158/// [`lock`]: Mutex::lock
159/// [`try_lock`]: Mutex::try_lock
160#[derive(Debug)]
161pub struct MutexGuard<'a, T: ?Sized + 'a> {
162 lock: &'a Mutex<T>,
163 _make_unsend: PhantomData<*const u8>,
164}
165
166unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
167
168impl<T: ?Sized> Deref for MutexGuard<'_, T> {
169 type Target = T;
170
171 fn deref(&self) -> &T {
172 unsafe { &*self.lock.inner.get() }
173 }
174}
175
176impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
177 fn deref_mut(&mut self) -> &mut T {
178 unsafe { &mut *self.lock.inner.get() }
179 }
180}
181
182impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> {
183 fn drop(&mut self) {
184 // TODO: poison the lock if there's a way to find out if we're panicking
185 unsafe {
186 asm!(
187 "stlr {1:w}, [{0}]",
188 "sev",
189 in(reg) &self.lock.is_locked as *const u32 as *mut u32,
190 in(reg) 0u32,
191 options(nostack),
192 );
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn test_no_contention() {
203 let n = Mutex::new(1);
204
205 {
206 let mut guard = n.lock().unwrap();
207 assert!(n.try_lock().is_err());
208 *guard += 1;
209 assert_eq!(*guard, 2);
210 }
211
212 {
213 let mut guard = n.lock().unwrap();
214 assert!(n.try_lock().is_err());
215 *guard += 1;
216 assert_eq!(*guard, 3);
217 }
218
219 {
220 let mut guard = n.try_lock().unwrap();
221 *guard += 1;
222 assert_eq!(*guard, 4);
223 }
224 }
225}