kernel/lib/sync/irq_mutex.rs
1// SPDX-License-Identifier: GPL-3.0-only
2//! Interrupt-aware spinlock (`IrqMutex`).
3//!
4//! Authors: MarioS271
5
6use core::cell::UnsafeCell;
7use core::ops::{Deref, DerefMut};
8use core::sync::atomic::{AtomicBool, Ordering};
9use x86_64::registers::rflags::RFlags;
10use crate::cpu::instructions;
11
12// TODO: refactor to be arch-abstract
13
14/// Spinlock for data shared between normal kernel code and interrupt handlers;
15/// disables interrupts while held to avoid self-deadlock.
16pub struct IrqMutex<T> {
17 data: UnsafeCell<T>,
18 locked: AtomicBool,
19}
20
21/// Safety: [`IrqMutex`] is an IRQ-safe spinlock-mutex, meaning data can only be accessed
22/// by locking it, therefore making parallel data access impossible
23unsafe impl<T: Send> Sync for IrqMutex<T> {}
24/// Safety: as long as `T` is [`Send`] (which is forced by the bound), [`IrqMutex`] can also
25/// be send because it does not add any non-send components
26unsafe impl<T: Send> Send for IrqMutex<T> {}
27
28impl<T> IrqMutex<T> {
29 /// Create a new unlocked `IrqMutex` wrapping `value`.
30 pub const fn new(value: T) -> Self {
31 IrqMutex {
32 data: UnsafeCell::new(value),
33 locked: AtomicBool::new(false),
34 }
35 }
36
37 /// Acquire the lock, disabling interrupts first. Returns a guard that restores IF on drop.
38 pub fn lock(&self) -> IrqMutexGuard<'_, T> {
39 let rflags = x86_64::registers::rflags::read();
40 instructions::disable_interrupts();
41
42 loop {
43 let res = self.locked.compare_exchange(
44 false,
45 true,
46 Ordering::Acquire,
47 Ordering::Relaxed
48 );
49 if res.is_ok() { break; }
50 }
51
52 IrqMutexGuard::new(self, rflags)
53 }
54
55 /// Release the lock without restoring the interrupt flag, for use in panic paths.
56 ///
57 /// # Safety
58 /// Calling this while a live [`IrqMutexGuard`] still exists creates two concurrent
59 /// accessors to the protected data. Only call from a panic handler that halts the
60 /// CPU immediately after and never touches the data through the mutex again.
61 pub unsafe fn force_unlock(&self) {
62 self.locked.store(false, Ordering::Release);
63 }
64}
65
66/// RAII guard for [`IrqMutex`]; releases the lock and restores previous interrupts state on drop
67pub struct IrqMutexGuard<'g, T> {
68 mutex: &'g IrqMutex<T>,
69 rflags: RFlags,
70}
71
72impl<'g, T> IrqMutexGuard<'g, T> {
73 /// Construct a guard associated with `mutex`, saving `rflags` for later restore.
74 pub fn new(mutex: &'g IrqMutex<T>, rflags: RFlags) -> IrqMutexGuard<'g, T> {
75 IrqMutexGuard{ mutex, rflags }
76 }
77}
78
79impl<'g, T> Drop for IrqMutexGuard<'g, T> {
80 /// Unlocks the mutex and restores interrupt state
81 fn drop(&mut self) {
82 self.mutex.locked.store(false, Ordering::Release);
83 if self.rflags.contains(RFlags::INTERRUPT_FLAG) {
84 instructions::enable_interrupts();
85 }
86 }
87}
88
89impl<'g, T> Deref for IrqMutexGuard<'g, T> {
90 type Target = T;
91
92 /// Returns a reference to the contained data
93 fn deref(&self) -> &Self::Target {
94 // Safety: the IrqMutexGuard can only exist when the data is locked, meaning concurrent access
95 // is impossible
96 unsafe { &*self.mutex.data.get() }
97 }
98}
99
100impl<'g, T> DerefMut for IrqMutexGuard<'g, T> {
101 /// Returns a mutable reference to the contained data
102 fn deref_mut(&mut self) -> &mut Self::Target {
103 // Safety: the IrqMutexGuard can only exist when the data is locked, meaning concurrent access
104 // is impossible; additionally, &mut guarantees only one mutable reference
105 unsafe { &mut *self.mutex.data.get() }
106 }
107}