From nobody Sat Jun 13 04:48:23 2026 Received: from mail-106111.protonmail.ch (mail-106111.protonmail.ch [79.135.106.111]) (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 8ECA138398D for ; Fri, 12 Jun 2026 19:46:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.111 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781293616; cv=none; b=hiWNcBRG4Q/CmisrSuAFlXrnTOwJZzzuRjQHs7kCYw/7LqepYdcrehscaxfB+UsYMTE/g1X0wESHHRoQmKAXBgg9kjOAMOB5udZbNQetosmvwiISvl1iicY7/pTfwlIgQLImGMUmc2hskcWNgcYy25zHEjq6rsr/XZHhDyHXmiw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781293616; c=relaxed/simple; bh=10TJOJl+9SDXmeXGGdC0BrHmMqCbtmNHIzqUAiJNBWY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=hbJ26XPwx6TM2DjDj3LbFbQy9nNHwC8vSHYPT3v1YoOPZusAga3jsOsiqaz2Q6MhWiW5B0VZkLwbqQ6JRBvz+rFTA39NbCHghgG9HZbUg0NMzRZTWxL5KbXe2iIJigk9Z6ZK3jtLrn2FbSGyePUqYh267m1/Mkp7nI9RrBhIkcQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=onurozkan.dev; spf=pass smtp.mailfrom=onurozkan.dev; dkim=pass (2048-bit key) header.d=onurozkan.dev header.i=@onurozkan.dev header.b=JBqZWLhq; arc=none smtp.client-ip=79.135.106.111 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=onurozkan.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=onurozkan.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=onurozkan.dev header.i=@onurozkan.dev header.b="JBqZWLhq" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=onurozkan.dev; s=protonmail; t=1781293605; x=1781552805; bh=yF+pSlHcJkDmIjs21f+9ignjqrVhe5dZ+8+xeRi7Kp4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:From:To: Cc:Date:Subject:Reply-To:Feedback-ID:Message-ID:BIMI-Selector; b=JBqZWLhqDG+Xdmn0dJF4BNnB+n7e7Mkv5euwmLr0N0JDHbe2Bewyh7aR6yBEvWtSR JsaeGYxaNkEyk4z/eXQABdpbkxBpaXS6Lg5zSb0BUtmUpDY5FywEZqSmAmgJt8QMeq /ITERyWlN2qQvQOxqSO0nbgTOqb0i0mSmN4vtatGZBJpKwQ/nwudT6FJ+5kHrMm5Vg 22Eet9WS7dVSYgmNrF/g9X6O0vtX8YjE3KE2XsvxImJ1Xy2428BdnD6AdnLfXL554w x8pS5ytY3JIO7oUkLXOKn7CfStvc00hffyw1KiVucILr1BanG4GJ3uuvje4Zrn9Fg9 cCbZAZQF4RWYw== X-Pm-Submission-Id: 4gcVQC0StPz1DDXZ From: =?UTF-8?q?Onur=20=C3=96zkan?= To: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Cc: ojeda@kernel.org, boqun@kernel.org, gary@garyguo.net, bjorn3_gh@protonmail.com, lossin@kernel.org, a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu, dakr@kernel.org, tamird@kernel.org, daniel.almeida@collabora.com, =?UTF-8?q?Onur=20=C3=96zkan?= Subject: [PATCH v2 1/1] rust: workqueue: add cancel_sync support Date: Fri, 12 Jun 2026 22:45:42 +0300 Message-ID: <20260612194633.350011-2-work@onurozkan.dev> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260612194633.350011-1-work@onurozkan.dev> References: <20260612194633.350011-1-work@onurozkan.dev> 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 Drivers can use this during teardown to cancel pending work and wait for running work to finish before dropping related resources. This is not implemented for Pin> because queuing a boxed work item transfers ownership of the box to the workqueue. There is therefore no separate safe owner that can cancel the boxed work while it is pending. Signed-off-by: Onur =C3=96zkan --- rust/kernel/workqueue.rs | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs index 7e253b6f299c..4d61d7a10fae 100644 --- a/rust/kernel/workqueue.rs +++ b/rust/kernel/workqueue.rs @@ -471,6 +471,23 @@ pub trait WorkItem { fn run(this: Self::Pointer); } =20 +/// Work item pointers that support cancellation. +/// +/// # Safety +/// +/// Implementers must ensure that `from_raw_work` rebuilds the exact owner= ship transferred +/// by a successful [`RawWorkItem::__enqueue`] call. +pub unsafe trait SupportsCancelling: WorkItemPointer + = Sized { + /// Rebuild this work item's pointer from its embedded `work_struct`. + /// + /// # Safety + /// + /// The provided `work_struct` pointer must originate from a previous = call to + /// [`RawWorkItem::__enqueue`] where the `queue_work_on` closure retur= ned true + /// and the pointer must still be valid. + unsafe fn from_raw_work(ptr: *mut bindings::work_struct) -> Self; +} + /// Links for a work item. /// /// This struct contains a function pointer to the [`run`] function from t= he [`WorkItemPointer`] @@ -537,6 +554,32 @@ pub unsafe fn raw_get(ptr: *const Self) -> *mut bindin= gs::work_struct { // the compiler does not complain that the `work` field is unused. unsafe { Opaque::cast_into(core::ptr::addr_of!((*ptr).work)) } } + + /// Cancels this work item if it is pending and waits for any running = execution to finish. + /// + /// On return, the work item is guaranteed to not be pending or execut= ing as long as there are + /// no racing re-enqueues. + /// + /// # Note + /// + /// Should be called from a sleepable context if the work was last que= ued on a non-BH + /// workqueue. + #[inline] + pub fn cancel_sync(&self) -> Option + where + T: WorkItem, + T::Pointer: SupportsCancelling, + { + let ptr =3D self.work.get(); + // SAFETY: `ptr` is a valid embedded `work_struct`. + if unsafe { bindings::cancel_work_sync(ptr) } { + // SAFETY: A `true` return means the work was pending and got = canceled, so the queued + // ownership transfer performed by `__enqueue` is reclaimed he= re. + Some(unsafe { T::Pointer::from_raw_work(ptr) }) + } else { + None + } + } } =20 /// Declares that a type contains a [`Work`]. @@ -725,6 +768,34 @@ pub unsafe fn raw_as_work(ptr: *const Self) -> *mut Wo= rk { // CAST: Work and work_struct have compatible layouts. wrk.cast() } + + /// Cancels this delayed work item if it is pending and waits for any = running execution to + /// finish. + /// + /// On return, the work item is guaranteed to not be pending or execut= ing as long as there are + /// no racing re-enqueues. + /// + /// # Note + /// + /// Should be called from a sleepable context if the work was last que= ued on a non-BH + /// workqueue. + #[inline] + pub fn cancel_sync(&self) -> Option + where + T: WorkItem, + T::Pointer: SupportsCancelling, + { + let ptr =3D self.dwork.get(); + + // SAFETY: `ptr` is a valid embedded `delayed_work`. + if unsafe { bindings::cancel_delayed_work_sync(ptr) } { + // SAFETY: A `true` return means the work was pending and got = canceled, so the queued + // ownership transfer performed by `__enqueue` is reclaimed he= re. + Some(unsafe { T::Pointer::from_raw_work(core::ptr::addr_of_mut= !((*ptr).work)) }) + } else { + None + } + } } =20 /// Declares that a type contains a [`DelayedWork`]. @@ -871,6 +942,24 @@ unsafe fn __enqueue(self, queue_work_on: F) -> Self= ::EnqueueOutput } } =20 +// SAFETY: `from_raw_work()` reconstructs exactly the `Arc` ownership p= reviously transferred by +// `Arc::into_raw()` in `RawWorkItem::__enqueue`, using the same `Work` field selected by +// `HasWork`. +unsafe impl SupportsCancelling for Arc +where + T: WorkItem, + T: HasWork, +{ + unsafe fn from_raw_work(ptr: *mut bindings::work_struct) -> Self { + // The `__enqueue` method always uses a `work_struct` stored in a = `Work`. + let ptr =3D ptr.cast::>(); + // SAFETY: This computes the pointer that `__enqueue` got from `Ar= c::into_raw`. + let ptr =3D unsafe { T::work_container_of(ptr) }; + // SAFETY: This pointer comes from `Arc::into_raw` and we've been = given back ownership. + unsafe { Arc::from_raw(ptr) } + } +} + // SAFETY: By the safety requirements of `HasDelayedWork`, the `work_struc= t` returned by methods in // `HasWork` provides a `work_struct` that is the `work` field of a `delay= ed_work`, and the rest of // the `delayed_work` has the same access rules as its `work` field. @@ -1013,6 +1102,33 @@ unsafe fn __enqueue(self, queue_work_on: F) -> Se= lf::EnqueueOutput } } =20 +// SAFETY: `from_raw_work()` reconstructs exactly the `ARef` ownership = previously transferred by +// `ARef::into_raw()` in `RawWorkItem::__enqueue`, using the same `Work` field selected by +// `HasWork`. +unsafe impl SupportsCancelling for ARef +where + T: AlwaysRefCounted, + T: WorkItem, + T: HasWork, +{ + unsafe fn from_raw_work(ptr: *mut bindings::work_struct) -> Self { + // The `__enqueue` method always uses a `work_struct` stored in a = `Work`. + let ptr =3D ptr.cast::>(); + + // SAFETY: This computes the pointer that `__enqueue` got from + // `ARef::into_raw`. + let ptr =3D unsafe { T::work_container_of(ptr) }; + + // SAFETY: The safety contract of `work_container_of` ensures that= it + // returns a valid non-null pointer. + let ptr =3D unsafe { NonNull::new_unchecked(ptr) }; + + // SAFETY: This pointer comes from `ARef::into_raw` and we've been= given + // back ownership. + unsafe { ARef::from_raw(ptr) } + } +} + // SAFETY: By the safety requirements of `HasDelayedWork`, the `work_struc= t` returned by methods in // `HasWork` provides a `work_struct` that is the `work` field of a `delay= ed_work`, and the rest of // the `delayed_work` has the same access rules as its `work` field. --=20 2.51.2