* [PATCH 1/7] rust: workqueue: replace deprecated system_wq with system_{percpu,dfl}_wq
2026-08-04 19:52 [PATCH 0/7] workqueue OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
@ 2026-08-04 19:52 ` Danilo Krummrich
2026-08-04 19:52 ` [PATCH 2/7] rust: workqueue: restrict delayed work to global wqs Danilo Krummrich
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Danilo Krummrich @ 2026-08-04 19:52 UTC (permalink / raw)
To: tj, jiangshanlai, aliceryhl, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross, daniel.almeida, tamird, acourbot,
work, jhubbard
Cc: rust-for-linux, linux-kernel, driver-core, Danilo Krummrich
system_wq is deprecated and triggers a runtime warning:
[ 0.857414] workqueue: work func ...WorkItemPointerKy0_E3runB7_ enqueued on deprecated workqueue. Use system_{percpu|dfl}_wq instead.
Replace system() with system_percpu() and system_dfl(), to match the
previous behavior of the deprecated system() and convert doc examples
and tests to system_dfl().
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
drivers/android/binder/process.rs | 4 ++--
rust/kernel/sync/completion.rs | 2 +-
rust/kernel/workqueue.rs | 40 +++++++++++++++++++------------
3 files changed, 28 insertions(+), 18 deletions(-)
diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs
index 96b8440ceac6..c0266fdaa598 100644
--- a/drivers/android/binder/process.rs
+++ b/drivers/android/binder/process.rs
@@ -1632,7 +1632,7 @@ pub(crate) fn release(this: Arc<Process>, _file: &File) {
if should_schedule {
// Ignore failures to schedule to the workqueue. Those just mean that we're already
// scheduled for execution.
- let _ = workqueue::system().enqueue(this);
+ let _ = workqueue::system_percpu().enqueue(this);
}
drop(binderfs_file);
@@ -1649,7 +1649,7 @@ pub(crate) fn flush(this: ArcBorrow<'_, Process>) -> Result {
if should_schedule {
// Ignore failures to schedule to the workqueue. Those just mean that we're already
// scheduled for execution.
- let _ = workqueue::system().enqueue(Arc::from(this));
+ let _ = workqueue::system_percpu().enqueue(Arc::from(this));
}
Ok(())
}
diff --git a/rust/kernel/sync/completion.rs b/rust/kernel/sync/completion.rs
index 35ff049ff078..1771bfc0ade2 100644
--- a/rust/kernel/sync/completion.rs
+++ b/rust/kernel/sync/completion.rs
@@ -38,7 +38,7 @@
/// done <- Completion::new(),
/// }), GFP_KERNEL)?;
///
-/// let _ = workqueue::system().enqueue(this.clone());
+/// let _ = workqueue::system_dfl().enqueue(this.clone());
///
/// Ok(this)
/// }
diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs
index 7e253b6f299c..3194b9c441aa 100644
--- a/rust/kernel/workqueue.rs
+++ b/rust/kernel/workqueue.rs
@@ -67,7 +67,7 @@
//! /// This method will enqueue the struct for execution on the system workqueue, where its value
//! /// will be printed.
//! fn print_later(val: Arc<MyStruct>) {
-//! let _ = workqueue::system().enqueue(val);
+//! let _ = workqueue::system_dfl().enqueue(val);
//! }
//! # print_later(MyStruct::new(42).unwrap());
//! ```
@@ -121,11 +121,11 @@
//! }
//!
//! fn print_1_later(val: Arc<MyStruct>) {
-//! let _ = workqueue::system().enqueue::<Arc<MyStruct>, 1>(val);
+//! let _ = workqueue::system_dfl().enqueue::<Arc<MyStruct>, 1>(val);
//! }
//!
//! fn print_2_later(val: Arc<MyStruct>) {
-//! let _ = workqueue::system().enqueue::<Arc<MyStruct>, 2>(val);
+//! let _ = workqueue::system_dfl().enqueue::<Arc<MyStruct>, 2>(val);
//! }
//! # print_1_later(MyStruct::new(24, 25).unwrap());
//! # print_2_later(MyStruct::new(41, 42).unwrap());
@@ -171,13 +171,13 @@
//! /// This method will enqueue the struct for execution on the system workqueue, where its value
//! /// will be printed 12 jiffies later.
//! fn print_later(val: Arc<MyStruct>) {
-//! let _ = workqueue::system().enqueue_delayed(val, 12);
+//! let _ = workqueue::system_dfl().enqueue_delayed(val, 12);
//! }
//!
//! /// It is also possible to use the ordinary `enqueue` method together with `DelayedWork`. This
//! /// is equivalent to calling `enqueue_delayed` with a delay of zero.
//! fn print_now(val: Arc<MyStruct>) {
-//! let _ = workqueue::system().enqueue(val);
+//! let _ = workqueue::system_dfl().enqueue(val);
//! }
//! # print_later(MyStruct::new(42).unwrap());
//! # print_now(MyStruct::new(42).unwrap());
@@ -1024,20 +1024,30 @@ unsafe impl<T, const ID: u64> RawDelayedWorkItem<ID> for ARef<T>
{
}
-/// Returns the system work queue (`system_wq`).
+/// Returns the system per-cpu work queue (`system_percpu_wq`).
///
/// It is the one used by `schedule[_delayed]_work[_on]()`. Multi-CPU multi-threaded. There are
/// users which expect relatively short queue flush time.
///
/// Callers shouldn't queue work items which can run for too long.
-pub fn system() -> &'static Queue {
- // SAFETY: `system_wq` is a C global, always available.
- unsafe { Queue::from_raw(bindings::system_wq) }
+pub fn system_percpu() -> &'static Queue {
+ // SAFETY: `system_percpu_wq` is a C global, always available.
+ unsafe { Queue::from_raw(bindings::system_percpu_wq) }
+}
+
+/// Returns the system default (unbound) work queue (`system_dfl_wq`).
+///
+/// Workers are not bound to any specific CPU, not concurrency managed, and all queued work items
+/// are executed immediately as long as `max_active` limit is not reached and resources are
+/// available.
+pub fn system_dfl() -> &'static Queue {
+ // SAFETY: `system_dfl_wq` is a C global, always available.
+ unsafe { Queue::from_raw(bindings::system_dfl_wq) }
}
/// Returns the system high-priority work queue (`system_highpri_wq`).
///
-/// It is similar to the one returned by [`system`] but for work items which require higher
+/// It is similar to the one returned by [`system_percpu`] but for work items which require higher
/// scheduling priority.
pub fn system_highpri() -> &'static Queue {
// SAFETY: `system_highpri_wq` is a C global, always available.
@@ -1046,8 +1056,8 @@ pub fn system_highpri() -> &'static Queue {
/// Returns the system work queue for potentially long-running work items (`system_long_wq`).
///
-/// It is similar to the one returned by [`system`] but may host long running work items. Queue
-/// flushing might take relatively long.
+/// It is similar to the one returned by [`system_percpu`] but may host long running work items.
+/// Queue flushing might take relatively long.
pub fn system_long() -> &'static Queue {
// SAFETY: `system_long_wq` is a C global, always available.
unsafe { Queue::from_raw(bindings::system_long_wq) }
@@ -1065,7 +1075,7 @@ pub fn system_unbound() -> &'static Queue {
/// Returns the system freezable work queue (`system_freezable_wq`).
///
-/// It is equivalent to the one returned by [`system`] except that it's freezable.
+/// It is equivalent to the one returned by [`system_percpu`] except that it's freezable.
///
/// A freezable workqueue participates in the freeze phase of the system suspend operations. Work
/// items on the workqueue are drained and no new work item starts execution until thawed.
@@ -1078,7 +1088,7 @@ pub fn system_freezable() -> &'static Queue {
///
/// It is inclined towards saving power and is converted to "unbound" variants if the
/// `workqueue.power_efficient` kernel parameter is specified; otherwise, it is similar to the one
-/// returned by [`system`].
+/// returned by [`system_percpu`].
pub fn system_power_efficient() -> &'static Queue {
// SAFETY: `system_power_efficient_wq` is a C global, always available.
unsafe { Queue::from_raw(bindings::system_power_efficient_wq) }
@@ -1097,7 +1107,7 @@ pub fn system_freezable_power_efficient() -> &'static Queue {
/// Returns the system bottom halves work queue (`system_bh_wq`).
///
-/// It is similar to the one returned by [`system`] but for work items which
+/// It is similar to the one returned by [`system_percpu`] but for work items which
/// need to run from a softirq context.
pub fn system_bh() -> &'static Queue {
// SAFETY: `system_bh_wq` is a C global, always available.
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 2/7] rust: workqueue: restrict delayed work to global wqs
2026-08-04 19:52 [PATCH 0/7] workqueue OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
2026-08-04 19:52 ` [PATCH 1/7] rust: workqueue: replace deprecated system_wq with system_{percpu,dfl}_wq Danilo Krummrich
@ 2026-08-04 19:52 ` Danilo Krummrich
2026-08-04 19:52 ` [PATCH 3/7] rust: workqueue: create workqueue subdirectory Danilo Krummrich
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Danilo Krummrich @ 2026-08-04 19:52 UTC (permalink / raw)
To: tj, jiangshanlai, aliceryhl, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross, daniel.almeida, tamird, acourbot,
work, jhubbard
Cc: rust-for-linux, linux-kernel, driver-core, stable,
Danilo Krummrich
From: Alice Ryhl <aliceryhl@google.com>
When a workqueue is shut down, delayed work that is pending but not
scheduled does not get properly cleaned up, so it's not safe to use
`enqueue_delayed` on a workqueue that might be destroyed. To fix this,
restricted `enqueue_delayed` to static queues.
This may be fixed in the future by an approach along the lines of [1].
Cc: stable@vger.kernel.org
Fixes: 7c098cd5eaae ("workqueue: rust: add delayed work items")
Reviewed-by: John Hubbard <jhubbard@nvidia.com>
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Reviewed-by: Gary Guo <gary@garyguo.net>
Link: https://lore.kernel.org/r/20250423-destroy-workqueue-flush-v1-1-3d74820780a5@google.com [1]
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
rust/kernel/workqueue.rs | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs
index 3194b9c441aa..b6e02ba427b3 100644
--- a/rust/kernel/workqueue.rs
+++ b/rust/kernel/workqueue.rs
@@ -302,8 +302,15 @@ pub fn enqueue<W, const ID: u64>(&self, w: W) -> W::EnqueueOutput
///
/// This may fail if the work item is already enqueued in a workqueue.
///
+ /// This is only valid for global workqueues (with static lifetimes) because those are the only
+ /// ones that outlive all possible delayed work items.
+ ///
/// The work item will be submitted using `WORK_CPU_UNBOUND`.
- pub fn enqueue_delayed<W, const ID: u64>(&self, w: W, delay: Jiffies) -> W::EnqueueOutput
+ pub fn enqueue_delayed<W, const ID: u64>(
+ &'static self,
+ w: W,
+ delay: Jiffies,
+ ) -> W::EnqueueOutput
where
W: RawDelayedWorkItem<ID> + Send + 'static,
{
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 3/7] rust: workqueue: create workqueue subdirectory
2026-08-04 19:52 [PATCH 0/7] workqueue OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
2026-08-04 19:52 ` [PATCH 1/7] rust: workqueue: replace deprecated system_wq with system_{percpu,dfl}_wq Danilo Krummrich
2026-08-04 19:52 ` [PATCH 2/7] rust: workqueue: restrict delayed work to global wqs Danilo Krummrich
@ 2026-08-04 19:52 ` Danilo Krummrich
2026-08-04 19:52 ` [PATCH 4/7] rust: workqueue: add creation of workqueues Danilo Krummrich
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Danilo Krummrich @ 2026-08-04 19:52 UTC (permalink / raw)
To: tj, jiangshanlai, aliceryhl, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross, daniel.almeida, tamird, acourbot,
work, jhubbard
Cc: rust-for-linux, linux-kernel, driver-core, Danilo Krummrich
From: Alice Ryhl <aliceryhl@google.com>
The following patch will implement a workqueue builder in a separate
file. To prepare for that, create a rust/kernel/workqueue subdirectory
and move the existing file.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
MAINTAINERS | 1 +
rust/kernel/{workqueue.rs => workqueue/mod.rs} | 0
2 files changed, 1 insertion(+)
rename rust/kernel/{workqueue.rs => workqueue/mod.rs} (100%)
diff --git a/MAINTAINERS b/MAINTAINERS
index f672858996f0..9cee804d840d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29155,6 +29155,7 @@ F: Documentation/core-api/workqueue.rst
F: include/linux/workqueue.h
F: kernel/workqueue.c
F: kernel/workqueue_internal.h
+F: rust/kernel/workqueue/
WWAN DRIVERS
M: Loic Poulain <loic.poulain@oss.qualcomm.com>
diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue/mod.rs
similarity index 100%
rename from rust/kernel/workqueue.rs
rename to rust/kernel/workqueue/mod.rs
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 4/7] rust: workqueue: add creation of workqueues
2026-08-04 19:52 [PATCH 0/7] workqueue OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
` (2 preceding siblings ...)
2026-08-04 19:52 ` [PATCH 3/7] rust: workqueue: create workqueue subdirectory Danilo Krummrich
@ 2026-08-04 19:52 ` Danilo Krummrich
2026-08-04 19:52 ` [PATCH 5/7] rust: workqueue: add ScopedQueue for lifetime bound items Danilo Krummrich
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Danilo Krummrich @ 2026-08-04 19:52 UTC (permalink / raw)
To: tj, jiangshanlai, aliceryhl, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross, daniel.almeida, tamird, acourbot,
work, jhubbard
Cc: rust-for-linux, linux-kernel, driver-core, Danilo Krummrich
From: Alice Ryhl <aliceryhl@google.com>
Creating workqueues is needed by various GPU drivers. Not only does it
give you better control over execution, it also allows devices to ensure
that all tasks have exited before the device is unbound (or similar) by
running the workqueue destructor.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>
[ * Fix import formatting of ptr::{self, NonNull},
* rename T to Kind,
* new_ordered(): set max_active: 1,
* new_power_efficient(): WQ_UNBOUND | WQ_POWER_EFFICIENT, add
.percpu(),
* new_bh(): WQ_PERCPU | WQ_BH, removed .percpu(),
* various doc improvements: new_percpu, max_active, cpu_intensive,
freezable.
- Danilo ]
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
rust/helpers/workqueue.c | 7 +
rust/kernel/workqueue/builder.rs | 389 +++++++++++++++++++++++++++++++
rust/kernel/workqueue/mod.rs | 44 +++-
3 files changed, 437 insertions(+), 3 deletions(-)
create mode 100644 rust/kernel/workqueue/builder.rs
diff --git a/rust/helpers/workqueue.c b/rust/helpers/workqueue.c
index ce1c3a5b2150..e4b9d1b3d6bf 100644
--- a/rust/helpers/workqueue.c
+++ b/rust/helpers/workqueue.c
@@ -14,3 +14,10 @@ __rust_helper void rust_helper_init_work_with_key(struct work_struct *work,
INIT_LIST_HEAD(&work->entry);
work->func = func;
}
+
+__rust_helper
+struct workqueue_struct *rust_helper_alloc_workqueue(const char *fmt, unsigned int flags,
+ int max_active, const void *data)
+{
+ return alloc_workqueue(fmt, flags, max_active, data);
+}
diff --git a/rust/kernel/workqueue/builder.rs b/rust/kernel/workqueue/builder.rs
new file mode 100644
index 000000000000..ec13b665d0a4
--- /dev/null
+++ b/rust/kernel/workqueue/builder.rs
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Workqueue builders.
+
+use kernel::{
+ alloc::AllocError,
+ prelude::*,
+ workqueue::{
+ OwnedQueue, //
+ Queue,
+ }, //
+};
+
+use core::{
+ marker::PhantomData,
+ ptr::{
+ self,
+ NonNull, //
+ },
+};
+
+/// Workqueue builder.
+///
+/// A valid combination of workqueue flags contains one of the base flags (`WQ_UNBOUND`, `WQ_BH`,
+/// or `WQ_PERCPU`) and a combination of modifier flags that are compatible with the selected base
+/// flag.
+///
+/// For details, please refer to `Documentation/core-api/workqueue.rst`.
+pub struct Builder<Kind> {
+ flags: bindings::wq_flags,
+ max_active: i32,
+ _kind: PhantomData<Kind>,
+}
+
+pub enum TypeUnbound {}
+pub enum TypePercpu {}
+pub enum TypePowerEfficient {}
+pub enum TypeBH {}
+pub enum TypeOrdered {}
+
+/// Entry-points to the builder API.
+impl Queue {
+ /// Build a workqueue whose work may execute on any cpu.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_unbound().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from unbound wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_UNBOUND")]
+ pub fn new_unbound() -> Builder<TypeUnbound> {
+ Builder {
+ flags: bindings::wq_flags_WQ_UNBOUND,
+ max_active: 0,
+ _kind: PhantomData,
+ }
+ }
+
+ /// Build a workqueue whose work items are bound to the CPU they are queued on.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_percpu().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from percpu wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_PERCPU")]
+ pub fn new_percpu() -> Builder<TypePercpu> {
+ Builder {
+ flags: bindings::wq_flags_WQ_PERCPU,
+ max_active: 0,
+ _kind: PhantomData,
+ }
+ }
+
+ /// Build a power-efficient workqueue.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_power_efficient().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from power-efficient wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_POWER_EFFICIENT")]
+ pub fn new_power_efficient() -> Builder<TypePowerEfficient> {
+ Builder {
+ flags: bindings::wq_flags_WQ_UNBOUND | bindings::wq_flags_WQ_POWER_EFFICIENT,
+ max_active: 0,
+ _kind: PhantomData,
+ }
+ }
+
+ /// Build a single-threaded workqueue that executes jobs in order.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_ordered().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from ordered wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "alloc_ordered_workqueue")]
+ #[doc(alias = "__WQ_ORDERED")]
+ pub fn new_ordered() -> Builder<TypeOrdered> {
+ Builder {
+ flags: bindings::wq_flags_WQ_UNBOUND | bindings::wq_flags___WQ_ORDERED,
+ max_active: 1,
+ _kind: PhantomData,
+ }
+ }
+
+ /// Build a workqueue that executes in bottom-half (softirq) context.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_bh().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from BH wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_BH")]
+ pub fn new_bh() -> Builder<TypeBH> {
+ Builder {
+ flags: bindings::wq_flags_WQ_PERCPU | bindings::wq_flags_WQ_BH,
+ max_active: 0,
+ _kind: PhantomData,
+ }
+ }
+}
+
+/// Options that may be used with all workqueue types.
+impl<Kind> Builder<Kind> {
+ /// Mark this workqueue high priority.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_unbound().highpri().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from highpri wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_HIGHPRI")]
+ pub fn highpri(mut self) -> Self {
+ self.flags |= bindings::wq_flags_WQ_HIGHPRI;
+ self
+ }
+
+ /// Creates the workqueue.
+ ///
+ /// The provided name is used verbatim as the workqueue name.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// // create an unbound workqueue registered with sysfs
+ /// let wq = Queue::new_unbound().sysfs().build(c"my-wq")?;
+ ///
+ /// // spawn a work item on it
+ /// wq.try_spawn(
+ /// GFP_KERNEL,
+ /// || pr_warn!("Printing from my-wq"),
+ /// )?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "alloc_workqueue")]
+ pub fn build(self, name: &CStr) -> Result<OwnedQueue, AllocError> {
+ // SAFETY:
+ // * c"%s" is compatible with passing the name as a c-string.
+ // * the builder only permits valid flag combinations
+ let ptr = unsafe {
+ bindings::alloc_workqueue(
+ c"%s".as_char_ptr(),
+ self.flags,
+ self.max_active,
+ name.as_char_ptr().cast::<c_void>(),
+ )
+ };
+
+ // INVARIANT: We successfully created the workqueue, so we can return ownership to the
+ // caller.
+ Ok(OwnedQueue {
+ queue: NonNull::new(ptr).ok_or(AllocError)?.cast(),
+ })
+ }
+
+ /// Creates the workqueue.
+ ///
+ /// # Examples
+ ///
+ /// This example shows how to pass a Rust string formatter to the workqueue name, creating
+ /// workqueues with names such as `my-wq-1` and `my-wq-2`.
+ ///
+ /// ```
+ /// use kernel::workqueue::{Queue, OwnedQueue};
+ ///
+ /// fn my_wq(num: u32) -> Result<OwnedQueue> {
+ /// // create a percpu workqueue called my-wq-{num}
+ /// let wq = Queue::new_percpu().build_fmt(fmt!("my-wq-{num}"))?;
+ /// Ok(wq)
+ /// }
+ /// ```
+ #[inline]
+ pub fn build_fmt(self, name: kernel::fmt::Arguments<'_>) -> Result<OwnedQueue, AllocError> {
+ // SAFETY:
+ // * c"%pA" is compatible with passing an `Arguments` pointer.
+ // * the builder only permits valid flag combinations
+ let ptr = unsafe {
+ bindings::alloc_workqueue(
+ c"%pA".as_char_ptr(),
+ self.flags,
+ self.max_active,
+ ptr::from_ref(&name).cast::<c_void>(),
+ )
+ };
+
+ // INVARIANT: We successfully created the workqueue, so we can return ownership to the
+ // caller.
+ Ok(OwnedQueue {
+ queue: NonNull::new(ptr).ok_or(AllocError)?.cast(),
+ })
+ }
+}
+
+/// Indicates that this workqueue is threaded.
+pub trait TypeThreaded {}
+impl TypeThreaded for TypeUnbound {}
+impl TypeThreaded for TypePercpu {}
+impl TypeThreaded for TypePowerEfficient {}
+
+/// Options that are not available on BH or ordered workqueues.
+impl<Kind: TypeThreaded> Builder<Kind> {
+ /// Set the maximum number of concurrently executing work items.
+ ///
+ /// For percpu workqueues this is per-CPU. If not set, a default value of
+ /// `WQ_DFL_ACTIVE` is used. The maximum value is `WQ_MAX_ACTIVE`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_unbound().max_active(16).build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from wq with max_active=16"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ pub fn max_active(mut self, max_active: u32) -> Self {
+ // If provided `max_active` is greater than `i32::MAX`, then we need to trigger the C-side
+ // comparison with `WQ_MAX_ACTIVE`, which we can do by clamping to `i32::MAX`.
+ self.max_active = i32::try_from(max_active).unwrap_or(i32::MAX);
+ self
+ }
+
+ /// Mark this workqueue as cpu intensive.
+ ///
+ /// Work items will not contribute to the concurrency level, preventing
+ /// them from stalling other work items in the same per-CPU worker pool.
+ /// This is meaningless for unbound workqueues.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_unbound().cpu_intensive().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from cpu-intensive wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_CPU_INTENSIVE")]
+ pub fn cpu_intensive(mut self) -> Self {
+ self.flags |= bindings::wq_flags_WQ_CPU_INTENSIVE;
+ self
+ }
+
+ /// Make this workqueue visible in sysfs.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_unbound().sysfs().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from sysfs wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_SYSFS")]
+ pub fn sysfs(mut self) -> Self {
+ self.flags |= bindings::wq_flags_WQ_SYSFS;
+ self
+ }
+}
+
+/// Indicates that this workqueue runs in a normal context (as opposed to softirq context).
+pub trait TypeNormal {}
+impl TypeNormal for TypeUnbound {}
+impl TypeNormal for TypePercpu {}
+impl TypeNormal for TypePowerEfficient {}
+impl TypeNormal for TypeOrdered {}
+
+/// Options that are not available on BH workqueues.
+impl<Kind: TypeNormal> Builder<Kind> {
+ /// Allow this workqueue to be frozen during suspend.
+ ///
+ /// Work items on the workqueue are drained and no new work items start
+ /// execution until thawed.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_unbound().freezable().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from freezable wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_FREEZABLE")]
+ pub fn freezable(mut self) -> Self {
+ self.flags |= bindings::wq_flags_WQ_FREEZABLE;
+ self
+ }
+
+ /// This workqueue may be used during memory reclaim.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_unbound().mem_reclaim().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from mem_reclaim wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_MEM_RECLAIM")]
+ pub fn mem_reclaim(mut self) -> Self {
+ self.flags |= bindings::wq_flags_WQ_MEM_RECLAIM;
+ self
+ }
+}
+
+/// Options only available on a power-efficient workqueue.
+impl Builder<TypePowerEfficient> {
+ /// Configure this power-efficient workqueue to be percpu.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::workqueue::Queue;
+ ///
+ /// let wq = Queue::new_power_efficient().percpu().build(c"my-wq")?;
+ /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from percpu power-efficient wq"))?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ #[inline]
+ #[doc(alias = "WQ_PERCPU")]
+ pub fn percpu(mut self) -> Self {
+ self.flags &= !bindings::wq_flags_WQ_UNBOUND;
+ self.flags |= bindings::wq_flags_WQ_PERCPU;
+ self
+ }
+}
diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
index b6e02ba427b3..747fea980aa2 100644
--- a/rust/kernel/workqueue/mod.rs
+++ b/rust/kernel/workqueue/mod.rs
@@ -186,7 +186,10 @@
//! C header: [`include/linux/workqueue.h`](srctree/include/linux/workqueue.h)
use crate::{
- alloc::{AllocError, Flags},
+ alloc::{
+ self,
+ AllocError, //
+ },
container_of,
prelude::*,
sync::{
@@ -200,7 +203,14 @@
time::Jiffies,
types::Opaque,
};
-use core::{marker::PhantomData, ptr::NonNull};
+use core::{
+ marker::PhantomData,
+ ops::Deref,
+ ptr::NonNull, //
+};
+
+mod builder;
+pub use self::builder::Builder;
/// Creates a [`Work`] initialiser with the given name and a newly-created lock class.
#[macro_export]
@@ -346,7 +356,7 @@ pub fn enqueue_delayed<W, const ID: u64>(
/// This method can fail because it allocates memory to store the work item.
pub fn try_spawn<T: 'static + Send + FnOnce()>(
&self,
- flags: Flags,
+ flags: alloc::Flags,
func: T,
) -> Result<(), AllocError> {
let init = pin_init!(ClosureWork {
@@ -359,6 +369,34 @@ pub fn try_spawn<T: 'static + Send + FnOnce()>(
}
}
+/// An owned kernel work queue.
+///
+/// Dropping a workqueue blocks on all pending work.
+///
+/// # Invariants
+///
+/// `queue` points at a valid workqueue that is owned by this `OwnedQueue`.
+pub struct OwnedQueue {
+ queue: NonNull<Queue>,
+}
+
+impl Deref for OwnedQueue {
+ type Target = Queue;
+ fn deref(&self) -> &Queue {
+ // SAFETY: By the type invariants, this pointer references a valid queue.
+ unsafe { &*self.queue.as_ptr() }
+ }
+}
+
+impl Drop for OwnedQueue {
+ fn drop(&mut self) {
+ // SAFETY: This `OwnedQueue` owns a valid workqueue, so we can destroy it. There is no
+ // delayed work scheduled on this queue that may attempt to use it after this call, as
+ // scheduling delayed work requires a 'static reference.
+ unsafe { bindings::destroy_workqueue(self.queue.as_ptr().cast()) }
+ }
+}
+
/// A helper type used in [`try_spawn`].
///
/// [`try_spawn`]: Queue::try_spawn
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 5/7] rust: workqueue: add ScopedQueue for lifetime bound items
2026-08-04 19:52 [PATCH 0/7] workqueue OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
` (3 preceding siblings ...)
2026-08-04 19:52 ` [PATCH 4/7] rust: workqueue: add creation of workqueues Danilo Krummrich
@ 2026-08-04 19:52 ` Danilo Krummrich
2026-08-04 19:52 ` [PATCH 6/7] rust: workqueue: add Work::cancel_work_sync() Danilo Krummrich
2026-08-04 19:52 ` [PATCH 7/7] rust: workqueue: add ScopedWork for non-'static work items Danilo Krummrich
6 siblings, 0 replies; 8+ messages in thread
From: Danilo Krummrich @ 2026-08-04 19:52 UTC (permalink / raw)
To: tj, jiangshanlai, aliceryhl, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross, daniel.almeida, tamird, acourbot,
work, jhubbard
Cc: rust-for-linux, linux-kernel, driver-core, Danilo Krummrich
From: Onur Özkan <work@onurozkan.dev>
Add a workqueue wrapper for work items that are not 'static.
Tyr reset work is queued from a handle that owns a Controller<'bound>
where the work item holds references tied to the lifetime of the bound
device and its mapped IO state. The existing API only accepts 'static
work items which cannot express that relationship.
Introduce ScopedQueue for this case. It owns the underlying workqueue
and ties enqueued work to the queue lifetime so borrowed state cannot
outlive the queue that may still run it.
Construction is unsafe because the queue must not be leaked.
`compile_fail` doc-tests are ignored for now as KUnit doesn't support
that. Enabling those tests as regular code block would raise this error:
ERROR:root:error[E0597]: `data` does not live long enough
--> rust/doctests_kernel_generated.rs:22029:44
|
22027 | let data = ();
| ---- binding `data` declared here
22028 | // SAFETY: Queue is not leaked.
22029 | queue = unsafe { new_queue(&data)? };
| ^^^^^ borrowed value does not live long enough
22030 | }
| - `data` dropped here while still borrowed
...
22034 | }
| - borrow might be used here, when `queue` is dropped and runs the `Drop` code for type `ScopedQueue`
|
= note: values in a scope are dropped in the opposite order they are defined
which is exactly the constraint ScopedQueue is meant to enforce.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Onur Özkan <work@onurozkan.dev>
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
rust/kernel/workqueue/mod.rs | 3 +
rust/kernel/workqueue/scoped_queue.rs | 187 ++++++++++++++++++++++++++
2 files changed, 190 insertions(+)
create mode 100644 rust/kernel/workqueue/scoped_queue.rs
diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
index 747fea980aa2..5de88c59b2e5 100644
--- a/rust/kernel/workqueue/mod.rs
+++ b/rust/kernel/workqueue/mod.rs
@@ -212,6 +212,9 @@
mod builder;
pub use self::builder::Builder;
+mod scoped_queue;
+pub use self::scoped_queue::ScopedQueue;
+
/// Creates a [`Work`] initialiser with the given name and a newly-created lock class.
#[macro_export]
macro_rules! new_work {
diff --git a/rust/kernel/workqueue/scoped_queue.rs b/rust/kernel/workqueue/scoped_queue.rs
new file mode 100644
index 000000000000..d4b9a03fd93c
--- /dev/null
+++ b/rust/kernel/workqueue/scoped_queue.rs
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Lifetime-scoped workqueues.
+//!
+//! Provides [`ScopedQueue`] 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.
+//!
+//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
+//! ```compile_fail,ignore
+//! use kernel::prelude::*;
+//! use kernel::workqueue::ScopedQueue;
+//!
+//! /// # Safety
+//! ///
+//! /// Returned queue must not be leaked.
+//! unsafe fn new_queue<'bound>(_: &'bound ()) -> Result<ScopedQueue<'bound>> {
+//! // SAFETY: Caller guarantees that the returned queue is not leaked.
+//! unsafe { ScopedQueue::new(c"scoped_queue") }
+//! }
+//!
+//! fn queue_outlives_borrowed_data() -> Result {
+//! let queue;
+//!
+//! {
+//! let data = ();
+//! // SAFETY: Queue is not leaked.
+//! queue = unsafe { new_queue(&data)? };
+//! }
+//! // Here the `compile_fail` is fulfilled as `queue` would be dropped
+//! // after `data`.
+//! Ok(())
+//! }
+//! ```
+//!
+//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
+//! ```compile_fail,ignore
+//! use kernel::prelude::*;
+//! use kernel::sync::Arc;
+//! use kernel::workqueue::{
+//! impl_has_work,
+//! new_work,
+//! ScopedQueue,
+//! Work,
+//! WorkItem,
+//! };
+//!
+//! #[pin_data]
+//! struct BorrowedWork<'bound> {
+//! data: &'bound (),
+//! #[pin]
+//! work: Work<BorrowedWork<'bound>>,
+//! }
+//!
+//! impl_has_work! {
+//! impl{'bound} HasWork<BorrowedWork<'bound>> for BorrowedWork<'bound> { self.work }
+//! }
+//!
+//! impl<'bound> WorkItem for BorrowedWork<'bound> {
+//! type Pointer = Arc<Self>;
+//!
+//! fn run(_this: Arc<Self>) {}
+//! }
+//!
+//! impl<'bound> BorrowedWork<'bound> {
+//! fn new(data: &'bound ()) -> Result<Arc<Self>> {
+//! Arc::pin_init(
+//! pin_init!(Self {
+//! data,
+//! work <- new_work!("BorrowedWork::work"),
+//! }),
+//! GFP_KERNEL,
+//! )
+//! }
+//! }
+//!
+//! struct Handle<'bound> {
+//! work: Arc<BorrowedWork<'bound>>,
+//! wq: ScopedQueue<'bound>,
+//! }
+//!
+//! impl<'bound> Handle<'bound> {
+//! /// # Safety
+//! ///
+//! /// Returned handle must not be leaked.
+//! unsafe fn new(data: &'bound ()) -> Result<Self> {
+//! Ok(Self {
+//! work: BorrowedWork::new(data)?,
+//! // SAFETY: Caller guarantees that the returned handle is not leaked.
+//! wq: unsafe { ScopedQueue::new(c"handle_wq")? },
+//! })
+//! }
+//! }
+//!
+//! fn handle_outlives_borrowed_data() -> Result {
+//! let handle;
+//!
+//! {
+//! let data = ();
+//! // SAFETY: Handle is not leaked.
+//! handle = unsafe { Handle::new(&data)? };
+//!
+//! let _ = handle.wq.enqueue(handle.work.clone());
+//! }
+//! // Here the `compile_fail` is fulfilled as `handle` would be dropped
+//! // after `data`.
+//! Ok(())
+//! }
+//! ```
+
+use super::{
+ OwnedQueue,
+ Queue,
+ RawWorkItem, //
+};
+
+use crate::{
+ bindings,
+ ffi,
+ prelude::*, //
+};
+
+use core::marker::PhantomData;
+
+/// An owned workqueue that can enqueue work items borrowing from `'scope`.
+///
+/// A `ScopedQueue` must not outlive data borrowed by its work items.
+pub struct ScopedQueue<'scope> {
+ inner: OwnedQueue,
+ _scope: PhantomData<&'scope mut &'scope ()>,
+}
+
+impl<'scope> ScopedQueue<'scope> {
+ /// Creates an ordered scoped workqueue.
+ ///
+ /// # Safety
+ ///
+ /// The caller must not leak the returned queue or otherwise prevent its
+ /// [`Drop`] implementation from running since dropping the queue drains
+ /// pending and running work that may borrow from `'scope`.
+ pub unsafe fn new(name: &'static CStr) -> Result<Self> {
+ Ok(Self {
+ inner: Queue::new_ordered().build(name)?,
+ _scope: PhantomData,
+ })
+ }
+
+ /// Enqueues a work item on this scoped queue.
+ 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,
+ )
+ })
+ }
+ }
+}
+
+impl Drop for ScopedQueue<'_> {
+ fn drop(&mut self) {
+ // This impl makes dropck require `'scope` to outlive `OwnedQueue`.
+ // See: https://doc.rust-lang.org/nomicon/phantom-data.html#generic-parameters-and-drop-checking
+ let _ = &self._scope;
+ }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6/7] rust: workqueue: add Work::cancel_work_sync()
2026-08-04 19:52 [PATCH 0/7] workqueue OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
` (4 preceding siblings ...)
2026-08-04 19:52 ` [PATCH 5/7] rust: workqueue: add ScopedQueue for lifetime bound items Danilo Krummrich
@ 2026-08-04 19:52 ` Danilo Krummrich
2026-08-04 19:52 ` [PATCH 7/7] rust: workqueue: add ScopedWork for non-'static work items Danilo Krummrich
6 siblings, 0 replies; 8+ messages in thread
From: Danilo Krummrich @ 2026-08-04 19:52 UTC (permalink / raw)
To: tj, jiangshanlai, aliceryhl, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross, daniel.almeida, tamird, acourbot,
work, jhubbard
Cc: rust-for-linux, linux-kernel, driver-core, Danilo Krummrich
Add a method to cancel a work item and wait for it to finish if it is
currently running.
This will also be used by ScopedWork's destructor to synchronously
cancel work before dropping borrowed data.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
rust/kernel/workqueue/mod.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
index 5de88c59b2e5..2b87f935712a 100644
--- a/rust/kernel/workqueue/mod.rs
+++ b/rust/kernel/workqueue/mod.rs
@@ -585,6 +585,14 @@ pub unsafe fn raw_get(ptr: *const Self) -> *mut bindings::work_struct {
// the compiler does not complain that the `work` field is unused.
unsafe { Opaque::cast_into(core::ptr::addr_of!((*ptr).work)) }
}
+
+ /// Cancels the work item and waits for it to finish if it is running.
+ ///
+ /// Returns `true` if the work was pending, `false` otherwise.
+ pub fn cancel_work_sync(&self) -> bool {
+ // SAFETY: We have a reference to a valid, initialized Work, so the pointer is valid.
+ unsafe { bindings::cancel_work_sync(Self::raw_get(self)) }
+ }
}
/// Declares that a type contains a [`Work<T, ID>`].
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 7/7] rust: workqueue: add ScopedWork for non-'static work items
2026-08-04 19:52 [PATCH 0/7] workqueue OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
` (5 preceding siblings ...)
2026-08-04 19:52 ` [PATCH 6/7] rust: workqueue: add Work::cancel_work_sync() Danilo Krummrich
@ 2026-08-04 19:52 ` Danilo Krummrich
6 siblings, 0 replies; 8+ messages in thread
From: Danilo Krummrich @ 2026-08-04 19:52 UTC (permalink / raw)
To: tj, jiangshanlai, aliceryhl, ojeda, boqun, gary, bjorn3_gh,
lossin, a.hindborg, tmgross, daniel.almeida, tamird, acourbot,
work, jhubbard
Cc: rust-for-linux, linux-kernel, driver-core, Danilo Krummrich
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<Self> as WorkItem::Pointer for the callback path, and implements
RawWorkItem for Pin<&ScopedWork<T>> for the enqueue path (requiring T:
Sync for cross-thread shared access safety).
Two enqueue paths are provided:
- Queue::enqueue_scoped() / ScopedQueue::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 | 43 +++-
rust/kernel/workqueue/scoped_queue.rs | 37 +++-
rust/kernel/workqueue/scoped_work.rs | 290 ++++++++++++++++++++++++++
3 files changed, 348 insertions(+), 22 deletions(-)
create mode 100644 rust/kernel/workqueue/scoped_work.rs
diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
index 2b87f935712a..11477d42020b 100644
--- a/rust/kernel/workqueue/mod.rs
+++ b/rust/kernel/workqueue/mod.rs
@@ -215,6 +215,13 @@
mod scoped_queue;
pub use self::scoped_queue::ScopedQueue;
+mod scoped_work;
+pub use self::scoped_work::{
+ new_scoped_work,
+ ScopedWork,
+ ScopedWorkItem, //
+};
+
/// Creates a [`Work`] initialiser with the given name and a newly-created lock class.
#[macro_export]
macro_rules! new_work {
@@ -286,20 +293,34 @@ pub unsafe fn from_raw<'a>(ptr: *const bindings::workqueue_struct) -> &'a Queue
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).
+ 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_queue.rs b/rust/kernel/workqueue/scoped_queue.rs
index d4b9a03fd93c..21928d60a63b 100644
--- a/rust/kernel/workqueue/scoped_queue.rs
+++ b/rust/kernel/workqueue/scoped_queue.rs
@@ -152,20 +152,35 @@ pub unsafe fn new(name: &'static CStr) -> Result<Self> {
pub fn enqueue<W, const ID: u64>(&self, work: W) -> W::EnqueueOutput
where
W: RawWorkItem<ID> + Send + 'scope,
+ {
+ // 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) }
+ }
+
+ /// Enqueues a work item without the `'scope` lifetime bound.
+ ///
+ /// Unlike [`ScopedQueue::enqueue`], this does not require `W: 'scope`.
+ ///
+ /// Prefer [`ScopedQueue::enqueue`] when the work item's lifetime satisfies
+ /// `'scope`.
+ ///
+ /// # 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).
+ pub unsafe fn enqueue_scoped<W, const ID: u64>(&self, work: W) -> W::EnqueueOutput
+ where
+ W: RawWorkItem<ID> + Send,
{
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`.
+ // SAFETY: The caller guarantees the work item remains valid until the work
+ // function runs or the item is cancelled. `W: Send` satisfies the
+ // cross-thread safety requirement. The closure only returns `false` if the
+ // `work_struct` is already in a workqueue.
unsafe {
work.__enqueue(move |work_ptr| {
bindings::queue_work_on(
diff --git a/rust/kernel/workqueue/scoped_work.rs b/rust/kernel/workqueue/scoped_work.rs
new file mode 100644
index 000000000000..bdc834a9a08d
--- /dev/null
+++ b/rust/kernel/workqueue/scoped_work.rs
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Scoped work items.
+//!
+//! Provides [`ScopedWork`] for work items whose inner data may carry non-`'static` lifetimes.
+//!
+//! Unlike [`Work`]-based work items, [`ScopedWork`] cancels work synchronously on drop via
+//! `cancel_work_sync()`, so ownership of the data is not transferred to the workqueue.
+//!
+//! # 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,
+//! };
+//!
+//! struct MyWork {
+//! value: u32,
+//! }
+//!
+//! impl ScopedWorkItem for MyWork {
+//! fn run(self: Pin<&Self>) {
+//! pr_info!("value = {}\n", self.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) };
+//! # // Allow the worker thread to pick up and execute the 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,
+//! };
+//!
+//! struct MyWork {
+//! value: u32,
+//! }
+//!
+//! impl ScopedWorkItem for MyWork {
+//! fn run(self: Pin<&Self>) {
+//! pr_info!("value = {}\n", self.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>(())
+//! ```
+
+use super::{
+ impl_has_work,
+ HasWork,
+ RawWorkItem,
+ Work,
+ WorkItem,
+ WorkItemPointer, //
+};
+
+use crate::{
+ bindings,
+ prelude::*,
+ sync::LockClassKey, //
+};
+
+use core::{
+ ops::Deref,
+ ptr::NonNull, //
+};
+
+/// 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 shared pinned reference; mutation should use interior mutability
+/// (e.g., [`Mutex`](crate::sync::Mutex)).
+pub trait ScopedWorkItem {
+ /// Called when the work item is executed.
+ fn run(self: Pin<&Self>);
+}
+
+// SAFETY: The `run` callback uses the `work_struct` pointer to recover a pointer to `ScopedWork<T>`
+// via `HasWork`, wraps it in `NonNull`, and calls `WorkItem::run`.
+unsafe impl<T: ScopedWorkItem, const ID: u64> WorkItemPointer<ID> for NonNull<ScopedWork<T>>
+where
+ ScopedWork<T>: WorkItem<ID, Pointer = Self>,
+ ScopedWork<T>: HasWork<ScopedWork<T>, ID>,
+{
+ unsafe extern "C" fn run(ptr: *mut bindings::work_struct) {
+ let ptr = ptr.cast::<Work<ScopedWork<T>, ID>>();
+
+ // SAFETY: The `work_struct` is embedded in `ScopedWork<T>` via `HasWork`.
+ let ptr = unsafe { <ScopedWork<T> as HasWork<ScopedWork<T>, ID>>::work_container_of(ptr) };
+
+ // SAFETY: `work_container_of` returns a valid, non-null pointer.
+ let nn = unsafe { NonNull::new_unchecked(ptr) };
+
+ <ScopedWork<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>`
+// instead.
+//
+// SAFETY: `__enqueue` is unreachable.
+unsafe impl<T: ScopedWorkItem, const ID: u64> RawWorkItem<ID> for NonNull<ScopedWork<T>>
+where
+ ScopedWork<T>: HasWork<ScopedWork<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: `&ScopedWork<T>` points to a valid, pinned `ScopedWork` with a valid `work_struct`.
+// The pointer remains valid until `cancel_work_sync()` completes.
+unsafe impl<'a, T: ScopedWorkItem + Sync, const ID: u64> RawWorkItem<ID> for &'a ScopedWork<T>
+where
+ ScopedWork<T>: HasWork<ScopedWork<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 `ScopedWork` with a `Work` field.
+ let work_ptr = unsafe {
+ <ScopedWork<T> as HasWork<ScopedWork<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)
+ }
+}
+
+/// A scoped work item that cancels synchronously on drop.
+///
+/// `ScopedWork<T>` contains both 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.
+#[pin_data(PinnedDrop)]
+pub struct ScopedWork<T: ScopedWorkItem> {
+ #[pin]
+ work: Work<Self>,
+ #[pin]
+ data: T,
+}
+
+impl_has_work! {
+ impl{T: ScopedWorkItem} HasWork<ScopedWork<T>> for ScopedWork<T> { self.work }
+}
+
+impl<T: ScopedWorkItem> WorkItem for ScopedWork<T> {
+ type Pointer = NonNull<Self>;
+
+ fn run(this: NonNull<Self>) {
+ // SAFETY: `this` points to a valid, pinned `ScopedWork`. `cancel_work_sync()` in
+ // `PinnedDrop` prevents use-after-drop.
+ let data = unsafe { Pin::new_unchecked(&(*this.as_ptr()).data) };
+
+ T::run(data);
+ }
+}
+
+impl<T: ScopedWorkItem> Deref for ScopedWork<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &self.data
+ }
+}
+
+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.
+ 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 {
+ work <- Work::new(name, key),
+ data <- init,
+ })
+ }
+
+ /// Synchronously cancels pending work and waits for any running work function to complete.
+ ///
+ /// Returns `true` if the work was pending, `false` otherwise.
+ pub fn cancel_work_sync(&self) -> bool {
+ self.work.cancel_work_sync()
+ }
+}
+
+#[pinned_drop]
+impl<T: ScopedWorkItem> PinnedDrop for ScopedWork<T> {
+ fn drop(self: Pin<&mut Self>) {
+ self.cancel_work_sync();
+ }
+}
+
+/// Creates a [`ScopedWork`] pin-initializer with a new lock class.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::workqueue::{
+/// new_scoped_work,
+/// ScopedWork,
+/// ScopedWorkItem,
+/// };
+///
+/// struct MyWork {
+/// value: u32,
+/// }
+///
+/// impl ScopedWorkItem for MyWork {
+/// fn run(self: Pin<&Self>) {
+/// pr_info!("value = {}\n", self.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
^ permalink raw reply related [flat|nested] 8+ messages in thread