Linux driver-core infrastructure
 help / color / mirror / Atom feed
From: Danilo Krummrich <dakr@kernel.org>
To: tj@kernel.org, jiangshanlai@gmail.com, aliceryhl@google.com,
	ojeda@kernel.org, boqun@kernel.org, gary@garyguo.net,
	bjorn3_gh@protonmail.com, lossin@kernel.org,
	a.hindborg@kernel.org, tmgross@umich.edu,
	daniel.almeida@collabora.com, tamird@kernel.org,
	acourbot@nvidia.com, work@onurozkan.dev, jhubbard@nvidia.com
Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
	driver-core@lists.linux.dev, Danilo Krummrich <dakr@kernel.org>
Subject: [PATCH v2 6/6] rust: workqueue: add ScopedWork for non-'static work items
Date: Fri,  7 Aug 2026 18:52:49 +0200	[thread overview]
Message-ID: <20260807165252.3849875-7-dakr@kernel.org> (raw)
In-Reply-To: <20260807165252.3849875-1-dakr@kernel.org>

Add ScopedWork<T>, a work item wrapper whose destructor calls
cancel_work_sync(), allowing T to carry non-'static lifetimes. Ownership
of the data is not transferred to the workqueue; instead, the
synchronous cancellation on drop guarantees the work function is not
running when the data is freed.

ScopedWork uses the existing Work/HasWork/WorkItem infrastructure with
NonNull<ScopedWorkRef<T>> as WorkItem::Pointer for the callback path,
and implements RawWorkItem for &ScopedWork<T> and &ScopedWorkRef<T> for
the enqueue path (requiring T: Sync for cross-thread shared access
safety).

Two enqueue paths are provided:
  - Queue::enqueue_scoped() (unsafe): the caller must ensure the work
    item is not forgotten.
  - ScopedQueue::enqueue() (safe): when the work item's lifetime
    satisfies the queue's 'scope bound.

Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
 rust/kernel/workqueue/mod.rs    |  46 +++-
 rust/kernel/workqueue/scoped.rs | 442 +++++++++++++++++++++++++++++---
 2 files changed, 444 insertions(+), 44 deletions(-)

diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
index 551fa1401b85..be5bfb4cfc30 100644
--- a/rust/kernel/workqueue/mod.rs
+++ b/rust/kernel/workqueue/mod.rs
@@ -213,7 +213,13 @@
 pub use self::builder::Builder;
 
 mod scoped;
-pub use self::scoped::ScopedQueue;
+pub use self::scoped::{
+    new_scoped_work,
+    ScopedQueue,
+    ScopedWork,
+    ScopedWorkItem,
+    ScopedWorkRef, //
+};
 
 /// Creates a [`Work`] initialiser with the given name and a newly-created lock class.
 #[macro_export]
@@ -283,23 +289,39 @@ pub unsafe fn from_raw<'a>(ptr: *const bindings::workqueue_struct) -> &'a Queue
     /// This may fail if the work item is already enqueued in a workqueue.
     ///
     /// The work item will be submitted using `WORK_CPU_UNBOUND`.
+    #[inline]
     pub fn enqueue<W, const ID: u64>(&self, w: W) -> W::EnqueueOutput
     where
         W: RawWorkItem<ID> + Send + 'static,
+    {
+        // SAFETY: `W: 'static` guarantees the work item remains valid indefinitely,
+        // so the `enqueue_scoped` requirement that the work item stays valid until
+        // the work function runs (or is cancelled) is trivially satisfied.
+        unsafe { self.enqueue_scoped(w) }
+    }
+
+    /// Enqueues a work item that may not be `'static`.
+    ///
+    /// Unlike [`Queue::enqueue`], this does not require the work item to be `'static`.
+    ///
+    /// The work item will be submitted using `WORK_CPU_UNBOUND`.
+    ///
+    /// # Safety
+    ///
+    /// The caller must ensure that the work item's destructor runs before any
+    /// lifetime it captures expires (i.e., the work item must not be forgotten).
+    #[inline]
+    pub unsafe fn enqueue_scoped<W, const ID: u64>(&self, w: W) -> W::EnqueueOutput
+    where
+        W: RawWorkItem<ID> + Send,
     {
         let queue_ptr = self.0.get();
 
-        // SAFETY: We only return `false` if the `work_struct` is already in a workqueue. The other
-        // `__enqueue` requirements are not relevant since `W` is `Send` and static.
-        //
-        // The call to `bindings::queue_work_on` will dereference the provided raw pointer, which
-        // is ok because `__enqueue` guarantees that the pointer is valid for the duration of this
-        // closure.
-        //
-        // Furthermore, if the C workqueue code accesses the pointer after this call to
-        // `__enqueue`, then the work item was successfully enqueued, and `bindings::queue_work_on`
-        // will have returned true. In this case, `__enqueue` promises that the raw pointer will
-        // stay valid until we call the function pointer in the `work_struct`, so the access is ok.
+        // SAFETY: We only return `false` if the `work_struct` is already in a workqueue. The
+        // caller guarantees the work item remains valid until the work function runs or the item
+        // is cancelled, satisfying the `__enqueue` requirement that the pointer stays valid until
+        // the function pointer in the `work_struct` is called. `W: Send` satisfies the
+        // cross-thread safety requirement.
         unsafe {
             w.__enqueue(move |work_ptr| {
                 bindings::queue_work_on(
diff --git a/rust/kernel/workqueue/scoped.rs b/rust/kernel/workqueue/scoped.rs
index 18a4b6f6cf18..1adf96f7eccd 100644
--- a/rust/kernel/workqueue/scoped.rs
+++ b/rust/kernel/workqueue/scoped.rs
@@ -1,13 +1,95 @@
 // SPDX-License-Identifier: GPL-2.0
 
-//! Lifetime-scoped workqueues.
+//! Lifetime-scoped workqueues and work items.
 //!
-//! Provides [`ScopedQueue`] for work items that may borrow data with some
-//! non-`'static` lifetime.
+//! Provides [`ScopedQueue`] and [`ScopedWork`] for work items that may borrow
+//! data with some non-`'static` lifetime.
 //!
-//! Unlike [`Queue`] which only accepts `'static` work items, [`ScopedQueue`]
-//! owns its underlying queue and relies on that queue being dropped to drain
-//! pending and running work before borrowed data can go out of scope.
+//! [`ScopedQueue`] owns its underlying queue and relies on that queue being
+//! dropped to drain pending and running work before borrowed data can go out
+//! of scope.
+//!
+//! [`ScopedWork`] wraps a work item whose destructor calls `cancel_work_sync()`,
+//! so ownership of the data is not transferred to the workqueue. This allows the
+//! inner data to carry non-`'static` lifetimes.
+//!
+//! Drivers should prefer [`ScopedWork`] with either a [`ScopedQueue`] or a
+//! system queue over [`Work`]-based items. When used with a [`ScopedQueue`],
+//! the work item must already outlive the queue, making [`Work`]'s separate
+//! allocation and reference count unnecessary.
+//!
+//! # Examples
+//!
+//! Enqueue on the system workqueue (unsafe, caller must not forget the work):
+//!
+//! ```
+//! # use kernel::time::{Delta, delay::fsleep};
+//! use kernel::workqueue::{
+//!     self,
+//!     new_scoped_work,
+//!     ScopedWork,
+//!     ScopedWorkItem,
+//!     ScopedWorkRef,
+//! };
+//!
+//! struct MyWork {
+//!     value: u32,
+//! }
+//!
+//! impl ScopedWorkItem for MyWork {
+//!     fn run(work: &ScopedWorkRef<Self>) {
+//!         pr_info!("value = {}\n", work.value);
+//!     }
+//! }
+//!
+//! let work = KBox::pin_init(
+//!     new_scoped_work!("MyWork", MyWork { value: 42 }),
+//!     GFP_KERNEL,
+//! )?;
+//!
+//! // SAFETY: `work` is not forgotten.
+//! unsafe { workqueue::system_dfl().enqueue_scoped(&*work) };
+//! # fsleep(Delta::from_millis(100));
+//! # Ok::<(), Error>(())
+//! ```
+//!
+//! Enqueue on a [`ScopedQueue`] using the safe path (work outlives the queue):
+//!
+//! ```
+//! use kernel::workqueue::{
+//!     new_scoped_work,
+//!     ScopedQueue,
+//!     ScopedWork,
+//!     ScopedWorkItem,
+//!     ScopedWorkRef,
+//! };
+//!
+//! struct MyWork {
+//!     value: u32,
+//! }
+//!
+//! impl ScopedWorkItem for MyWork {
+//!     fn run(work: &ScopedWorkRef<Self>) {
+//!         pr_info!("value = {}\n", work.value);
+//!     }
+//! }
+//!
+//! let work = KBox::pin_init(
+//!     new_scoped_work!("MyWork", MyWork { value: 42 }),
+//!     GFP_KERNEL,
+//! )?;
+//!
+//! // SAFETY: The queue is not forgotten.
+//! let queue = unsafe { ScopedQueue::new(c"example_wq")? };
+//!
+//! // Safe since `work` outlives `queue`.
+//! queue.enqueue(&*work);
+//! # Ok::<(), Error>(())
+//! ```
+//!
+//! [`ScopedQueue`] can also be used with regular [`Work`]-based items. The
+//! following `compile_fail` examples demonstrate the lifetime enforcement that
+//! [`ScopedQueue`] provides in that case.
 //!
 //! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
 //! ```compile_fail,ignore
@@ -112,18 +194,30 @@
 //! ```
 
 use super::{
+    impl_has_work,
+    HasWork,
     OwnedQueue,
     Queue,
-    RawWorkItem, //
+    RawWorkItem,
+    Work,
+    WorkItem,
+    WorkItemPointer, //
 };
 
 use crate::{
     bindings,
-    ffi,
-    prelude::*, //
+    prelude::*,
+    sync::LockClassKey,
+    types::Opaque, //
 };
 
-use core::marker::PhantomData;
+use pin_init::Wrapper;
+
+use core::{
+    marker::PhantomData,
+    ops::Deref,
+    ptr::NonNull, //
+};
 
 /// An owned workqueue that can enqueue work items borrowing from `'scope`.
 ///
@@ -133,6 +227,15 @@ pub struct ScopedQueue<'scope> {
     _scope: PhantomData<&'scope mut &'scope ()>,
 }
 
+impl Deref for ScopedQueue<'_> {
+    type Target = Queue;
+
+    #[inline]
+    fn deref(&self) -> &Queue {
+        &self.inner
+    }
+}
+
 impl<'scope> ScopedQueue<'scope> {
     /// Creates an ordered scoped workqueue.
     ///
@@ -155,28 +258,11 @@ pub fn enqueue<W, const ID: u64>(&self, work: W) -> W::EnqueueOutput
     where
         W: RawWorkItem<ID> + Send + 'scope,
     {
-        let queue_ptr = self.inner.0.get();
-
-        // SAFETY:
-        // - Closure returns `false` only if `queue_work_on` returns `false`
-        //   and that means `work_ptr` is already in a workqueue.
-        //
-        // - `W: 'scope` and dropck keep borrowed data alive until this queue is
-        //   dropped. The constructor requires that the queue is not leaked and
-        //   dropping `inner` drains pending and running work so the function
-        //   pointer is not called after any lifetime in `W` expires.
-        //
-        // - The last requirement of `__enqueue` is not relevant here because `W`
-        //   is `Send`.
-        unsafe {
-            work.__enqueue(move |work_ptr| {
-                bindings::queue_work_on(
-                    bindings::wq_misc_consts_WORK_CPU_UNBOUND as ffi::c_int,
-                    queue_ptr,
-                    work_ptr,
-                )
-            })
-        }
+        // SAFETY: `W: 'scope` and dropck keep borrowed data alive until this queue
+        // is dropped. The constructor requires that the queue is not leaked and
+        // dropping `inner` drains pending and running work, so the function pointer
+        // is not called after any lifetime in `W` expires.
+        unsafe { self.enqueue_scoped(work) }
     }
 }
 
@@ -188,3 +274,295 @@ fn drop(&mut self) {
         let _ = &self._scope;
     }
 }
+
+/// Trait for types that can be used as scoped work items.
+///
+/// Implementers define the work function that executes when the item is dequeued by a workqueue
+/// thread. The callback receives a reference to the containing [`ScopedWorkRef`], which provides
+/// access to the inner data via [`Deref`] and can be used to re-enqueue the work item.
+pub trait ScopedWorkItem: Sized {
+    /// Called when the work item is executed.
+    fn run(work: &ScopedWorkRef<Self>);
+}
+
+/// The work function's view of a [`ScopedWork`] item.
+///
+/// The work function callback receives `&ScopedWorkRef<T>`, which [`Deref`]s to `&T` and can be
+/// passed to queue enqueue methods for re-enqueueing from within the work function.
+#[pin_data]
+pub struct ScopedWorkRef<T: ScopedWorkItem> {
+    #[pin]
+    work: Work<Self>,
+    #[pin]
+    data: T,
+}
+
+impl_has_work! {
+    impl{T: ScopedWorkItem} HasWork<ScopedWorkRef<T>> for ScopedWorkRef<T> { self.work }
+}
+
+impl<T: ScopedWorkItem> Deref for ScopedWorkRef<T> {
+    type Target = T;
+
+    #[inline]
+    fn deref(&self) -> &T {
+        &self.data
+    }
+}
+
+impl<T: ScopedWorkItem> WorkItem for ScopedWorkRef<T> {
+    type Pointer = NonNull<Self>;
+
+    #[inline]
+    fn run(this: NonNull<Self>) {
+        // SAFETY: `this` points to a valid, pinned `ScopedWorkRef`. `cancel_work_sync()` in
+        // `ScopedWork`'s `PinnedDrop` prevents use-after-drop.
+        let work = unsafe { &*this.as_ptr() };
+
+        T::run(work);
+    }
+}
+
+// SAFETY: The `run` callback uses the `work_struct` pointer to recover a pointer to
+// `ScopedWorkRef<T>` via `HasWork`, wraps it in `NonNull`, and calls `WorkItem::run`.
+unsafe impl<T: ScopedWorkItem, const ID: u64> WorkItemPointer<ID> for NonNull<ScopedWorkRef<T>>
+where
+    ScopedWorkRef<T>: WorkItem<ID, Pointer = Self>,
+    ScopedWorkRef<T>: HasWork<ScopedWorkRef<T>, ID>,
+{
+    unsafe extern "C" fn run(ptr: *mut bindings::work_struct) {
+        let ptr = ptr.cast::<Work<ScopedWorkRef<T>, ID>>();
+
+        // SAFETY: The `work_struct` is embedded in `ScopedWorkRef<T>` via `HasWork`.
+        let ptr =
+            unsafe { <ScopedWorkRef<T> as HasWork<ScopedWorkRef<T>, ID>>::work_container_of(ptr) };
+
+        // SAFETY: `work_container_of` returns a valid, non-null pointer.
+        let nn = unsafe { NonNull::new_unchecked(ptr) };
+
+        <ScopedWorkRef<T> as WorkItem<ID>>::run(nn);
+    }
+}
+
+// Required because `WorkItemPointer<ID>: RawWorkItem<ID>` is a supertrait bound. This `__enqueue`
+// is never called; the enqueue path goes through the `RawWorkItem` impl for `&ScopedWork<T>` or
+// `&ScopedWorkRef<T>` instead.
+//
+// SAFETY: `__enqueue` is unreachable.
+unsafe impl<T: ScopedWorkItem, const ID: u64> RawWorkItem<ID> for NonNull<ScopedWorkRef<T>>
+where
+    ScopedWorkRef<T>: HasWork<ScopedWorkRef<T>, ID>,
+{
+    type EnqueueOutput = bool;
+
+    unsafe fn __enqueue<F>(self, _queue_work_on: F) -> Self::EnqueueOutput
+    where
+        F: FnOnce(*mut bindings::work_struct) -> bool,
+    {
+        unreachable!()
+    }
+}
+
+// SAFETY: `&ScopedWorkRef<T>` points to a valid `ScopedWorkRef` with a valid `work_struct`.
+// The pointer remains valid until `cancel_work_sync()` completes in `ScopedWork`'s drop.
+unsafe impl<'a, T: ScopedWorkItem + Sync, const ID: u64> RawWorkItem<ID> for &'a ScopedWorkRef<T>
+where
+    ScopedWorkRef<T>: HasWork<ScopedWorkRef<T>, ID>,
+{
+    type EnqueueOutput = bool;
+
+    unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput
+    where
+        F: FnOnce(*mut bindings::work_struct) -> bool,
+    {
+        let self_ptr = core::ptr::from_ref(self);
+
+        // SAFETY: `self_ptr` points to a valid `ScopedWorkRef` with a `Work` field.
+        let work_ptr = unsafe {
+            <ScopedWorkRef<T> as HasWork<ScopedWorkRef<T>, ID>>::raw_get_work(self_ptr.cast_mut())
+        };
+
+        // SAFETY: `work_ptr` points to a valid `Work`.
+        let work_ptr = unsafe { Work::raw_get(work_ptr) };
+
+        queue_work_on(work_ptr)
+    }
+}
+
+// SAFETY: `&ScopedWork<T>` accesses the inner `ScopedWorkRef` through `Opaque::get()`.
+// The pointer remains valid until `cancel_work_sync()` completes in `ScopedWork`'s drop.
+unsafe impl<'a, T: ScopedWorkItem + Sync, const ID: u64> RawWorkItem<ID> for &'a ScopedWork<T>
+where
+    ScopedWorkRef<T>: HasWork<ScopedWorkRef<T>, ID>,
+{
+    type EnqueueOutput = bool;
+
+    unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput
+    where
+        F: FnOnce(*mut bindings::work_struct) -> bool,
+    {
+        // SAFETY: The inner ScopedWorkRef is valid and initialized.
+        let inner: &ScopedWorkRef<T> = unsafe { &*self.inner.get() };
+
+        // SAFETY: Delegates to the `&ScopedWorkRef<T>` impl.
+        unsafe { inner.__enqueue(queue_work_on) }
+    }
+}
+
+/// A scoped work item that cancels synchronously on drop.
+///
+/// `ScopedWork<T>` contains a `work_struct` and the user data `T`. Its destructor calls
+/// `cancel_work_sync()`, guaranteeing the work function is not running when the data is dropped.
+///
+/// This allows `T` to carry non-`'static` lifetimes.
+///
+/// Construct via [`new_scoped_work!`] which returns an `impl PinInit` suitable for embedding
+/// in-place inside other pinned structs.
+///
+/// # Examples
+///
+/// Self-re-enqueueing from within the work function:
+///
+/// ```
+/// # use kernel::sync::atomic::{Atomic, Relaxed};
+/// # use kernel::time::{Delta, delay::fsleep};
+/// use kernel::workqueue::{
+///     new_scoped_work,
+///     Queue,
+///     ScopedQueue,
+///     ScopedWork,
+///     ScopedWorkItem,
+///     ScopedWorkRef,
+/// };
+///
+/// struct RequeueWork<'a> {
+///     counter: Atomic<u32>,
+///     queue: &'a Queue,
+/// }
+///
+/// impl ScopedWorkItem for RequeueWork<'_> {
+///     fn run(work: &ScopedWorkRef<Self>) {
+///         if work.counter.fetch_add(1u32, Relaxed) < 2 {
+///             // SAFETY: The `ScopedWork` is not forgotten.
+///             unsafe { work.queue.enqueue_scoped(work) };
+///         }
+///     }
+/// }
+///
+/// // SAFETY: The queue is not forgotten.
+/// let queue = unsafe { ScopedQueue::new(c"requeue_wq")? };
+///
+/// let work = KBox::pin_init(
+///     new_scoped_work!("RequeueWork", RequeueWork { counter: Atomic::new(0u32), queue: &queue }),
+///     GFP_KERNEL,
+/// )?;
+///
+/// // SAFETY: `work` is not forgotten.
+/// unsafe { queue.enqueue_scoped(&*work) };
+/// # fsleep(Delta::from_millis(300));
+///
+/// assert_eq!(work.counter.load(Relaxed), 3);
+/// # Ok::<(), Error>(())
+/// ```
+#[pin_data(PinnedDrop)]
+pub struct ScopedWork<T: ScopedWorkItem> {
+    #[pin]
+    inner: Opaque<ScopedWorkRef<T>>,
+}
+
+// SAFETY: `&ScopedWork<T>` only provides `&ScopedWorkRef<T>` (via `Deref`), which is safe to share
+// when `T: Sync`.
+unsafe impl<T: ScopedWorkItem + Sync> Sync for ScopedWork<T> {}
+
+// SAFETY: ScopedWork can be sent to another thread when T: Send.
+unsafe impl<T: ScopedWorkItem + Send> Send for ScopedWork<T> {}
+
+impl<T: ScopedWorkItem> Deref for ScopedWork<T> {
+    type Target = ScopedWorkRef<T>;
+
+    #[inline]
+    fn deref(&self) -> &ScopedWorkRef<T> {
+        // SAFETY: The inner `ScopedWorkRef` is always valid and initialized.
+        unsafe { &*self.inner.get() }
+    }
+}
+
+impl<T: ScopedWorkItem> ScopedWork<T> {
+    /// Creates a pin-initializer for a new scoped work item.
+    ///
+    /// Use [`new_scoped_work!`] to automatically provide the lock class key.
+    #[inline]
+    pub fn new<E>(
+        name: &'static CStr,
+        key: Pin<&'static LockClassKey>,
+        init: impl PinInit<T, E>,
+    ) -> impl PinInit<Self, Error>
+    where
+        Error: From<E>,
+    {
+        try_pin_init!(Self {
+            inner <- Opaque::pin_init(try_pin_init!(ScopedWorkRef::<T> {
+                work <- Work::new(name, key),
+                data <- init,
+            })),
+        })
+    }
+}
+
+#[pinned_drop]
+impl<T: ScopedWorkItem> PinnedDrop for ScopedWork<T> {
+    #[inline]
+    fn drop(self: Pin<&mut Self>) {
+        let inner = self.inner.get();
+
+        // SAFETY: `inner` points to a valid `ScopedWorkRef`. After `cancel_work_sync()` returns,
+        // the work function is guaranteed to not be running.
+        unsafe { bindings::cancel_work_sync(Work::raw_get(&raw const (*inner).work)) };
+    }
+}
+
+/// Creates a [`ScopedWork`] pin-initializer with a new lock class.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::workqueue::{
+///     new_scoped_work,
+///     ScopedWork,
+///     ScopedWorkItem,
+///     ScopedWorkRef,
+/// };
+///
+/// struct MyWork {
+///     value: u32,
+/// }
+///
+/// impl ScopedWorkItem for MyWork {
+///     fn run(work: &ScopedWorkRef<Self>) {
+///         pr_info!("value = {}\n", work.value);
+///     }
+/// }
+///
+/// #[pin_data]
+/// struct MyData {
+///     #[pin]
+///     work: ScopedWork<MyWork>,
+/// }
+///
+/// fn init_data() -> impl PinInit<MyData, Error> {
+///     try_pin_init!(MyData {
+///         work <- new_scoped_work!("MyWork", MyWork { value: 7 }),
+///     })
+/// }
+/// ```
+#[macro_export]
+macro_rules! new_scoped_work {
+    ($name:literal, $init:expr) => {
+        $crate::workqueue::ScopedWork::new(
+            $crate::c_str!($name),
+            $crate::static_lock_class!(),
+            $init,
+        )
+    };
+}
+pub use new_scoped_work;
-- 
2.55.0


  parent reply	other threads:[~2026-08-07 16:53 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-07 16:52 [PATCH v2 0/6] workqueue: OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 1/6] rust: workqueue: replace deprecated system_wq with system_{percpu,dfl}_wq Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 2/6] rust: workqueue: restrict delayed work to global wqs Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 3/6] rust: workqueue: create workqueue subdirectory Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 4/6] rust: workqueue: add creation of workqueues Danilo Krummrich
2026-08-07 22:39   ` Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 5/6] rust: workqueue: add ScopedQueue for lifetime bound items Danilo Krummrich
2026-08-07 16:52 ` Danilo Krummrich [this message]
2026-08-07 18:35   ` [PATCH v2 6/6] rust: workqueue: add ScopedWork for non-'static work items Danilo Krummrich

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260807165252.3849875-7-dakr@kernel.org \
    --to=dakr@kernel.org \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=driver-core@lists.linux.dev \
    --cc=gary@garyguo.net \
    --cc=jhubbard@nvidia.com \
    --cc=jiangshanlai@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tamird@kernel.org \
    --cc=tj@kernel.org \
    --cc=tmgross@umich.edu \
    --cc=work@onurozkan.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox