I am working on bare metal programming in rust on a Raspberry Pi 3 running in 64 bit mode. I have implemented a spinlock as follows:
use core::{sync::atomic::{AtomicBool, Ordering}, cell::UnsafeCell, ops::{Deref, DerefMut}};
pub struct SpinMutex<T> {
lock: AtomicBool,
data: UnsafeCell<T>
}
impl<T> SpinMutex<T> {
#[allow(dead_code)]
pub const fn new(data: T) -> Self {
Self {
lock: AtomicBool::new(false),
data: UnsafeCell::new(data)
}
}
pub fn lock(&self) -> SpinMutexGuard<T> {
while self.lock.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {}
SpinMutexGuard {
lock: &self.lock,
data: unsafe { &mut *self.data.get() }
}
}
}
unsafe impl<T> Sync for SpinMutex<T> {}
pub struct SpinMutexGuard<'a, T> {
lock: &'a AtomicBool,
data: &'a mut T
}
impl<'a, T> Deref for SpinMutexGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.data
}
}
impl<'a, T> DerefMut for SpinMutexGuard<'a, T> {
fn deref_mut(&mut self) -> &mut T {
self.data
}
}
impl<'a, T> Drop for SpinMutexGuard<'a, T> {
/// The dropping of the MutexGuard will release the lock it was created from.
fn drop(&mut self) {
self.lock.store(false, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use super::{SpinMutex};
#[test]
fn test_spin_mutex() {
let state = SpinMutex::new(0);
assert_eq!(*state.lock().data, 0);
*state.lock().data = 9;
assert_eq!(*state.lock().data, 9);
}
}
When I run the tests on my local machine (64 bit windows) the locks works. However, on the Raspberry Pi the lock
method gets stuck in an infinite loop and never returns. Is there a reason why this happens?
Here is how Rust compiles compare_exchange_weak
with inlining disabled:
80ba8: 9100400a add x10, x0, #0x10
80bac: 085f7d48 ldxrb w8, [x10]
80bb0: 34000068 cbz w8, 80bbc
80bb4: d5033f5f clrex
80bb8: 14000004 b 80bc8
80bbc: 52800029 mov w9, #0x1 // #1
80bc0: 080b7d49 stxrb w11, w9, [x10]
80bc4: 3400004b cbz w11, 80bcc
80bc8: 2a1f03e9 mov w9, wzr
80bcc: 7100011f cmp w8, #0x0
80bd0: 52000120 eor w0, w9, #0x1
80bd4: 1a9f07e1 cset w1, ne /
Here's a non-exhaustive list of conditions that I'm certain (I've tested them both ways) need to occur in order for atomics to work on RPi 4 aarch64 in EL1
(ARMv8).
This will probably be very similar to RPi 3 (ARMv7).
SCTLR_EL1
bit [0] set to 0b1
)SCTLR_EL1
bit [2] set to 0b1
)MAIR
(I've used 0xff
- I'm not sure which bits are redundant wrt. atomics, but I don't think there's much reason to use anything else for normal memory).