Message ID | 20230307-rust-drm-v1-3-917ff5bc80a8@asahilina.net |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:5915:0:0:0:0:0 with SMTP id v21csp2464010wrd; Tue, 7 Mar 2023 06:34:07 -0800 (PST) X-Google-Smtp-Source: AK7set/5vni317+DBddOsLjROKTsHd+kkxjeVsYEe+NNgRD+G90C5z1cW+qcI5SmJnfxoZpaRiOq X-Received: by 2002:a17:906:60c3:b0:8a9:f870:d25b with SMTP id f3-20020a17090660c300b008a9f870d25bmr12317314ejk.15.1678199647554; Tue, 07 Mar 2023 06:34:07 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1678199647; cv=none; d=google.com; s=arc-20160816; b=L2DIEcm8lZrnNLpZ/FKQEUWJO7F07PXHgtLABrp41/pHGXEuEX3wJkBt+MnFb2V20g i0nt69Jbee3AqNXrLjmPHhFIb/Tmf/kFIbIjmTkLauedJKwXEWQ91g0oojkGu1s9yg54 A2HYXh37ntfWiGgcsmv9Mqlu1ekzDrSvuWRwjk2URygt69vDqY1mtGVuMyAOuJFFTxoi uTnICW/66sDBbAwBhZX9gLdIo9ZGu/K9XSqTg13xYqeXTg4j3E2Q5jVMSABF1AulQ7zR +F70tXAZc5PjiOmf0eJlRirv+cjh4JenvzVt1n9veOwy3U+niteHNn1NBelkhisiOHts Nd1w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:in-reply-to:references:message-id :content-transfer-encoding:mime-version:subject:date:from :dkim-signature; bh=CEw+Sztl0zdEzhfLYH1vt5wINWt/pPO2pi0SYIF0wAc=; b=E0cMvn4e2oIlb2k3har/E3bFLgtldachTr5p90kLS3wrah/E+tRwnpEBzTj3X3M1u/ y2f7bL2m1DfLK+/vN8icosxRsScqDx4+f5PAf9dKdVjB1vXm6Z2PxImhsqyexgprdewD 4aHFQJozJzZHpe63Q/GIRstQVT58b1Bi8iCnW3UVhitXxJb9HeYYvFUiA15oX9nv29mU aJfRZuwjHgrBL3c5b9uT5gJ1lXNDWndt4fINA6NCtzgnEFRD7ulKhmhvXzCVnW3VU/+X e8Pzzw44sYE9qoE3z8/Zhgl2OeOU7IP+OI1HapPgxC1aQo8jXWr832m5HdBofr/Anj7w uTSQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@asahilina.net header.s=default header.b="xAGB4P/o"; 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=asahilina.net Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id u4-20020a170906950400b008e10c06e907si582688ejx.450.2023.03.07.06.33.43; Tue, 07 Mar 2023 06:34:07 -0800 (PST) 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=@asahilina.net header.s=default header.b="xAGB4P/o"; 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=asahilina.net Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229876AbjCGOcG (ORCPT <rfc822;toshivichauhan@gmail.com> + 99 others); Tue, 7 Mar 2023 09:32:06 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60788 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230164AbjCGObV (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Tue, 7 Mar 2023 09:31:21 -0500 Received: from mail.marcansoft.com (marcansoft.com [212.63.210.85]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1116D8C815; Tue, 7 Mar 2023 06:27:04 -0800 (PST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: linasend@asahilina.net) by mail.marcansoft.com (Postfix) with ESMTPSA id A3475424AC; Tue, 7 Mar 2023 14:26:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=asahilina.net; s=default; t=1678199222; bh=QBiytwbsc0G1We1dJW1nIZFmB+ztwRRDlj9w4vMV0Ro=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=xAGB4P/oo/JGUzu7/9TffIgLeqScyHn1BdOEXg7j24rlLZwfpKL/pW783VTJLoZDd UUUwvllNfz+n8CEH7/VkCJfz2Kt3HCxhYc/EJZjVmLNa1vGNSB69UYBsBpQovNlcoh 47pv2L5TfESCngho/MbRa1R/Qyn3RvwWrlZmhNgbqrCevLiIoGZkBStabFgTUDOae6 EORSq8TCMuYNpvigWU0w1OENwm1hxxYY3R9b2vemSUiKCBpX1ZjdRzQRuNu1MU7U9X tjCGS3DMxQykbNKsmAIuPbPSkWE6vsMwcLH0hy4JZPjUONAvu86zjL0knadsl9l23V 0nrfhIIc5PVpA== From: Asahi Lina <lina@asahilina.net> Date: Tue, 07 Mar 2023 23:25:28 +0900 Subject: [PATCH RFC 03/18] rust: drm: file: Add File abstraction MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20230307-rust-drm-v1-3-917ff5bc80a8@asahilina.net> References: <20230307-rust-drm-v1-0-917ff5bc80a8@asahilina.net> In-Reply-To: <20230307-rust-drm-v1-0-917ff5bc80a8@asahilina.net> To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>, Maxime Ripard <mripard@kernel.org>, Thomas Zimmermann <tzimmermann@suse.de>, David Airlie <airlied@gmail.com>, Daniel Vetter <daniel@ffwll.ch>, Miguel Ojeda <ojeda@kernel.org>, Alex Gaynor <alex.gaynor@gmail.com>, Wedson Almeida Filho <wedsonaf@gmail.com>, Boqun Feng <boqun.feng@gmail.com>, Gary Guo <gary@garyguo.net>, =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= <bjorn3_gh@protonmail.com>, Sumit Semwal <sumit.semwal@linaro.org>, =?utf-8?q?Christian_K=C3=B6nig?= <christian.koenig@amd.com>, Luben Tuikov <luben.tuikov@amd.com>, Jarkko Sakkinen <jarkko@kernel.org>, Dave Hansen <dave.hansen@linux.intel.com> Cc: Alyssa Rosenzweig <alyssa@rosenzweig.io>, Karol Herbst <kherbst@redhat.com>, Ella Stanforth <ella@iglunix.org>, Faith Ekstrand <faith.ekstrand@collabora.com>, Mary <mary@mary.zone>, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, linux-media@vger.kernel.org, linaro-mm-sig@lists.linaro.org, linux-sgx@vger.kernel.org, asahi@lists.linux.dev, Asahi Lina <lina@asahilina.net> X-Mailer: b4 0.12.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1678199191; l=5871; i=lina@asahilina.net; s=20230221; h=from:subject:message-id; bh=QBiytwbsc0G1We1dJW1nIZFmB+ztwRRDlj9w4vMV0Ro=; b=NkHywoaUixWj5shg1jl7LNEBfnrmuiLisYFWU1LZZmq0/+I3LVSV8lK+ExlBi3b/t/O2ZUFyH rzDEcCifeKOBMCzcXcZoiQK3N2xZUcpC/i3ZFKNDrBE1tR6HrJ806z4 X-Developer-Key: i=lina@asahilina.net; a=ed25519; pk=Qn8jZuOtR1m5GaiDfTrAoQ4NE1XoYVZ/wmt5YtXWFC4= X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_NONE,SPF_PASS, URIBL_BLOCKED autolearn=ham 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: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1759719873807134979?= X-GMAIL-MSGID: =?utf-8?q?1759719873807134979?= |
Series |
Rust DRM subsystem abstractions (& preview AGX driver)
|
|
Commit Message
Asahi Lina
March 7, 2023, 2:25 p.m. UTC
A DRM File is the DRM counterpart to a kernel file structure,
representing an open DRM file descriptor. Add a Rust abstraction to
allow drivers to implement their own File types that implement the
DriverFile trait.
Signed-off-by: Asahi Lina <lina@asahilina.net>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/drm/drv.rs | 7 ++-
rust/kernel/drm/file.rs | 113 ++++++++++++++++++++++++++++++++++++++++
rust/kernel/drm/mod.rs | 1 +
4 files changed, 120 insertions(+), 2 deletions(-)
Comments
On Tue, 2023-03-07 at 23:25 +0900, Asahi Lina wrote: > A DRM File is the DRM counterpart to a kernel file structure, > representing an open DRM file descriptor. Add a Rust abstraction to > allow drivers to implement their own File types that implement the > DriverFile trait. > > Signed-off-by: Asahi Lina <lina@asahilina.net> > --- > rust/bindings/bindings_helper.h | 1 + > rust/kernel/drm/drv.rs | 7 ++- > rust/kernel/drm/file.rs | 113 > ++++++++++++++++++++++++++++++++++++++++ > rust/kernel/drm/mod.rs | 1 + > 4 files changed, 120 insertions(+), 2 deletions(-) > > diff --git a/rust/bindings/bindings_helper.h > b/rust/bindings/bindings_helper.h > index 2a999138c4ae..7d7828faf89c 100644 > --- a/rust/bindings/bindings_helper.h > +++ b/rust/bindings/bindings_helper.h > @@ -8,6 +8,7 @@ > > #include <drm/drm_device.h> > #include <drm/drm_drv.h> > +#include <drm/drm_file.h> > #include <drm/drm_ioctl.h> > #include <linux/delay.h> > #include <linux/device.h> > diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs > index 29a465515dc9..1dcb651e1417 100644 > --- a/rust/kernel/drm/drv.rs > +++ b/rust/kernel/drm/drv.rs > @@ -144,6 +144,9 @@ pub trait Driver { > /// Should be either `drm::gem::Object<T>` or > `drm::gem::shmem::Object<T>`. > type Object: AllocImpl; > > + /// The type used to represent a DRM File (client) > + type File: drm::file::DriverFile; > + > /// Driver metadata > const INFO: DriverInfo; > > @@ -213,8 +216,8 @@ macro_rules! drm_device_register { > impl<T: Driver> Registration<T> { > const VTABLE: bindings::drm_driver = drm_legacy_fields! { > load: None, > - open: None, // TODO: File abstraction > - postclose: None, // TODO: File abstraction > + open: Some(drm::file::open_callback::<T::File>), > + postclose: Some(drm::file::postclose_callback::<T::File>), > lastclose: None, > unload: None, > release: None, > diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs > new file mode 100644 > index 000000000000..48751e93c38a > --- /dev/null > +++ b/rust/kernel/drm/file.rs > @@ -0,0 +1,113 @@ > +// SPDX-License-Identifier: GPL-2.0 OR MIT > + > +//! DRM File objects. > +//! > +//! C header: > [`include/linux/drm/drm_file.h`](../../../../include/linux/drm/drm_fi > le.h) > + > +use crate::{bindings, drm, error::Result}; > +use alloc::boxed::Box; > +use core::marker::PhantomData; > +use core::ops::Deref; > + > +/// Trait that must be implemented by DRM drivers to represent a DRM > File (a client instance). > +pub trait DriverFile { > + /// The parent `Driver` implementation for this `DriverFile`. > + type Driver: drm::drv::Driver; > + > + /// Open a new file (called when a client opens the DRM device). > + fn open(device: &drm::device::Device<Self::Driver>) -> > Result<Box<Self>>; > +} > + > +/// An open DRM File. > +/// > +/// # Invariants > +/// `raw` is a valid pointer to a `drm_file` struct. > +#[repr(transparent)] > +pub struct File<T: DriverFile> { > + raw: *mut bindings::drm_file, > + _p: PhantomData<T>, > +} > + > +pub(super) unsafe extern "C" fn open_callback<T: DriverFile>( > + raw_dev: *mut bindings::drm_device, > + raw_file: *mut bindings::drm_file, > +) -> core::ffi::c_int { > + let drm = core::mem::ManuallyDrop::new(unsafe { > drm::device::Device::from_raw(raw_dev) }); Maybe you can help educate me a bit here... This feels like a really sketchy pattern. We're creating a Device from a pointer, an operation which inherently consumes a reference but then marking it ManuallyDrop so drm_device_put() never gets called. It took me a while but I think I figured out what you're trying to do: Make it so all the Rust stuff works with Device, not drm_device but it still feels really wrong. It works, it just feels like there's a lot of unsafe abstraction juggling happening here and I expect this operation is going to be pretty common in the Rust abstraction layer. Am I missing something? ~Faith > + // SAFETY: This reference won't escape this function > + let file = unsafe { &mut *raw_file }; > + > + let inner = match T::open(&drm) { > + Err(e) => { > + return e.to_kernel_errno(); > + } > + Ok(i) => i, > + }; > + > + file.driver_priv = Box::into_raw(inner) as *mut _; > + > + 0 > +} > + > +pub(super) unsafe extern "C" fn postclose_callback<T: DriverFile>( > + _dev: *mut bindings::drm_device, > + raw_file: *mut bindings::drm_file, > +) { > + // SAFETY: This reference won't escape this function > + let file = unsafe { &*raw_file }; > + > + // Drop the DriverFile > + unsafe { Box::from_raw(file.driver_priv as *mut T) }; > +} > + > +impl<T: DriverFile> File<T> { > + // Not intended to be called externally, except via > declare_drm_ioctls!() > + #[doc(hidden)] > + pub unsafe fn from_raw(raw_file: *mut bindings::drm_file) -> > File<T> { > + File { > + raw: raw_file, > + _p: PhantomData, > + } > + } > + > + #[allow(dead_code)] > + /// Return the raw pointer to the underlying `drm_file`. > + pub(super) fn raw(&self) -> *const bindings::drm_file { > + self.raw > + } > + > + /// Return an immutable reference to the raw `drm_file` > structure. > + pub(super) fn file(&self) -> &bindings::drm_file { > + unsafe { &*self.raw } > + } > +} > + > +impl<T: DriverFile> Deref for File<T> { > + type Target = T; > + > + fn deref(&self) -> &T { > + unsafe { &*(self.file().driver_priv as *const T) } > + } > +} > + > +impl<T: DriverFile> crate::private::Sealed for File<T> {} > + > +/// Generic trait to allow users that don't care about driver > specifics to accept any File<T>. > +/// > +/// # Safety > +/// Must only be implemented for File<T> and return the pointer, > following the normal invariants > +/// of that type. > +pub unsafe trait GenericFile: crate::private::Sealed { > + /// Returns the raw const pointer to the `struct drm_file` > + fn raw(&self) -> *const bindings::drm_file; > + /// Returns the raw mut pointer to the `struct drm_file` > + fn raw_mut(&mut self) -> *mut bindings::drm_file; > +} > + > +unsafe impl<T: DriverFile> GenericFile for File<T> { > + fn raw(&self) -> *const bindings::drm_file { > + self.raw > + } > + fn raw_mut(&mut self) -> *mut bindings::drm_file { > + self.raw > + } > +} > diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs > index 69376b3c6db9..a767942d0b52 100644 > --- a/rust/kernel/drm/mod.rs > +++ b/rust/kernel/drm/mod.rs > @@ -4,4 +4,5 @@ > > pub mod device; > pub mod drv; > +pub mod file; > pub mod ioctl; >
On 10/03/2023 06.16, Faith Ekstrand wrote: > On Tue, 2023-03-07 at 23:25 +0900, Asahi Lina wrote: >> A DRM File is the DRM counterpart to a kernel file structure, >> representing an open DRM file descriptor. Add a Rust abstraction to >> allow drivers to implement their own File types that implement the >> DriverFile trait. >> >> Signed-off-by: Asahi Lina <lina@asahilina.net> >> --- >> rust/bindings/bindings_helper.h | 1 + >> rust/kernel/drm/drv.rs | 7 ++- >> rust/kernel/drm/file.rs | 113 >> ++++++++++++++++++++++++++++++++++++++++ >> rust/kernel/drm/mod.rs | 1 + >> 4 files changed, 120 insertions(+), 2 deletions(-) >> >> diff --git a/rust/bindings/bindings_helper.h >> b/rust/bindings/bindings_helper.h >> index 2a999138c4ae..7d7828faf89c 100644 >> --- a/rust/bindings/bindings_helper.h >> +++ b/rust/bindings/bindings_helper.h >> @@ -8,6 +8,7 @@ >> >> #include <drm/drm_device.h> >> #include <drm/drm_drv.h> >> +#include <drm/drm_file.h> >> #include <drm/drm_ioctl.h> >> #include <linux/delay.h> >> #include <linux/device.h> >> diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs >> index 29a465515dc9..1dcb651e1417 100644 >> --- a/rust/kernel/drm/drv.rs >> +++ b/rust/kernel/drm/drv.rs >> @@ -144,6 +144,9 @@ pub trait Driver { >> /// Should be either `drm::gem::Object<T>` or >> `drm::gem::shmem::Object<T>`. >> type Object: AllocImpl; >> >> + /// The type used to represent a DRM File (client) >> + type File: drm::file::DriverFile; >> + >> /// Driver metadata >> const INFO: DriverInfo; >> >> @@ -213,8 +216,8 @@ macro_rules! drm_device_register { >> impl<T: Driver> Registration<T> { >> const VTABLE: bindings::drm_driver = drm_legacy_fields! { >> load: None, >> - open: None, // TODO: File abstraction >> - postclose: None, // TODO: File abstraction >> + open: Some(drm::file::open_callback::<T::File>), >> + postclose: Some(drm::file::postclose_callback::<T::File>), >> lastclose: None, >> unload: None, >> release: None, >> diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs >> new file mode 100644 >> index 000000000000..48751e93c38a >> --- /dev/null >> +++ b/rust/kernel/drm/file.rs >> @@ -0,0 +1,113 @@ >> +// SPDX-License-Identifier: GPL-2.0 OR MIT >> + >> +//! DRM File objects. >> +//! >> +//! C header: >> [`include/linux/drm/drm_file.h`](../../../../include/linux/drm/drm_fi >> le.h) >> + >> +use crate::{bindings, drm, error::Result}; >> +use alloc::boxed::Box; >> +use core::marker::PhantomData; >> +use core::ops::Deref; >> + >> +/// Trait that must be implemented by DRM drivers to represent a DRM >> File (a client instance). >> +pub trait DriverFile { >> + /// The parent `Driver` implementation for this `DriverFile`. >> + type Driver: drm::drv::Driver; >> + >> + /// Open a new file (called when a client opens the DRM device). >> + fn open(device: &drm::device::Device<Self::Driver>) -> >> Result<Box<Self>>; >> +} >> + >> +/// An open DRM File. >> +/// >> +/// # Invariants >> +/// `raw` is a valid pointer to a `drm_file` struct. >> +#[repr(transparent)] >> +pub struct File<T: DriverFile> { >> + raw: *mut bindings::drm_file, >> + _p: PhantomData<T>, >> +} >> + >> +pub(super) unsafe extern "C" fn open_callback<T: DriverFile>( >> + raw_dev: *mut bindings::drm_device, >> + raw_file: *mut bindings::drm_file, >> +) -> core::ffi::c_int { >> + let drm = core::mem::ManuallyDrop::new(unsafe { >> drm::device::Device::from_raw(raw_dev) }); > > Maybe you can help educate me a bit here... This feels like a really > sketchy pattern. We're creating a Device from a pointer, an operation > which inherently consumes a reference but then marking it ManuallyDrop > so drm_device_put() never gets called. It took me a while but I think > I figured out what you're trying to do: Make it so all the Rust stuff > works with Device, not drm_device but it still feels really wrong. It > works, it just feels like there's a lot of unsafe abstraction juggling > happening here and I expect this operation is going to be pretty common > in the Rust abstraction layer. So I think this is going to be a pretty common pattern in this kind of abstraction. The problem is that, of course, in C there is no distinction between an owned reference and a borrowed one. Here we have a borrowed reference to a struct drm_device, and we need to turn it into a &Device (which is the Rust equivalent type). But for &Device to exist we need a Device to exist in the first place, and Device normally implies ownership of the underlying drm_device. We could just acquire a reference here, but then we're needlessly grabbing a ref only to drop it at the end of the function, which is pointless when the caller is holding another reference for us while the callback runs. And of course Rust likes to claim to offer zero-cost abstractions, so it would be kind of sad to have to do that... ^^ Just doing drm::device::Device::from_raw(raw_dev) is a ticking time bomb, because we haven't acquired a reference (which would normally be required). If that Device ever gets dropped, we've messed up the refcounting and stolen the caller's reference. We could try to ensure it gets passed to core::mem::forget in all paths out, but that gets error-prone very quickly when trying to cover error paths. So instead, we put it into a ManuallyDrop. That takes care of neutering the ref drop, so we don't have to worry about messing that up. Then the only remaining safety requirement is that that the ManuallyDrop<Device> never escape the callback function, and that's easy to ensure: we only pass a &ref to the user (which via auto-deref ends up being a &Device), and then nothing bad can happen. If the user wants an owned reference to the device to keep around, they can call .clone() on it and that's when the incref happens. Basically, ManuallyDrop<T> where T is a reference counted type represents a borrowed reference to a T coming from the C side. You can see another use of this pattern in gem::Object, which contains a ManuallyDrop<Device> that represents a borrowed reference to the device that owns that object. The DRM core (as far as I know!) guarantees that DRM devices outlive all of their GEM objects, so we can materialize a borrowed reference and as long as it never leaves the GEM object, it will be sound. Then we can take &Device references from it whenever we want, and the usual Rust borrow checker rules ensure we can't do something illegal. ~~ Lina
On Fri, 2023-03-10 at 07:16 +0900, Asahi Lina wrote: > On 10/03/2023 06.16, Faith Ekstrand wrote: > > On Tue, 2023-03-07 at 23:25 +0900, Asahi Lina wrote: > > > A DRM File is the DRM counterpart to a kernel file structure, > > > representing an open DRM file descriptor. Add a Rust abstraction > > > to > > > allow drivers to implement their own File types that implement > > > the > > > DriverFile trait. > > > > > > Signed-off-by: Asahi Lina <lina@asahilina.net> > > > --- > > > rust/bindings/bindings_helper.h | 1 + > > > rust/kernel/drm/drv.rs | 7 ++- > > > rust/kernel/drm/file.rs | 113 > > > ++++++++++++++++++++++++++++++++++++++++ > > > rust/kernel/drm/mod.rs | 1 + > > > 4 files changed, 120 insertions(+), 2 deletions(-) > > > > > > diff --git a/rust/bindings/bindings_helper.h > > > b/rust/bindings/bindings_helper.h > > > index 2a999138c4ae..7d7828faf89c 100644 > > > --- a/rust/bindings/bindings_helper.h > > > +++ b/rust/bindings/bindings_helper.h > > > @@ -8,6 +8,7 @@ > > > > > > #include <drm/drm_device.h> > > > #include <drm/drm_drv.h> > > > +#include <drm/drm_file.h> > > > #include <drm/drm_ioctl.h> > > > #include <linux/delay.h> > > > #include <linux/device.h> > > > diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs > > > index 29a465515dc9..1dcb651e1417 100644 > > > --- a/rust/kernel/drm/drv.rs > > > +++ b/rust/kernel/drm/drv.rs > > > @@ -144,6 +144,9 @@ pub trait Driver { > > > /// Should be either `drm::gem::Object<T>` or > > > `drm::gem::shmem::Object<T>`. > > > type Object: AllocImpl; > > > > > > + /// The type used to represent a DRM File (client) > > > + type File: drm::file::DriverFile; > > > + > > > /// Driver metadata > > > const INFO: DriverInfo; > > > > > > @@ -213,8 +216,8 @@ macro_rules! drm_device_register { > > > impl<T: Driver> Registration<T> { > > > const VTABLE: bindings::drm_driver = drm_legacy_fields! { > > > load: None, > > > - open: None, // TODO: File abstraction > > > - postclose: None, // TODO: File abstraction > > > + open: Some(drm::file::open_callback::<T::File>), > > > + postclose: > > > Some(drm::file::postclose_callback::<T::File>), > > > lastclose: None, > > > unload: None, > > > release: None, > > > diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs > > > new file mode 100644 > > > index 000000000000..48751e93c38a > > > --- /dev/null > > > +++ b/rust/kernel/drm/file.rs > > > @@ -0,0 +1,113 @@ > > > +// SPDX-License-Identifier: GPL-2.0 OR MIT > > > + > > > +//! DRM File objects. > > > +//! > > > +//! C header: > > > [`include/linux/drm/drm_file.h`](../../../../include/linux/drm/dr > > > m_fi > > > le.h) > > > + > > > +use crate::{bindings, drm, error::Result}; > > > +use alloc::boxed::Box; > > > +use core::marker::PhantomData; > > > +use core::ops::Deref; > > > + > > > +/// Trait that must be implemented by DRM drivers to represent a > > > DRM > > > File (a client instance). > > > +pub trait DriverFile { > > > + /// The parent `Driver` implementation for this > > > `DriverFile`. > > > + type Driver: drm::drv::Driver; > > > + > > > + /// Open a new file (called when a client opens the DRM > > > device). > > > + fn open(device: &drm::device::Device<Self::Driver>) -> > > > Result<Box<Self>>; > > > +} > > > + > > > +/// An open DRM File. > > > +/// > > > +/// # Invariants > > > +/// `raw` is a valid pointer to a `drm_file` struct. > > > +#[repr(transparent)] > > > +pub struct File<T: DriverFile> { > > > + raw: *mut bindings::drm_file, > > > + _p: PhantomData<T>, > > > +} > > > + > > > +pub(super) unsafe extern "C" fn open_callback<T: DriverFile>( > > > + raw_dev: *mut bindings::drm_device, > > > + raw_file: *mut bindings::drm_file, > > > +) -> core::ffi::c_int { > > > + let drm = core::mem::ManuallyDrop::new(unsafe { > > > drm::device::Device::from_raw(raw_dev) }); > > > > Maybe you can help educate me a bit here... This feels like a > > really > > sketchy pattern. We're creating a Device from a pointer, an > > operation > > which inherently consumes a reference but then marking it > > ManuallyDrop > > so drm_device_put() never gets called. It took me a while but I > > think > > I figured out what you're trying to do: Make it so all the Rust > > stuff > > works with Device, not drm_device but it still feels really wrong. > > It > > works, it just feels like there's a lot of unsafe abstraction > > juggling > > happening here and I expect this operation is going to be pretty > > common > > in the Rust abstraction layer. > > So I think this is going to be a pretty common pattern in this kind > of > abstraction. The problem is that, of course, in C there is no > distinction between an owned reference and a borrowed one. Here we > have > a borrowed reference to a struct drm_device, and we need to turn it > into > a &Device (which is the Rust equivalent type). But for &Device to > exist > we need a Device to exist in the first place, and Device normally > implies ownership of the underlying drm_device. Thanks! Putting it in terms of borrow really helps clear up the difference. > We could just acquire a reference here, but then we're needlessly > grabbing a ref only to drop it at the end of the function, which is > pointless when the caller is holding another reference for us while > the > callback runs. And of course Rust likes to claim to offer zero-cost > abstractions, so it would be kind of sad to have to do that... ^^ Yeah, I agree we don't want to take extra references. > Just doing drm::device::Device::from_raw(raw_dev) is a ticking time > bomb, because we haven't acquired a reference (which would normally > be > required). If that Device ever gets dropped, we've messed up the > refcounting and stolen the caller's reference. We could try to ensure > it > gets passed to core::mem::forget in all paths out, but that gets > error-prone very quickly when trying to cover error paths. So > instead, > we put it into a ManuallyDrop. That takes care of neutering the ref > drop, so we don't have to worry about messing that up. Then the only > remaining safety requirement is that that the ManuallyDrop<Device> > never > escape the callback function, and that's easy to ensure: we only pass > a > &ref to the user (which via auto-deref ends up being a &Device), and > then nothing bad can happen. If the user wants an owned reference to > the > device to keep around, they can call .clone() on it and that's when > the > incref happens. > > Basically, ManuallyDrop<T> where T is a reference counted type > represents a borrowed reference to a T coming from the C side. You > can > see another use of this pattern in gem::Object, which contains a > ManuallyDrop<Device> that represents a borrowed reference to the > device > that owns that object. The DRM core (as far as I know!) guarantees > that > DRM devices outlive all of their GEM objects, so we can materialize a > borrowed reference and as long as it never leaves the GEM object, it > will be sound. Then we can take &Device references from it whenever > we > want, and the usual Rust borrow checker rules ensure we can't do > something illegal. Ok, that all matches my understanding of what I thought was going on. I do wonder if it would be good to wrap this up in a struct DeviceBorrow { dev: ManuallyDrop<Device> } impl DeviceBorrow { pub unsafe fn from_raw(*mut bindings::drm_device) -> DeviceBorrow; } impl Deref<Device> for DeviceBorrow { ... } with documentation, etc. Seeing a ManuallyDrop which is never dropped sets my rust senses tingling. Maybe that's too much typing for each object? I don't want to add a bunch of extra work but this seems like a pretty common pattern we're going to hit everywhere. ~Faith
On Mon, Mar 13, 2023 at 12:49:57PM -0500, Faith Ekstrand wrote: > On Fri, 2023-03-10 at 07:16 +0900, Asahi Lina wrote: > > On 10/03/2023 06.16, Faith Ekstrand wrote: > > > On Tue, 2023-03-07 at 23:25 +0900, Asahi Lina wrote: > > > > A DRM File is the DRM counterpart to a kernel file structure, > > > > representing an open DRM file descriptor. Add a Rust abstraction > > > > to > > > > allow drivers to implement their own File types that implement > > > > the > > > > DriverFile trait. > > > > > > > > Signed-off-by: Asahi Lina <lina@asahilina.net> > > > > --- > > > > rust/bindings/bindings_helper.h | 1 + > > > > rust/kernel/drm/drv.rs | 7 ++- > > > > rust/kernel/drm/file.rs | 113 > > > > ++++++++++++++++++++++++++++++++++++++++ > > > > rust/kernel/drm/mod.rs | 1 + > > > > 4 files changed, 120 insertions(+), 2 deletions(-) > > > > > > > > diff --git a/rust/bindings/bindings_helper.h > > > > b/rust/bindings/bindings_helper.h > > > > index 2a999138c4ae..7d7828faf89c 100644 > > > > --- a/rust/bindings/bindings_helper.h > > > > +++ b/rust/bindings/bindings_helper.h > > > > @@ -8,6 +8,7 @@ > > > > > > > > #include <drm/drm_device.h> > > > > #include <drm/drm_drv.h> > > > > +#include <drm/drm_file.h> > > > > #include <drm/drm_ioctl.h> > > > > #include <linux/delay.h> > > > > #include <linux/device.h> > > > > diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs > > > > index 29a465515dc9..1dcb651e1417 100644 > > > > --- a/rust/kernel/drm/drv.rs > > > > +++ b/rust/kernel/drm/drv.rs > > > > @@ -144,6 +144,9 @@ pub trait Driver { > > > > /// Should be either `drm::gem::Object<T>` or > > > > `drm::gem::shmem::Object<T>`. > > > > type Object: AllocImpl; > > > > > > > > + /// The type used to represent a DRM File (client) > > > > + type File: drm::file::DriverFile; > > > > + > > > > /// Driver metadata > > > > const INFO: DriverInfo; > > > > > > > > @@ -213,8 +216,8 @@ macro_rules! drm_device_register { > > > > impl<T: Driver> Registration<T> { > > > > const VTABLE: bindings::drm_driver = drm_legacy_fields! { > > > > load: None, > > > > - open: None, // TODO: File abstraction > > > > - postclose: None, // TODO: File abstraction > > > > + open: Some(drm::file::open_callback::<T::File>), > > > > + postclose: > > > > Some(drm::file::postclose_callback::<T::File>), > > > > lastclose: None, > > > > unload: None, > > > > release: None, > > > > diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs > > > > new file mode 100644 > > > > index 000000000000..48751e93c38a > > > > --- /dev/null > > > > +++ b/rust/kernel/drm/file.rs > > > > @@ -0,0 +1,113 @@ > > > > +// SPDX-License-Identifier: GPL-2.0 OR MIT > > > > + > > > > +//! DRM File objects. > > > > +//! > > > > +//! C header: > > > > [`include/linux/drm/drm_file.h`](../../../../include/linux/drm/dr > > > > m_fi > > > > le.h) > > > > + > > > > +use crate::{bindings, drm, error::Result}; > > > > +use alloc::boxed::Box; > > > > +use core::marker::PhantomData; > > > > +use core::ops::Deref; > > > > + > > > > +/// Trait that must be implemented by DRM drivers to represent a > > > > DRM > > > > File (a client instance). > > > > +pub trait DriverFile { > > > > + /// The parent `Driver` implementation for this > > > > `DriverFile`. > > > > + type Driver: drm::drv::Driver; > > > > + > > > > + /// Open a new file (called when a client opens the DRM > > > > device). > > > > + fn open(device: &drm::device::Device<Self::Driver>) -> > > > > Result<Box<Self>>; > > > > +} > > > > + > > > > +/// An open DRM File. > > > > +/// > > > > +/// # Invariants > > > > +/// `raw` is a valid pointer to a `drm_file` struct. > > > > +#[repr(transparent)] > > > > +pub struct File<T: DriverFile> { > > > > + raw: *mut bindings::drm_file, > > > > + _p: PhantomData<T>, > > > > +} > > > > + > > > > +pub(super) unsafe extern "C" fn open_callback<T: DriverFile>( > > > > + raw_dev: *mut bindings::drm_device, > > > > + raw_file: *mut bindings::drm_file, > > > > +) -> core::ffi::c_int { > > > > + let drm = core::mem::ManuallyDrop::new(unsafe { > > > > drm::device::Device::from_raw(raw_dev) }); > > > > > > Maybe you can help educate me a bit here... This feels like a > > > really > > > sketchy pattern. We're creating a Device from a pointer, an > > > operation > > > which inherently consumes a reference but then marking it > > > ManuallyDrop > > > so drm_device_put() never gets called. It took me a while but I > > > think > > > I figured out what you're trying to do: Make it so all the Rust > > > stuff > > > works with Device, not drm_device but it still feels really wrong. > > > It > > > works, it just feels like there's a lot of unsafe abstraction > > > juggling > > > happening here and I expect this operation is going to be pretty > > > common > > > in the Rust abstraction layer. > > > > So I think this is going to be a pretty common pattern in this kind > > of > > abstraction. The problem is that, of course, in C there is no > > distinction between an owned reference and a borrowed one. Here we > > have > > a borrowed reference to a struct drm_device, and we need to turn it > > into > > a &Device (which is the Rust equivalent type). But for &Device to > > exist > > we need a Device to exist in the first place, and Device normally > > implies ownership of the underlying drm_device. > > Thanks! Putting it in terms of borrow really helps clear up the > difference. > > > We could just acquire a reference here, but then we're needlessly > > grabbing a ref only to drop it at the end of the function, which is > > pointless when the caller is holding another reference for us while > > the > > callback runs. And of course Rust likes to claim to offer zero-cost > > abstractions, so it would be kind of sad to have to do that... ^^ > > Yeah, I agree we don't want to take extra references. > > > Just doing drm::device::Device::from_raw(raw_dev) is a ticking time > > bomb, because we haven't acquired a reference (which would normally > > be > > required). If that Device ever gets dropped, we've messed up the > > refcounting and stolen the caller's reference. We could try to ensure > > it > > gets passed to core::mem::forget in all paths out, but that gets > > error-prone very quickly when trying to cover error paths. So > > instead, > > we put it into a ManuallyDrop. That takes care of neutering the ref > > drop, so we don't have to worry about messing that up. Then the only > > remaining safety requirement is that that the ManuallyDrop<Device> > > never > > escape the callback function, and that's easy to ensure: we only pass > > a > > &ref to the user (which via auto-deref ends up being a &Device), and > > then nothing bad can happen. If the user wants an owned reference to > > the > > device to keep around, they can call .clone() on it and that's when > > the > > incref happens. > > > > Basically, ManuallyDrop<T> where T is a reference counted type > > represents a borrowed reference to a T coming from the C side. You > > can > > see another use of this pattern in gem::Object, which contains a > > ManuallyDrop<Device> that represents a borrowed reference to the > > device > > that owns that object. The DRM core (as far as I know!) guarantees > > that > > DRM devices outlive all of their GEM objects, so we can materialize a > > borrowed reference and as long as it never leaves the GEM object, it > > will be sound. Then we can take &Device references from it whenever > > we > > want, and the usual Rust borrow checker rules ensure we can't do > > something illegal. > > Ok, that all matches my understanding of what I thought was going on. I > do wonder if it would be good to wrap this up in a > > struct DeviceBorrow { > dev: ManuallyDrop<Device> > } > > impl DeviceBorrow { > pub unsafe fn from_raw(*mut bindings::drm_device) -> DeviceBorrow; > } > > impl Deref<Device> for DeviceBorrow { > ... > } > > with documentation, etc. Seeing a ManuallyDrop which is never dropped > sets my rust senses tingling. Maybe that's too much typing for each > object? I don't want to add a bunch of extra work but this seems like > a pretty common pattern we're going to hit everywhere. > I just want to mention, there is a different way to do the abstraction here: similar to https://lore.kernel.org/rust-for-linux/ZA9l0EHCRRr%2Fmyoq@boqun-archlinux * Define Device as tranparent represention of struct drm_device: #[repr(transparent)] struct Device(Opaque<bindings::drm_device>); * impl `AlwaysRefCounted`[1] for `Device`, therefore we can use `ARef<Device>`[2] as a smart pointer to `drm_device`. * drm_device related methods are still implemented in `impl Device` * In `open_callback`, we can just get a `&Device` from `*mut bindings::drm_device` unsafely, and that's all. Or introduce a helper function if we want: pub unsafe fn with_device<F>(ptr: *mut drm_device, f: F) -> Result where F: FnOnce(&Device) -> Result { let d = unsafe { &*ptr }; f(d) } The main difference is that we now treat a pointer to drm_device as a reference to the device, not the owner. It seems we need to also change our driver/device framework to use this approach, but it looks better to me. Regards, Boqun [1]: https://rust-for-linux.github.io/docs/kernel/trait.AlwaysRefCounted.html [2]: https://rust-for-linux.github.io/docs/kernel/struct.ARef.html > ~Faith
On Mon, Mar 13, 2023 at 07:07:09PM -0700, Boqun Feng wrote: > On Mon, Mar 13, 2023 at 12:49:57PM -0500, Faith Ekstrand wrote: > > On Fri, 2023-03-10 at 07:16 +0900, Asahi Lina wrote: > > > On 10/03/2023 06.16, Faith Ekstrand wrote: > > > > On Tue, 2023-03-07 at 23:25 +0900, Asahi Lina wrote: > > > > > A DRM File is the DRM counterpart to a kernel file structure, > > > > > representing an open DRM file descriptor. Add a Rust abstraction > > > > > to > > > > > allow drivers to implement their own File types that implement > > > > > the > > > > > DriverFile trait. > > > > > > > > > > Signed-off-by: Asahi Lina <lina@asahilina.net> > > > > > --- > > > > > rust/bindings/bindings_helper.h | 1 + > > > > > rust/kernel/drm/drv.rs | 7 ++- > > > > > rust/kernel/drm/file.rs | 113 > > > > > ++++++++++++++++++++++++++++++++++++++++ > > > > > rust/kernel/drm/mod.rs | 1 + > > > > > 4 files changed, 120 insertions(+), 2 deletions(-) > > > > > > > > > > diff --git a/rust/bindings/bindings_helper.h > > > > > b/rust/bindings/bindings_helper.h > > > > > index 2a999138c4ae..7d7828faf89c 100644 > > > > > --- a/rust/bindings/bindings_helper.h > > > > > +++ b/rust/bindings/bindings_helper.h > > > > > @@ -8,6 +8,7 @@ > > > > > > > > > > #include <drm/drm_device.h> > > > > > #include <drm/drm_drv.h> > > > > > +#include <drm/drm_file.h> > > > > > #include <drm/drm_ioctl.h> > > > > > #include <linux/delay.h> > > > > > #include <linux/device.h> > > > > > diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs > > > > > index 29a465515dc9..1dcb651e1417 100644 > > > > > --- a/rust/kernel/drm/drv.rs > > > > > +++ b/rust/kernel/drm/drv.rs > > > > > @@ -144,6 +144,9 @@ pub trait Driver { > > > > > /// Should be either `drm::gem::Object<T>` or > > > > > `drm::gem::shmem::Object<T>`. > > > > > type Object: AllocImpl; > > > > > > > > > > + /// The type used to represent a DRM File (client) > > > > > + type File: drm::file::DriverFile; > > > > > + > > > > > /// Driver metadata > > > > > const INFO: DriverInfo; > > > > > > > > > > @@ -213,8 +216,8 @@ macro_rules! drm_device_register { > > > > > impl<T: Driver> Registration<T> { > > > > > const VTABLE: bindings::drm_driver = drm_legacy_fields! { > > > > > load: None, > > > > > - open: None, // TODO: File abstraction > > > > > - postclose: None, // TODO: File abstraction > > > > > + open: Some(drm::file::open_callback::<T::File>), > > > > > + postclose: > > > > > Some(drm::file::postclose_callback::<T::File>), > > > > > lastclose: None, > > > > > unload: None, > > > > > release: None, > > > > > diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs > > > > > new file mode 100644 > > > > > index 000000000000..48751e93c38a > > > > > --- /dev/null > > > > > +++ b/rust/kernel/drm/file.rs > > > > > @@ -0,0 +1,113 @@ > > > > > +// SPDX-License-Identifier: GPL-2.0 OR MIT > > > > > + > > > > > +//! DRM File objects. > > > > > +//! > > > > > +//! C header: > > > > > [`include/linux/drm/drm_file.h`](../../../../include/linux/drm/dr > > > > > m_fi > > > > > le.h) > > > > > + > > > > > +use crate::{bindings, drm, error::Result}; > > > > > +use alloc::boxed::Box; > > > > > +use core::marker::PhantomData; > > > > > +use core::ops::Deref; > > > > > + > > > > > +/// Trait that must be implemented by DRM drivers to represent a > > > > > DRM > > > > > File (a client instance). > > > > > +pub trait DriverFile { > > > > > + /// The parent `Driver` implementation for this > > > > > `DriverFile`. > > > > > + type Driver: drm::drv::Driver; > > > > > + > > > > > + /// Open a new file (called when a client opens the DRM > > > > > device). > > > > > + fn open(device: &drm::device::Device<Self::Driver>) -> > > > > > Result<Box<Self>>; > > > > > +} > > > > > + > > > > > +/// An open DRM File. > > > > > +/// > > > > > +/// # Invariants > > > > > +/// `raw` is a valid pointer to a `drm_file` struct. > > > > > +#[repr(transparent)] > > > > > +pub struct File<T: DriverFile> { > > > > > + raw: *mut bindings::drm_file, > > > > > + _p: PhantomData<T>, > > > > > +} > > > > > + > > > > > +pub(super) unsafe extern "C" fn open_callback<T: DriverFile>( > > > > > + raw_dev: *mut bindings::drm_device, > > > > > + raw_file: *mut bindings::drm_file, > > > > > +) -> core::ffi::c_int { > > > > > + let drm = core::mem::ManuallyDrop::new(unsafe { > > > > > drm::device::Device::from_raw(raw_dev) }); > > > > > > > > Maybe you can help educate me a bit here... This feels like a > > > > really > > > > sketchy pattern. We're creating a Device from a pointer, an > > > > operation > > > > which inherently consumes a reference but then marking it > > > > ManuallyDrop > > > > so drm_device_put() never gets called. It took me a while but I > > > > think > > > > I figured out what you're trying to do: Make it so all the Rust > > > > stuff > > > > works with Device, not drm_device but it still feels really wrong. > > > > It > > > > works, it just feels like there's a lot of unsafe abstraction > > > > juggling > > > > happening here and I expect this operation is going to be pretty > > > > common > > > > in the Rust abstraction layer. > > > > > > So I think this is going to be a pretty common pattern in this kind > > > of > > > abstraction. The problem is that, of course, in C there is no > > > distinction between an owned reference and a borrowed one. Here we > > > have > > > a borrowed reference to a struct drm_device, and we need to turn it > > > into > > > a &Device (which is the Rust equivalent type). But for &Device to > > > exist > > > we need a Device to exist in the first place, and Device normally > > > implies ownership of the underlying drm_device. > > > > Thanks! Putting it in terms of borrow really helps clear up the > > difference. > > > > > We could just acquire a reference here, but then we're needlessly > > > grabbing a ref only to drop it at the end of the function, which is > > > pointless when the caller is holding another reference for us while > > > the > > > callback runs. And of course Rust likes to claim to offer zero-cost > > > abstractions, so it would be kind of sad to have to do that... ^^ > > > > Yeah, I agree we don't want to take extra references. > > > > > Just doing drm::device::Device::from_raw(raw_dev) is a ticking time > > > bomb, because we haven't acquired a reference (which would normally > > > be > > > required). If that Device ever gets dropped, we've messed up the > > > refcounting and stolen the caller's reference. We could try to ensure > > > it > > > gets passed to core::mem::forget in all paths out, but that gets > > > error-prone very quickly when trying to cover error paths. So > > > instead, > > > we put it into a ManuallyDrop. That takes care of neutering the ref > > > drop, so we don't have to worry about messing that up. Then the only > > > remaining safety requirement is that that the ManuallyDrop<Device> > > > never > > > escape the callback function, and that's easy to ensure: we only pass > > > a > > > &ref to the user (which via auto-deref ends up being a &Device), and > > > then nothing bad can happen. If the user wants an owned reference to > > > the > > > device to keep around, they can call .clone() on it and that's when > > > the > > > incref happens. > > > > > > Basically, ManuallyDrop<T> where T is a reference counted type > > > represents a borrowed reference to a T coming from the C side. You > > > can > > > see another use of this pattern in gem::Object, which contains a > > > ManuallyDrop<Device> that represents a borrowed reference to the > > > device > > > that owns that object. The DRM core (as far as I know!) guarantees > > > that > > > DRM devices outlive all of their GEM objects, so we can materialize a > > > borrowed reference and as long as it never leaves the GEM object, it > > > will be sound. Then we can take &Device references from it whenever > > > we > > > want, and the usual Rust borrow checker rules ensure we can't do > > > something illegal. > > > > Ok, that all matches my understanding of what I thought was going on. I > > do wonder if it would be good to wrap this up in a > > > > struct DeviceBorrow { > > dev: ManuallyDrop<Device> > > } > > > > impl DeviceBorrow { > > pub unsafe fn from_raw(*mut bindings::drm_device) -> DeviceBorrow; > > } > > > > impl Deref<Device> for DeviceBorrow { > > ... > > } > > > > with documentation, etc. Seeing a ManuallyDrop which is never dropped > > sets my rust senses tingling. Maybe that's too much typing for each > > object? I don't want to add a bunch of extra work but this seems like > > a pretty common pattern we're going to hit everywhere. > > > > I just want to mention, there is a different way to do the abstraction > here: > > similar to https://lore.kernel.org/rust-for-linux/ZA9l0EHCRRr%2Fmyoq@boqun-archlinux > > * Define Device as tranparent represention of struct drm_device: > > #[repr(transparent)] > struct Device(Opaque<bindings::drm_device>); > > * impl `AlwaysRefCounted`[1] for `Device`, therefore we can use > `ARef<Device>`[2] as a smart pointer to `drm_device`. > > * drm_device related methods are still implemented in `impl Device` > > * In `open_callback`, we can just get a `&Device` from `*mut > bindings::drm_device` unsafely, and that's all. Or introduce a helper > function if we want: > > pub unsafe fn with_device<F>(ptr: *mut drm_device, f: F) -> Result > where > F: FnOnce(&Device) -> Result > { > let d = unsafe { &*ptr }; > f(d) > } > > The main difference is that we now treat a pointer to drm_device as a > reference to the device, not the owner. > > It seems we need to also change our driver/device framework to use this > approach, but it looks better to me. So I really don't have enough rust clue to have any useful opinion on how the glue should look like, but semantically the struct drm_file should only ever be borrowed as a parameter to a driver hook, so that rust can guarantee that the driver doesn't do anything funny and uses it beyond the end of that function. This holds for all the callbacks like open/close or also all the ioctl. The other semantic thing is that that the ioctls should be able to rely on open having fully constructed the thing. I think the trait and dependent type stuff ensure that? What I've missed (but maybe just looked in the wrong place) is that the ioctl support (and really anything else where the driver gets a struct drm_file on the C side, but I don't think there is anything else) should also make sure you get the right driver-specific type and not something else. I did notice the FIXME in the first patch, I guess if it makes landing all this easier we could also keep this as a todo item to improve once things landed. That btw holds for a lot of the big "how to map semantics correctly to rust" questions I'm throwing up here. Maybe a Documentation/gpu/rust.rst file would be good to include, with these todo items noted instead of just FIXME sprinkled in patches? At least for things that will take more effort to polish. -Daniel > > Regards, > Boqun > > [1]: https://rust-for-linux.github.io/docs/kernel/trait.AlwaysRefCounted.html > [2]: https://rust-for-linux.github.io/docs/kernel/struct.ARef.html > > > ~Faith
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 2a999138c4ae..7d7828faf89c 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -8,6 +8,7 @@ #include <drm/drm_device.h> #include <drm/drm_drv.h> +#include <drm/drm_file.h> #include <drm/drm_ioctl.h> #include <linux/delay.h> #include <linux/device.h> diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs index 29a465515dc9..1dcb651e1417 100644 --- a/rust/kernel/drm/drv.rs +++ b/rust/kernel/drm/drv.rs @@ -144,6 +144,9 @@ pub trait Driver { /// Should be either `drm::gem::Object<T>` or `drm::gem::shmem::Object<T>`. type Object: AllocImpl; + /// The type used to represent a DRM File (client) + type File: drm::file::DriverFile; + /// Driver metadata const INFO: DriverInfo; @@ -213,8 +216,8 @@ macro_rules! drm_device_register { impl<T: Driver> Registration<T> { const VTABLE: bindings::drm_driver = drm_legacy_fields! { load: None, - open: None, // TODO: File abstraction - postclose: None, // TODO: File abstraction + open: Some(drm::file::open_callback::<T::File>), + postclose: Some(drm::file::postclose_callback::<T::File>), lastclose: None, unload: None, release: None, diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs new file mode 100644 index 000000000000..48751e93c38a --- /dev/null +++ b/rust/kernel/drm/file.rs @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM File objects. +//! +//! C header: [`include/linux/drm/drm_file.h`](../../../../include/linux/drm/drm_file.h) + +use crate::{bindings, drm, error::Result}; +use alloc::boxed::Box; +use core::marker::PhantomData; +use core::ops::Deref; + +/// Trait that must be implemented by DRM drivers to represent a DRM File (a client instance). +pub trait DriverFile { + /// The parent `Driver` implementation for this `DriverFile`. + type Driver: drm::drv::Driver; + + /// Open a new file (called when a client opens the DRM device). + fn open(device: &drm::device::Device<Self::Driver>) -> Result<Box<Self>>; +} + +/// An open DRM File. +/// +/// # Invariants +/// `raw` is a valid pointer to a `drm_file` struct. +#[repr(transparent)] +pub struct File<T: DriverFile> { + raw: *mut bindings::drm_file, + _p: PhantomData<T>, +} + +pub(super) unsafe extern "C" fn open_callback<T: DriverFile>( + raw_dev: *mut bindings::drm_device, + raw_file: *mut bindings::drm_file, +) -> core::ffi::c_int { + let drm = core::mem::ManuallyDrop::new(unsafe { drm::device::Device::from_raw(raw_dev) }); + // SAFETY: This reference won't escape this function + let file = unsafe { &mut *raw_file }; + + let inner = match T::open(&drm) { + Err(e) => { + return e.to_kernel_errno(); + } + Ok(i) => i, + }; + + file.driver_priv = Box::into_raw(inner) as *mut _; + + 0 +} + +pub(super) unsafe extern "C" fn postclose_callback<T: DriverFile>( + _dev: *mut bindings::drm_device, + raw_file: *mut bindings::drm_file, +) { + // SAFETY: This reference won't escape this function + let file = unsafe { &*raw_file }; + + // Drop the DriverFile + unsafe { Box::from_raw(file.driver_priv as *mut T) }; +} + +impl<T: DriverFile> File<T> { + // Not intended to be called externally, except via declare_drm_ioctls!() + #[doc(hidden)] + pub unsafe fn from_raw(raw_file: *mut bindings::drm_file) -> File<T> { + File { + raw: raw_file, + _p: PhantomData, + } + } + + #[allow(dead_code)] + /// Return the raw pointer to the underlying `drm_file`. + pub(super) fn raw(&self) -> *const bindings::drm_file { + self.raw + } + + /// Return an immutable reference to the raw `drm_file` structure. + pub(super) fn file(&self) -> &bindings::drm_file { + unsafe { &*self.raw } + } +} + +impl<T: DriverFile> Deref for File<T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*(self.file().driver_priv as *const T) } + } +} + +impl<T: DriverFile> crate::private::Sealed for File<T> {} + +/// Generic trait to allow users that don't care about driver specifics to accept any File<T>. +/// +/// # Safety +/// Must only be implemented for File<T> and return the pointer, following the normal invariants +/// of that type. +pub unsafe trait GenericFile: crate::private::Sealed { + /// Returns the raw const pointer to the `struct drm_file` + fn raw(&self) -> *const bindings::drm_file; + /// Returns the raw mut pointer to the `struct drm_file` + fn raw_mut(&mut self) -> *mut bindings::drm_file; +} + +unsafe impl<T: DriverFile> GenericFile for File<T> { + fn raw(&self) -> *const bindings::drm_file { + self.raw + } + fn raw_mut(&mut self) -> *mut bindings::drm_file { + self.raw + } +} diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index 69376b3c6db9..a767942d0b52 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -4,4 +4,5 @@ pub mod device; pub mod drv; +pub mod file; pub mod ioctl;