diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 01d26b8e9..f549446bc 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -42,6 +42,7 @@ pub mod rom_data; pub mod rtc; pub mod spi; mod spinlock; +pub mod spinlock_mutex; #[cfg(feature = "time-driver")] pub mod time_driver; #[cfg(feature = "_rp235x")] diff --git a/embassy-rp/src/spinlock_mutex.rs b/embassy-rp/src/spinlock_mutex.rs new file mode 100644 index 000000000..85174cf86 --- /dev/null +++ b/embassy-rp/src/spinlock_mutex.rs @@ -0,0 +1,93 @@ +//! Mutex implementation utilizing an hardware spinlock + +use core::marker::PhantomData; +use core::sync::atomic::Ordering; + +use embassy_sync::blocking_mutex::raw::RawMutex; + +use crate::spinlock::{Spinlock, SpinlockValid}; + +/// A mutex that allows borrowing data across executors and interrupts by utilizing an hardware spinlock +/// +/// # Safety +/// +/// This mutex is safe to share between different executors and interrupts. +pub struct SpinlockRawMutex { + _phantom: PhantomData<()>, +} +unsafe impl Send for SpinlockRawMutex {} +unsafe impl Sync for SpinlockRawMutex {} + +impl SpinlockRawMutex { + /// Create a new `SpinlockRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawMutex for SpinlockRawMutex +where + Spinlock: SpinlockValid, +{ + const INIT: Self = Self::new(); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + // Store the initial interrupt state in stack variable + let interrupts_active = cortex_m::register::primask::read().is_active(); + + // Spin until we get the lock + loop { + // Need to disable interrupts to ensure that we will not deadlock + // if an interrupt or higher prio locks the spinlock after we acquire the lock + cortex_m::interrupt::disable(); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + if let Some(lock) = Spinlock::::try_claim() { + // We just acquired the lock. + // 1. Forget it, so we don't immediately unlock + core::mem::forget(lock); + break; + } + // We didn't get the lock, enable interrupts if they were enabled before we started + if interrupts_active { + // safety: interrupts are only enabled, if they had been enabled before + unsafe { + cortex_m::interrupt::enable(); + } + } + } + + let retval = f(); + + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Release the spinlock to allow others to lock mutex again + // safety: this point is only reached a spinlock was acquired before + unsafe { + Spinlock::::release(); + } + + // Re-enable interrupts if they were enabled before the mutex was locked + if interrupts_active { + // safety: interrupts are only enabled, if they had been enabled before + unsafe { + cortex_m::interrupt::enable(); + } + } + + retval + } +} + +pub mod blocking_mutex { + //! Mutex implementation utilizing an hardware spinlock + use embassy_sync::blocking_mutex::Mutex; + + use crate::spinlock_mutex::SpinlockRawMutex; + /// A mutex that allows borrowing data across executors and interrupts by utilizing an hardware spinlock. + /// + /// # Safety + /// + /// This mutex is safe to share between different executors and interrupts. + pub type SpinlockMutex = Mutex, T>; +}