mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v13 0/7] rust: add ww_mutex support
@ 2026-10-20 15:29 Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 1/7] rust: add C wrappers for ww_mutex inline functions Onur Özkan
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Onur Özkan @ 2026-10-20 15:29 UTC (permalink / raw)
  To: rust-for-linux
  Cc: lossin, lyude, ojeda, alex.gaynor, boqun.feng, gary, a.hindborg,
	aliceryhl, tmgross, dakr, peterz, mingo, will, longman,
	felipe_life, daniel, daniel.almeida, thomas.hellstrom,
	linux-kernel, Onur Özkan

This series adds Rust abstractions for ww_mutex, including Class, Mutex,
AcquireCtx and MutexGuard. AcquireCtxBackoff and ww_transaction handle
deadlock recovery with ordinary guards. DynLockSet tracks dynamically
discovered locks and provides separate acquisition and work callbacks.

Changes in v13:
  - Require static classes and remove the class lifetime parameters from
    Mutex and AcquireCtx.
  - Make AcquireCtx's locking methods safe. Panic on context drop if any
    acquired locks remain and check for acquired-lock count overflow
    before entering C.
  - Tighten AcquireCtx::from_raw()'s safety contract to cover forgotten
    guards.
  - Return Option from try_lock().
  - Split LockSet into AcquireCtxBackoff and DynLockSet in separate
    patches. Add ww_transaction() for allocation-free retries with
    ordinary guards.
  - Start each DynLockSet transaction with a new ticket, keep it unchanged
    across retries and clear pending backoff state on return.

Dropped Alice's Reviewed-by from the Mutex/AcquireCtx patch because the
locking API and safety contracts changed.

Previous version: https://lore.kernel.org/all/cover.1791799751.git.work@onurozkan.dev

Onur Özkan (7):
  rust: add C wrappers for ww_mutex inline functions
  ww_mutex: add ww_class field unconditionally
  rust: implement Class for ww_class support
  rust: ww_mutex: add Mutex, AcquireCtx and MutexGuard
  rust: ww_mutex: add AcquireCtxBackoff
  rust: ww_mutex: implement DynLockSet
  MAINTAINERS: add Onur Özkan as WW MUTEX maintainer

 MAINTAINERS                                   |   2 +
 include/linux/ww_mutex.h                      |   8 +-
 rust/helpers/helpers.c                        |   1 +
 rust/helpers/ww_mutex.c                       |  33 ++
 rust/kernel/sync/lock.rs                      |   1 +
 rust/kernel/sync/lock/ww_mutex.rs             | 449 ++++++++++++++++++
 rust/kernel/sync/lock/ww_mutex/acquire_ctx.rs | 179 +++++++
 .../sync/lock/ww_mutex/acquire_ctx_backoff.rs | 413 ++++++++++++++++
 rust/kernel/sync/lock/ww_mutex/class.rs       | 130 +++++
 .../kernel/sync/lock/ww_mutex/dyn_lock_set.rs | 351 ++++++++++++++
 10 files changed, 1561 insertions(+), 6 deletions(-)
 create mode 100644 rust/helpers/ww_mutex.c
 create mode 100644 rust/kernel/sync/lock/ww_mutex.rs
 create mode 100644 rust/kernel/sync/lock/ww_mutex/acquire_ctx.rs
 create mode 100644 rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs
 create mode 100644 rust/kernel/sync/lock/ww_mutex/class.rs
 create mode 100644 rust/kernel/sync/lock/ww_mutex/dyn_lock_set.rs


base-commit: 47324d3a5b3abd781295044d01d92d09f184e872
-- 
2.51.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v13 1/7] rust: add C wrappers for ww_mutex inline functions
  2026-10-20 15:29 [PATCH v13 0/7] rust: add ww_mutex support Onur Özkan
@ 2026-10-20 15:29 ` Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 2/7] ww_mutex: add ww_class field unconditionally Onur Özkan
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Onur Özkan @ 2026-10-20 15:29 UTC (permalink / raw)
  To: rust-for-linux
  Cc: lossin, lyude, ojeda, alex.gaynor, boqun.feng, gary, a.hindborg,
	aliceryhl, tmgross, dakr, peterz, mingo, will, longman,
	felipe_life, daniel, daniel.almeida, thomas.hellstrom,
	linux-kernel, Onur Özkan

Some of the kernel's `ww_mutex` functions are implemented as
`static inline`, so they are inaccessible from Rust as bindgen
can't generate code on them. This patch provides C function wrappers
around these inline implementations, so bindgen can see them and
generate the corresponding Rust code.

Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Onur Özkan <work@onurozkan.dev>
---
 MAINTAINERS             |  1 +
 rust/helpers/helpers.c  |  1 +
 rust/helpers/ww_mutex.c | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 35 insertions(+)
 create mode 100644 rust/helpers/ww_mutex.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 47946f1c507b9..198bb740f7cc2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15283,6 +15283,7 @@ F:	kernel/locking/
 F:	lib/locking*.[ch]
 F:	rust/helpers/mutex.c
 F:	rust/helpers/spinlock.c
+F:	rust/helpers/ww_mutex.c
 F:	rust/kernel/sync/lock.rs
 F:	rust/kernel/sync/lock/
 F:	rust/kernel/sync/locked_by.rs
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 440fb7638e3cb..0287064c82c18 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -105,4 +105,5 @@
 #include "vmalloc.c"
 #include "wait.c"
 #include "workqueue.c"
+#include "ww_mutex.c"
 #include "xarray.c"
diff --git a/rust/helpers/ww_mutex.c b/rust/helpers/ww_mutex.c
new file mode 100644
index 0000000000000..e7aa65ac58745
--- /dev/null
+++ b/rust/helpers/ww_mutex.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/ww_mutex.h>
+
+__rust_helper
+void rust_helper_ww_mutex_init(struct ww_mutex *lock, struct ww_class *ww_class)
+{
+	ww_mutex_init(lock, ww_class);
+}
+
+__rust_helper
+void rust_helper_ww_acquire_init(struct ww_acquire_ctx *ctx, struct ww_class *ww_class)
+{
+	ww_acquire_init(ctx, ww_class);
+}
+
+__rust_helper
+void rust_helper_ww_acquire_done(struct ww_acquire_ctx *ctx)
+{
+	ww_acquire_done(ctx);
+}
+
+__rust_helper
+void rust_helper_ww_acquire_fini(struct ww_acquire_ctx *ctx)
+{
+	ww_acquire_fini(ctx);
+}
+
+__rust_helper
+bool rust_helper_ww_mutex_is_locked(struct ww_mutex *lock)
+{
+	return ww_mutex_is_locked(lock);
+}
-- 
2.51.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v13 2/7] ww_mutex: add ww_class field unconditionally
  2026-10-20 15:29 [PATCH v13 0/7] rust: add ww_mutex support Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 1/7] rust: add C wrappers for ww_mutex inline functions Onur Özkan
@ 2026-10-20 15:29 ` Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 3/7] rust: implement Class for ww_class support Onur Özkan
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Onur Özkan @ 2026-10-20 15:29 UTC (permalink / raw)
  To: rust-for-linux
  Cc: lossin, lyude, ojeda, alex.gaynor, boqun.feng, gary, a.hindborg,
	aliceryhl, tmgross, dakr, peterz, mingo, will, longman,
	felipe_life, daniel, daniel.almeida, thomas.hellstrom,
	linux-kernel, Onur Özkan

Removes DEBUG_WW_MUTEXES guard around the ww_class field
in struct ww_mutex and struct ww_acquire_ctx to always
initialize it.

Keeping the class pointer available unconditionally allows
Rust abstractions to keep from_raw() simple on Mutex and
AcquireCtx and validate Mutex and AcquireCtx class pairing
in much simpler way.

Link: https://lore.kernel.org/all/ECC0425A-8B18-4626-8EA8-2F843C45E0A1@collabora.com/
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Onur Özkan <work@onurozkan.dev>
---
 include/linux/ww_mutex.h | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/include/linux/ww_mutex.h b/include/linux/ww_mutex.h
index 0c95ead5a2977..c955824aa5d48 100644
--- a/include/linux/ww_mutex.h
+++ b/include/linux/ww_mutex.h
@@ -48,9 +48,7 @@ struct ww_class {
 context_lock_struct(ww_mutex) {
 	struct WW_MUTEX_BASE base;
 	struct ww_acquire_ctx *ctx;
-#ifdef DEBUG_WW_MUTEXES
 	struct ww_class *ww_class;
-#endif
 };
 
 context_lock_struct(ww_acquire_ctx) {
@@ -59,9 +57,9 @@ context_lock_struct(ww_acquire_ctx) {
 	unsigned int acquired;
 	unsigned short wounded;
 	unsigned short is_wait_die;
+	struct ww_class *ww_class;
 #ifdef DEBUG_WW_MUTEXES
 	unsigned int done_acquire;
-	struct ww_class *ww_class;
 	void *contending_lock;
 #endif
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
@@ -111,9 +109,7 @@ static inline void ww_mutex_init(struct ww_mutex *lock,
 {
 	ww_mutex_base_init(&lock->base, ww_class->mutex_name, &ww_class->mutex_key);
 	lock->ctx = NULL;
-#ifdef DEBUG_WW_MUTEXES
 	lock->ww_class = ww_class;
-#endif
 }
 
 /**
@@ -149,8 +145,8 @@ static inline void ww_acquire_init(struct ww_acquire_ctx *ctx,
 	ctx->acquired = 0;
 	ctx->wounded = false;
 	ctx->is_wait_die = ww_class->is_wait_die;
-#ifdef DEBUG_WW_MUTEXES
 	ctx->ww_class = ww_class;
+#ifdef DEBUG_WW_MUTEXES
 	ctx->done_acquire = 0;
 	ctx->contending_lock = NULL;
 #endif
-- 
2.51.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v13 3/7] rust: implement Class for ww_class support
  2026-10-20 15:29 [PATCH v13 0/7] rust: add ww_mutex support Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 1/7] rust: add C wrappers for ww_mutex inline functions Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 2/7] ww_mutex: add ww_class field unconditionally Onur Özkan
@ 2026-10-20 15:29 ` Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 4/7] rust: ww_mutex: add Mutex, AcquireCtx and MutexGuard Onur Özkan
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Onur Özkan @ 2026-10-20 15:29 UTC (permalink / raw)
  To: rust-for-linux
  Cc: lossin, lyude, ojeda, alex.gaynor, boqun.feng, gary, a.hindborg,
	aliceryhl, tmgross, dakr, peterz, mingo, will, longman,
	felipe_life, daniel, daniel.almeida, thomas.hellstrom,
	linux-kernel, Onur Özkan

Adds the Class type, the first step in supporting ww_mutex in Rust.
Class represents ww_class, used for deadlock avoidance for supporting
both wait-die and wound-wait semantics.

Also adds the define_ww_class and define_wd_class macros for safely
declaring static instances.

Reviewed-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Signed-off-by: Onur Özkan <work@onurozkan.dev>
---
 rust/kernel/sync/lock.rs                |   1 +
 rust/kernel/sync/lock/ww_mutex.rs       |   7 ++
 rust/kernel/sync/lock/ww_mutex/class.rs | 130 ++++++++++++++++++++++++
 3 files changed, 138 insertions(+)
 create mode 100644 rust/kernel/sync/lock/ww_mutex.rs
 create mode 100644 rust/kernel/sync/lock/ww_mutex/class.rs

diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs
index 10b6b5e9b024f..f509250822c55 100644
--- a/rust/kernel/sync/lock.rs
+++ b/rust/kernel/sync/lock.rs
@@ -15,6 +15,7 @@
 
 pub mod mutex;
 pub mod spinlock;
+pub mod ww_mutex;
 
 pub(super) mod global;
 pub use global::{GlobalGuard, GlobalLock, GlobalLockBackend, GlobalLockedBy};
diff --git a/rust/kernel/sync/lock/ww_mutex.rs b/rust/kernel/sync/lock/ww_mutex.rs
new file mode 100644
index 0000000000000..727c51cc73af8
--- /dev/null
+++ b/rust/kernel/sync/lock/ww_mutex.rs
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust abstractions for the kernel's wound-wait locking primitives.
+
+pub use class::Class;
+
+mod class;
diff --git a/rust/kernel/sync/lock/ww_mutex/class.rs b/rust/kernel/sync/lock/ww_mutex/class.rs
new file mode 100644
index 0000000000000..36c7e87d72660
--- /dev/null
+++ b/rust/kernel/sync/lock/ww_mutex/class.rs
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Provides [`Class`] to group wound/wait mutexes to be acquired together
+//! and specifies which deadlock avoidance algorithm to use (e.g., wound-wait
+//! or wait-die).
+//!
+//! The [`define_ww_class!`] and [`define_wd_class!`] macros provide safe
+//! ways to create classes.
+
+use crate::{
+    bindings,
+    prelude::*,
+    types::Opaque, //
+};
+
+/// Defines a static wound-wait [`Class`].
+///
+/// # Examples
+///
+/// ```
+/// use kernel::define_ww_class;
+///
+/// define_ww_class!(SOME_WW_CLASS);
+/// ```
+#[macro_export]
+macro_rules! define_ww_class {
+    ($name:ident) => {
+        static $name: $crate::sync::lock::ww_mutex::Class =
+            // SAFETY: This is `static`, so address is fixed and won't move.
+            unsafe {
+                $crate::sync::lock::ww_mutex::Class::new_unpinned(
+                    $crate::c_str!(::core::concat!(::core::stringify!($name), "_acquire")),
+                    $crate::c_str!(::core::concat!(::core::stringify!($name), "_mutex")),
+                    false,
+                )
+            };
+    };
+}
+
+/// Defines a static wait-die [`Class`].
+///
+/// # Examples
+///
+/// ```
+/// use kernel::define_wd_class;
+///
+/// define_wd_class!(SOME_WD_CLASS);
+/// ```
+#[macro_export]
+macro_rules! define_wd_class {
+    ($name:ident) => {
+        static $name: $crate::sync::lock::ww_mutex::Class =
+            // SAFETY: This is `static`, so address is fixed and won't move.
+            unsafe {
+                $crate::sync::lock::ww_mutex::Class::new_unpinned(
+                    $crate::c_str!(::core::concat!(::core::stringify!($name), "_acquire")),
+                    $crate::c_str!(::core::concat!(::core::stringify!($name), "_mutex")),
+                    true,
+                )
+            };
+    };
+}
+
+/// Used to group mutexes together for deadlock avoidance.
+///
+/// All mutexes that might be acquired together should use the same class.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::{
+///     define_wd_class,
+///     define_ww_class, //
+/// };
+///
+/// define_ww_class!(SOME_WW_CLASS);
+/// define_wd_class!(SOME_WD_CLASS);
+///
+/// # Ok::<(), Error>(())
+/// ```
+#[pin_data]
+#[repr(transparent)]
+pub struct Class {
+    #[pin]
+    pub(super) inner: Opaque<bindings::ww_class>,
+}
+
+impl Class {
+    /// Creates an unpinned [`Class`].
+    ///
+    /// # Safety
+    ///
+    /// Caller must guarantee that the returned value must be pinned before
+    /// its first use.
+    #[doc(hidden)]
+    pub const unsafe fn new_unpinned(
+        acquire_name: &'static CStr,
+        mutex_name: &'static CStr,
+        is_wait_die: bool,
+    ) -> Self {
+        Class {
+            inner: Opaque::new(bindings::ww_class {
+                stamp: bindings::atomic_long_t { counter: 0 },
+                acquire_name: crate::str::as_char_ptr_in_const_context(acquire_name),
+                mutex_name: crate::str::as_char_ptr_in_const_context(mutex_name),
+                is_wait_die: is_wait_die as u32,
+                ..pin_init::zeroed()
+            }),
+        }
+    }
+
+    /// Creates a [`Class`] from a raw pointer.
+    ///
+    /// This function is intended for interoperability with C code.
+    ///
+    /// # Safety
+    ///
+    /// The caller must ensure that `ptr` points to the `inner` field of
+    /// [`Class`] and that it remains valid for the lifetime `'a`.
+    #[inline]
+    pub const unsafe fn from_raw<'a>(ptr: *mut bindings::ww_class) -> &'a Self {
+        // SAFETY: By the safety contract, `ptr` is valid to construct `Class`.
+        unsafe { &*ptr.cast() }
+    }
+}
+
+// SAFETY: [`Class`] is immutable except for its atomic stamp. It's fine to share it across threads.
+unsafe impl Sync for Class {}
+// SAFETY: Doesn't hold anything thread-specific. It's safe to send to other threads.
+unsafe impl Send for Class {}
-- 
2.51.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v13 4/7] rust: ww_mutex: add Mutex, AcquireCtx and MutexGuard
  2026-10-20 15:29 [PATCH v13 0/7] rust: add ww_mutex support Onur Özkan
                   ` (2 preceding siblings ...)
  2026-10-20 15:29 ` [PATCH v13 3/7] rust: implement Class for ww_class support Onur Özkan
@ 2026-10-20 15:29 ` Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 5/7] rust: ww_mutex: add AcquireCtxBackoff Onur Özkan
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Onur Özkan @ 2026-10-20 15:29 UTC (permalink / raw)
  To: rust-for-linux
  Cc: lossin, lyude, ojeda, alex.gaynor, boqun.feng, gary, a.hindborg,
	aliceryhl, tmgross, dakr, peterz, mingo, will, longman,
	felipe_life, daniel, daniel.almeida, thomas.hellstrom,
	linux-kernel, Onur Özkan

Covers the low-level locking API (lock, try_lock and
interruptible variants) and integration with kernel bindings.

Signed-off-by: Onur Özkan <work@onurozkan.dev>
---
 rust/kernel/sync/lock/ww_mutex.rs             | 431 ++++++++++++++++++
 rust/kernel/sync/lock/ww_mutex/acquire_ctx.rs | 179 ++++++++
 2 files changed, 610 insertions(+)
 create mode 100644 rust/kernel/sync/lock/ww_mutex/acquire_ctx.rs

diff --git a/rust/kernel/sync/lock/ww_mutex.rs b/rust/kernel/sync/lock/ww_mutex.rs
index 727c51cc73af8..91eaa6cf2a189 100644
--- a/rust/kernel/sync/lock/ww_mutex.rs
+++ b/rust/kernel/sync/lock/ww_mutex.rs
@@ -1,7 +1,438 @@
 // SPDX-License-Identifier: GPL-2.0
 
 //! Rust abstractions for the kernel's wound-wait locking primitives.
+//!
+//! It is designed to avoid deadlocks when locking multiple [`Mutex`]es
+//! that belong to the same [`Class`]. Each lock acquisition uses an
+//! [`AcquireCtx`] to track ordering and ensure forward progress.
+//!
+//! See srctree/Documentation/locking/ww-mutex-design.rst for more details.
 
+use crate::{
+    bindings,
+    container_of,
+    error::to_result,
+    prelude::*,
+    types::{
+        NotThreadSafe,
+        Opaque, //
+    }, //
+};
+use core::cell::UnsafeCell;
+
+pub use acquire_ctx::AcquireCtx;
 pub use class::Class;
 
+mod acquire_ctx;
 mod class;
+
+/// A wound-wait (ww) mutex that is powered with deadlock avoidance
+/// when acquiring multiple locks of the same [`Class`].
+///
+/// Each mutex belongs to a [`Class`], which the wound-wait algorithm
+/// uses to figure out the order of acquisition and prevent deadlocks.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::{
+///     define_ww_class,
+///     sync::{
+///         lock::ww_mutex::{
+///             AcquireCtx,
+///             Class,
+///             Mutex, //
+///         },
+///         Arc,
+///     },
+/// };
+/// use pin_init::stack_pin_init;
+///
+/// define_ww_class!(SOME_WW_CLASS);
+///
+/// let mutex = Arc::pin_init(Mutex::new(42, &SOME_WW_CLASS), GFP_KERNEL)?;
+/// let ctx = KBox::pin_init(AcquireCtx::new(&SOME_WW_CLASS), GFP_KERNEL)?;
+///
+/// let guard = ctx.lock(&mutex)?;
+/// assert_eq!(*guard, 42);
+///
+/// # Ok::<(), Error>(())
+/// ```
+#[pin_data]
+#[repr(C)]
+pub struct Mutex<T: ?Sized> {
+    #[pin]
+    inner: Opaque<bindings::ww_mutex>,
+    data: UnsafeCell<T>,
+}
+
+impl<T> Mutex<T> {
+    /// Initializes [`Mutex`] with the given `data` and [`Class`].
+    #[inline]
+    pub fn new(data: T, class: &'static Class) -> impl PinInit<Self> {
+        let class_ptr = class.inner.get();
+        pin_init!(Mutex {
+            inner <- Opaque::ffi_init(|slot: *mut bindings::ww_mutex| {
+                // SAFETY: `class` is valid indefinitely.
+                unsafe { bindings::ww_mutex_init(slot, class_ptr) }
+            }),
+            data: UnsafeCell::new(data),
+        })
+    }
+}
+
+impl<T: ?Sized> Mutex<T> {
+    /// Checks if this [`Mutex`] is currently locked.
+    ///
+    /// The returned value is racy as another thread can acquire
+    /// or release the lock immediately after this call returns.
+    #[inline]
+    pub fn is_locked(&self) -> bool {
+        // SAFETY: It's safe to call `ww_mutex_is_locked` on
+        // a valid mutex.
+        unsafe { bindings::ww_mutex_is_locked(self.inner.get()) }
+    }
+
+    /// Locks this [`Mutex`] without [`AcquireCtx`].
+    #[inline]
+    pub fn lock(&self) -> Result<MutexGuard<'_, T>> {
+        lock(self, None)
+    }
+
+    /// Similar to [`Self::lock`], but can be interrupted by signals.
+    #[inline]
+    pub fn lock_interruptible(&self) -> Result<MutexGuard<'_, T>> {
+        lock_interruptible(self, None)
+    }
+
+    /// Tries to lock this [`Mutex`] with no [`AcquireCtx`] and without blocking.
+    ///
+    /// Returns `None` if the mutex could not be acquired.
+    #[must_use = "if unused, the lock will be immediately unlocked"]
+    #[inline]
+    pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
+        try_lock(self, None)
+    }
+}
+
+impl Mutex<()> {
+    /// Creates a [`Mutex`] from a raw pointer.
+    ///
+    /// This function is intended for interoperability with C code.
+    ///
+    /// # Safety
+    ///
+    /// The caller must ensure that:
+    ///
+    /// - `ptr` is a valid pointer to a `ww_mutex`.
+    /// - `ptr` must remain valid for the lifetime `'a`.
+    /// - The `ww_class` associated with this mutex must be valid indefinitely.
+    #[inline]
+    pub unsafe fn from_raw<'a>(ptr: *mut bindings::ww_mutex) -> &'a Self {
+        // SAFETY: By the safety contract, the caller guarantees that `ptr`
+        // points to a valid `ww_mutex` which is the `inner` field of `Mutex`,
+        // that it remains valid for the lifetime `'a` and the associated
+        // `ww_class` remains valid indefinitely.
+        //
+        // Because [`Mutex`] is `#[repr(C)]`, the `inner` field sits at a
+        // stable offset that `container_of!` can safely rely on.
+        unsafe { &*container_of!(Opaque::cast_from(ptr), Self, inner) }
+    }
+}
+
+// SAFETY: `Mutex` can be sent to another thread if the protected
+// data `T` can be.
+unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
+
+// SAFETY: `Mutex` serialises access to the protected data, so it is
+// `Sync` as long as the data is `Send`.
+unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
+
+/// A guard that provides exclusive access to the data protected
+/// by a [`Mutex`].
+///
+/// # Invariants
+///
+/// The guard holds an exclusive lock on the associated [`Mutex`]. The lock is held
+/// for the entire lifetime of this guard and is automatically released when the
+/// guard is dropped.
+#[must_use = "the lock unlocks immediately when the guard is unused"]
+pub struct MutexGuard<'a, T: ?Sized> {
+    mutex: &'a Mutex<T>,
+    _not_send: NotThreadSafe,
+}
+
+impl<'a, T: ?Sized> MutexGuard<'a, T> {
+    /// Creates a new guard for the given [`Mutex`].
+    #[inline]
+    fn new(mutex: &'a Mutex<T>) -> Self {
+        debug_assert!(mutex.is_locked());
+
+        // INVARIANT: Locking and `from_raw` ensure that the current task holds the mutex
+        // and transfer sole responsibility for unlocking it to this guard.
+        Self {
+            mutex,
+            _not_send: NotThreadSafe,
+        }
+    }
+}
+
+impl<'a> MutexGuard<'a, ()> {
+    /// Creates a [`MutexGuard`] from a raw pointer.
+    ///
+    /// This function is intended for interoperability with C code.
+    ///
+    /// # Safety
+    ///
+    /// The caller must ensure that:
+    ///
+    /// - `ptr` is a valid pointer to a `ww_mutex`.
+    /// - `ptr` must remain valid for the lifetime `'b`.
+    /// - The `ww_class` associated with the `ww_mutex` must be valid indefinitely.
+    /// - The current task holds the mutex.
+    /// - The caller transfers responsibility for unlocking the mutex to the returned guard
+    ///   and must not unlock it separately (including through another guard).
+    /// - Any associated acquire context remains valid until the mutex is unlocked,
+    ///   even if the returned guard is forgotten. The context must not be finalized or
+    ///   reinitialized before then.
+    #[inline]
+    pub unsafe fn from_raw<'b>(ptr: *mut bindings::ww_mutex) -> MutexGuard<'b, ()> {
+        // SAFETY: By this function's safety contract, the caller guarantees that `ptr` points to a
+        // valid `ww_mutex` which is the `inner` field of a `Mutex`. The caller also guarantees
+        // that `ptr` is valid for the lifetime `'b` and its `ww_class` is valid indefinitely.
+        let mutex = unsafe { Mutex::from_raw(ptr) };
+
+        MutexGuard::new(mutex)
+    }
+}
+
+impl<T: ?Sized> core::ops::Deref for MutexGuard<'_, T> {
+    type Target = T;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: self.mutex is locked, so we have exclusive access.
+        unsafe { &*self.mutex.data.get() }
+    }
+}
+
+impl<T: ?Sized + Unpin> core::ops::DerefMut for MutexGuard<'_, T> {
+    #[inline]
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        // SAFETY: self.mutex is locked, so we have exclusive access.
+        unsafe { &mut *self.mutex.data.get() }
+    }
+}
+
+impl<T: ?Sized> Drop for MutexGuard<'_, T> {
+    #[inline]
+    fn drop(&mut self) {
+        // SAFETY: self.mutex is locked and are about to release it.
+        unsafe { bindings::ww_mutex_unlock(self.mutex.inner.get()) };
+    }
+}
+
+// SAFETY: `MutexGuard` can be shared between threads if the data can.
+unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
+
+/// Locks a mutex with an optional acquire context.
+#[inline]
+fn lock<'a, T: ?Sized>(mutex: &'a Mutex<T>, ctx: Option<&AcquireCtx>) -> Result<MutexGuard<'a, T>> {
+    let ctx_ptr = match ctx {
+        Some(ctx) => {
+            ctx.validate_mutex(mutex)?;
+            ctx.inner.get()
+        }
+        None => core::ptr::null_mut(),
+    };
+
+    // SAFETY: `mutex` is pinned and valid. `ctx_ptr` is null or refers to a pinned context of
+    // the same class. Callers keep the context valid until the mutex is unlocked.
+    let ret = unsafe { bindings::ww_mutex_lock(mutex.inner.get(), ctx_ptr) };
+    to_result(ret)?;
+
+    Ok(MutexGuard::new(mutex))
+}
+
+/// Locks a mutex with an optional acquire context, allowing interruption by signals.
+#[inline]
+fn lock_interruptible<'a, T: ?Sized>(
+    mutex: &'a Mutex<T>,
+    ctx: Option<&AcquireCtx>,
+) -> Result<MutexGuard<'a, T>> {
+    let ctx_ptr = match ctx {
+        Some(ctx) => {
+            ctx.validate_mutex(mutex)?;
+            ctx.inner.get()
+        }
+        None => core::ptr::null_mut(),
+    };
+
+    // SAFETY: `mutex` is pinned and valid. `ctx_ptr` is null or refers to a pinned context of
+    // the same class. Callers keep the context valid until the mutex is unlocked.
+    let ret = unsafe { bindings::ww_mutex_lock_interruptible(mutex.inner.get(), ctx_ptr) };
+    to_result(ret)?;
+
+    Ok(MutexGuard::new(mutex))
+}
+
+/// Tries to lock a mutex with an optional acquire context without blocking.
+#[inline]
+fn try_lock<'a, T: ?Sized>(
+    mutex: &'a Mutex<T>,
+    ctx: Option<&AcquireCtx>,
+) -> Option<MutexGuard<'a, T>> {
+    let ctx_ptr = match ctx {
+        Some(ctx) => {
+            ctx.validate_mutex(mutex).ok()?;
+            ctx.inner.get()
+        }
+        None => core::ptr::null_mut(),
+    };
+
+    // SAFETY: `mutex` is pinned and valid. `ctx_ptr` is null or refers to a pinned context of
+    // the same class. Callers keep the context valid until the mutex is unlocked.
+    let ret = unsafe { bindings::ww_mutex_trylock(mutex.inner.get(), ctx_ptr) };
+
+    if ret != 0 {
+        Some(MutexGuard::new(mutex))
+    } else {
+        None
+    }
+}
+
+#[kunit_tests(rust_kernel_ww_mutex)]
+mod tests {
+    use super::*;
+    use crate::{
+        define_wd_class,
+        define_ww_class,
+        sync::Arc, //
+    };
+
+    define_ww_class!(TEST_WOUND_WAIT_CLASS);
+    define_wd_class!(TEST_WAIT_DIE_CLASS);
+
+    #[test]
+    fn test_ww_mutex_basic_lock_unlock() -> Result {
+        let mutex = Arc::pin_init(Mutex::new(42, &TEST_WOUND_WAIT_CLASS), GFP_KERNEL)?;
+        let ctx = KBox::pin_init(AcquireCtx::new(&TEST_WOUND_WAIT_CLASS), GFP_KERNEL)?;
+
+        let guard = ctx.lock(&mutex)?;
+        assert_eq!(*guard, 42);
+
+        // Drop the lock.
+        drop(guard);
+
+        let mut guard = ctx.lock(&mutex)?;
+        *guard = 100;
+        assert_eq!(*guard, 100);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_ww_mutex_trylock() -> Result {
+        let mutex = Arc::pin_init(Mutex::new(123, &TEST_WAIT_DIE_CLASS), GFP_KERNEL)?;
+        let ctx = KBox::pin_init(AcquireCtx::new(&TEST_WAIT_DIE_CLASS), GFP_KERNEL)?;
+
+        // `try_lock` on unlocked mutex should succeed.
+        let guard = ctx.try_lock(&mutex).ok_or(EBUSY)?;
+        assert_eq!(*guard, 123);
+
+        // Now it should fail immediately as it's already locked.
+        assert!(ctx.try_lock(&mutex).is_none());
+
+        drop(guard);
+        assert!(ctx.try_lock(&mutex).is_some());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_ww_mutex_class_mismatch() -> Result {
+        let mutex = Arc::pin_init(Mutex::new(123, &TEST_WOUND_WAIT_CLASS), GFP_KERNEL)?;
+        let ctx = KBox::pin_init(AcquireCtx::new(&TEST_WAIT_DIE_CLASS), GFP_KERNEL)?;
+
+        assert_eq!(ctx.lock(&mutex).err(), Some(EINVAL));
+        assert_eq!(ctx.lock_interruptible(&mutex).err(), Some(EINVAL));
+        assert!(ctx.try_lock(&mutex).is_none());
+        assert!(!mutex.is_locked());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_ww_mutex_is_locked() -> Result {
+        let mutex = Arc::pin_init(Mutex::new("hello", &TEST_WOUND_WAIT_CLASS), GFP_KERNEL)?;
+        let ctx = KBox::pin_init(AcquireCtx::new(&TEST_WOUND_WAIT_CLASS), GFP_KERNEL)?;
+
+        // Should not be locked initially.
+        assert!(!mutex.is_locked());
+
+        let guard = ctx.lock(&mutex)?;
+        assert!(mutex.is_locked());
+
+        drop(guard);
+        assert!(!mutex.is_locked());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_ww_acquire_context_done() -> Result {
+        let mutex1 = Arc::pin_init(Mutex::new(1, &TEST_WAIT_DIE_CLASS), GFP_KERNEL)?;
+        let mutex2 = Arc::pin_init(Mutex::new(2, &TEST_WAIT_DIE_CLASS), GFP_KERNEL)?;
+        let ctx = KBox::pin_init(AcquireCtx::new(&TEST_WAIT_DIE_CLASS), GFP_KERNEL)?;
+
+        // Acquire multiple mutexes with the same context.
+        let guard1 = ctx.lock(&mutex1)?;
+        let guard2 = ctx.lock(&mutex2)?;
+
+        assert_eq!(*guard1, 1);
+        assert_eq!(*guard2, 2);
+
+        ctx.done();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_mutex_without_ctx() -> Result {
+        let mutex = Arc::pin_init(Mutex::new(100, &TEST_WOUND_WAIT_CLASS), GFP_KERNEL)?;
+        let guard = mutex.lock()?;
+
+        assert_eq!(*guard, 100);
+        assert!(mutex.is_locked());
+        assert!(mutex.try_lock().is_none());
+
+        drop(guard);
+        assert!(!mutex.is_locked());
+
+        let guard = mutex.try_lock().ok_or(EBUSY)?;
+        assert_eq!(*guard, 100);
+        drop(guard);
+        assert!(!mutex.is_locked());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_guard_from_raw_unlocks_mutex() -> Result {
+        let mutex = Arc::pin_init(Mutex::new((), &TEST_WOUND_WAIT_CLASS), GFP_KERNEL)?;
+        let guard = mutex.lock()?;
+
+        // Keep the mutex locked without leaving another guard responsible for unlocking it.
+        core::mem::forget(guard);
+
+        // SAFETY: `mutex` and its class remain valid for this test. The current task holds
+        // the mutex, and forgetting the previous guard lets this guard take over unlocking it.
+        let guard = unsafe { MutexGuard::from_raw(mutex.inner.get()) };
+        assert!(mutex.is_locked());
+
+        drop(guard);
+        assert!(!mutex.is_locked());
+
+        Ok(())
+    }
+}
diff --git a/rust/kernel/sync/lock/ww_mutex/acquire_ctx.rs b/rust/kernel/sync/lock/ww_mutex/acquire_ctx.rs
new file mode 100644
index 0000000000000..777d177f985ff
--- /dev/null
+++ b/rust/kernel/sync/lock/ww_mutex/acquire_ctx.rs
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Provides [`AcquireCtx`] for managing multiple wound/wait
+//! mutexes from the same [`Class`].
+
+use super::{
+    lock,
+    lock_interruptible,
+    try_lock,
+    Class,
+    Mutex,
+    MutexGuard, //
+};
+use crate::{
+    bindings,
+    prelude::*,
+    types::Opaque, //
+};
+
+/// Groups multiple [`Mutex`]es for deadlock avoidance when acquired
+/// with the same [`Class`].
+///
+/// Dropping the context panics if any mutex acquired with it is still locked.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::{
+///     define_ww_class,
+///     sync::{
+///         lock::ww_mutex::{
+///             AcquireCtx,
+///             Class,
+///             Mutex, //
+///         },
+///         Arc,
+///     },
+/// };
+/// use pin_init::stack_pin_init;
+///
+/// define_ww_class!(SOME_WW_CLASS);
+///
+/// // Create mutexes.
+/// let mutex1 = Arc::pin_init(Mutex::new(1, &SOME_WW_CLASS), GFP_KERNEL)?;
+/// let mutex2 = Arc::pin_init(Mutex::new(2, &SOME_WW_CLASS), GFP_KERNEL)?;
+///
+/// // Create acquire context for deadlock avoidance.
+/// let ctx = KBox::pin_init(AcquireCtx::new(&SOME_WW_CLASS), GFP_KERNEL)?;
+///
+/// let guard1 = ctx.lock(&mutex1)?;
+/// let guard2 = ctx.lock(&mutex2)?;
+///
+/// // Mark acquisition phase as complete.
+/// ctx.done();
+///
+/// # Ok::<(), Error>(())
+/// ```
+#[pin_data(PinnedDrop)]
+#[repr(transparent)]
+pub struct AcquireCtx {
+    #[pin]
+    pub(super) inner: Opaque<bindings::ww_acquire_ctx>,
+}
+
+impl AcquireCtx {
+    /// Initializes a new [`AcquireCtx`] with the given [`Class`].
+    #[inline]
+    pub fn new(class: &'static Class) -> impl PinInit<Self> {
+        let class_ptr = class.inner.get();
+        pin_init!(AcquireCtx {
+            inner <- Opaque::ffi_init(|slot: *mut bindings::ww_acquire_ctx| {
+                // SAFETY: `class` is valid indefinitely.
+                unsafe { bindings::ww_acquire_init(slot, class_ptr) }
+            }),
+        })
+    }
+
+    /// Creates a [`AcquireCtx`] from a raw pointer.
+    ///
+    /// This function is intended for interoperability with C code.
+    ///
+    /// # Safety
+    ///
+    /// The caller must ensure that `ptr` points to the `inner` field of an initialized
+    /// [`AcquireCtx`] belonging to the current task. The context must remain pinned
+    /// and valid for the lifetime `'a` and until all mutexes acquired with it are
+    /// unlocked, even if their guards are forgotten. It must not be finalized or
+    /// reinitialized during that time.
+    /// The associated `ww_class` must be valid indefinitely.
+    #[inline]
+    pub unsafe fn from_raw<'a>(ptr: *mut bindings::ww_acquire_ctx) -> &'a Self {
+        // SAFETY: By the safety contract, `ptr` is valid to construct `AcquireCtx`.
+        unsafe { &*ptr.cast() }
+    }
+
+    /// Marks the end of the acquire phase.
+    ///
+    /// Calling this function is optional. It is just useful to document
+    /// the code and clearly designated the acquire phase from actually
+    /// using the locked data structures.
+    ///
+    /// After calling this function, no more mutexes can be acquired with
+    /// this context.
+    #[inline]
+    pub fn done(&self) {
+        // SAFETY: `self.inner` contains a valid, initialized acquire context.
+        unsafe { bindings::ww_acquire_done(self.inner.get()) };
+    }
+
+    /// Locks the given [`Mutex`] on this [`AcquireCtx`].
+    #[inline]
+    pub fn lock<'a, T>(&'a self, mutex: &'a Mutex<T>) -> Result<MutexGuard<'a, T>> {
+        lock(mutex, Some(self))
+    }
+
+    /// Similar to [`Self::lock`], but can be interrupted by signals.
+    #[inline]
+    pub fn lock_interruptible<'a, T>(&'a self, mutex: &'a Mutex<T>) -> Result<MutexGuard<'a, T>> {
+        lock_interruptible(mutex, Some(self))
+    }
+
+    /// Tries to lock the [`Mutex`] on this [`AcquireCtx`] without blocking.
+    ///
+    /// Unlike [`Self::lock`], no deadlock handling is performed.
+    ///
+    /// Returns `None` if the mutex belongs to a different [`Class`] or could not
+    /// be acquired.
+    #[must_use = "if unused, the lock will be immediately unlocked"]
+    #[inline]
+    pub fn try_lock<'a, T>(&'a self, mutex: &'a Mutex<T>) -> Option<MutexGuard<'a, T>> {
+        try_lock(mutex, Some(self))
+    }
+
+    /// Checks the mutex's class and the acquired-lock count.
+    ///
+    /// Returns [`EINVAL`] if the [`Mutex`] has a different [`Class`] or
+    /// [`EOVERFLOW`] if the acquired-lock count cannot be incremented.
+    #[inline]
+    pub(super) fn validate_mutex<T: ?Sized>(&self, mutex: &Mutex<T>) -> Result {
+        let ctx_ptr = self.inner.get();
+        let mutex_ptr = mutex.inner.get();
+
+        // SAFETY: `ctx_ptr` is a valid pointer for the entire lifetime of `self`.
+        let ctx_class = unsafe { (*ctx_ptr).ww_class };
+
+        // SAFETY: `mutex_ptr` is a valid pointer for the entire lifetime of `mutex`.
+        let mutex_class = unsafe { (*mutex_ptr).ww_class };
+
+        // `self` and `mutex` must use the same class.
+        if ctx_class != mutex_class {
+            return Err(EINVAL);
+        }
+
+        // Check for overflow of the count checked by `AcquireCtx`'s destructor.
+        // SAFETY: `ctx_ptr` points to a valid context owned by the current task.
+        if unsafe { (*ctx_ptr).acquired } == u32::MAX {
+            return Err(EOVERFLOW);
+        }
+
+        Ok(())
+    }
+}
+
+#[pinned_drop]
+impl PinnedDrop for AcquireCtx {
+    #[inline]
+    fn drop(self: Pin<&mut Self>) {
+        let ptr = self.inner.get();
+        // SAFETY: `ptr` points to this initialized context, owned by the current task.
+        let acquired = unsafe { (*ptr).acquired };
+        assert_eq!(
+            acquired, 0,
+            "ww acquire context dropped with locked mutexes"
+        );
+
+        // SAFETY: The assertion above ensures that all acquired locks were released.
+        unsafe { bindings::ww_acquire_fini(ptr) };
+    }
+}
-- 
2.51.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v13 5/7] rust: ww_mutex: add AcquireCtxBackoff
  2026-10-20 15:29 [PATCH v13 0/7] rust: add ww_mutex support Onur Özkan
                   ` (3 preceding siblings ...)
  2026-10-20 15:29 ` [PATCH v13 4/7] rust: ww_mutex: add Mutex, AcquireCtx and MutexGuard Onur Özkan
@ 2026-10-20 15:29 ` Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 6/7] rust: ww_mutex: implement DynLockSet Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 7/7] MAINTAINERS: add Onur Özkan as WW MUTEX maintainer Onur Özkan
  6 siblings, 0 replies; 8+ messages in thread
From: Onur Özkan @ 2026-10-20 15:29 UTC (permalink / raw)
  To: rust-for-linux
  Cc: lossin, lyude, ojeda, alex.gaynor, boqun.feng, gary, a.hindborg,
	aliceryhl, tmgross, dakr, peterz, mingo, will, longman,
	felipe_life, daniel, daniel.almeida, thomas.hellstrom,
	linux-kernel, Onur Özkan

AcquireCtxBackoff wraps AcquireCtx to remember the contended mutex.
The ww_transaction helper provides deadlock retries with ordinary
guards.

After the caller releases its guards, backoff waits for the contended
mutex. Retries keep the same acquire context and ticket.

Signed-off-by: Onur Özkan <work@onurozkan.dev>
---
 rust/kernel/sync/lock/ww_mutex.rs             |   7 +
 .../sync/lock/ww_mutex/acquire_ctx_backoff.rs | 283 ++++++++++++++++++
 2 files changed, 290 insertions(+)
 create mode 100644 rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs

diff --git a/rust/kernel/sync/lock/ww_mutex.rs b/rust/kernel/sync/lock/ww_mutex.rs
index 91eaa6cf2a189..2249214959b1c 100644
--- a/rust/kernel/sync/lock/ww_mutex.rs
+++ b/rust/kernel/sync/lock/ww_mutex.rs
@@ -6,6 +6,8 @@
 //! that belong to the same [`Class`]. Each lock acquisition uses an
 //! [`AcquireCtx`] to track ordering and ensure forward progress.
 //!
+//! [`ww_transaction`] provides automatic deadlock recovery for ordinary guards.
+//!
 //! See srctree/Documentation/locking/ww-mutex-design.rst for more details.
 
 use crate::{
@@ -21,9 +23,14 @@
 use core::cell::UnsafeCell;
 
 pub use acquire_ctx::AcquireCtx;
+pub use acquire_ctx_backoff::{
+    ww_transaction,
+    AcquireCtxBackoff, //
+};
 pub use class::Class;
 
 mod acquire_ctx;
+mod acquire_ctx_backoff;
 mod class;
 
 /// A wound-wait (ww) mutex that is powered with deadlock avoidance
diff --git a/rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs b/rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs
new file mode 100644
index 0000000000000..71ad1cba37192
--- /dev/null
+++ b/rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Contended-lock backoff and transaction helpers for wound/wait mutexes.
+
+use super::{
+    AcquireCtx,
+    Class,
+    Mutex,
+    MutexGuard, //
+};
+use crate::prelude::*;
+use core::cell::Cell;
+
+/// An [`AcquireCtx`] that remembers the contended mutex.
+///
+/// After [`Self::lock`] returns [`EDEADLK`], release all guards and call
+/// [`Self::backoff`] before retrying. [`ww_transaction`] handles this retry loop.
+///
+/// Mutexes must outlive this wrapper so they remain valid for retry.
+///
+/// # Invariants
+///
+/// `backoff_guard`, when present, is the sole guard for a mutex locked with `ctx`.
+/// It is released before the context borrow ends. If this wrapper is forgotten,
+/// `AcquireCtx`'s destructor checks for remaining locks.
+pub struct AcquireCtxBackoff<'ctx, 'a> {
+    ctx: Pin<&'ctx mut AcquireCtx>,
+    // The cells also make `'a` invariant: recorded mutexes must outlive retries.
+    contended: Cell<Option<&'a Mutex<()>>>,
+    backoff_guard: Cell<Option<MutexGuard<'a, ()>>>,
+}
+
+impl<'ctx, 'a> AcquireCtxBackoff<'ctx, 'a> {
+    /// Creates a wrapper for an existing [`AcquireCtx`].
+    ///
+    /// Preserves the context's state and ticket.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the context still holds any locks, including forgotten guards.
+    #[inline]
+    pub fn new(ctx: Pin<&'ctx mut AcquireCtx>) -> Self {
+        // INVARIANT: There is no acquired backoff lock yet.
+        let this = Self {
+            ctx,
+            contended: Cell::new(None),
+            backoff_guard: Cell::new(None),
+        };
+        this.assert_no_locks_held();
+        this
+    }
+
+    /// Locks `mutex`, remembering it if the acquisition returns [`EDEADLK`].
+    ///
+    /// Further acquisitions return [`EDEADLK`] until [`Self::backoff`] succeeds.
+    /// A mutex already acquired by the caller returns [`EALREADY`].
+    pub fn lock<'b, T>(&'b self, mutex: &'a Mutex<T>) -> Result<MutexGuard<'b, T>> {
+        if self.contended.get().is_some() {
+            return Err(EDEADLK);
+        }
+
+        if let Some(guard) = self.backoff_guard.take() {
+            if guard.mutex.inner.get() == mutex.inner.get() {
+                // Transfer ownership of the backoff lock.
+                core::mem::forget(guard);
+                return Ok(MutexGuard::new(mutex));
+            }
+            self.backoff_guard.set(Some(guard));
+        }
+
+        match self.ctx.lock(mutex) {
+            Err(e) if e == EDEADLK => {
+                // SAFETY: `mutex` is initialized, remains valid for `'a`, and
+                // its class is static. Only the C mutex is accessed through this view.
+                let mutex = unsafe { Mutex::from_raw(mutex.inner.get()) };
+                self.contended.set(Some(mutex));
+                Err(e)
+            }
+            result => result,
+        }
+    }
+
+    /// Acquires the contended mutex after the caller's guards are released.
+    ///
+    /// The next [`Self::lock`] for this mutex returns its guard without locking
+    /// it again. The context and its ticket are preserved.
+    /// Returns [`EDEADLK`] if no failed acquisition was recorded.
+    ///
+    /// # Panics
+    ///
+    /// Panics if any caller-owned locks are still held, including forgotten guards.
+    pub fn backoff(&self) -> Result {
+        drop(self.backoff_guard.take());
+        self.assert_no_locks_held();
+        let mutex = self.contended.take().ok_or(EDEADLK)?;
+
+        // With no locks held, regular locking waits for this mutex to become available.
+        // INVARIANT: The guard stays in this wrapper until transferred or dropped,
+        // and `Drop` releases it before the borrow of the context ends.
+        let guard = super::lock(mutex, Some(&self.ctx))?;
+        self.backoff_guard.set(Some(guard));
+        Ok(())
+    }
+
+    /// Marks acquisition complete; no more locks may be acquired with this context.
+    #[inline]
+    pub fn done(&self) {
+        self.ctx.done();
+    }
+
+    #[inline]
+    fn assert_no_locks_held(&self) {
+        // SAFETY: This initialized context belongs to the current task.
+        let acquired = unsafe { (*self.ctx.inner.get()).acquired };
+        assert_eq!(acquired, 0, "ww transaction still holds caller-owned locks");
+    }
+}
+
+impl Drop for AcquireCtxBackoff<'_, '_> {
+    #[inline]
+    fn drop(&mut self) {
+        // Release the backoff lock before ending the context borrow.
+        drop(self.backoff_guard.take());
+    }
+}
+
+/// Runs a locking algorithm, backing off and retrying on [`EDEADLK`].
+///
+/// The callback must immediately propagate acquisition errors and drop its guards
+/// on return. It may use the protected data once all needed locks are acquired.
+/// The context is stack allocated, and retries keep the same ticket. Mutexes must
+/// outlive the transaction. Do not nest transactions on the same context.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::{
+///     define_ww_class,
+///     prelude::*,
+///     sync::lock::ww_mutex::{
+///         ww_transaction,
+///         Mutex, //
+///     }, //
+/// };
+/// use pin_init::stack_pin_init;
+///
+/// define_ww_class!(CLASS);
+/// stack_pin_init!(let first = Mutex::new(1, &CLASS));
+/// stack_pin_init!(let second = Mutex::new(2, &CLASS));
+/// ww_transaction(&CLASS, |ctx| {
+///     let mut first = ctx.lock(&first)?;
+///     let mut second = ctx.lock(&second)?;
+///     core::mem::swap(&mut *first, &mut *second);
+///     Ok(())
+/// })?;
+/// # Ok::<(), Error>(())
+/// ```
+pub fn ww_transaction<'a, R>(
+    class: &'static Class,
+    mut transaction: impl FnMut(&AcquireCtxBackoff<'_, 'a>) -> Result<R>,
+) -> Result<R> {
+    pin_init::stack_pin_init!(let acquire_ctx = AcquireCtx::new(class));
+    let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+    retry(&ctx, || transaction(&ctx))
+}
+
+fn retry<R>(
+    ctx: &AcquireCtxBackoff<'_, '_>,
+    mut transaction: impl FnMut() -> Result<R>,
+) -> Result<R> {
+    loop {
+        match transaction() {
+            Err(e) if e == EDEADLK => ctx.backoff()?,
+            result => return result,
+        }
+    }
+}
+
+#[kunit_tests(rust_kernel_ww_transaction)]
+mod tests {
+    use super::*;
+    use crate::define_ww_class;
+    use pin_init::stack_pin_init;
+
+    define_ww_class!(TEST_TRANSACTION_CLASS);
+
+    #[test]
+    fn test_transaction_retry() -> Result {
+        stack_pin_init!(let first = Mutex::new(10, &TEST_TRANSACTION_CLASS));
+        stack_pin_init!(let second = Mutex::new(20, &TEST_TRANSACTION_CLASS));
+        let mut attempts = 0;
+        let mut identity = None;
+
+        let values = ww_transaction(&TEST_TRANSACTION_CLASS, |ctx| {
+            attempts += 1;
+            let ptr = ctx.ctx.inner.get();
+            // SAFETY: The transaction owns this initialized context throughout
+            // the callback, and this task is its only user.
+            let stamp = unsafe { (*ptr).stamp };
+
+            if attempts == 1 {
+                identity = Some((ptr, stamp));
+                let _guard = ctx.lock(&first)?;
+                // Simulate a failed acquisition with its contended mutex recorded.
+                // SAFETY: `second` is initialized, pinned, and outlives the transaction.
+                let mutex = unsafe { Mutex::from_raw(second.inner.get()) };
+                ctx.contended.set(Some(mutex));
+                return Err(EDEADLK);
+            }
+
+            assert_eq!(attempts, 2);
+            assert_eq!(identity, Some((ptr, stamp)));
+            assert!(!first.is_locked());
+            assert!(second.is_locked());
+
+            // Request the contended mutex after acquiring another lock.
+            let mut a = ctx.lock(&first)?;
+            let mut b = ctx.lock(&second)?;
+            assert_eq!(ctx.lock(&second).err(), Some(EALREADY));
+            core::mem::swap(&mut *a, &mut *b);
+            Ok((*a, *b))
+        })?;
+
+        assert_eq!(attempts, 2);
+        assert_eq!(values, (20, 10));
+        assert!(!first.is_locked());
+        assert!(!second.is_locked());
+        Ok(())
+    }
+
+    #[test]
+    fn test_backoff_lock_cleanup() -> Result {
+        stack_pin_init!(let first = Mutex::new(1, &TEST_TRANSACTION_CLASS));
+        stack_pin_init!(let contended = Mutex::new(2, &TEST_TRANSACTION_CLASS));
+
+        // Release the unclaimed backoff lock on both success and error.
+        for fail in [false, true] {
+            let mut attempts = 0;
+            let result = ww_transaction(&TEST_TRANSACTION_CLASS, |ctx| {
+                attempts += 1;
+                let _guard = ctx.lock(&first)?;
+                if attempts == 1 {
+                    // SAFETY: `contended` is initialized, pinned, and outlives the transaction.
+                    let mutex = unsafe { Mutex::from_raw(contended.inner.get()) };
+                    ctx.contended.set(Some(mutex));
+                    return Err(EDEADLK);
+                }
+
+                assert_eq!(attempts, 2);
+                assert!(contended.is_locked());
+                if fail {
+                    Err(EINVAL)
+                } else {
+                    Ok(())
+                }
+            });
+
+            assert_eq!(result, if fail { Err(EINVAL) } else { Ok(()) });
+            assert_eq!(attempts, 2);
+            assert!(!first.is_locked());
+            assert!(!contended.is_locked());
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_deadlock_without_contention() -> Result {
+        stack_pin_init!(let mutex = Mutex::new(5, &TEST_TRANSACTION_CLASS));
+        let mut attempts = 0;
+
+        let result: Result = ww_transaction(&TEST_TRANSACTION_CLASS, |ctx| {
+            attempts += 1;
+            let _guard = ctx.lock(&mutex)?;
+            // A callback-generated EDEADLK cannot identify a lock to wait for.
+            Err(if attempts == 1 { EDEADLK } else { EINVAL })
+        });
+
+        assert_eq!(result, Err(EDEADLK));
+        assert_eq!(attempts, 1);
+        assert!(!mutex.is_locked());
+        Ok(())
+    }
+}
-- 
2.51.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v13 6/7] rust: ww_mutex: implement DynLockSet
  2026-10-20 15:29 [PATCH v13 0/7] rust: add ww_mutex support Onur Özkan
                   ` (4 preceding siblings ...)
  2026-10-20 15:29 ` [PATCH v13 5/7] rust: ww_mutex: add AcquireCtxBackoff Onur Özkan
@ 2026-10-20 15:29 ` Onur Özkan
  2026-10-20 15:29 ` [PATCH v13 7/7] MAINTAINERS: add Onur Özkan as WW MUTEX maintainer Onur Özkan
  6 siblings, 0 replies; 8+ messages in thread
From: Onur Özkan @ 2026-10-20 15:29 UTC (permalink / raw)
  To: rust-for-linux
  Cc: lossin, lyude, ojeda, alex.gaynor, boqun.feng, gary, a.hindborg,
	aliceryhl, tmgross, dakr, peterz, mingo, will, longman,
	felipe_life, daniel, daniel.almeida, thomas.hellstrom,
	linux-kernel, Onur Özkan

DynLockSet is a high-level and safe API built on top of ww_mutex
for dynamically discovered locks. It keeps acquired locks held,
handles repeated objects and provides access to their data.

When EDEADLK is hit it drops all held locks and waits for the
contended mutex. It then retries the given (by the user)
locking algorithm with that mutex held and the same acquire
context until it succeeds.

Signed-off-by: Onur Özkan <work@onurozkan.dev>
---
 rust/kernel/sync/lock/ww_mutex.rs             |   4 +
 .../sync/lock/ww_mutex/acquire_ctx_backoff.rs | 138 ++++++-
 .../kernel/sync/lock/ww_mutex/dyn_lock_set.rs | 351 ++++++++++++++++++
 3 files changed, 489 insertions(+), 4 deletions(-)
 create mode 100644 rust/kernel/sync/lock/ww_mutex/dyn_lock_set.rs

diff --git a/rust/kernel/sync/lock/ww_mutex.rs b/rust/kernel/sync/lock/ww_mutex.rs
index 2249214959b1c..fea2603d1bd1f 100644
--- a/rust/kernel/sync/lock/ww_mutex.rs
+++ b/rust/kernel/sync/lock/ww_mutex.rs
@@ -7,6 +7,8 @@
 //! [`AcquireCtx`] to track ordering and ensure forward progress.
 //!
 //! [`ww_transaction`] provides automatic deadlock recovery for ordinary guards.
+//! Use [`DynLockSet::ww_transaction`] for dynamically discovered locks and
+//! separate acquisition and work callbacks.
 //!
 //! See srctree/Documentation/locking/ww-mutex-design.rst for more details.
 
@@ -28,10 +30,12 @@
     AcquireCtxBackoff, //
 };
 pub use class::Class;
+pub use dyn_lock_set::DynLockSet;
 
 mod acquire_ctx;
 mod acquire_ctx_backoff;
 mod class;
+mod dyn_lock_set;
 
 /// A wound-wait (ww) mutex that is powered with deadlock avoidance
 /// when acquiring multiple locks of the same [`Class`].
diff --git a/rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs b/rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs
index 71ad1cba37192..c6d213f74635f 100644
--- a/rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs
+++ b/rust/kernel/sync/lock/ww_mutex/acquire_ctx_backoff.rs
@@ -8,13 +8,17 @@
     Mutex,
     MutexGuard, //
 };
-use crate::prelude::*;
+use crate::{
+    bindings,
+    prelude::*, //
+};
 use core::cell::Cell;
 
 /// An [`AcquireCtx`] that remembers the contended mutex.
 ///
 /// After [`Self::lock`] returns [`EDEADLK`], release all guards and call
-/// [`Self::backoff`] before retrying. [`ww_transaction`] handles this retry loop.
+/// [`Self::backoff`] before retrying. [`ww_transaction`] and
+/// [`super::DynLockSet::ww_transaction`] handle this retry loop.
 ///
 /// Mutexes must outlive this wrapper so they remain valid for retry.
 ///
@@ -109,11 +113,36 @@ pub fn done(&self) {
     }
 
     #[inline]
-    fn assert_no_locks_held(&self) {
+    pub(super) fn take_contended_guard(&self) -> Option<MutexGuard<'_, ()>> {
+        self.backoff_guard.take()
+    }
+
+    #[inline]
+    pub(super) fn assert_no_locks_held(&self) {
         // SAFETY: This initialized context belongs to the current task.
         let acquired = unsafe { (*self.ctx.inner.get()).acquired };
         assert_eq!(acquired, 0, "ww transaction still holds caller-owned locks");
     }
+
+    #[inline]
+    pub(super) fn clear_backoff(&self) {
+        drop(self.backoff_guard.take());
+        self.contended.set(None);
+    }
+
+    pub(super) fn restart(&self) {
+        self.clear_backoff();
+        self.assert_no_locks_held();
+        let ptr = self.ctx.inner.get();
+        // SAFETY: This wrapper exclusively borrows the initialized, pinned context.
+        // No acquired locks remain, so it can be finalized and reinitialized with
+        // its static class.
+        unsafe {
+            let class = (*ptr).ww_class;
+            bindings::ww_acquire_fini(ptr);
+            bindings::ww_acquire_init(ptr, class);
+        }
+    }
 }
 
 impl Drop for AcquireCtxBackoff<'_, '_> {
@@ -164,7 +193,7 @@ pub fn ww_transaction<'a, R>(
     retry(&ctx, || transaction(&ctx))
 }
 
-fn retry<R>(
+pub(super) fn retry<R>(
     ctx: &AcquireCtxBackoff<'_, '_>,
     mut transaction: impl FnMut() -> Result<R>,
 ) -> Result<R> {
@@ -178,6 +207,7 @@ fn retry<R>(
 
 #[kunit_tests(rust_kernel_ww_transaction)]
 mod tests {
+    use super::super::DynLockSet;
     use super::*;
     use crate::define_ww_class;
     use pin_init::stack_pin_init;
@@ -280,4 +310,104 @@ fn test_deadlock_without_contention() -> Result {
         assert!(!mutex.is_locked());
         Ok(())
     }
+
+    #[test]
+    fn test_dynamic_retry() -> Result {
+        stack_pin_init!(let first = Mutex::new(10, &TEST_TRANSACTION_CLASS));
+        stack_pin_init!(let second = Mutex::new(20, &TEST_TRANSACTION_CLASS));
+        stack_pin_init!(let acquire_ctx = AcquireCtx::new(&TEST_TRANSACTION_CLASS));
+        let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+        let mut set = DynLockSet::new(&ctx);
+        let mut attempts = 0;
+        let mut identity = None;
+
+        let result = set.ww_transaction(
+            |set| {
+                attempts += 1;
+                let ptr = ctx.ctx.inner.get();
+                // SAFETY: The pinned context is initialized and used only by
+                // this task throughout the transaction.
+                let stamp = unsafe { (*ptr).stamp };
+                if attempts == 1 {
+                    identity = Some((ptr, stamp));
+                    set.lock(&first)?;
+                    // SAFETY: `second` is initialized, pinned, and outlives the transaction.
+                    let mutex = unsafe { Mutex::from_raw(second.inner.get()) };
+                    ctx.contended.set(Some(mutex));
+                    return Err(EDEADLK);
+                }
+
+                assert_eq!(attempts, 2);
+                assert_eq!(identity, Some((ptr, stamp)));
+                assert!(!first.is_locked());
+                // Backoff makes the contended mutex available before lock().
+                assert_eq!(set.with_locked(&second, |v| *v)?, 20);
+                set.lock(&second)?;
+                set.lock(&first)
+            },
+            |set| {
+                set.with_locked(&first, |v| *v += 1)?;
+                set.with_locked(&second, |v| {
+                    *v += 2;
+                    *v
+                })
+            },
+        )?;
+
+        assert_eq!(result, 22);
+        assert_eq!(attempts, 2);
+        assert!(!first.is_locked());
+        assert!(!second.is_locked());
+        Ok(())
+    }
+
+    #[test]
+    fn test_dynamic_error_cleanup() -> Result {
+        stack_pin_init!(let first = Mutex::new(1, &TEST_TRANSACTION_CLASS));
+        stack_pin_init!(let contended = Mutex::new(2, &TEST_TRANSACTION_CLASS));
+        stack_pin_init!(let acquire_ctx = AcquireCtx::new(&TEST_TRANSACTION_CLASS));
+        let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+        let mut set = DynLockSet::new(&ctx);
+
+        for swallow in [false, true] {
+            let mut work_ran = false;
+            let result = set.ww_transaction(
+                |set| {
+                    set.lock(&first)?;
+                    // A swallowed or mapped EDEADLK must not poison the next call.
+                    // SAFETY: `contended` is initialized, pinned, and outlives the transaction.
+                    let mutex = unsafe { Mutex::from_raw(contended.inner.get()) };
+                    ctx.contended.set(Some(mutex));
+                    assert_eq!(set.lock(&first), Err(EDEADLK));
+                    if swallow {
+                        Ok(())
+                    } else {
+                        Err(EINVAL)
+                    }
+                },
+                |set| {
+                    work_ran = true;
+                    ctx.done();
+                    set.with_locked(&first, |v| *v += 1)
+                },
+            );
+
+            assert_eq!(result, if swallow { Ok(()) } else { Err(EINVAL) });
+            assert_eq!(work_ran, swallow);
+            assert!(ctx.contended.get().is_none());
+            assert!(!first.is_locked());
+            assert!(!contended.is_locked());
+
+            set.ww_transaction(
+                |set| {
+                    set.lock(&contended)?;
+                    set.lock(&first)
+                },
+                |set| set.with_locked(&contended, |v| *v += 1),
+            )?;
+            assert!(!first.is_locked());
+            assert!(!contended.is_locked());
+        }
+        Ok(())
+    }
 }
diff --git a/rust/kernel/sync/lock/ww_mutex/dyn_lock_set.rs b/rust/kernel/sync/lock/ww_mutex/dyn_lock_set.rs
new file mode 100644
index 0000000000000..d82da83839ffb
--- /dev/null
+++ b/rust/kernel/sync/lock/ww_mutex/dyn_lock_set.rs
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Wound/wait locking for a set of objects discovered at runtime.
+//!
+//! [`DynLockSet`] keeps locks held while the caller inspects objects and finds
+//! more locks to acquire. It handles repeated mutexes and provides access to
+//! their protected data. [`DynLockSet::ww_transaction`] handles deadlock retries
+//! and runs the work callback once all locks are held.
+
+use super::{
+    acquire_ctx_backoff::retry,
+    AcquireCtxBackoff,
+    Mutex,
+    MutexGuard, //
+};
+use crate::{
+    bindings,
+    prelude::*,
+    types::NotThreadSafe, //
+};
+use core::ptr::NonNull;
+
+/// Used by [`DynLockSet`] to track acquired locks.
+///
+/// This type is strictly crate-private and must never be exposed
+/// outside this crate.
+struct RawGuard {
+    mutex_ptr: NonNull<bindings::ww_mutex>,
+    _not_send: NotThreadSafe,
+}
+
+impl Drop for RawGuard {
+    #[inline]
+    fn drop(&mut self) {
+        // SAFETY: `mutex_ptr` originates from a locked `Mutex` and remains
+        // valid for the lifetime of this guard, so unlocking here is sound.
+        unsafe { bindings::ww_mutex_unlock(self.mutex_ptr.as_ptr()) };
+    }
+}
+
+/// A tracked set of [`Mutex`] locks acquired with the same [`AcquireCtxBackoff`].
+///
+/// Keeps locks held across loops and helper calls, handles repeated mutexes and
+/// provides access to their data through [`Self::with_locked`].
+pub struct DynLockSet<'ctx, 'm> {
+    ctx: &'ctx AcquireCtxBackoff<'ctx, 'm>,
+    taken: KVec<RawGuard>,
+}
+
+impl<'ctx, 'm> DynLockSet<'ctx, 'm> {
+    /// Creates an empty set using the given acquire context.
+    #[inline]
+    pub fn new(ctx: &'ctx AcquireCtxBackoff<'ctx, 'm>) -> Self {
+        Self {
+            ctx,
+            taken: KVec::new(),
+        }
+    }
+
+    /// Attempts to lock the given [`Mutex`] and stores a guard for it.
+    ///
+    /// Returns success if the mutex is already held by this set, unless deadlock
+    /// recovery is pending.
+    pub fn lock<T>(&mut self, mutex: &'m Mutex<T>) -> Result {
+        match self.ctx.lock(mutex) {
+            Ok(guard) => self.add_guard(guard),
+            Err(e) if e == EALREADY => {
+                // The context may hold a guard owned outside this set.
+                if self
+                    .taken
+                    .iter()
+                    .any(|guard| guard.mutex_ptr.as_ptr() == mutex.inner.get())
+                {
+                    Ok(())
+                } else {
+                    Err(e)
+                }
+            }
+            Err(e) => Err(e),
+        }
+    }
+
+    /// Acquires a dynamic set, retrying on deadlock then runs `work` once.
+    ///
+    /// Each call releases existing locks and starts a new transaction. Retries keep
+    /// the same ticket. The set retains its allocation across retries and calls.
+    ///
+    /// `work` runs once after acquisition and must not acquire more locks. Its errors
+    /// are returned without retrying. All locks and pending backoff state are released
+    /// on return. The callbacks must not change the set's context or nest transactions
+    /// on that context.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the context holds caller-owned locks outside this set at a transaction
+    /// boundary, including forgotten guards.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::{
+    ///     define_ww_class,
+    ///     prelude::*,
+    ///     sync::lock::ww_mutex::{
+    ///         AcquireCtx,
+    ///         AcquireCtxBackoff,
+    ///         DynLockSet,
+    ///         Mutex, //
+    ///     }, //
+    /// };
+    /// use pin_init::stack_pin_init;
+    ///
+    /// define_ww_class!(CLASS);
+    ///
+    /// stack_pin_init!(let first = Mutex::new(1, &CLASS));
+    /// stack_pin_init!(let second = Mutex::new(2, &CLASS));
+    ///
+    /// stack_pin_init!(let acquire_ctx = AcquireCtx::new(&CLASS));
+    /// let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+    /// let mut set = DynLockSet::new(&ctx);
+    ///
+    /// set.ww_transaction(
+    ///     |set| {
+    ///         set.lock(&first)?;
+    ///
+    ///         if set.with_locked(&first, |value| *value > 0)? {
+    ///             set.lock(&second)?;
+    ///         }
+    ///
+    ///         Ok(())
+    ///     },
+    ///     |set| set.with_locked(&first, |value| *value += 1),
+    /// )?;
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn ww_transaction<R>(
+        &mut self,
+        mut acquire: impl FnMut(&mut Self) -> Result,
+        work: impl FnOnce(&mut Self) -> Result<R>,
+    ) -> Result<R> {
+        let ctx = self.ctx;
+        self.clear();
+        ctx.restart();
+
+        let result = retry(ctx, || {
+            if let Some(guard) = self.ctx.take_contended_guard() {
+                self.add_guard(guard)?;
+            }
+
+            let result = acquire(self);
+            if result.is_err() {
+                self.clear();
+            }
+
+            result
+        });
+
+        let result = result.and_then(|()| work(self));
+
+        self.clear();
+        ctx.clear_backoff();
+        ctx.assert_no_locks_held();
+
+        result
+    }
+
+    fn add_guard<T: ?Sized>(&mut self, guard: MutexGuard<'ctx, T>) -> Result {
+        let raw_guard = RawGuard {
+            // SAFETY: The guard holds this mutex which remains valid for `'ctx`.
+            mutex_ptr: unsafe { NonNull::new_unchecked(guard.mutex.inner.get()) },
+            _not_send: NotThreadSafe,
+        };
+
+        // Transfer unlocking to `raw_guard` before the fallible push so failure
+        // drops only one guard and unlocks the mutex exactly once.
+        core::mem::forget(guard);
+        self.taken.push(raw_guard, GFP_KERNEL)?;
+
+        Ok(())
+    }
+
+    /// Executes `access` with a mutable reference to the data behind [`Mutex`].
+    ///
+    /// Fails with [`EINVAL`] if the [`Mutex`] was not locked in this set.
+    pub fn with_locked<T: Unpin, Y>(
+        &mut self,
+        mutex: &'m Mutex<T>,
+        access: impl for<'b> FnOnce(&'b mut T) -> Y,
+    ) -> Result<Y> {
+        let mutex_ptr = mutex.inner.get();
+
+        if self
+            .taken
+            .iter()
+            .any(|guard| guard.mutex_ptr.as_ptr() == mutex_ptr)
+        {
+            // SAFETY: We hold the lock corresponding to `mutex` so we have
+            // exclusive access to its protected data.
+            let value = unsafe { &mut *mutex.data.get() };
+
+            Ok(access(value))
+        } else {
+            // `mutex` isn't locked in this set.
+            Err(EINVAL)
+        }
+    }
+
+    /// Releases all locks in the set.
+    #[inline]
+    pub fn clear(&mut self) {
+        // `Drop` implementation of the `RawGuard` takes care of the unlocking.
+        self.taken.clear();
+    }
+}
+
+impl Drop for DynLockSet<'_, '_> {
+    #[inline]
+    fn drop(&mut self) {
+        self.clear();
+    }
+}
+
+#[kunit_tests(rust_kernel_dyn_lock_set)]
+mod tests {
+    use super::super::AcquireCtx;
+    use super::*;
+    use crate::{
+        define_wd_class,
+        define_ww_class, //
+    };
+    use pin_init::stack_pin_init;
+
+    define_ww_class!(TEST_WOUND_WAIT_CLASS);
+    define_wd_class!(TEST_WAIT_DIE_CLASS);
+
+    #[test]
+    fn test_lock_and_clear() -> Result {
+        stack_pin_init!(let mutex = Mutex::new(10, &TEST_WOUND_WAIT_CLASS));
+        stack_pin_init!(let acquire_ctx = AcquireCtx::new(&TEST_WOUND_WAIT_CLASS));
+        let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+        let mut set = DynLockSet::new(&ctx);
+
+        assert_eq!(set.taken.capacity(), 0);
+
+        set.lock(&mutex)?;
+        set.lock(&mutex)?;
+        assert_eq!(set.taken.len(), 1);
+
+        set.with_locked(&mutex, |v| *v += 7)?;
+        assert_eq!(set.with_locked(&mutex, |v| *v)?, 17);
+
+        let capacity = set.taken.capacity();
+        assert!(capacity > 0);
+
+        set.clear();
+        assert!(!mutex.is_locked());
+        assert_eq!(set.taken.len(), 0);
+        assert_eq!(set.taken.capacity(), capacity);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_drop_unlocks() -> Result {
+        stack_pin_init!(let first = Mutex::new(1, &TEST_WOUND_WAIT_CLASS));
+        stack_pin_init!(let second = Mutex::new(2, &TEST_WOUND_WAIT_CLASS));
+        stack_pin_init!(let acquire_ctx = AcquireCtx::new(&TEST_WOUND_WAIT_CLASS));
+        let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+
+        {
+            let mut set = DynLockSet::new(&ctx);
+
+            set.lock(&first)?;
+            set.lock(&second)?;
+        }
+
+        assert!(!first.is_locked());
+        assert!(!second.is_locked());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_context_reuse() -> Result {
+        stack_pin_init!(let mutex = Mutex::new(5, &TEST_WOUND_WAIT_CLASS));
+        let mut acquire_ctx = KBox::pin_init(AcquireCtx::new(&TEST_WOUND_WAIT_CLASS), GFP_KERNEL)?;
+
+        {
+            let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+            let mut set = DynLockSet::new(&ctx);
+
+            set.ww_transaction(
+                |set| set.lock(&mutex),
+                |set| set.with_locked(&mutex, |value| *value += 1),
+            )?;
+
+            assert!(!mutex.is_locked());
+        }
+
+        let guard = acquire_ctx.lock(&mutex)?;
+        assert_eq!(*guard, 6);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_reject_external_guard() -> Result {
+        stack_pin_init!(let mutex = Mutex::new(5, &TEST_WOUND_WAIT_CLASS));
+        stack_pin_init!(let acquire_ctx = AcquireCtx::new(&TEST_WOUND_WAIT_CLASS));
+        let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+        let guard = ctx.lock(&mutex)?;
+        let mut set = DynLockSet::new(&ctx);
+
+        assert_eq!(set.lock(&mutex), Err(EALREADY));
+        assert_eq!(set.with_locked(&mutex, |v| *v), Err(EINVAL));
+
+        set.clear();
+        assert!(mutex.is_locked());
+        assert_eq!(*guard, 5);
+
+        drop(guard);
+        assert!(!mutex.is_locked());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_access_without_lock() -> Result {
+        stack_pin_init!(let mutex = Mutex::new(5, &TEST_WOUND_WAIT_CLASS));
+        stack_pin_init!(let acquire_ctx = AcquireCtx::new(&TEST_WOUND_WAIT_CLASS));
+        let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+        let mut set = DynLockSet::new(&ctx);
+
+        assert_eq!(set.with_locked(&mutex, |v| *v), Err(EINVAL));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_class_mismatch() -> Result {
+        stack_pin_init!(let mutex = Mutex::new(5, &TEST_WOUND_WAIT_CLASS));
+        stack_pin_init!(let acquire_ctx = AcquireCtx::new(&TEST_WAIT_DIE_CLASS));
+        let ctx = AcquireCtxBackoff::new(acquire_ctx.as_mut());
+        let mut set = DynLockSet::new(&ctx);
+
+        assert_eq!(set.lock(&mutex), Err(EINVAL));
+        assert!(!mutex.is_locked());
+
+        Ok(())
+    }
+}
-- 
2.51.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v13 7/7] MAINTAINERS: add Onur Özkan as WW MUTEX maintainer
  2026-10-20 15:29 [PATCH v13 0/7] rust: add ww_mutex support Onur Özkan
                   ` (5 preceding siblings ...)
  2026-10-20 15:29 ` [PATCH v13 6/7] rust: ww_mutex: implement DynLockSet Onur Özkan
@ 2026-10-20 15:29 ` Onur Özkan
  6 siblings, 0 replies; 8+ messages in thread
From: Onur Özkan @ 2026-10-20 15:29 UTC (permalink / raw)
  To: rust-for-linux
  Cc: lossin, lyude, ojeda, alex.gaynor, boqun.feng, gary, a.hindborg,
	aliceryhl, tmgross, dakr, peterz, mingo, will, longman,
	felipe_life, daniel, daniel.almeida, thomas.hellstrom,
	linux-kernel, Onur Özkan

Add myself as a WW MUTEX maintainer to reflect ongoing work and future
maintenance of WW MUTEX.

Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Signed-off-by: Onur Özkan <work@onurozkan.dev>
---
 MAINTAINERS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 198bb740f7cc2..ff41d34f76f70 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15265,6 +15265,7 @@ M:	Peter Zijlstra <peterz@infradead.org>
 M:	Ingo Molnar <mingo@redhat.com>
 M:	Will Deacon <will@kernel.org>
 M:	Boqun Feng <boqun@kernel.org> (LOCKDEP & RUST)
+M:	Onur Özkan <work@onurozkan.dev> (WW MUTEX)
 R:	Waiman Long <longman@redhat.com>
 L:	linux-kernel@vger.kernel.org
 S:	Maintained
-- 
2.51.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-10-20 15:31 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-20 15:29 [PATCH v13 0/7] rust: add ww_mutex support Onur Özkan
2026-10-20 15:29 ` [PATCH v13 1/7] rust: add C wrappers for ww_mutex inline functions Onur Özkan
2026-10-20 15:29 ` [PATCH v13 2/7] ww_mutex: add ww_class field unconditionally Onur Özkan
2026-10-20 15:29 ` [PATCH v13 3/7] rust: implement Class for ww_class support Onur Özkan
2026-10-20 15:29 ` [PATCH v13 4/7] rust: ww_mutex: add Mutex, AcquireCtx and MutexGuard Onur Özkan
2026-10-20 15:29 ` [PATCH v13 5/7] rust: ww_mutex: add AcquireCtxBackoff Onur Özkan
2026-10-20 15:29 ` [PATCH v13 6/7] rust: ww_mutex: implement DynLockSet Onur Özkan
2026-10-20 15:29 ` [PATCH v13 7/7] MAINTAINERS: add Onur Özkan as WW MUTEX maintainer Onur Özkan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®