Skip to main content

interrupt_mutex/
lib.rs

1//! A mutex for sharing data with interrupt handlers.
2//!
3//! Using normal mutexes to share data with interrupt handlers may result in deadlocks.
4//! This is because interrupts may be raised while the mutex is being held on the same thread.
5//!
6//! [`InterruptMutex`] wraps another mutex and disables interrupts while the inner mutex is locked.
7//! When the mutex is unlocked, the previous interrupt state is restored.
8//! This makes [`InterruptMutex`] suitable for sharing data with interrupts.
9//!
10//! When used in bare-metal environments with spinlocks, locking the mutex corresponds to Linux's `spin_lock_irqsave` and unlocking corresponds to `spin_unlock_irqrestore`.
11//! See the [Unreliable Guide To Locking — The Linux Kernel documentation].
12//! While `spin_lock_irqsave(lock, flags)` saves the interrupt flags in the explicit `flags` argument, [`InterruptMutex`] saves the interrupt flags internally.
13//!
14//! [Unreliable Guide To Locking — The Linux Kernel documentation]: https://www.kernel.org/doc/html/latest/kernel-hacking/locking.html#locking-between-hard-irq-and-softirqs-tasklets
15//!
16//! [Drop Order]: #caveats
17//!
18//! # Caveats
19//!
20//! <div class="warning">Interrupts are disabled on a best-effort basis.</div>
21//!
22//! Holding an [`InterruptMutexGuard`] does not guarantee that interrupts are disabled.
23//! Dropping guards from different [`InterruptMutex`]es in the wrong order might enable interrupts prematurely.
24//! Similarly, you can just enable interrupts manually while holding a guard.
25//!
26//! # Examples
27//!
28//! ```no_run
29//! // Make a mutex of your choice into an `InterruptMutex`.
30//! type InterruptMutex<T> = interrupt_mutex::InterruptMutex<parking_lot::RawMutex, T>;
31//!
32//! static X: InterruptMutex<Vec<i32>> = InterruptMutex::new(Vec::new());
33//!
34//! fn interrupt_handler() {
35//!     X.lock().push(1);
36//! }
37//! #
38//! # fn raise_interrupt() {}
39//!
40//! let v = X.lock();
41//! // Raise an interrupt
42//! raise_interrupt();
43//! assert_eq!(*v, vec![]);
44//! drop(v);
45//!
46//! // The interrupt handler runs
47//!
48//! let v = X.lock();
49//! assert_eq!(*v, vec![1]);
50//! drop(v);
51//! ```
52
53#![no_std]
54
55use core::cell::UnsafeCell;
56use core::mem::MaybeUninit;
57
58use lock_api::{GuardNoSend, RawMutex};
59
60/// A mutex for sharing data with interrupt handlers.
61///
62/// This mutex wraps another [`RawMutex`] and disables interrupts while locked.
63pub struct RawInterruptMutex<I> {
64    inner: I,
65    interrupt_guard: UnsafeCell<MaybeUninit<interrupts::Guard>>,
66}
67
68// SAFETY: The `UnsafeCell` is locked by `inner`, initialized on `lock` and uninitialized on `unlock`.
69unsafe impl<I: Sync> Sync for RawInterruptMutex<I> {}
70// SAFETY: Mutexes cannot be send to other threads while locked.
71// Sending them while unlocked is fine.
72unsafe impl<I: Send> Send for RawInterruptMutex<I> {}
73
74unsafe impl<I: RawMutex> RawMutex for RawInterruptMutex<I> {
75    #[allow(clippy::declare_interior_mutable_const)]
76    const INIT: Self = Self {
77        inner: I::INIT,
78        interrupt_guard: UnsafeCell::new(MaybeUninit::uninit()),
79    };
80
81    type GuardMarker = GuardNoSend;
82
83    #[inline]
84    fn lock(&self) {
85        let guard = interrupts::disable();
86        self.inner.lock();
87        // SAFETY: We have exclusive access through locking `inner`.
88        unsafe {
89            self.interrupt_guard.get().write(MaybeUninit::new(guard));
90        }
91    }
92
93    #[inline]
94    fn try_lock(&self) -> bool {
95        let guard = interrupts::disable();
96        let ok = self.inner.try_lock();
97        if ok {
98            // SAFETY: We have exclusive access through locking `inner`.
99            unsafe {
100                self.interrupt_guard.get().write(MaybeUninit::new(guard));
101            }
102        }
103        ok
104    }
105
106    #[inline]
107    unsafe fn unlock(&self) {
108        // SAFETY: We have exclusive access through locking `inner`.
109        let guard = unsafe { self.interrupt_guard.get().replace(MaybeUninit::uninit()) };
110        // SAFETY: `guard` was initialized when locking.
111        let guard = unsafe { guard.assume_init() };
112        unsafe {
113            self.inner.unlock();
114        }
115        drop(guard);
116    }
117
118    #[inline]
119    fn is_locked(&self) -> bool {
120        self.inner.is_locked()
121    }
122}
123
124/// A [`lock_api::Mutex`] based on [`RawInterruptMutex`].
125pub type InterruptMutex<I, T> = lock_api::Mutex<RawInterruptMutex<I>, T>;
126
127/// A [`lock_api::MutexGuard`] based on [`RawInterruptMutex`].
128pub type InterruptMutexGuard<'a, I, T> = lock_api::MutexGuard<'a, RawInterruptMutex<I>, T>;