Received: by 2002:a05:6358:3188:b0:123:57c1:9b43 with SMTP id q8csp5873200rwd; Wed, 24 May 2023 07:54:21 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ60JwE8wpmM6xkO5EHKKovK5CCibQjkprMgK4Nmi4rK5oDCqBqIWHqtDmKiQACtxm07B13g X-Received: by 2002:a17:902:7683:b0:1a6:82ac:f277 with SMTP id m3-20020a170902768300b001a682acf277mr16561436pll.14.1684940060807; Wed, 24 May 2023 07:54:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1684940060; cv=none; d=google.com; s=arc-20160816; b=pqLrXpD00OWOzlD7ogJnJeXarEgnLbxWWYMzKaBnf1Q21D0tRCDGOYjmKq2q6mhI1A xIx52LI7bF1e/fhcLzpRs18+/xjFNVDGUmCoLrWy/f+78vdaEGwGSqclZWjw+HJgjgc4 Fv8vndy3bTK9+uWosFcKjrOtrf733brCXoG6yVoWuYc0OQQdI7tlanKWiWzFQrQPccTG XkO/em+iV0ZJct6UB40moibnv8H+y1NNit/Kq1YQY7942Hw2HYlvravfTta5+Fqyqfmd RkmWUJgdm6BrJux6EaK2YYRtLivepdEwTrEiLYOphy1Mi1+1paT5EjLboj+eB6oXtapG sZDQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :feedback-id:references:in-reply-to:message-id:subject:cc:from:to :dkim-signature:date; bh=CL9s3PHQprbA9Ukgl2ZS7fO2vcssugR3QiwDaOjylcQ=; b=Dt+Pl85rF0WQY1B7e6BCEKpw9EAbix43CKeXxikAcHRWrcKOHOPnFZXFazZYIfBjIE vVkgSxmWxv76TjEVfbXmG/plZvPUEa+INWrHYowZCFHIjgtJp1jRJXT1iZpy5rmNWCUC JU8hmPsagvINV6o8iD0xgjv1zR0e3QVAy73HR0gmU94FD11fHUtuYul7Yl7QzgnG5TYl KmG+3bgvzlnvk3Cwp2os0GQ0uBP5+6oGQSK5Su6ElCnC/NmrrfjB2ijFGvFHD3Lw4+OW 1p9cDjpHBF4wn7La/cQzYqZ+uv7z4XI6mtitcYVJPwVu3qXt6dWzyolfHQ1Gm2arXc5z Kgxw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@proton.me header.s=qkvvwriddbeojgix67vuuke7hy.protonmail header.b="dffR/9pF"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=proton.me Return-Path: Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id y15-20020a170902b48f00b001aae741abafsi429929plr.440.2023.05.24.07.54.06; Wed, 24 May 2023 07:54:20 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@proton.me header.s=qkvvwriddbeojgix67vuuke7hy.protonmail header.b="dffR/9pF"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=proton.me Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235996AbjEXOxJ (ORCPT + 99 others); Wed, 24 May 2023 10:53:09 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:41998 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233497AbjEXOxG (ORCPT ); Wed, 24 May 2023 10:53:06 -0400 Received: from mail-4316.protonmail.ch (mail-4316.protonmail.ch [185.70.43.16]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2C4101A8 for ; Wed, 24 May 2023 07:52:36 -0700 (PDT) Date: Wed, 24 May 2023 14:51:48 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proton.me; s=qkvvwriddbeojgix67vuuke7hy.protonmail; t=1684939919; x=1685199119; bh=CL9s3PHQprbA9Ukgl2ZS7fO2vcssugR3QiwDaOjylcQ=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=dffR/9pFMHkotKTsQh3Nq252whjndeWU2fztjO9IZkIIVrjHR+IkxTEsN22sJd8Lz 9X8LfzEqVhpZiaIvzS1xu5+YlFHuBMcugBWoSr9wXmGxbAjANYwoNp20vHCBPsMOY0 uWwuxXGM2w5MeNpTc4UCvrM54Ic8s0TC4tA3vNPXROZYW3kwE7lr2m34AJvtbX0uiQ 1/qV5QsQP1jNjUYJPYePfu6LFY4juCJQ10bkXygKNDOR/vBfB7wwlF2ymQ+RQSKj09 wjkiw1fuIBdEfBB4ugXZkmynBIPlHTp755xaAESCDYdGBRnB+fqafYq4p6r39F1pMv pV560yVRUWlRw== To: Alice Ryhl From: Benno Lossin Cc: rust-for-linux@vger.kernel.org, Miguel Ojeda , Wedson Almeida Filho , Tejun Heo , Lai Jiangshan , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?Q?Bj=C3=B6rn_Roy_Baron?= , linux-kernel@vger.kernel.org, patches@lists.linux.dev Subject: Re: [PATCH v1 6/7] rust: workqueue: add safe API to workqueue Message-ID: In-Reply-To: <20230517203119.3160435-7-aliceryhl@google.com> References: <20230517203119.3160435-1-aliceryhl@google.com> <20230517203119.3160435-7-aliceryhl@google.com> Feedback-ID: 71780778:user:proton MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Status: No, score=-1.7 required=5.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,RCVD_IN_MSPIKE_H2,SPF_HELO_PASS,SPF_PASS, T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Wednesday, May 17th, 2023 at 22:31, Alice Ryhl wr= ote: > This commit introduces `ArcWorkItem`, `BoxWorkItem`, and > `define_work_adapter_newtype!` that make it possible to use the > workqueue without any unsafe code whatsoever. >=20 > The `ArcWorkItem` and `BoxWorkItem` traits are used when a struct has a > single `work_struct` field. >=20 > The `define_work_adapter_newtype!` macro is used when a struct has > multiple `work_struct` fields. For each `work_struct` field, a newtype > struct is defined that wraps `Arc`, and pushing an instance > of the newtype to a workqueue will enqueue it using the associated > `work_struct` field. The newtypes are matched with `work_struct` fields > by having the T in `Work` be the newtype. >=20 > Signed-off-by: Alice Ryhl > --- > rust/kernel/workqueue.rs | 332 ++++++++++++++++++++++++++++++++++++++- > 1 file changed, 331 insertions(+), 1 deletion(-) >=20 > diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs > index 7509618af252..007005ddcaf0 100644 > --- a/rust/kernel/workqueue.rs > +++ b/rust/kernel/workqueue.rs > @@ -4,8 +4,9 @@ > //! > //! C header: [`include/linux/workqueue.h`](../../../../include/linux/wo= rkqueue.h) >=20 > -use crate::{bindings, prelude::*, types::Opaque}; > +use crate::{bindings, prelude::*, sync::Arc, types::Opaque}; > use core::marker::{PhantomData, PhantomPinned}; > +use core::result::Result; >=20 > /// A kernel work queue. > /// > @@ -279,6 +280,335 @@ macro_rules! impl_has_work { > )*}; > } >=20 > +/// Declares that [`Arc`] should implement [`WorkItem`]. > +/// > +/// # Examples > +/// > +/// The example below will make [`Arc`] implement the [`WorkIt= em`] trait so that you can > +/// enqueue it in a workqueue. > +/// > +/// ``` > +/// use kernel::sync::Arc; > +/// > +/// struct MyStruct { > +/// work_field: Work>, > +/// } > +/// > +/// kernel::impl_has_work! { > +/// impl HasWork> for MyStruct { self.work_field } > +/// } > +/// > +/// impl ArcWorkItem for MyStruct { > +/// fn run(self: Arc) { > +/// pr_info!("Executing MyStruct on a workqueue."); > +/// } > +/// } > +/// ``` > +/// > +/// [`Arc`]: crate::sync::Arc > +/// [`Arc`]: crate::sync::Arc > +pub trait ArcWorkItem { > + /// Called when this work item is executed. > + fn run(self: Arc); > +} > + > +unsafe impl WorkItem for Arc > +where > + T: ArcWorkItem + HasWork + ?Sized, > +{ > + type EnqueueOutput =3D Result<(), Self>; > + > + unsafe fn __enqueue(self, queue_work_on: F) -> Self::EnqueueOutpu= t > + where > + F: FnOnce(*mut bindings::work_struct) -> bool, > + { > + let ptr =3D Arc::into_raw(self); > + > + // Using `get_work_offset` here for object-safety. > + // > + // SAFETY: The pointer is valid since we just got it from `into_= raw`. > + let off =3D unsafe { (&*ptr).get_work_offset() }; > + > + // SAFETY: The `HasWork` impl promises that this offset gives us= a field of type > + // `Work` in the same allocation. > + let work_ptr =3D unsafe { (ptr as *const u8).add(off) as *const = Work }; > + // SAFETY: The pointer is not dangling. > + let work_ptr =3D unsafe { Work::raw_get(work_ptr) }; > + > + match (queue_work_on)(work_ptr) { > + true =3D> Ok(()), > + // SAFETY: The work queue has not taken ownership of the poi= nter. > + false =3D> Err(unsafe { Arc::from_raw(ptr) }), > + } > + } > +} > + > +// Let `Work>` be usable with types that are `ArcWorkItem`. > +// > +// We do not allow unsized types here. The `Work>` field should a= lways specify the actual > +// concrete type stored in the `Arc`. > +// > +// SAFETY: The `Work>` field must be initialized with this `run` = method because the `Work` > +// struct prevents you from initializing it in any other way. The `__enq= ueue` trait uses the > +// same `Work>` field because `HasWork` promises to always return= the same field. > +unsafe impl WorkItemAdapter for Arc > +where > + T: ArcWorkItem + HasWork + Sized, > +{ > + unsafe extern "C" fn run(ptr: *mut bindings::work_struct) { > + // SAFETY: The `__enqueue` method always uses a `work_struct` st= ored in a `Work`. > + let ptr =3D ptr as *mut Work; > + // SAFETY: This computes the pointer that `__enqueue` got from `= Arc::into_raw`. > + let ptr =3D unsafe { T::work_container_of(ptr) }; > + // SAFETY: This pointer comes from `Arc::into_raw` and we've bee= n given back ownership. > + let arc =3D unsafe { Arc::from_raw(ptr) }; > + > + arc.run(); > + } > +} > + > +/// Declares that [`Pin`]`<`[`Box`]`>` should implement [`WorkItem= `]. > +/// > +/// # Examples > +/// > +/// The example below will make [`Pin`]`<`[`Box`]`>` implement= the [`WorkItem`] trait so > +/// that you can enqueue it in a workqueue. > +/// > +/// ``` > +/// struct MyStruct { > +/// work_field: Work>>, > +/// } > +/// > +/// kernel::impl_has_work! { > +/// impl HasWork>> for MyStruct { self.work_field = } > +/// } > +/// > +/// impl BoxWorkItem for MyStruct { > +/// fn run(self: Pin>) { > +/// pr_info!("Executing MyStruct on a workqueue."); > +/// } > +/// } > +/// ``` > +/// > +/// [`Box`]: alloc::boxed::Box > +/// [`Pin`]: core::pin::Pin > +pub trait BoxWorkItem { > + /// Called when this work item is executed. > + fn run(self: Pin>); > +} > + > +unsafe impl WorkItem for Pin> > +where > + T: BoxWorkItem + HasWork + ?Sized, > +{ > + // When a box is in a workqueue, the workqueue has exclusive ownersh= ip of the box. Therefore, > + // it's not possible to enqueue a box while it is in a workqueue. > + type EnqueueOutput =3D (); > + > + unsafe fn __enqueue(self, queue_work_on: F) > + where > + F: FnOnce(*mut bindings::work_struct) -> bool, > + { > + // SAFETY: We will not used the contents in an unpinned manner. > + let ptr =3D unsafe { Box::into_raw(Pin::into_inner_unchecked(sel= f)) }; > + > + // Using `get_work_offset` here for object-safety. > + // > + // SAFETY: The pointer is valid since we just got it from `into_= raw`. > + let off =3D unsafe { (&*ptr).get_work_offset() }; > + > + // SAFETY: The `HasWork` impl promises that this offset gives us= a field of type > + // `Work` in the same allocation. > + let work_ptr =3D unsafe { (ptr as *mut u8).add(off) as *mut Work= }; > + // SAFETY: The pointer is not dangling. > + let work_ptr =3D unsafe { Work::raw_get(work_ptr) }; > + > + match (queue_work_on)(work_ptr) { > + true =3D> {} > + // SAFETY: This method requires exclusive ownership of the b= ox, so it cannot be in a > + // workqueue. > + false =3D> unsafe { core::hint::unreachable_unchecked() }, > + } > + } > +} > + > +// Let `Work>>` be usable with types that are `BoxWorkItem`. > +// > +// We do not allow unsized types here. The `Work>>` field sho= uld always specify the actual > +// concrete type stored in the `Box`. > +// > +// SAFETY: The `Work>>` field must be initialized with this r= un method because the `Work` > +// struct prevents you from initializing it in any other way. The `__enq= ueue` trait uses the > +// same `Work>>` field because `HasWork` promises to always r= eturn the same field. > +unsafe impl WorkItemAdapter for Pin> > +where > + T: BoxWorkItem + HasWork + Sized, > +{ > + unsafe extern "C" fn run(ptr: *mut bindings::work_struct) { > + // SAFETY: The `__enqueue` method always uses a `work_struct` st= ored in a `Work`. > + let ptr =3D ptr as *mut Work; > + // SAFETY: This computes the pointer that `__enqueue` got from `= Arc::into_raw`. > + let ptr =3D unsafe { T::work_container_of(ptr) }; > + // SAFETY: This pointer comes from `Box::into_raw` and we've bee= n given back ownership. > + // The box was originally pinned, so pinning it again is ok. > + let boxed =3D unsafe { Pin::new_unchecked(Box::from_raw(ptr)) }; > + > + boxed.run(); > + } > +} > + > +/// Helper macro for structs with several `Work` fields that can be in s= everal queues at once. > +/// > +/// For each `Work` field in your type `T`, a newtype struct that wraps = an `Arc` or > +/// `Pin>` should be defined. > +/// > +/// # Examples > +/// > +/// ``` > +/// struct MyStruct { > +/// work1: Work, > +/// work2: Work, > +/// } > +/// > +/// impl_has_work! { > +/// impl HasWork for MyStruct { self.work1 } > +/// impl HasWork for MyStruct { self.work2 } > +/// } > +/// > +/// define_work_adapter_newtype! { > +/// struct MyStructWork1(Arc); > +/// struct MyStructWork2(Arc); > +/// } > +/// > +/// impl MyStructWork1 { > +/// fn run(self) { > +/// // ... > +/// } > +/// } > +/// > +/// impl MyStructWork2 { > +/// fn run(self) { > +/// // ... > +/// } > +/// } > +/// ``` > +/// > +/// This will let you push a `MyStructWork1(arc)` or `MyStructWork2(arc)= ` to a work queue. The [`Arc`] > +/// can be in two work queues at the same time, and the `run` method on = the wrapper type is called > +/// when the work item is called. > +/// > +/// [`Arc`]: crate::sync::Arc > +#[macro_export] > +macro_rules! define_work_adapter_newtype { > + ( > + $(#[$outer:meta])* > + $pub:vis struct $name:ident( > + $(#[$innermeta:meta])* > + $fpub:vis Arc<$inner:ty> $(,)? > + ); > + $($rest:tt)* > + ) =3D> { > + $(#[$outer])* > + $pub struct $name($(#[$innermeta])* $fpub $crate::sync::Arc<$inn= er>); I am a bit confused as to why these types *contain* a pointer. Shouldn't these be exactly the same `Work<$inner>`, except they allow multiple `run` functions? So IMO they should embed a `Work<$inner>` and the manually defined `run` function would take a `$inner`. > + > + unsafe impl $crate::workqueue::WorkItem for $name { > + type EnqueueOutput =3D ::core::result::Result<(), $name>; > + > + unsafe fn __enqueue(self, queue_work_on: F) -> Self::Enqu= eueOutput > + where > + F: ::core::ops::FnOnce(*mut $crate::bindings::work_struc= t) -> bool, > + { > + let ptr =3D $crate::sync::Arc::into_raw(self.0); > + > + // SAFETY: The pointer is not dangling since we just got= it from Arc::into_raw. > + let work_ptr =3D unsafe { <$inner as $crate::workqueue::= HasWork::<$name>>::raw_get_work(ptr.cast_mut()) }; > + > + // SAFETY: The pointer is not dangling. > + let work_ptr =3D unsafe { $crate::workqueue::Work::raw_g= et(work_ptr) }; > + > + match (queue_work_on)(work_ptr) { > + true =3D> Ok(()), > + // SAFETY: The work queue has not taken ownership of= the pointer. > + false =3D> Err($name(unsafe { $crate::sync::Arc::fro= m_raw(ptr) })), > + } > + } > + } > + > + unsafe impl $crate::workqueue::WorkItemAdapter for $name { > + unsafe extern "C" fn run(ptr: *mut $crate::bindings::work_st= ruct) { > + // SAFETY: The `__enqueue` method always uses a `work_st= ruct` stored in a `Work`. > + let ptr =3D ptr as *mut $crate::workqueue::Work; > + // SAFETY: This computes the pointer that `__enqueue` go= t from `Arc::into_raw`. > + let ptr =3D unsafe { <$inner as $crate::workqueue::HasWo= rk::<$name>>::work_container_of(ptr) }; > + // SAFETY: This pointer comes from `Arc::into_raw` and w= e've been given back ownership. > + let arc =3D unsafe { $crate::sync::Arc::from_raw(ptr) }; > + > + $name::run($name(arc)); > + } > + } > + > + define_work_adapter_newtype! { $($rest)* } > + }; > + > + ( > + $(#[$outer:meta])* > + $pub:vis struct $name:ident( > + $(#[$innermeta:meta])* > + $fpub:vis Pin> $(,)? > + ); > + $($rest:tt)* > + ) =3D> { > + $(#[$outer])* > + $pub struct $name($(#[$innermeta])* $fpub ::core::pin::Pin<::all= oc::boxed::Box<$inner>>); > + > + unsafe impl $crate::workqueue::WorkItem for $name { > + type EnqueueOutput =3D (); > + > + unsafe fn __enqueue(self, queue_work_on: F) > + where > + F: ::core::ops::FnOnce(*mut $crate::bindings::work_struc= t) -> bool, > + { > + // SAFETY: We will not used the contents in an unpinned = manner. > + let boxed =3D unsafe { ::core::pin::Pin::into_inner_unch= ecked(self.0) }; > + let ptr =3D ::alloc::boxed::Box::into_raw(boxed); > + > + // SAFETY: The pointer is not dangling since we just got= it from Box::into_raw. > + let work_ptr =3D unsafe { <$inner as $crate::workqueue::= HasWork::<$name>>::raw_get_work(ptr) }; > + > + // SAFETY: The pointer is not dangling. > + let work_ptr =3D unsafe { $crate::workqueue::Work::raw_g= et(work_ptr) }; > + > + match (queue_work_on)(work_ptr) { > + true =3D> {}, > + // SAFETY: This method requires exclusive ownership = of the box, so it cannot be in a > + // workqueue. > + false =3D> unsafe { ::core::hint::unreachable_unchec= ked() }, > + } > + } > + } > + > + unsafe impl $crate::workqueue::WorkItemAdapter for $name { > + unsafe extern "C" fn run(ptr: *mut $crate::bindings::work_st= ruct) { > + // SAFETY: The `__enqueue` method always uses a `work_st= ruct` stored in a `Work`. > + let ptr =3D ptr as *mut $crate::workqueue::Work; > + // SAFETY: This computes the pointer that `__enqueue` go= t from `Arc::into_raw`. > + let ptr =3D unsafe { <$inner as $crate::workqueue::HasWo= rk::<$name>>::work_container_of(ptr) }; > + // SAFETY: This pointer comes from `Box::into_raw` and w= e've been given back ownership. > + let boxed =3D unsafe { ::alloc::boxed::Box::from_raw(ptr= ) }; > + // SAFETY: The box was originally pinned, so pinning it = again is ok. > + let boxed =3D unsafe { ::core::pin::Pin::new_unchecked(b= oxed) }; > + > + $name::run($name(boxed)); > + } > + } > + > + define_work_adapter_newtype! { $($rest)* } > + }; > + > + // After processing the last definition, we call ourselves with no i= nput. > + () =3D> {}; > +} > + > /// Returns the system work queue (`system_wq`). > /// > /// It is the one used by `schedule[_delayed]_work[_on]()`. Multi-CPU mu= lti-threaded. There are > -- > 2.40.1.606.ga4b1b128d6-goog >=20 -- Cheers, Benno