rustraspberry-piarmbare-metalspinlock

Bare metal spinlock implementation in rust


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  /

Solution

  • 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).