Skip to main content

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}