From nobody Tue Sep 29 13:18:39 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 52BD133938E; Fri, 7 Aug 2026 16:53:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121586; cv=none; b=f/IIsNw1CnxHlkDSPm2S4mH/UXzPJMz56At0o67DnwyRdhURypuoi70Kkio6NAHyKg8RKCuNOuIWAeM7KcpBNLkBe5bhJpriZLGj0pYzU5uwMvD13v3oeEhs39vnhuOkL1ngvRX6J+vSuBL3VftUh6c+wVdydoZcBaViiZ3Z1Ok= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121586; c=relaxed/simple; bh=tQG+wFTH8tV0Rv4l2ySoGI48KmoiZE6vXuY1apmDyAw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=S8wj28B6JllipH4c2rRJyZunXCD45SyWaFy+IIPfFWlirjlgQWamb//d06kEpxEoI9obI73kBSksOsQkP1CNVgJoMS2Ft7TAr+qGjnFw2/JLq4/O2ASbmUCnwJD8m+sSwdVsWOrm9/vlDUBd0OY6E7iU/yPKvA85Ix04Dhqhn3U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=PYbQvZOm; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="PYbQvZOm" Received: by smtp.kernel.org (Postfix) with ESMTPSA id F3B0A1F00A3A; Fri, 7 Aug 2026 16:52:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786121584; bh=h7uQ+Jf+M59X9jSqV4epQWoeZKccCRQt2D+d94V16sA=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=PYbQvZOmWP7m9z/Ksh/yvIyU1oPDv5Aut7BweqwpnwEYjMRFv9G89qmbf+pNKxTBV skzuhUQQHpr56wv87tONFbG1mDdEn5NYB+bBcniR7XWHPZ6/oWJQAitoxDeZpAa5q5 laR4RgDcIsYWoAfy38umFNvRHUhe+s+X4E8eKwfHwU8ZR7gnniYMHmZsXLvm0Kr7Q5 phPVT1gUyT457ZyRXX3WtZX1efF1ujhc4lr5fwomHiwjEt6R7w3Gac6jjZ1/n5YxbH UO6gYrUhrYS/RL36slbHAzrKH6ou0VXzsf1im478TYAMYlBJtNBioicYt7XEqWAcuB FhpyEmjFjykgQ== From: Danilo Krummrich 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 Subject: [PATCH v2 1/6] rust: workqueue: replace deprecated system_wq with system_{percpu,dfl}_wq Date: Fri, 7 Aug 2026 18:52:44 +0200 Message-ID: <20260807165252.3849875-2-dakr@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260807165252.3849875-1-dakr@kernel.org> References: <20260807165252.3849875-1-dakr@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable system_wq is deprecated and triggers a runtime warning: [ 0.857414] workqueue: work func ...WorkItemPointerKy0_E3runB7_ enqueue= d 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 Reviewed-by: Daniel Almeida --- Documentation/rust/testing.rst | 2 +- .../translations/zh_CN/rust/testing.rst | 2 +- drivers/android/binder/process.rs | 4 +- rust/kernel/sync/completion.rs | 2 +- rust/kernel/workqueue.rs | 42 ++++++++++++------- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Documentation/rust/testing.rst b/Documentation/rust/testing.rst index e3943aceceb9..73046523a9a2 100644 --- a/Documentation/rust/testing.rst +++ b/Documentation/rust/testing.rst @@ -97,7 +97,7 @@ operator are also supported as usual, e.g.: =20 /// ``` /// # use kernel::{spawn_work_item, workqueue}; - /// spawn_work_item!(workqueue::system(), || pr_info!("x\n"))?; + /// spawn_work_item!(workqueue::system_dfl(), || pr_info!("x\n"))?; /// # Ok::<(), Error>(()) /// ``` =20 diff --git a/Documentation/translations/zh_CN/rust/testing.rst b/Documentat= ion/translations/zh_CN/rust/testing.rst index ca81f1cef6eb..5fdd553f2b41 100644 --- a/Documentation/translations/zh_CN/rust/testing.rst +++ b/Documentation/translations/zh_CN/rust/testing.rst @@ -93,7 +93,7 @@ KUnit =E6=B5=8B=E8=AF=95=E5=8D=B3=E6=96=87=E6=A1=A3=E6=B5= =8B=E8=AF=95 =20 /// ``` /// # use kernel::{spawn_work_item, workqueue}; - /// spawn_work_item!(workqueue::system(), || pr_info!("x\n"))?; + /// spawn_work_item!(workqueue::system_dfl(), || pr_info!("x\n"))?; /// # Ok::<(), Error>(()) /// ``` =20 diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/pro= cess.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, _file: &Fil= e) { if should_schedule { // Ignore failures to schedule to the workqueue. Those just me= an that we're already // scheduled for execution. - let _ =3D workqueue::system().enqueue(this); + let _ =3D workqueue::system_percpu().enqueue(this); } =20 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 me= an that we're already // scheduled for execution. - let _ =3D workqueue::system().enqueue(Arc::from(this)); + let _ =3D 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 _ =3D workqueue::system().enqueue(this.clone()); +/// let _ =3D workqueue::system_dfl().enqueue(this.clone()); /// /// Ok(this) /// } diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs index 7e253b6f299c..7bc07ccd1ebb 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 wo= rkqueue, where its value //! /// will be printed. //! fn print_later(val: Arc) { -//! let _ =3D workqueue::system().enqueue(val); +//! let _ =3D workqueue::system_dfl().enqueue(val); //! } //! # print_later(MyStruct::new(42).unwrap()); //! ``` @@ -121,11 +121,11 @@ //! } //! //! fn print_1_later(val: Arc) { -//! let _ =3D workqueue::system().enqueue::, 1>(val); +//! let _ =3D workqueue::system_dfl().enqueue::, 1>(val); //! } //! //! fn print_2_later(val: Arc) { -//! let _ =3D workqueue::system().enqueue::, 2>(val); +//! let _ =3D workqueue::system_dfl().enqueue::, 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 wo= rkqueue, where its value //! /// will be printed 12 jiffies later. //! fn print_later(val: Arc) { -//! let _ =3D workqueue::system().enqueue_delayed(val, 12); +//! let _ =3D 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) { -//! let _ =3D workqueue::system().enqueue(val); +//! let _ =3D workqueue::system_dfl().enqueue(val); //! } //! # print_later(MyStruct::new(42).unwrap()); //! # print_now(MyStruct::new(42).unwrap()); @@ -1024,20 +1024,32 @@ unsafe impl RawDelayedWorkItem for ARef { } =20 -/// 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 mult= i-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) } +#[inline] +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, an= d all queued work items +/// are executed immediately as long as `max_active` limit is not reached = and resources are +/// available. +#[inline] +pub fn system_dfl() -> &'static Queue { + // SAFETY: `system_dfl_wq` is a C global, always available. + unsafe { Queue::from_raw(bindings::system_dfl_wq) } } =20 /// Returns the system high-priority work queue (`system_highpri_wq`). /// -/// It is similar to the one returned by [`system`] but for work items whi= ch require higher +/// It is similar to the one returned by [`system_percpu`] but for work it= ems which require higher /// scheduling priority. pub fn system_highpri() -> &'static Queue { // SAFETY: `system_highpri_wq` is a C global, always available. @@ -1046,8 +1058,8 @@ pub fn system_highpri() -> &'static Queue { =20 /// 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 runn= ing work items. Queue -/// flushing might take relatively long. +/// It is similar to the one returned by [`system_percpu`] but may host lo= ng 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 +1077,7 @@ pub fn system_unbound() -> &'static Queue { =20 /// Returns the system freezable work queue (`system_freezable_wq`). /// -/// It is equivalent to the one returned by [`system`] except that it's fr= eezable. +/// 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 s= uspend operations. Work /// items on the workqueue are drained and no new work item starts executi= on until thawed. @@ -1078,7 +1090,7 @@ pub fn system_freezable() -> &'static Queue { /// /// It is inclined towards saving power and is converted to "unbound" vari= ants 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 +1109,7 @@ pub fn system_freezable_power_efficient() -> &'static= Queue { =20 /// Returns the system bottom halves work queue (`system_bh_wq`). /// -/// It is similar to the one returned by [`system`] but for work items whi= ch +/// It is similar to the one returned by [`system_percpu`] but for work it= ems which /// need to run from a softirq context. pub fn system_bh() -> &'static Queue { // SAFETY: `system_bh_wq` is a C global, always available. --=20 2.55.0 From nobody Tue Sep 29 13:18:39 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E0FFF37C11C; Fri, 7 Aug 2026 16:53:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121592; cv=none; b=otyAIYLOdzNSc5+H2yeoPHr6RrvfzwJHHxj28YMzlzhpmcW2z8NoBRefMrrSFRPhhZeaJq/Jjl0oL+0+3C00H2kZas388ClvPFAgXKYdUmSpQnGVSAvOBnQ2uEIQN/rjoDxj3mxi02skL09YwOpzC7jD/wIZN8FjwE+KW7QLaxw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121592; c=relaxed/simple; bh=PCxHtdmeKUBVXjZ6z/+UpqGA8bi2tfy2YstFfqIzA28=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qi8mjYQ+npHjr9/C89xciCk7XLz2Uf4KBkclnD772GVkq0OQWIDlcrOCiDhsm4Lf8RrfwejbugURMdf5AF8BYXjIOps6bjglMoqw5uAiexqyBID2obdeFnq4l2vPl0aQH2PjsrCk63xhNGUusysMs8XHt4tW9yHON/md6imiG74= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Fg0Lw/QX; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Fg0Lw/QX" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 5B85F1F000E9; Fri, 7 Aug 2026 16:53:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786121590; bh=TuCyYKvBUtBSmQ9IKU9O1/6HYi2d9Xj+Lt3PXMMi62g=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Fg0Lw/QXoVNJ0XJK2StKNtliv8gkWxtKvDCYg9qUgvVFOA5RStpf8o/qe/QVgy6qK sZoe0xKnVf4WFkyTQ5oEI+CSpWF5y8ROSJFfh3Bnu6/n3tWCql2o3oSDaO6Pye7Ub9 e760Mp/R9KNuhSxQ8LH+zlIT5G/V/+pblYczQ4lRXft7z/UaLkTmkFC3JtjGKtWl2F bP3eRaLxkz21HScdsKAbtuFPQi/SImL2dpuhtR+Scjl5nOyiuKU4Ab9hBFb+DyQzM7 LApTs0p3FnCBYfbpWD0gNyxG0TheN34kE0TmLXIuFeeN8Xb4lFQFzP0byTbKx7nhQ/ j0rGMDgB8gs+g== From: Danilo Krummrich 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, stable@vger.kernel.org, Danilo Krummrich Subject: [PATCH v2 2/6] rust: workqueue: restrict delayed work to global wqs Date: Fri, 7 Aug 2026 18:52:45 +0200 Message-ID: <20260807165252.3849875-3-dakr@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260807165252.3849875-1-dakr@kernel.org> References: <20260807165252.3849875-1-dakr@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alice Ryhl 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 Reviewed-by: Danilo Krummrich Reviewed-by: Gary Guo Link: https://lore.kernel.org/r/20250423-destroy-workqueue-flush-v1-1-3d748= 20780a5@google.com [1] Signed-off-by: Alice Ryhl Reviewed-by: Andreas Hindborg Signed-off-by: Danilo Krummrich Reviewed-by: Daniel Almeida --- 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 7bc07ccd1ebb..399d2ed24135 100644 --- a/rust/kernel/workqueue.rs +++ b/rust/kernel/workqueue.rs @@ -302,8 +302,15 @@ pub fn enqueue(&self, w: W) -> W::En= queueOutput /// /// This may fail if the work item is already enqueued in a workqueue. /// + /// This is only valid for global workqueues (with static lifetimes) b= ecause 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(&self, w: W, delay: Jiffies) = -> W::EnqueueOutput + pub fn enqueue_delayed( + &'static self, + w: W, + delay: Jiffies, + ) -> W::EnqueueOutput where W: RawDelayedWorkItem + Send + 'static, { --=20 2.55.0 From nobody Tue Sep 29 13:18:39 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9F9AE38AC78; Fri, 7 Aug 2026 16:53:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121596; cv=none; b=Eq5SH6DTMsuvpbAJBi+KB3tLZOhs1Tp27P/PTjqBjf66NEfoL8Q7UTzwDbxqZI6wz51J9htfyf2Xignn6CA6Oev/54Qt4TC/h3KM21CiJGY3BZCfvnLL2z/UbuDiQT1GK0UogORVeHmH469CInNz13cEiFyP/tDu8QnkLgSPkMw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121596; c=relaxed/simple; bh=6+gjh4Gvuj/YByD6T4ssZ+a7Z2aa8iuzagit5WYBSzc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=n3ThIgwJAGrRbDP1EGpKHk9FTo78+FJ6q+DNz9LXCFHnGadQ07dJ968ZKqigE0S6CN2PrrMzUE8zhMHs2sjywZvqqdHL4S8dymF3K+qQ+5sje+esAbKT6/vAXw7WKrZN4mkpxqZdubZKrSI85ebhkcXTUYohhaiZjBxzDWCVzmE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Yt6jcyIv; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Yt6jcyIv" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 2B5B71F00A3A; Fri, 7 Aug 2026 16:53:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786121595; bh=Q3IT0ymdhSoxgrdPIf0dd7pDLblnnETkkoNTCHMHVzg=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Yt6jcyIvaiFn8+Q7fzkLmp/HDVwRwEngnIO5qYGdMVhMI62db/LqbJkxOZm/mF3NY 1dwkA49P18mLrPAbgj0B26AgO1lie4Wrla/OmcRs/B92aUWD9srA9YAdSHSZGqFnOx pNwk+hQ5uOmF5dWipMuK2Bnh14Aw+0frXCaK/VjwYWvVSMF/2n/8iwFmjozh12S2qS 44C8Sb/R0F6AUioBWsnkiBVSQcEFeveLSF5dNfTJdIpu8UHacKMF83b1hnDSakcjii 2ptiCXzvKGI/EJH1TIs1eDfiLXI3RoSrifrKj+GuulPnOmjooOAk895Ui8FuFY7Eqz PncScXU/o0D6w== From: Danilo Krummrich 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 Subject: [PATCH v2 3/6] rust: workqueue: create workqueue subdirectory Date: Fri, 7 Aug 2026 18:52:46 +0200 Message-ID: <20260807165252.3849875-4-dakr@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260807165252.3849875-1-dakr@kernel.org> References: <20260807165252.3849875-1-dakr@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alice Ryhl 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 Signed-off-by: Danilo Krummrich Reviewed-by: Daniel Almeida --- MAINTAINERS | 1 + rust/kernel/{workqueue.rs =3D> workqueue/mod.rs} | 0 2 files changed, 1 insertion(+) rename rust/kernel/{workqueue.rs =3D> 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/ =20 WWAN DRIVERS M: Loic Poulain 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 --=20 2.55.0 From nobody Tue Sep 29 13:18:39 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 176C8313527; Fri, 7 Aug 2026 16:53:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121602; cv=none; b=lplfAMVnhELrSbTBr95IGzkiA1pWisfEuooXnRPw6Vip9poxXjU6ALwZkuSDwTEk06in5gl6Y/gVDw/I8Du4fm4gzqBxRYP6k0MvMbg4GGoyD7img4kO1aHsWQXyxmVKGZ6nEinpBhPE1+6cTFv2DZQf3Q8MXoO9SbinHn/MtaU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121602; c=relaxed/simple; bh=VshsSwNDeXW1qvODIB3pbB+f7tAuM6dzpEdyNdHPnnU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=LLOGLVG40AP5QMFTmUhisr+TT09My22Oh9MO+9UsuOHLGzeUdrmSUOUepeMIsGGJg+o3HjzTvRhsbEYTn4oeMWOm8Dnzj8Y2tdnmTjJqcOsg1sZn1YUq4TKjVf5vQXy/kaz2AiQlQYHKW6m70mOP/3oWbtREmuMyz+/VRcPQX8w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=NPCXLpk5; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="NPCXLpk5" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B44571F000E9; Fri, 7 Aug 2026 16:53:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786121600; bh=dciya5lMnm+jiQ3SsM0SyBhI3w9EpgFJ7aqc0y/koVo=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=NPCXLpk5KTFojUtG+jV4prBStqMatqdVhW65cStHCjj9kpRxNZy6Xx/aARraotjTc 7CmbvdIoFK6NIYGKaEtyNeV8w6/zUHYRdTN265S5myORUX8TTXG/jmjVQrqL7kr/v9 PWSrHqM3xb8adCKCINdTccW+aT1jHaJZg+YAyZ0DZ/LUDuc3ekzE3XmbukEABMyTG1 MNl+vPqBE+tNW0rJTHXoK9FZNxVAOYIjjX5lStFV+WE5JZ/8zeg1R4xwcAI48QEDMv w4HfhpWUOyCqx+e/Aqiq7sybYxdsWs3uNNYNsG8s7YuOPLf8CyELxg4CzRvx8MEqQG M4QzehDXqrlbw== From: Danilo Krummrich 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 Subject: [PATCH v2 4/6] rust: workqueue: add creation of workqueues Date: Fri, 7 Aug 2026 18:52:47 +0200 Message-ID: <20260807165252.3849875-5-dakr@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260807165252.3849875-1-dakr@kernel.org> References: <20260807165252.3849875-1-dakr@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alice Ryhl 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 Reviewed-by: Andreas Hindborg [ * 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(), * move sysfs() to TypeNormal, * impl Send + Sync for OwnedQueue, * add missing inline annotations, * various doc improvements: new_percpu, max_active, cpu_intensive, freezable. - Danilo ] Signed-off-by: Danilo Krummrich Reviewed-by: Daniel Almeida --- rust/helpers/workqueue.c | 7 + rust/kernel/workqueue/builder.rs | 389 +++++++++++++++++++++++++++++++ rust/kernel/workqueue/mod.rs | 51 +++- 3 files changed, 444 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 =3D func; } + +__rust_helper +struct workqueue_struct *rust_helper_alloc_workqueue(const char *fmt, unsi= gned 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/build= er.rs new file mode 100644 index 000000000000..673b121483ec --- /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 compatibl= e with the selected base +/// flag. +/// +/// For details, please refer to `Documentation/core-api/workqueue.rst`. +pub struct Builder { + flags: bindings::wq_flags, + max_active: i32, + _kind: PhantomData, +} + +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 =3D Queue::new_unbound().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from unbound wq"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "WQ_UNBOUND")] + pub fn new_unbound() -> Builder { + 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 q= ueued on. + /// + /// # Examples + /// + /// ``` + /// use kernel::workqueue::Queue; + /// + /// let wq =3D Queue::new_percpu().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from percpu wq"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "WQ_PERCPU")] + pub fn new_percpu() -> Builder { + Builder { + flags: bindings::wq_flags_WQ_PERCPU, + max_active: 0, + _kind: PhantomData, + } + } + + /// Build a power-efficient workqueue. + /// + /// # Examples + /// + /// ``` + /// use kernel::workqueue::Queue; + /// + /// let wq =3D Queue::new_power_efficient().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from power-efficient w= q"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "WQ_POWER_EFFICIENT")] + pub fn new_power_efficient() -> Builder { + Builder { + flags: bindings::wq_flags_WQ_UNBOUND | bindings::wq_flags_WQ_P= OWER_EFFICIENT, + max_active: 0, + _kind: PhantomData, + } + } + + /// Build a single-threaded workqueue that executes jobs in order. + /// + /// # Examples + /// + /// ``` + /// use kernel::workqueue::Queue; + /// + /// let wq =3D Queue::new_ordered().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from ordered wq"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "alloc_ordered_workqueue")] + #[doc(alias =3D "__WQ_ORDERED")] + pub fn new_ordered() -> Builder { + 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 =3D Queue::new_bh().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from BH wq"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "WQ_BH")] + pub fn new_bh() -> Builder { + 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 Builder { + /// Mark this workqueue high priority. + /// + /// # Examples + /// + /// ``` + /// use kernel::workqueue::Queue; + /// + /// let wq =3D Queue::new_unbound().highpri().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from highpri wq"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "WQ_HIGHPRI")] + pub fn highpri(mut self) -> Self { + self.flags |=3D 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 =3D 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 =3D "alloc_workqueue")] + pub fn build(self, name: &CStr) -> Result { + // SAFETY: + // * c"%s" is compatible with passing the name as a c-string. + // * the builder only permits valid flag combinations + let ptr =3D unsafe { + bindings::alloc_workqueue( + c"%s".as_char_ptr(), + self.flags, + self.max_active, + name.as_char_ptr().cast::(), + ) + }; + + // INVARIANT: We successfully created the workqueue, so we can ret= urn 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 work= queue 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 { + /// // create a percpu workqueue called my-wq-{num} + /// let wq =3D Queue::new_percpu().build_fmt(fmt!("my-wq-{num}"))?; + /// Ok(wq) + /// } + /// ``` + #[inline] + pub fn build_fmt(self, name: kernel::fmt::Arguments<'_>) -> Result { + // SAFETY: + // * c"%pA" is compatible with passing an `Arguments` pointer. + // * the builder only permits valid flag combinations + let ptr =3D unsafe { + bindings::alloc_workqueue( + c"%pA".as_char_ptr(), + self.flags, + self.max_active, + ptr::from_ref(&name).cast::(), + ) + }; + + // INVARIANT: We successfully created the workqueue, so we can ret= urn 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 Builder { + /// 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 =3D Queue::new_unbound().max_active(16).build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from wq with max_activ= e=3D16"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + pub fn max_active(mut self, max_active: u32) -> Self { + // If provided `max_active` is greater than `i32::MAX`, then we ne= ed to trigger the C-side + // comparison with `WQ_MAX_ACTIVE`, which we can do by clamping to= `i32::MAX`. + self.max_active =3D 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 poo= l. + /// This is meaningless for unbound workqueues. + /// + /// # Examples + /// + /// ``` + /// use kernel::workqueue::Queue; + /// + /// let wq =3D 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 =3D "WQ_CPU_INTENSIVE")] + pub fn cpu_intensive(mut self) -> Self { + self.flags |=3D bindings::wq_flags_WQ_CPU_INTENSIVE; + 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 Builder { + /// Make this workqueue visible in sysfs. + /// + /// # Examples + /// + /// ``` + /// use kernel::workqueue::Queue; + /// + /// let wq =3D Queue::new_unbound().sysfs().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from sysfs wq"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "WQ_SYSFS")] + pub fn sysfs(mut self) -> Self { + self.flags |=3D bindings::wq_flags_WQ_SYSFS; + self + } + + /// 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 =3D Queue::new_unbound().freezable().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from freezable wq"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "WQ_FREEZABLE")] + pub fn freezable(mut self) -> Self { + self.flags |=3D bindings::wq_flags_WQ_FREEZABLE; + self + } + + /// This workqueue may be used during memory reclaim. + /// + /// # Examples + /// + /// ``` + /// use kernel::workqueue::Queue; + /// + /// let wq =3D 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 =3D "WQ_MEM_RECLAIM")] + pub fn mem_reclaim(mut self) -> Self { + self.flags |=3D bindings::wq_flags_WQ_MEM_RECLAIM; + self + } +} + +/// Options only available on a power-efficient workqueue. +impl Builder { + /// Configure this power-efficient workqueue to be percpu. + /// + /// # Examples + /// + /// ``` + /// use kernel::workqueue::Queue; + /// + /// let wq =3D Queue::new_power_efficient().percpu().build(c"my-wq")?; + /// wq.try_spawn(GFP_KERNEL, || pr_info!("Hello from percpu power-effi= cient wq"))?; + /// # Ok::<(), Error>(()) + /// ``` + #[inline] + #[doc(alias =3D "WQ_PERCPU")] + pub fn percpu(mut self) -> Self { + self.flags &=3D !bindings::wq_flags_WQ_UNBOUND; + self.flags |=3D bindings::wq_flags_WQ_PERCPU; + self + } +} diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs index 399d2ed24135..8eb2d037be83 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/workqueu= e.h) =20 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; =20 /// Creates a [`Work`] initialiser with the given name and a newly-created= lock class. #[macro_export] @@ -346,7 +356,7 @@ pub fn enqueue_delayed( /// This method can fail because it allocates memory to store the work= item. pub fn try_spawn( &self, - flags: Flags, + flags: alloc::Flags, func: T, ) -> Result<(), AllocError> { let init =3D pin_init!(ClosureWork { @@ -359,6 +369,41 @@ pub fn try_spawn( } } =20 +/// 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, +} + +// SAFETY: `OwnedQueue` uniquely owns a valid `Queue`, which is `Send + Sy= nc`. +unsafe impl Send for OwnedQueue {} +// SAFETY: `&OwnedQueue` only provides `&Queue` (via `Deref`), which is sa= fe to share. +unsafe impl Sync for OwnedQueue {} + +impl Deref for OwnedQueue { + type Target =3D Queue; + #[inline] + fn deref(&self) -> &Queue { + // SAFETY: By the type invariants, this pointer references a valid= queue. + unsafe { &*self.queue.as_ptr() } + } +} + +impl Drop for OwnedQueue { + #[inline] + fn drop(&mut self) { + // SAFETY: This `OwnedQueue` owns a valid workqueue, so we can des= troy 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 --=20 2.55.0 From nobody Tue Sep 29 13:18:39 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8BE2B39EF1C; Fri, 7 Aug 2026 16:53:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121608; cv=none; b=Mw8vyUFStqZQxFpxfCV7aBY555EXjH33KlXqhfWqVAbHJ4llxP2n7YSGxIdHgsil6WgSrlwj766LPsV+E3CubuqceELUlzrV2iJmw1F8VcDZwZ4cI1bR1kRpliwwojk21UsXUcoVHlagqiNygquoViyJTo2ZPCeyLQW1zpu5rsk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121608; c=relaxed/simple; bh=MeyJSyi4gNjiMX9oh3a84EhMG9idgmhZucxZZguxoPM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=sCnFnfcS1A4gV2xgAI6RUoB5uAVvK/GZEPqSQu7ki6S283p09OcYUesGBqJOcMEG8Ux4PeMVf9Yu7GGJ3vQ6q6GDsggUm+dTeE8OJVt3szjZtma6LJiqHhShfamSxKrklRezY4ap/J+SLbTnqw02h6mR/M2hu1hPH4cF0VKlG04= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=PZsbqvC7; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="PZsbqvC7" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 2DCE71F00A3A; Fri, 7 Aug 2026 16:53:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786121606; bh=v5rO7Pt9TZyJwWtTgJ+3okuK8bq+zJs8wct93yfdHpU=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=PZsbqvC7e+qLpU2aSUQ7kn+VaVpVxD30TKDxFPuwD457ZRAnQ8riV8iKUfqyyZskr L6vc2hGEJz4AlRAfhqEBRG59zqrPunftKRAYNLncAVjOhyhcyUPozvjD0LTL26s5nn trXW+/bdS1gO1b774bOyazxnG6XteO8ntjkkVtXwAxW1F2VXUGKuGD7MTVo13Mwprt 3TBuMAi3L40e7WqcarGDd2HTjJ/Fcq5DyqHKaViAG5VgcDMzuUQnCmhwVZmNBXfQZz exR4yV5UNdO2R1mV5YpnfJakQCI7pVZEE3n0JP4daKk/bQE5JS9JszdAvMyDU7t6Ls YullI4FVhQfVw== From: Danilo Krummrich 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 Subject: [PATCH v2 5/6] rust: workqueue: add ScopedQueue for lifetime bound items Date: Fri, 7 Aug 2026 18:52:48 +0200 Message-ID: <20260807165252.3849875-6-dakr@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260807165252.3849875-1-dakr@kernel.org> References: <20260807165252.3849875-1-dakr@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Onur =C3=96zkan 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 =3D (); | ---- binding `data` declared here 22028 | // SAFETY: Queue is not leaked. 22029 | queue =3D unsafe { new_queue(&data)? }; | ^^^^^ borrowed value does n= ot live long enough 22030 | } | - `data` dropped here while still borrowed ... 22034 | } | - borrow might be used here, when `queue` is dropped and runs the `Dro= p` code for type `ScopedQueue` | =3D note: values in a scope are dropped in the opposite order they are d= efined which is exactly the constraint ScopedQueue is meant to enforce. Suggested-by: Danilo Krummrich Signed-off-by: Onur =C3=96zkan [ Move from scoped_queue.rs to scoped.rs, which can be shared with ScopedWork; add missing inline annotations. - Danilo ] Signed-off-by: Danilo Krummrich Reviewed-by: Daniel Almeida --- rust/kernel/workqueue/mod.rs | 3 + rust/kernel/workqueue/scoped.rs | 190 ++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 rust/kernel/workqueue/scoped.rs diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs index 8eb2d037be83..551fa1401b85 100644 --- a/rust/kernel/workqueue/mod.rs +++ b/rust/kernel/workqueue/mod.rs @@ -212,6 +212,9 @@ mod builder; pub use self::builder::Builder; =20 +mod scoped; +pub use self::scoped::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.rs b/rust/kernel/workqueue/scoped= .rs new file mode 100644 index 000000000000..18a4b6f6cf18 --- /dev/null +++ b/rust/kernel/workqueue/scoped.rs @@ -0,0 +1,190 @@ +// 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, [`ScopedQueu= e`] +//! owns its underlying queue and relies on that queue being dropped to dr= ain +//! 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> { +//! // 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 =3D (); +//! // SAFETY: Queue is not leaked. +//! queue =3D 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>, +//! } +//! +//! impl_has_work! { +//! impl{'bound} HasWork> for BorrowedWork<'bound= > { self.work } +//! } +//! +//! impl<'bound> WorkItem for BorrowedWork<'bound> { +//! type Pointer =3D Arc; +//! +//! fn run(_this: Arc) {} +//! } +//! +//! impl<'bound> BorrowedWork<'bound> { +//! fn new(data: &'bound ()) -> Result> { +//! Arc::pin_init( +//! pin_init!(Self { +//! data, +//! work <- new_work!("BorrowedWork::work"), +//! }), +//! GFP_KERNEL, +//! ) +//! } +//! } +//! +//! struct Handle<'bound> { +//! work: Arc>, +//! wq: ScopedQueue<'bound>, +//! } +//! +//! impl<'bound> Handle<'bound> { +//! /// # Safety +//! /// +//! /// Returned handle must not be leaked. +//! unsafe fn new(data: &'bound ()) -> Result { +//! Ok(Self { +//! work: BorrowedWork::new(data)?, +//! // SAFETY: Caller guarantees that the returned handle is n= ot leaked. +//! wq: unsafe { ScopedQueue::new(c"handle_wq")? }, +//! }) +//! } +//! } +//! +//! fn handle_outlives_borrowed_data() -> Result { +//! let handle; +//! +//! { +//! let data =3D (); +//! // SAFETY: Handle is not leaked. +//! handle =3D unsafe { Handle::new(&data)? }; +//! +//! let _ =3D handle.wq.enqueue(handle.work.clone()); +//! } +//! // Here the `compile_fail` is fulfilled as `handle` would be dropp= ed +//! // 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 i= ts + /// [`Drop`] implementation from running since dropping the queue drai= ns + /// pending and running work that may borrow from `'scope`. + #[inline] + pub unsafe fn new(name: &'static CStr) -> Result { + Ok(Self { + inner: Queue::new_ordered().build(name)?, + _scope: PhantomData, + }) + } + + /// Enqueues a work item on this scoped queue. + #[inline] + pub fn enqueue(&self, work: W) -> W::EnqueueOutput + where + W: RawWorkItem + Send + 'scope, + { + let queue_ptr =3D self.inner.0.get(); + + // SAFETY: + // - Closure returns `false` only if `queue_work_on` returns `fals= e` + // and that means `work_ptr` is already in a workqueue. + // + // - `W: 'scope` and dropck keep borrowed data alive until this qu= eue is + // dropped. The constructor requires that the queue is not leake= d and + // dropping `inner` drains pending and running work so the funct= ion + // pointer is not called after any lifetime in `W` expires. + // + // - The last requirement of `__enqueue` is not relevant here beca= use `W` + // is `Send`. + unsafe { + work.__enqueue(move |work_ptr| { + bindings::queue_work_on( + bindings::wq_misc_consts_WORK_CPU_UNBOUND as ffi::c_in= t, + queue_ptr, + work_ptr, + ) + }) + } + } +} + +impl Drop for ScopedQueue<'_> { + #[inline] + fn drop(&mut self) { + // This impl makes dropck require `'scope` to outlive `OwnedQueue`. + // See: https://doc.rust-lang.org/nomicon/phantom-data.html#generi= c-parameters-and-drop-checking + let _ =3D &self._scope; + } +} --=20 2.55.0 From nobody Tue Sep 29 13:18:39 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 492F2392C34; Fri, 7 Aug 2026 16:53:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121613; cv=none; b=KN0wAUTTNiLV7OzthqySQsc0GxVkLyVuQ/pUzaEufxyyBV5xvAJ/MOE2xIRph6hritoySliDpBGe3Z7iJhFwtdXWqKUExr6V6YHyI0yXrLhtG/ktreirKkGmbUddpChTFyHqnYUufP2TQ6ZXzDYnOqbg6dNLxxARYyxAo87w3Jk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786121613; c=relaxed/simple; bh=sPV84w4SX+U/1LZWuC3bVnh4+DTDuSfP+duYQoCrcQw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tfeWldZG+S1MPXCnTuiF4tRdJKwgo0XGT2TamhjAu4q/QcdQYkpZFg8Db/Qa153mlGJKTmM6umM5f22UUP+XXjyMqEoOy14BLGzsd7weJLFO1+aFuLla7GeTtSR6jr9pD/rjB5PWFdewOVzRjU/88z5HH8SaGBrD83lO1YnR91c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ERpZdC21; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ERpZdC21" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 9682E1F000E9; Fri, 7 Aug 2026 16:53:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786121610; bh=XLbEWJSeXDkTQHNGYVl4uqwESecA9mbAlTlzm692Aik=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=ERpZdC21r1/NhV+iFxVokt8aeqMZE/f91/eU+39aFi5LrJwi7AdYPNOBbV5XhL0GK UUx9Rsp5JMru7fTEQUD5y4rYJFcxiKMpcVxpH7sPXGKwh+1lLC9Y+5MSQ4KxcMz+gW WSqg3GRXSA+XZk8/AEvK6Hdy2z46Hj/Um6LlswpkVSU1CjfUkqCs2a7ndIXp7olkLV DDd+WUWY9a85jZc6OOLYeaCbPLizxbrnYLFdPp8zZiVRhETodz0V8BW5le9CGM8CLT ihyHuWToj6VjEbwbpJAA2Rj9ZHRzsiJcVnzM9bB3xCmo4+YvzDYhExj2x2GuFQgqfM /EeqznHTn9TaQ== From: Danilo Krummrich 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 Subject: [PATCH v2 6/6] rust: workqueue: add ScopedWork for non-'static work items Date: Fri, 7 Aug 2026 18:52:49 +0200 Message-ID: <20260807165252.3849875-7-dakr@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260807165252.3849875-1-dakr@kernel.org> References: <20260807165252.3849875-1-dakr@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add ScopedWork, 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> as WorkItem::Pointer for the callback path, and implements RawWorkItem for &ScopedWork and &ScopedWorkRef 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 --- 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; =20 mod scoped; -pub use self::scoped::ScopedQueue; +pub use self::scoped::{ + new_scoped_work, + ScopedQueue, + ScopedWork, + ScopedWorkItem, + ScopedWorkRef, // +}; =20 /// 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::work= queue_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(&self, w: W) -> W::EnqueueOutput where W: RawWorkItem + Send + 'static, + { + // SAFETY: `W: 'static` guarantees the work item remains valid ind= efinitely, + // so the `enqueue_scoped` requirement that the work item stays va= lid 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 forg= otten). + #[inline] + pub unsafe fn enqueue_scoped(&self, w: W) -> W::Enqu= eueOutput + where + W: RawWorkItem + Send, { let queue_ptr =3D self.0.get(); =20 - // 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` a= nd static. - // - // The call to `bindings::queue_work_on` will dereference the prov= ided 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 tha= t the raw pointer will - // stay valid until we call the function pointer in the `work_stru= ct`, 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 fu= nction runs or the item + // is cancelled, satisfying the `__enqueue` requirement that the p= ointer 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 =20 -//! 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 bo= rrow +//! data with some non-`'static` lifetime. //! -//! Unlike [`Queue`] which only accepts `'static` work items, [`ScopedQueu= e`] -//! owns its underlying queue and relies on that queue being dropped to dr= ain -//! pending and running work before borrowed data can go out of scope. +//! [`ScopedQueue`] owns its underlying queue and relies on that queue bei= ng +//! 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_s= ync()`, +//! so ownership of the data is not transferred to the workqueue. This all= ows 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 separa= te +//! allocation and reference count unnecessary. +//! +//! # Examples +//! +//! Enqueue on the system workqueue (unsafe, caller must not forget the wo= rk): +//! +//! ``` +//! # 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) { +//! pr_info!("value =3D {}\n", work.value); +//! } +//! } +//! +//! let work =3D 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 qu= eue): +//! +//! ``` +//! use kernel::workqueue::{ +//! new_scoped_work, +//! ScopedQueue, +//! ScopedWork, +//! ScopedWorkItem, +//! ScopedWorkRef, +//! }; +//! +//! struct MyWork { +//! value: u32, +//! } +//! +//! impl ScopedWorkItem for MyWork { +//! fn run(work: &ScopedWorkRef) { +//! pr_info!("value =3D {}\n", work.value); +//! } +//! } +//! +//! let work =3D KBox::pin_init( +//! new_scoped_work!("MyWork", MyWork { value: 42 }), +//! GFP_KERNEL, +//! )?; +//! +//! // SAFETY: The queue is not forgotten. +//! let queue =3D 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 @@ //! ``` =20 use super::{ + impl_has_work, + HasWork, OwnedQueue, Queue, - RawWorkItem, // + RawWorkItem, + Work, + WorkItem, + WorkItemPointer, // }; =20 use crate::{ bindings, - ffi, - prelude::*, // + prelude::*, + sync::LockClassKey, + types::Opaque, // }; =20 -use core::marker::PhantomData; +use pin_init::Wrapper; + +use core::{ + marker::PhantomData, + ops::Deref, + ptr::NonNull, // +}; =20 /// An owned workqueue that can enqueue work items borrowing from `'scope`. /// @@ -133,6 +227,15 @@ pub struct ScopedQueue<'scope> { _scope: PhantomData<&'scope mut &'scope ()>, } =20 +impl Deref for ScopedQueue<'_> { + type Target =3D Queue; + + #[inline] + fn deref(&self) -> &Queue { + &self.inner + } +} + impl<'scope> ScopedQueue<'scope> { /// Creates an ordered scoped workqueue. /// @@ -155,28 +258,11 @@ pub fn enqueue(&self, work: W) -> W= ::EnqueueOutput where W: RawWorkItem + Send + 'scope, { - let queue_ptr =3D self.inner.0.get(); - - // SAFETY: - // - Closure returns `false` only if `queue_work_on` returns `fals= e` - // and that means `work_ptr` is already in a workqueue. - // - // - `W: 'scope` and dropck keep borrowed data alive until this qu= eue is - // dropped. The constructor requires that the queue is not leake= d and - // dropping `inner` drains pending and running work so the funct= ion - // pointer is not called after any lifetime in `W` expires. - // - // - The last requirement of `__enqueue` is not relevant here beca= use `W` - // is `Send`. - unsafe { - work.__enqueue(move |work_ptr| { - bindings::queue_work_on( - bindings::wq_misc_consts_WORK_CPU_UNBOUND as ffi::c_in= t, - queue_ptr, - work_ptr, - ) - }) - } + // SAFETY: `W: 'scope` and dropck keep borrowed data alive until t= his queue + // is dropped. The constructor requires that the queue is not leak= ed and + // dropping `inner` drains pending and running work, so the functi= on pointer + // is not called after any lifetime in `W` expires. + unsafe { self.enqueue_scoped(work) } } } =20 @@ -188,3 +274,295 @@ fn drop(&mut self) { let _ =3D &self._scope; } } + +/// Trait for types that can be used as scoped work items. +/// +/// Implementers define the work function that executes when the item is d= equeued by a workqueue +/// thread. The callback receives a reference to the containing [`ScopedWo= rkRef`], which provides +/// access to the inner data via [`Deref`] and can be used to re-enqueue t= he work item. +pub trait ScopedWorkItem: Sized { + /// Called when the work item is executed. + fn run(work: &ScopedWorkRef); +} + +/// The work function's view of a [`ScopedWork`] item. +/// +/// The work function callback receives `&ScopedWorkRef`, 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 { + #[pin] + work: Work, + #[pin] + data: T, +} + +impl_has_work! { + impl{T: ScopedWorkItem} HasWork> for ScopedWorkRef= { self.work } +} + +impl Deref for ScopedWorkRef { + type Target =3D T; + + #[inline] + fn deref(&self) -> &T { + &self.data + } +} + +impl WorkItem for ScopedWorkRef { + type Pointer =3D NonNull; + + #[inline] + fn run(this: NonNull) { + // SAFETY: `this` points to a valid, pinned `ScopedWorkRef`. `canc= el_work_sync()` in + // `ScopedWork`'s `PinnedDrop` prevents use-after-drop. + let work =3D unsafe { &*this.as_ptr() }; + + T::run(work); + } +} + +// SAFETY: The `run` callback uses the `work_struct` pointer to recover a = pointer to +// `ScopedWorkRef` via `HasWork`, wraps it in `NonNull`, and calls `Wor= kItem::run`. +unsafe impl WorkItemPointer for NonN= ull> +where + ScopedWorkRef: WorkItem, + ScopedWorkRef: HasWork, ID>, +{ + unsafe extern "C" fn run(ptr: *mut bindings::work_struct) { + let ptr =3D ptr.cast::, ID>>(); + + // SAFETY: The `work_struct` is embedded in `ScopedWorkRef` via= `HasWork`. + let ptr =3D + unsafe { as HasWork, ID>>::= work_container_of(ptr) }; + + // SAFETY: `work_container_of` returns a valid, non-null pointer. + let nn =3D unsafe { NonNull::new_unchecked(ptr) }; + + as WorkItem>::run(nn); + } +} + +// Required because `WorkItemPointer: RawWorkItem` is a supertrait= bound. This `__enqueue` +// is never called; the enqueue path goes through the `RawWorkItem` impl f= or `&ScopedWork` or +// `&ScopedWorkRef` instead. +// +// SAFETY: `__enqueue` is unreachable. +unsafe impl RawWorkItem for NonNull<= ScopedWorkRef> +where + ScopedWorkRef: HasWork, ID>, +{ + type EnqueueOutput =3D bool; + + unsafe fn __enqueue(self, _queue_work_on: F) -> Self::EnqueueOutput + where + F: FnOnce(*mut bindings::work_struct) -> bool, + { + unreachable!() + } +} + +// SAFETY: `&ScopedWorkRef` points to a valid `ScopedWorkRef` with a va= lid `work_struct`. +// The pointer remains valid until `cancel_work_sync()` completes in `Scop= edWork`'s drop. +unsafe impl<'a, T: ScopedWorkItem + Sync, const ID: u64> RawWorkItem f= or &'a ScopedWorkRef +where + ScopedWorkRef: HasWork, ID>, +{ + type EnqueueOutput =3D bool; + + unsafe fn __enqueue(self, queue_work_on: F) -> Self::EnqueueOutput + where + F: FnOnce(*mut bindings::work_struct) -> bool, + { + let self_ptr =3D core::ptr::from_ref(self); + + // SAFETY: `self_ptr` points to a valid `ScopedWorkRef` with a `Wo= rk` field. + let work_ptr =3D unsafe { + as HasWork, ID>>::raw_get_w= ork(self_ptr.cast_mut()) + }; + + // SAFETY: `work_ptr` points to a valid `Work`. + let work_ptr =3D unsafe { Work::raw_get(work_ptr) }; + + queue_work_on(work_ptr) + } +} + +// SAFETY: `&ScopedWork` accesses the inner `ScopedWorkRef` through `Op= aque::get()`. +// The pointer remains valid until `cancel_work_sync()` completes in `Scop= edWork`'s drop. +unsafe impl<'a, T: ScopedWorkItem + Sync, const ID: u64> RawWorkItem f= or &'a ScopedWork +where + ScopedWorkRef: HasWork, ID>, +{ + type EnqueueOutput =3D bool; + + unsafe fn __enqueue(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 =3D unsafe { &*self.inner.get() }; + + // SAFETY: Delegates to the `&ScopedWorkRef` impl. + unsafe { inner.__enqueue(queue_work_on) } + } +} + +/// A scoped work item that cancels synchronously on drop. +/// +/// `ScopedWork` contains a `work_struct` and the user data `T`. Its de= structor calls +/// `cancel_work_sync()`, guaranteeing the work function is not running wh= en the data is dropped. +/// +/// This allows `T` to carry non-`'static` lifetimes. +/// +/// Construct via [`new_scoped_work!`] which returns an `impl PinInit` sui= table 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, +/// queue: &'a Queue, +/// } +/// +/// impl ScopedWorkItem for RequeueWork<'_> { +/// fn run(work: &ScopedWorkRef) { +/// 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 =3D unsafe { ScopedQueue::new(c"requeue_wq")? }; +/// +/// let work =3D 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 { + #[pin] + inner: Opaque>, +} + +// SAFETY: `&ScopedWork` only provides `&ScopedWorkRef` (via `Deref`= ), which is safe to share +// when `T: Sync`. +unsafe impl Sync for ScopedWork {} + +// SAFETY: ScopedWork can be sent to another thread when T: Send. +unsafe impl Send for ScopedWork {} + +impl Deref for ScopedWork { + type Target =3D ScopedWorkRef; + + #[inline] + fn deref(&self) -> &ScopedWorkRef { + // SAFETY: The inner `ScopedWorkRef` is always valid and initializ= ed. + unsafe { &*self.inner.get() } + } +} + +impl ScopedWork { + /// Creates a pin-initializer for a new scoped work item. + /// + /// Use [`new_scoped_work!`] to automatically provide the lock class k= ey. + #[inline] + pub fn new( + name: &'static CStr, + key: Pin<&'static LockClassKey>, + init: impl PinInit, + ) -> impl PinInit + where + Error: From, + { + try_pin_init!(Self { + inner <- Opaque::pin_init(try_pin_init!(ScopedWorkRef:: { + work <- Work::new(name, key), + data <- init, + })), + }) + } +} + +#[pinned_drop] +impl PinnedDrop for ScopedWork { + #[inline] + fn drop(self: Pin<&mut Self>) { + let inner =3D self.inner.get(); + + // SAFETY: `inner` points to a valid `ScopedWorkRef`. After `cance= l_work_sync()` returns, + // the work function is guaranteed to not be running. + unsafe { bindings::cancel_work_sync(Work::raw_get(&raw const (*inn= er).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) { +/// pr_info!("value =3D {}\n", work.value); +/// } +/// } +/// +/// #[pin_data] +/// struct MyData { +/// #[pin] +/// work: ScopedWork, +/// } +/// +/// fn init_data() -> impl PinInit { +/// try_pin_init!(MyData { +/// work <- new_scoped_work!("MyWork", MyWork { value: 7 }), +/// }) +/// } +/// ``` +#[macro_export] +macro_rules! new_scoped_work { + ($name:literal, $init:expr) =3D> { + $crate::workqueue::ScopedWork::new( + $crate::c_str!($name), + $crate::static_lock_class!(), + $init, + ) + }; +} +pub use new_scoped_work; --=20 2.55.0