mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 13:23:02 -04:00
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:
committed by
Peter Zijlstra
parent
df9165ab52
commit
5967f4df55
@@ -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;
|
||||
|
||||
@@ -306,4 +306,7 @@ macro_rules! global_lock_inner {
|
||||
(backend SpinLock) => {
|
||||
$crate::sync::lock::spinlock::SpinLockBackend
|
||||
};
|
||||
(backend SpinLockIrq) => {
|
||||
$crate::sync::lock::spinlock::SpinLockIrqBackend
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user