rust: sync: Add SpinLockIrq

A variant of `SpinLock` that ensures interrupts are disabled in the
critical section. `lock()` will ensure that either interrupts are
already disabled or disable them. `unlock()` will reverse the respective
operation.

[Boqun: Port to use spin_lock_irq_disable() and
spin_unlock_irq_enable()]

Signed-off-by: Lyude Paul <lyude@redhat.com>
Signed-off-by: Boqun Feng <boqun@kernel.org>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Gary Guo <gary@garyguo.net>
Link: https://patch.msgid.link/20260807070218.27144-18-boqun@kernel.org
This commit is contained in:
Lyude Paul
2026-08-07 00:02:14 -07:00
committed by Peter Zijlstra
parent df9165ab52
commit 5967f4df55
3 changed files with 241 additions and 1 deletions

View File

@@ -27,7 +27,14 @@
pub use condvar::{new_condvar, CondVar, CondVarTimeoutResult};
pub use lock::global::{global_lock, GlobalGuard, GlobalLock, GlobalLockBackend, GlobalLockedBy};
pub use lock::mutex::{new_mutex, Mutex, MutexGuard};
pub use lock::spinlock::{new_spinlock, SpinLock, SpinLockGuard};
pub use lock::spinlock::{
new_spinlock,
new_spinlock_irq,
SpinLock,
SpinLockGuard,
SpinLockIrq,
SpinLockIrqGuard, //
};
pub use locked_by::LockedBy;
pub use refcount::Refcount;
pub use set_once::SetOnce;

View File

@@ -306,4 +306,7 @@ macro_rules! global_lock_inner {
(backend SpinLock) => {
$crate::sync::lock::spinlock::SpinLockBackend
};
(backend SpinLockIrq) => {
$crate::sync::lock::spinlock::SpinLockIrqBackend
};
}

View File

@@ -4,6 +4,7 @@
//!
//! This module allows Rust code to use the kernel's `spinlock_t`.
use super::*;
use crate::prelude::*;
/// Creates a [`SpinLock`] initialiser with the given name and a newly-created lock class.
///
@@ -143,3 +144,232 @@ unsafe fn assert_is_held(ptr: *mut Self::State) {
unsafe { bindings::spin_assert_is_held(ptr) }
}
}
/// Creates a [`SpinLockIrq`] initialiser with the given name and a newly-created lock class.
///
/// It uses the name if one is given, otherwise it generates one based on the file name and line
/// number.
#[macro_export]
macro_rules! new_spinlock_irq {
($inner:expr $(, $name:literal)? $(,)?) => {
$crate::sync::SpinLockIrq::new(
$inner, $crate::optional_name!($($name)?), $crate::static_lock_class!())
};
}
pub use new_spinlock_irq;
/// A variant of `SpinLock` that ensures interrupts are disabled in the critical section.
///
/// For more info on spinlocks, see [`SpinLock`]. For more information on interrupts,
/// [see the interrupt module](kernel::interrupt).
///
/// # Examples
///
/// The following example shows how to declare, allocate initialise and access a struct (`Example`)
/// that contains an inner struct (`Inner`) that is protected by a spinlock that requires local
/// processor interrupts to be disabled.
///
/// ```
/// use kernel::sync::{new_spinlock_irq, SpinLockIrq};
///
/// struct Inner {
/// a: u32,
/// b: u32,
/// }
///
/// #[pin_data]
/// struct Example {
/// #[pin]
/// c: SpinLockIrq<Inner>,
/// #[pin]
/// d: SpinLockIrq<Inner>,
/// }
///
/// impl Example {
/// fn new() -> impl PinInit<Self> {
/// pin_init!(Self {
/// c <- new_spinlock_irq!(Inner { a: 0, b: 10 }),
/// d <- new_spinlock_irq!(Inner { a: 20, b: 30 }),
/// })
/// }
/// }
///
/// // Allocate a boxed `Example`
/// let e = KBox::pin_init(Example::new(), GFP_KERNEL)?;
///
/// // Accessing an `Example` from a context where interrupts may not be disabled already.
/// let c_guard = e.c.lock(); // interrupts are disabled now, +1 interrupt disable refcount
/// let d_guard = e.d.lock(); // no interrupt state change, +1 interrupt disable refcount
///
/// assert_eq!(c_guard.a, 0);
/// assert_eq!(c_guard.b, 10);
/// assert_eq!(d_guard.a, 20);
/// assert_eq!(d_guard.b, 30);
///
/// drop(c_guard); // Dropping c_guard will not re-enable interrupts just yet, since d_guard is
/// // still in scope.
/// drop(d_guard); // Last interrupt disable reference dropped here, so interrupts are re-enabled
/// // now
/// # Ok::<(), Error>(())
/// ```
///
/// [`lock()`]: SpinLockIrq::lock
pub type SpinLockIrq<T> = super::Lock<T, SpinLockIrqBackend>;
/// A kernel `spinlock_t` lock backend that can only be acquired in interrupt disabled contexts.
pub struct SpinLockIrqBackend;
/// A [`Guard`] acquired from locking a [`SpinLockIrq`] using [`lock()`].
///
/// This is simply a type alias for a [`Guard`] returned from locking a [`SpinLockIrq`] using
/// [`lock()`]. It will unlock the [`SpinLockIrq`] and decrement the local processor's interrupt
/// disablement refcount upon being dropped.
///
/// [`lock()`]: SpinLockIrq::lock
pub type SpinLockIrqGuard<'a, T> = Guard<'a, T, SpinLockIrqBackend>;
// SAFETY: The underlying kernel `spinlock_t` object ensures mutual exclusion. `relock` uses the
// default implementation that always calls the same locking method.
unsafe impl Backend for SpinLockIrqBackend {
type State = bindings::spinlock_t;
type GuardState = ();
#[inline]
unsafe fn init(
ptr: *mut Self::State,
name: *const crate::ffi::c_char,
key: *mut bindings::lock_class_key,
) {
// SAFETY: The safety requirements ensure that `ptr` is valid for writes, and `name` and
// `key` are valid for read indefinitely.
unsafe { bindings::__spin_lock_init(ptr, name, key) }
}
#[inline]
unsafe fn lock(ptr: *mut Self::State) -> Self::GuardState {
// SAFETY: The safety requirements of this function ensure that `ptr` points to valid
// memory, and that it has been initialised before.
unsafe { bindings::spin_lock_irq_disable(ptr) }
}
#[inline]
unsafe fn unlock(ptr: *mut Self::State, _guard_state: &Self::GuardState) {
// SAFETY: The safety requirements of this function ensure that `ptr` is valid and that the
// caller is the owner of the spinlock.
unsafe { bindings::spin_unlock_irq_enable(ptr) }
}
#[inline]
unsafe fn try_lock(ptr: *mut Self::State) -> Option<Self::GuardState> {
// SAFETY: The `ptr` pointer is guaranteed to be valid and initialized before use.
let result = unsafe { bindings::spin_trylock_irq_disable(ptr) };
if result != 0 {
Some(())
} else {
None
}
}
#[inline]
unsafe fn assert_is_held(ptr: *mut Self::State) {
// SAFETY: The `ptr` pointer is guaranteed to be valid and initialized before use.
unsafe { bindings::spin_assert_is_held(ptr) }
}
}
#[kunit_tests(rust_spinlock_irq_condvar)]
mod tests {
use super::*;
use crate::{
sync::*,
workqueue::{
self,
impl_has_work,
new_work,
Work,
WorkItem, //
},
};
struct TestState {
value: u32,
waiter_ready: bool,
}
#[pin_data]
struct Test {
#[pin]
state: SpinLockIrq<TestState>,
#[pin]
state_changed: CondVar,
#[pin]
waiter_state_changed: CondVar,
#[pin]
wait_work: Work<Self>,
}
impl_has_work! {
impl HasWork<Self> for Test { self.wait_work }
}
impl Test {
pub(crate) fn new() -> Result<Arc<Self>> {
Arc::try_pin_init(
try_pin_init!(
Self {
state <- new_spinlock_irq!(TestState {
value: 1,
waiter_ready: false
}),
state_changed <- new_condvar!(),
waiter_state_changed <- new_condvar!(),
wait_work <- new_work!("IrqCondvarTest::wait_work")
}
),
GFP_KERNEL,
)
}
}
impl WorkItem for Test {
type Pointer = Arc<Self>;
fn run(this: Arc<Self>) {
// Wait for the test to be ready to wait for us
let mut state = this.state.lock();
// Make sure the interrupts actually turned off
// SAFETY: It's always safe to call `lockdep_assert_irqs_disabled()`
unsafe { bindings::lockdep_assert_irqs_disabled() };
while !state.waiter_ready {
this.waiter_state_changed.wait(&mut state);
}
// Deliver the exciting value update our test has been waiting for
state.value += 1;
this.state_changed.notify_sync();
}
}
#[test]
fn spinlock_irq_condvar() -> Result {
let testdata = Test::new()?;
let _ = workqueue::system().enqueue(testdata.clone());
// Let the updater know when we're ready to wait
let mut state = testdata.state.lock();
state.waiter_ready = true;
testdata.waiter_state_changed.notify_sync();
// Wait for the exciting value update
testdata.state_changed.wait(&mut state);
assert_eq!(state.value, 2);
Ok(())
}
}