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>;