From nobody Sun Jun 14 08:17:54 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 7A51F299943; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775100229; cv=none; b=R09KAL3BlJ/UBy5G0sJBWqnnRwcU0G0dlR5Mwsig4ZCiuSDjNAxzuL475ubB+5cto2DOlvWppGueNkXSF5z//lB40UaYT6h1xYXXXuEGSLEeqHaq0WiFdhsZXaaAGbdLO+WGHrlL2LhZNR4i0RMSrsA5pQezqkZppo9gRSCM69I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775100229; c=relaxed/simple; bh=NAevoZnfcNs3+ozdq6T4miOpHYGB4ulcSVDjELVWttE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qgxr20MA+JxtTUv+CEJgL+vn4V4VM7wqjfZvLOEmjyo/AaX6VIJFKb233uHm81nOw7bOSg4z6XP7Izkw8bPDjrwAFFyXetaGBLZ7i9bJiI8MJwSVsHzdi9RWk6fhn9fXyRhuyMEVnXkDiIPkO84jFHi3BR/Rk7UEnuB09i3fvx8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=jlZwKSE7; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="jlZwKSE7" Received: by smtp.kernel.org (Postfix) with ESMTPS id 49A0AC19421; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1775100229; bh=NAevoZnfcNs3+ozdq6T4miOpHYGB4ulcSVDjELVWttE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=jlZwKSE7KfBC37sgzm8LVJLNPybs1fO3HqgM+T6Un8SCGOrgPxRuCVcj1LVjBURy+ F8pVVVrmkaO5nuNiQ322x2XE9/E8bLTwYLZutQzoJB5rtfOZ7i0bl1i3kWU0QAFz2q M81f+gsI/i2pUgKir4zYNNdkHU8dNRRoIZ2KsSUt0l6DMh9pEfaYBdkN29Z4PZC22P 5H3SSQ9PCwXG5wMyFS9vvcxZGb5h54dQjToyCf/5ZjFGYEr3t4jcmeG31jnSh2/NX9 IFKy0Hs5bsuUWRkbwEb5KP07CrVssTHQtJS0CyTeT8M1pclfQxkpdoynNls8/vxPu8 SrO0ACYlP0nxA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 388AE111227B; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) From: Aakash Bollineni via B4 Relay Date: Thu, 02 Apr 2026 08:53:46 +0530 Subject: [PATCH 1/3] rust: helpers: add workqueue helpers 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 Message-Id: <20260402-rust-next-v1-1-0940bb8f201c@multicorewareinc.com> References: <20260402-rust-next-v1-0-0940bb8f201c@multicorewareinc.com> In-Reply-To: <20260402-rust-next-v1-0-0940bb8f201c@multicorewareinc.com> To: rust-for-linux@vger.kernel.org Cc: Aakash Bollineni , linux-kernel@vger.kernel.org X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1775100227; l=2252; i=aakash.bollineni@multicorewareinc.com; s=20260402; h=from:subject:message-id; bh=wZ0Dz1PhUI/Psa+wxQ3zKnWwUiWulSm2trcU2tpe4UQ=; b=go69+KD4+Ci2CE4Wrsc43j78PKvHKlfOVwoPNu5ygpADwWPGB4Eux5gK83ogUI5RV3r4dN20j ebJnafbai3ADvG2hxMBI60fcK+WAQnMasa8Zuqxgj4QVCrFnbRzl339 X-Developer-Key: i=aakash.bollineni@multicorewareinc.com; a=ed25519; pk=r3Gonl+2k+8RozN9U/XwfICQdnRlAcLeeAfsExmurdE= X-Endpoint-Received: by B4 Relay for aakash.bollineni@multicorewareinc.com/20260402 with auth_id=711 X-Original-From: Aakash Bollineni Reply-To: aakash.bollineni@multicorewareinc.com From: Aakash Bollineni Add C-helpers to bridge the Rust workqueue abstraction with the kernel's C workqueue macros. These helpers wrap core workqueue functions that are either inline or macros in C, making them accessible to Rust FFI. New wrappers: - rust_helper_work_pending(): Wraps work_pending(). - rust_helper_cancel_work_sync(): Wraps cancel_work_sync(). - rust_helper_cancel_delayed_work_sync(): Wraps cancel_delayed_work_sync(). - rust_helper_init_delayed_work(): Performs robust initialization of a delayed_work structure, ensuring the correct timer function (delayed_work_timer_fn) and lockdep maps are initialized using standard kernel macros. These helpers are essential for supporting safe cancellation and correct DelayedWork lifecycle management in the Rust workqueue API. Signed-off-by: Aakash Bollineni --- rust/helpers/workqueue.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/rust/helpers/workqueue.c b/rust/helpers/workqueue.c index ce1c3a5b2150..06447bec912a 100644 --- a/rust/helpers/workqueue.c +++ b/rust/helpers/workqueue.c @@ -14,3 +14,37 @@ __rust_helper void rust_helper_init_work_with_key(struct= work_struct *work, INIT_LIST_HEAD(&work->entry); work->func =3D func; } + +__rust_helper bool rust_helper_work_pending(struct work_struct *work) +{ + return work_pending(work); +} + +__rust_helper bool rust_helper_cancel_work_sync(struct work_struct *work) +{ + return cancel_work_sync(work); +} + +__rust_helper bool rust_helper_cancel_delayed_work_sync(struct delayed_wor= k *dwork) +{ + return cancel_delayed_work_sync(dwork); +} + +__rust_helper void rust_helper_init_delayed_work(struct delayed_work *dwor= k, + work_func_t func, + const char *name, + struct lock_class_key *key, + const char *tname, + struct lock_class_key *tkey) +{ + __init_work(&dwork->work, 0); + dwork->work.func =3D func; + lockdep_init_map(&dwork->work.lockdep_map, name, key, 0); + + timer_init_key(&dwork->timer, delayed_work_timer_fn, TIMER_IRQSAFE, tname= , tkey); +} + +__rust_helper void *rust_helper_get_dwork_timer_fn(void) +{ + return (void *)delayed_work_timer_fn; +} --=20 2.43.0 From nobody Sun Jun 14 08:17:54 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 7A4A71E47CC; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775100229; cv=none; b=qF2aFLWdmPaKb3LqkfuqZEBpLLSGYPk5tfSUotPKp9IraFBr8xJtwGJgazHW6H+2inZxjgtDYzBa4UKLpY0nngnFnUTmaabTFltE/ZQ+EP4ZBb9wSrVOqbFT7xQWMjOZWu1oSUC0FMT4pSx2ZJblUrwhPwvq7F40E5udS5N/RmU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775100229; c=relaxed/simple; bh=Canct8d26DC7YS3eq8mUNd9g0gWJkPm+eMY5rGkP8J4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=cGKUuErhcDwIB6bdNugBtQkrUY354LL5/qIXUgKrgsAEPLAD6qIVcncUQv+2tpURz7V2h9wpfDuQyN03gNb9c/p2wqHl9A6uVmVIWU3s7CvSgzDKJ4q+f7NoUiqcUuX1jX8pPLnpz4sqSplZSjFCK9Ze7JFwCIu0WhrOpB2+038= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=JMVB5nyQ; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="JMVB5nyQ" Received: by smtp.kernel.org (Postfix) with ESMTPS id 556FDC2BCAF; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1775100229; bh=Canct8d26DC7YS3eq8mUNd9g0gWJkPm+eMY5rGkP8J4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=JMVB5nyQbqhB00JSt8yxNoeyPvG0ZDyqeOGQAjxnKCDvKI/3oddeWIbZnplrvYyLf bbia2WP9hrt2RC+M8UJgI12dISWeuHNcgoPmdkjDlvL1jUJEPXprFhtDY1j/Kye7JS Y0FCFVslruAhT6nAMNGkJt/XBnM2Zqvi4JgROCRHEikyspGCqtw24BLYBgix+kSLrX 1vc1k89WX7U8w54vFpM8og90XngrNCPsRMRmg8Q7uaV+AgQci1x2FH+DppuL84dUPl VocnCjdJpuOurmCl3US5TSpTk7ndnlIenk5luckOeLsmZhM3sOgOlgcGSMQe/g82Fc V/JcDIivhqayw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4838C111227F; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) From: Aakash Bollineni via B4 Relay Date: Thu, 02 Apr 2026 08:53:47 +0530 Subject: [PATCH 2/3] rust: workqueue: add safe cancellation and status methods 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 Message-Id: <20260402-rust-next-v1-2-0940bb8f201c@multicorewareinc.com> References: <20260402-rust-next-v1-0-0940bb8f201c@multicorewareinc.com> In-Reply-To: <20260402-rust-next-v1-0-0940bb8f201c@multicorewareinc.com> To: rust-for-linux@vger.kernel.org Cc: Aakash Bollineni , linux-kernel@vger.kernel.org X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1775100227; l=23253; i=aakash.bollineni@multicorewareinc.com; s=20260402; h=from:subject:message-id; bh=xdIrHnROhBStUJFRZgJk1DzBatmEJTz/SwXQE8wZUQs=; b=uqpkQECPSlivWP7uW9JEBj+5mPmk4m0EDTZehuZewILSWNfpwkb9zgGPWgJT81BS6gkD+pHXM NR0XG4RoneVBGehmX3gtKJlc3l4R3rl+LNNGdpoU0MRq2qSNEbS7NT+ X-Developer-Key: i=aakash.bollineni@multicorewareinc.com; a=ed25519; pk=r3Gonl+2k+8RozN9U/XwfICQdnRlAcLeeAfsExmurdE= X-Endpoint-Received: by B4 Relay for aakash.bollineni@multicorewareinc.com/20260402 with auth_id=711 X-Original-From: Aakash Bollineni Reply-To: aakash.bollineni@multicorewareinc.com From: Aakash Bollineni Modernize the Rust workqueue by adding methods for status checking and cancellation of work and delayed work items. Specifically, this patch adds: - is_pending(): Returns true if the work item is currently enqueued. - cancel(): Attempts to cancel the work item before it runs. If successful, it reclaims and returns ownership of the original pointer (Arc/KBox). - cancel_sync(): Synchronously cancels the work item, waiting for it to finish if it's already running. Reclaims ownership if the work was pending. To support safe pointer reclamation, a new "Handle-based" model is introduced via the WorkItemPointer::reclaim method. This ensures that the "leaked" ownership from an enqueue operation is safely recovered without memory leaks. Additionally, we enforce #[repr(transparent)] on core work wrappers to ensure memory layout compatibility with C pointers and fix pointer arithmetic in the work_container_of implementation. Signed-off-by: Aakash Bollineni --- rust/kernel/workqueue.rs | 467 +++++++++++++++++++++++++++++++++++++++++++= ---- 1 file changed, 432 insertions(+), 35 deletions(-) diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs index 706e833e9702..3e76164d103d 100644 --- a/rust/kernel/workqueue.rs +++ b/rust/kernel/workqueue.rs @@ -448,8 +448,26 @@ pub unsafe trait WorkItemPointer: RawWo= rkItem { /// The provided `work_struct` pointer must originate from a previous = call to [`__enqueue`] /// where the `queue_work_on` closure returned true, and the pointer m= ust still be valid. /// + /// The implementation must ensure that the pointer is reclaimed (e.g.= , via `from_raw`) + /// before calling the `run` method of the underlying [`WorkItem`]. + /// /// [`__enqueue`]: RawWorkItem::__enqueue unsafe extern "C" fn run(ptr: *mut bindings::work_struct); + + /// Reclaims ownership of the pointer from the work item. + /// + /// This is called when a work item is successfully cancelled, allowin= g the caller + /// to recover the original pointer (e.g., `Arc` or `KBox`) that was "= leaked" + /// during enqueuing. + /// + /// # Safety + /// + /// The provided `work_struct` pointer must originate from a previous = call to [`__enqueue`] + /// where the `queue_work_on` closure returned true, and the work item= must have been + /// successfully cancelled (i.e., `cancel_work` returned true). + /// + /// [`__enqueue`]: RawWorkItem::__enqueue + unsafe fn reclaim(ptr: *mut bindings::work_struct) -> Self; } =20 /// Defines the method that should be called when this work item is execut= ed. @@ -516,6 +534,156 @@ pub fn new(name: &'static CStr, key: Pin<&'static Loc= kClassKey>) -> impl PinInit }) } =20 + /// Returns whether the work item is currently pending. + /// + /// # Warning + /// + /// This method is inherently racy. A work item can be enqueued or sta= rt running + /// immediately after this check returns. Do not rely on this for corr= ectness + /// logic (e.g., as a guard for unsafe operations); use it only for de= bugging or + /// non-critical status reporting. + /// + /// # Examples + /// + /// ``` + /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork}; + /// # use kernel::impl_has_work; + /// # use kernel::sync::Arc; + /// # #[pin_data] + /// # struct MyStruct { #[pin] work: Work } + /// # impl_has_work! { impl HasWork for MyStruct { self.work } } + /// # impl WorkItem for MyStruct { + /// # type Pointer =3D Arc; + /// # fn run(_this: Arc) {} + /// # } + /// let my_struct =3D Arc::pin_init(pin_init!(MyStruct { + /// work <- new_work!("MyStruct::work"), + /// }), kernel::alloc::flags::GFP_KERNEL).unwrap(); + /// assert!(!my_struct.work.is_pending()); + /// workqueue::system().enqueue(my_struct.clone()); + /// assert!(my_struct.work.is_pending()); + /// # let _ =3D my_struct.work.cancel(); + /// ``` + #[inline] + pub fn is_pending(&self) -> bool { + // SAFETY: `self.work` is a valid pointer to a `work_struct`. + unsafe { bindings::work_pending(self.work.get()) } + } + + /// Cancels the work item. + /// + /// If the work item was successfully cancelled (i.e., it was pending = and had not yet + /// started running), the original pointer is reclaimed and returned. + /// + /// # Guarantees + /// + /// This method is non-blocking and may return while the work item is = still running + /// on another CPU. If it returns `None`, the work item might be about= to start, + /// is currently running, or has already finished. + /// + /// # Safety + /// + /// This is safe because ownership is only reclaimed if the kernel con= firms (via + /// `cancel_work` returning true) that the work item is no longer in a= ny queue and + /// will not be executed by the workqueue for this specific enqueue ev= ent. + /// + /// [`cancel_sync`]: Work::cancel_sync + /// + /// # Examples + /// + /// ``` + /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork}; + /// # use kernel::impl_has_work; + /// # use kernel::sync::Arc; + /// # #[pin_data] + /// # struct MyStruct { #[pin] work: Work } + /// # impl_has_work! { impl HasWork for MyStruct { self.work } } + /// # impl WorkItem for MyStruct { + /// # type Pointer =3D Arc; + /// # fn run(_this: Arc) {} + /// # } + /// let my_struct =3D Arc::pin_init(pin_init!(MyStruct { + /// work <- new_work!("MyStruct::work"), + /// }), kernel::alloc::flags::GFP_KERNEL).unwrap(); + /// workqueue::system().enqueue(my_struct.clone()); + /// assert!(my_struct.work.is_pending()); + /// let reclaimed =3D my_struct.work.cancel(); + /// assert!(reclaimed.is_some()); + /// assert!(!my_struct.work.is_pending()); + /// ``` + pub fn cancel(&self) -> Option + where + T: WorkItem, + { + let work_ptr =3D self.work.get(); + // SAFETY: `work_ptr` is a valid pointer to a `work_struct`. + if unsafe { bindings::cancel_work(work_ptr) } { + // SAFETY: The work item was successfully cancelled and is gua= ranteed not to run, + // so we can safely reclaim the ownership leaked during `enque= ue`. + Some(unsafe { T::Pointer::reclaim(work_ptr) }) + } else { + None + } + } + + /// Synchronously cancels the work item. + /// + /// This method waits for the work item to finish if it is currently r= unning. + /// If the work item was successfully cancelled from the queue, the po= inter is + /// reclaimed and returned. + /// + /// # Guarantees + /// + /// After this method returns, the work item is guaranteed to be: + /// - Not pending in any queue. + /// - Not running on any CPU. + /// + /// This makes it safe to use during teardown (e.g., driver `remove` o= r object `drop`) + /// to ensure no background tasks are accessing resources that are abo= ut to be freed. + /// + /// # Safety + /// + /// Same as [`cancel`], it only reclaims ownership if the kernel confi= rms the work + /// was still pending and is now removed. + /// + /// [`cancel`]: Work::cancel + /// + /// # Examples + /// + /// ``` + /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork}; + /// # use kernel::impl_has_work; + /// # use kernel::sync::Arc; + /// # #[pin_data] + /// # struct MyStruct { #[pin] work: Work } + /// # impl_has_work! { impl HasWork for MyStruct { self.work } } + /// # impl WorkItem for MyStruct { + /// # type Pointer =3D Arc; + /// # fn run(_this: Arc) {} + /// # } + /// let my_struct =3D Arc::pin_init(pin_init!(MyStruct { + /// work <- new_work!("MyStruct::work"), + /// }), kernel::alloc::flags::GFP_KERNEL).unwrap(); + /// workqueue::system().enqueue(my_struct.clone()); + /// let reclaimed =3D my_struct.work.cancel_sync(); + /// assert!(reclaimed.is_some()); + /// ``` + pub fn cancel_sync(&self) -> Option + where + T: WorkItem, + { + let work_ptr =3D self.work.get(); + // SAFETY: `work_ptr` is a valid pointer to a `work_struct`. + if unsafe { bindings::cancel_work_sync(work_ptr) } { + // SAFETY: The work item was successfully cancelled/waited for= , and is guaranteed + // not to run again unless re-enqueued. We reclaim the ownersh= ip leaked during + // `enqueue`. + Some(unsafe { T::Pointer::reclaim(work_ptr) }) + } else { + None + } + } + /// Get a pointer to the inner `work_struct`. /// /// # Safety @@ -674,25 +842,14 @@ pub fn new( pin_init!(Self { dwork <- Opaque::ffi_init(|slot: *mut bindings::delayed_work| { // SAFETY: The `WorkItemPointer` implementation promises t= hat `run` can be used as - // the work item function. + // the work item function. We use the C-helper to ensure t= he timer function + // and data are initialized correctly according to kernel = macros. unsafe { - bindings::init_work_with_key( - core::ptr::addr_of_mut!((*slot).work), + bindings::init_delayed_work( + slot, Some(T::Pointer::run), - false, work_name.as_char_ptr(), work_key.as_ptr(), - ) - } - - // SAFETY: The `delayed_work_timer_fn` function pointer ca= n be used here because - // the timer is embedded in a `struct delayed_work`, and o= nly ever scheduled via - // the core workqueue code, and configured to run in irqsa= fe context. - unsafe { - bindings::timer_init_key( - core::ptr::addr_of_mut!((*slot).timer), - Some(bindings::delayed_work_timer_fn), - bindings::TIMER_IRQSAFE, timer_name.as_char_ptr(), timer_key.as_ptr(), ) @@ -702,6 +859,89 @@ pub fn new( }) } =20 + /// Returns whether the work item is currently pending. + /// + /// # Warning + /// + /// This method is inherently racy. See [`Work::is_pending`]. + /// + /// # Examples + /// + /// See [`Work::is_pending`]. + /// + /// [`Work::is_pending`]: Work::is_pending + #[inline] + pub fn is_pending(&self) -> bool { + // SAFETY: `self.dwork` is reaching into a valid Opaque. + unsafe { + let ptr: *mut bindings::delayed_work =3D self.dwork.get(); + bindings::work_pending(core::ptr::addr_of_mut!((*ptr).work)) + } + } + + /// Cancels the delayed work item. + /// + /// If the work item was successfully cancelled (i.e., it was pending = and had not yet + /// started running), the original pointer is reclaimed and returned. + /// + /// # Guarantees + /// + /// See [`Work::cancel`]. + /// + /// # Safety + /// + /// Same as [`Work::cancel`]. + /// + /// [`cancel_sync`]: DelayedWork::cancel_sync + /// [`Work::cancel`]: Work::cancel + /// + /// # Examples + /// + /// See [`Work::cancel`]. + pub fn cancel(&self) -> Option + where + T: WorkItem, + { + let dwork_ptr =3D self.dwork.get(); + // SAFETY: `dwork_ptr` is a valid pointer to a `delayed_work`. + if unsafe { bindings::cancel_delayed_work(dwork_ptr) } { + // SAFETY: The work item was successfully cancelled and is gua= ranteed not to run, + // so we can safely reclaim the ownership leaked during `enque= ue`. + Some(unsafe { T::Pointer::reclaim(core::ptr::addr_of_mut!((*dw= ork_ptr).work)) }) + } else { + None + } + } + + /// Synchronously cancels the delayed work item. + /// + /// This method waits for the work item to finish if it is currently r= unning. + /// If the work item was successfully cancelled from the queue, the po= inter is + /// reclaimed and returned. + /// + /// # Guarantees + /// + /// See [`Work::cancel_sync`]. + /// + /// # Safety + /// + /// Same as [`Work::cancel_sync`]. + pub fn cancel_sync(&self) -> Option + where + T: WorkItem, + { + let dwork_ptr =3D self.dwork.get(); + // SAFETY: `dwork_ptr` is a valid pointer to a `delayed_work`. + if unsafe { bindings::cancel_delayed_work_sync(dwork_ptr) } { + // SAFETY: The work item was successfully cancelled/waited for= , and is guaranteed + // not to run again unless re-enqueued. We reclaim the ownersh= ip leaked during + // `enqueue`. + Some(unsafe { T::Pointer::reclaim(core::ptr::addr_of_mut!((*dw= ork_ptr).work)) }) + } else { + None + } + } + /// Get a pointer to the inner `delayed_work`. /// /// # Safety @@ -781,22 +1021,11 @@ unsafe fn raw_get_work( unsafe fn work_container_of( ptr: *mut $crate::workqueue::Work<$work_type $(, $id)?>, ) -> *mut Self { - // SAFETY: The caller promises that the pointer points at = a field of the right type - // in the right kind of struct. - let ptr =3D unsafe { $crate::workqueue::Work::raw_get(ptr)= }; - - // SAFETY: The caller promises that the pointer points at = a field of the right type - // in the right kind of struct. - let delayed_work =3D unsafe { - $crate::container_of!(ptr, $crate::bindings::delayed_w= ork, work) - }; - - let delayed_work: *mut $crate::workqueue::DelayedWork<$wor= k_type $(, $id)?> =3D - delayed_work.cast(); - - // SAFETY: The caller promises that the pointer points at = a field of the right type - // in the right kind of struct. - unsafe { $crate::container_of!(delayed_work, Self, $field)= } + // SAFETY: The caller promises that the pointer points at = the `work` field + // of a `delayed_work` struct, which is itself the `dwork`= field of a + // `DelayedWork` wrapper, which is the `$field` field of a= `Self` struct. + let ptr =3D ptr.cast::<$crate::workqueue::DelayedWork<$wor= k_type $(, $id)?>>(); + unsafe { $crate::container_of!(ptr, Self, $field) } } } )*}; @@ -827,6 +1056,15 @@ unsafe impl WorkItemPointer for= Arc =20 T::run(arc) } + + unsafe fn reclaim(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) } + } } =20 // SAFETY: The `work_struct` raw pointer is guaranteed to be valid for the= duration of the call to @@ -874,7 +1112,8 @@ unsafe impl RawDelayedWorkItem f= or Arc { } =20 -// SAFETY: TODO. +// SAFETY: The `WorkItemPointer` implementation for `Pin>` is safe= because `KBox::from_raw` +// correctly reconstructs the box that was leaked during `enqueue` (via `K= Box::into_raw`). unsafe impl WorkItemPointer for Pin> where T: WorkItem, @@ -883,18 +1122,35 @@ unsafe impl WorkItemPointer fo= r Pin> unsafe extern "C" fn run(ptr: *mut bindings::work_struct) { // 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`. + // SAFETY: This computes the pointer that `__enqueue` got from `KB= ox::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. + // SAFETY: This pointer comes from `KBox::into_raw` and we've been= given back ownership. let boxed =3D unsafe { KBox::from_raw(ptr) }; // SAFETY: The box was already pinned when it was enqueued. let pinned =3D unsafe { Pin::new_unchecked(boxed) }; =20 T::run(pinned) } + + unsafe fn reclaim(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 `KB= ox::into_raw`. + let ptr =3D unsafe { T::work_container_of(ptr) }; + // SAFETY: This pointer comes from `KBox::into_raw` and we've been= given back ownership. + let boxed =3D unsafe { KBox::from_raw(ptr) }; + // SAFETY: The box was already pinned when it was enqueued. + unsafe { Pin::new_unchecked(boxed) } + } } =20 -// SAFETY: TODO. +// SAFETY: The `work_struct` raw pointer is guaranteed to be valid for the= duration of the call to +// the closure because we have exclusive ownership of the `KBox`, and we d= on't drop it ourselves. +// If `queue_work_on` returns true, it is further guaranteed to be valid u= ntil a call to the +// function pointer in `work_struct` because we leak the memory it points = to, and only reclaim it +// if the closure returns false (not reachable for KBox as it must succeed= ) or in +// `WorkItemPointer::run`, which is what the function pointer in the `work= _struct` must be +// pointing to. unsafe impl RawWorkItem for Pin> where T: WorkItem, @@ -1022,3 +1278,144 @@ pub fn system_bh_highpri() -> &'static Queue { // SAFETY: `system_bh_highpri_wq` is a C global, always available. unsafe { Queue::from_raw(bindings::system_bh_highpri_wq) } } + +#[macros::kunit_tests(rust_kernel_workqueue)] +mod tests { + use super::*; + use crate::sync::Arc; + + #[pin_data] + struct TestWorkItem { + #[pin] + work: Work, + value: i32, + } + + impl_has_work! { + impl HasWork for TestWorkItem { self.work } + } + + impl WorkItem for TestWorkItem { + type Pointer =3D Arc; + fn run(_this: Arc) {} + } + + #[pin_data] + struct TestDelayedWorkItem { + #[pin] + delayed_work: DelayedWork, + value: i32, + } + + impl_has_delayed_work! { + impl HasDelayedWork for TestDelayedWorkItem { self.delayed_w= ork } + } + + impl WorkItem for TestDelayedWorkItem { + type Pointer =3D Arc; + fn run(_this: Arc) {} + } + + /// Helper to get Arc strong count for verification in tests. + fn get_arc_count(arc: &Arc) -> i32 { + // SAFETY: ArcInner has refcount as its first field. Arc points to= data at DATA_OFFSET. + unsafe { + let ptr =3D Arc::as_ptr(arc); + let inner_ptr =3D (ptr as *const u8).sub(Arc::::DATA_OFFSET= ); + let refcount_ptr =3D inner_ptr as *const i32; + core::ptr::read_volatile(refcount_ptr) + } + } + + #[test] + fn test_work_cancel_reclaim() { + let item =3D Arc::pin_init( + pin_init!(TestWorkItem { + work <- new_work!("TestWorkItem::work"), + value: 42, + }), + GFP_KERNEL, + ) + .expect("Failed to allocate TestWorkItem"); + + let initial_count =3D get_arc_count(&item); + + // Enqueue + let _ =3D system().enqueue(item.clone()); + + // Cancel and Reclaim (if it was pending) + if let Some(reclaimed) =3D item.work.cancel() { + assert!(get_arc_count(&item) =3D=3D initial_count + 1); + drop(reclaimed); + assert!(get_arc_count(&item) =3D=3D initial_count); + } + } + + #[test] + fn test_work_cancel_sync_reclaim() { + let item =3D Arc::pin_init( + pin_init!(TestWorkItem { + work <- new_work!("TestWorkItem::work"), + value: 42, + }), + GFP_KERNEL, + ) + .expect("Failed to allocate TestWorkItem"); + + let initial_count =3D get_arc_count(&item); + + // Enqueue + let _ =3D system().enqueue(item.clone()); + + // Cancel Sync and Reclaim + if let Some(reclaimed) =3D item.work.cancel_sync() { + assert!(get_arc_count(&item) =3D=3D initial_count + 1); + drop(reclaimed); + assert!(get_arc_count(&item) =3D=3D initial_count); + } + } + + #[test] + fn test_work_stress_enqueue_cancel() { + let item =3D Arc::pin_init( + pin_init!(TestWorkItem { + work <- new_work!("TestWorkItem::work"), + value: 42, + }), + GFP_KERNEL, + ) + .expect("Failed to allocate TestWorkItem"); + + for _ in 0..100 { + if let Ok(_) =3D system().enqueue(item.clone()) { + let _ =3D item.work.cancel_sync(); + } + } + + assert_eq!(get_arc_count(&item), 1); + } + + #[test] + fn test_delayed_work_cancel_reclaim() { + let item =3D Arc::pin_init( + pin_init!(TestDelayedWorkItem { + delayed_work <- new_delayed_work!("TestDelayedWorkItem::de= layed_work"), + value: 42, + }), + GFP_KERNEL, + ) + .expect("Failed to allocate TestDelayedWorkItem"); + + let initial_count =3D get_arc_count(&item); + + // Enqueue delayed + let _ =3D system().enqueue_delayed(item.clone(), 100); + + // Cancel and Reclaim + if let Some(reclaimed) =3D item.delayed_work.cancel() { + assert!(get_arc_count(&item) =3D=3D initial_count + 1); + drop(reclaimed); + assert!(get_arc_count(&item) =3D=3D initial_count); + } + } +} --=20 2.43.0 From nobody Sun Jun 14 08:17:54 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 7C38D345CD0; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775100229; cv=none; b=HiIk7Tkn3LJYHkjL9VPenp4GZoEpznODuiGSLPkhO5DIcGabmx93iOg82ke0+dbsZGBzhnNypYG3Ls0A84ETDkUI0vPJ2GbSi2XrTwAxzR8wpE6YrSLm1C5qAfi7++JVlqB0ceTPP7rYkntQgC/seELzG8e5z0vwSyy3rGMM4o4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775100229; c=relaxed/simple; bh=evIL1nsXHjuu00/bb95LxB49HKD9PzsuBGt0qKw+zkU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pxHLZfDxJVKoliW8ebbjVIVedRM5fnUirQenLGq+HC147XARyBXN3TjXSqAiIMof/f8otoOLpQ7FRbCrXIka9X37KJczNuG+cbtPnb26lWIZQErgctCnYxHBrzxRgrunqt/UNgonDdn7pCyRAPJim2aOf0fjr+oGkMENU5bMqLA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=U3Nd6buj; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="U3Nd6buj" Received: by smtp.kernel.org (Postfix) with ESMTPS id 60264C2BCB0; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1775100229; bh=evIL1nsXHjuu00/bb95LxB49HKD9PzsuBGt0qKw+zkU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=U3Nd6bujtBUQ/7Gw5IgwWEGo+SOQwh2FmhGVNsCMsnjg5R+bYBh8z/630Jhf6W9YJ MRlkMObLPkhMaD3SH7knq8YWD2wQgUBA93ptqiGB97vfgyvuvrbfAYrXRbeZbP/116 fDEJoNyK7JkwZFpBEsFubA4/iHgh1edg3VLFM9ADcLmZJ9qPcgvnxHYl/kcsf6cQwz Ab6aLtmy5ZY8nEaqvDqvxkEYLE/JPATMJgSCd6t2J9dnkGA4OHmQ9DYyidAZd5pGfm ZqFRnROnVvfIpMsEbkuyn6NTUysIjbb2UOe+P70RqQnwGUKQC8QtbFJM1btyG2E5ZJ Na4LWCBUH2/Ow== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 586A4111227A; Thu, 2 Apr 2026 03:23:49 +0000 (UTC) From: Aakash Bollineni via B4 Relay Date: Thu, 02 Apr 2026 08:53:48 +0530 Subject: [PATCH 3/3] rust: workqueue: add KUnit and sample stress tests 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 Message-Id: <20260402-rust-next-v1-3-0940bb8f201c@multicorewareinc.com> References: <20260402-rust-next-v1-0-0940bb8f201c@multicorewareinc.com> In-Reply-To: <20260402-rust-next-v1-0-0940bb8f201c@multicorewareinc.com> To: rust-for-linux@vger.kernel.org Cc: Aakash Bollineni , linux-kernel@vger.kernel.org X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1775100227; l=9449; i=aakash.bollineni@multicorewareinc.com; s=20260402; h=from:subject:message-id; bh=R2vmLu2avJ+Dd7CwhIAtTPqzzOJt675bHUJ2ZA8KFJE=; b=xihdfwBCHhaQj82a42CGq/no0MnKKebse6juN4K3KoIBWeVagx2rhqV/NDWC5yGrNJO/YtIEf X4GZJJ9fH8HDzd/0XKg1T54TWkk8pAzAs2p1k7p7h3h7MVhac/8FWAX X-Developer-Key: i=aakash.bollineni@multicorewareinc.com; a=ed25519; pk=r3Gonl+2k+8RozN9U/XwfICQdnRlAcLeeAfsExmurdE= X-Endpoint-Received: by B4 Relay for aakash.bollineni@multicorewareinc.com/20260402 with auth_id=711 X-Original-From: Aakash Bollineni Reply-To: aakash.bollineni@multicorewareinc.com From: Aakash Bollineni To ensure the safety and correctness of the improved workqueue API, this patch adds comprehensive testing infrastructure: 1. KUnit Tests: Adds an internal 'rust_kernel_workqueue' test suite to rust/kernel/workqueue.rs. These tests verify basic and synchronous cancellation, refcount stability, and concurrency safety for both standard and delayed work. 2. Sample Module: Adds samples/rust/rust_workqueue_test.rs as a standalone module that performs a 1000-iteration stress test designed to verify race-free pointer handover during concurrent enqueue/cancel operations. The tests use distinct types for standard and delayed work items to ensure clear trait dispatch and prevent field offset conflicts. Signed-off-by: Aakash Bollineni --- samples/rust/Makefile | 2 + samples/rust/rust_workqueue_test.rs | 214 ++++++++++++++++++++++++++++++++= ++++ 2 files changed, 216 insertions(+) diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 6c0aaa58cccc..0f304bd90997 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -20,3 +20,5 @@ obj-$(CONFIG_SAMPLE_RUST_SOC) +=3D rust_soc.o rust_print-y :=3D rust_print_main.o rust_print_events.o =20 subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) +=3D hostprogs + +obj-y +=3D rust_workqueue_test.o diff --git a/samples/rust/rust_workqueue_test.rs b/samples/rust/rust_workqu= eue_test.rs new file mode 100644 index 000000000000..c055ec1964c0 --- /dev/null +++ b/samples/rust/rust_workqueue_test.rs @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Robust stress test for Rust workqueue API. + +use kernel::prelude::*; +use kernel::sync::Arc; +use kernel::time::msecs_to_jiffies; +use kernel::workqueue::{self, new_work, Work, WorkItem}; + +#[pin_data] +struct TestItem { + #[pin] + work: Work, + value: i32, + #[pin] + delayed_work: workqueue::DelayedWork, +} + +kernel::impl_has_work! { + impl HasWork for TestItem { self.work } +} + +// SAFETY: The `delayed_work` field is at a fixed offset and is valid for = the lifetime of +// `TestItem`. +unsafe impl workqueue::HasDelayedWork for TestItem {} + +impl WorkItem for TestItem { + type Pointer =3D Arc; + + fn run(this: Arc) { + pr_info!( + "Rust workqueue test: Work item running (value: {})\n", + this.value + ); + } +} + +/// Helper to get Arc strong count for verification in tests. +/// This uses internal layout knowledge of Arc. +fn get_arc_count(arc: &Arc) -> i32 { + // SAFETY: ArcInner has refcount as its first field. Arc points to dat= a at DATA_OFFSET. + unsafe { + let ptr =3D Arc::as_ptr(arc); + let inner_ptr =3D (ptr as *const u8).sub(Arc::::DATA_OFFSET); + // The first field of ArcInner is Refcount, which is a transparent= wrapper around + // refcount_t. In the kernel, refcount_t is an atomic_t (i32). + let refcount_ptr =3D inner_ptr as *const i32; + // We use a relaxed load to get the current value. + core::ptr::read_volatile(refcount_ptr) + } +} + +struct RustWorkqueueTest; + +impl kernel::Module for RustWorkqueueTest { + fn init(_module: &'static ThisModule) -> Result { + pr_info!("Rust workqueue test: starting robust verification\n"); + + // 1. Basic Lifecycle with Refcount Validation + { + let test_item =3D Arc::pin_init( + pin_init!(TestItem { + work <- new_work!("TestItem::work"), + value: 42, + delayed_work <- workqueue::new_delayed_work!("TestItem= ::delayed_work"), + }), + GFP_KERNEL, + )?; + + let initial_count =3D get_arc_count(&test_item); + pr_info!("Initial Arc strong count: {}\n", initial_count); + + // Enqueue + let enqueued_item =3D test_item.clone(); + + if let Err(returned_item) =3D workqueue::system().enqueue(enqu= eued_item) { + pr_warn!("Work already pending, unexpected!\n"); + let _ =3D returned_item; + } else { + pr_info!( + "Work enqueued successfully. Strong count: {}\n", + get_arc_count(&test_item) + ); + } + + // Cancel immediately (best effort) + if let Some(reclaimed) =3D test_item.work.cancel() { + let count_after_cancel =3D get_arc_count(&test_item); + pr_info!( + "Success: Work cancelled and Arc reclaimed. Strong cou= nt: {}\n", + count_after_cancel + ); + + // VALIDATION: Reclamation must restore the refcount (minu= s the clone we just + // reclaimed) + if count_after_cancel !=3D initial_count + 1 { + pr_err!( + "ERROR: Refcount mismatch after cancel! Expected {= }, got {}\n", + initial_count + 1, + count_after_cancel + ); + return Err(ENXIO); + } + drop(reclaimed); + if get_arc_count(&test_item) !=3D initial_count { + pr_err!( + "ERROR: Refcount mismatch after drop! Expected {},= got {}\n", + initial_count, + get_arc_count(&test_item) + ); + return Err(ENXIO); + } + } else { + pr_info!("Work already running or finished, could not recl= aim via cancel().\n"); + } + } + + // 2. Stress Testing: Enqueue/Cancel Sync Loop + { + pr_info!("Starting stress test (1000 iterations)...\n"); + let test_item =3D Arc::pin_init( + pin_init!(TestItem { + work <- new_work!("TestItem::work"), + value: 99, + delayed_work <- workqueue::new_delayed_work!("TestItem= ::delayed_work"), + }), + GFP_KERNEL, + )?; + + for i in 0..1000 { + let _ =3D workqueue::system().enqueue(test_item.clone()); + // Use cancel_sync to ensure determinism for the next iter= ation + let _ =3D test_item.work.cancel_sync(); + + if i % 250 =3D=3D 0 { + pr_info!("Stress test progress: {}/1000\n", i); + } + } + + if get_arc_count(&test_item) !=3D 1 { + pr_err!( + "ERROR: Refcount leak detected after stress test! coun= t: {}\n", + get_arc_count(&test_item) + ); + return Err(ENXIO); + } else { + pr_info!("Stress test completed successfully. No refcount = leaks.\n"); + } + } + + // 3. Delayed Work Cancellation Test + { + let test_item =3D Arc::pin_init( + pin_init!(TestItem { + work <- new_work!("TestItem::work"), + value: 7, + delayed_work <- workqueue::new_delayed_work!("TestItem= ::delayed_work"), + }), + GFP_KERNEL, + )?; + + let initial_count =3D get_arc_count(&test_item); + + // Schedule with a long delay + let to_enqueue =3D test_item.clone(); + let res =3D workqueue::system() + .enqueue_delayed(to_enqueue, msecs_to_jiffies(5000)); + if let Err(returned) =3D res { + pr_warn!("Delayed work already pending, returned item.\n"); + drop(returned); + } else { + pr_info!("Delayed work enqueued. count: {}\n", get_arc_cou= nt(&test_item)); + } + + if test_item.delayed_work.is_pending() { + pr_info!("Delayed work is pending as expected.\n"); + } + + if let Some(reclaimed) =3D test_item.delayed_work.cancel() { + pr_info!("Success: Delayed work reclaimed. No leak.\n"); + drop(reclaimed); + } else { + pr_warn!("Notice: Delayed work not reclaimed (running or n= ever enqueued).\n"); + } + + let final_count =3D get_arc_count(&test_item); + if final_count !=3D initial_count { + pr_err!( + "ERROR: Refcount leak after delayed cancel! expected {= }, got {}\n", + initial_count, + final_count + ); + return Err(ENXIO); + } + } + + pr_info!("Rust workqueue test: all robust checks passed\n"); + Ok(RustWorkqueueTest) + } +} + +impl Drop for RustWorkqueueTest { + fn drop(&mut self) { + pr_info!("Rust workqueue test: exit\n"); + } +} + +module! { + type: RustWorkqueueTest, + name: "rust_workqueue_test", + authors: ["Aakash Bollineni"], + description: "Robust stress test for Rust workqueue API", + license: "GPL", +} --=20 2.43.0