Skip to main content

esp_sync/
lib.rs

1//! Syncronization primitives for ESP32 devices
2//!
3//! ## Feature Flags
4#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
5#![doc(html_logo_url = "https://docs.espressif.com/projects/rust/esp-rs-grey-bg.svg")]
6#![cfg_attr(xtensa, feature(asm_experimental_arch))]
7#![deny(missing_docs, rust_2018_idioms, rustdoc::all)]
8// Don't trip up on broken/private links when running semver-checks
9#![cfg_attr(
10    semver_checks,
11    allow(rustdoc::private_intra_doc_links, rustdoc::broken_intra_doc_links)
12)]
13#![no_std]
14
15// MUST be the first module
16mod fmt;
17
18use core::{cell::UnsafeCell, marker::PhantomData};
19
20pub mod raw;
21
22use raw::{RawLock, SingleCoreInterruptLock};
23
24/// Opaque token that can be used to release a lock.
25// The interpretation of this value depends on the lock type that created it,
26// but bit #31 is reserved for the reentry flag.
27//
28// Xtensa: PS has 15 useful bits. Bits 12..16 and 19..32 are unused, so we can
29// use bit #31 as our reentry flag.
30// We can assume the reserved bit is 0 otherwise rsil - wsr pairings would be
31// undefined behavior: Quoting the ISA summary, table 64:
32// Writing a non-zero value to these fields results in undefined processor
33// behavior.
34//
35// Risc-V: we either get the restore state from bit 3 of mstatus, or
36// we create the restore state from the current Priority, which is at most 31.
37#[derive(Clone, Copy, Debug)]
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39pub struct RestoreState(u32, PhantomData<*const ()>);
40
41impl RestoreState {
42    const REENTRY_FLAG: u32 = 1 << 31;
43
44    /// Creates a new RestoreState from a raw inner state.
45    ///
46    /// # Safety
47    ///
48    /// The `inner` value must be appropriate for the [RawMutex] implementation that creates it.
49    pub const unsafe fn new(inner: u32) -> Self {
50        Self(inner, PhantomData)
51    }
52
53    /// Returns an invalid RestoreState.
54    ///
55    /// Note that due to the safety contract of [`RawLock::enter`]/[`RawLock::exit`], you must not
56    /// pass a `RestoreState` obtained from this method to [`RawLock::exit`].
57    pub const fn invalid() -> Self {
58        Self(0, PhantomData)
59    }
60
61    #[inline]
62    fn mark_reentry(&mut self) {
63        self.0 |= Self::REENTRY_FLAG;
64    }
65
66    #[inline]
67    fn is_reentry(self) -> bool {
68        self.0 & Self::REENTRY_FLAG != 0
69    }
70
71    /// Returns the raw value used to create this RestoreState.
72    #[inline]
73    pub fn inner(self) -> u32 {
74        self.0
75    }
76}
77
78#[cfg(single_core)]
79mod single_core {
80    use core::cell::Cell;
81
82    #[repr(transparent)]
83    pub(super) struct LockedState {
84        locked: Cell<bool>,
85    }
86
87    impl LockedState {
88        pub const fn new() -> Self {
89            Self {
90                locked: Cell::new(false),
91            }
92        }
93
94        #[inline]
95        pub fn lock(&self, lock: &impl crate::RawLock) -> crate::RestoreState {
96            let mut tkn = unsafe { lock.enter() };
97            let was_locked = self.locked.replace(true);
98            if was_locked {
99                tkn.mark_reentry();
100            }
101            tkn
102        }
103
104        /// # Safety:
105        ///
106        /// This function must only be called if the lock was acquired by the
107        /// current thread.
108        #[inline]
109        pub unsafe fn unlock(&self) {
110            self.locked.set(false)
111        }
112    }
113}
114
115#[cfg(multi_core)]
116mod multi_core {
117    use core::sync::atomic::{AtomicUsize, Ordering};
118
119    // Safety: Ensure that when adding new chips `raw_core` doesn't return this
120    // value.
121    const UNUSED_THREAD_ID_VALUE: usize = 0x100;
122
123    #[inline]
124    fn thread_id() -> usize {
125        // This method must never return UNUSED_THREAD_ID_VALUE
126        cfg_select! {
127            all(multi_core, riscv) => riscv::register::mhartid::read(),
128            all(multi_core, xtensa) => (xtensa_lx::get_processor_id() & 0x2000) as usize,
129            _ => 0,
130        }
131    }
132
133    #[repr(transparent)]
134    pub(super) struct LockedState {
135        owner: AtomicUsize,
136    }
137
138    impl LockedState {
139        #[inline]
140        pub const fn new() -> Self {
141            Self {
142                owner: AtomicUsize::new(UNUSED_THREAD_ID_VALUE),
143            }
144        }
145
146        #[inline]
147        pub fn lock(&self, lock: &impl crate::RawLock) -> crate::RestoreState {
148            // We acquire the lock inside an interrupt-free context to prevent a subtle
149            // race condition:
150            // In case an interrupt handler tries to lock the same resource, it could win if
151            // the current thread is holding the lock but isn't yet in interrupt-free context.
152            // If we maintain non-reentrant semantics, this situation would panic.
153            // If we allow reentrancy, the interrupt handler would technically be a different
154            // context with the same `current_thread_id`, so it would be allowed to lock the
155            // resource in a theoretically incorrect way.
156            let try_lock = || {
157                let mut tkn = unsafe { lock.enter() };
158
159                let current_thread_id = thread_id();
160
161                let try_lock_result = self
162                    .owner
163                    .compare_exchange(
164                        UNUSED_THREAD_ID_VALUE,
165                        current_thread_id,
166                        Ordering::Acquire,
167                        Ordering::Relaxed,
168                    )
169                    .map(|_| ());
170
171                match try_lock_result {
172                    Ok(()) => Some(tkn),
173                    Err(owner) if owner == current_thread_id => {
174                        tkn.mark_reentry();
175                        Some(tkn)
176                    }
177                    Err(_) => {
178                        unsafe { lock.exit(tkn) };
179                        None
180                    }
181                }
182            };
183
184            loop {
185                if let Some(token) = try_lock() {
186                    return token;
187                }
188            }
189        }
190
191        /// # Safety:
192        ///
193        /// This function must only be called if the lock was acquired by the
194        /// current thread.
195        #[inline]
196        pub unsafe fn unlock(&self) {
197            #[cfg(debug_assertions)]
198            if self.owner.load(Ordering::Relaxed) != thread_id() {
199                panic_attempt_unlock_not_owned();
200            }
201            self.owner.store(UNUSED_THREAD_ID_VALUE, Ordering::Release);
202        }
203    }
204
205    #[cfg(debug_assertions)]
206    #[inline(never)]
207    #[cold]
208    fn panic_attempt_unlock_not_owned() -> ! {
209        panic!("tried to unlock a mutex locked on a different thread");
210    }
211}
212
213#[cfg(multi_core)]
214use multi_core::LockedState;
215#[cfg(single_core)]
216use single_core::LockedState;
217
218/// A generic lock that wraps a [`RawLock`] implementation and tracks
219/// whether the caller has locked recursively.
220pub struct GenericRawMutex<L: RawLock> {
221    lock: L,
222    inner: LockedState,
223}
224
225// Safety: LockedState ensures thread-safety
226unsafe impl<L: RawLock> Sync for GenericRawMutex<L> {}
227
228impl<L: RawLock> GenericRawMutex<L> {
229    /// Create a new lock.
230    pub const fn new(lock: L) -> Self {
231        Self {
232            lock,
233            inner: LockedState::new(),
234        }
235    }
236
237    /// Acquires the lock.
238    ///
239    /// # Safety
240    ///
241    /// - Each release call must be paired with an acquire call.
242    /// - The returned token must be passed to the corresponding `release` call.
243    /// - The caller must ensure to release the locks in the reverse order they were acquired.
244    #[inline]
245    unsafe fn acquire(&self) -> RestoreState {
246        self.inner.lock(&self.lock)
247    }
248
249    /// Releases the lock.
250    ///
251    /// # Safety
252    ///
253    /// - This function must only be called if the lock was acquired by the current thread.
254    /// - The caller must ensure to release the locks in the reverse order they were acquired.
255    /// - Each release call must be paired with an acquire call.
256    #[inline]
257    unsafe fn release(&self, token: RestoreState) {
258        if !token.is_reentry() {
259            unsafe {
260                self.inner.unlock();
261
262                self.lock.exit(token)
263            }
264        }
265    }
266
267    /// Runs the callback with this lock locked.
268    ///
269    /// Note that this function is not reentrant, calling it reentrantly will
270    /// panic.
271    #[inline]
272    pub fn lock_non_reentrant<R>(&self, f: impl FnOnce() -> R) -> R {
273        let _token = LockGuard::new_non_reentrant(self);
274        f()
275    }
276
277    /// Runs the callback with this lock locked.
278    #[inline]
279    pub fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
280        let _token = LockGuard::new_reentrant(self);
281        f()
282    }
283}
284
285/// A mutual exclusion primitive.
286///
287/// This lock disables interrupts on the current core while locked.
288#[cfg_attr(
289    multi_core,
290    doc = r#"It needs a bit of memory, but it does not take a global critical
291    section, making it preferrable for use in multi-core systems."#
292)]
293pub struct RawMutex {
294    inner: GenericRawMutex<SingleCoreInterruptLock>,
295}
296
297impl Default for RawMutex {
298    #[inline]
299    fn default() -> Self {
300        Self::new()
301    }
302}
303
304impl RawMutex {
305    /// Create a new lock.
306    #[inline]
307    pub const fn new() -> Self {
308        Self {
309            inner: GenericRawMutex::new(SingleCoreInterruptLock),
310        }
311    }
312
313    /// Acquires the lock.
314    ///
315    /// # Safety
316    ///
317    /// - Each release call must be paired with an acquire call.
318    /// - The returned token must be passed to the corresponding `release` call.
319    /// - The caller must ensure to release the locks in the reverse order they were acquired.
320    #[inline]
321    pub unsafe fn acquire(&self) -> RestoreState {
322        unsafe { self.inner.acquire() }
323    }
324
325    /// Releases the lock.
326    ///
327    /// # Safety
328    ///
329    /// - This function must only be called if the lock was acquired by the current thread.
330    /// - The caller must ensure to release the locks in the reverse order they were acquired.
331    /// - Each release call must be paired with an acquire call.
332    #[inline]
333    pub unsafe fn release(&self, token: RestoreState) {
334        unsafe {
335            self.inner.release(token);
336        }
337    }
338
339    /// Runs the callback with this lock locked.
340    ///
341    /// Note that this function is not reentrant, calling it reentrantly will
342    /// panic.
343    #[inline]
344    pub fn lock_non_reentrant<R>(&self, f: impl FnOnce() -> R) -> R {
345        self.inner.lock_non_reentrant(f)
346    }
347
348    /// Runs the callback with this lock locked.
349    #[inline]
350    pub fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
351        self.inner.lock(f)
352    }
353}
354
355unsafe impl embassy_sync_06::blocking_mutex::raw::RawMutex for RawMutex {
356    #[allow(clippy::declare_interior_mutable_const)]
357    const INIT: Self = Self::new();
358
359    fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
360        self.inner.lock(f)
361    }
362}
363
364unsafe impl embassy_sync_07::blocking_mutex::raw::RawMutex for RawMutex {
365    #[allow(clippy::declare_interior_mutable_const)]
366    const INIT: Self = Self::new();
367
368    fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
369        self.inner.lock(f)
370    }
371}
372
373unsafe impl embassy_sync_08::blocking_mutex::raw::RawMutex for RawMutex {
374    #[allow(clippy::declare_interior_mutable_const)]
375    const INIT: Self = Self::new();
376
377    fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
378        self.inner.lock(f)
379    }
380}
381
382/// A non-reentrant (panicking) mutex.
383///
384/// This is largely equivalent to a `critical_section::Mutex<RefCell<T>>`, but accessing the inner
385/// data doesn't hold a critical section on multi-core systems.
386pub struct NonReentrantMutex<T> {
387    lock_state: RawMutex,
388    data: UnsafeCell<T>,
389}
390
391impl<T> NonReentrantMutex<T> {
392    /// Create a new instance
393    pub const fn new(data: T) -> Self {
394        Self {
395            lock_state: RawMutex::new(),
396            data: UnsafeCell::new(data),
397        }
398    }
399
400    /// Provide exclusive access to the protected data to the given closure.
401    ///
402    /// Calling this reentrantly will panic.
403    pub fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
404        self.lock_state
405            .lock_non_reentrant(|| f(unsafe { &mut *self.data.get() }))
406    }
407}
408
409unsafe impl<T: Send> Send for NonReentrantMutex<T> {}
410unsafe impl<T: Send> Sync for NonReentrantMutex<T> {}
411
412struct LockGuard<'a, L: RawLock> {
413    lock: &'a GenericRawMutex<L>,
414    token: RestoreState,
415}
416
417impl<'a, L: RawLock> LockGuard<'a, L> {
418    #[inline]
419    fn new_non_reentrant(lock: &'a GenericRawMutex<L>) -> Self {
420        let this = Self::new_reentrant(lock);
421        if this.token.is_reentry() {
422            panic_lock_not_reentrant();
423        }
424        this
425    }
426
427    #[inline]
428    fn new_reentrant(lock: &'a GenericRawMutex<L>) -> Self {
429        let token = unsafe {
430            // SAFETY: the same lock will be released when dropping the guard.
431            // This ensures that the lock is released on the same thread, in the reverse
432            // order it was acquired.
433            lock.acquire()
434        };
435
436        Self { lock, token }
437    }
438}
439
440impl<L: RawLock> Drop for LockGuard<'_, L> {
441    fn drop(&mut self) {
442        unsafe { self.lock.release(self.token) };
443    }
444}
445
446#[inline(never)]
447#[cold]
448fn panic_lock_not_reentrant() -> ! {
449    panic!("lock is not reentrant");
450}