interrupt_mutex/lib.rs
1//! A mutex for sharing data with interrupt handlers or signal 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//! ```
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//! # // Setup signal handling for demo
39//! #
40//! # use nix::libc;
41//! # use nix::sys::signal::{self, SigHandler, Signal};
42//! #
43//! # extern "C" fn handle_sigint(_signal: libc::c_int) {
44//! # interrupt_handler();
45//! # }
46//! #
47//! # let handler = SigHandler::Handler(handle_sigint);
48//! # unsafe { signal::signal(Signal::SIGINT, handler) }.unwrap();
49//! #
50//! # fn raise_interrupt() {
51//! # signal::raise(Signal::SIGINT);
52//! # }
53//!
54//! let v = X.lock();
55//! // Raise an interrupt
56//! raise_interrupt();
57//! assert_eq!(*v, vec![]);
58//! drop(v);
59//!
60//! // The interrupt handler runs
61//!
62//! let v = X.lock();
63//! assert_eq!(*v, vec![1]);
64//! drop(v);
65//! ```
66
67#![no_std]
68
69use core::cell::UnsafeCell;
70use core::mem::MaybeUninit;
71
72use lock_api::{GuardNoSend, RawMutex};
73
74/// A mutex for sharing data with interrupt handlers or signal handlers.
75///
76/// This mutex wraps another [`RawMutex`] and disables interrupts while locked.
77pub struct RawInterruptMutex<I> {
78 inner: I,
79 interrupt_guard: UnsafeCell<MaybeUninit<interrupts::Guard>>,
80}
81
82// SAFETY: The `UnsafeCell` is locked by `inner`, initialized on `lock` and uninitialized on `unlock`.
83unsafe impl<I: Sync> Sync for RawInterruptMutex<I> {}
84// SAFETY: Mutexes cannot be send to other threads while locked.
85// Sending them while unlocked is fine.
86unsafe impl<I: Send> Send for RawInterruptMutex<I> {}
87
88unsafe impl<I: RawMutex> RawMutex for RawInterruptMutex<I> {
89 const INIT: Self = Self {
90 inner: I::INIT,
91 interrupt_guard: UnsafeCell::new(MaybeUninit::uninit()),
92 };
93
94 type GuardMarker = GuardNoSend;
95
96 #[inline]
97 fn lock(&self) {
98 let guard = interrupts::disable();
99 self.inner.lock();
100 // SAFETY: We have exclusive access through locking `inner`.
101 unsafe {
102 self.interrupt_guard.get().write(MaybeUninit::new(guard));
103 }
104 }
105
106 #[inline]
107 fn try_lock(&self) -> bool {
108 let guard = interrupts::disable();
109 let ok = self.inner.try_lock();
110 if ok {
111 // SAFETY: We have exclusive access through locking `inner`.
112 unsafe {
113 self.interrupt_guard.get().write(MaybeUninit::new(guard));
114 }
115 }
116 ok
117 }
118
119 #[inline]
120 unsafe fn unlock(&self) {
121 // SAFETY: We have exclusive access through locking `inner`.
122 let guard = unsafe { self.interrupt_guard.get().replace(MaybeUninit::uninit()) };
123 // SAFETY: `guard` was initialized when locking.
124 let guard = unsafe { guard.assume_init() };
125 unsafe {
126 self.inner.unlock();
127 }
128 drop(guard);
129 }
130
131 #[inline]
132 fn is_locked(&self) -> bool {
133 self.inner.is_locked()
134 }
135}
136
137/// A [`lock_api::Mutex`] based on [`RawInterruptMutex`].
138pub type InterruptMutex<I, T> = lock_api::Mutex<RawInterruptMutex<I>, T>;
139
140/// A [`lock_api::MutexGuard`] based on [`RawInterruptMutex`].
141pub type InterruptMutexGuard<'a, I, T> = lock_api::MutexGuard<'a, RawInterruptMutex<I>, T>;