From nobody Wed Oct 8 20:01:09 2025 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 4558F2550A4; Tue, 24 Jun 2025 21:56:11 +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=1750802172; cv=none; b=k9EK0lV2tFVftL/G/uu8T/OobvaTUuZDZoX1YcoGekbpZ1ti7meouqh+O4DTeFfj99q1Yrc+nWe+RYF/duks897+qS5j791kchgfxQoXsFpjodpYefdNEZA5kCBLhU0cxFwuV4+4W8zAf8iY3Js5DPbCKNSMtXkAY129gCih8oA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750802172; c=relaxed/simple; bh=2iMU85xcd+FS2UmQuxpkKbGqhdtchAImhjzX8IQdIV8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KIa3yPjqxjcKGXl/2Lb2UIQnSt7GitWXwwPWZB7tQ0g/AAE955HqTaMrS6ljJm1zbKeC9SUGSqR6nMZegjSV9LFFOK6shD4tbO/j9PxSSw49VXil5LrWNpqmqNx4edP/ssOBGwXBKzNL8c+Zzrn1RnoZv8J4Vs8v3QVJnMawyyc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=m2GT07sN; 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="m2GT07sN" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E22EDC4CEF2; Tue, 24 Jun 2025 21:56:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1750802171; bh=2iMU85xcd+FS2UmQuxpkKbGqhdtchAImhjzX8IQdIV8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=m2GT07sNIb6uQupbUfR0mmbAejF5QjJCOLyCoIQI36IJGMY8MVKIg8O9PKTH7dUbj eQpa2qFLACq0US5DEBq1suBADL4yD0VOKqO2bKzC1x3Eh1JnwtqlB0Lk+n5Ckn5pqP fjYlnR3+FamjtbxHA6qmG5dm+rH6NJmUScY/6u7XgR+ofnAaxAtBAUMq5PfOUO++S3 yX89CUXrJzNw5m9mAj1X6WzH+YRFlH2g+9Ao+2O0Rp9CHLdJnAaVuwUJ7nJ9Gc5PZ8 1PLuY/znG/c6tV/8nWD0TvQXVU+t9c0r33JGCyR52lZnTQL8d4Hr02wyVT75YcsZzD awNhj0MOfD8YA== From: Danilo Krummrich To: gregkh@linuxfoundation.org, rafael@kernel.org, ojeda@kernel.org, alex.gaynor@gmail.com, boqun.feng@gmail.com, gary@garyguo.net, bjorn3_gh@protonmail.com, lossin@kernel.org, a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu, david.m.ertman@intel.com, ira.weiny@intel.com, leon@kernel.org, kwilczynski@kernel.org, bhelgaas@google.com Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org, Danilo Krummrich Subject: [PATCH v3 1/4] rust: revocable: support fallible PinInit types Date: Tue, 24 Jun 2025 23:53:59 +0200 Message-ID: <20250624215600.221167-2-dakr@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250624215600.221167-1-dakr@kernel.org> References: <20250624215600.221167-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" Currently, Revocable::new() only supports infallible PinInit implementations, i.e. impl PinInit. This has been sufficient so far, since users such as Devres do not support fallibility. Since this is about to change, make Revocable::new() generic over the error type E. Reviewed-by: Benno Lossin Reviewed-by: Alice Ryhl Acked-by: Miguel Ojeda Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 2 +- rust/kernel/revocable.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 57502534d985..544e50efab43 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -100,7 +100,7 @@ struct DevresInner { impl DevresInner { fn new(dev: &Device, data: T, flags: Flags) -> Result>> { let inner =3D Arc::pin_init( - pin_init!( DevresInner { + try_pin_init!( DevresInner { dev: dev.into(), callback: Self::devres_callback, data <- Revocable::new(data), diff --git a/rust/kernel/revocable.rs b/rust/kernel/revocable.rs index fa1fd70efa27..46768b374656 100644 --- a/rust/kernel/revocable.rs +++ b/rust/kernel/revocable.rs @@ -82,11 +82,11 @@ unsafe impl Sync for Revocable {} =20 impl Revocable { /// Creates a new revocable instance of the given data. - pub fn new(data: impl PinInit) -> impl PinInit { - pin_init!(Self { + pub fn new(data: impl PinInit) -> impl PinInit { + try_pin_init!(Self { is_available: AtomicBool::new(true), data <- Opaque::pin_init(data), - }) + }? E) } =20 /// Tries to access the revocable wrapped object. --=20 2.49.0 From nobody Wed Oct 8 20:01:09 2025 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 0AD9C2550A4; Tue, 24 Jun 2025 21:56:16 +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=1750802177; cv=none; b=rS/MqTzCEdDccAT1x3I0lPCfBBA6eSsAckzMf2Ub5t9uO6xzd5zcZKAnZrfdT46xhWZci0UsV7wZ2BFBhmN5q+xF+Gw7UEPmXQsuf6g+WYOKz9QH5ph0I4QEDe4dfBES7cxNUY3Ru7oVuP+h7Xllkzses0NBnnHicp8AchBqNQ0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750802177; c=relaxed/simple; bh=VmOot5ihvDT8vHo6Pvu2OwL+yrz5YZtsw3vjYbzmAQU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bFrN5SKLH9PC+SQGvfhQqlbr4HftsfTTKgZK03T8s67KQuarIJipCKAeuy/8M5uJtTZ4G/FCz7J5Q8I4zUinIRkEkWJ4VxMsjj+1u6+JtSyNAWEfZisYntJIm5qGEEMkktnnBT3A1aM3wRPYossGRMH0Rqac+pDhOQ4qRTaU+Pc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=teRJ32aq; 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="teRJ32aq" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3D009C4CEE3; Tue, 24 Jun 2025 21:56:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1750802176; bh=VmOot5ihvDT8vHo6Pvu2OwL+yrz5YZtsw3vjYbzmAQU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=teRJ32aqZDeCpoZHq0/ujJsiCHgonzydE79dMh3W9hnPsDSMltT2v52Dls1HGxZWl lDsy4Ahi73AKWG+o4zsqMTz1GdMq4E8ZylljERGlJKzgA0WE77XtASHr7HgvAwLb+s QF+TWKWmjC0cCcTfdfAu/8yPT6YtvIxJclzzPODWdBZZDyHPjigPRGWeJZvGsgEaXz Wgyp2I9iaAWsrdB1MlGIlYDxU18KmLIgfu4SpD5oOMERF+XScg16DZjQ34uk2nXSRt fnfS4k+MIDAOFypaZ7AumcyJOXohSYqlWkXSpRetrkGyNCL2ptuhzKv5fq4+4CpUno zcTe1MdhAy6cg== From: Danilo Krummrich To: gregkh@linuxfoundation.org, rafael@kernel.org, ojeda@kernel.org, alex.gaynor@gmail.com, boqun.feng@gmail.com, gary@garyguo.net, bjorn3_gh@protonmail.com, lossin@kernel.org, a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu, david.m.ertman@intel.com, ira.weiny@intel.com, leon@kernel.org, kwilczynski@kernel.org, bhelgaas@google.com Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org, Danilo Krummrich , Dave Airlie , Simona Vetter , Viresh Kumar Subject: [PATCH v3 2/4] rust: devres: replace Devres::new_foreign_owned() Date: Tue, 24 Jun 2025 23:54:00 +0200 Message-ID: <20250624215600.221167-3-dakr@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250624215600.221167-1-dakr@kernel.org> References: <20250624215600.221167-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" Replace Devres::new_foreign_owned() with devres::register(). The current implementation of Devres::new_foreign_owned() creates a full Devres container instance, including the internal Revocable and completion. However, none of that is necessary for the intended use of giving full ownership of an object to devres and getting it dropped once the given device is unbound. Hence, implement devres::register(), which is limited to consume the given data, wrap it in a KBox and drop the KBox once the given device is unbound, without any other synchronization. Cc: Dave Airlie Cc: Simona Vetter Cc: Viresh Kumar Acked-by: Viresh Kumar Reviewed-by: Benno Lossin Reviewed-by: Alice Ryhl Signed-off-by: Danilo Krummrich --- rust/helpers/device.c | 7 ++++ rust/kernel/cpufreq.rs | 11 +++--- rust/kernel/devres.rs | 70 +++++++++++++++++++++++++++++++++------ rust/kernel/drm/driver.rs | 14 ++++---- 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/rust/helpers/device.c b/rust/helpers/device.c index b2135c6686b0..502fef7e9ae8 100644 --- a/rust/helpers/device.c +++ b/rust/helpers/device.c @@ -8,3 +8,10 @@ int rust_helper_devm_add_action(struct device *dev, { return devm_add_action(dev, action, data); } + +int rust_helper_devm_add_action_or_reset(struct device *dev, + void (*action)(void *), + void *data) +{ + return devm_add_action_or_reset(dev, action, data); +} diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs index 11b03e9d7e89..dd84e2b4d7ae 100644 --- a/rust/kernel/cpufreq.rs +++ b/rust/kernel/cpufreq.rs @@ -13,7 +13,7 @@ cpu::CpuId, cpumask, device::{Bound, Device}, - devres::Devres, + devres, error::{code::*, from_err_ptr, from_result, to_result, Result, VTABLE_= DEFAULT_ERROR}, ffi::{c_char, c_ulong}, prelude::*, @@ -1046,10 +1046,13 @@ pub fn new() -> Result { =20 /// Same as [`Registration::new`], but does not return a [`Registratio= n`] instance. /// - /// Instead the [`Registration`] is owned by [`Devres`] and will be re= voked / dropped, once the + /// Instead the [`Registration`] is owned by [`devres::register`] and = will be dropped, once the /// device is detached. - pub fn new_foreign_owned(dev: &Device) -> Result { - Devres::new_foreign_owned(dev, Self::new()?, GFP_KERNEL) + pub fn new_foreign_owned(dev: &Device) -> Result + where + T: 'static, + { + devres::register(dev, Self::new()?, GFP_KERNEL) } } =20 diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 544e50efab43..47ead37faf4c 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -9,12 +9,12 @@ alloc::Flags, bindings, device::{Bound, Device}, - error::{Error, Result}, + error::{to_result, Error, Result}, ffi::c_void, prelude::*, revocable::{Revocable, RevocableGuard}, sync::{rcu, Arc, Completion}, - types::ARef, + types::{ARef, ForeignOwnable}, }; =20 #[pin_data] @@ -184,14 +184,6 @@ pub fn new(dev: &Device, data: T, flags: Flags)= -> Result { Ok(Devres(inner)) } =20 - /// Same as [`Devres::new`], but does not return a `Devres` instance. = Instead the given `data` - /// is owned by devres and will be revoked / dropped, once the device = is detached. - pub fn new_foreign_owned(dev: &Device, data: T, flags: Flags) -= > Result { - let _ =3D DevresInner::new(dev, data, flags)?; - - Ok(()) - } - /// Obtain `&'a T`, bypassing the [`Revocable`]. /// /// This method allows to directly obtain a `&'a T`, bypassing the [`R= evocable`], by presenting @@ -261,3 +253,61 @@ fn drop(&mut self) { } } } + +/// Consume `data` and [`Drop::drop`] `data` once `dev` is unbound. +fn register_foreign(dev: &Device, data= : P) -> Result { + let ptr =3D data.into_foreign(); + + #[allow(clippy::missing_safety_doc)] + unsafe extern "C" fn callback(ptr: *mut kernel::ffi= ::c_void) { + // SAFETY: `ptr` is the pointer to the `ForeignOwnable` leaked abo= ve and hence valid. + drop(unsafe { P::from_foreign(ptr.cast()) }); + } + + // SAFETY: + // - `dev.as_raw()` is a pointer to a valid and bound device. + // - `ptr` is a valid pointer the `ForeignOwnable` devres takes owners= hip of. + to_result(unsafe { + // `devm_add_action_or_reset()` also calls `callback` on failure, = such that the + // `ForeignOwnable` is released eventually. + bindings::devm_add_action_or_reset(dev.as_raw(), Some(callback::), ptr.cast()) + }) +} + +/// Encapsulate `data` in a [`KBox`] and [`Drop::drop`] `data` once `dev` = is unbound. +/// +/// # Examples +/// +/// ```no_run +/// use kernel::{device::{Bound, Device}, devres}; +/// +/// /// Registration of e.g. a class device, IRQ, etc. +/// struct Registration; +/// +/// impl Registration { +/// fn new() -> Self { +/// // register +/// +/// Self +/// } +/// } +/// +/// impl Drop for Registration { +/// fn drop(&mut self) { +/// // unregister +/// } +/// } +/// +/// fn from_bound_context(dev: &Device) -> Result { +/// devres::register(dev, Registration::new(), GFP_KERNEL) +/// } +/// ``` +pub fn register(dev: &Device, data: impl PinInit, flags= : Flags) -> Result +where + T: 'static, + Error: From, +{ + let data =3D KBox::pin_init(data, flags)?; + + register_foreign(dev, data) +} diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs index acb638086131..f63addaf7235 100644 --- a/rust/kernel/drm/driver.rs +++ b/rust/kernel/drm/driver.rs @@ -5,9 +5,7 @@ //! C header: [`include/linux/drm/drm_drv.h`](srctree/include/linux/drm/dr= m_drv.h) =20 use crate::{ - bindings, device, - devres::Devres, - drm, + bindings, device, devres, drm, error::{to_result, Result}, prelude::*, str::CStr, @@ -130,18 +128,22 @@ fn new(drm: &drm::Device, flags: usize) -> Result<= Self> { } =20 /// Same as [`Registration::new`}, but transfers ownership of the [`Re= gistration`] to - /// [`Devres`]. + /// [`devres::register`]. pub fn new_foreign_owned( drm: &drm::Device, dev: &device::Device, flags: usize, - ) -> Result { + ) -> Result + where + T: 'static, + { if drm.as_ref().as_raw() !=3D dev.as_raw() { return Err(EINVAL); } =20 let reg =3D Registration::::new(drm, flags)?; - Devres::new_foreign_owned(dev, reg, GFP_KERNEL) + + devres::register(dev, reg, GFP_KERNEL) } =20 /// Returns a reference to the `Device` instance for this registration. --=20 2.49.0 From nobody Wed Oct 8 20:01:09 2025 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 96F412550A4; Tue, 24 Jun 2025 21:56:21 +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=1750802181; cv=none; b=kLCbLg05XIsearv2ZZSFvvlx+0VDq8lWe/R/YbxXDMp1bPSNp+vmSgitGu0yiuY9/M5efwC+fBWNSsPJbBOahKyBL9udM4GTXCdrxk7euyRKjl/f8jSnrcz/UtapsFZUy0jBYfDHx3MmP3/nAp29ft/QocF3+LySKCmgwsaidOY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750802181; c=relaxed/simple; bh=dx4a+bZV5PhRq106yEHXAnSF/szd9gVYUC+VoZ6iawU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sTJIb/fwhYSdsy2bW+/HgU6T22KTLW1/n69U3aR2A4T/DqlCYZl09xBZtiTGJgoYkXba7awxPz/TqgxXEzuY2cbqWM5OWim42ZaHN8i0SWOc8exdA7IZo+SFdQJIpxMlMEuLhzE6cvUG1dbK86W9WdQepBOdaKX+AC31O7Co2ps= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=RF9IcWfw; 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="RF9IcWfw" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 19266C4CEF0; Tue, 24 Jun 2025 21:56:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1750802181; bh=dx4a+bZV5PhRq106yEHXAnSF/szd9gVYUC+VoZ6iawU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RF9IcWfw0dM3URYiIt3+mvZbseedpWL+SCw6Za4KbBA8XvDjD+Udc/PU1I/sGNJo0 ka1//ElVGOwgzDRBv8eKWyP7D+SL9SLZQwLiBetk+2OYw5dkKeBvTfldR6R1x5zRge 35/7+3CA7Sj3pqkfH2Vzj7q4SywAazBPFWm8RJ56r/r3DCedaCZarGfcCkC6apk2Fu 9dKUmMt7ilE7yi3EVjKNdsdH1YdXEVNJEO7gz+MV8afJg1jEiWyngv3YXG1j6zPQRK lwCwaFT70ar+/2FLcnTNKS3KJ0wyyr+Dykik3mTw83C2yM+w+DAiHrLE9LvE93BdJh +4CUTXvbwXa8w== From: Danilo Krummrich To: gregkh@linuxfoundation.org, rafael@kernel.org, ojeda@kernel.org, alex.gaynor@gmail.com, boqun.feng@gmail.com, gary@garyguo.net, bjorn3_gh@protonmail.com, lossin@kernel.org, a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu, david.m.ertman@intel.com, ira.weiny@intel.com, leon@kernel.org, kwilczynski@kernel.org, bhelgaas@google.com Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org, Danilo Krummrich Subject: [PATCH v3 3/4] rust: devres: get rid of Devres' inner Arc Date: Tue, 24 Jun 2025 23:54:01 +0200 Message-ID: <20250624215600.221167-4-dakr@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250624215600.221167-1-dakr@kernel.org> References: <20250624215600.221167-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" So far Devres uses an inner memory allocation and reference count, i.e. an inner Arc, in order to ensure that the devres callback can't run into a use-after-free in case where the Devres object is dropped while the devres callback runs concurrently. Instead, use a completion in order to avoid a potential UAF: In Devres::drop(), if we detect that we can't remove the devres action anymore, we wait for the completion that is completed from the devres callback. If, in turn, we were able to successfully remove the devres action, we can just go ahead. This, again, allows us to get rid of the internal Arc, and instead let Devres consume an `impl PinInit` in order to return an `impl PinInit, E>`, which enables us to get away with less memory allocations. Additionally, having the resulting explicit synchronization in Devres::drop() prevents potential subtle undesired side effects of the devres callback dropping the final Arc reference asynchronously within the devres callback. Signed-off-by: Danilo Krummrich --- drivers/gpu/nova-core/driver.rs | 7 +- drivers/gpu/nova-core/gpu.rs | 6 +- rust/kernel/devres.rs | 210 +++++++++++++++++++------------- rust/kernel/pci.rs | 20 +-- samples/rust/rust_driver_pci.rs | 19 +-- 5 files changed, 151 insertions(+), 111 deletions(-) diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver= .rs index 8c86101c26cb..110f2b355db4 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 =20 -use kernel::{auxiliary, bindings, c_str, device::Core, pci, prelude::*}; +use kernel::{auxiliary, bindings, c_str, device::Core, pci, prelude::*, sy= nc::Arc}; =20 use crate::gpu::Gpu; =20 @@ -34,7 +34,10 @@ fn probe(pdev: &pci::Device, _info: &Self::IdInfo)= -> Result(0, c_str!("nova-c= ore/bar0"))?; + let bar =3D Arc::pin_init( + pdev.iomap_region_sized::(0, c_str!("nova-core/bar0= ")), + GFP_KERNEL, + )?; =20 let this =3D KBox::pin_init( try_pin_init!(Self { diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs index 60b86f370284..47653c14838b 100644 --- a/drivers/gpu/nova-core/gpu.rs +++ b/drivers/gpu/nova-core/gpu.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 =20 -use kernel::{device, devres::Devres, error::code::*, pci, prelude::*}; +use kernel::{device, devres::Devres, error::code::*, pci, prelude::*, sync= ::Arc}; =20 use crate::driver::Bar0; use crate::firmware::{Firmware, FIRMWARE_VERSION}; @@ -161,14 +161,14 @@ fn new(bar: &Bar0) -> Result { pub(crate) struct Gpu { spec: Spec, /// MMIO mapping of PCI BAR 0 - bar: Devres, + bar: Arc>, fw: Firmware, } =20 impl Gpu { pub(crate) fn new( pdev: &pci::Device, - devres_bar: Devres, + devres_bar: Arc>, ) -> Result> { let bar =3D devres_bar.access(pdev.as_ref())?; let spec =3D Spec::new(bar)?; diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 47ead37faf4c..eeffdc8115aa 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -13,16 +13,21 @@ ffi::c_void, prelude::*, revocable::{Revocable, RevocableGuard}, - sync::{rcu, Arc, Completion}, - types::{ARef, ForeignOwnable}, + sync::{rcu, Completion}, + types::{ARef, ForeignOwnable, ScopeGuard, Opaque}, }; =20 +use pin_init::Wrapper; + +/// [`Devres`] inner data accessed from [`Devres::callback`]. #[pin_data] -struct DevresInner { - dev: ARef, - callback: unsafe extern "C" fn(*mut c_void), +struct Inner { #[pin] data: Revocable, + /// Tracks whether [`Devres::callback`] has been completed. + #[pin] + devm: Completion, + /// Tracks whether revoking [`Self::data`] has been completed. #[pin] revoke: Completion, } @@ -44,6 +49,10 @@ struct DevresInner { /// [`Devres`] users should make sure to simply free the corresponding bac= king resource in `T`'s /// [`Drop`] implementation. /// +/// # Invariants +/// +/// [`Self::inner`] is guaranteed to be initialized and is always accessed= read-only. +/// /// # Example /// /// ```no_run @@ -88,100 +97,108 @@ struct DevresInner { /// # fn no_run(dev: &Device) -> Result<(), Error> { /// // SAFETY: Invalid usage for example purposes. /// let iomem =3D unsafe { IoMem::<{ core::mem::size_of::() }>::new(0= xBAAAAAAD)? }; -/// let devres =3D Devres::new(dev, iomem, GFP_KERNEL)?; +/// let devres =3D KBox::pin_init(Devres::new(dev, iomem), GFP_KERNEL)?; /// /// let res =3D devres.try_access().ok_or(ENXIO)?; /// res.write8(0x42, 0x0); /// # Ok(()) /// # } /// ``` -pub struct Devres(Arc>); +#[pin_data(PinnedDrop)] +pub struct Devres { + dev: ARef, + /// Pointer to [`Self::devres_callback`]. + /// + /// Has to be stored, since Rust does not guarantee to always return t= he same address for a + /// function. However, the C API uses the address as a key. + callback: unsafe extern "C" fn(*mut c_void), + /// Contains all the fields shared with [`Self::callback`]. + // TODO: Replace with `UnsafePinned`, once available. + #[pin] + inner: Opaque>, +} + +impl Devres { + /// Creates a new [`Devres`] instance of the given `data`. + /// + /// The `data` encapsulated within the returned `Devres` instance' `da= ta` will be + /// (revoked)[`Revocable`] once the device is detached. + pub fn new<'a, E>( + dev: &'a Device, + data: impl PinInit + 'a, + ) -> impl PinInit + 'a + where + T: 'a, + Error: From, + { + let callback =3D Self::devres_callback; =20 -impl DevresInner { - fn new(dev: &Device, data: T, flags: Flags) -> Result>> { - let inner =3D Arc::pin_init( - try_pin_init!( DevresInner { - dev: dev.into(), - callback: Self::devres_callback, + try_pin_init!(&this in Self { + // INVARIANT: `inner` is properly initialized. + inner <- Opaque::pin_init(try_pin_init!(Inner { data <- Revocable::new(data), + devm <- Completion::new(), revoke <- Completion::new(), - }), - flags, - )?; - - // Convert `Arc` into a raw pointer and make devres o= wn this reference until - // `Self::devres_callback` is called. - let data =3D inner.clone().into_raw(); + })), + callback, + dev: { + // SAFETY: `this` is a valid pointer to uninitialized memo= ry. + let inner =3D unsafe { &raw mut (*this.as_ptr()).inner }; =20 - // SAFETY: `devm_add_action` guarantees to call `Self::devres_call= back` once `dev` is - // detached. - let ret =3D - unsafe { bindings::devm_add_action(dev.as_raw(), Some(inner.ca= llback), data as _) }; - - if ret !=3D 0 { - // SAFETY: We just created another reference to `inner` in ord= er to pass it to - // `bindings::devm_add_action`. If `bindings::devm_add_action`= fails, we have to drop - // this reference accordingly. - let _ =3D unsafe { Arc::from_raw(data) }; - return Err(Error::from_errno(ret)); - } + // SAFETY: + // - `dev.as_raw()` is a pointer to a valid bound device. + // - `inner` is guaranteed to be a valid for the duration = of the lifetime of `Self`. + // - `devm_add_action()` is guaranteed not to call `callba= ck` until `this` has been + // properly initialized, because we require `dev` (i.e.= the *bound* device) to + // live at least as long as the returned `impl PinInit<= Self, Error>`. + to_result(unsafe { + bindings::devm_add_action(dev.as_raw(), Some(callback)= , inner.cast()) + })?; =20 - Ok(inner) + dev.into() + }, + }) } =20 - fn as_ptr(&self) -> *const Self { - self as _ + fn inner(&self) -> &Inner { + // SAFETY: By the type invairants of `Self`, `inner` is properly i= nitialized and always + // accessed read-only. + unsafe { &*self.inner.get() } } =20 - fn remove_action(this: &Arc) -> bool { - // SAFETY: - // - `self.inner.dev` is a valid `Device`, - // - the `action` and `data` pointers are the exact same ones as g= iven to devm_add_action() - // previously, - // - `self` is always valid, even if the action has been released = already. - let success =3D unsafe { - bindings::devm_remove_action_nowarn( - this.dev.as_raw(), - Some(this.callback), - this.as_ptr() as _, - ) - } =3D=3D 0; - - if success { - // SAFETY: We leaked an `Arc` reference to devm_add_action() i= n `DevresInner::new`; if - // devm_remove_action_nowarn() was successful we can (and have= to) claim back ownership - // of this reference. - let _ =3D unsafe { Arc::from_raw(this.as_ptr()) }; - } - - success + fn data(&self) -> &Revocable { + &self.inner().data } =20 #[allow(clippy::missing_safety_doc)] unsafe extern "C" fn devres_callback(ptr: *mut kernel::ffi::c_void) { - let ptr =3D ptr as *mut DevresInner; - // Devres owned this memory; now that we received the callback, dr= op the `Arc` and hence the - // reference. - // SAFETY: Safe, since we leaked an `Arc` reference to devm_add_ac= tion() in - // `DevresInner::new`. - let inner =3D unsafe { Arc::from_raw(ptr) }; + // SAFETY: In `Self::new` we've passed a valid pointer to `Inner` = to `devm_add_action()`, + // hence `ptr` must be a valid pointer to `Inner`. + let inner =3D unsafe { &*ptr.cast::>() }; + + // Ensure that `inner` can't be used anymore after we signal compl= etion of this callback. + let inner =3D ScopeGuard::new_with_data(inner, |inner| inner.devm.= complete_all()); =20 if !inner.data.revoke() { // If `revoke()` returns false, it means that `Devres::drop` a= lready started revoking - // `inner.data` for us. Hence we have to wait until `Devres::d= rop()` signals that it - // completed revoking `inner.data`. + // `data` for us. Hence we have to wait until `Devres::drop` s= ignals that it + // completed revoking `data`. inner.revoke.wait_for_completion(); } } -} =20 -impl Devres { - /// Creates a new [`Devres`] instance of the given `data`. The `data` = encapsulated within the - /// returned `Devres` instance' `data` will be revoked once the device= is detached. - pub fn new(dev: &Device, data: T, flags: Flags) -> Result= { - let inner =3D DevresInner::new(dev, data, flags)?; - - Ok(Devres(inner)) + fn remove_action(&self) -> bool { + // SAFETY: + // - `self.dev` is a valid `Device`, + // - the `action` and `data` pointers are the exact same ones as g= iven to + // `devm_add_action()` previously, + (unsafe { + bindings::devm_remove_action_nowarn( + self.dev.as_raw(), + Some(self.callback), + core::ptr::from_ref(self.inner()).cast_mut().cast(), + ) + } =3D=3D 0) } =20 /// Obtain `&'a T`, bypassing the [`Revocable`]. @@ -213,44 +230,63 @@ pub fn new(dev: &Device, data: T, flags: Flags= ) -> Result { /// } /// ``` pub fn access<'a>(&'a self, dev: &'a Device) -> Result<&'a T> { - if self.0.dev.as_raw() !=3D dev.as_raw() { + if self.dev.as_raw() !=3D dev.as_raw() { return Err(EINVAL); } =20 // SAFETY: `dev` being the same device as the device this `Devres`= has been created for - // proves that `self.0.data` hasn't been revoked and is guaranteed= to not be revoked as - // long as `dev` lives; `dev` lives at least as long as `self`. - Ok(unsafe { self.0.data.access() }) + // proves that `self.data` hasn't been revoked and is guaranteed t= o not be revoked as long + // as `dev` lives; `dev` lives at least as long as `self`. + Ok(unsafe { self.data().access() }) } =20 /// [`Devres`] accessor for [`Revocable::try_access`]. pub fn try_access(&self) -> Option> { - self.0.data.try_access() + self.data().try_access() } =20 /// [`Devres`] accessor for [`Revocable::try_access_with`]. pub fn try_access_with R>(&self, f: F) -> Option { - self.0.data.try_access_with(f) + self.data().try_access_with(f) } =20 /// [`Devres`] accessor for [`Revocable::try_access_with_guard`]. pub fn try_access_with_guard<'a>(&'a self, guard: &'a rcu::Guard) -> O= ption<&'a T> { - self.0.data.try_access_with_guard(guard) + self.data().try_access_with_guard(guard) } } =20 -impl Drop for Devres { - fn drop(&mut self) { +// SAFETY: `Devres` can be send to any task, if `T: Send`. +unsafe impl Send for Devres {} + +// SAFETY: `Devres` can be shared with any task, if `T: Sync`. +unsafe impl Sync for Devres {} + +#[pinned_drop] +impl PinnedDrop for Devres { + fn drop(self: Pin<&mut Self>) { // SAFETY: When `drop` runs, it is guaranteed that nobody is acces= sing the revocable data // anymore, hence it is safe not to wait for the grace period to f= inish. - if unsafe { self.0.data.revoke_nosync() } { - // We revoked `self.0.data` before the devres action did, henc= e try to remove it. - if !DevresInner::remove_action(&self.0) { + if unsafe { self.data().revoke_nosync() } { + // We revoked `self.data` before the devres action did, hence = try to remove it. + if !self.remove_action() { // We could not remove the devres action, which means that= it now runs concurrently, - // hence signal that `self.0.data` has been revoked succes= sfully. - self.0.revoke.complete_all(); + // hence signal that `self.data` has been revoked by us su= ccessfully. + self.inner().revoke.complete_all(); + + // Wait for `Self::devres_callback` to be done using this = object. + self.inner().devm.wait_for_completion(); } + } else { + // `Self::devres_callback` revokes `self.data` for us, hence w= ait for it to be done + // using this object. + self.inner().devm.wait_for_completion(); } + + // INVARIANT: At this point it is guaranteed that `inner` can't be= accessed any more. + // + // SAFETY: `inner` is valid for dropping. + unsafe { core::ptr::drop_in_place(self.inner.get()) }; } } =20 diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 8435f8132e38..db0eb7eaf9b1 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -5,7 +5,6 @@ //! C header: [`include/linux/pci.h`](srctree/include/linux/pci.h) =20 use crate::{ - alloc::flags::*, bindings, container_of, device, device_id::RawDeviceId, devres::Devres, @@ -398,19 +397,20 @@ pub fn resource_len(&self, bar: u32) -> Result { impl Device { /// Mapps an entire PCI-BAR after performing a region-request on it. I= /O operation bound checks /// can be performed on compile time for offsets (plus the requested t= ype size) < SIZE. - pub fn iomap_region_sized( - &self, + pub fn iomap_region_sized<'a, const SIZE: usize>( + &'a self, bar: u32, - name: &CStr, - ) -> Result>> { - let bar =3D Bar::::new(self, bar, name)?; - let devres =3D Devres::new(self.as_ref(), bar, GFP_KERNEL)?; - - Ok(devres) + name: &'a CStr, + ) -> impl PinInit>, Error> + 'a { + Devres::new(self.as_ref(), Bar::::new(self, bar, name)) } =20 /// Mapps an entire PCI-BAR after performing a region-request on it. - pub fn iomap_region(&self, bar: u32, name: &CStr) -> Result> { + pub fn iomap_region<'a>( + &'a self, + bar: u32, + name: &'a CStr, + ) -> impl PinInit, Error> + 'a { self.iomap_region_sized::<0>(bar, name) } } diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci= .rs index 15147e4401b2..5c35f1414172 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -25,8 +25,10 @@ impl TestIndex { const NO_EVENTFD: Self =3D Self(0); } =20 +#[pin_data(PinnedDrop)] struct SampleDriver { pdev: ARef, + #[pin] bar: Devres, } =20 @@ -73,13 +75,11 @@ fn probe(pdev: &pci::Device, info: &Self::IdInfo)= -> Result pdev.enable_device_mem()?; pdev.set_master(); =20 - let bar =3D pdev.iomap_region_sized::<{ Regs::END }>(0, c_str!("ru= st_driver_pci"))?; - - let drvdata =3D KBox::new( - Self { + let drvdata =3D KBox::pin_init( + try_pin_init!(Self { pdev: pdev.into(), - bar, - }, + bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c_str!(= "rust_driver_pci")), + }), GFP_KERNEL, )?; =20 @@ -90,12 +90,13 @@ fn probe(pdev: &pci::Device, info: &Self::IdInfo)= -> Result Self::testdev(info, bar)? ); =20 - Ok(drvdata.into()) + Ok(drvdata) } } =20 -impl Drop for SampleDriver { - fn drop(&mut self) { +#[pinned_drop] +impl PinnedDrop for SampleDriver { + fn drop(self: Pin<&mut Self>) { dev_dbg!(self.pdev.as_ref(), "Remove Rust PCI driver sample.\n"); } } --=20 2.49.0 From nobody Wed Oct 8 20:01:09 2025 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 E433E25B30D; Tue, 24 Jun 2025 21:56:25 +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=1750802186; cv=none; b=bEb55GM+Um+jDTP2vBBQv1SVpD9V9xsy+gbd1Kkx9XuERCOk8fZzWdxVKwxD1iEsH1/haZHmQ4WrEQTX1dZoLUkoOAwSZDxfRb1n1UP7CZ6CeI+BGiEDEqmJ6MRR0ek9Ow/QXzASKHaSHLiBYEsPwHMOEmITCMon/2WYApZIvts= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750802186; c=relaxed/simple; bh=YW/ds6pprKKQoy3LKVLjqadOwFxWlgr4J1KG6YTdzrQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Fv2iNRZce9ZI7HyCXkCP9BLVdw/TF+t+PD9G1jZUGll9gU0kPDvGr5Qa9kY61TTxiVQjVvqFe99xgOXyZ0o1YwFm6Umy65miM05kKDDYVUXuwLcqJWjKLBbKyo8ihm8un8OQIzadEu2TmQY953dhUI55HHHN6DqFkAm791MzRTs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=UKcdUGuS; 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="UKcdUGuS" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 91EEEC4CEE3; Tue, 24 Jun 2025 21:56:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1750802185; bh=YW/ds6pprKKQoy3LKVLjqadOwFxWlgr4J1KG6YTdzrQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UKcdUGuSMF5k9b0dDwYIXsT6x4C30Gad32hUYQh9PartgGZtbWG6Id2KBkxQxieDB ZYW5xVr51JWs3xs1bnkik5K56+IGVmDXOluAUbJNg32XoLUbufCVojGxe2eCZQV0ZB VX2HAfRPSfKmCVqtga4JrcZ4dTArSnRdQTDNBGQoFTGNI9p6/+g8AceQ0wF6DopVyQ QZzxBiYXWWU+pkUVOp5dcnUXJ+NTd4iAKElMSoWjZSaPjILJ+MIFA3ZwmSvOfxFm00 9dCuCAQzDA3Ec4VSeBuKVhuPDm+3OsShpFl7GNjXVI8jVpP+2nOh3/5dadH92aTztG Nq1oUy+gKlWOA== From: Danilo Krummrich To: gregkh@linuxfoundation.org, rafael@kernel.org, ojeda@kernel.org, alex.gaynor@gmail.com, boqun.feng@gmail.com, gary@garyguo.net, bjorn3_gh@protonmail.com, lossin@kernel.org, a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu, david.m.ertman@intel.com, ira.weiny@intel.com, leon@kernel.org, kwilczynski@kernel.org, bhelgaas@google.com Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org, Danilo Krummrich Subject: [PATCH v3 4/4] rust: devres: implement register_release() Date: Tue, 24 Jun 2025 23:54:02 +0200 Message-ID: <20250624215600.221167-5-dakr@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250624215600.221167-1-dakr@kernel.org> References: <20250624215600.221167-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" register_release() is useful when a device resource has associated data, but does not require the capability of accessing it or manually releasing it. If we would want to be able to access the device resource and release the device resource manually before the device is unbound, but still keep access to the associated data, we could implement it as follows. struct Registration { inner: Devres, data: T, } However, if we never need to access the resource or release it manually, register_release() is great optimization for the above, since it does not require the synchronization of the Devres type. Suggested-by: Alice Ryhl Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 90 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index eeffdc8115aa..ff33e835c44f 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -16,6 +16,7 @@ sync::{rcu, Completion}, types::{ARef, ForeignOwnable, ScopeGuard, Opaque}, }; +use core::ops::Deref; =20 use pin_init::Wrapper; =20 @@ -347,3 +348,92 @@ pub fn register(dev: &Device, data: impl = PinInit, flags: Flag =20 register_foreign(dev, data) } + +/// [`Devres`]-releaseable resource. +/// +/// Register an object implementing this trait with [`register_release`]. = Its `release` +/// function will be called once the device is being unbound. +pub trait Release { + /// Called once the [`Device`] given to [`register_release`] is unboun= d. + fn release(&self); +} + +impl Release for crate::sync::ArcBorrow<'_, T> { + fn release(&self) { + self.deref().release(); + } +} + +impl Release for Pin<&'_ T> { + fn release(&self) { + self.deref().release(); + } +} + +impl Release for &'_ T { + fn release(&self) { + (*self).release(); + } +} + +/// Consume the `data`, [`Release::release`] and [`Drop::drop`] `data` onc= e `dev` is unbound. +/// +/// # Examples +/// +/// ```no_run +/// use kernel::{device::{Bound, Device}, devres, devres::Release, sync::A= rc}; +/// +/// /// Registration of e.g. a class device, IRQ, etc. +/// struct Registration { +/// data: T, +/// } +/// +/// impl Registration { +/// fn new(data: T) -> Result> { +/// // register +/// +/// Ok(Arc::new(Self { data }, GFP_KERNEL)?) +/// } +/// } +/// +/// impl Release for Registration { +/// fn release(&self) { +/// // unregister +/// } +/// } +/// +/// fn from_bound_context(dev: &Device) -> Result { +/// let reg =3D Registration::new(0x42)?; +/// +/// devres::register_release(dev, reg.clone()) +/// } +/// ``` +pub fn register_release

(dev: &Device, data: P) -> Result +where + P: ForeignOwnable + 'static, + for<'a> P::Borrowed<'a>: Release, +{ + let ptr =3D data.into_foreign(); + + #[allow(clippy::missing_safety_doc)] + unsafe extern "C" fn callback

(ptr: *mut kernel::ffi::c_void) + where + P: ForeignOwnable, + for<'a> P::Borrowed<'a>: Release, + { + // SAFETY: `ptr` is the pointer to the `ForeignOwnable` leaked abo= ve and hence valid. + unsafe { P::borrow(ptr.cast()) }.release(); + + // SAFETY: `ptr` is the pointer to the `ForeignOwnable` leaked abo= ve and hence valid. + drop(unsafe { P::from_foreign(ptr.cast()) }); + } + + // SAFETY: + // - `dev.as_raw()` is a pointer to a valid and bound device. + // - `ptr` is a valid pointer the `ForeignOwnable` devres takes owners= hip of. + to_result(unsafe { + // `devm_add_action_or_reset()` also calls `callback` on failure, = such that the + // `ForeignOwnable` is released eventually. + bindings::devm_add_action_or_reset(dev.as_raw(), Some(callback::), ptr.cast()) + }) +} --=20 2.49.0