Received: by 2002:a05:6358:11c7:b0:104:8066:f915 with SMTP id i7csp989352rwl; Wed, 5 Apr 2023 10:15:34 -0700 (PDT) X-Google-Smtp-Source: AKy350b9IABXwBWAQpP5wEsl0z7tfRk/Y+l1JqausEHY7L61CZ0uhvDFa6xx935xfmTedBrnYIlL X-Received: by 2002:a05:6a20:4b04:b0:da:2b8f:94e2 with SMTP id fp4-20020a056a204b0400b000da2b8f94e2mr5540748pzb.57.1680714934100; Wed, 05 Apr 2023 10:15:34 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1680714934; cv=none; d=google.com; s=arc-20160816; b=crZ7oB1mIxUtKHAiaOGPDQLsm+XpICAgHzw4hx6e2KuFATEHoRz1r+x7VIRpVs4/4C YxhQySisSjQN+HLKGXax+rw8i7lZkzlRi9exQRU8zNFUopNQGgjhWOpvUVAqO+H1sLuJ YwjdpJJNyDeV2l4oEo/lCWbZc+KXYvkxMsFqSNNK4fnNznOogSlzW2jznlDJjrElYEw1 nB71fytNEeXaNZ3xom0QUnoZt7YRSGEnpsaWBNyjaoDZ9jtOFhiVfgHM2vBU8/mgxYJx f+aTaaqDEuNlOoCjLPBSCrX19IVhJJwphyAqtoL/yp6/iVg27UWqUjoFAqs0W1wSHlwq EYgg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:in-reply-to:content-disposition:mime-version :references:mail-followup-to:message-id:subject:cc:to:from:date :dkim-signature; bh=Hb0s11wzWUtncbdbj1+tJGRJTpj0TLkIHZg2ChvIYvU=; b=cHG5KtWoWteBSlpgPvLasb6iy6c13tEbv5mOwDRgy6+0lLFLFrCqUkNsF6lDIXU09c PtC9SismnCiu+BpL0sme0zncJAp1UJjn6cOmpqaB9RnwgC/UR2f+KteN/nFvxhgrGUJO 9KGasNHf7BLQP+/QLlsrXpagNTNi6K8sHwhDJ5w2+8G98Zl5mIZ8sdjaNqNYqUa1r9Fq 1F0gL1n+Q8J41TnMA3aLGsS8nTPG9bjNuCqGlFnyrLdyo9xftUTm6vP9XBpN8N5Rscuf WTYBiD0AaTGMwzR0P4oWxLtkLOR+m96m5lQH84rNMuGnKA6sDA3cHZ6RG9EiSr6YOo4K zxYA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@ffwll.ch header.s=google header.b=AIepK3u1; 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 Return-Path: Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id v186-20020a6389c3000000b0050cfba70e65si12714710pgd.318.2023.04.05.10.15.21; Wed, 05 Apr 2023 10:15:34 -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=@ffwll.ch header.s=google header.b=AIepK3u1; 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 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229492AbjDERKR (ORCPT + 99 others); Wed, 5 Apr 2023 13:10:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55272 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230100AbjDERKP (ORCPT ); Wed, 5 Apr 2023 13:10:15 -0400 Received: from mail-ej1-x635.google.com (mail-ej1-x635.google.com [IPv6:2a00:1450:4864:20::635]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 58CBEE41 for ; Wed, 5 Apr 2023 10:10:10 -0700 (PDT) Received: by mail-ej1-x635.google.com with SMTP id a640c23a62f3a-9333b408ee7so3664666b.1 for ; Wed, 05 Apr 2023 10:10:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ffwll.ch; s=google; t=1680714609; h=in-reply-to:content-disposition:mime-version:references :mail-followup-to:message-id:subject:cc:to:from:date:from:to:cc :subject:date:message-id:reply-to; bh=Hb0s11wzWUtncbdbj1+tJGRJTpj0TLkIHZg2ChvIYvU=; b=AIepK3u1s3NY52wFU0VpMZvX2NKO+NsK4/OuLn3pWaxT/kbRpMn2pPm2RDRoAs9H1Q r78d6VKmoz/o52L5nGumO00t5txi23tC0S6+HCYSVU5Qg3B+PD5siZRuIEpkSZ2BPmZ6 G2s2mC3moD8U//YHMlyb8n+RQcerj0M/2FLys= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1680714609; h=in-reply-to:content-disposition:mime-version:references :mail-followup-to:message-id:subject:cc:to:from:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=Hb0s11wzWUtncbdbj1+tJGRJTpj0TLkIHZg2ChvIYvU=; b=CpIoTZZm1pdxGgzt6q/wEbTozT2ylMqlCyLfIYSf6YvrwNUwQLq9W0/W9HBfQ92J41 Y/2JGEJz0WcToeErahWT58pvCxkvtjHMCr2v3N78Vp3SC8dyhLEm64fA4izGoofEmpfu dI3l7NxeGZWJJt3wqmzoP/6xV6QSsul3Q4KxrLSlx3NNBUze0tysaKOAia4V4hfXfU3T MxQgprjKpCkMYQKQGt+NYy0iNYlLaaYU4WpsNc3rJio1PCsyRFqQz/IfFzAzJUIY3SiV IU3gDpCBCQRlVcETydFR6g5XEyyubYbyIQ8K7qkrxbKBYRQ1H6r3fQPS2sKYXHQ7qdgc /u+w== X-Gm-Message-State: AAQBX9fzYQwMUuurTvLPifUhNr88AX9n31kPqJn078tziG303RgXydij EtfsLoSSADk0xnVQIBy/lpFvqA== X-Received: by 2002:a05:6402:2811:b0:502:465:28e1 with SMTP id h17-20020a056402281100b00502046528e1mr3106022ede.0.1680714608441; Wed, 05 Apr 2023 10:10:08 -0700 (PDT) Received: from phenom.ffwll.local (212-51-149-33.fiber7.init7.net. [212.51.149.33]) by smtp.gmail.com with ESMTPSA id tq24-20020a170907c51800b00947abb70c93sm7264820ejc.112.2023.04.05.10.10.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 05 Apr 2023 10:10:07 -0700 (PDT) Date: Wed, 5 Apr 2023 19:10:05 +0200 From: Daniel Vetter To: Asahi Lina Cc: Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Daniel Vetter , Miguel Ojeda , Alex Gaynor , Wedson Almeida Filho , Boqun Feng , Gary Guo , =?iso-8859-1?Q?Bj=F6rn?= Roy Baron , Sumit Semwal , Christian =?iso-8859-1?Q?K=F6nig?= , Luben Tuikov , Jarkko Sakkinen , Dave Hansen , Alyssa Rosenzweig , Karol Herbst , Ella Stanforth , Faith Ekstrand , Mary , 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 Subject: Re: [PATCH RFC 02/18] rust: drm: Add Device and Driver abstractions Message-ID: Mail-Followup-To: Asahi Lina , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Miguel Ojeda , Alex Gaynor , Wedson Almeida Filho , Boqun Feng , Gary Guo , =?iso-8859-1?Q?Bj=F6rn?= Roy Baron , Sumit Semwal , Christian =?iso-8859-1?Q?K=F6nig?= , Luben Tuikov , Jarkko Sakkinen , Dave Hansen , Alyssa Rosenzweig , Karol Herbst , Ella Stanforth , Faith Ekstrand , Mary , 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 References: <20230307-rust-drm-v1-0-917ff5bc80a8@asahilina.net> <20230307-rust-drm-v1-2-917ff5bc80a8@asahilina.net> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20230307-rust-drm-v1-2-917ff5bc80a8@asahilina.net> X-Operating-System: Linux phenom 6.1.0-7-amd64 X-Spam-Status: No, score=-0.2 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_NONE autolearn=unavailable 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 Tue, Mar 07, 2023 at 11:25:27PM +0900, Asahi Lina wrote: > Add the initial abstractions for DRM drivers and devices. These go > together in one commit since they are fairly tightly coupled types. > > A few things have been stubbed out, to be implemented as further bits of > the DRM subsystem are introduced. > > Signed-off-by: Asahi Lina Ok so this is fairly fundamental lifetime fun and might be fairly orthogonal to most of the things you actually want to do with a drm driver (like implement gem or whatever). So separate mail. So upfront short intro. There's 3 different lifetimes involved in building a drm driver: - struct drm_driver. It's refcounted because it's fundamentally an uapi interface thing, and all the various uapi interfaces that build on top of this (drm_file, dma_buf, ...) need to hold references on it. It's supposed to survive for as long as userspace needs it or the underlying driver is bound, whichever is longer. - struct device. Refcounted and good for dmesg printing, nothing else. Yes, because ... - ... the actual hardware resource, in many places also represented by struct device. Not refcounted, instead it's limited by hotunplug or more precisiely, how long your driver is bound to the struct device. You could make a case that in C this is represented by the bus specific type (e.g. platform_device), and the bus-specific hooks delineate the lifetime (for platform devices that's that's from ->probe to ->remove). Since there's no C type for this I'll call this hwdevice. I think for rust it would be good if we model a bit more precisely in rust. It might be possible to use the bus-specific types as the hwdevice, but that's not entirely right either because each bus device is both representing the hwdevice and the refcounted struct device. Now onto lifetimes, or at least how this is usually handled. - struct device should be obvious, the important part really is that the rust wrappers should not allow anything to be done with that which is tied to the hwdevice lifetime. Which is almost everything you want to do with a struct (platform_)device (aside from pure sw stuff like dmesg printing). - the hwdevice is _not_ refcounted. I think in rust this maps to borrow semantics, to make sure that the reference only stays valid during a driver callback. The driver core/bus driver ensure that all the various callbacks (pm_ops, platform_driver, ...) finish before the ->remove callback starts. - usually the the link from hwdevice to drm_device is done with a refcounted drm_device stored with dev_set_drvdata. For rust it'd be nice if that's the Driver and fully typesafe and automatically cleaned up. - which brings us to how hwdevice cleanup works in C: That's done with all the devm_ helpers for practically anything you might want to set up for hw access: mappings, interrupts, .... Note that there's also devm_*malloc functions, when drivers use them that's almost always a bug because generally allocations should stick around with the drm_device and not go down with the non-refcounted hwdevice lifetime. For added fun the bus/driver core also uses devm_ to mange things tied to the refcounted struct device, which works because devm_ nests and ->probe opens up a new devm_ bucket which is torn down at ->remove time. But luckily drivers should never deal with that, so for them (on the C side at least) devm_ is the right lifetime model for things tied to the hwdevice lifetime. For rust this means that we really should try to tie all the hw related things into devm or equivalent, and make both sure it's automatically cleaned up at that point, but also no later (because if you clean up hw stuff after ->remove you have a driver bug). - Similarly on the drm_device side we have drmm_. You can have some refcounted things within the lifetime of drm_device (like dma_buf), but if things the driver creates survive past the point of drm_device, then probably something went really wrong. Either a leak or you'll blow up in the C code. So again for rust I think we should try to model this, and make sure (with borrow semantics and avoiding full refcounting like the plague in driver code) that driver structs and other sw things can't outlive the drm_device, but also don't hold it alive unduly. - Since the point of a drm_device is to drive hardware, you need to be able to safely dereference the drm_device->dev pointer and know whether it's still a hwdevice (i.e. useful) or just a struct device because the hw is gone. That's done with drm_dev_enter/exit and making sure that ->remove calls drm_dev_unplug as the very first thing, before it starts tearing down hw resources like mappings, interrupts, ... On the C side we entirely rely on review for this, and it just doesn't work. Unless exhaustively tested, hotunplug just dies, and I think for more complex drivers this is something where Rust type enforcement could really shine: We'd need to make sure that a driver can only get at the hwtype where it's safe (bus/driver core callbacks or drm_dev_enter/exit as a mutex-guard thing). Furthermore we need to ensure that that drm_dev_unplug really is the first thing done in ->remove (and by symmetry drm_dev_register the last thing probe does). I think a neat way would be if ->probe would return a container of things that implement a Uapi trait, which has register and unplug functions, and then the rust glue calls that. More aggressively would be to outright not implement ->remove for rust drivers and entirely rely on the devm stack of cleanup actions. This would still need a type trick to ensure that drm_dev_register is the very last thing that's called (to make sure the matching drm_dev_unplug is the first thing). - Despite that we have refcounted pointers going both ways from drm_device<->device there's no loop, because the device->drm_device reference is dropped with hwdevice lifetime (through devm if you're using devm_drm_dev_alloc in a C driver), which breaks the loop. Note that the drm_device->device refcount/pointer stays until the very end of drm_device (need that for dmesg printing), but outside of drm_dev_enter/exit it's really just a temption for bugs. - I think ideally drivers themselves should not even try to refcount drm_device or device, but instead have all it all tied directly. The exceptions really are only for when you have separate, free-standing uapi objects (like dma_buf or dma_fence or drm_file), and in those cases the combo of C code + rust glue should ensure that the refcounting is done right. If a rust driver has any additional refcounting need for these structs then I think we've screwed up the data lifetime model. Apologies for the wall of text. I hope I didn't forget anything crucial, I've been pondering this for a few weeks now :-) Imo this doesn't need to be fixed before we merge asahi, but it is something that I think really should fix because despite years of effort and all the auto-cleanup infrastructure like devm_ and drmm_ C drivers are still buggy by default, there's no clear understanding outside of a select few about the problems ("devm_kmalloc considered harmful" is some actual talk title), and I think this is something where Rust typing and borrow checker really could substantially improve the state of the art. And yes for a soc driver that's all fairly irrelevant, because it's physically not possible to remove a device, but for most drm drivers it is absolutely possible to burn them with a hotunplug (hotunplug of device pass-through to a vm if you can't physically hotunplug the device itself), so this isn't academic at all. I'll try and type up the separate mail about semantics of gem drivers and all that stuff tomorrow. Cheers, Daniel > --- > rust/bindings/bindings_helper.h | 3 + > rust/kernel/drm/device.rs | 76 +++++++++ > rust/kernel/drm/drv.rs | 339 ++++++++++++++++++++++++++++++++++++++++ > rust/kernel/drm/mod.rs | 2 + > 4 files changed, 420 insertions(+) > > diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h > index 2687bef1676f..2a999138c4ae 100644 > --- a/rust/bindings/bindings_helper.h > +++ b/rust/bindings/bindings_helper.h > @@ -6,10 +6,13 @@ > * Sorted alphabetically. > */ > > +#include > +#include > #include > #include > #include > #include > +#include > #include > #include > #include > diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs > new file mode 100644 > index 000000000000..6007f941137a > --- /dev/null > +++ b/rust/kernel/drm/device.rs > @@ -0,0 +1,76 @@ > +// SPDX-License-Identifier: GPL-2.0 OR MIT > + > +//! DRM device. > +//! > +//! C header: [`include/linux/drm/drm_device.h`](../../../../include/linux/drm/drm_device.h) > + > +use crate::{bindings, device, drm, types::ForeignOwnable}; > +use core::marker::PhantomData; > + > +/// Represents a reference to a DRM device. The device is reference-counted and is guaranteed to > +/// not be dropped while this object is alive. > +pub struct Device { > + // Type invariant: ptr must be a valid and initialized drm_device, > + // and this value must either own a reference to it or the caller > + // must ensure that it is never dropped if the reference is borrowed. > + pub(super) ptr: *mut bindings::drm_device, > + _p: PhantomData, > +} > + > +impl Device { > + // Not intended to be called externally, except via declare_drm_ioctls!() > + #[doc(hidden)] > + pub unsafe fn from_raw(raw: *mut bindings::drm_device) -> Device { > + Device { > + ptr: raw, > + _p: PhantomData, > + } > + } > + > + #[allow(dead_code)] > + pub(crate) fn raw(&self) -> *const bindings::drm_device { > + self.ptr > + } > + > + pub(crate) fn raw_mut(&mut self) -> *mut bindings::drm_device { > + self.ptr > + } > + > + /// Returns a borrowed reference to the user data associated with this Device. > + pub fn data(&self) -> ::Borrowed<'_> { > + unsafe { T::Data::borrow((*self.ptr).dev_private) } > + } > +} > + > +impl Drop for Device { > + fn drop(&mut self) { > + // SAFETY: By the type invariants, we know that `self` owns a reference, so it is safe to > + // relinquish it now. > + unsafe { bindings::drm_dev_put(self.ptr) }; > + } > +} > + > +impl Clone for Device { > + fn clone(&self) -> Self { > + // SAFETY: We get a new reference and then create a new owning object from the raw pointer > + unsafe { > + bindings::drm_dev_get(self.ptr); > + Device::from_raw(self.ptr) > + } > + } > +} > + > +// SAFETY: `Device` only holds a pointer to a C device, which is safe to be used from any thread. > +unsafe impl Send for Device {} > + > +// SAFETY: `Device` only holds a pointer to a C device, references to which are safe to be used > +// from any thread. > +unsafe impl Sync for Device {} > + > +// Make drm::Device work for dev_info!() and friends > +unsafe impl device::RawDevice for Device { > + fn raw_device(&self) -> *mut bindings::device { > + // SAFETY: ptr must be valid per the type invariant > + unsafe { (*self.ptr).dev } > + } > +} > diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs > new file mode 100644 > index 000000000000..29a465515dc9 > --- /dev/null > +++ b/rust/kernel/drm/drv.rs > @@ -0,0 +1,339 @@ > +// SPDX-License-Identifier: GPL-2.0 OR MIT > + > +//! DRM driver core. > +//! > +//! C header: [`include/linux/drm/drm_drv.h`](../../../../include/linux/drm/drm_drv.h) > + > +use crate::{ > + bindings, device, drm, > + error::code::*, > + error::from_kernel_err_ptr, > + error::{Error, Result}, > + prelude::*, > + private::Sealed, > + str::CStr, > + types::ForeignOwnable, > + ThisModule, > +}; > +use core::{ > + marker::{PhantomData, PhantomPinned}, > + pin::Pin, > +}; > +use macros::vtable; > + > +/// Driver use the GEM memory manager. This should be set for all modern drivers. > +pub const FEAT_GEM: u32 = bindings::drm_driver_feature_DRIVER_GEM; > +/// Driver supports mode setting interfaces (KMS). > +pub const FEAT_MODESET: u32 = bindings::drm_driver_feature_DRIVER_MODESET; > +/// Driver supports dedicated render nodes. > +pub const FEAT_RENDER: u32 = bindings::drm_driver_feature_DRIVER_RENDER; > +/// Driver supports the full atomic modesetting userspace API. > +/// > +/// Drivers which only use atomic internally, but do not support the full userspace API (e.g. not > +/// all properties converted to atomic, or multi-plane updates are not guaranteed to be tear-free) > +/// should not set this flag. > +pub const FEAT_ATOMIC: u32 = bindings::drm_driver_feature_DRIVER_ATOMIC; > +/// Driver supports DRM sync objects for explicit synchronization of command submission. > +pub const FEAT_SYNCOBJ: u32 = bindings::drm_driver_feature_DRIVER_SYNCOBJ; > +/// Driver supports the timeline flavor of DRM sync objects for explicit synchronization of command > +/// submission. > +pub const FEAT_SYNCOBJ_TIMELINE: u32 = bindings::drm_driver_feature_DRIVER_SYNCOBJ_TIMELINE; > + > +/// Information data for a DRM Driver. > +pub struct DriverInfo { > + /// Driver major version. > + pub major: i32, > + /// Driver minor version. > + pub minor: i32, > + /// Driver patchlevel version. > + pub patchlevel: i32, > + /// Driver name. > + pub name: &'static CStr, > + /// Driver description. > + pub desc: &'static CStr, > + /// Driver date. > + pub date: &'static CStr, > +} > + > +/// Internal memory management operation set, normally created by memory managers (e.g. GEM). > +/// > +/// See `kernel::drm::gem` and `kernel::drm::gem::shmem`. > +pub struct AllocOps { > + pub(crate) gem_create_object: Option< > + unsafe extern "C" fn( > + dev: *mut bindings::drm_device, > + size: usize, > + ) -> *mut bindings::drm_gem_object, > + >, > + pub(crate) prime_handle_to_fd: Option< > + unsafe extern "C" fn( > + dev: *mut bindings::drm_device, > + file_priv: *mut bindings::drm_file, > + handle: u32, > + flags: u32, > + prime_fd: *mut core::ffi::c_int, > + ) -> core::ffi::c_int, > + >, > + pub(crate) prime_fd_to_handle: Option< > + unsafe extern "C" fn( > + dev: *mut bindings::drm_device, > + file_priv: *mut bindings::drm_file, > + prime_fd: core::ffi::c_int, > + handle: *mut u32, > + ) -> core::ffi::c_int, > + >, > + pub(crate) gem_prime_import: Option< > + unsafe extern "C" fn( > + dev: *mut bindings::drm_device, > + dma_buf: *mut bindings::dma_buf, > + ) -> *mut bindings::drm_gem_object, > + >, > + pub(crate) gem_prime_import_sg_table: Option< > + unsafe extern "C" fn( > + dev: *mut bindings::drm_device, > + attach: *mut bindings::dma_buf_attachment, > + sgt: *mut bindings::sg_table, > + ) -> *mut bindings::drm_gem_object, > + >, > + pub(crate) gem_prime_mmap: Option< > + unsafe extern "C" fn( > + obj: *mut bindings::drm_gem_object, > + vma: *mut bindings::vm_area_struct, > + ) -> core::ffi::c_int, > + >, > + pub(crate) dumb_create: Option< > + unsafe extern "C" fn( > + file_priv: *mut bindings::drm_file, > + dev: *mut bindings::drm_device, > + args: *mut bindings::drm_mode_create_dumb, > + ) -> core::ffi::c_int, > + >, > + pub(crate) dumb_map_offset: Option< > + unsafe extern "C" fn( > + file_priv: *mut bindings::drm_file, > + dev: *mut bindings::drm_device, > + handle: u32, > + offset: *mut u64, > + ) -> core::ffi::c_int, > + >, > + pub(crate) dumb_destroy: Option< > + unsafe extern "C" fn( > + file_priv: *mut bindings::drm_file, > + dev: *mut bindings::drm_device, > + handle: u32, > + ) -> core::ffi::c_int, > + >, > +} > + > +/// Trait for memory manager implementations. Implemented internally. > +pub trait AllocImpl: Sealed { > + /// The C callback operations for this memory manager. > + const ALLOC_OPS: AllocOps; > +} > + > +/// A DRM driver implementation. > +#[vtable] > +pub trait Driver { > + /// Context data associated with the DRM driver > + /// > + /// Determines the type of the context data passed to each of the methods of the trait. > + type Data: ForeignOwnable + Sync + Send; > + > + /// The type used to manage memory for this driver. > + /// > + /// Should be either `drm::gem::Object` or `drm::gem::shmem::Object`. > + type Object: AllocImpl; > + > + /// Driver metadata > + const INFO: DriverInfo; > + > + /// Feature flags > + const FEATURES: u32; > + > + /// IOCTL list. See `kernel::drm::ioctl::declare_drm_ioctls!{}`. > + const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor]; > +} > + > +/// A registration of a DRM device > +/// > +/// # Invariants: > +/// > +/// drm is always a valid pointer to an allocated drm_device > +pub struct Registration { > + drm: drm::device::Device, > + registered: bool, > + fops: bindings::file_operations, > + vtable: Pin>, > + _p: PhantomData, > + _pin: PhantomPinned, > +} > + > +#[cfg(CONFIG_DRM_LEGACY)] > +macro_rules! drm_legacy_fields { > + ( $($field:ident: $val:expr),* $(,)? ) => { > + bindings::drm_driver { > + $( $field: $val ),*, > + firstopen: None, > + preclose: None, > + dma_ioctl: None, > + dma_quiescent: None, > + context_dtor: None, > + irq_handler: None, > + irq_preinstall: None, > + irq_postinstall: None, > + irq_uninstall: None, > + get_vblank_counter: None, > + enable_vblank: None, > + disable_vblank: None, > + dev_priv_size: 0, > + } > + } > +} > + > +#[cfg(not(CONFIG_DRM_LEGACY))] > +macro_rules! drm_legacy_fields { > + ( $($field:ident: $val:expr),* $(,)? ) => { > + bindings::drm_driver { > + $( $field: $val ),* > + } > + } > +} > + > +/// Registers a DRM device with the rest of the kernel. > +/// > +/// It automatically picks up THIS_MODULE. > +#[allow(clippy::crate_in_macro_def)] > +#[macro_export] > +macro_rules! drm_device_register { > + ($reg:expr, $data:expr, $flags:expr $(,)?) => {{ > + $crate::drm::drv::Registration::register($reg, $data, $flags, &crate::THIS_MODULE) > + }}; > +} > + > +impl Registration { > + const VTABLE: bindings::drm_driver = drm_legacy_fields! { > + load: None, > + open: None, // TODO: File abstraction > + postclose: None, // TODO: File abstraction > + lastclose: None, > + unload: None, > + release: None, > + master_set: None, > + master_drop: None, > + debugfs_init: None, > + gem_create_object: T::Object::ALLOC_OPS.gem_create_object, > + prime_handle_to_fd: T::Object::ALLOC_OPS.prime_handle_to_fd, > + prime_fd_to_handle: T::Object::ALLOC_OPS.prime_fd_to_handle, > + gem_prime_import: T::Object::ALLOC_OPS.gem_prime_import, > + gem_prime_import_sg_table: T::Object::ALLOC_OPS.gem_prime_import_sg_table, > + gem_prime_mmap: T::Object::ALLOC_OPS.gem_prime_mmap, > + dumb_create: T::Object::ALLOC_OPS.dumb_create, > + dumb_map_offset: T::Object::ALLOC_OPS.dumb_map_offset, > + dumb_destroy: T::Object::ALLOC_OPS.dumb_destroy, > + > + major: T::INFO.major, > + minor: T::INFO.minor, > + patchlevel: T::INFO.patchlevel, > + name: T::INFO.name.as_char_ptr() as *mut _, > + desc: T::INFO.desc.as_char_ptr() as *mut _, > + date: T::INFO.date.as_char_ptr() as *mut _, > + > + driver_features: T::FEATURES, > + ioctls: T::IOCTLS.as_ptr(), > + num_ioctls: T::IOCTLS.len() as i32, > + fops: core::ptr::null_mut(), > + }; > + > + /// Creates a new [`Registration`] but does not register it yet. > + /// > + /// It is allowed to move. > + pub fn new(parent: &dyn device::RawDevice) -> Result { > + let vtable = Pin::new(Box::try_new(Self::VTABLE)?); > + let raw_drm = unsafe { bindings::drm_dev_alloc(&*vtable, parent.raw_device()) }; > + let raw_drm = from_kernel_err_ptr(raw_drm)?; > + > + // The reference count is one, and now we take ownership of that reference as a > + // drm::device::Device. > + let drm = unsafe { drm::device::Device::from_raw(raw_drm) }; > + > + Ok(Self { > + drm, > + registered: false, > + vtable, > + fops: Default::default(), // TODO: GEM abstraction > + _pin: PhantomPinned, > + _p: PhantomData, > + }) > + } > + > + /// Registers a DRM device with the rest of the kernel. > + /// > + /// Users are encouraged to use the [`drm_device_register!()`] macro because it automatically > + /// picks up the current module. > + pub fn register( > + self: Pin<&mut Self>, > + data: T::Data, > + flags: usize, > + module: &'static ThisModule, > + ) -> Result { > + if self.registered { > + // Already registered. > + return Err(EINVAL); > + } > + > + // SAFETY: We never move out of `this`. > + let this = unsafe { self.get_unchecked_mut() }; > + let data_pointer = ::into_foreign(data); > + // SAFETY: `drm` is valid per the type invariant > + unsafe { > + (*this.drm.raw_mut()).dev_private = data_pointer as *mut _; > + } > + > + this.fops.owner = module.0; > + this.vtable.fops = &this.fops; > + > + // SAFETY: The device is now initialized and ready to be registered. > + let ret = unsafe { bindings::drm_dev_register(this.drm.raw_mut(), flags as u64) }; > + if ret < 0 { > + // SAFETY: `data_pointer` was returned by `into_foreign` above. > + unsafe { T::Data::from_foreign(data_pointer) }; > + return Err(Error::from_kernel_errno(ret)); > + } > + > + this.registered = true; > + Ok(()) > + } > + > + /// Returns a reference to the `Device` instance for this registration. > + pub fn device(&self) -> &drm::device::Device { > + &self.drm > + } > +} > + > +// SAFETY: `Registration` doesn't offer any methods or access to fields when shared between threads > +// or CPUs, so it is safe to share it. > +unsafe impl Sync for Registration {} > + > +// SAFETY: Registration with and unregistration from the drm subsystem can happen from any thread. > +// Additionally, `T::Data` (which is dropped during unregistration) is `Send`, so it is ok to move > +// `Registration` to different threads. > +#[allow(clippy::non_send_fields_in_send_ty)] > +unsafe impl Send for Registration {} > + > +impl Drop for Registration { > + /// Removes the registration from the kernel if it has completed successfully before. > + fn drop(&mut self) { > + if self.registered { > + // Get a pointer to the data stored in device before destroying it. > + // SAFETY: `drm` is valid per the type invariant > + let data_pointer = unsafe { (*self.drm.raw_mut()).dev_private }; > + > + // SAFETY: Since `registered` is true, `self.drm` is both valid and registered. > + unsafe { bindings::drm_dev_unregister(self.drm.raw_mut()) }; > + > + // Free data as well. > + // SAFETY: `data_pointer` was returned by `into_foreign` during registration. > + unsafe { ::from_foreign(data_pointer) }; > + } > + } > +} > diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs > index 9ec6d7cbcaf3..69376b3c6db9 100644 > --- a/rust/kernel/drm/mod.rs > +++ b/rust/kernel/drm/mod.rs > @@ -2,4 +2,6 @@ > > //! DRM subsystem abstractions. > > +pub mod device; > +pub mod drv; > pub mod ioctl; > > -- > 2.35.1 > -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch