From patchwork Wed Mar 5 22:59:17 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003605 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id C93DFC19F32 for ; Wed, 5 Mar 2025 23:04:36 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 451DF10E0D7; Wed, 5 Mar 2025 23:04:36 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="GoYhkgAv"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id D1DD410E0D7 for ; Wed, 5 Mar 2025 23:04:34 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215874; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=IIuY6TyfKud/M9WMFn5VR3J80u8Grm+L9fJlJoOSHhY=; b=GoYhkgAvPSkfF63prYHDgqxKHMUnKvFjaHqVaKmci1J2oCeLupfUQfGYaxAktobuofKH3x j43mm0ri2nYKPfNCdUuWUG1TLRhvEPWvsQna7mGNm9IoXgBquEz6t6lgKt1dOkPV5L7Doj JN8n/3J1soxgvbzVqCskULuc5XVVhR4= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-125-jnJ7gJu3Nuqg6hYvZ-kGQg-1; Wed, 05 Mar 2025 18:04:29 -0500 X-MC-Unique: jnJ7gJu3Nuqg6hYvZ-kGQg-1 X-Mimecast-MFC-AGG-ID: jnJ7gJu3Nuqg6hYvZ-kGQg_1741215867 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 8A93A1800349; Wed, 5 Mar 2025 23:04:26 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 78BB9300019E; Wed, 5 Mar 2025 23:04:21 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Asahi Lina , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 01/33] rust: drm: Add a small handful of fourcc bindings Date: Wed, 5 Mar 2025 17:59:17 -0500 Message-ID: <20250305230406.567126-2-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This adds some very basic rust bindings for fourcc. We only have a single format code added for the moment, but this is enough to get a driver registered. Signed-off-by: Lyude Paul --- V3: * Drop FormatList and ModifierList These aren't actually needed as pointed out by Louis Chauvet * Add a constant for FORMAT_MOD_INVALID I realized that we actually need this because the format list isn't terminated with a 0 like I thought, and we can't pick this up automatically through bindgen * Split out the FormatInfo WIP We'll want this someday, but not yet. Signed-off-by: Lyude Paul --- rust/kernel/drm/fourcc.rs | 21 +++++++++++++++++++++ rust/kernel/drm/mod.rs | 1 + 2 files changed, 22 insertions(+) create mode 100644 rust/kernel/drm/fourcc.rs diff --git a/rust/kernel/drm/fourcc.rs b/rust/kernel/drm/fourcc.rs new file mode 100644 index 0000000000000..62203478b5955 --- /dev/null +++ b/rust/kernel/drm/fourcc.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM fourcc bindings. +//! +//! C header: [`include/uapi/drm/drm_fourcc.h`](srctree/include/uapi/drm/drm_fourcc.h) + +/// Return a fourcc format code. +const fn fourcc_code(a: u8, b: u8, c: u8, d: u8) -> u32 { + (a as u32) | (b as u32) << 8 | (c as u32) << 16 | (d as u32) << 24 +} + +// TODO: We manually import this because we don't have a reasonable way of getting constants from +// function-like macros in bindgen yet. +#[allow(dead_code)] +pub(crate) const FORMAT_MOD_INVALID: u64 = 0xffffffffffffff; + +// TODO: We need to automate importing all of these. For the time being, just add the single one +// that we need + +/// 32 bpp RGB +pub const XRGB888: u32 = fourcc_code(b'X', b'R', b'2', b'4'); diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index c44760a1332fa..2c12dbd181997 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -5,5 +5,6 @@ pub mod device; pub mod drv; pub mod file; +pub mod fourcc; pub mod gem; pub mod ioctl; From patchwork Wed Mar 5 22:59:18 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003606 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 464A1C19F32 for ; Wed, 5 Mar 2025 23:04:56 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id A655810E0F8; Wed, 5 Mar 2025 23:04:55 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="VXDjNYe4"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 331ED10E0F8 for ; Wed, 5 Mar 2025 23:04:54 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215893; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Yi8QcXdg+H/kQ2ed4byvlgjFhZtCiY0sf5Enn4vU128=; b=VXDjNYe4y0yYNThwo1SvUnSgh4ufez9hAqEh2XOl6VdqilDsAILoy/UTmA5MSBHbthzska PuIrexJLcoxb9YIdWgRI5p0h/5xBX4s1Qq2QrQBoZ5nkJamY4A1w+Uc/EDshxQrc4Vi2Rk OofN2Uily6z6/3hhsQzg/rFFEZpwGtM= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-673-vVkYYhf5OqK_am3JQdX4Xg-1; Wed, 05 Mar 2025 18:04:48 -0500 X-MC-Unique: vVkYYhf5OqK_am3JQdX4Xg-1 X-Mimecast-MFC-AGG-ID: vVkYYhf5OqK_am3JQdX4Xg_1741215885 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 23A4D1809CA6; Wed, 5 Mar 2025 23:04:45 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 2C41A300019E; Wed, 5 Mar 2025 23:04:39 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Greg Kroah-Hartman , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 02/33] rust: drm: Add traits for registering KMS devices Date: Wed, 5 Mar 2025 17:59:18 -0500 Message-ID: <20250305230406.567126-3-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This commit adds some traits for registering DRM devices with KMS support, implemented through the kernel::drm::kms::KmsDriver trait. Devices which don't have KMS support can simply use PhantomData. Signed-off-by: Lyude Paul --- V3: * Get rid of Kms, long live KmsDriver After Daniel pointed out that we should just make KmsDriver a supertrait of Driver, it immediately occurred to me that there's no actual need for Kms to be a separate trait at all. So, drop Kms entirely and move its requirements over to KmsDriver. * Drop fbdev module entirely and move fbdev related setup into AllocImpl (Daniel) * Rebase to use drm_client_setup() TODO: * Generate feature flags automatically, these shouldn't need to be specified by the user Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 6 ++ rust/kernel/drm/device.rs | 10 +- rust/kernel/drm/drv.rs | 56 ++++++++-- rust/kernel/drm/gem/mod.rs | 4 + rust/kernel/drm/gem/shmem.rs | 4 + rust/kernel/drm/kms.rs | 186 ++++++++++++++++++++++++++++++++ rust/kernel/drm/mod.rs | 1 + 7 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 rust/kernel/drm/kms.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index ca857fb00b1a5..e1ed4f40c8e89 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -6,10 +6,16 @@ * Sorted alphabetically. */ +#include +#include +#include #include #include #include +#include +#include #include +#include #include #include #include diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index 5b4db2dfe87f5..cf063de387329 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -5,8 +5,8 @@ //! C header: [`include/linux/drm/drm_device.h`](srctree/include/linux/drm/drm_device.h) use crate::{ - bindings, device, drm, - drm::drv::AllocImpl, + bindings, device, + drm::{self, drv::AllocImpl, kms::private::KmsImpl as KmsImplPrivate}, error::code::*, error::from_err_ptr, error::Result, @@ -73,7 +73,7 @@ impl Device { dumb_create: T::Object::ALLOC_OPS.dumb_create, dumb_map_offset: T::Object::ALLOC_OPS.dumb_map_offset, show_fdinfo: None, - fbdev_probe: None, + fbdev_probe: T::Object::ALLOC_OPS.fbdev_probe, major: T::INFO.major, minor: T::INFO.minor, @@ -153,6 +153,10 @@ pub fn data(&self) -> ::Borrowed<'_> { // SAFETY: `Self::data` is always converted and set on device creation. unsafe { ::from_foreign(drm.raw_data()) }; } + + pub(crate) const fn has_kms() -> bool { + ::MODE_CONFIG_OPS.is_some() + } } // SAFETY: DRM device objects are always reference counted and the get/put functions diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs index e42e266bdd0da..3e09e130730f6 100644 --- a/rust/kernel/drm/drv.rs +++ b/rust/kernel/drm/drv.rs @@ -6,14 +6,15 @@ use crate::{ alloc::flags::*, - bindings, + bindings, device, devres::Devres, - drm, + drm::{self, kms::private::KmsImpl as KmsImplPrivate}, error::{Error, Result}, private::Sealed, str::CStr, types::{ARef, ForeignOwnable}, }; +use core::ptr::null; use macros::vtable; /// Driver use the GEM memory manager. This should be set for all modern drivers. @@ -115,6 +116,12 @@ pub struct AllocOps { offset: *mut u64, ) -> core::ffi::c_int, >, + pub(crate) fbdev_probe: Option< + unsafe extern "C" fn( + fbdev_helper: *mut bindings::drm_fb_helper, + sizes: *mut bindings::drm_fb_helper_surface_size, + ) -> core::ffi::c_int, + >, } /// Trait for memory manager implementations. Implemented internally. @@ -142,6 +149,14 @@ pub trait Driver { /// The type used to represent a DRM File (client) type File: drm::file::DriverFile; + /// The KMS implementation for this driver. + /// + /// Drivers that wish to support KMS should pass their implementation of `drm::kms::KmsDriver` + /// here. Drivers which do not have KMS support can simply pass `drm::kms::NoKms` here. + type Kms: drm::kms::KmsImpl + where + Self: Sized; + /// Driver metadata const INFO: DriverInfo; @@ -159,21 +174,44 @@ pub trait Driver { impl Registration { /// Creates a new [`Registration`] and registers it. - pub fn new(drm: ARef>, flags: usize) -> Result { + pub fn new(dev: &device::Device, data: T::Data, flags: usize) -> Result { + let drm = drm::device::Device::::new(dev, data)?; + let has_kms = drm::device::Device::::has_kms(); + + let mode_config_info = if has_kms { + // SAFETY: We have yet to register this device + Some(unsafe { T::Kms::setup_kms(&drm)? }) + } else { + None + }; + // SAFETY: Safe by the invariants of `drm::device::Device`. let ret = unsafe { bindings::drm_dev_register(drm.as_raw(), flags) }; if ret < 0 { return Err(Error::from_errno(ret)); } + #[cfg(CONFIG_DRM_CLIENT = "y")] + if has_kms { + if let Some(ref info) = mode_config_info { + if let Some(fourcc) = info.preferred_fourcc { + // SAFETY: We just registered `drm` above, fulfilling the C API requirements + unsafe { bindings::drm_client_setup_with_fourcc(drm.as_raw(), fourcc) } + } else { + // SAFETY: We just registered `drm` above, fulfilling the C API requirements + unsafe { bindings::drm_client_setup(drm.as_raw(), null()) } + } + } + } + Ok(Self(drm)) } /// Same as [`Registration::new`}, but transfers ownership of the [`Registration`] to `Devres`. - pub fn new_foreign_owned(drm: ARef>, flags: usize) -> Result { - let reg = Registration::::new(drm.clone(), flags)?; + pub fn new_foreign_owned(dev: &device::Device, data: T::Data, flags: usize) -> Result { + let reg = Registration::::new(dev, data, flags)?; - Devres::new_foreign_owned(drm.as_ref(), reg, GFP_KERNEL) + Devres::new_foreign_owned(dev, reg, GFP_KERNEL) } /// Returns a reference to the `Device` instance for this registration. @@ -195,5 +233,11 @@ fn drop(&mut self) { // SAFETY: Safe by the invariant of `ARef>`. The existance of this // `Registration` also guarantees the this `drm::device::Device` is actually registered. unsafe { bindings::drm_dev_unregister(self.0.as_raw()) }; + + if drm::device::Device::::has_kms() { + // SAFETY: We just checked above that KMS was setup for this device, so this is safe to + // call + unsafe { bindings::drm_atomic_helper_shutdown(self.0.as_raw()) } + } } } diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index 3fcab497cc2a5..605b0a22ac08b 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -300,6 +300,10 @@ impl drv::AllocImpl for Object { gem_prime_import_sg_table: None, dumb_create: None, dumb_map_offset: None, + #[cfg(CONFIG_DRM_FBDEV_EMULATION = "y")] + fbdev_probe: Some(bindings::drm_fbdev_dma_driver_fbdev_probe), + #[cfg(CONFIG_DRM_FBDEV_EMULATION = "n")] + fbdev_probe: None, }; } diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 92da0d7d59912..9c0162b268aa8 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -279,6 +279,10 @@ impl drv::AllocImpl for Object { gem_prime_import_sg_table: Some(bindings::drm_gem_shmem_prime_import_sg_table), dumb_create: Some(bindings::drm_gem_shmem_dumb_create), dumb_map_offset: None, + #[cfg(CONFIG_DRM_FBDEV_EMULATION = "y")] + fbdev_probe: Some(bindings::drm_fbdev_shmem_driver_fbdev_probe), + #[cfg(CONFIG_DRM_FBDEV_EMULATION = "n")] + fbdev_probe: None, }; } diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs new file mode 100644 index 0000000000000..78970c69f4cda --- /dev/null +++ b/rust/kernel/drm/kms.rs @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! KMS driver abstractions for rust. + +use crate::{ + device, + drm::{device::Device, drv::Driver}, + error::to_result, + prelude::*, + types::*, +}; +use bindings; +use core::{marker::PhantomData, ops::Deref}; + +/// The C vtable for a [`Device`]. +/// +/// This is created internally by DRM. +pub struct ModeConfigOps { + pub(crate) kms_vtable: bindings::drm_mode_config_funcs, + pub(crate) kms_helper_vtable: bindings::drm_mode_config_helper_funcs, +} + +/// A trait representing a type that can be used for setting up KMS, or a stub. +/// +/// For drivers which don't have KMS support, the methods provided by this trait may be stubs. It is +/// implemented internally by DRM. +pub trait KmsImpl: private::KmsImpl {} + +pub(crate) mod private { + use super::*; + + /// Private callback implemented internally by DRM for setting up KMS on a device, or stubbing + /// the KMS setup for devices which don't have KMS support. + #[allow(unreachable_pub)] + pub trait KmsImpl { + /// The parent driver for this KMS implementation + type Driver: Driver; + + /// The optional KMS callback operations for this driver. + const MODE_CONFIG_OPS: Option; + + /// The callback for setting up KMS on a device + /// + /// # Safety + /// + /// `drm` must be unregistered. + unsafe fn setup_kms(_drm: &Device) -> Result { + build_error::build_error("This should never be reachable") + } + } +} + +/// A [`Device`] with KMS initialized that has not been registered with userspace. +/// +/// This type is identical to [`Device`], except that it is able to create new static KMS resources. +/// It represents a KMS device that is not yet visible to userspace, and also contains miscellaneous +/// state required during the initialization process of a [`Device`]. +pub struct UnregisteredKmsDevice<'a, T: Driver> { + drm: &'a Device, +} + +impl<'a, T: Driver> Deref for UnregisteredKmsDevice<'a, T> { + type Target = Device; + + fn deref(&self) -> &Self::Target { + self.drm + } +} + +impl<'a, T: Driver> UnregisteredKmsDevice<'a, T> { + /// Construct a new [`UnregisteredKmsDevice`]. + /// + /// # Safety + /// + /// The caller promises that `drm` is an unregistered [`Device`]. + pub(crate) unsafe fn new(drm: &'a Device) -> Self { + Self { drm } + } +} + +/// A trait which must be implemented by drivers that wish to support KMS +/// +/// It should be implemented for the same type that implements [`Driver`]. Drivers which don't +/// support KMS should use [`PhantomData`]. +/// +/// [`PhantomData`]: PhantomData +#[vtable] +pub trait KmsDriver: Driver { + /// Return a [`ModeConfigInfo`] structure for this [`device::Device`]. + fn mode_config_info( + dev: &device::Device, + drm_data: ::Borrowed<'_>, + ) -> Result; + + /// Create mode objects like [`crtc::Crtc`], [`plane::Plane`], etc. for this device + fn create_objects(drm: &UnregisteredKmsDevice<'_, Self>) -> Result + where + Self: Sized; +} + +impl private::KmsImpl for T { + type Driver = Self; + + const MODE_CONFIG_OPS: Option = Some(ModeConfigOps { + kms_vtable: bindings::drm_mode_config_funcs { + atomic_check: Some(bindings::drm_atomic_helper_check), + fb_create: Some(bindings::drm_gem_fb_create), + mode_valid: None, + atomic_commit: Some(bindings::drm_atomic_helper_commit), + get_format_info: None, + atomic_state_free: None, + atomic_state_alloc: None, + atomic_state_clear: None, + }, + + kms_helper_vtable: bindings::drm_mode_config_helper_funcs { + atomic_commit_setup: None, + atomic_commit_tail: None, + }, + }); + + unsafe fn setup_kms(drm: &Device) -> Result { + let mode_config_info = T::mode_config_info(drm.as_ref(), drm.data())?; + + // SAFETY: `MODE_CONFIG_OPS` is always Some() in this implementation + let ops = unsafe { T::MODE_CONFIG_OPS.as_ref().unwrap_unchecked() }; + + // SAFETY: + // - This function can only be called before registration via our safety contract. + // - Before registration, we are the only ones with access to this device. + unsafe { + (*drm.as_raw()).mode_config = bindings::drm_mode_config { + funcs: &ops.kms_vtable, + helper_private: &ops.kms_helper_vtable, + min_width: mode_config_info.min_resolution.0, + min_height: mode_config_info.min_resolution.1, + max_width: mode_config_info.max_resolution.0, + max_height: mode_config_info.max_resolution.1, + cursor_width: mode_config_info.max_cursor.0, + cursor_height: mode_config_info.max_cursor.1, + preferred_depth: mode_config_info.preferred_depth, + ..Default::default() + }; + } + + // SAFETY: We just setup all of the required info this function needs in `drm_device` + to_result(unsafe { bindings::drmm_mode_config_init(drm.as_raw()) })?; + + // SAFETY: `drm` is guaranteed to be unregistered via our safety contract. + let drm = unsafe { UnregisteredKmsDevice::new(drm) }; + + T::create_objects(&drm)?; + + // TODO: Eventually add a hook to customize how state readback happens, for now just reset + // SAFETY: Since all static modesetting objects were created in `T::create_objects()`, and + // that is the only place they can be created, this fulfills the C API requirements. + unsafe { bindings::drm_mode_config_reset(drm.as_raw()) }; + + Ok(mode_config_info) + } +} + +impl KmsImpl for T {} + +impl private::KmsImpl for PhantomData { + type Driver = T; + + const MODE_CONFIG_OPS: Option = None; +} + +impl KmsImpl for PhantomData {} + +/// Various device-wide information for a [`Device`] that is provided during initialization. +#[derive(Copy, Clone)] +pub struct ModeConfigInfo { + /// The minimum (w, h) resolution this driver can support + pub min_resolution: (i32, i32), + /// The maximum (w, h) resolution this driver can support + pub max_resolution: (i32, i32), + /// The maximum (w, h) cursor size this driver can support + pub max_cursor: (u32, u32), + /// The preferred depth for dumb ioctls + pub preferred_depth: u32, + /// An optional default fourcc format code to be preferred for clients. + pub preferred_fourcc: Option, +} diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index 2c12dbd181997..049ae675cb9b1 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -8,3 +8,4 @@ pub mod fourcc; pub mod gem; pub mod ioctl; +pub mod kms; From patchwork Wed Mar 5 22:59:19 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003607 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id BF5E0C19F32 for ; Wed, 5 Mar 2025 23:05:00 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 3336B10E79A; Wed, 5 Mar 2025 23:05:00 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="M35huzuu"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 55F1A10E747 for ; Wed, 5 Mar 2025 23:04:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215898; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=t9G2Y/B8oV7JE32hgCTUV0vmO4Ma4WWVkNyK9m/56k4=; b=M35huzuuD5Pjn6T5XZq1bCEFTcsED5lQIzgsCwdWRVWjkGm3BpclFootTM2Asv+wEhSvcI AumJYSqQhSHu51ftlSf4s1HQS746Xgo6YNi8OYoxcUd7fbidHeo6r7XVk4Oioe+30s3U59 sxXBssepKKMKCOKneEtawcFgMm5P/Lc= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-152-fn86Z4c_Pd6p_StcYfz-ew-1; Wed, 05 Mar 2025 18:04:55 -0500 X-MC-Unique: fn86Z4c_Pd6p_StcYfz-ew-1 X-Mimecast-MFC-AGG-ID: fn86Z4c_Pd6p_StcYfz-ew_1741215893 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id D209B1955F30; Wed, 5 Mar 2025 23:04:51 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 9AC0E300019E; Wed, 5 Mar 2025 23:04:47 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 03/33] rust: drm/kms: Introduce the main ModeConfigObject traits Date: Wed, 5 Mar 2025 17:59:19 -0500 Message-ID: <20250305230406.567126-4-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" The KMS API has a very consistent idea of a "mode config object", which includes any object with a drm_mode_object struct embedded in it. These objects have their own object IDs which DRM exposes to userspace, and we introduce the ModeConfigObject trait to represent any object matching these characteristics. One slightly less consistent trait of these objects however: some mode objects have a reference count, while others don't. Since rust requires that we are able to define the lifetime of an object up-front, we introduce two other super-traits of ModeConfigObject for this: * StaticModeObject - this trait represents any mode object which does not have a reference count of its own. Such objects can be considered to share the lifetime of their parent KMS device * RcModeObject - this trait represents any mode object which does have its own reference count. Objects implementing this trait get a free blanket implementation of AlwaysRefCounted, and as such can be used with the ARef container without us having to implement AlwaysRefCounted for each individual mode object. This will be able to handle most lifetimes we'll need with one exception: it's entirely possible a driver may want to hold a "owned" reference to a static mode object. We allow for this by introducing the KmsRef container, which grabs an owned refcount to the parent KMS device of a StaticModeObject and holds a pointer to said object - essentially allowing it to act identically to an owned refcount by preventing the device's lifetime from ending until the KmsRef is dropped. I choose not to use AlwaysRefCounted for this as holding a refcount to the device has its own set of implications since if you forget to drop the KmsRef the device will never be destroyed. Signed-off-by: Lyude Paul --- V3: * Document why modesetting objects require Send + Sync * Make `ModeObject` an unsafe trait I was prompted to make this change in response to one of Daniel's comments, as it occurred to me that we need something that ensures that implementers are only returning valid `drm_mode_object` pointers so we have something to put down for the various related safety comments in RcModeObject. Also, update the safety comments there. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms.rs | 127 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 78970c69f4cda..885bd5266a2d7 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -7,10 +7,11 @@ drm::{device::Device, drv::Driver}, error::to_result, prelude::*, + private::Sealed, types::*, }; use bindings; -use core::{marker::PhantomData, ops::Deref}; +use core::{marker::PhantomData, ops::Deref, ptr::NonNull}; /// The C vtable for a [`Device`]. /// @@ -184,3 +185,127 @@ pub struct ModeConfigInfo { /// An optional default fourcc format code to be preferred for clients. pub preferred_fourcc: Option, } + +/// A modesetting object in DRM. +/// +/// This is any type of object where the underlying C object contains a [`struct drm_mode_object`]. +/// This type requires [`Send`] + [`Sync`] as all modesetting objects in DRM are able to be sent +/// between threads. +/// +/// This type is only implemented by the DRM crate itself. +/// +/// # Safety +/// +/// [`raw_mode_obj()`] must always return a valid pointer to an initialized +/// [`struct drm_mode_object`]. +/// +/// [`struct drm_mode_object`]: srctree/include/drm/drm_mode_object.h +/// [`raw_mode_obj()`]: ModeObject::raw_mode_obj() +pub unsafe trait ModeObject: Sealed + Send + Sync { + /// The parent driver for this [`ModeObject`]. + type Driver: KmsDriver; + + /// Return the [`Device`] for this [`ModeObject`]. + fn drm_dev(&self) -> &Device; + + /// Return a pointer to the [`struct drm_mode_object`] for this [`ModeObject`]. + /// + /// [`struct drm_mode_object`]: (srctree/include/drm/drm_mode_object.h) + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object; +} + +/// A trait for modesetting objects which don't come with their own reference-counting. +/// +/// Some [`ModeObject`] types in DRM do not have a reference count. These types are considered +/// "static" and share the lifetime of their parent [`Device`]. To retrieve an owned reference to +/// such types, see [`KmsRef`]. +/// +/// # Safety +/// +/// This trait must only be implemented for modesetting objects which do not have a refcount within +/// their [`struct drm_mode_object`], otherwise [`KmsRef`] can't guarantee the object will stay +/// alive. +/// +/// [`struct drm_mode_object`]: (srctree/include/drm/drm_mode_object.h) +pub unsafe trait StaticModeObject: ModeObject {} + +/// An owned reference to a [`StaticModeObject`]. +/// +/// Note that since [`StaticModeObject`] types share the lifetime of their parent [`Device`], the +/// parent [`Device`] will stay alive as long as this type exists. Thus, users should be aware that +/// storing a [`KmsRef`] within a [`ModeObject`] is a circular reference. +/// +/// # Invariants +/// +/// `self.0` points to a valid instance of `T` throughout the lifetime of this type. +pub struct KmsRef(NonNull); + +// SAFETY: Owned references to DRM device are thread-safe. +unsafe impl Send for KmsRef {} +// SAFETY: Owned references to DRM device are thread-safe. +unsafe impl Sync for KmsRef {} + +impl From<&T> for KmsRef { + fn from(value: &T) -> Self { + // INVARIANT: Because the lifetime of the StaticModeObject is the same as the lifetime of + // its parent device, we can ensure that `value` remains alive by incrementing the device's + // reference count. The device will only disappear once we drop this reference in `Drop`. + value.drm_dev().inc_ref(); + + Self(value.into()) + } +} + +impl Drop for KmsRef { + fn drop(&mut self) { + // SAFETY: We're reclaiming the reference we leaked in From<&T> + drop(unsafe { ARef::from_raw(self.drm_dev().into()) }) + } +} + +impl Deref for KmsRef { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: We're guaranteed object will point to a valid object as long as we hold dev + unsafe { self.0.as_ref() } + } +} + +impl Clone for KmsRef { + fn clone(&self) -> Self { + // INVARIANT: Because the lifetime of the StaticModeObject is the same as the lifetime of + // its parent device, we can ensure that `value` remains alive by incrementing the device's + // reference count. The device will only disappear once we drop this reference in `Drop`. + self.drm_dev().inc_ref(); + + Self(self.0) + } +} + +/// A trait for [`ModeObject`] which is reference counted. +/// +/// This trait is implemented by DRM for any [`ModeObject`] which has a reference count provided by +/// [`struct drm_mode_object`]. It provides a common implementation of [`AlwaysRefCounted`], since +/// all [`RcModeObject`] types use the same functions for refcounting. +/// +/// # Safety +/// +/// The [`ModeObject`] must initialize the refcount in its [`struct drm_mode_object`] field. +/// +/// [`struct drm_mode_object`]: (srctree/include/drm/drm_mode_object.h) +pub unsafe trait RcModeObject: ModeObject {} + +unsafe impl AlwaysRefCounted for T { + fn inc_ref(&self) { + // SAFETY: We're guaranteed by the safety contract of `ModeObject` that `raw_mode_obj()` + // always returns a pointer to an initialized `drm_mode_object`. + unsafe { bindings::drm_mode_object_get(self.raw_mode_obj()) } + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: We're guaranteed by the safety contract of `ModeObject` that `raw_mode_obj()` + // always returns a pointer to an initialized `drm_mode_object`. + unsafe { bindings::drm_mode_object_put(obj.as_ref().raw_mode_obj()) } + } +} From patchwork Wed Mar 5 22:59:20 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003608 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 2E5A5C282EC for ; Wed, 5 Mar 2025 23:05:14 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 95ADE10E32A; Wed, 5 Mar 2025 23:05:13 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="UIfb3V+D"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id C97A910E84B for ; Wed, 5 Mar 2025 23:05:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215911; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=LogiM9ZDUPwbyWWHPCC3QoqDMOo0yzri5ySeSxOIFk0=; b=UIfb3V+Dd26p7qmAdjZjCLAWnEQxtG1fHczzgoY+/ARviMsEJIY12uxFXwxHYrDbkn1evw pHl42i4v3EyNiZsmTQJ/1znbQKoeYYNcmhffknzE6sfineGJLO7MmOa4swn28L/wd1ESCe KwikzmBfO/xuEQqgaUjwwMwDtO8erRE= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-308-BZTtjuv8N4mFiCS4A-5xvQ-1; Wed, 05 Mar 2025 18:05:07 -0500 X-MC-Unique: BZTtjuv8N4mFiCS4A-5xvQ-1 X-Mimecast-MFC-AGG-ID: BZTtjuv8N4mFiCS4A-5xvQ_1741215904 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id D08BB180AEBF; Wed, 5 Mar 2025 23:05:03 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 67B14300019E; Wed, 5 Mar 2025 23:04:57 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Greg Kroah-Hartman , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 04/33] rust: drm/kms: Add drm_connector bindings Date: Wed, 5 Mar 2025 17:59:20 -0500 Message-ID: <20250305230406.567126-5-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" We start off by introducing wrappers for the first important type of mode object: a DRM display connector. This introduces Connector and ConnectorState. Both DriverConnector and DriverConnectorState must be implemented by KMS drivers, and a driver may have as many implementations of these two traits as it needs. This also introduces the general data pattern we'll be using for all of the core mode objects that can be used in atomic commits. It's important to note that both Connector and ConnectorState are intended to be "subclassable". To explain what this means, we need to look at how a DRM driver normally uses objects like DRM connectors. Typically, a driver in C will define its connectors like so: struct foo_connector { struct drm_connector base; int bar; } Note that we have a drm_connector struct embedded in foo_connector, but we have data which comes after it which is defined by the driver. This is important for a number of reasons: connectors can have their own mutexes and various other hardware-specific information that a driver may want access to at any time. The same goes for drm_connector_state, where drivers will subclass this struct in the same way. It's worth noting as well that it isn't uncommon for a driver to have multiple types of connectors, but we'll handle in a later commit. As a result, we've designed Connector and ConnectorState so that for both types: a DRM driver can add custom data into the T. As well, there's some basic limitations on how this data may be accessed: * Data within the `DriverConnector` struct is pinned in order to allow mutexes and other structs which need pinning to be stored within it. As well, it is impossible to get a direct mutable reference to the data within DriverConnector - as there's no locks for doing so which would cause a race condition. * Data within the `DriverConnectorState` struct is currently not pinned. While it's not unheard of for a driver to put something like a mutex in its atomic states, (VKMS actually does this in some spots) this quickly complicates things especially with nonblocking modesets - and doesn't really fit into the philosophy of an atomic state anyway. We may add support for this in the future later if this does end up being needed, but for now we hold back in order to make it much easier for drivers to access private data within the atomic state. As well, the functions we provide for converting to/from raw connector state pointers are notably different from many other rust types in the kernel. Instead of converting raw state pointers to raw ConnectorState pointers, we allow for direct immutable and mutable references. The reason for this is that it makes accessing private driver data in the state much easier, and unlike Connector - we can actually uphold all of the required data aliasing rules thanks to states only being mutable by a single thread before they've been swapped in. Note that currently, we don't provide a way to access said private data for ConnectorState since allowing direct access to a &mut ConnectorState could allow a caller to modify portions of drm_connector_state which are meant to be invariant throughout the lifetime of the connector state. We'll address this in the next few commits when we introduce the global atomic state type. And finally - we introduce the following internal traits for the crate side of things: * AsRawConnector - any type which can spit out a *mut bindings::drm_connector or be recovered from one * AsRawConnectorState - any type which can return a reference to a bindings::drm_connector_state * private::AsRawConnectorState - just methods for AsRawConnectorState that we don't want to be accessible to our users (since they could be used to introduce UB) * FromRawConnectorState - any type which can be recovered from a raw pointer to a bindings::drm_connector_state The reason for having AsRawConnectorState and FromRawConnectorState as separate traits unlike AsRawConnector is due to the fact that we'll introduce objects later on which can be used as DRM connector states, but cannot be directly derived from a *mut bindings::drm_connector_state because they hold additional state or have additional side-effects. Likewise, we'll also have other objects which can be used as raw DRM connectors - hence AsRawConnector. Signed-off-by: Lyude Paul --- V3: * Add safety comment to implementation of ModeObject * Make AsRawConnector an unsafe trait, we need a guarantee that as_raw() always returns a valid pointer. * Improve safety comments in atomic_duplicate_state_callback * Improve safety comments in Connector::new() * Switch to requiring a UnregisteredKmsDevice instead of a Device This is in preparation for the static/dynamic connector split, which we may as well prepare for since we don't have any use for dynamic connectors yet. * Drop redundant Connector associated type in AsRawConnector trait * Improve safety comments in FromRawConnectorState * Introduce UnregisteredConnector type * Don't have AsRawConnector be a supertrait of StaticModeObject. We don't want Unregistered mode object variants to be able to return a pointer to the DRM device since that would break the UnregisteredKmsDevice pattern. * Introduce an actual enum for connector types I realized we actually could do this fairly easy by using #[non_exhaustive], which should future-proof us against new connector types being added someday (if that ever happens). * Use addr_of_mut! for accessing fields we were using &mut for. I think this is correct after going through some other rfl work? Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/kms.rs | 2 + rust/kernel/drm/kms/connector.rs | 616 +++++++++++++++++++++++++++++++ 3 files changed, 619 insertions(+) create mode 100644 rust/kernel/drm/kms/connector.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index e1ed4f40c8e89..c41a3309223b2 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 885bd5266a2d7..f10e9f83ccb78 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -2,6 +2,8 @@ //! KMS driver abstractions for rust. +pub mod connector; + use crate::{ device, drm::{device::Device, drv::Driver}, diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs new file mode 100644 index 0000000000000..ed65c06ece627 --- /dev/null +++ b/rust/kernel/drm/kms/connector.rs @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM display connectors. +//! +//! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_connector.h) + +use super::{KmsDriver, ModeObject, RcModeObject}; +use crate::{ + alloc::KBox, + bindings, + drm::{device::Device, kms::UnregisteredKmsDevice}, + error::to_result, + init::Zeroable, + prelude::*, + private::Sealed, + types::{NotThreadSafe, Opaque}, +}; +use core::{ + marker::*, + mem, + ops::*, + ptr::{addr_of_mut, null_mut}, + stringify, +}; +use macros::{paste, pin_data}; + +/// A macro for generating our type ID enumerator. +macro_rules! declare_conn_types { + ($( $oldname:ident as $newname:ident ),+) => { + /// An enumerator for all possible [`Connector`] type IDs. + #[repr(i32)] + #[non_exhaustive] + #[derive(Copy, Clone, PartialEq, Eq)] + pub enum Type { + // Note: bindgen defaults the macro values to u32 and not i32, but DRM takes them as an + // i32 - so just do the conversion here + $( + #[doc = concat!("The connector type ID for a ", stringify!($newname), " connector.")] + $newname = paste!(crate::bindings::[]) as i32 + ),+, + + // 9PinDIN is special because of the 9, making it an invalid ident. Just define it here + // manually since it's the only one + + /// The connector type ID for a 9PinDIN connector. + _9PinDin = crate::bindings::DRM_MODE_CONNECTOR_9PinDIN as i32 + } + }; +} + +declare_conn_types! { + Unknown as Unknown, + Composite as Composite, + Component as Component, + DisplayPort as DisplayPort, + VGA as Vga, + DVII as DviI, + DVID as DviD, + DVIA as DviA, + SVIDEO as SVideo, + LVDS as Lvds, + HDMIA as HdmiA, + HDMIB as HdmiB, + TV as Tv, + eDP as Edp, + VIRTUAL as Virtual, + DSI as Dsi, + DPI as Dpi, + WRITEBACK as Writeback, + SPI as Spi, + USB as Usb +} + +/// The main trait for implementing the [`struct drm_connector`] API for [`Connector`]. +/// +/// Any KMS driver should have at least one implementation of this type, which allows them to create +/// [`Connector`] objects. Additionally, a driver may store driver-private data within the type that +/// implements [`DriverConnector`] - and it will be made available when using a fully typed +/// [`Connector`] object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_connector`] pointers are contained within a [`Connector`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_connector_state`] pointers are contained within a +/// [`ConnectorState`]. +/// +/// [`struct drm_connector`]: srctree/include/drm/drm_connector.h +/// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h +#[vtable] +pub trait DriverConnector: Send + Sync + Sized { + /// The generated C vtable for this [`DriverConnector`] implementation + #[unique] + const OPS: &'static DriverConnectorOps = &DriverConnectorOps { + funcs: bindings::drm_connector_funcs { + dpms: None, + atomic_get_property: None, + atomic_set_property: None, + early_unregister: None, + late_register: None, + set_property: None, + reset: Some(connector_reset_callback::), + atomic_print_state: None, + atomic_destroy_state: Some(atomic_destroy_state_callback::), + destroy: Some(connector_destroy_callback::), + force: None, + detect: None, + fill_modes: None, + debugfs_init: None, + oob_hotplug_event: None, + atomic_duplicate_state: Some(atomic_duplicate_state_callback::), + }, + helper_funcs: bindings::drm_connector_helper_funcs { + mode_valid: None, + atomic_check: None, + get_modes: None, + detect_ctx: None, + enable_hpd: None, + disable_hpd: None, + best_encoder: None, + atomic_commit: None, + mode_valid_ctx: None, + atomic_best_encoder: None, + prepare_writeback_job: None, + cleanup_writeback_job: None, + }, + }; + + /// The type to pass to the `args` field of [`UnregisteredConnector::new`]. + /// + /// This type will be made available in in the `args` argument of [`Self::new`]. Drivers which + /// don't need this can simply pass [`()`] here. + type Args; + + /// The parent [`KmsDriver`] implementation. + type Driver: KmsDriver; + + /// The [`DriverConnectorState`] implementation for this [`DriverConnector`]. + /// + /// See [`DriverConnectorState`] for more info. + type State: DriverConnectorState; + + /// The constructor for creating a [`Connector`] using this [`DriverConnector`] implementation. + /// + /// Drivers may use this to instantiate their [`DriverConnector`] object. + fn new(device: &Device, args: Self::Args) -> impl PinInit; +} + +/// The generated C vtable for a [`DriverConnector`]. +/// +/// This type is created internally by DRM. +pub struct DriverConnectorOps { + funcs: bindings::drm_connector_funcs, + helper_funcs: bindings::drm_connector_helper_funcs, +} + +/// The main interface for a [`struct drm_connector`]. +/// +/// This type is the main interface for dealing with DRM connectors. In addition, it also allows +/// immutable access to whatever private data is contained within an implementor's +/// [`DriverConnector`] type. +/// +/// # Invariants +/// +/// - The DRM C API and our interface guarantees that only the user has mutable access to `state`, +/// up until [`drm_atomic_helper_commit_hw_done`] is called. Therefore, `connector` follows rust's +/// data aliasing rules and does not need to be behind an [`Opaque`] type. +/// - `connector` and `inner` are initialized for as long as this object is made available to users. +/// - The data layout of this structure begins with [`struct drm_connector`]. +/// - The atomic state for this type can always be assumed to be of type +/// [`ConnectorState`]. +/// +/// [`struct drm_connector`]: srctree/include/drm/drm_connector.h +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_helper.h +#[repr(C)] +#[pin_data] +pub struct Connector { + connector: Opaque, + #[pin] + inner: T, + #[pin] + _p: PhantomPinned, +} + +impl Sealed for Connector {} + +// SAFETY: DRM expects this struct to be zero-initialized +unsafe impl Zeroable for bindings::drm_connector {} + +impl Deref for Connector { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A trait implemented by any type that acts as a [`struct drm_connector`] interface. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// [`as_raw()`] must always return a pointer to a valid initialized [`struct drm_connector`]. +/// +/// [`as_raw()`]: AsRawConnector::as_raw() +/// [`struct drm_connector`]: srctree/include/drm/drm_connector.h +pub unsafe trait AsRawConnector { + /// Return the raw [`struct drm_connector`] for this DRM connector. + /// + /// Drivers should never use this directly + /// + /// [`struct drm_Connector`]: srctree/include/drm/drm_connector.h + fn as_raw(&self) -> *mut bindings::drm_connector; + + /// Convert a raw `bindings::drm_connector` pointer into an object of this type. + /// + /// # Safety + /// + /// Callers promise that `ptr` points to a valid instance of this type. + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_connector) -> &'a Self; +} + +/// A supertrait of [`AsRawConnector`] for [`struct drm_connector`] interfaces that can perform +/// modesets. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// Any object implementing this trait must only be made directly available to the user after +/// [`create_objects`] has completed. +/// +/// [`struct drm_connector`]: srctree/include/drm/drm_connector.h +/// [`create_objects`]: KmsDriver::create_objects +pub unsafe trait ModesettableConnector: AsRawConnector { + /// The type that should be returned for a plane state acquired using this plane interface + type State: FromRawConnectorState; +} + +// SAFETY: Our connector interfaces are guaranteed to be thread-safe +unsafe impl Send for Connector {} + +// SAFETY: Our connector interfaces are guaranteed to be thread-safe +unsafe impl Sync for Connector {} + +// SAFETY: We don't expose Connector to users before `base` is initialized in ::new(), so +// `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_object. +unsafe impl ModeObject for Connector { + type Driver = T::Driver; + + fn drm_dev(&self) -> &Device { + // SAFETY: The parent device for a DRM connector will never outlive the connector, and this + // pointer is invariant through the lifetime of the connector + unsafe { Device::borrow((*self.as_raw()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose DRM connectors to users before `base` is initialized + unsafe { addr_of_mut!((*self.as_raw()).base) } + } +} + +// SAFETY: DRM connectors are refcounted mode objects +unsafe impl RcModeObject for Connector {} + +// SAFETY: +// * Via our type variants our data layout starts with `drm_connector` +// * Since we don't expose `Connector` to users before it has been initialized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `drm_connector`. +unsafe impl AsRawConnector for Connector { + fn as_raw(&self) -> *mut bindings::drm_connector { + self.connector.get() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_connector) -> &'a Self { + // SAFETY: Our data layout starts with `bindings::drm_connector` + unsafe { &*ptr.cast() } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::create_objects has been +// called. +unsafe impl ModesettableConnector for Connector { + type State = ConnectorState; +} + +/// A [`Connector`] that has not yet been registered with userspace. +/// +/// KMS registration is single-threaded, so this object is not thread-safe. +/// +/// # Invariants +/// +/// - This object can only exist before its respective KMS device has been registered. +/// - Otherwise, it inherits all invariants of [`Connector`] and has an identical data layout. +pub struct UnregisteredConnector(Connector, NotThreadSafe); + +// SAFETY: We share the invariants of `Connector` +unsafe impl AsRawConnector for UnregisteredConnector { + fn as_raw(&self) -> *mut bindings::drm_connector { + self.0.as_raw() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_connector) -> &'a Self { + // SAFETY: This is another from_raw() call, so this function shares the same safety contract + let connector = unsafe { Connector::::from_raw(ptr) }; + + // SAFETY: Our data layout is identical via our type invariants. + unsafe { mem::transmute(connector) } + } +} + +impl Deref for UnregisteredConnector { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +impl UnregisteredConnector { + /// Construct a new [`UnregisteredConnector`]. + /// + /// A driver may use this to create new [`UnregisteredConnector`] objects. + /// + /// [`KmsDriver::create_objects`]: kernel::drm::kms::KmsDriver::create_objects + pub fn new<'a>( + dev: &'a UnregisteredKmsDevice<'a, T::Driver>, + type_: Type, + args: T::Args, + ) -> Result<&'a Self> { + let new: Pin>> = KBox::try_pin_init( + try_pin_init!(Connector:: { + connector: Opaque::new(bindings::drm_connector { + helper_private: &T::OPS.helper_funcs, + ..Default::default() + }), + inner <- T::new(dev, args), + _p: PhantomPinned + }), + GFP_KERNEL, + )?; + + // SAFETY: + // - `dev` will hold a reference to the new connector, and thus outlives us. + // - We just allocated `new` above + // - `new` starts with `drm_connector` via its type invariants. + to_result(unsafe { + bindings::drm_connector_init(dev.as_raw(), new.as_raw(), &T::OPS.funcs, type_ as i32) + })?; + + // SAFETY: We don't move anything + let this = unsafe { Pin::into_inner_unchecked(new) }; + + // We'll re-assemble the box in connector_destroy_callback() + let this = KBox::into_raw(this); + + // UnregisteredConnector has an equivalent data layout + let this: *mut Self = this.cast(); + + // SAFETY: We just allocated the connector above, so this pointer must be valid + Ok(unsafe { &*this }) + } +} + +unsafe extern "C" fn connector_destroy_callback( + connector: *mut bindings::drm_connector, +) { + // SAFETY: DRM guarantees that `connector` points to a valid initialized `drm_connector`. + unsafe { + bindings::drm_connector_unregister(connector); + bindings::drm_connector_cleanup(connector); + }; + + // SAFETY: + // - We originally created the connector in a `Box` + // - We are guaranteed to hold the last remaining reference to this connector + // - This cast is safe via `DriverConnector`s type invariants. + drop(unsafe { KBox::from_raw(connector as *mut Connector) }); +} + +// SAFETY: DRM expects this struct to be zero-initialized +unsafe impl Zeroable for bindings::drm_connector_state {} + +/// A trait implemented by any type which can produce a reference to a +/// [`struct drm_connector_state`]. +/// +/// This is implemented internally by DRM. +/// +/// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h +pub trait AsRawConnectorState: private::AsRawConnectorState { + /// The type that represents this connector state's DRM connector. + type Connector: AsRawConnector; +} + +pub(super) mod private { + use super::*; + + /// Trait for retrieving references to the base connector state contained within any connector + /// state compatible type + #[allow(unreachable_pub)] + pub trait AsRawConnectorState { + /// Return an immutable reference to the raw connector state. + fn as_raw(&self) -> &bindings::drm_connector_state; + + /// Get a mutable reference to the raw [`struct drm_connector_state`] contained within this + /// type. + /// + /// + /// # Safety + /// + /// The caller promises this mutable reference will not be used to modify any contents of + /// [`struct drm_connector_state`] which DRM would consider to be static - like the + /// backpointer to the DRM connector that owns this state. This also means the mutable + /// reference should never be exposed outside of this crate. + /// + /// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_connector_state; + } +} + +pub(super) use private::AsRawConnectorState as AsRawConnectorStatePrivate; + +/// A trait implemented for any type which can be constructed directly from a +/// [`struct drm_connector_state`] pointer. +/// +/// This is implemented internally by DRM. +/// +/// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h +pub trait FromRawConnectorState: AsRawConnectorState { + /// Get an immutable reference to this type from the given raw [`struct drm_connector_state`] + /// pointer. + /// + /// # Safety + /// + /// - The caller guarantees `ptr` is contained within a valid instance of `Self`. + /// - The caller guarantees that `ptr` cannot not be modified for the lifetime of `'a`. + /// + /// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h + unsafe fn from_raw<'a>(ptr: *const bindings::drm_connector_state) -> &'a Self; + + /// Get a mutable reference to this type from the given raw [`struct drm_connector_state`] + /// pointer. + /// + /// # Safety + /// + /// - The caller guarantees that `ptr` is contained within a valid instance of `Self`. + /// - The caller guarantees that `ptr` cannot have any other references taken out for the + /// lifetime of `'a`. + /// + /// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h + unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_connector_state) -> &'a mut Self; +} + +/// The main interface for a [`struct drm_connector_state`]. +/// +/// This type is the main interface for dealing with the atomic state of DRM connectors. In +/// addition, it allows access to whatever private data is contained within an implementor's +/// [`DriverConnectorState`] type. +/// +/// # Invariants +/// +/// - The DRM C API and our interface guarantees that only the user has mutable access to `state`, +/// up until [`drm_atomic_helper_commit_hw_done`] is called. Therefore, `connector` follows rust's +/// data aliasing rules and does not need to be behind an [`Opaque`] type. +/// - `state` and `inner` initialized for as long as this object is exposed to users. +/// - The data layout of this structure begins with [`struct drm_connector_state`]. +/// - The connector for this atomic state can always be assumed to be of type +/// [`Connector`]. +/// +/// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_helper.h +#[derive(Default)] +#[repr(C)] +pub struct ConnectorState { + state: bindings::drm_connector_state, + inner: T, +} + +/// The main trait for implementing the [`struct drm_connector_state`] API for a [`Connector`]. +/// +/// A driver may store driver-private data within the implementor's type, which will be available +/// when using a full typed [`ConnectorState`] object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_connector`] pointers are contained within a [`Connector`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_connector_state`] pointers are contained within a [`ConnectorState`]. +/// +/// [`struct drm_connector`]: srctree/include/drm_connector.h +/// [`struct drm_connector_state`]: srctree/include/drm_connector.h +pub trait DriverConnectorState: Clone + Default + Sized { + /// The parent [`DriverConnector`]. + type Connector: DriverConnector; +} + +impl Sealed for ConnectorState {} + +impl AsRawConnectorState for ConnectorState { + type Connector = Connector; +} + +impl private::AsRawConnectorState for ConnectorState { + fn as_raw(&self) -> &bindings::drm_connector_state { + &self.state + } + + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_connector_state { + &mut self.state + } +} + +impl FromRawConnectorState for ConnectorState { + unsafe fn from_raw<'a>(ptr: *const bindings::drm_connector_state) -> &'a Self { + // Our data layout starts with `bindings::drm_connector_state`. + let ptr: *const Self = ptr.cast(); + + // SAFETY: + // - Our safety contract requires that `ptr` be contained within `Self`. + // - Our safety contract requires the caller ensure that it is safe for us to take an + // immutable reference. + unsafe { &*ptr } + } + + unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_connector_state) -> &'a mut Self { + // Our data layout starts with `bindings::drm_connector_state`. + let ptr: *mut Self = ptr.cast(); + + // SAFETY: + // - Our safety contract requires that `ptr` be contained within `Self`. + // - Our safety contract requires the caller ensure it is safe for us to take a mutable + // reference. + unsafe { &mut *ptr } + } +} + +unsafe extern "C" fn atomic_duplicate_state_callback( + connector: *mut bindings::drm_connector, +) -> *mut bindings::drm_connector_state { + // SAFETY: DRM guarantees that `connector` points to a valid initialized `drm_connector`. + let state = unsafe { (*connector).state }; + if state.is_null() { + return null_mut(); + } + + // SAFETY: + // - We just verified that `state` is non-null + // - This cast is guaranteed to be safe via our type invariants. + let state = unsafe { ConnectorState::::from_raw(state) }; + + let new = Box::try_init( + try_init!(ConnectorState:: { + state: bindings::drm_connector_state { + ..Default::default() + }, + inner: state.inner.clone() + }), + GFP_KERNEL, + ); + + if let Ok(mut new) = new { + // SAFETY: + // - `new` provides a valid pointer to a newly allocated `drm_plane_state` via type + // invariants + // - This initializes `new` via memcpy() + unsafe { + bindings::__drm_atomic_helper_connector_duplicate_state(connector, new.as_raw_mut()) + }; + + KBox::into_raw(new).cast() + } else { + null_mut() + } +} + +unsafe extern "C" fn atomic_destroy_state_callback( + _connector: *mut bindings::drm_connector, + connector_state: *mut bindings::drm_connector_state, +) { + // SAFETY: DRM guarantees that `state` points to a valid instance of `drm_connector_state` + unsafe { bindings::__drm_atomic_helper_connector_destroy_state(connector_state) }; + + // SAFETY: + // - DRM guarantees we are the only one with access to this `drm_connector_state` + // - This cast is safe via our type invariants. + drop(unsafe { KBox::from_raw(connector_state.cast::>()) }); +} + +unsafe extern "C" fn connector_reset_callback( + connector: *mut bindings::drm_connector, +) { + // SAFETY: DRM guarantees that `state` points to a valid instance of `drm_connector_state` + let state = unsafe { (*connector).state }; + if !state.is_null() { + // SAFETY: + // - We're guaranteed `connector` is `Connector` via type invariants + // - We're guaranteed `state` is `ConnectorState` via type invariants. + unsafe { atomic_destroy_state_callback::(connector, state) } + + // SAFETY: No special requirements here, DRM expects this to be NULL + unsafe { (*connector).state = null_mut() }; + } + + // Unfortunately, this is the best we can do at the moment as this FFI callback was mistakenly + // presumed to be infallible :( + let new = KBox::new(ConnectorState::::default(), GFP_KERNEL).expect("Blame the API, sorry!"); + + // DRM takes ownership of the state from here, resets it, and then assigns it to the connector + // SAFETY: + // - DRM guarantees that `connector` points to a valid instance of `drm_connector`. + // - The cast to `drm_connector_state` is safe via `ConnectorState`s type invariants. + unsafe { bindings::__drm_atomic_helper_connector_reset(connector, Box::into_raw(new).cast()) }; +} From patchwork Wed Mar 5 22:59:21 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003609 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 85312C19F32 for ; Wed, 5 Mar 2025 23:05:26 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id E123A10E7F9; Wed, 5 Mar 2025 23:05:25 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="DACNdJWH"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id EEA1E10E7E6 for ; Wed, 5 Mar 2025 23:05:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215923; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=sl6/ekVgAWYCDTBSA2VtxkimLd/n9xlZs5F5lrJfmw0=; b=DACNdJWHL5hwYZXy1A0q9MiRmZNJU6E0sVpuP/PMZuUIs3VlioRGdRcZEW9ldTF35n4ZTy T0VlbyN/ssqCtvEC6rccmHsVtPmbkkPwTU0cxJvmTqZg/fxpZ4briBw6RlKuXlRsdGLipX SD1XPEANi1xS4eX7gcCE8J0bo0Hirs4= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-689-_WqexueYPgqrl-lh9vpcxw-1; Wed, 05 Mar 2025 18:05:18 -0500 X-MC-Unique: _WqexueYPgqrl-lh9vpcxw-1 X-Mimecast-MFC-AGG-ID: _WqexueYPgqrl-lh9vpcxw_1741215916 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3D7181955DB9; Wed, 5 Mar 2025 23:05:16 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 1D9B8300019E; Wed, 5 Mar 2025 23:05:10 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Greg Kroah-Hartman , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 05/33] rust: drm/kms: Add drm_plane bindings Date: Wed, 5 Mar 2025 17:59:21 -0500 Message-ID: <20250305230406.567126-6-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" The next step is adding a set of basic bindings to create a plane, which has to happen before we can create a CRTC (since we need to be able to at least specify a primary plane for a CRTC upon creation). This mostly follows the same general pattern as connectors (AsRawPlane, AsRawPlaneState, etc.). There is one major difference with planes vs. other types of atomic mode objects: drm_plane_state isn't the only base plane struct used in DRM drivers, as some drivers will use helpers like drm_shadow_plane_state which have a drm_plane_state embedded within them. Since we'll eventually be adding bindings for shadow planes, we introduce a PlaneStateHelper trait - which represents any data type which can be used as the main wrapping structure around a drm_plane_state - and we implement this trait for PlaneState. This trait can be used in our C callbacks to allow for drivers to use different wrapping structures without needing to implement a separate set of FFI callbacks for each type. Currently planes are the only type I'm aware of which do this. Signed-off-by: Lyude Paul --- V2: * Start using Gerry Guo's updated #[vtable] function so that our driver operations table has a static location in memory V3: * Add safety comment for implementation of ModeObject * Make AsRawPlane unsafe, since we need a guarantee that `as_raw()` always returns a valid pointer to an initialized drm_plane. * Add comments to __drm_atomic_helper_duplicate_state() * Switch `PlaneType` to camel casing * Improve safety comment in `Plane::::new()` * Fix parameter types for `formats` and `format_modifiers`, as pointed out by Louis Chauvet DRM will copy all of these into its own storage. * Improve safety comments in FromRawPlaneState * Introduce UnregisteredPlane type * Don't have AsRawPlane be a supertrait of StaticModeObject. We don't want Unregistered mode object variants to be able to return a pointer to the DRM device since that would break the UnregisteredKmsDevice pattern. * Change name of PlaneType to Type (for consistency with the other type IDs we've adde) * Use addr_of_mut! in more places instead of &mut Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 2 + rust/kernel/drm/fourcc.rs | 1 - rust/kernel/drm/kms.rs | 1 + rust/kernel/drm/kms/plane.rs | 621 ++++++++++++++++++++++++++++++++ 4 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 rust/kernel/drm/kms/plane.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index c41a3309223b2..5b85f3faca525 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/fourcc.rs b/rust/kernel/drm/fourcc.rs index 62203478b5955..a30e40dbc037c 100644 --- a/rust/kernel/drm/fourcc.rs +++ b/rust/kernel/drm/fourcc.rs @@ -11,7 +11,6 @@ const fn fourcc_code(a: u8, b: u8, c: u8, d: u8) -> u32 { // TODO: We manually import this because we don't have a reasonable way of getting constants from // function-like macros in bindgen yet. -#[allow(dead_code)] pub(crate) const FORMAT_MOD_INVALID: u64 = 0xffffffffffffff; // TODO: We need to automate importing all of these. For the time being, just add the single one diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index f10e9f83ccb78..6cc5bb53f3628 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -3,6 +3,7 @@ //! KMS driver abstractions for rust. pub mod connector; +pub mod plane; use crate::{ device, diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs new file mode 100644 index 0000000000000..9f262156eac6c --- /dev/null +++ b/rust/kernel/drm/kms/plane.rs @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM display planes. +//! +//! C header: [`include/drm/drm_plane.h`](srctree/include/drm/drm_plane.h) + +use super::{KmsDriver, ModeObject, StaticModeObject, UnregisteredKmsDevice}; +use crate::{ + alloc::KBox, + bindings, + drm::{device::Device, fourcc::*}, + error::{to_result, Error}, + init::Zeroable, + prelude::*, + private::Sealed, + types::{NotThreadSafe, Opaque}, +}; +use core::{ + marker::*, + mem, + ops::*, + pin::Pin, + ptr::{addr_of_mut, null, null_mut}, +}; +use macros::pin_data; + +/// The main trait for implementing the [`struct drm_plane`] API for [`Plane`]. +/// +/// Any KMS driver should have at least one implementation of this type, which allows them to create +/// [`Plane`] objects. Additionally, a driver may store driver-private data within the type that +/// implements [`DriverPlane`] - and it will be made available when using a fully typed [`Plane`] +/// object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_plane`] pointers are contained within a [`Plane`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_plane_state`] pointers are contained within a [`PlaneState`]. +/// +/// [`struct drm_plane`]: srctree/include/drm/drm_plane.h +/// [`struct drm_plane_state`]: srctree/include/drm/drm_plane.h +#[vtable] +pub trait DriverPlane: Send + Sync + Sized { + /// The generated C vtable for this [`DriverPlane`] implementation. + #[unique] + const OPS: &'static DriverPlaneOps = &DriverPlaneOps { + funcs: bindings::drm_plane_funcs { + update_plane: Some(bindings::drm_atomic_helper_update_plane), + disable_plane: Some(bindings::drm_atomic_helper_disable_plane), + destroy: Some(plane_destroy_callback::), + reset: Some(plane_reset_callback::), + set_property: None, + atomic_duplicate_state: Some(atomic_duplicate_state_callback::), + atomic_destroy_state: Some(atomic_destroy_state_callback::), + atomic_set_property: None, + atomic_get_property: None, + late_register: None, + early_unregister: None, + atomic_print_state: None, + format_mod_supported: None, + }, + + helper_funcs: bindings::drm_plane_helper_funcs { + prepare_fb: None, + cleanup_fb: None, + begin_fb_access: None, + end_fb_access: None, + atomic_check: None, + atomic_update: None, + atomic_enable: None, + atomic_disable: None, + atomic_async_check: None, + atomic_async_update: None, + panic_flush: None, + get_scanout_buffer: None, + }, + }; + + /// The type to pass to the `args` field of [`UnregisteredPlane::new`]. + /// + /// This type will be made available in in the `args` argument of [`Self::new`]. Drivers which + /// don't need this can simply pass [`()`] here. + type Args; + + /// The parent [`KmsDriver`] implementation. + type Driver: KmsDriver; + + /// The [`DriverPlaneState`] implementation for this [`DriverPlane`]. + /// + /// See [`DriverPlaneState`] for more info. + type State: DriverPlaneState; + + /// The constructor for creating a [`Plane`] using this [`DriverPlane`] implementation. + /// + /// Drivers may use this to instantiate their [`DriverPlane`] object. + fn new(device: &Device, args: Self::Args) -> impl PinInit; +} + +/// The generated C vtable for a [`DriverPlane`]. +/// +/// This type is created internally by DRM. +pub struct DriverPlaneOps { + funcs: bindings::drm_plane_funcs, + helper_funcs: bindings::drm_plane_helper_funcs, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +/// An enumerator describing a type of [`Plane`]. +/// +/// This is mainly just relevant for DRM legacy drivers. +/// +/// # Invariants +/// +/// This type is identical to [`enum drm_plane_type`]. +/// +/// [`enum drm_plane_type`]: srctree/include/drm/drm_plane.h +pub enum Type { + /// Overlay planes represent all non-primary, non-cursor planes. Some drivers refer to these + /// types of planes as "sprites" internally. + Overlay = bindings::drm_plane_type_DRM_PLANE_TYPE_OVERLAY, + + /// A primary plane attached to a CRTC that is the most likely to be able to light up the CRTC + /// when no scaling/cropping is used, and the plane covers the whole CRTC. + Primary = bindings::drm_plane_type_DRM_PLANE_TYPE_PRIMARY, + + /// A cursor plane attached to a CRTC that is more likely to be enabled when no scaling/cropping + /// is used, and the framebuffer has the size indicated by [`ModeConfigInfo::max_cursor`]. + /// + /// [`ModeConfigInfo::max_cursor`]: crate::drm::kms::ModeConfigInfo + Cursor = bindings::drm_plane_type_DRM_PLANE_TYPE_CURSOR, +} + +/// The main interface for a [`struct drm_plane`]. +/// +/// This type is the main interface for dealing with DRM planes. In addition, it also allows +/// immutable access to whatever private data is contained within an implementor's [`DriverPlane`] +/// type. +/// +/// # Invariants +/// +/// - `plane` and `inner` are initialized for as long as this object is made available to users. +/// - The data layout of this structure begins with [`struct drm_plane`]. +/// - The atomic state for this type can always be assumed to be of type [`PlaneState`]. +/// +/// [`struct drm_plane`]: srctree/include/drm/drm_plane.h +#[repr(C)] +#[pin_data] +pub struct Plane { + /// The FFI drm_plane object + plane: Opaque, + /// The driver's private inner data + #[pin] + inner: T, + #[pin] + _p: PhantomPinned, +} + +unsafe impl Zeroable for bindings::drm_plane {} + +impl Sealed for Plane {} + +impl Deref for Plane { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A [`Plane`] that has not yet been registered with userspace. +/// +/// KMS registration is single-threaded, so this object is not thread-safe. +/// +/// # Invariants +/// +/// - This object can only exist before its respective KMS device has been registered. +/// - Otherwise, it inherits all invariants of [`Plane`] and has an identical data layout. +pub struct UnregisteredPlane(Plane, NotThreadSafe); + +// SAFETY: We share the invariants of `Plane` +unsafe impl AsRawPlane for UnregisteredPlane { + fn as_raw(&self) -> *mut bindings::drm_plane { + self.0.as_raw() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_plane) -> &'a Self { + // SAFETY: This is another from_raw() call, so this function shares the same safety contract + let plane = unsafe { Plane::::from_raw(ptr) }; + + // SAFETY: Our data layout is identical via our type invariants. + unsafe { mem::transmute(plane) } + } +} + +impl Deref for UnregisteredPlane { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +impl UnregisteredPlane { + /// Construct a new [`UnregisteredPlane`]. + /// + /// A driver may use this from their [`KmsDriver::create_objects`] callback in order to + /// construct new [`UnregisteredPlane`] objects. + /// + /// [`KmsDriver::create_objects`]: kernel::drm::kms::KmsDriver::create_objects + pub fn new<'a, 'b: 'a>( + dev: &'a UnregisteredKmsDevice<'a, T::Driver>, + possible_crtcs: u32, + formats: &[u32], + format_modifiers: Option<&[u64]>, + type_: Type, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'b Self> { + let this: Pin>> = KBox::try_pin_init( + try_pin_init!(Plane:: { + plane: Opaque::new(bindings::drm_plane { + helper_private: &T::OPS.helper_funcs, + ..Default::default() + }), + inner <- T::new(dev, args), + _p: PhantomPinned + }), + GFP_KERNEL, + )?; + + // TODO: Move this over to using collect() someday + // Create a modifiers array with the sentinel for passing to DRM + let format_modifiers_raw; + if let Some(modifiers) = format_modifiers { + let mut raw = KVec::with_capacity(modifiers.len() + 1, GFP_KERNEL)?; + for modifier in modifiers { + raw.push(*modifier, GFP_KERNEL)?; + } + raw.push(FORMAT_MOD_INVALID, GFP_KERNEL)?; + + format_modifiers_raw = Some(raw); + } else { + format_modifiers_raw = None; + } + + // SAFETY: + // - `dev` handles destroying the plane, and thus will outlive us and always be valid. + // - We just allocated `this`, and we won't move it since it's pinned + // - We just allocated the `format_modifiers_raw` vec and added the sentinel DRM expects + // above + // - `drm_universal_plane_init` will memcpy() the following parameters into its own storage, + // so it's safe for them to become inaccessible after this call returns: + // - `formats` + // - `format_modifiers_raw` + // - `name` + // - `type_` is equivalent to `drm_plane_type` via its type invariants. + to_result(unsafe { + bindings::drm_universal_plane_init( + dev.as_raw(), + this.as_raw(), + possible_crtcs, + &T::OPS.funcs, + formats.as_ptr(), + formats.len() as _, + format_modifiers_raw.map_or(null(), |f| f.as_ptr()), + type_ as _, + name.map_or(null(), |n| n.as_char_ptr()), + ) + })?; + + // SAFETY: We don't move anything + let this = unsafe { Pin::into_inner_unchecked(this) }; + + // We'll re-assemble the box in plane_destroy_callback() + let this = KBox::into_raw(this); + + // UnregisteredPlane has an equivalent data layout + let this: *mut Self = this.cast(); + + // SAFETY: We just allocated the plane above, so this pointer must be valid + Ok(unsafe { &*this }) + } +} + +/// A trait implemented by any type that acts as a [`struct drm_plane`] interface. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// [`as_raw()`] must always return a valid pointer to an initialized [`struct drm_plane`]. +/// +/// [`struct drm_plane`]: srctree/include/drm/drm_plane.h +/// [`as_raw()`]: AsRawPlane::as_raw() +pub unsafe trait AsRawPlane { + /// Return the raw `bindings::drm_plane` for this DRM plane. + /// + /// Drivers should never use this directly. + fn as_raw(&self) -> *mut bindings::drm_plane; + + /// Convert a raw `bindings::drm_plane` pointer into an object of this type. + /// + /// # Safety + /// + /// Callers promise that `ptr` points to a valid instance of this type + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_plane) -> &'a Self; +} + +// SAFETY: +// - Via our type variants our data layout starts with `drm_plane` +// - Since we don't expose `plane` to users before it has been initialized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `drm_plane`. +unsafe impl AsRawPlane for Plane { + fn as_raw(&self) -> *mut bindings::drm_plane { + self.plane.get() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_plane) -> &'a Self { + // Our data layout start with `bindings::drm_plane`. + let ptr: *mut Self = ptr.cast(); + + // SAFETY: Our safety contract requires that `ptr` point to a valid intance of `Self`. + unsafe { &*ptr } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::create_objects has been +// called. +unsafe impl ModesettablePlane for Plane { + type State = PlaneState; +} + +// SAFETY: We don't expose Plane to users before `base` is initialized in ::new(), so +// `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_object. +unsafe impl ModeObject for Plane { + type Driver = T::Driver; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM planes exist for as long as the device does, so this pointer is always valid + unsafe { Device::borrow((*self.as_raw()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose DRM planes to users before `base` is initialized + unsafe { addr_of_mut!((*self.as_raw()).base) } + } +} + +// SAFETY: Planes do not have a refcount +unsafe impl StaticModeObject for Plane {} + +// SAFETY: Our interface is thread-safe. +unsafe impl Send for Plane {} + +// SAFETY: Our interface is thread-safe. +unsafe impl Sync for Plane {} + +/// A supertrait of [`AsRawPlane`] for [`struct drm_plane`] interfaces that can perform modesets. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// Any object implementing this trait must only be made directly available to the user after +/// [`create_objects`] has completed. +/// +/// [`struct drm_plane`]: srctree/include/drm/drm_plane.h +/// [`create_objects`]: KmsDriver::create_objects +pub unsafe trait ModesettablePlane: AsRawPlane { + /// The type that should be returned for a plane state acquired using this plane interface + type State: FromRawPlaneState; +} + +/// A trait implemented by any type which can produce a reference to a [`struct drm_plane_state`]. +/// +/// This is implemented internally by DRM. +/// +/// [`struct drm_plane_state`]: srctree/include/drm/drm_plane.h +pub trait AsRawPlaneState: private::AsRawPlaneState { + /// The type that this plane state interface returns to represent the parent DRM plane + type Plane: ModesettablePlane; +} + +pub(crate) mod private { + /// Trait for retrieving references to the base plane state contained within any plane state + /// compatible type + #[allow(unreachable_pub)] + pub trait AsRawPlaneState { + /// Return an immutable reference to the raw plane state + fn as_raw(&self) -> &bindings::drm_plane_state; + + /// Get a mutable reference to the raw `bindings::drm_plane_state` contained within this + /// type. + /// + /// # Safety + /// + /// The caller promises this mutable reference will not be used to modify any contents of + /// `bindings::drm_plane_state` which DRM would consider to be static - like the backpointer + /// to the DRM plane that owns this state. This also means the mutable reference should + /// never be exposed outside of this crate. + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_plane_state; + } +} + +pub(crate) use private::AsRawPlaneState as AsRawPlaneStatePrivate; + +/// A trait implemented for any type which can be constructed directly from a +/// [`struct drm_plane_state`] pointer. +/// +/// This is implemented internally by DRM. +/// +/// [`struct drm_plane_state`]: srctree/include/drm/drm_plane.h +pub trait FromRawPlaneState: AsRawPlaneState { + /// Get an immutable reference to this type from the given raw [`struct drm_plane_state`] + /// pointer. + /// + /// # Safety + /// + /// - The caller guarantees `ptr` is contained within a valid instance of `Self` + /// - The caller guarantees that `ptr` cannot not be modified for the lifetime of `'a`. + /// + /// [`struct drm_plane_state`]: srctree/include/drm/drm_plane.h + unsafe fn from_raw<'a>(ptr: *const bindings::drm_plane_state) -> &'a Self; + + /// Get a mutable reference to this type from the given raw [`struct drm_plane_state`] pointer. + /// + /// # Safety + /// + /// - The caller guarantees that `ptr` is contained within a valid instance of `Self` + /// - The caller guarantees that `ptr` cannot have any other references taken out for the + /// lifetime of `'a`. + /// + /// [`struct drm_plane_state`]: srctree/include/drm/drm_plane.h + unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_plane_state) -> &'a mut Self; +} + +/// The main interface for a [`struct drm_plane_state`]. +/// +/// This type is the main interface for dealing with the atomic state of DRM planes. In addition, it +/// allows access to whatever private data is contained within an implementor's [`DriverPlaneState`] +/// type. +/// +/// # Invariants +/// +/// - The DRM C API and our interface guarantees that only the user has mutable access to `state`, +/// up until [`drm_atomic_helper_commit_hw_done`] is called. Therefore, `plane` follows rust's +/// data aliasing rules and does not need to be behind an [`Opaque`] type. +/// - `state` and `inner` initialized for as long as this object is exposed to users. +/// - The data layout of this structure begins with [`struct drm_plane_state`]. +/// - The plane for this atomic state can always be assumed to be of type [`Plane`]. +/// +/// [`struct drm_plane_state`]: srctree/include/drm/drm_plane.h +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_helper.h +#[derive(Default)] +#[repr(C)] +pub struct PlaneState { + state: bindings::drm_plane_state, + inner: T, +} + +/// The main trait for implementing the [`struct drm_plane_state`] API for a [`Plane`]. +/// +/// A driver may store driver-private data within the implementor's type, which will be available +/// when using a full typed [`PlaneState`] object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_plane`] pointers are contained within a [`Plane`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_plane_state`] pointers are contained within a [`PlaneState`]. +/// +/// [`struct drm_plane`]: srctree/include/drm_plane.h +/// [`struct drm_plane_state`]: srctree/include/drm_plane.h +pub trait DriverPlaneState: Clone + Default + Sized { + /// The type for this driver's drm_plane implementation + type Plane: DriverPlane; +} + +impl Sealed for PlaneState {} + +impl AsRawPlaneState for PlaneState { + type Plane = Plane; +} + +impl private::AsRawPlaneState for PlaneState { + fn as_raw(&self) -> &bindings::drm_plane_state { + &self.state + } + + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_plane_state { + &mut self.state + } +} + +impl FromRawPlaneState for PlaneState { + unsafe fn from_raw<'a>(ptr: *const bindings::drm_plane_state) -> &'a Self { + // Our data layout starts with `bindings::drm_plane_state`. + let ptr: *const Self = ptr.cast(); + + // SAFETY: + // - Our safety contract requires that `ptr` be contained within `Self`. + // - Our safety contract requires the caller ensure that it is safe for us to take an + // immutable reference. + unsafe { &*ptr } + } + + unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_plane_state) -> &'a mut Self { + // Our data layout starts with `bindings::drm_plane_state`. + let ptr: *mut Self = ptr.cast(); + + // SAFETY: + // - Our safety contract requires that `ptr` be contained within `Self`. + // - Our safety contract requires the caller ensure it is safe for us to take a mutable + // reference. + unsafe { &mut *ptr } + } +} + +unsafe impl Zeroable for bindings::drm_plane_state {} + +impl Deref for PlaneState { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for PlaneState { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +unsafe extern "C" fn plane_destroy_callback(plane: *mut bindings::drm_plane) { + // SAFETY: DRM guarantees that `plane` points to a valid initialized `drm_plane`. + unsafe { bindings::drm_plane_cleanup(plane) }; + + // SAFETY: + // - DRM guarantees we are now the only one with access to this [`drm_plane`]. + // - This cast is safe via `DriverPlane`s type invariants. + drop(unsafe { KBox::from_raw(plane as *mut Plane) }); +} + +unsafe extern "C" fn atomic_duplicate_state_callback( + plane: *mut bindings::drm_plane, +) -> *mut bindings::drm_plane_state { + // SAFETY: DRM guarantees that `plane` points to a valid initialized `drm_plane`. + let state = unsafe { (*plane).state }; + if state.is_null() { + return null_mut(); + } + + // SAFETY: This cast is safe via `DriverPlaneState`s type invariants. + let state = unsafe { PlaneState::::from_raw(state) }; + + let new = KBox::try_init( + try_init!(PlaneState:: { + state: bindings::drm_plane_state { + ..Default::default() + }, + inner: state.inner.clone() + }), + GFP_KERNEL, + ); + + if let Ok(mut new) = new { + // SAFETY: + // - `new` provides a valid pointer to a newly allocated `drm_plane_state` via type + // invariants + // - This initializes `new` via memcpy() + unsafe { bindings::__drm_atomic_helper_plane_duplicate_state(plane, new.as_raw_mut()) }; + + KBox::into_raw(new).cast() + } else { + null_mut() + } +} + +unsafe extern "C" fn atomic_destroy_state_callback( + _plane: *mut bindings::drm_plane, + state: *mut bindings::drm_plane_state, +) { + // SAFETY: DRM guarantees that `state` points to a valid instance of `drm_plane_state` + unsafe { bindings::__drm_atomic_helper_plane_destroy_state(state) }; + + // SAFETY: + // * DRM guarantees we are the only one with access to this `drm_plane_state` + // * This cast is safe via our type invariants. + drop(unsafe { KBox::from_raw(state.cast::>()) }); +} + +unsafe extern "C" fn plane_reset_callback(plane: *mut bindings::drm_plane) { + // SAFETY: DRM guarantees that `state` points to a valid instance of `drm_plane_state` + let state = unsafe { (*plane).state }; + if !state.is_null() { + // SAFETY: + // - We're guaranteed `plane` is `Plane` via type invariants + // - We're guaranteed `state` is `PlaneState` via type invariants. + unsafe { atomic_destroy_state_callback::(plane, state) } + + // SAFETY: No special requirements here, DRM expects this to be NULL + unsafe { + (*plane).state = null_mut(); + } + } + + // Unfortunately, this is the best we can do at the moment as this FFI callback was mistakenly + // presumed to be infallible :( + let new = + KBox::new(PlaneState::::default(), GFP_KERNEL).expect("Blame the API, sorry!"); + + // DRM takes ownership of the state from here, resets it, and then assigns it to the plane + // SAFETY: + // - DRM guarantees that `plane` points to a valid instance of `drm_plane`. + // - The cast to `drm_plane_state` is safe via `PlaneState`s type invariants. + unsafe { bindings::__drm_atomic_helper_plane_reset(plane, KBox::into_raw(new).cast()) }; +} From patchwork Wed Mar 5 22:59:22 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003610 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 5B733C19F32 for ; Wed, 5 Mar 2025 23:05:44 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id C0E3D10E84B; Wed, 5 Mar 2025 23:05:43 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="AVsh+Oo+"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 287BB10E84B for ; Wed, 5 Mar 2025 23:05:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215941; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=o3hYAZMTN2jTPeOCbI/wI2H7Z/11t/pfZBKrXGMP/6Y=; b=AVsh+Oo+XMyG4Hb9/Hzy9xB2YWnLht31cCzxt4XQ8sCndSd9h76V516rJ3ACKYQLYjblM1 h6WWYdkrwhSjIPD8X91ovTe60kChi+xNkeAjpaGyVah0a1i+PdcbrJVKPnkW96/xDe7nBF NEUtV3vF8704sEDl062ItNLGq7aLAlQ= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-226-mpPo4-fhOxOK9YLqfb8UNA-1; Wed, 05 Mar 2025 18:05:32 -0500 X-MC-Unique: mpPo4-fhOxOK9YLqfb8UNA-1 X-Mimecast-MFC-AGG-ID: mpPo4-fhOxOK9YLqfb8UNA_1741215927 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C7FC019560B8; Wed, 5 Mar 2025 23:05:26 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 75C7E300019E; Wed, 5 Mar 2025 23:05:22 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 06/33] rust: drm/kms: Add drm_crtc bindings Date: Wed, 5 Mar 2025 17:59:22 -0500 Message-ID: <20250305230406.567126-7-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This introduces basic bindings for DRM CRTCs which follow the same general pattern as connectors and planes (e.g. AsRawCrtc, AsRawCrtcState, etc.). There is one big difference though - drm_crtc_state appears to be the one atomic state that actually has data which can be mutated from outside of the atomic commit phase - which means we can't keep rust referencs to it, and instead need to use the Opaque type and implement things through pointers instead. This should be the last mode object we're introducing for the time being with its own atomic state. Note that we've not added bindings for private modesetting objects yet, but I don't think those will be needed for rvkms - and the same general patterns we're using here should work for adding private modesetting objects. Signed-off-by: Lyude Paul --- TODO: * Add commit data in the future V3: * Add safety comments for ModeObject implementation * Make AsRawCrtc unsafe, as we need a guarantee that `as_raw()` will always return a pointer to a valid `drm_crtc`. * Update safety comments in atomic_duplicate_state_callback * Rename generics in Crtc::new to PrimaryData and CursorData * Add missing safety comment in Crtc::new() * Improve safety comments in AsRawCrtc * Break up the conversion from Pin> to &Crtc a bit * Document why there's an UnsafeCell in CrtcState, because even I forgot the reason for this :). * Introduce UnregisteredCrtc type * Don't have AsRawCrtc be a supertrait of StaticModeObject. We don't want Unregistered mode object variants to be able to return a pointer to the DRM device since that would break the UnregisteredKmsDevice pattern. Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 2 +- rust/kernel/drm/kms.rs | 1 + rust/kernel/drm/kms/crtc.rs | 577 ++++++++++++++++++++++++++++++++ 3 files changed, 579 insertions(+), 1 deletion(-) create mode 100644 rust/kernel/drm/kms/crtc.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 5b85f3faca525..551ddcf02ea0e 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 6cc5bb53f3628..f8d6d522c9d96 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -3,6 +3,7 @@ //! KMS driver abstractions for rust. pub mod connector; +pub mod crtc; pub mod plane; use crate::{ diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs new file mode 100644 index 0000000000000..95c79ffb584cd --- /dev/null +++ b/rust/kernel/drm/kms/crtc.rs @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM CRTCs. +//! +//! C header: [`include/drm/drm_crtc.h`](srctree/include/drm/drm_crtc.h) + +use super::{plane::*, KmsDriver, ModeObject, StaticModeObject, UnregisteredKmsDevice}; +use crate::{ + alloc::KBox, + bindings, + drm::device::Device, + error::to_result, + init::Zeroable, + prelude::*, + private::Sealed, + types::{NotThreadSafe, Opaque}, +}; +use core::{ + cell::UnsafeCell, + marker::*, + mem, + ops::{Deref, DerefMut}, + ptr::{addr_of_mut, null, null_mut}, +}; +use macros::vtable; + +/// The main trait for implementing the [`struct drm_crtc`] API for [`Crtc`]. +/// +/// Any KMS driver should have at least one implementation of this type, which allows them to create +/// [`Crtc`] objects. Additionally, a driver may store driver-private data within the type that +/// implements [`DriverCrtc`] - and it will be made available when using a fully typed [`Crtc`] +/// object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_crtc`] pointers are contained within a [`Crtc`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_crtc_state`] pointers are contained within a [`CrtcState`]. +/// +/// [`struct drm_crtc`]: srctree/include/drm/drm_crtc.h +/// [`struct drm_crtc_state`]: srctree/include/drm/drm_crtc.h +#[vtable] +pub trait DriverCrtc: Send + Sync + Sized { + /// The generated C vtable for this [`DriverCrtc`] implementation. + #[unique] + const OPS: &'static DriverCrtcOps = &DriverCrtcOps { + funcs: bindings::drm_crtc_funcs { + atomic_destroy_state: Some(atomic_destroy_state_callback::), + atomic_duplicate_state: Some(atomic_duplicate_state_callback::), + atomic_get_property: None, + atomic_print_state: None, + atomic_set_property: None, + cursor_move: None, + cursor_set2: None, + cursor_set: None, + destroy: Some(crtc_destroy_callback::), + disable_vblank: None, + early_unregister: None, + enable_vblank: None, + gamma_set: None, + get_crc_sources: None, + get_vblank_counter: None, + get_vblank_timestamp: None, + late_register: None, + page_flip: Some(bindings::drm_atomic_helper_page_flip), + page_flip_target: None, + reset: Some(crtc_reset_callback::), + set_config: Some(bindings::drm_atomic_helper_set_config), + set_crc_source: None, + set_property: None, + verify_crc_source: None, + }, + + helper_funcs: bindings::drm_crtc_helper_funcs { + atomic_disable: None, + atomic_enable: None, + atomic_check: None, + dpms: None, + commit: None, + prepare: None, + disable: None, + mode_set: None, + mode_valid: None, + mode_fixup: None, + atomic_begin: None, + atomic_flush: None, + mode_set_nofb: None, + mode_set_base: None, + mode_set_base_atomic: None, + get_scanout_position: None, + }, + }; + + /// The type to pass to the `args` field of [`UnregisteredCrtc::new`]. + /// + /// This type will be made available in in the `args` argument of [`Self::new`]. Drivers which + /// don't need this can simply pass [`()`] here. + type Args; + + /// The parent [`KmsDriver`] implementation. + type Driver: KmsDriver; + + /// The [`DriverCrtcState`] implementation for this [`DriverCrtc`]. + /// + /// See [`DriverCrtcState`] for more info. + type State: DriverCrtcState; + + /// The constructor for creating a [`Crtc`] using this [`DriverCrtc`] implementation. + /// + /// Drivers may use this to instantiate their [`DriverCrtc`] object. + fn new(device: &Device, args: &Self::Args) -> impl PinInit; +} + +/// The generated C vtable for a [`DriverCrtc`]. +/// +/// This type is created internally by DRM. +pub struct DriverCrtcOps { + funcs: bindings::drm_crtc_funcs, + helper_funcs: bindings::drm_crtc_helper_funcs, +} + +/// The main interface for a [`struct drm_crtc`]. +/// +/// This type is the main interface for dealing with DRM CRTCs. In addition, it also allows +/// immutable access to whatever private data is contained within an implementor's [`DriverCrtc`] +/// type. +/// +/// # Invariants +/// +/// - `crtc` and `inner` are initialized for as long as this object is made available to users. +/// - The data layout of this structure begins with [`struct drm_crtc`]. +/// - The atomic state for this type can always be assumed to be of type [`CrtcState`]. +/// +/// [`struct drm_crtc`]: srctree/include/drm/drm_crtc.h +#[repr(C)] +#[pin_data] +pub struct Crtc { + // The FFI drm_crtc object + crtc: Opaque, + /// The driver's private inner data + #[pin] + inner: T, + #[pin] + _p: PhantomPinned, +} + +// SAFETY: DRM expects this struct to be zero-initialized +unsafe impl Zeroable for bindings::drm_crtc {} + +impl Sealed for Crtc {} + +// SAFETY: Our CRTC interfaces are guaranteed to be thread-safe +unsafe impl Send for Crtc {} + +// SAFETY: Our CRTC interfaces are guaranteed to be thread-safe +unsafe impl Sync for Crtc {} + +impl Deref for Crtc { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// SAFETY: We don't expose Crtc to users before `base` is initialized in ::new(), so +// `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_object. +unsafe impl ModeObject for Crtc { + type Driver = T::Driver; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM connectors exist for as long as the device does, so this pointer is always + // valid + unsafe { Device::borrow((*self.as_raw()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose Crtc to users before it's initialized, so `base` is always + // initialized + unsafe { addr_of_mut!((*self.as_raw()).base) } + } +} + +// SAFETY: CRTCs are non-refcounted modesetting objects +unsafe impl StaticModeObject for Crtc {} + +/// A [`Crtc`] that has not yet been registered with userspace. +/// +/// KMS registration is single-threaded, so this object is not thread-safe. +/// +/// # Invariants +/// +/// - This object can only exist before its respective KMS device has been registered. +/// - Otherwise, it inherits all invariants of [`Crtc`] and has an identical data layout. +pub struct UnregisteredCrtc(Crtc, NotThreadSafe); + +impl UnregisteredCrtc { + /// Construct a new [`UnregisteredCrtc`]. + /// + /// A driver may use this from their [`KmsDriver::create_objects`] callback in order to + /// construct new [`UnregisteredCrtc`] objects. + /// + /// [`KmsDriver::create_objects`]: kernel::drm::kms::KmsDriver::create_objects + pub fn new<'a, 'b: 'a, PrimaryData, CursorData>( + dev: &'a UnregisteredKmsDevice<'a, T::Driver>, + primary: &'a UnregisteredPlane, + cursor: Option<&'a UnregisteredPlane>, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'a Self> + where + PrimaryData: DriverPlane, + CursorData: DriverPlane, + { + let this: Pin>> = KBox::try_pin_init( + try_pin_init!(Crtc:: { + crtc: Opaque::new(bindings::drm_crtc { + helper_private: &T::OPS.helper_funcs, + ..Default::default() + }), + inner <- T::new(dev, &args), + _p: PhantomPinned, + }), + GFP_KERNEL, + )?; + + // SAFETY: + // - `dev` handles destroying the CRTC and thus will outlive us. + // - We just allocated `this`, and we won't move it since it's pinned + // - `primary` and `cursor` share the lifetime 'a with `dev` + // - This function will memcpy the contents of `name` into its own storage. + to_result(unsafe { + bindings::drm_crtc_init_with_planes( + dev.as_raw(), + this.as_raw(), + primary.as_raw(), + cursor.map_or(null_mut(), |c| c.as_raw()), + &T::OPS.funcs, + name.map_or(null(), |n| n.as_char_ptr()), + ) + })?; + + // SAFETY: We don't move anything + let this = unsafe { Pin::into_inner_unchecked(this) }; + + // We'll re-assemble the box in crtc_destroy_callback() + let this = KBox::into_raw(this); + + // UnregisteredCrtc has an equivalent data layout + let this: *mut Self = this.cast(); + + // SAFETY: We just allocated the crtc above, so this pointer must be valid + Ok(unsafe { &*this }) + } +} + +// SAFETY: We inherit all relevant invariants of `Crtc` +unsafe impl AsRawCrtc for UnregisteredCrtc { + fn as_raw(&self) -> *mut bindings::drm_crtc { + self.0.as_raw() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_crtc) -> &'a Self { + // SAFETY: This is another from_raw() call, so this function shares the same safety contract + let crtc = unsafe { Crtc::::from_raw(ptr) }; + + // SAFETY: Our data layout is identical via our type invariants. + unsafe { mem::transmute(crtc) } + } +} + +impl Deref for UnregisteredCrtc { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +/// A trait implemented by any type that acts as a [`struct drm_crtc`] interface. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// [`as_raw()`] must always return a valid pointer to a [`struct drm_crtc`]. +/// +/// [`struct drm_crtc`]: srctree/include/drm/drm_crtc.h +/// [`as_raw()`]: AsRawCrtc::as_raw() +pub unsafe trait AsRawCrtc { + /// Return a raw pointer to the `bindings::drm_crtc` for this object + fn as_raw(&self) -> *mut bindings::drm_crtc; + + /// Convert a raw [`struct drm_crtc`] pointer into an object of this type. + /// + /// # Safety + /// + /// Callers promise that `ptr` points to a valid instance of this type + /// + /// [`struct drm_crtc`]: srctree/include/drm/drm_crtc.h + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_crtc) -> &'a Self; +} + +// SAFETY: +// - Via our type variants our data layout starts with `drm_crtc` +// - Since we don't expose `crtc` to users before it has been initialized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `drm_crtc`. +unsafe impl AsRawCrtc for Crtc { + fn as_raw(&self) -> *mut bindings::drm_crtc { + self.crtc.get() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_crtc) -> &'a Self { + // Our data layout start with `bindings::drm_crtc`. + let ptr: *mut Self = ptr.cast(); + + // SAFETY: Our safety contract requires that `ptr` point to a valid intance of `Self`. + unsafe { &*ptr } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::create_objects has been +// called. +unsafe impl ModesettableCrtc for Crtc { + type State = CrtcState; +} + +/// A supertrait of [`AsRawCrtc`] for [`struct drm_crtc`] interfaces that can perform modesets. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// Any object implementing this trait must only be made directly available to the user after +/// [`create_objects`] has completed. +/// +/// [`struct drm_crtc`]: srctree/include/drm/drm_crtc.h +/// [`create_objects`]: KmsDriver::create_objects +pub unsafe trait ModesettableCrtc: AsRawCrtc { + /// The type that should be returned for a CRTC state acquired using this CRTC interface + type State: FromRawCrtcState; +} +unsafe impl Zeroable for bindings::drm_crtc_state {} + +impl Sealed for CrtcState {} + +/// The main trait for implementing the [`struct drm_crtc_state`] API for a [`Crtc`]. +/// +/// A driver may store driver-private data within the implementor's type, which will be available +/// when using a full typed [`CrtcState`] object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_crtc`] pointers are contained within a [`Crtc`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_crtc_state`] pointers are contained within a [`CrtcState`]. +/// +/// [`struct drm_crtc`]: srctree/include/drm_crtc.h +/// [`struct drm_crtc_state`]: srctree/include/drm_crtc.h +pub trait DriverCrtcState: Clone + Default + Unpin { + /// The parent CRTC driver for this CRTC state + type Crtc: DriverCrtc + where + Self: Sized; +} + +/// The main interface for a [`struct drm_crtc_state`]. +/// +/// This type is the main interface for dealing with the atomic state of DRM crtcs. In addition, it +/// allows access to whatever private data is contained within an implementor's [`DriverCrtcState`] +/// type. +/// +/// # Invariants +/// +/// - `state` and `inner` initialized for as long as this object is exposed to users. +/// - The data layout of this structure begins with [`struct drm_crtc_state`]. +/// - The CRTC for this type can always be assumed to be of type [`Crtc`]. +/// +/// [`struct drm_crtc_state`]: srctree/include/drm/drm_crtc.h +#[repr(C)] +pub struct CrtcState { + // It should be noted that CrtcState is a bit of an oddball - it's the only atomic state + // structure that can be modified after it has been swapped in, which is why we need to have + // `state` within an `Opaque<>`… + state: Opaque, + + // …it is also one of the few atomic states that some drivers will embed work structures into, + // which means there's a good chance in the future we may have pinned data here - making it + // impossible for us to hold a mutable or immutable reference to the CrtcState. In preparation + // for that possibility, we keep `T` in an UnsafeCell. + inner: UnsafeCell, +} + +impl Deref for CrtcState { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Our interface ensures that `inner` will not be modified unless only a single + // mutable reference exists to `inner`, so this is safe + unsafe { &*self.inner.get() } + } +} + +impl DerefMut for CrtcState { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.get_mut() + } +} + +/// A trait implemented by any type which can produce a reference to a [`struct drm_crtc_state`]. +/// +/// This is implemented internally by DRM. +/// +/// [`struct drm_crtc_state`]: srctree/include/drm/drm_crtc.h +pub trait AsRawCrtcState: private::AsRawCrtcState { + /// The type that this CRTC state interface returns to represent the parent CRTC + type Crtc: ModesettableCrtc; +} + +pub(crate) mod private { + use super::*; + + #[allow(unreachable_pub)] + pub trait AsRawCrtcState { + /// Return a raw pointer to the DRM CRTC state + /// + /// Note that CRTC states are the only atomic state in KMS which don't nicely follow rust's + /// data aliasing rules already. + fn as_raw(&self) -> *mut bindings::drm_crtc_state; + } +} + +/// A trait for providing common methods which can be used on any type that can be used as an atomic +/// CRTC state. +pub trait RawCrtcState: AsRawCrtcState { + /// Return the CRTC that owns this state. + fn crtc(&self) -> &Self::Crtc { + // SAFETY: + // - This type conversion is guaranteed by type invariance + // - Our interface ensures that this access follows rust's data-aliasing rules + // - `crtc` is guaranteed to never be NULL and is invariant throughout the lifetime of the + // state + unsafe { ::from_raw((*self.as_raw()).crtc) } + } +} +impl RawCrtcState for T {} + +/// A trait implemented for any type which can be constructed directly from a +/// [`struct drm_crtc_state`] pointer. +/// +/// This is implemented internally by DRM. +/// +/// [`struct drm_crtc_state`]: srctree/include/drm/drm_crtc.h +pub trait FromRawCrtcState: AsRawCrtcState { + /// Obtain a reference back to this type from a raw DRM crtc state pointer + /// + /// # Safety + /// + /// Callers must ensure that ptr contains a valid instance of this type. + unsafe fn from_raw<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Self; +} + +impl private::AsRawCrtcState for CrtcState { + #[inline] + fn as_raw(&self) -> *mut bindings::drm_crtc_state { + self.state.get() + } +} + +impl AsRawCrtcState for CrtcState { + type Crtc = Crtc; +} + +impl FromRawCrtcState for CrtcState { + unsafe fn from_raw<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Self { + // SAFETY: Our data layout starts with `bindings::drm_crtc_state` + unsafe { &*(ptr.cast()) } + } +} + +unsafe extern "C" fn crtc_destroy_callback(crtc: *mut bindings::drm_crtc) { + // SAFETY: DRM guarantees that `crtc` points to a valid initialized `drm_crtc`. + unsafe { bindings::drm_crtc_cleanup(crtc) }; + + // SAFETY: + // - DRM guarantees we are now the only one with access to this [`drm_crtc`]. + // - This cast is safe via `DriverCrtc`s type invariants. + // - We created this as a pinned type originally + drop(unsafe { Pin::new_unchecked(KBox::from_raw(crtc as *mut Crtc)) }); +} + +unsafe extern "C" fn atomic_duplicate_state_callback( + crtc: *mut bindings::drm_crtc, +) -> *mut bindings::drm_crtc_state { + // SAFETY: DRM guarantees that `crtc` points to a valid initialized `drm_crtc`. + let state = unsafe { (*crtc).state }; + if state.is_null() { + return null_mut(); + } + + // SAFETY: This cast is safe via `DriverCrtcState`s type invariants. + let crtc = unsafe { Crtc::::from_raw(crtc) }; + + // SAFETY: This cast is safe via `DriverCrtcState`s type invariants. + let state = unsafe { CrtcState::::from_raw(state) }; + + let new = KBox::try_init( + try_init!(CrtcState:: { + state: Opaque::new(Default::default()), + inner: UnsafeCell::new((*state).clone()), + }), + GFP_KERNEL, + ); + + if let Ok(new) = new { + let new = KBox::into_raw(new).cast(); + + // SAFETY: + // - `new` provides a valid pointer to a newly allocated `drm_crtc_state` via type + // invariants + // - This initializes `new` via memcpy() + unsafe { bindings::__drm_atomic_helper_crtc_duplicate_state(crtc.as_raw(), new) } + + new + } else { + null_mut() + } +} + +unsafe extern "C" fn atomic_destroy_state_callback( + _crtc: *mut bindings::drm_crtc, + crtc_state: *mut bindings::drm_crtc_state, +) { + // SAFETY: DRM guarantees that `state` points to a valid instance of `drm_crtc_state` + unsafe { bindings::__drm_atomic_helper_crtc_destroy_state(crtc_state) }; + + // SAFETY: + // - DRM guarantees we are the only one with access to this `drm_crtc_state` + // - This cast is safe via our type invariants. + // - All data in `CrtcState` is either Unpin, or pinned + drop(unsafe { KBox::from_raw(crtc_state as *mut CrtcState) }); +} + +unsafe extern "C" fn crtc_reset_callback(crtc: *mut bindings::drm_crtc) { + // SAFETY: DRM guarantees that `state` points to a valid instance of `drm_crtc_state` + let state = unsafe { (*crtc).state }; + if !state.is_null() { + // SAFETY: + // - We're guaranteed `crtc` is `Crtc` via type invariants + // - We're guaranteed `state` is `CrtcState` via type invariants. + unsafe { atomic_destroy_state_callback::(crtc, state) } + + // SAFETY: No special requirements here, DRM expects this to be NULL + unsafe { + (*crtc).state = null_mut(); + } + } + + // SAFETY: `crtc` is guaranteed to be of type `Crtc` by type invariance + let crtc = unsafe { Crtc::::from_raw(crtc) }; + + // Unfortunately, this is the best we can do at the moment as this FFI callback was mistakenly + // presumed to be infallible :( + let new = KBox::try_init( + try_init!(CrtcState:: { + state: Opaque::new(Default::default()), + inner: UnsafeCell::new(Default::default()), + }), + GFP_KERNEL, + ) + .expect("Unfortunately, this API was presumed infallible"); + + // SAFETY: DRM takes ownership of the state from here, and will never move it + unsafe { bindings::__drm_atomic_helper_crtc_reset(crtc.as_raw(), KBox::into_raw(new).cast()) }; +} From patchwork Wed Mar 5 22:59:23 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003611 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 16C22C28B22 for ; Wed, 5 Mar 2025 23:05:46 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 7466010E747; Wed, 5 Mar 2025 23:05:45 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="TyEro6WE"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id ABEBD10E747 for ; Wed, 5 Mar 2025 23:05:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215942; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=mGO5kh/FxYY4WW0hhW60ooUs3kL6mfXtdUoujsd3eU4=; b=TyEro6WEQJJH71Lv+X1ky00y0BmnNY6T9Btii+XnWR7utk39KiU4qulRQlUI2xvB2GHp3A 3n3eChtziv8TWmr8WGgouHk25frSMU4VhSJddmBOVee7B39ZAfmoLgbig/2oCrgwdOr0kW rDJy0gCQxUV/9hdfahsLF0q6pV9Wm9k= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-362-vsZp3jOhPpaF8m1PkZoR5Q-1; Wed, 05 Mar 2025 18:05:39 -0500 X-MC-Unique: vsZp3jOhPpaF8m1PkZoR5Q-1 X-Mimecast-MFC-AGG-ID: vsZp3jOhPpaF8m1PkZoR5Q_1741215937 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id EAE8E1801A00; Wed, 5 Mar 2025 23:05:36 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id C3072300019E; Wed, 5 Mar 2025 23:05:32 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 07/33] rust: drm/kms: Add drm_encoder bindings Date: Wed, 5 Mar 2025 17:59:23 -0500 Message-ID: <20250305230406.567126-8-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" The last thing we need to be able to register a KMS driver is the ability to create DRM encoders, so let's add bindings for that. Again, these bindings follow the same general pattern as CRTCs, planes, and connector with one difference: encoders don't have an atomic state. Note that not having an atomic state doesn't mean there aren't plenty of valid usecases for a driver to stick private data within a DRM encoder, hence why we reuse the aforementioned pattern. Signed-off-by: Lyude Paul --- V3: * Add safety comments for ModeObject implementation * Make AsRawEncoder unsafe so that we have a guarantee that `as_raw()` always returns a valid pointer. * Introduce UnregisteredEncoder type * Don't have AsRawEncoder be a supertrait of StaticModeObject. We don't want Unregistered mode object variants to be able to return a pointer to the DRM device since that would break the UnregisteredKmsDevice pattern. * Turn all of the encoder type IDs into an enum using a new macro * Use addr_of_mut!() instead of &mut for accessing C struct fields Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/kms.rs | 1 + rust/kernel/drm/kms/encoder.rs | 324 ++++++++++++++++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 rust/kernel/drm/kms/encoder.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 551ddcf02ea0e..a6735f6fba947 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index f8d6d522c9d96..f0044d396e1eb 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -4,6 +4,7 @@ pub mod connector; pub mod crtc; +pub mod encoder; pub mod plane; use crate::{ diff --git a/rust/kernel/drm/kms/encoder.rs b/rust/kernel/drm/kms/encoder.rs new file mode 100644 index 0000000000000..2e4e88055c890 --- /dev/null +++ b/rust/kernel/drm/kms/encoder.rs @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM encoders. +//! +//! C header: [`include/drm/drm_encoder.h`](srctree/include/drm/drm_encoder.h) + +use super::{KmsDriver, ModeObject, StaticModeObject, UnregisteredKmsDevice}; +use crate::{ + alloc::KBox, + drm::device::Device, + error::to_result, + init::Zeroable, + prelude::*, + private::Sealed, + types::{NotThreadSafe, Opaque}, +}; +use bindings; +use core::{ + marker::*, + mem, + ops::Deref, + ptr::{addr_of_mut, null}, +}; +use macros::paste; + +/// A macro for generating our type ID enumerator. +macro_rules! declare_encoder_types { + ($( $oldname:ident as $newname:ident ),+) => { + #[repr(i32)] + #[non_exhaustive] + #[derive(Copy, Clone, PartialEq, Eq)] + /// An enumerator for all possible [`Encoder`] type IDs. + pub enum Type { + // Note: bindgen defaults the macro values to u32 and not i32, but DRM takes them as an + // i32 - so just do the conversion here + $( + #[doc = concat!("The encoder type ID for a ", stringify!($newname), " encoder.")] + $newname = paste!(crate::bindings::[]) as i32 + ),+ + } + }; +} + +declare_encoder_types! { + NONE as None, + DAC as Dac, + TMDS as Tmds, + LVDS as Lvds, + VIRTUAL as Virtual, + DSI as Dsi, + DPMST as DpMst, + DPI as Dpi +} + +/// The main trait for implementing the [`struct drm_encoder`] API for [`Encoder`]. +/// +/// Any KMS driver should have at least one implementation of this type, which allows them to create +/// [`Encoder`] objects. Additionally, a driver may store driver-private data within the type that +/// implements [`DriverEncoder`] - and it will be made available when using a fully typed +/// [`Encoder`] object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that passed-in +/// [`struct drm_encoder`] pointers are contained within a [`Encoder`]. +/// +/// [`struct drm_encoder`]: srctree/include/drm/drm_encoder.h +#[vtable] +pub trait DriverEncoder: Send + Sync + Sized { + /// The generated C vtable for this [`DriverEncoder`] implementation. + #[unique] + const OPS: &'static DriverEncoderOps = &DriverEncoderOps { + funcs: bindings::drm_encoder_funcs { + reset: None, + destroy: Some(encoder_destroy_callback::), + late_register: None, + early_unregister: None, + debugfs_init: None, + }, + helper_funcs: bindings::drm_encoder_helper_funcs { + dpms: None, + mode_valid: None, + mode_fixup: None, + prepare: None, + mode_set: None, + commit: None, + detect: None, + enable: None, + disable: None, + atomic_check: None, + atomic_enable: None, + atomic_disable: None, + atomic_mode_set: None, + }, + }; + + /// The parent driver for this drm_encoder implementation + type Driver: KmsDriver; + + /// The type to pass to the `args` field of [`UnregisteredEncoder::new`]. + /// + /// This type will be made available in in the `args` argument of [`Self::new`]. Drivers which + /// don't need this can simply pass [`()`] here. + type Args; + + /// The constructor for creating a [`Encoder`] using this [`DriverEncoder`] implementation. + /// + /// Drivers may use this to instantiate their [`DriverEncoder`] object. + fn new(device: &Device, args: Self::Args) -> impl PinInit; +} + +/// The generated C vtable for a [`DriverEncoder`]. +/// +/// This type is created internally by DRM. +pub struct DriverEncoderOps { + funcs: bindings::drm_encoder_funcs, + helper_funcs: bindings::drm_encoder_helper_funcs, +} + +/// A trait implemented by any type that acts as a [`struct drm_encoder`] interface. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// [`as_raw()`] must always return a valid pointer to a [`struct drm_encoder`]. +/// +/// [`struct drm_encoder`]: srctree/include/drm/drm_encoder.h +/// [`as_raw()`]: AsRawEncoder::as_raw() +pub unsafe trait AsRawEncoder { + /// Return the raw `bindings::drm_encoder` for this DRM encoder. + /// + /// Drivers should never use this directly + fn as_raw(&self) -> *mut bindings::drm_encoder; + + /// Convert a raw `bindings::drm_encoder` pointer into an object of this type. + /// + /// # Safety + /// + /// Callers promise that `ptr` points to a valid instance of this type + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_encoder) -> &'a Self; +} + +/// The main interface for a [`struct drm_encoder`]. +/// +/// This type is the main interface for dealing with DRM encoders. In addition, it also allows +/// immutable access to whatever private data is contained within an implementor's +/// [`DriverEncoder`] type. +/// +/// # Invariants +/// +/// - `encoder` and `inner` are initialized for as long as this object is made available to users. +/// - The data layout of this structure begins with [`struct drm_encoder`]. +/// +/// [`struct drm_encoder`]: srctree/include/drm/drm_encoder.h +#[repr(C)] +#[pin_data] +pub struct Encoder { + /// The FFI drm_encoder object + encoder: Opaque, + /// The driver's private inner data + #[pin] + inner: T, + #[pin] + _p: PhantomPinned, +} + +impl Sealed for Encoder {} + +// SAFETY: DRM expects this to be zero-initialized +unsafe impl Zeroable for bindings::drm_encoder {} + +// SAFETY: Our interface is thread-safe. +unsafe impl Send for Encoder {} +// SAFETY: Our interface is thread-safe. +unsafe impl Sync for Encoder {} + +// SAFETY: We don't expose Encoder to users before `base` is initialized in ::new(), so +// `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_object. +unsafe impl ModeObject for Encoder { + type Driver = T::Driver; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM encoders exist for as long as the device does, so this pointer is always + // valid + unsafe { Device::borrow((*self.encoder.get()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose Encoder to users before it's initialized, so `base` is always + // initialized + unsafe { addr_of_mut!((*self.encoder.get()).base) } + } +} + +// SAFETY: Encoders do not have a refcount +unsafe impl StaticModeObject for Encoder {} + +impl Deref for Encoder { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// SAFETY: +// - Via our type invariants our data layout starts with `drm_encoder`. +// - Since we don't expose `Encoder` to users befre it has been initialized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `drm_encoder`. +unsafe impl AsRawEncoder for Encoder { + fn as_raw(&self) -> *mut bindings::drm_encoder { + self.encoder.get() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_encoder) -> &'a Self { + // SAFETY: Our data layout is starts with to `bindings::drm_encoder` + unsafe { &*ptr.cast() } + } +} + +/// A [`Encoder`] that has not yet been registered with userspace. +/// +/// KMS registration is single-threaded, so this object is not thread-safe. +/// +/// # Invariants +/// +/// - This object can only exist before its respective KMS device has been registered. +/// - Otherwise, it inherits all invariants of [`Encoder`] and has an identical data layout. +pub struct UnregisteredEncoder(Encoder, NotThreadSafe); + +// SAFETY: We inherit all relevant invariants of `Encoder` +unsafe impl AsRawEncoder for UnregisteredEncoder { + fn as_raw(&self) -> *mut bindings::drm_encoder { + self.0.as_raw() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_encoder) -> &'a Self { + // SAFETY: This is another from_raw() call, so this function shares the same safety contract + let encoder = unsafe { Encoder::::from_raw(ptr) }; + + // SAFETY: Our data layout is identical via our type invariants. + unsafe { mem::transmute(encoder) } + } +} + +impl Deref for UnregisteredEncoder { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +impl UnregisteredEncoder { + /// Construct a new [`UnregisteredEncoder`]. + /// + /// A driver may use this from their [`KmsDriver::create_objects`] callback in order to + /// construct new [`UnregisteredEncoder`] objects. + /// + /// [`KmsDriver::create_objects`]: kernel::drm::kms::KmsDriver::create_objects + pub fn new<'a, 'b: 'a>( + dev: &'a UnregisteredKmsDevice<'a, T::Driver>, + type_: Type, + possible_crtcs: u32, + possible_clones: u32, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'b Self> { + let this: Pin>> = KBox::try_pin_init( + try_pin_init!(Encoder:: { + encoder: Opaque::new(bindings::drm_encoder { + helper_private: &T::OPS.helper_funcs, + possible_crtcs, + possible_clones, + ..Default::default() + }), + inner <- T::new(dev, args), + _p: PhantomPinned + }), + GFP_KERNEL, + )?; + + // SAFETY: + // - `dev` is responsible for destroying the encoder and thus outlives us. + // - as_raw() returns valid pointers for each type here + // - This initializes `this` + // - Our type is proof that this is being called before KMS device registration + // - `name` is optional and will be auto-generated by DRM if passed as NULL + to_result(unsafe { + bindings::drm_encoder_init( + dev.as_raw(), + this.as_raw(), + &T::OPS.funcs, + type_ as _, + name.map_or(null(), |n| n.as_char_ptr()), + ) + })?; + + // SAFETY: We don't move anything + let this = unsafe { Pin::into_inner_unchecked(this) }; + + // We'll re-assemble the box in encoder_destroy_callback() + let this = KBox::into_raw(this); + + // UnregisteredEncoder has an equivalent data layout + let this: *mut Self = this.cast(); + + // SAFETY: We just allocated the encoder above, so this pointer must be valid + Ok(unsafe { &*this }) + } +} + +unsafe extern "C" fn encoder_destroy_callback( + encoder: *mut bindings::drm_encoder, +) { + // SAFETY: DRM guarantees that `encoder` points to a valid initialized `drm_encoder`. + unsafe { bindings::drm_encoder_cleanup(encoder) }; + + // SAFETY: + // - DRM guarantees we are now the only one with access to this [`drm_encoder`]. + // - This cast is safe via `DriverEncoder`s type invariants. + unsafe { drop(KBox::from_raw(encoder as *mut Encoder)) }; +} From patchwork Wed Mar 5 22:59:24 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003612 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 49BCDC19F32 for ; Wed, 5 Mar 2025 23:06:02 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id BC6B010E7E6; Wed, 5 Mar 2025 23:06:01 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="JxqJXwrX"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id D756010E7E6 for ; Wed, 5 Mar 2025 23:06:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215960; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ebrZLCn49Ar/u+hhgDqOxZKPNPiNI29a3OC6pvuCf4w=; b=JxqJXwrX4jEAVNYcsTzRVp06/vJAAWgF+TCAl3HJPtmA5WfHJyvPGK6e8nbjALHGiLhwYu jMjni7tboj6RGVIC3DDT1af5TYQOjvVWVi+/TXtX4FAFAEnnsa8sEjJ+o63bXHCBqFNPmY VUisxbI55K6QlB7rF7DrgigC+Yd0CbQ= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-653-XgmrCJ5TNj6LdSlFvAzo0Q-1; Wed, 05 Mar 2025 18:05:46 -0500 X-MC-Unique: XgmrCJ5TNj6LdSlFvAzo0Q-1 X-Mimecast-MFC-AGG-ID: XgmrCJ5TNj6LdSlFvAzo0Q_1741215944 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 2F9A51956087; Wed, 5 Mar 2025 23:05:44 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 5A2A130001A1; Wed, 5 Mar 2025 23:05:40 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 08/33] rust: drm/kms: Add UnregisteredConnector::attach_encoder() Date: Wed, 5 Mar 2025 17:59:24 -0500 Message-ID: <20250305230406.567126-9-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This adds a simple binding for completing the last step of creating a DRM connector - attaching its encoder. This function should only be called before the connector is registered, and DRM should enforce this itself by returning an error if a driver tries to add an encoder to an already-registered DRM connector. Note that unlike most of the methods we'll be adding to DRM mode objects, this is directly implemented on the Connector type since I don't really think it would make sense for us to allow this operation on an OpaqueConnector (a DRM connector without a known DriverConnector implementation, something we'll be adding in the next few commits). Signed-off-by: Lyude Paul --- V3: * Move to UnregisteredConnector interface * Improve safety comments Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/connector.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index ed65c06ece627..6fe0a7517bd55 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -4,7 +4,7 @@ //! //! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_connector.h) -use super::{KmsDriver, ModeObject, RcModeObject}; +use super::{encoder::*, KmsDriver, ModeObject, RcModeObject}; use crate::{ alloc::KBox, bindings, @@ -362,6 +362,18 @@ pub fn new<'a>( // SAFETY: We just allocated the connector above, so this pointer must be valid Ok(unsafe { &*this }) } + + /// Attach an encoder to this [`Connector`]. + #[must_use] + pub fn attach_encoder(&self, encoder: &impl AsRawEncoder) -> Result { + // SAFETY: + // - Both as_raw() calls are guaranteed to return a valid pointer + // - We're guaranteed this connector is not registered via our type invariants, thus this + // function is safe to call + to_result(unsafe { + bindings::drm_connector_attach_encoder(self.as_raw(), encoder.as_raw()) + }) + } } unsafe extern "C" fn connector_destroy_callback( From patchwork Wed Mar 5 22:59:25 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003637 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 367BBC282EC for ; Wed, 5 Mar 2025 23:06:15 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 9F81910E84C; Wed, 5 Mar 2025 23:06:14 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="OaQM78hx"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 9B71410E84C for ; Wed, 5 Mar 2025 23:06:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215972; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=OnVLYW2FIG18ZU9j87EnxxPDzjwGXevU3H0JDILigMI=; b=OaQM78hxPpsa25/yy3Hn/wn2D+dW5XWes0qYd3jeD2eN4s84h2tKgN1e1sxh3sCsvcC3ik UZqccjcxYxaK9mrC47NXIkjlZCnZR0dH2KFxIHwCThd+Prs8wcjOKuy3ljij5jVUBGkQkj UoLX1pwAJXm4MucjommSIjd5Xh2ZG9U= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-80-iYOOLzw8P96mzlZ7Xt0KiQ-1; Wed, 05 Mar 2025 18:05:57 -0500 X-MC-Unique: iYOOLzw8P96mzlZ7Xt0KiQ-1 X-Mimecast-MFC-AGG-ID: iYOOLzw8P96mzlZ7Xt0KiQ_1741215954 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 2AACB1955DCD; Wed, 5 Mar 2025 23:05:54 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 133B7300019E; Wed, 5 Mar 2025 23:05:49 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 09/33] rust: drm/kms: Add DriverConnector::get_mode callback Date: Wed, 5 Mar 2025 17:59:25 -0500 Message-ID: <20250305230406.567126-10-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Next up is filling out some of the basic connector hotplugging callbacks - which we'll need for setting up the fbdev helpers for KMS devices. Note that connector hotplugging in DRM follows a BFL scheme: pretty much all probing is protected under the mighty drm_device->mode_config.lock, which of course is a bit counter-intuitive to rust's locking schemes where data is always associated with its lock. Since that lock is embedded in an FFI type and not a rust type, we need to introduce our own wrapper type that acts as a lock acquisition for this. This brings us to introducing a few new types: * ModeConfigGuard - the most basic lock guard, as long as this object is alive we are guaranteed to be holding drm_device->mode_config.lock. This object doesn't do much else on its own currently. * ConnectorGuard - an object which corresponds to a specific typed DRM connector. This can only be acquired with a ModeConfigGuard, and will be used to allow calling methods that are only safe to call with drm_device->mode_config.lock held. Since it implements Deref> as well, it can also be used for any other operations that would normally be available on a DRM connector. And finally, we add the DriverConnector::get_modes() trait method which drivers can use to implement the drm_connector_helper_funcs.get_modes callback. Note that while we make this trait method mandatory, we only do so for the time being since VKMS doesn't do very much with DRM connectors - and as such we have no need yet to implement alternative connector probing schemes outside of get_modes(). Signed-off-by: Lyude Paul --- V3: * Document uses of ManuallyDrop * Use addr_of_mut!() instead of &mut * Add some missing invariant comments Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/kms.rs | 90 +++++++++++++++++++++++++++++++- rust/kernel/drm/kms/connector.rs | 62 ++++++++++++++++++++-- 3 files changed, 147 insertions(+), 6 deletions(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index a6735f6fba947..27828dd36d4f2 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index f0044d396e1eb..7935e935f9975 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -8,15 +8,20 @@ pub mod plane; use crate::{ - device, + container_of, device, drm::{device::Device, drv::Driver}, error::to_result, prelude::*, private::Sealed, + sync::{Mutex, MutexGuard}, types::*, }; use bindings; -use core::{marker::PhantomData, ops::Deref, ptr::NonNull}; +use core::{ + marker::PhantomData, + ops::Deref, + ptr::{self, addr_of_mut, NonNull}, +}; /// The C vtable for a [`Device`]. /// @@ -191,6 +196,23 @@ pub struct ModeConfigInfo { pub preferred_fourcc: Option, } +impl Device { + /// Retrieve a pointer to the mode_config mutex + #[inline] + pub(crate) fn mode_config_mutex(&self) -> &Mutex<()> { + // SAFETY: This lock is initialized for as long as `Device` is exposed to users + unsafe { Mutex::from_raw(addr_of_mut!((*self.as_raw()).mode_config.mutex)) } + } + + /// Acquire the [`mode_config.mutex`] for this [`Device`]. + #[inline] + pub fn mode_config_lock(&self) -> ModeConfigGuard<'_, T> { + // INVARIANT: We're locking mode_config.mutex, fulfilling our invariant that this lock is + // held throughout ModeConfigGuard's lifetime. + ModeConfigGuard(self.mode_config_mutex().lock(), PhantomData) + } +} + /// A modesetting object in DRM. /// /// This is any type of object where the underlying C object contains a [`struct drm_mode_object`]. @@ -314,3 +336,67 @@ unsafe fn dec_ref(obj: NonNull) { unsafe { bindings::drm_mode_object_put(obj.as_ref().raw_mode_obj()) } } } + +/// A mode config guard. +/// +/// This is an exclusive primitive that represents when [`drm_device.mode_config.mutex`] is held - as +/// some modesetting operations (particularly ones related to [`connectors`](connector)) are still +/// protected under this single lock. The lock will be dropped once this object is dropped. +/// +/// # Invariants +/// +/// - `self.0` is contained within a [`struct drm_mode_config`], which is contained within a +/// [`struct drm_device`]. +/// - The [`KmsDriver`] implementation of that [`struct drm_device`] is always `T`. +/// - This type proves that [`drm_device.mode_config.mutex`] is acquired. +/// +/// [`struct drm_mode_config`]: (srctree/include/drm/drm_device.h) +/// [`drm_device.mode_config.mutex`]: (srctree/include/drm/drm_device.h) +/// [`struct drm_device`]: (srctree/include/drm/drm_device.h) +pub struct ModeConfigGuard<'a, T: KmsDriver>(MutexGuard<'a, ()>, PhantomData); + +impl<'a, T: KmsDriver> ModeConfigGuard<'a, T> { + /// Construct a new [`ModeConfigGuard`]. + /// + /// # Safety + /// + /// The caller must ensure that [`drm_device.mode_config.mutex`] is acquired. + /// + /// [`drm_device.mode_config.mutex`]: (srctree/include/drm/drm_device.h) + pub(crate) unsafe fn new(drm: &'a Device) -> Self { + // SAFETY: Our safety contract fulfills the requirements of `MutexGuard::new()` + // INVARIANT: And our safety contract ensures that this type proves that + // `drm_device.mode_config.mutex` is acquired. + Self( + unsafe { MutexGuard::new(drm.mode_config_mutex(), ()) }, + PhantomData, + ) + } + + /// Return the [`Device`] that this [`ModeConfigGuard`] belongs to. + pub fn drm_dev(&self) -> &'a Device { + // SAFETY: + // - `self` is embedded within a `drm_mode_config` via our type invariants + // - `self.0.lock` has an equivalent data type to `mutex` via its type invariants. + let mode_config = unsafe { container_of!(self.0.lock, bindings::drm_mode_config, mutex) }; + + // SAFETY: And that `drm_mode_config` lives in a `drm_device` via type invariants. + unsafe { + Device::borrow(container_of!( + mode_config, + bindings::drm_device, + mode_config + )) + } + } + + /// Assert that the given device is the owner of this mode config guard. + /// + /// # Panics + /// + /// Panics if `dev` is different from the owning device for this mode config guard. + #[inline] + pub(crate) fn assert_owner(&self, dev: &Device) { + assert!(ptr::eq(self.drm_dev(), dev)); + } +} diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index 6fe0a7517bd55..14de3b0529f89 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -4,7 +4,7 @@ //! //! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_connector.h) -use super::{encoder::*, KmsDriver, ModeObject, RcModeObject}; +use super::{encoder::*, KmsDriver, ModeConfigGuard, ModeObject, RcModeObject}; use crate::{ alloc::KBox, bindings, @@ -17,7 +17,7 @@ }; use core::{ marker::*, - mem, + mem::{self, ManuallyDrop}, ops::*, ptr::{addr_of_mut, null_mut}, stringify, @@ -106,7 +106,7 @@ pub trait DriverConnector: Send + Sync + Sized { destroy: Some(connector_destroy_callback::), force: None, detect: None, - fill_modes: None, + fill_modes: Some(bindings::drm_helper_probe_single_connector_modes), debugfs_init: None, oob_hotplug_event: None, atomic_duplicate_state: Some(atomic_duplicate_state_callback::), @@ -114,7 +114,7 @@ pub trait DriverConnector: Send + Sync + Sized { helper_funcs: bindings::drm_connector_helper_funcs { mode_valid: None, atomic_check: None, - get_modes: None, + get_modes: Some(get_modes_callback::), detect_ctx: None, enable_hpd: None, disable_hpd: None, @@ -145,6 +145,12 @@ pub trait DriverConnector: Send + Sync + Sized { /// /// Drivers may use this to instantiate their [`DriverConnector`] object. fn new(device: &Device, args: Self::Args) -> impl PinInit; + + /// Retrieve a list of available display modes for this [`Connector`]. + fn get_modes<'a>( + connector: ConnectorGuard<'a, Self>, + guard: &ModeConfigGuard<'a, Self::Driver>, + ) -> i32; } /// The generated C vtable for a [`DriverConnector`]. @@ -196,6 +202,21 @@ fn deref(&self) -> &Self::Target { } } +impl Connector { + /// Acquire a [`ConnectorGuard`] for this connector from a [`ModeConfigGuard`]. + /// + /// This verifies using the provided reference that the given guard is actually for the same + /// device as this connector's parent. + /// + /// # Panics + /// + /// Panics if `guard` is not a [`ModeConfigGuard`] for this connector's parent [`Device`]. + pub fn guard<'a>(&'a self, guard: &ModeConfigGuard<'a, T::Driver>) -> ConnectorGuard<'a, T> { + guard.assert_owner(self.drm_dev()); + ConnectorGuard(self) + } +} + /// A trait implemented by any type that acts as a [`struct drm_connector`] interface. /// /// This is implemented internally by DRM. @@ -392,6 +413,39 @@ pub fn attach_encoder(&self, encoder: &impl AsRawEncoder) -> Result { drop(unsafe { KBox::from_raw(connector as *mut Connector) }); } +unsafe extern "C" fn get_modes_callback( + connector: *mut bindings::drm_connector, +) -> core::ffi::c_int { + // SAFETY: This is safe via `DriverConnector`s type invariants. + let connector = unsafe { Connector::::from_raw(connector) }; + + // SAFETY: This FFI callback is only called while `mode_config.lock` is held + // We use ManuallyDrop here to prevent the lock from being released after the callback + // completes, as that should be handled by DRM. + let guard = ManuallyDrop::new(unsafe { ModeConfigGuard::new(connector.drm_dev()) }); + + T::get_modes(connector.guard(&guard), &guard) +} + +/// A privileged [`Connector`] obtained while holding a [`ModeConfigGuard`]. +/// +/// This provides access to various methods for [`Connector`] that must happen under lock, such as +/// setting resolution preferences and adding display modes. +/// +/// # Invariants +/// +/// Shares the invariants of [`ModeConfigGuard`]. +#[derive(Copy, Clone)] +pub struct ConnectorGuard<'a, T: DriverConnector>(&'a Connector); + +impl Deref for ConnectorGuard<'_, T> { + type Target = Connector; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + // SAFETY: DRM expects this struct to be zero-initialized unsafe impl Zeroable for bindings::drm_connector_state {} From patchwork Wed Mar 5 22:59:26 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003638 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id ACBB9C282EC for ; Wed, 5 Mar 2025 23:06:18 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 1DF0A10E84D; Wed, 5 Mar 2025 23:06:18 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="FZTtIbcJ"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 9341010E84D for ; Wed, 5 Mar 2025 23:06:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215975; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BrOarNfcTcrGH7ROFN5mUBFA32lVUZ4XyCrGK8TCRKY=; b=FZTtIbcJxxDZcHCoZmJVtwuvRxKkozuesiD9kLWHCG0SFqAZ07oCXr0/I8oq7O9iI75zzY aJOCzPvb6MSFJHHWKDzvSNnkti3DRPqtegq02BGiS3SLCZE9CDY0cSMktgxyUW9ijFvmje G1Mlrbi+1wI+/ACfZ/gYFGSZN6z619g= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-433-kdYPVQH0Nm-WjvXIeT85nA-1; Wed, 05 Mar 2025 18:06:05 -0500 X-MC-Unique: kdYPVQH0Nm-WjvXIeT85nA-1 X-Mimecast-MFC-AGG-ID: kdYPVQH0Nm-WjvXIeT85nA_1741215963 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 80A51180087E; Wed, 5 Mar 2025 23:06:02 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id EBE78300019E; Wed, 5 Mar 2025 23:05:57 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Greg Kroah-Hartman , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 10/33] rust: drm/kms: Add ConnectorGuard::add_modes_noedid() Date: Wed, 5 Mar 2025 17:59:26 -0500 Message-ID: <20250305230406.567126-11-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" A simple binding for drm_add_modes_noedid() using the ConnectorGuard type we just added. Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/kms/connector.rs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 27828dd36d4f2..846eb6eb8fc4c 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index 14de3b0529f89..855a47b189a91 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -446,6 +446,17 @@ fn deref(&self) -> &Self::Target { } } +impl<'a, T: DriverConnector> ConnectorGuard<'a, T> { + /// Add modes for a [`ConnectorGuard`] without an EDID. + /// + /// Add the specified modes to the connector's mode list up to the given maximum resultion. + /// Returns how many modes were added. + pub fn add_modes_noedid(&self, (max_h, max_v): (i32, i32)) -> i32 { + // SAFETY: We hold the locks required to call this via our type invariants. + unsafe { bindings::drm_add_modes_noedid(self.as_raw(), max_h, max_v) } + } +} + // SAFETY: DRM expects this struct to be zero-initialized unsafe impl Zeroable for bindings::drm_connector_state {} From patchwork Wed Mar 5 22:59:27 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003640 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id EE462C28B22 for ; Wed, 5 Mar 2025 23:06:29 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 48DFE10E850; Wed, 5 Mar 2025 23:06:29 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="BVecKYjU"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id B30D410E84E for ; Wed, 5 Mar 2025 23:06:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215985; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=qk/jp+gPRUmslXZDnoYRXtjViDFAYaKs0fIHC124xLM=; b=BVecKYjUPBus3axpyH+JHXnr+g6Qb2AoaT6VNz2jMH+6Qa7caNh3nX93CqiApi+ov3UofA BoWBBr7lMR3L2ymEbUuMd8Rg5+c1gVMbNkObh80Mz9LXlQYY5gjiuSMtRgrdo4tEvkEQEV p6JT+2w+CtfdfFhYDMZdFkO7hi85ZPw= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-547-EFdwfM_IOfOKpD2hnobrBw-1; Wed, 05 Mar 2025 18:06:15 -0500 X-MC-Unique: EFdwfM_IOfOKpD2hnobrBw-1 X-Mimecast-MFC-AGG-ID: EFdwfM_IOfOKpD2hnobrBw_1741215970 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 4B2B5180AF58; Wed, 5 Mar 2025 23:06:10 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id BAD24300019E; Wed, 5 Mar 2025 23:06:05 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 11/33] rust: drm/kms: Add ConnectorGuard::set_preferred_mode Date: Wed, 5 Mar 2025 17:59:27 -0500 Message-ID: <20250305230406.567126-12-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Add a wrapper for `drm_set_preferred_mode()` for our new `ConnectorGuard` type so we can set the preferred mode for RVKMS connectors. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/connector.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index 855a47b189a91..244db1cfdc552 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -455,6 +455,12 @@ pub fn add_modes_noedid(&self, (max_h, max_v): (i32, i32)) -> i32 { // SAFETY: We hold the locks required to call this via our type invariants. unsafe { bindings::drm_add_modes_noedid(self.as_raw(), max_h, max_v) } } + + /// Set the preferred display mode for the underlying [`Connector`]. + pub fn set_preferred_mode(&self, (h_pref, w_pref): (i32, i32)) { + // SAFETY: We hold the locks required to call this via our type invariants. + unsafe { bindings::drm_set_preferred_mode(self.as_raw(), h_pref, w_pref) } + } } // SAFETY: DRM expects this struct to be zero-initialized From patchwork Wed Mar 5 22:59:28 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003639 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id E9831C19F32 for ; Wed, 5 Mar 2025 23:06:28 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 53DAA10E84E; Wed, 5 Mar 2025 23:06:28 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="DWobE7mX"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 7159F10E84E for ; Wed, 5 Mar 2025 23:06:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215985; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=/MBAtf8b0iFaHt8G5vaAlm0/XTAjGoaebIJGPgNqjtU=; b=DWobE7mX4Kys1/yMWnNthYJmJl5VgutIwMz8kjDlDV1Zep3mMeVW9zLl1BakWQsZfED0qb YAYeeCFtHZ/tNv82ocRqrMymvYLcpVOuF1AIbpskyM51hMADM8cpsnnBalI7yONQjQQoWd T4nATEju6fspbGp+wBqzgXQoZeRcULQ= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-673-Ck2kwp_CP5Gvgo2lBvhYrw-1; Wed, 05 Mar 2025 18:06:22 -0500 X-MC-Unique: Ck2kwp_CP5Gvgo2lBvhYrw-1 X-Mimecast-MFC-AGG-ID: Ck2kwp_CP5Gvgo2lBvhYrw_1741215980 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 511351955D4B; Wed, 5 Mar 2025 23:06:20 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 65A82300019E; Wed, 5 Mar 2025 23:06:16 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 12/33] rust: drm/kms: Add RawConnector and RawConnectorState Date: Wed, 5 Mar 2025 17:59:28 -0500 Message-ID: <20250305230406.567126-13-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Now that we have more then one way to refer to connectors, we also want to ensure that any methods which are common to any kind of connector type can be used on all connector representations. This is where RawConnector and RawConnectorState come in: we implement these traits for any type which implements AsRawConnector or AsRawConnectorState respectively. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/connector.rs | 35 ++++++++++++++++++++++++++++++++ rust/kernel/drm/kms/crtc.rs | 26 ++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index 244db1cfdc552..0cfe346b4760e 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -397,6 +397,27 @@ pub fn attach_encoder(&self, encoder: &impl AsRawEncoder) -> Result { } } +/// Common methods available on any type which implements [`AsRawConnector`]. +/// +/// This is implemented internally by DRM, and provides many of the basic methods for working with +/// connectors. +pub trait RawConnector: AsRawConnector { + /// Return the index of this DRM connector + #[inline] + fn index(&self) -> u32 { + // SAFETY: The index is initialized by the time we expose DRM connector objects to users, + // and is invariant throughout the lifetime of the connector + unsafe { (*self.as_raw()).index } + } + + /// Return the bitmask derived from this DRM connector's index + #[inline] + fn mask(&self) -> u32 { + 1 << self.index() + } +} +impl RawConnector for T {} + unsafe extern "C" fn connector_destroy_callback( connector: *mut bindings::drm_connector, ) { @@ -536,6 +557,20 @@ pub trait FromRawConnectorState: AsRawConnectorState { unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_connector_state) -> &'a mut Self; } +/// Common methods available on any type which implements [`AsRawConnectorState`]. +/// +/// This is implemented internally by DRM, and provides many of the basic methods for working with +/// the atomic state of [`Connector`]s. +pub trait RawConnectorState: AsRawConnectorState { + /// Return the connector that this atomic state belongs to. + fn connector(&self) -> &Self::Connector { + // SAFETY: This is guaranteed safe by type invariance, and we're guaranteed by DRM that + // `self.state.connector` points to a valid instance of a `Connector` + unsafe { Self::Connector::from_raw((*self.as_raw()).connector) } + } +} +impl RawConnectorState for T {} + /// The main interface for a [`struct drm_connector_state`]. /// /// This type is the main interface for dealing with the atomic state of DRM connectors. In diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 95c79ffb584cd..9950b09754072 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -341,6 +341,26 @@ pub unsafe trait ModesettableCrtc: AsRawCrtc { /// The type that should be returned for a CRTC state acquired using this CRTC interface type State: FromRawCrtcState; } + +/// Common methods available on any type which implements [`AsRawCrtc`]. +/// +/// This is implemented internally by DRM, and provides many of the basic methods for working with +/// CRTCs. +pub trait RawCrtc: AsRawCrtc { + /// Return the index of this CRTC. + fn index(&self) -> u32 { + // SAFETY: The index is initialized by the time we expose Crtc objects to users, and is + // invariant throughout the lifetime of the Crtc + unsafe { (*self.as_raw()).index } + } + + /// Return the index of this DRM CRTC in the form of a bitmask. + fn mask(&self) -> u32 { + 1 << self.index() + } +} +impl RawCrtc for T {} + unsafe impl Zeroable for bindings::drm_crtc_state {} impl Sealed for CrtcState {} @@ -432,8 +452,10 @@ pub trait AsRawCrtcState { } } -/// A trait for providing common methods which can be used on any type that can be used as an atomic -/// CRTC state. +/// Common methods available on any type which implements [`AsRawCrtcState`]. +/// +/// This is implemented internally by DRM, and provides many of the basic methods for working with +/// the atomic state of [`Crtc`]s. pub trait RawCrtcState: AsRawCrtcState { /// Return the CRTC that owns this state. fn crtc(&self) -> &Self::Crtc { From patchwork Wed Mar 5 22:59:29 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003641 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 3B6EBC282EC for ; Wed, 5 Mar 2025 23:06:36 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id A554210E851; Wed, 5 Mar 2025 23:06:35 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="LwtiUu4i"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id E307C10E852 for ; Wed, 5 Mar 2025 23:06:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741215993; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=lI0pBqW1V5q10bkXXVuUMbPUNgGv55fdwvDT8YLI59Y=; b=LwtiUu4iv3krIpP0t6wORNd8nFFIeKuycHYwC3HrFS606okNKLPg3tGVuCALF2Unojtj9U CLLgtsqRHKt2VRRsZWEGMSYkp8ZDkOwvXDA1yKAJXOPz1urDy9i6JeHVX4xeJ2SxHaIEeN 1Rf5E3azYfOrPwKxksYt/OUI45fLbbU= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-456-CS-WicQbPn64VR2-SL284A-1; Wed, 05 Mar 2025 18:06:29 -0500 X-MC-Unique: CS-WicQbPn64VR2-SL284A-1 X-Mimecast-MFC-AGG-ID: CS-WicQbPn64VR2-SL284A_1741215987 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 61E7419560B4; Wed, 5 Mar 2025 23:06:27 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id A6C8430001A1; Wed, 5 Mar 2025 23:06:23 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 13/33] rust: drm/kms: Add RawPlane and RawPlaneState Date: Wed, 5 Mar 2025 17:59:29 -0500 Message-ID: <20250305230406.567126-14-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Same thing as RawCrtc and RawCrtcState, but for DRM planes now Signed-off-by: Lyude Paul --- V3: * Limit unsafe scope in RawPlane::index() * Improve safety comments Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/plane.rs | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 9f262156eac6c..d1fabdf42df54 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -373,6 +373,29 @@ pub unsafe trait ModesettablePlane: AsRawPlane { type State: FromRawPlaneState; } +/// Common methods available on any type which implements [`AsRawPlane`]. +/// +/// This is implemented internally by DRM, and provides many of the basic methods for working with +/// planes. +pub trait RawPlane: AsRawPlane { + /// Return the index of this DRM plane + #[inline] + fn index(&self) -> u32 { + // SAFETY: + // - The index is initialized by the time we expose planes to users, and does not change + // throughout its lifetime + // - `.as_raw()` always returns a valid poiinter. + unsafe { *self.as_raw() }.index + } + + /// Return the index of this DRM plane in the form of a bitmask + #[inline] + fn mask(&self) -> u32 { + 1 << self.index() + } +} +impl RawPlane for T {} + /// A trait implemented by any type which can produce a reference to a [`struct drm_plane_state`]. /// /// This is implemented internally by DRM. @@ -436,6 +459,20 @@ pub trait FromRawPlaneState: AsRawPlaneState { unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_plane_state) -> &'a mut Self; } +/// Common methods available on any type which implements [`AsRawPlane`]. +/// +/// This is implemented internally by DRM, and provides many of the basic methods for working with +/// the atomic state of [`Plane`]s. +pub trait RawPlaneState: AsRawPlaneState { + /// Return the plane that this plane state belongs to. + fn plane(&self) -> &Self::Plane { + // SAFETY: The index is initialized by the time we expose Plane objects to users, and is + // invariant throughout the lifetime of the Plane + unsafe { Self::Plane::from_raw(self.as_raw().plane) } + } +} +impl RawPlaneState for T {} + /// The main interface for a [`struct drm_plane_state`]. /// /// This type is the main interface for dealing with the atomic state of DRM planes. In addition, it From patchwork Wed Mar 5 22:59:30 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003643 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id BD90CC19F32 for ; Wed, 5 Mar 2025 23:06:52 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 1FA6110E852; Wed, 5 Mar 2025 23:06:52 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="REzJvCeD"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 27AD010E84F for ; Wed, 5 Mar 2025 23:06:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216009; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=t861S+lJA1FAHGRt4CQfszfhAAbg1AWVUqKzD27olGw=; b=REzJvCeDnH73Y7djDwQDTEGffATs6V1l2JMM2j0yBUATfmMOimWrHtloykEwNwCHiP5Jz2 zwS0lpE9apTVaZ88NbzlFdWdN2RQDE2G2l5fC/TPVUMUvx/rJVPFdXwyRZQq0qiMAdLCU6 j/q8jArwblCIuL9HWi5GTe/2IlKuPT8= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-614-R_vdkGnWMOKYaG23ECL0KQ-1; Wed, 05 Mar 2025 18:06:38 -0500 X-MC-Unique: R_vdkGnWMOKYaG23ECL0KQ-1 X-Mimecast-MFC-AGG-ID: R_vdkGnWMOKYaG23ECL0KQ_1741215996 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9D01319560AD; Wed, 5 Mar 2025 23:06:36 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id DB9C8300019E; Wed, 5 Mar 2025 23:06:32 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 14/33] rust: drm/kms: Add OpaqueConnector and OpaqueConnectorState Date: Wed, 5 Mar 2025 17:59:30 -0500 Message-ID: <20250305230406.567126-15-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Since we allow drivers to have multiple implementations of DriverConnector and DriverConnectorState (in C, the equivalent of this is having multiple structs which embed drm_connector) - there are some situations we will run into where it's not possible for us to know the corresponding DriverConnector or DriverConnectorState for a given connector. The most obvious one is iterating through all connectors on a KMS device. So, take advantage of the various connector traits we added to introduce OpaqueConnector<> and OpaqueConnectorState<> which both can be used as a DRM connector and connector state respectively without needing to know the corresponding traits. Signed-off-by: Lyude Paul --- V3: * Add safety comment to implementation of ModeObject * Add safety comments to AsRawConnector implementation * Implement try_from_opaque() and from_opaque() using a macro * Ensure all Opaque types have the ability to "upcast" Signed-off-by: Lyude Paul --- rust/kernel/drm/kms.rs | 115 +++++++++++++++++++++ rust/kernel/drm/kms/connector.rs | 171 ++++++++++++++++++++++++++++++- 2 files changed, 285 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 7935e935f9975..1d6ca9c92659a 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -61,6 +61,104 @@ unsafe fn setup_kms(_drm: &Device) -> Result { } } +/// Implement the repetitive from_opaque/try_from_opaque methods for all mode object and state +/// types. +/// +/// Because there are so many different ways of accessing mode objects, their states, etc. we need a +/// macro that we can use for consistently implementing try_from_opaque()/from_opaque() functions to +/// convert from Opaque mode objects to fully typed mode objects. This macro handles that, and can +/// generate said functions for any kind of type which the original mode object driver trait can be +/// derived from. All conversions check the mode object's vtable. For example: +/// +/// ```compile_fail +/// impl<'a, T: DriverConnectorState> ConnectorState { +/// impl_from_opaque_mode_obj! { +/// // | An optional lifetime and meta-variables to declare for each function +/// // | | The type of the input parameter `opaque` +/// // | | | The converted type +/// // v v v +/// fn <'a, D, C>(&'a OpaqueConnectorState) -> &'a Self +/// where // <-- An optional set of additional trait bounds to match against +/// T: DriverConnectorState; +/// use +/// // | Meta-variable that will contain ::OPS (the auto-generated vtable) +/// // | | The driver trait implemented by the driver for generating the vtable +/// // | | It will add the bound C: DriverConnector to the function +/// // v v +/// C as DriverConnector, +/// // | Meta-variable to assign to KmsDriver +/// // | | This must always be KmsDriver, it's just here for clarity +/// // v v +/// D as KmsDriver, +/// } +/// } +/// ``` +macro_rules! impl_from_opaque_mode_obj { + ( + fn < + $( $lifetime:lifetime, )? + $( $decl_bound_id:ident ),* + > ($opaque:ty) -> $inner_ret_ty:ty + $( + where + $( $extra_bound_id:ident : $extra_trait:ident<$( $extra_assoc:ident = $extra_meta_match:ident ),+> ),+ + )? ; + use + $obj_trait_meta:ident as $obj_trait:ident, + $drv_trait_meta:ident as KmsDriver + ) => { + #[doc = "Try to convert `opaque` into a fully qualified `Self`."] + #[doc = ""] + #[doc = concat!("This will try to convert `opaque` into `Self` if it shares the same [`", + stringify!($obj_trait), "`] implementation as `Self`.")] + pub fn try_from_opaque<$( $lifetime, )? $( $decl_bound_id ),* >( + opaque: $opaque + ) -> Result<$inner_ret_ty, $opaque> + where + $drv_trait_meta: KmsDriver, + $obj_trait_meta: $obj_trait + $( , $( $extra_bound_id: $extra_trait<$( $extra_assoc = $extra_meta_match ),+> ),+ )? + { + let funcs = opaque.vtable(); + let obj_funcs = &$obj_trait_meta::OPS.funcs; + + if core::ptr::eq(funcs, obj_funcs) { + // SAFETY: We only perform this transmutation if the opaque object shares our vtable + // pointers, so the underlying full object must share our data layout. + Ok(unsafe { core::mem::transmute(opaque) }) + } else { + Err(opaque) + } + } + + #[doc = "Convert `opaque` into a fully qualified `Self`."] + #[doc = ""] + #[doc = concat!("This is an infallible version of [`Self::try_from_opaque`]. This ", + "function is mainly useful for drivers where only a single [`", + stringify!($obj_trait), "`] implementation exists.")] + #[doc = ""] + #[doc = "# Panics"] + #[doc = ""] + #[doc = concat!("This function will panic if `opaque` belongs to a different [`", + stringify!($obj_trait), "`] implementation.")] + pub fn from_opaque<$( $lifetime, )? $( $decl_bound_id ),* >( + opaque: $opaque + ) -> $inner_ret_ty + where + $drv_trait_meta: KmsDriver, + $obj_trait_meta: $obj_trait + $( , $( $extra_bound_id: $extra_trait<$( $extra_assoc = $extra_meta_match ),+> ),+ )? + { + Self::try_from_opaque(opaque) + .map_or(None, |o| Some(o)) + .expect(concat!("Passed ", stringify!($opaque), " does not share this ", + stringify!($obj_trait), " implementation.")) + } + }; +} + +pub(crate) use impl_from_opaque_mode_obj; + /// A [`Device`] with KMS initialized that has not been registered with userspace. /// /// This type is identical to [`Device`], except that it is able to create new static KMS resources. @@ -337,6 +435,23 @@ unsafe fn dec_ref(obj: NonNull) { } } +/// A trait for any object related to a [`ModeObject`] that can return its vtable. +/// +/// This reference will used for checking whether an opaque representation of a mode object uses a +/// specific driver trait implementation. +/// +/// # Safety +/// +/// `ModeObjectVtable::vtable()` must always return a valid pointer to the relevant mode object's +/// vtable. +pub(crate) unsafe trait ModeObjectVtable { + /// The type for the auto-generated vtable. + type Vtable; + + /// Return a static reference to the auto-generated vtable for the relevant mode object. + fn vtable(&self) -> *const Self::Vtable; +} + /// A mode config guard. /// /// This is an exclusive primitive that represents when [`drm_device.mode_config.mutex`] is held - as diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index 0cfe346b4760e..4521643d19749 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -4,7 +4,7 @@ //! //! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_connector.h) -use super::{encoder::*, KmsDriver, ModeConfigGuard, ModeObject, RcModeObject}; +use super::{encoder::*, KmsDriver, ModeConfigGuard, ModeObject, ModeObjectVtable, RcModeObject}; use crate::{ alloc::KBox, bindings, @@ -203,6 +203,13 @@ fn deref(&self) -> &Self::Target { } impl Connector { + super::impl_from_opaque_mode_obj! { + fn <'a, D>(&'a OpaqueConnector) -> &'a Self; + use + T as DriverConnector, + D as KmsDriver + } + /// Acquire a [`ConnectorGuard`] for this connector from a [`ModeConfigGuard`]. /// /// This verifies using the provided reference that the given guard is actually for the same @@ -286,6 +293,16 @@ fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { // SAFETY: DRM connectors are refcounted mode objects unsafe impl RcModeObject for Connector {} +// SAFETY: `funcs` is initialized by DRM when the connector is allocated +unsafe impl ModeObjectVtable for Connector { + type Vtable = bindings::drm_connector_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `funcs` is initialized by DRM when the connector is allocated + unsafe { *self.as_raw() }.funcs + } +} + // SAFETY: // * Via our type variants our data layout starts with `drm_connector` // * Since we don't expose `Connector` to users before it has been initialized, this and our data @@ -448,6 +465,81 @@ impl RawConnector for T {} T::get_modes(connector.guard(&guard), &guard) } +/// A [`struct drm_connector`] without a known [`DriverConnector`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`DriverConnector`] +/// implementation for a [`struct drm_connector`] automatically. It is identical to [`Connector`], +/// except that it does not provide access to the driver's private data. +/// +/// # Invariants +/// +/// - `connector` is initialized for as long as this object is exposed to users. +/// - The data layout of this type is equivalent to [`struct drm_connector`]. +/// +/// [`struct drm_connector`]: srctree/include/drm/drm_connector.h +#[repr(transparent)] +pub struct OpaqueConnector { + connector: Opaque, + _p: PhantomData, +} + +impl Sealed for OpaqueConnector {} + +// SAFETY: +// - Via our type variants our data layout starts is identical to `drm_connector` +// - Since we don't expose `OpaqueConnector` to users before it has been initialized, this and our +// data layout ensure that `as_raw()` always returns a valid pointer to a `drm_connector`. +unsafe impl AsRawConnector for OpaqueConnector { + fn as_raw(&self) -> *mut bindings::drm_connector { + self.connector.get() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_connector) -> &'a Self { + // SAFETY: Our data layout is identical to `bindings::drm_connector` + unsafe { &*ptr.cast() } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::create_objects has been +// called. +unsafe impl ModesettableConnector for OpaqueConnector { + type State = OpaqueConnectorState; +} + +// SAFETY: We don't expose OpaqueConnector to users before `base` is initialized in +// Connector::new(), so `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_object. +unsafe impl ModeObject for OpaqueConnector { + type Driver = T; + + fn drm_dev(&self) -> &Device { + // SAFETY: The parent device for a DRM connector will never outlive the connector, and this + // pointer is invariant through the lifetime of the connector + unsafe { Device::borrow((*self.as_raw()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose DRM connectors to users before `base` is initialized + unsafe { &mut (*self.as_raw()).base } + } +} + +// SAFETY: Connectors are reference counted mode objects +unsafe impl RcModeObject for OpaqueConnector {} + +// SAFETY: `funcs` is initialized by DRM when the connector is allocated +unsafe impl ModeObjectVtable for OpaqueConnector { + type Vtable = bindings::drm_connector_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `funcs` is initialized by DRM when the connector is allocated + unsafe { *self.as_raw() }.funcs + } +} + +// SAFETY: Our connector interfaces are guaranteed to be thread-safe +unsafe impl Send for OpaqueConnector {} +unsafe impl Sync for OpaqueConnector {} + /// A privileged [`Connector`] obtained while holding a [`ModeConfigGuard`]. /// /// This provides access to various methods for [`Connector`] that must happen under lock, such as @@ -655,6 +747,83 @@ unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_connector_state) -> &'a mut S } } +// SAFETY: `funcs` is initialized by DRM when the connector is allocated +unsafe impl ModeObjectVtable for ConnectorState { + type Vtable = bindings::drm_connector_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.connector().vtable() + } +} + +impl ConnectorState { + super::impl_from_opaque_mode_obj! { + fn <'a, D, C>(&'a OpaqueConnectorState) -> &'a Self + where + T: DriverConnectorState; + use + C as DriverConnector, + D as KmsDriver + } +} + +/// A [`struct drm_connector_state`] without a known [`DriverConnectorState`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`DriverConnectorState`] +/// implementation for a [`struct drm_connector_state`] automatically. It is identical to +/// [`Connector`], except that it does not provide access to the driver's private data. +/// +/// # Invariants +/// +/// - `state` is initialized for as long as this object is exposed to users. +/// - The data layout of this type is identical to [`struct drm_connector_state`]. +/// - The DRM C API and our interface guarantees that only the user has mutable access to `state`, +/// up until [`drm_atomic_helper_commit_hw_done`] is called. Therefore, `connector` follows rust's +/// data aliasing rules and does not need to be behind an [`Opaque`] type. +/// +/// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_helper.h +#[repr(transparent)] +pub struct OpaqueConnectorState { + state: bindings::drm_connector_state, + _p: PhantomData, +} + +impl AsRawConnectorState for OpaqueConnectorState { + type Connector = OpaqueConnector; +} + +impl private::AsRawConnectorState for OpaqueConnectorState { + fn as_raw(&self) -> &bindings::drm_connector_state { + &self.state + } + + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_connector_state { + &mut self.state + } +} + +impl FromRawConnectorState for OpaqueConnectorState { + unsafe fn from_raw<'a>(ptr: *const bindings::drm_connector_state) -> &'a Self { + // SAFETY: Our data layout is identical to `bindings::drm_connector_state` + unsafe { &*ptr.cast() } + } + + unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_connector_state) -> &'a mut Self { + // SAFETY: Our data layout is identical to `bindings::drm_connector_state` + unsafe { &mut *ptr.cast() } + } +} + +// SAFETY: See OpaqueConnector's ModeObjectVtable implementation +unsafe impl ModeObjectVtable for OpaqueConnectorState { + type Vtable = bindings::drm_connector_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.connector().vtable() + } +} + unsafe extern "C" fn atomic_duplicate_state_callback( connector: *mut bindings::drm_connector, ) -> *mut bindings::drm_connector_state { From patchwork Wed Mar 5 22:59:31 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003642 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id DC67DC28B25 for ; Wed, 5 Mar 2025 23:06:52 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 2985A10E853; Wed, 5 Mar 2025 23:06:52 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="AAXJuDb0"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 96EF010E84F for ; Wed, 5 Mar 2025 23:06:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216009; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=FiII/UFZj0PJa3aqHS4adfK6RLGvexStwXEtTIs9AeA=; b=AAXJuDb0xn5cnkZ9lXlvrNX3xW839UpDifTcL/9ZHzrPtZF1WrOQ7SAHuaKlnY7zfecCen UDuhn1B/NC91bWMGjBshHIOoHZia1Am488Q4901SSsX5fwUUvU8iTV83flO26KBCuWeWFp Bx+j5viIl8/irDFP+dgxWEiD7tBSLHs= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-354-Xad8I59GMX2nR9Rn7DYrPA-1; Wed, 05 Mar 2025 18:06:46 -0500 X-MC-Unique: Xad8I59GMX2nR9Rn7DYrPA-1 X-Mimecast-MFC-AGG-ID: Xad8I59GMX2nR9Rn7DYrPA_1741216004 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F0DCA1800361; Wed, 5 Mar 2025 23:06:43 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id ECAF5300019E; Wed, 5 Mar 2025 23:06:39 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 15/33] rust: drm/kms: Add OpaqueCrtc and OpaqueCrtcState Date: Wed, 5 Mar 2025 17:59:31 -0500 Message-ID: <20250305230406.567126-16-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This is the same thing as OpaqueConnector and OpaqueConnectorState, but for CRTCs now. Signed-off-by: Lyude Paul --- V3: * Add safety comment for implementation of ModeObject * Add safety comments to AsRawCrtc implementation * Implement try_from_opaque() and from_opaque() using a macro Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/crtc.rs | 167 +++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 9950b09754072..bcdd452ff7b10 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -4,7 +4,9 @@ //! //! C header: [`include/drm/drm_crtc.h`](srctree/include/drm/drm_crtc.h) -use super::{plane::*, KmsDriver, ModeObject, StaticModeObject, UnregisteredKmsDevice}; +use super::{ + plane::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice, +}; use crate::{ alloc::KBox, bindings, @@ -185,6 +187,25 @@ fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { // SAFETY: CRTCs are non-refcounted modesetting objects unsafe impl StaticModeObject for Crtc {} +// SAFETY: `funcs` is initialized when the crtc is allocated +unsafe impl ModeObjectVtable for Crtc { + type Vtable = bindings::drm_crtc_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `as_raw()` always returns a valid pointer to a CRTC + unsafe { *self.as_raw() }.funcs + } +} + +impl Crtc { + super::impl_from_opaque_mode_obj! { + fn <'a, D>(&'a OpaqueCrtc) -> &'a Self; + use + T as DriverCrtc, + D as KmsDriver + } +} + /// A [`Crtc`] that has not yet been registered with userspace. /// /// KMS registration is single-threaded, so this object is not thread-safe. @@ -361,6 +382,86 @@ fn mask(&self) -> u32 { } impl RawCrtc for T {} +/// A [`struct drm_crtc`] without a known [`DriverCrtc`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`DriverCrtc`] implementation +/// for a [`struct drm_crtc`] automatically. It is identical to [`Crtc`], except that it does not +/// provide access to the driver's private data. +/// +/// It may be upcasted to a full [`Crtc`] using [`Crtc::from_opaque`] or +/// [`Crtc::try_from_opaque`]. +/// +/// # Invariants +/// +/// - `crtc` is initialized for as long as this object is made available to users. +/// - The data layout of this structure is equivalent to [`struct drm_crtc`]. +/// +/// [`struct drm_crtc`]: srctree/include/drm/drm_crtc.h +#[repr(transparent)] +pub struct OpaqueCrtc { + crtc: Opaque, + _p: PhantomData, +} + +impl Sealed for OpaqueCrtc {} + +// SAFETY: +// - Via our type variants our data layout is identical to `drm_crtc` +// - Since we don't expose `OpaqueCrtc` to users before it has been initialized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `drm_crtc`. +unsafe impl AsRawCrtc for OpaqueCrtc { + fn as_raw(&self) -> *mut bindings::drm_crtc { + self.crtc.get() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_crtc) -> &'a Self { + // SAFETY: Our data layout starts with `bindings::drm_crtc` + unsafe { &*ptr.cast() } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::create_objects has been +// called. +unsafe impl ModesettableCrtc for OpaqueCrtc { + type State = OpaqueCrtcState; +} + +// SAFETY: We don't expose OpaqueCrtc to users before `base` is initialized in Crtc::::new(), +// so `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_object. +unsafe impl ModeObject for OpaqueCrtc { + type Driver = T; + + fn drm_dev(&self) -> &Device { + // SAFETY: The parent device for a DRM connector will never outlive the connector, and this + // pointer is invariant through the lifetime of the connector + unsafe { Device::borrow((*self.as_raw()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose DRM connectors to users before `base` is initialized + unsafe { addr_of_mut!((*self.as_raw()).base) } + } +} + +// SAFETY: CRTCs are non-refcounted modesetting objects +unsafe impl StaticModeObject for OpaqueCrtc {} + +// SAFETY: Our CRTC interface is guaranteed to be thread-safe +unsafe impl Send for OpaqueCrtc {} + +// SAFETY: Our CRTC interface is guaranteed to be thread-safe +unsafe impl Sync for OpaqueCrtc {} + +// SAFETY: `funcs` is initialized when the CRTC is allocated +unsafe impl ModeObjectVtable for OpaqueCrtc { + type Vtable = bindings::drm_crtc_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `as_raw()` always returns a valid pointer to a crtc + unsafe { (*self.as_raw()).funcs } + } +} + unsafe impl Zeroable for bindings::drm_crtc_state {} impl Sealed for CrtcState {} @@ -429,6 +530,26 @@ fn deref_mut(&mut self) -> &mut Self::Target { } } +// SAFETY: Shares the safety guarantee of Crtc's ModeObjectVtable impl +unsafe impl ModeObjectVtable for CrtcState { + type Vtable = bindings::drm_crtc_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.crtc().vtable() + } +} + +impl CrtcState { + super::impl_from_opaque_mode_obj! { + fn <'a, D, C>(&'a OpaqueCrtcState) -> &'a Self + where + T: DriverCrtcState; + use + C as DriverCrtc, + D as KmsDriver + } +} + /// A trait implemented by any type which can produce a reference to a [`struct drm_crtc_state`]. /// /// This is implemented internally by DRM. @@ -502,6 +623,50 @@ unsafe fn from_raw<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Self { } } +/// A [`struct drm_crtc_state`] without a known [`DriverCrtcState`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`DriverCrtcState`] +/// implementation for a [`struct drm_crtc_state`] automatically. It is identical to [`Crtc`], +/// except that it does not provide access to the driver's private data. +/// +/// # Invariants +/// +/// - `state` is initialized for as long as this object is exposed to users. +/// - The data layout of this type is identical to [`struct drm_crtc_state`]. +/// +/// [`struct drm_crtc_state`]: srctree/include/drm/drm_crtc.h +#[repr(transparent)] +pub struct OpaqueCrtcState { + state: Opaque, + _p: PhantomData, +} + +impl AsRawCrtcState for OpaqueCrtcState { + type Crtc = OpaqueCrtc; +} + +impl private::AsRawCrtcState for OpaqueCrtcState { + fn as_raw(&self) -> *mut bindings::drm_crtc_state { + self.state.get() + } +} + +impl FromRawCrtcState for OpaqueCrtcState { + unsafe fn from_raw<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Self { + // SAFETY: Our data layout is identical to `bindings::drm_crtc_state` + unsafe { &*(ptr.cast()) } + } +} + +// SAFETY: Shares the safety guarantees of OpaqueCrtc's ModeObjectVtable impl +unsafe impl ModeObjectVtable for OpaqueCrtcState { + type Vtable = bindings::drm_crtc_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.crtc().vtable() + } +} + unsafe extern "C" fn crtc_destroy_callback(crtc: *mut bindings::drm_crtc) { // SAFETY: DRM guarantees that `crtc` points to a valid initialized `drm_crtc`. unsafe { bindings::drm_crtc_cleanup(crtc) }; From patchwork Wed Mar 5 22:59:32 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003645 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 76CA0C19F32 for ; Wed, 5 Mar 2025 23:07:23 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id CABDC10E855; Wed, 5 Mar 2025 23:07:22 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="dZBd+sqM"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id D549810E855 for ; Wed, 5 Mar 2025 23:07:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216039; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BXbABbsFOrkboMzt8NBf4+h/5hP5zznTgfYSP2dMRX4=; b=dZBd+sqM3lg3O64/FOzRuyox6Bvnykl1gMFMcLLAw1xOifdVJmMeWEheprKE8Xzo9S4GAQ QW0WnD+tagb1GXItoBUM2WWlju18KiZduJ7/QQ7jSvadrrPoZ5uKAUBYoIwsSVX7QgnSrK gQTQJ+jgKPE8UIErMZoGzNkbpnwL7Rs= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-265-LeCARbz_Ndugqsb0rL7kxg-1; Wed, 05 Mar 2025 18:06:52 -0500 X-MC-Unique: LeCARbz_Ndugqsb0rL7kxg-1 X-Mimecast-MFC-AGG-ID: LeCARbz_Ndugqsb0rL7kxg_1741216010 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 5FD111955DBA; Wed, 5 Mar 2025 23:06:50 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 4557D300019E; Wed, 5 Mar 2025 23:06:47 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 16/33] rust: drm/kms: Add OpaquePlane and OpaquePlaneState Date: Wed, 5 Mar 2025 17:59:32 -0500 Message-ID: <20250305230406.567126-17-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Same thing as OpaqueCrtc and OpaqueCrtcState, but for plane states now. Signed-off-by: Lyude Paul --- V3: * Add safety comment to implementation of ModeObject * Add safety comments to implementation of AsRawPlane * Implement try_from_opaque() and from_opaque() using a macro * Add missing upcasts * Use addr_of_mut!() instead of &mut Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/plane.rs | 176 ++++++++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index d1fabdf42df54..be69dc16c6cc7 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -4,7 +4,7 @@ //! //! C header: [`include/drm/drm_plane.h`](srctree/include/drm/drm_plane.h) -use super::{KmsDriver, ModeObject, StaticModeObject, UnregisteredKmsDevice}; +use super::{KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice}; use crate::{ alloc::KBox, bindings, @@ -169,6 +169,25 @@ fn deref(&self) -> &Self::Target { } } +// SAFETY: `funcs` is initialized when the plane is allocated +unsafe impl ModeObjectVtable for Plane { + type Vtable = bindings::drm_plane_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `as_raw()` always returns a valid plane pointer + unsafe { *self.as_raw() }.funcs + } +} + +impl Plane { + super::impl_from_opaque_mode_obj! { + fn <'a, D>(&'a OpaquePlane) -> &'a Self; + use + T as DriverPlane, + D as KmsDriver + } +} + /// A [`Plane`] that has not yet been registered with userspace. /// /// KMS registration is single-threaded, so this object is not thread-safe. @@ -396,6 +415,84 @@ fn mask(&self) -> u32 { } impl RawPlane for T {} +/// A [`struct drm_plane`] without a known [`DriverPlane`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`DriverPlane`] implementation +/// for a [`struct drm_plane`] automatically. It is identical to [`Plane`], except that it does not +/// provide access to the driver's private data. +/// +/// It may be upcasted to a full [`Plane`] using [`Plane::from_opaque`] or +/// [`Plane::try_from_opaque`]. +/// +/// # Invariants +/// +/// - `plane` is initialized for as long as this object is made available to users. +/// - The data layout of this structure is equivalent to [`struct drm_plane`]. +/// +/// [`struct drm_plane`]: srctree/include/drm/drm_plane.h +#[repr(transparent)] +pub struct OpaquePlane { + plane: Opaque, + _p: PhantomData, +} + +impl Sealed for OpaquePlane {} + +// SAFETY: +// * Via our type variants our data layout is identical to `drm_plane` +// * Since we don't expose `plane` to users before it has been initialized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `drm_plane`. +unsafe impl AsRawPlane for OpaquePlane { + fn as_raw(&self) -> *mut bindings::drm_plane { + self.plane.get() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_plane) -> &'a Self { + // SAFETY: Our data layout is identical to `bindings::drm_plane` + unsafe { &*ptr.cast() } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::create_objects has been +// called. +unsafe impl ModesettablePlane for OpaquePlane { + type State = OpaquePlaneState; +} + +// SAFETY: We don't expose OpaquePlane to users before `base` is initialized in +// Plane::::new(), so `raw_mode_obj` always returns a valid pointer to a +// bindings::drm_mode_object. +unsafe impl ModeObject for OpaquePlane { + type Driver = T; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM planes exist for as long as the device does, so this pointer is always valid + unsafe { Device::borrow((*self.as_raw()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose DRM planes to users before `base` is initialized + unsafe { addr_of_mut!((*self.as_raw()).base) } + } +} + +// SAFETY: Planes do not have a refcount +unsafe impl StaticModeObject for OpaquePlane {} + +// SAFETY: `funcs` is initialized when the plane is allocated +unsafe impl ModeObjectVtable for OpaquePlane { + type Vtable = bindings::drm_plane_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `as_raw()` always returns a valid pointer to a plane + unsafe { *self.as_raw() }.funcs + } +} + +// SAFETY: Our plane interfaces are guaranteed to be thread-safe +unsafe impl Send for OpaquePlane {} +unsafe impl Sync for OpaquePlane {} + /// A trait implemented by any type which can produce a reference to a [`struct drm_plane_state`]. /// /// This is implemented internally by DRM. @@ -572,6 +669,83 @@ fn deref_mut(&mut self) -> &mut Self::Target { } } +// SAFETY: Shares the safety guarantee of Plane's vtable impl +unsafe impl ModeObjectVtable for PlaneState { + type Vtable = bindings::drm_plane_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.plane().vtable() + } +} + +impl PlaneState { + super::impl_from_opaque_mode_obj! { + fn <'a, D, P>(&'a OpaquePlaneState) -> &'a Self + where + T: DriverPlaneState; + use + P as DriverPlane, + D as KmsDriver + } +} + +/// A [`struct drm_plane_state`] without a known [`DriverPlaneState`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`DriverPlaneState`] +/// implementation for a [`struct drm_plane_state`] automatically. It is identical to [`Plane`], +/// except that it does not provide access to the driver's private data. +/// +/// # Invariants +/// +/// - The DRM C API and our interface guarantees that only the user has mutable access to `state`, +/// up until [`drm_atomic_helper_commit_hw_done`] is called. Therefore, `plane` follows rust's +/// data aliasing rules and does not need to be behind an [`Opaque`] type. +/// - `state` is initialized for as long as this object is exposed to users. +/// - The data layout of this structure is identical to [`struct drm_plane_state`]. +/// +/// [`struct drm_plane_state`]: srctree/include/drm/drm_plane.h +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_helper.h +#[repr(transparent)] +pub struct OpaquePlaneState { + state: bindings::drm_plane_state, + _p: PhantomData, +} + +impl AsRawPlaneState for OpaquePlaneState { + type Plane = OpaquePlane; +} + +impl private::AsRawPlaneState for OpaquePlaneState { + fn as_raw(&self) -> &bindings::drm_plane_state { + &self.state + } + + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_plane_state { + &mut self.state + } +} + +impl FromRawPlaneState for OpaquePlaneState { + unsafe fn from_raw<'a>(ptr: *const bindings::drm_plane_state) -> &'a Self { + // SAFETY: Our data layout is identical to `ptr` + unsafe { &*ptr.cast() } + } + + unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_plane_state) -> &'a mut Self { + // SAFETY: Our data layout is identical to `ptr` + unsafe { &mut *ptr.cast() } + } +} + +// SAFETY: Shares the safety guarantee of OpaquePlane's vtable impl +unsafe impl ModeObjectVtable for OpaquePlaneState { + type Vtable = bindings::drm_plane_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.plane().vtable() + } +} + unsafe extern "C" fn plane_destroy_callback(plane: *mut bindings::drm_plane) { // SAFETY: DRM guarantees that `plane` points to a valid initialized `drm_plane`. unsafe { bindings::drm_plane_cleanup(plane) }; From patchwork Wed Mar 5 22:59:33 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003644 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 0723EC19F32 for ; Wed, 5 Mar 2025 23:07:03 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 509E610E84F; Wed, 5 Mar 2025 23:07:03 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="iGSgPfUP"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id B4E7410E84F for ; Wed, 5 Mar 2025 23:07:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216021; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=iGhdforG/hfsTGcf1d8lWIscR/N9faUXID3wN0fe8hE=; b=iGSgPfUPg3VXLesHXUxyj85lVsp/ED/Ll+pzlw+k25uzpVhc4Qlxw9cqWwkvAvIYKOi9Rv 5GWXhDHHu02nPL9i6rmlNTzzZX2zaXUeJ4bXBI3vxU8RCoiZygqJIuiMje4WA0LVAbGT0x Ke9HIka80GKsHXOfQxTIHsz+qXlE0FE= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-391-7XKPghc_PD-C1h_03dlyvw-1; Wed, 05 Mar 2025 18:06:58 -0500 X-MC-Unique: 7XKPghc_PD-C1h_03dlyvw-1 X-Mimecast-MFC-AGG-ID: 7XKPghc_PD-C1h_03dlyvw_1741216016 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6ED5F180025A; Wed, 5 Mar 2025 23:06:56 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 57C33300019E; Wed, 5 Mar 2025 23:06:53 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 17/33] rust: drm/kms: Add OpaqueEncoder Date: Wed, 5 Mar 2025 17:59:33 -0500 Message-ID: <20250305230406.567126-18-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Same thing as OpaquePlane, but for encoders now. Signed-off-by: Lyude Paul --- V3: * Add safety comment to ModeObject implementation * Add safety comments for AsRawEncoder * Implement try_from_opaque() and from_opaque() using a macro Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/encoder.rs | 91 +++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms/encoder.rs b/rust/kernel/drm/kms/encoder.rs index 2e4e88055c890..9ae2ff8eb5d50 100644 --- a/rust/kernel/drm/kms/encoder.rs +++ b/rust/kernel/drm/kms/encoder.rs @@ -4,7 +4,7 @@ //! //! C header: [`include/drm/drm_encoder.h`](srctree/include/drm/drm_encoder.h) -use super::{KmsDriver, ModeObject, StaticModeObject, UnregisteredKmsDevice}; +use super::{KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice}; use crate::{ alloc::KBox, drm::device::Device, @@ -219,6 +219,25 @@ unsafe fn from_raw<'a>(ptr: *mut bindings::drm_encoder) -> &'a Self { } } +// SAFETY: `funcs` is initialized when the encoder is allocated +unsafe impl ModeObjectVtable for Encoder { + type Vtable = bindings::drm_encoder_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `as_raw()` always returns a valid pointer to an encoder + unsafe { *self.as_raw() }.funcs + } +} + +impl Encoder { + super::impl_from_opaque_mode_obj! { + fn <'a, D>(&'a OpaqueEncoder) -> &'a Self; + use + T as DriverEncoder, + D as KmsDriver + } +} + /// A [`Encoder`] that has not yet been registered with userspace. /// /// KMS registration is single-threaded, so this object is not thread-safe. @@ -311,6 +330,76 @@ pub fn new<'a, 'b: 'a>( } } +/// A [`struct drm_encoder`] without a known [`DriverEncoder`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`DriverEncoder`] implementation +/// for a [`struct drm_encoder`] automatically. It is identical to [`Encoder`], except that it does not +/// provide access to the driver's private data. +/// +/// # Invariants +/// +/// Same as [`Encoder`]. +#[repr(transparent)] +pub struct OpaqueEncoder { + encoder: Opaque, + _p: PhantomData, +} + +impl Sealed for OpaqueEncoder {} + +// SAFETY: All of our encoder interfaces are thread-safe +unsafe impl Send for OpaqueEncoder {} + +// SAFETY: All of our encoder interfaces are thread-safe +unsafe impl Sync for OpaqueEncoder {} + +// SAFETY: We don't expose OpaqueEncoder to users before `base` is initialized in +// OpaqueEncoder::new(), so `raw_mode_obj` always returns a valid poiner to a +// bindings::drm_mode_object. +unsafe impl ModeObject for OpaqueEncoder { + type Driver = T; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM encoders exist for as long as the device does, so this pointer is always + // valid + unsafe { Device::borrow((*self.encoder.get()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose Encoder to users before it's initialized, so `base` is always + // initialized + unsafe { addr_of_mut!((*self.encoder.get()).base) } + } +} + +// SAFETY: Encoders do not have a refcount +unsafe impl StaticModeObject for OpaqueEncoder {} + +// SAFETY: +// - Via our type variants our data layout is identical to with `drm_encoder` +// - Since we don't expose `Encoder` to users before it has been initialized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `drm_encoder`. +unsafe impl AsRawEncoder for OpaqueEncoder { + fn as_raw(&self) -> *mut bindings::drm_encoder { + self.encoder.get() + } + + unsafe fn from_raw<'a>(ptr: *mut bindings::drm_encoder) -> &'a Self { + // SAFETY: Our data layout is identical to `bindings::drm_encoder` + unsafe { &*ptr.cast() } + } +} + +// SAFETY: `funcs` is initialized when the encoder is allocated +unsafe impl ModeObjectVtable for OpaqueEncoder { + type Vtable = bindings::drm_encoder_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `as_raw()` always returns a valid pointer to an encoder + unsafe { *self.as_raw() }.funcs + } +} + unsafe extern "C" fn encoder_destroy_callback( encoder: *mut bindings::drm_encoder, ) { From patchwork Wed Mar 5 22:59:34 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003658 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 6462DC282EC for ; Wed, 5 Mar 2025 23:09:20 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id C338010E861; Wed, 5 Mar 2025 23:09:19 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="DIb9xQyg"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 3D09110E861 for ; Wed, 5 Mar 2025 23:09:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216157; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BIJKMPeeyjM+BCdj2obTh0M4U7dhUYRDeftthaamkpQ=; b=DIb9xQyggwJHPlAmpXJl6UAx0rFte8srP2t9mOlNkEHQR92NnAPc2TdoWqCzf5slU8hDyB iGo2Emb5qz9W+xEgPKlXqdyjeVfROe89yCvKFrtXKq9GhSAqO9dApdCKYbrhzpzf35AcQn vzc4YyLsBH5dDTUyeFC4F4f49N5USLM= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-149-Xq7aBEVQMQ-UyuNa0PK1UQ-1; Wed, 05 Mar 2025 18:07:19 -0500 X-MC-Unique: Xq7aBEVQMQ-UyuNa0PK1UQ-1 X-Mimecast-MFC-AGG-ID: Xq7aBEVQMQ-UyuNa0PK1UQ_1741216037 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B7DDE180899B; Wed, 5 Mar 2025 23:07:16 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 55F4930001A1; Wed, 5 Mar 2025 23:07:12 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Asahi Lina , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 18/33] rust: drm/kms: Add drm_atomic_state bindings Date: Wed, 5 Mar 2025 17:59:34 -0500 Message-ID: <20250305230406.567126-19-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Next up is introducing bindings that we can use to represent the global DRM atomic state, along with all of the various object states contained within. We do this by introducing a few new concepts: borrowed states, atomic state mutators, and atomic state composers. To understand these, we need to quickly touch upon the general life of an atomic commit. Assuming a driver does its own internal atomic commit, the procedure looks something like this: * Allocate a new atomic state * Duplicate the atomic state of each mode object we want to mutate, and add the duplicated state to the new atomic state * Check (possibly more then once) the atomic state, possibly modifying it along the way * Commit the atomic state to software (we'll call this commit time). At this point no new objects can be added to the state * Finish committing the atomic state to hardware asynchronously With this in mind, we introduce AtomicStateMutator and AtomicStateComposer (along with leaky variants intended for uses in FFI calls). An AtomicStateMutator allows mutating an atomic state but does not allow for adding new objects to the state. Subsequently, an AtomicStateComposer allows for both mutating an atomic state and adding new mode objects. We control when we expose each of these types in order to implement the limitations required by the aforementioned example. Note as well that AtomicStateComposer is intended to eventually be usable directly by drivers. In this scenario, a driver will be able to create an AtomicStateComposer (the equivalent of allocating an atomic state in C) and then commit it by passing it to our DRM bindings by-value, insuring that once the commit process begins it is impossible to keep using the AtomicStateComposer. The next part of this is allowing users to modify the atomic states of all of the objects contained within an atomic state. Since it's an extremely common usecase for objects to mutate the atomic state of multiple objects at once in an unpredictable order, we need a mechanism that will allow us to hand out &mut references to each state while ensuring at runtime that we do not break rust's data aliasing rules (which disallow us from ever having more then one &mut reference to the same piece of data). We do this by introducing the concept of a "borrowed" state. This is a very similar concept to RefCell, where it is ensured during runtime that when a &mut reference is taken out another one cannot be created until the corresponding Ref object has been dropped. Our equivalent Ref types are ConnectorState, BorrowedCrtcState, and BorrowedPlaneStateMutator. Each one of these types can be used in the same manner as a Ref - no additional borrows for an atomic state may be taken until the existing one has been dropped. Subsequently, all of these types implement their respective AsRaw* and FromRaw* counter-parts - and allow dereferencing to each driver-private data structure for fully qualified borrows (like CrtcState<'a, CrtcStateMutator>. This allows a pretty clean way of mutating multiple states at once without ever breaking rust's mutability rules. We'll use all of these types over the next few commits to begin introducing various atomic modeset callbacks to each mode object type. Signed-off-by: Lyude Paul --- V3: * Drop the TODO about printing a kernel error in ConnectorStateMutator I thought this was something we'd want early on in designing this, but since then I'm pretty sure we just want to return None - there are valid cases where we'd get None while doing connector iteration through an atomic state * Improve safety comments in ConnectorStateMutator::new() * Rename Borrowed*State to *StateMutator I think this makes things a lot clearer, as well - cleanup the documentation regarding this. * Drop plane state iterator for now. It's not that we don't need this, it's just that I haven't actually finished writing these up for all types so I'd rather focus on that later now that we've demonstrated it's a thing that is possible. And it at least shouldn't be needed for getting these bindings/rvkms upstream. * Drop num_plane() for the time being Without the plane state iterator in this patch series there's no current usecase for this, so just drop the function for the time being and we'll reintroduce it when it's ready. Signed-off-by: Lyude Paul --- rust/helpers/drm/atomic.c | 32 +++ rust/helpers/drm/drm.c | 3 + rust/kernel/drm/kms.rs | 1 + rust/kernel/drm/kms/atomic.rs | 359 +++++++++++++++++++++++++++++++ rust/kernel/drm/kms/connector.rs | 104 ++++++++- rust/kernel/drm/kms/crtc.rs | 103 ++++++++- rust/kernel/drm/kms/plane.rs | 105 ++++++++- 7 files changed, 700 insertions(+), 7 deletions(-) create mode 100644 rust/helpers/drm/atomic.c create mode 100644 rust/kernel/drm/kms/atomic.rs diff --git a/rust/helpers/drm/atomic.c b/rust/helpers/drm/atomic.c new file mode 100644 index 0000000000000..fff70053f6943 --- /dev/null +++ b/rust/helpers/drm/atomic.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +void rust_helper_drm_atomic_state_get(struct drm_atomic_state *state) +{ + drm_atomic_state_get(state); +} + +void rust_helper_drm_atomic_state_put(struct drm_atomic_state *state) +{ + drm_atomic_state_put(state); +} + +// Macros for generating one repetitive atomic state accessors (like drm_atomic_get_new_plane_state) +#define STATE_FUNC(type, tense) \ + struct drm_ ## type ## _state *rust_helper_drm_atomic_get_ ## tense ## _ ## type ## _state( \ + const struct drm_atomic_state *state, \ + struct drm_ ## type *type \ + ) { \ + return drm_atomic_get_## tense ## _ ## type ## _state(state, type); \ + } +#define STATE_FUNCS(type) \ + STATE_FUNC(type, new); \ + STATE_FUNC(type, old); + +STATE_FUNCS(plane); +STATE_FUNCS(crtc); +STATE_FUNCS(connector); + +#undef STATE_FUNCS +#undef STATE_FUNC diff --git a/rust/helpers/drm/drm.c b/rust/helpers/drm/drm.c index 028b8ab429572..365f6807774d4 100644 --- a/rust/helpers/drm/drm.c +++ b/rust/helpers/drm/drm.c @@ -1,5 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 +#ifdef CONFIG_DRM_KMS_HELPER +#include "atomic.c" +#endif #include "gem.c" #ifdef CONFIG_DRM_GEM_SHMEM_HELPER #include "gem_shmem_helper.c" diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 1d6ca9c92659a..978bb6712ffbe 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -2,6 +2,7 @@ //! KMS driver abstractions for rust. +pub mod atomic; pub mod connector; pub mod crtc; pub mod encoder; diff --git a/rust/kernel/drm/kms/atomic.rs b/rust/kernel/drm/kms/atomic.rs new file mode 100644 index 0000000000000..3d5c70dbc4274 --- /dev/null +++ b/rust/kernel/drm/kms/atomic.rs @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! [`struct drm_atomic_state`] related bindings for rust. +//! +//! [`struct drm_atomic_state`]: srctree/include/drm/drm_atomic.h +use super::{connector::*, crtc::*, plane::*, KmsDriver, ModeObject}; +use crate::{ + bindings, + drm::device::Device, + error::{from_err_ptr, to_result}, + prelude::*, + types::*, +}; +use core::{cell::Cell, marker::*, mem::ManuallyDrop, ops::*, ptr::NonNull}; + +/// The main wrapper around [`struct drm_atomic_state`]. +/// +/// This type is usually embedded within another interface such as an [`AtomicStateMutator`]. +/// +/// # Invariants +/// +/// - The data layout of this type is identical to [`struct drm_atomic_state`]. +/// - `state` is initialized for as long as this type is exposed to users. +/// +/// [`struct drm_atomic_state`]: srctree/include/drm/drm_atomic.h +#[repr(transparent)] +pub struct AtomicState { + pub(super) state: Opaque, + _p: PhantomData, +} + +impl AtomicState { + /// Reconstruct an immutable reference to an atomic state from the given pointer + /// + /// # Safety + /// + /// `ptr` must point to a valid initialized instance of [`struct drm_atomic_state`]. + /// + /// [`struct drm_atomic_state`]: srctree/include/drm/drm_atomic.h + #[allow(dead_code)] + pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::drm_atomic_state) -> &'a Self { + // SAFETY: Our data layout is identical + // INVARIANT: Our safety contract upholds the guarantee that `state` is initialized for as + // long as this type is exposed to users. + unsafe { &*ptr.cast() } + } + + pub(crate) fn as_raw(&self) -> *mut bindings::drm_atomic_state { + self.state.get() + } + + /// Return the [`Device`] associated with this [`AtomicState`]. + pub fn drm_dev(&self) -> &Device { + // SAFETY: + // - `state` is initialized via our type invariants. + // - `dev` is invariant throughout the lifetime of `AtomicState` + unsafe { Device::borrow((*self.state.get()).dev) } + } + + /// Return the old atomic state for `crtc`, if it is present within this [`AtomicState`]. + pub fn get_old_crtc_state(&self, crtc: &C) -> Option<&C::State> + where + C: ModesettableCrtc + ModeObject, + { + // SAFETY: This function either returns NULL or a valid pointer to a `drm_crtc_state` + unsafe { + bindings::drm_atomic_get_old_crtc_state(self.as_raw(), crtc.as_raw()) + .as_ref() + .map(|p| C::State::from_raw(p)) + } + } + + /// Return the old atomic state for `plane`, if it is present within this [`AtomicState`]. + pub fn get_old_plane_state

(&self, plane: &P) -> Option<&P::State> + where + P: ModesettablePlane + ModeObject, + { + // SAFETY: This function either returns NULL or a valid pointer to a `drm_plane_state` + unsafe { + bindings::drm_atomic_get_old_plane_state(self.as_raw(), plane.as_raw()) + .as_ref() + .map(|p| P::State::from_raw(p)) + } + } + + /// Return the old atomic state for `connector` if it is present within this [`AtomicState`]. + pub fn get_old_connector_state(&self, connector: &C) -> Option<&C::State> + where + C: ModesettableConnector + ModeObject, + { + // SAFETY: This function either returns NULL or a valid pointer to a `drm_connector_state`. + unsafe { + bindings::drm_atomic_get_old_connector_state(self.as_raw(), connector.as_raw()) + .as_ref() + .map(|p| C::State::from_raw(p)) + } + } +} + +// SAFETY: DRM atomic state objects are always reference counted and the get/put functions satisfy +// the requirements. +unsafe impl AlwaysRefCounted for AtomicState { + fn inc_ref(&self) { + // SAFETY: `state` is initialized for as long as this type is exposed to users + unsafe { bindings::drm_atomic_state_get(self.state.get()) } + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: `obj` contains a valid non-null pointer to an initialized `Self`. + unsafe { bindings::drm_atomic_state_put(obj.as_ptr().cast()) } + } +} + +/// A smart-pointer for modifying the contents of an atomic state. +/// +/// As it's not unreasonable for a modesetting driver to want to have references to the state of +/// multiple modesetting objects at once, along with mutating multiple states for unique modesetting +/// objects at once, this type provides a mechanism for safely doing both of these things. +/// +/// To honor Rust's aliasing rules regarding mutable references, this structure ensures only one +/// mutable reference to a mode object's atomic state may exist at a time - and refuses to provide +/// another if one has already been taken out using runtime checks. +pub struct AtomicStateMutator { + /// The state being mutated. Note that the use of `ManuallyDrop` here is because mutators are + /// only constructed in FFI callbacks and thus borrow their references to the atomic state from + /// DRM. Composers, which make use of mutators internally, can potentially be owned by rust code + /// if a driver is performing an atomic commit internally - and thus will call the drop + /// implementation here. + state: ManuallyDrop>>, + + /// Bitmask of borrowed CRTC state objects + pub(super) borrowed_crtcs: Cell, + /// Bitmask of borrowed plane state objects + pub(super) borrowed_planes: Cell, + /// Bitmask of borrowed connector state objects + pub(super) borrowed_connectors: Cell, +} + +impl AtomicStateMutator { + /// Construct a new [`AtomicStateMutator`] + /// + /// # Safety + /// + /// `ptr` must point to a valid `drm_atomic_state` + #[allow(dead_code)] + pub(super) unsafe fn new(ptr: NonNull) -> Self { + Self { + // SAFETY: The data layout of `AtomicState` is identical to drm_atomic_state + // We use `ManuallyDrop` because `AtomicStateMutator` is only ever provided to users in + // the context of KMS callbacks. As such, skipping ref inc/dec for the atomic state is + // convienent for our bindings. + state: ManuallyDrop::new(unsafe { ARef::from_raw(ptr.cast()) }), + borrowed_planes: Cell::default(), + borrowed_crtcs: Cell::default(), + borrowed_connectors: Cell::default(), + } + } + + pub(crate) fn as_raw(&self) -> *mut bindings::drm_atomic_state { + self.state.as_raw() + } + + /// Return the [`Device`] for this [`AtomicStateMutator`]. + pub fn drm_dev(&self) -> &Device { + self.state.drm_dev() + } + + /// Retrieve the last committed atomic state for `crtc` if `crtc` has already been added to the + /// atomic state being composed. + /// + /// Returns `None` otherwise. + pub fn get_old_crtc_state(&self, crtc: &C) -> Option<&C::State> + where + C: ModesettableCrtc + ModeObject, + { + self.state.get_old_crtc_state(crtc) + } + + /// Retrieve the last committed atomic state for `connector` if `connector` has already been + /// added to the atomic state being composed. + /// + /// Returns `None` otherwise. + pub fn get_old_connector_state(&self, connector: &C) -> Option<&C::State> + where + C: ModesettableConnector + ModeObject, + { + self.state.get_old_connector_state(connector) + } + + /// Retrieve the last committed atomic state for `plane` if `plane` has already been added to + /// the atomic state being composed. + /// + /// Returns `None` otherwise. + pub fn get_old_plane_state

(&self, plane: &P) -> Option<&P::State> + where + P: ModesettablePlane + ModeObject, + { + self.state.get_old_plane_state(plane) + } + + /// Return a composer for `plane`s new atomic state if it was previously added to the atomic + /// state being composed. + /// + /// Returns `None` otherwise, or if another mutator still exists for this state. + pub fn get_new_crtc_state(&self, crtc: &C) -> Option> + where + C: ModesettableCrtc + ModeObject, + { + // SAFETY: DRM either returns NULL or a valid pointer to a `drm_crtc_state` + let state = + unsafe { bindings::drm_atomic_get_new_crtc_state(self.as_raw(), crtc.as_raw()) }; + + CrtcStateMutator::::new(self, NonNull::new(state)?) + } + + /// Return a composer for `plane`s new atomic state if it was previously added to the atomic + /// state being composed. + /// + /// Returns `None` otherwise, or if another mutator still exists for this state. + pub fn get_new_plane_state

(&self, plane: &P) -> Option> + where + P: ModesettablePlane + ModeObject, + { + // SAFETY: DRM either returns NULL or a valid pointer to a `drm_plane_state`. + let state = + unsafe { bindings::drm_atomic_get_new_plane_state(self.as_raw(), plane.as_raw()) }; + + PlaneStateMutator::::new(self, NonNull::new(state)?) + } + + /// Return a composer for `crtc`s new atomic state if it was previously added to the atomic + /// state being composed. + /// + /// Returns `None` otherwise, or if another mutator still exists for this state. + pub fn get_new_connector_state( + &self, + connector: &C, + ) -> Option> + where + C: ModesettableConnector + ModeObject, + { + // SAFETY: DRM either returns NULL or a valid pointer to a `drm_connector_state` + let state = unsafe { + bindings::drm_atomic_get_new_connector_state(self.as_raw(), connector.as_raw()) + }; + + ConnectorStateMutator::::new(self, NonNull::new(state)?) + } +} + +/// An [`AtomicStateMutator`] wrapper which is not yet part of any commit operation. +/// +/// Since it's not yet part of a commit operation, new mode objects may be added to the state. It +/// also holds a reference to the underlying [`AtomicState`] that will be released when this object +/// is dropped. +pub struct AtomicStateComposer(AtomicStateMutator); + +impl Deref for AtomicStateComposer { + type Target = AtomicStateMutator; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for AtomicStateComposer { + fn drop(&mut self) { + // SAFETY: We're in drop, so this is guaranteed to be the last possible reference + unsafe { ManuallyDrop::drop(&mut self.0.state) } + } +} + +impl AtomicStateComposer { + /// # Safety + /// + /// The caller guarantees that `ptr` points to a valid instance of `drm_atomic_state`. + #[allow(dead_code)] + pub(crate) unsafe fn new(ptr: NonNull) -> Self { + // SAFETY: see `AtomicStateMutator::from_raw()` + Self(unsafe { AtomicStateMutator::new(ptr) }) + } + + /// Attempt to add the state for `crtc` to the atomic state for this composer if it hasn't + /// already been added, and create a mutator for it. + /// + /// If a composer already exists for this `crtc`, this function returns `Error(EBUSY)`. If + /// attempting to add the state fails, another error code will be returned. + pub fn add_crtc_state(&self, crtc: &C) -> Result> + where + C: ModesettableCrtc + ModeObject, + { + // SAFETY: DRM will only return a valid pointer to a `drm_crtc_state` - or an error. + let state = unsafe { + from_err_ptr(bindings::drm_atomic_get_crtc_state( + self.as_raw(), + crtc.as_raw(), + )) + .map(|c| NonNull::new_unchecked(c)) + }?; + + CrtcStateMutator::::new(self, state).ok_or(EBUSY) + } + + /// Attempt to add the state for `plane` to the atomic state for this composer if it hasn't + /// already been added, and create a mutator for it. + /// + /// If a composer already exists for this `plane`, this function returns `Error(EBUSY)`. If + /// attempting to add the state fails, another error code will be returned. + pub fn add_plane_state

(&self, plane: &P) -> Result> + where + P: ModesettablePlane + ModeObject, + { + // SAFETY: DRM will only return a valid pointer to a `drm_plane_state` - or an error. + let state = unsafe { + from_err_ptr(bindings::drm_atomic_get_plane_state( + self.as_raw(), + plane.as_raw(), + )) + .map(|p| NonNull::new_unchecked(p)) + }?; + + PlaneStateMutator::::new(self, state).ok_or(EBUSY) + } + + /// Attempt to add the state for `connector` to the atomic state for this composer if it hasn't + /// already been added, and create a mutator for it. + /// + /// If a composer already exists for this `connector`, this function returns `Error(EBUSY)`. If + /// attempting to add the state fails, another error code will be returned. + pub fn add_connector_state( + &self, + connector: &C, + ) -> Result> + where + C: ModesettableConnector + ModeObject, + { + // SAFETY: DRM will only return a valid pointer to a `drm_plane_state` - or an error. + let state = unsafe { + from_err_ptr(bindings::drm_atomic_get_connector_state( + self.as_raw(), + connector.as_raw(), + )) + .map(|c| NonNull::new_unchecked(c)) + }?; + + ConnectorStateMutator::::new(self, state).ok_or(EBUSY) + } + + /// Attempt to add any planes affected by changes on `crtc` to this [`AtomicStateComposer`]. + /// + /// Will return an [`Error`] if this fails. + pub fn add_affected_planes(&self, crtc: &C) -> Result + where + C: ModesettableCrtc + ModeObject, + { + // SAFETY: Both .as_raw() values are guaranteed to return a valid pointer + to_result(unsafe { bindings::drm_atomic_add_affected_planes(self.as_raw(), crtc.as_raw()) }) + } +} diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index 4521643d19749..4435eebd60202 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -4,7 +4,9 @@ //! //! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_connector.h) -use super::{encoder::*, KmsDriver, ModeConfigGuard, ModeObject, ModeObjectVtable, RcModeObject}; +use super::{ + atomic::*, encoder::*, KmsDriver, ModeConfigGuard, ModeObject, ModeObjectVtable, RcModeObject, +}; use crate::{ alloc::KBox, bindings, @@ -16,10 +18,11 @@ types::{NotThreadSafe, Opaque}, }; use core::{ + cell::Cell, marker::*, mem::{self, ManuallyDrop}, ops::*, - ptr::{addr_of_mut, null_mut}, + ptr::{addr_of_mut, null_mut, NonNull}, stringify, }; use macros::{paste, pin_data}; @@ -824,6 +827,103 @@ fn vtable(&self) -> *const Self::Vtable { } } +/// An interface for mutating a [`Connector`]s atomic state. +/// +/// This type is typically returned by an [`AtomicStateMutator`] within contexts where it is +/// possible to safely mutate a connector's state. In order to uphold rust's data-aliasing rules, +/// only [`ConnectorStateMutator`] may exist at a time. +pub struct ConnectorStateMutator<'a, T: FromRawConnectorState> { + state: &'a mut T, + mask: &'a Cell, +} + +impl<'a, T: FromRawConnectorState> ConnectorStateMutator<'a, T> { + pub(super) fn new( + mutator: &'a AtomicStateMutator, + state: NonNull, + ) -> Option { + // SAFETY: + // - `connector` is invariant throughout the lifetime of the atomic state. + // - `state` is initialized by the time it is passed to this function. + // - We're guaranteed that `state` is compatible with `drm_connector` by type invariants. + let connector = unsafe { T::Connector::from_raw((*state.as_ptr()).connector) }; + let conn_mask = connector.mask(); + let borrowed_mask = mutator.borrowed_connectors.get(); + + if borrowed_mask & conn_mask == 0 { + mutator.borrowed_connectors.set(borrowed_mask | conn_mask); + Some(Self { + mask: &mutator.borrowed_connectors, + // SAFETY: We're guaranteed `state` is of `T` by type invariance, and we just + // confirmed by checking `borrowed_connectors` that no other mutable borrows have + // been taken out for `state` + state: unsafe { T::from_raw_mut(state.as_ptr()) }, + }) + } else { + None + } + } +} + +impl<'a, T: DriverConnectorState> Deref for ConnectorStateMutator<'a, ConnectorState> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.state.inner + } +} + +impl<'a, T: DriverConnectorState> DerefMut for ConnectorStateMutator<'a, ConnectorState> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.state.inner + } +} + +impl<'a, T: FromRawConnectorState> Drop for ConnectorStateMutator<'a, T> { + fn drop(&mut self) { + let mask = self.state.connector().mask(); + self.mask.set(self.mask.get() & !mask); + } +} + +impl<'a, T: FromRawConnectorState> AsRawConnectorState for ConnectorStateMutator<'a, T> { + type Connector = T::Connector; +} + +impl<'a, T: FromRawConnectorState> private::AsRawConnectorState for ConnectorStateMutator<'a, T> { + fn as_raw(&self) -> &bindings::drm_connector_state { + self.state.as_raw() + } + + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_connector_state { + // SAFETY: We're bound by the same safety contract as this function + unsafe { self.state.as_raw_mut() } + } +} + +// SAFETY: we inherit the safety guarantees of `T` +unsafe impl<'a, T> ModeObjectVtable for ConnectorStateMutator<'a, T> +where + T: FromRawConnectorState + ModeObjectVtable, +{ + type Vtable = T::Vtable; + + fn vtable(&self) -> *const Self::Vtable { + self.state.vtable() + } +} + +impl<'a, T: DriverConnectorState> ConnectorStateMutator<'a, ConnectorState> { + super::impl_from_opaque_mode_obj! { + fn (ConnectorStateMutator<'a, OpaqueConnectorState>) -> Self + where + T: DriverConnectorState; + use + C as DriverConnector, + D as KmsDriver + } +} + unsafe extern "C" fn atomic_duplicate_state_callback( connector: *mut bindings::drm_connector, ) -> *mut bindings::drm_connector_state { diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index bcdd452ff7b10..3b9c9d97fcf24 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -5,7 +5,8 @@ //! C header: [`include/drm/drm_crtc.h`](srctree/include/drm/drm_crtc.h) use super::{ - plane::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice, + atomic::*, plane::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, + UnregisteredKmsDevice, }; use crate::{ alloc::KBox, @@ -18,11 +19,11 @@ types::{NotThreadSafe, Opaque}, }; use core::{ - cell::UnsafeCell, + cell::{Cell, UnsafeCell}, marker::*, mem, ops::{Deref, DerefMut}, - ptr::{addr_of_mut, null, null_mut}, + ptr::{addr_of_mut, null, null_mut, NonNull}, }; use macros::vtable; @@ -667,6 +668,102 @@ fn vtable(&self) -> *const Self::Vtable { } } +/// An interface for mutating a [`Crtc`]s atomic state. +/// +/// This type is typically returned by an [`AtomicStateMutator`] within contexts where it is +/// possible to safely mutate a plane's state. In order to uphold rust's data-aliasing rules, only +/// [`CrtcStateMutator`] may exist at a time. +/// +/// # Invariants +/// +/// `self.state` always points to a valid instance of a [`FromRawCrtcState`] object. +pub struct CrtcStateMutator<'a, T: FromRawCrtcState> { + state: NonNull, + mask: &'a Cell, +} + +impl<'a, T: FromRawCrtcState> CrtcStateMutator<'a, T> { + pub(super) fn new( + mutator: &'a AtomicStateMutator, + state: NonNull, + ) -> Option { + // SAFETY: `crtc` is invariant throughout the lifetime of the atomic state, and always + // points to a valid `Crtc` + let crtc = unsafe { T::Crtc::from_raw((*state.as_ptr()).crtc) }; + let crtc_mask = crtc.mask(); + let borrowed_mask = mutator.borrowed_crtcs.get(); + + if borrowed_mask & crtc_mask == 0 { + mutator.borrowed_crtcs.set(borrowed_mask | crtc_mask); + Some(Self { + mask: &mutator.borrowed_crtcs, + state: state.cast(), + }) + } else { + None + } + } +} + +impl<'a, T: DriverCrtcState> CrtcStateMutator<'a, CrtcState> { + super::impl_from_opaque_mode_obj! { + fn (CrtcStateMutator<'a, OpaqueCrtcState>) -> Self + where + T: DriverCrtcState; + use + T as DriverCrtc, + D as KmsDriver + } +} + +impl<'a, T: FromRawCrtcState> Drop for CrtcStateMutator<'a, T> { + fn drop(&mut self) { + // SAFETY: Our interface is proof that we are the only ones with a reference to this data + let mask = unsafe { self.state.as_ref() }.crtc().mask(); + self.mask.set(self.mask.get() & !mask); + } +} + +impl<'a, T: DriverCrtcState> Deref for CrtcStateMutator<'a, CrtcState> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Our interface ensures that `self.state.inner` follows rust's data-aliasing rules, + // so this is safe + unsafe { &*(*self.state.as_ptr()).inner.get() } + } +} + +impl<'a, T: DriverCrtcState> DerefMut for CrtcStateMutator<'a, CrtcState> { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: Our interface ensures that `self.state.inner` follows rust's data-aliasing rules, + // so this is safe + unsafe { (*self.state.as_ptr()).inner.get_mut() } + } +} + +impl<'a, T: FromRawCrtcState> AsRawCrtcState for CrtcStateMutator<'a, T> { + type Crtc = T::Crtc; +} + +impl<'a, T: FromRawCrtcState> private::AsRawCrtcState for CrtcStateMutator<'a, T> { + fn as_raw(&self) -> *mut bindings::drm_crtc_state { + self.state.as_ptr().cast() + } +} + +// SAFETY: Shares the safety guarantees of T::Crtc's ModeObjectVtable impl +unsafe impl<'a, T: FromRawCrtcState> ModeObjectVtable for CrtcStateMutator<'a, T> +where + T::Crtc: ModeObjectVtable, +{ + type Vtable = ::Vtable; + + fn vtable(&self) -> *const Self::Vtable { + self.crtc().vtable() + } +} + unsafe extern "C" fn crtc_destroy_callback(crtc: *mut bindings::drm_crtc) { // SAFETY: DRM guarantees that `crtc` points to a valid initialized `drm_crtc`. unsafe { bindings::drm_crtc_cleanup(crtc) }; diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index be69dc16c6cc7..4e73c2966ca22 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -4,7 +4,9 @@ //! //! C header: [`include/drm/drm_plane.h`](srctree/include/drm/drm_plane.h) -use super::{KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice}; +use super::{ + atomic::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice, +}; use crate::{ alloc::KBox, bindings, @@ -16,11 +18,12 @@ types::{NotThreadSafe, Opaque}, }; use core::{ + cell::Cell, marker::*, mem, ops::*, pin::Pin, - ptr::{addr_of_mut, null, null_mut}, + ptr::{addr_of_mut, null, null_mut, NonNull}, }; use macros::pin_data; @@ -746,6 +749,104 @@ fn vtable(&self) -> *const Self::Vtable { } } +/// An interface for mutating a [`Plane`]s atomic state. +/// +/// This type is typically returned by an [`AtomicStateMutator`] within contexts where it is +/// possible to safely mutate a plane's state. In order to uphold rust's data-aliasing rules, only +/// [`PlaneStateMutator`] may exist at a time. +pub struct PlaneStateMutator<'a, T: FromRawPlaneState> { + state: &'a mut T, + mask: &'a Cell, +} + +impl<'a, T: FromRawPlaneState> PlaneStateMutator<'a, T> { + pub(super) fn new( + mutator: &'a AtomicStateMutator, + state: NonNull, + ) -> Option { + // SAFETY: `plane` is invariant throughout the lifetime of the atomic state, is + // initialized by this point, and we're guaranteed it is of type `AsRawPlane` by type + // invariance + let plane = unsafe { T::Plane::from_raw((*state.as_ptr()).plane) }; + let plane_mask = plane.mask(); + let borrowed_mask = mutator.borrowed_planes.get(); + + if borrowed_mask & plane_mask == 0 { + mutator.borrowed_planes.set(borrowed_mask | plane_mask); + Some(Self { + mask: &mutator.borrowed_planes, + // SAFETY: We're guaranteed `state` is of `FromRawPlaneState` by type invariance, + // and we just confirmed by checking `borrowed_planes` that no other mutable borrows + // have been taken out for `state` + state: unsafe { T::from_raw_mut(state.as_ptr()) }, + }) + } else { + None + } + } +} + +impl<'a, T: FromRawPlaneState> Drop for PlaneStateMutator<'a, T> { + fn drop(&mut self) { + let mask = self.state.plane().mask(); + self.mask.set(self.mask.get() & !mask); + } +} + +impl<'a, T: FromRawPlaneState> AsRawPlaneState for PlaneStateMutator<'a, T> { + type Plane = T::Plane; +} + +impl<'a, T: FromRawPlaneState> private::AsRawPlaneState for PlaneStateMutator<'a, T> { + fn as_raw(&self) -> &bindings::drm_plane_state { + self.state.as_raw() + } + + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_plane_state { + // SAFETY: This function is bound by the same safety contract as `self.inner.as_raw_mut()` + unsafe { self.state.as_raw_mut() } + } +} + +impl<'a, T: FromRawPlaneState> Sealed for PlaneStateMutator<'a, T> {} + +impl<'a, T: DriverPlaneState> Deref for PlaneStateMutator<'a, PlaneState> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.state.inner + } +} + +impl<'a, T: DriverPlaneState> DerefMut for PlaneStateMutator<'a, PlaneState> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.state.inner + } +} + +// SAFETY: Shares the safety guarantees of `T`'s ModeObjectVtable impl +unsafe impl<'a, T: FromRawPlaneState> ModeObjectVtable for PlaneStateMutator<'a, T> +where + T: FromRawPlaneState + ModeObjectVtable, +{ + type Vtable = T::Vtable; + + fn vtable(&self) -> *const Self::Vtable { + self.state.vtable() + } +} + +impl<'a, T: DriverPlaneState> PlaneStateMutator<'a, PlaneState> { + super::impl_from_opaque_mode_obj! { + fn (PlaneStateMutator<'a, OpaquePlaneState>) -> Self + where + T: DriverPlaneState; + use + P as DriverPlane, + D as KmsDriver + } +} + unsafe extern "C" fn plane_destroy_callback(plane: *mut bindings::drm_plane) { // SAFETY: DRM guarantees that `plane` points to a valid initialized `drm_plane`. unsafe { bindings::drm_plane_cleanup(plane) }; From patchwork Wed Mar 5 22:59:35 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003646 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id BA3F0C282EC for ; Wed, 5 Mar 2025 23:07:41 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 2F43010E854; Wed, 5 Mar 2025 23:07:41 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="eYdGplTg"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 1B62010E854 for ; Wed, 5 Mar 2025 23:07:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216059; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=j/D4SEgwMMDnFfsGfNIexwP9lfVhLyJ7Nk/oo5mITW4=; b=eYdGplTg6VVMWYu4/qadzNpfzBtnnkWh6673giK8nw4hHWhoonEEYWOlVZxnn6gqfLK8FN Ich45VxPD2IaBARVEkSYU4gzmZ/l/ch+c+iJR71hnm7x5teLuqvF74ac2KESHjst3SEOG3 QQalCwVW9YX1go5civRLTdca3NPYEtM= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-251-oPRhXijONZ-enFPmMxsMWA-1; Wed, 05 Mar 2025 18:07:29 -0500 X-MC-Unique: oPRhXijONZ-enFPmMxsMWA-1 X-Mimecast-MFC-AGG-ID: oPRhXijONZ-enFPmMxsMWA_1741216046 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id BB960180025B; Wed, 5 Mar 2025 23:07:26 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id A13EB30001A1; Wed, 5 Mar 2025 23:07:22 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 19/33] rust: drm/kms: Add DriverCrtc::atomic_check() Date: Wed, 5 Mar 2025 17:59:35 -0500 Message-ID: <20250305230406.567126-20-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" An optional trait method for implementing a CRTC's atomic state check. Signed-off-by: Lyude Paul --- V3: * Document uses of ManuallyDrop Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/atomic.rs | 1 - rust/kernel/drm/kms/crtc.rs | 55 +++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/rust/kernel/drm/kms/atomic.rs b/rust/kernel/drm/kms/atomic.rs index 3d5c70dbc4274..e0a1b5b860d6f 100644 --- a/rust/kernel/drm/kms/atomic.rs +++ b/rust/kernel/drm/kms/atomic.rs @@ -274,7 +274,6 @@ impl AtomicStateComposer { /// # Safety /// /// The caller guarantees that `ptr` points to a valid instance of `drm_atomic_state`. - #[allow(dead_code)] pub(crate) unsafe fn new(ptr: NonNull) -> Self { // SAFETY: see `AtomicStateMutator::from_raw()` Self(unsafe { AtomicStateMutator::new(ptr) }) diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 3b9c9d97fcf24..50f5b68f4a3fe 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -12,7 +12,7 @@ alloc::KBox, bindings, drm::device::Device, - error::to_result, + error::{from_result, to_result}, init::Zeroable, prelude::*, private::Sealed, @@ -21,7 +21,7 @@ use core::{ cell::{Cell, UnsafeCell}, marker::*, - mem, + mem::{self, ManuallyDrop}, ops::{Deref, DerefMut}, ptr::{addr_of_mut, null, null_mut, NonNull}, }; @@ -78,7 +78,11 @@ pub trait DriverCrtc: Send + Sync + Sized { helper_funcs: bindings::drm_crtc_helper_funcs { atomic_disable: None, atomic_enable: None, - atomic_check: None, + atomic_check: if Self::HAS_ATOMIC_CHECK { + Some(atomic_check_callback::) + } else { + None + }, dpms: None, commit: None, prepare: None, @@ -113,6 +117,21 @@ pub trait DriverCrtc: Send + Sync + Sized { /// /// Drivers may use this to instantiate their [`DriverCrtc`] object. fn new(device: &Device, args: &Self::Args) -> impl PinInit; + + /// The optional [`drm_crtc_helper_funcs.atomic_check`] hook for this crtc. + /// + /// Drivers may use this to customize the atomic check phase of their [`Crtc`] objects. The + /// result of this function determines whether the atomic check passed or failed. + /// + /// [`drm_crtc_helper_funcs.atomic_check`]: srctree/include/drm/drm_modeset_helper_vtables.h + fn atomic_check( + _crtc: &Crtc, + _old_state: &CrtcState, + _new_state: CrtcStateMutator<'_, CrtcState>, + _state: &AtomicStateComposer, + ) -> Result { + build_error::build_error("This should not be reachable") + } } /// The generated C vtable for a [`DriverCrtc`]. @@ -859,3 +878,33 @@ fn vtable(&self) -> *const Self::Vtable { // SAFETY: DRM takes ownership of the state from here, and will never move it unsafe { bindings::__drm_atomic_helper_crtc_reset(crtc.as_raw(), KBox::into_raw(new).cast()) }; } + +unsafe extern "C" fn atomic_check_callback( + crtc: *mut bindings::drm_crtc, + state: *mut bindings::drm_atomic_state, +) -> i32 { + // SAFETY: + // - We're guaranteed `crtc` is of type `Crtc` via type invariants. + // - We're guaranteed by DRM that `crtc` is pointing to a valid initialized state. + let crtc = unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: DRM guarantees `state` points to a valid `drm_atomic_state` + // We use a ManuallyDrop here to avoid AtomicStateComposer dropping an owned reference we never + // acquired. + let state = + unsafe { ManuallyDrop::new(AtomicStateComposer::new(NonNull::new_unchecked(state))) }; + + // SAFETY: Since we are in the atomic update callback, we're guaranteed by DRM that both the old + // and new atomic state are present within `state` + let (old_state, new_state) = unsafe { + ( + state.get_old_crtc_state(crtc).unwrap_unchecked(), + state.get_new_crtc_state(crtc).unwrap_unchecked(), + ) + }; + + from_result(|| { + T::atomic_check(crtc, old_state, new_state, &state)?; + Ok(0) + }) +} From patchwork Wed Mar 5 22:59:36 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003648 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id D78D3C282EC for ; Wed, 5 Mar 2025 23:07:54 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 17D0E10E858; Wed, 5 Mar 2025 23:07:54 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="NQJBGNbc"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id CF10B10E85A for ; Wed, 5 Mar 2025 23:07:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216072; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3Sa15p0zk9/0E89lqNYU9Y3htAIogxJ6GNdp/TIXh9E=; b=NQJBGNbc73lSIyX37/g2VGF00ucTURTVWRRUkj4tiVot30r7Fz3X3WeTbjMMM9XkQVqv6C jP620sto5tzwFt9QdrSNuFZta88rMcQUHmM+OjhMI5LUxQUTMMeouxoLQNTiOZXM+LwhE/ eVXi+CQZtR89gk3mEDK5qqw9bYxkm4M= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-590-gMt0t3KrPU-Esgh-380sOA-1; Wed, 05 Mar 2025 18:07:36 -0500 X-MC-Unique: gMt0t3KrPU-Esgh-380sOA-1 X-Mimecast-MFC-AGG-ID: gMt0t3KrPU-Esgh-380sOA_1741216054 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E452E18001D0; Wed, 5 Mar 2025 23:07:33 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 802F5300019E; Wed, 5 Mar 2025 23:07:30 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 20/33] rust: drm/kms: Add DriverPlane::atomic_update() Date: Wed, 5 Mar 2025 17:59:36 -0500 Message-ID: <20250305230406.567126-21-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" A mandatory trait method used for implementing DRM's atomic plane update callback. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/plane.rs | 45 +++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 4e73c2966ca22..b090aebc35649 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -70,7 +70,11 @@ pub trait DriverPlane: Send + Sync + Sized { begin_fb_access: None, end_fb_access: None, atomic_check: None, - atomic_update: None, + atomic_update: if Self::HAS_ATOMIC_UPDATE { + Some(atomic_update_callback::) + } else { + None + }, atomic_enable: None, atomic_disable: None, atomic_async_check: None, @@ -98,6 +102,21 @@ pub trait DriverPlane: Send + Sync + Sized { /// /// Drivers may use this to instantiate their [`DriverPlane`] object. fn new(device: &Device, args: Self::Args) -> impl PinInit; + + /// The optional [`drm_plane_helper_funcs.atomic_update`] hook for this plane. + /// + /// Drivers may use this to customize the atomic update phase of their [`Plane`] objects. If not + /// specified, this function is a no-op. + /// + /// [`drm_plane_helper_funcs.atomic_update`]: srctree/include/drm/drm_modeset_helper_vtables.h + fn atomic_update( + _plane: &Plane, + _new_state: PlaneStateMutator<'_, PlaneState>, + _old_state: &PlaneState, + _state: &AtomicStateMutator, + ) { + build_error::build_error("This should not be reachable") + } } /// The generated C vtable for a [`DriverPlane`]. @@ -931,3 +950,27 @@ fn (PlaneStateMutator<'a, OpaquePlaneState>) -> Self // - The cast to `drm_plane_state` is safe via `PlaneState`s type invariants. unsafe { bindings::__drm_atomic_helper_plane_reset(plane, KBox::into_raw(new).cast()) }; } + +unsafe extern "C" fn atomic_update_callback( + plane: *mut bindings::drm_plane, + state: *mut bindings::drm_atomic_state, +) { + // SAFETY: + // - We're guaranteed `plane` is of type `Plane` via type invariants. + // - We're guaranteed by DRM that `plane` is pointing to a valid initialized state. + let plane = unsafe { Plane::from_raw(plane) }; + + // SAFETY: DRM guarantees `state` points to a valid `drm_atomic_state` + let state = unsafe { AtomicStateMutator::new(NonNull::new_unchecked(state)) }; + + // SAFETY: Since we are in the atomic update callback, we're guaranteed by DRM that both the old + // and new atomic state are present within `state` + let (old_state, new_state) = unsafe { + ( + state.get_old_plane_state(plane).unwrap_unchecked(), + state.get_new_plane_state(plane).unwrap_unchecked(), + ) + }; + + T::atomic_update(plane, new_state, old_state, &state); +} From patchwork Wed Mar 5 22:59:37 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003647 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id D2A67C19F32 for ; Wed, 5 Mar 2025 23:07:53 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 45D9D10E859; Wed, 5 Mar 2025 23:07:53 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="cGhLaVZE"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 9021710E859 for ; Wed, 5 Mar 2025 23:07:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216071; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=P8Wya8waZxf4PRUJ4SmPnCKJ5/8nzDdWThNqRlGolhE=; b=cGhLaVZE099ReiP/uX997zwgQjvRMj31nuVHhdvUUiJLWJtKmPVo3FEpxGbnfACuer8NY1 syvrvj2TAfq3Rb/+4pk8g4575yRMsbSh7W1vVAN6T6yd1tG1kP7HrCBPDUkZ5JhItOuT7u llzya6Z92F7ZaY+J/lnSvpJsfm3iiW8= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-113-fTF3WE_ONlOJowyS2fczRQ-1; Wed, 05 Mar 2025 18:07:43 -0500 X-MC-Unique: fTF3WE_ONlOJowyS2fczRQ-1 X-Mimecast-MFC-AGG-ID: fTF3WE_ONlOJowyS2fczRQ_1741216061 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6C3631800262; Wed, 5 Mar 2025 23:07:41 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id CAB83300019E; Wed, 5 Mar 2025 23:07:37 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 21/33] rust: drm/kms: Add DriverPlane::atomic_check() Date: Wed, 5 Mar 2025 17:59:37 -0500 Message-ID: <20250305230406.567126-22-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Optional trait method for implementing a plane's atomic_check(). Signed-off-by: Lyude Paul --- V3: * Document ManuallyDrop uses better Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/plane.rs | 53 ++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index b090aebc35649..f3adc30c17489 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -11,7 +11,7 @@ alloc::KBox, bindings, drm::{device::Device, fourcc::*}, - error::{to_result, Error}, + error::{from_result, to_result, Error}, init::Zeroable, prelude::*, private::Sealed, @@ -20,7 +20,7 @@ use core::{ cell::Cell, marker::*, - mem, + mem::{self, ManuallyDrop}, ops::*, pin::Pin, ptr::{addr_of_mut, null, null_mut, NonNull}, @@ -69,7 +69,11 @@ pub trait DriverPlane: Send + Sync + Sized { cleanup_fb: None, begin_fb_access: None, end_fb_access: None, - atomic_check: None, + atomic_check: if Self::HAS_ATOMIC_CHECK { + Some(atomic_check_callback::) + } else { + None + }, atomic_update: if Self::HAS_ATOMIC_UPDATE { Some(atomic_update_callback::) } else { @@ -117,6 +121,21 @@ fn atomic_update( ) { build_error::build_error("This should not be reachable") } + + /// The optional [`drm_plane_helper_funcs.atomic_check`] hook for this plane. + /// + /// Drivers may use this to customize the atomic check phase of their [`Plane`] objects. The + /// result of this function determines whether the atomic check passed or failed. + /// + /// [`drm_plane_helper_funcs.atomic_check`]: srctree/include/drm/drm_modeset_helper_vtables.h + fn atomic_check( + _plane: &Plane, + _new_state: PlaneStateMutator<'_, PlaneState>, + _old_state: &PlaneState, + _state: &AtomicStateComposer, + ) -> Result { + build_error::build_error("This should not be reachable") + } } /// The generated C vtable for a [`DriverPlane`]. @@ -974,3 +993,31 @@ fn (PlaneStateMutator<'a, OpaquePlaneState>) -> Self T::atomic_update(plane, new_state, old_state, &state); } + +unsafe extern "C" fn atomic_check_callback( + plane: *mut bindings::drm_plane, + state: *mut bindings::drm_atomic_state, +) -> i32 { + // SAFETY: + // - We're guaranteed `plane` is of type `Plane` via type invariants. + // - We're guaranteed by DRM that `plane` is pointing to a valid initialized state. + let plane = unsafe { Plane::from_raw(plane) }; + + // SAFETY: We're guaranteed by DRM that `state` points to a valid instance of `drm_atomic_state` + // We use ManuallyDrop here since AtomicStateComposer would otherwise drop a owned reference to + // the atomic state upon finishing this callback. + let state = ManuallyDrop::new(unsafe { + AtomicStateComposer::::new(NonNull::new_unchecked(state)) + }); + + // SAFETY: We're guaranteed by DRM that both the old and new atomic state are present within + // this `drm_atomic_state` + let (old_state, new_state) = unsafe { + ( + state.get_old_plane_state(plane).unwrap_unchecked(), + state.get_new_plane_state(plane).unwrap_unchecked(), + ) + }; + + from_result(|| T::atomic_check(plane, new_state, old_state, &state).map(|_| 0)) +} From patchwork Wed Mar 5 22:59:38 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003649 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 8C2E4C19F32 for ; Wed, 5 Mar 2025 23:08:06 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 00B4310E857; Wed, 5 Mar 2025 23:08:06 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="UErTHCkJ"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 1AA2810E857 for ; Wed, 5 Mar 2025 23:08:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216084; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=9knJ5NY3utv62oPTmxDgFn+ShbZu7msy2bjZd2Ss7KQ=; b=UErTHCkJu0sK90nX6jvTIiWhKnXdybEUoVqyWuxklabjDFJe2GrNLZHHuBXTC55zdRkOl5 U97q/SbQY+xgFD+KGoYyEa//vrHQLPf6N9vVuvo6qtYlxQV/4qtiMR/j1t8hoOCfk+rYlT 15V0JyWKZg+TQTeRTD9hCnCMoiFGdyk= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-460-49tX_X7rPSCi0De8hFzj0Q-1; Wed, 05 Mar 2025 18:07:50 -0500 X-MC-Unique: 49tX_X7rPSCi0De8hFzj0Q-1 X-Mimecast-MFC-AGG-ID: 49tX_X7rPSCi0De8hFzj0Q_1741216068 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9481E19560BA; Wed, 5 Mar 2025 23:07:48 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id EA261300019E; Wed, 5 Mar 2025 23:07:44 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 22/33] rust: drm/kms: Add RawCrtcState::active() Date: Wed, 5 Mar 2025 17:59:38 -0500 Message-ID: <20250305230406.567126-23-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" A binding for checking drm_crtc_state.active. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/crtc.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 50f5b68f4a3fe..99719c4e288e6 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -607,6 +607,14 @@ fn crtc(&self) -> &Self::Crtc { // state unsafe { ::from_raw((*self.as_raw()).crtc) } } + + /// Returns whether or not the CRTC is active in this atomic state. + fn active(&self) -> bool { + // SAFETY: `active` and the rest of its containing bitfield can only be modified from the + // atomic check context, and are invariant beyond that point - so our interface can ensure + // this access is serialized + unsafe { (*self.as_raw()).active } + } } impl RawCrtcState for T {} From patchwork Wed Mar 5 22:59:39 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003650 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 700B8C19F32 for ; Wed, 5 Mar 2025 23:08:10 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id D375910E85A; Wed, 5 Mar 2025 23:08:09 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="NcEtAal3"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id AFD5B10E85A for ; Wed, 5 Mar 2025 23:08:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216087; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=T2VpDj6SMIZeSrAkqn3RnehhHLuj14WMhqBC+m4f1mU=; b=NcEtAal3ycvE1nq0kQglPBBtldzadWcDp5Jwbu70OsuwRP3zByXJF9jBO9gevfyRfgLXYM Ref4+mY3o3qZizlzm+9gtxpxt/SkOStoy+mRX/0+PJOc/VygI5tNyHcVCmDaB3Y8FaAP1E WGUEr4GqxfOA94B4N+CQF3pWhUabqFI= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-33-2v86yqRoN-WEcr2o8c_Y7A-1; Wed, 05 Mar 2025 18:07:57 -0500 X-MC-Unique: 2v86yqRoN-WEcr2o8c_Y7A-1 X-Mimecast-MFC-AGG-ID: 2v86yqRoN-WEcr2o8c_Y7A_1741216075 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B3B1A1800349; Wed, 5 Mar 2025 23:07:55 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 1A62C300019E; Wed, 5 Mar 2025 23:07:51 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 23/33] rust: drm/kms: Add RawPlaneState::crtc() Date: Wed, 5 Mar 2025 17:59:39 -0500 Message-ID: <20250305230406.567126-24-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Add a binding for checking drm_plane_state.crtc. Note that we don't have a way of knowing what DriverCrtc implementation would be used here (and want to make this function also available on OpaquePlaneState types), so we return an OpaqueCrtc. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/plane.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index f3adc30c17489..a30f7f8caaafb 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -5,7 +5,8 @@ //! C header: [`include/drm/drm_plane.h`](srctree/include/drm/drm_plane.h) use super::{ - atomic::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice, + atomic::*, crtc::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, + UnregisteredKmsDevice, }; use crate::{ alloc::KBox, @@ -608,6 +609,16 @@ fn plane(&self) -> &Self::Plane { // invariant throughout the lifetime of the Plane unsafe { Self::Plane::from_raw(self.as_raw().plane) } } + + /// Return the current [`OpaqueCrtc`] assigned to this plane, if there is one. + fn crtc<'a, 'b: 'a, D>(&'a self) -> Option<&'b OpaqueCrtc> + where + Self::Plane: ModeObject, + D: KmsDriver, + { + // SAFETY: This cast is guaranteed safe by `OpaqueCrtc`s invariants. + NonNull::new(self.as_raw().crtc).map(|c| unsafe { OpaqueCrtc::from_raw(c.as_ptr()) }) + } } impl RawPlaneState for T {} From patchwork Wed Mar 5 22:59:40 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003651 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id EB7FEC282EC for ; Wed, 5 Mar 2025 23:08:19 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 45AA210E85C; Wed, 5 Mar 2025 23:08:19 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="hfYtKTuc"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 48EE310E85C for ; Wed, 5 Mar 2025 23:08:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216097; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=bGbLhta6NjlzPjR8SQEXQQ3XRNfxpmzeftS1Pw9ReGk=; b=hfYtKTucEwVPLcWlGEh2jYnCyeYdOxDyAsVgKNSGpR6+lmJLAlRduRZUyAkyOh9cuq8KvN DotGWLPGSPSO9YvgOyuh2SZ1Wf1pMaQQPooIOb0Y5+JkucRuoMscuGelT0BhsxwxtKn9a7 R7Kh6ekfVTemstyrZ17BSFSqp3+uuAA= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-654-NEQZTfTCPGGuhlnexhj2qg-1; Wed, 05 Mar 2025 18:08:08 -0500 X-MC-Unique: NEQZTfTCPGGuhlnexhj2qg-1 X-Mimecast-MFC-AGG-ID: NEQZTfTCPGGuhlnexhj2qg_1741216086 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id DA39218001D7; Wed, 5 Mar 2025 23:08:05 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 4906B300019E; Wed, 5 Mar 2025 23:08:02 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 24/33] rust: drm/kms: Add RawPlaneState::atomic_helper_check() Date: Wed, 5 Mar 2025 17:59:40 -0500 Message-ID: <20250305230406.567126-25-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Add a binding for drm_atomic_helper_check_plane_state(). Since we want to make sure that the user is passing in the new state for a Crtc instead of an old state, we explicitly ask for a reference to a CrtcStateMutator. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/crtc.rs | 2 ++ rust/kernel/drm/kms/plane.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 99719c4e288e6..aaa208b35c3c1 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -593,6 +593,8 @@ pub trait AsRawCrtcState { } } +pub(super) use private::AsRawCrtcState as AsRawCrtcStatePrivate; + /// Common methods available on any type which implements [`AsRawCrtcState`]. /// /// This is implemented internally by DRM, and provides many of the basic methods for working with diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index a30f7f8caaafb..63b453b7307fd 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -619,6 +619,33 @@ fn crtc<'a, 'b: 'a, D>(&'a self) -> Option<&'b OpaqueCrtc> // SAFETY: This cast is guaranteed safe by `OpaqueCrtc`s invariants. NonNull::new(self.as_raw().crtc).map(|c| unsafe { OpaqueCrtc::from_raw(c.as_ptr()) }) } + + /// Run the atomic check helper for this plane and the given CRTC state. + fn atomic_helper_check( + &mut self, + crtc_state: &CrtcStateMutator<'_, S>, + can_position: bool, + can_update_disabled: bool, + ) -> Result + where + D: KmsDriver, + S: FromRawCrtcState, + S::Crtc: ModesettableCrtc + ModeObject, + Self::Plane: ModeObject, + { + // SAFETY: We're passing the mutable reference from `self.as_raw_mut()` directly to DRM, + // which is safe. + to_result(unsafe { + bindings::drm_atomic_helper_check_plane_state( + self.as_raw_mut(), + crtc_state.as_raw(), + bindings::DRM_PLANE_NO_SCALING as _, // TODO: add parameters for scaling + bindings::DRM_PLANE_NO_SCALING as _, + can_position, + can_update_disabled, + ) + }) + } } impl RawPlaneState for T {} From patchwork Wed Mar 5 22:59:41 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003652 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 64CE3C282EC for ; Wed, 5 Mar 2025 23:08:24 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id C9EE710E85B; Wed, 5 Mar 2025 23:08:23 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="b2PFf3M2"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id AEE3C10E85B for ; Wed, 5 Mar 2025 23:08:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216101; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=jOW7KDNY/rnfsCrl7d4OXtnM2CoD0PhVS/YjiuzS72A=; b=b2PFf3M2BRO7uZz2vsBt+NOneflcmELj07vIYT4RLUSFOprbq4CX7YqMChE+FdVKy7Iblj MOwHJeSS5HhWKjd+kyKlgAmQnygkB8rAIk7FZRdkbYBV73ChgHtyYZMDA/vYRm+sBwoPCg /FjuOA6PtAjiN90vZMVUyze9mFKEfTQ= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-660-SE5Zl_emMbu36FTP4ZVAKg-1; Wed, 05 Mar 2025 18:08:18 -0500 X-MC-Unique: SE5Zl_emMbu36FTP4ZVAKg-1 X-Mimecast-MFC-AGG-ID: SE5Zl_emMbu36FTP4ZVAKg_1741216095 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id CDB52180035E; Wed, 5 Mar 2025 23:08:15 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id DCC21300019E; Wed, 5 Mar 2025 23:08:10 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 25/33] rust: drm/kms: Add drm_framebuffer bindings Date: Wed, 5 Mar 2025 17:59:41 -0500 Message-ID: <20250305230406.567126-26-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This adds some very simple bindings for drm_framebuffer. We don't use them much yet, but we'll eventually be using them when rvkms eventually gets CRC and writeback support. Just like Connector objects, these use RcModeObject. Signed-off-by: Lyude Paul --- V3: * Replace Framebuffer struct with tuple * Add safety comments for ModeObject implementation * Add comment for why we're using Sealed Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/kms.rs | 1 + rust/kernel/drm/kms/framebuffer.rs | 74 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 rust/kernel/drm/kms/framebuffer.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 846eb6eb8fc4c..2e80a62062fc8 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 978bb6712ffbe..429ce28229c9e 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -6,6 +6,7 @@ pub mod connector; pub mod crtc; pub mod encoder; +pub mod framebuffer; pub mod plane; use crate::{ diff --git a/rust/kernel/drm/kms/framebuffer.rs b/rust/kernel/drm/kms/framebuffer.rs new file mode 100644 index 0000000000000..5a60b580fe0bf --- /dev/null +++ b/rust/kernel/drm/kms/framebuffer.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM framebuffers. +//! +//! C header: [`include/drm/drm_framebuffer.h`](srctree/include/drm/drm_framebuffer.h) + +use super::{KmsDriver, ModeObject, RcModeObject}; +use crate::{drm::device::Device, types::*}; +use bindings; +use core::{marker::*, ptr}; + +/// The main interface for [`struct drm_framebuffer`]. +/// +/// # Invariants +/// +/// - `self.0` is initialized for as long as this object is exposed to users. +/// - This type has an identical data layout to [`struct drm_framebuffer`] +/// +/// [`struct drm_framebuffer`]: srctree/include/drm/drm_framebuffer.h +#[repr(transparent)] +pub struct Framebuffer(Opaque, PhantomData); + +// SAFETY: +// - `self.0` is initialized for as long as this object is exposed to users +// - `base` is initialized by DRM when `self.0` is initialized, thus `raw_mode_obj()` always returns +// a valid pointer. +unsafe impl ModeObject for Framebuffer { + type Driver = T; + + fn drm_dev(&self) -> &Device { + // SAFETY: `dev` points to an initialized `struct drm_device` for as long as this type is + // initialized + unsafe { Device::borrow((*self.0.get()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose Framebuffer to users before its initialized, so `base` is + // always initialized + unsafe { ptr::addr_of_mut!((*self.0.get()).base) } + } +} + +// SAFETY: Framebuffers are refcounted mode objects. +unsafe impl RcModeObject for Framebuffer {} + +// SAFETY: References to framebuffers are safe to be accessed from any thread +unsafe impl Send for Framebuffer {} +// SAFETY: References to framebuffers are safe to be accessed from any thread +unsafe impl Sync for Framebuffer {} + +// For implementing ModeObject +impl crate::private::Sealed for Framebuffer {} + +impl PartialEq for Framebuffer { + fn eq(&self, other: &Self) -> bool { + ptr::eq(self.0.get(), other.0.get()) + } +} +impl Eq for Framebuffer {} + +impl Framebuffer { + /// Convert a raw pointer to a `struct drm_framebuffer` into a [`Framebuffer`] + /// + /// # Safety + /// + /// The caller guarantews that `ptr` points to a initialized `struct drm_framebuffer` for at + /// least the entire lifetime of `'a`. + #[inline] + #[allow(dead_code)] + pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::drm_framebuffer) -> &'a Self { + // SAFETY: Our data layout is identical to drm_framebuffer + unsafe { &*ptr.cast() } + } +} From patchwork Wed Mar 5 22:59:42 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003653 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 87364C282EC for ; Wed, 5 Mar 2025 23:08:31 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 017FE10E85D; Wed, 5 Mar 2025 23:08:31 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="Gp8HOiyX"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 7FA0D10E85D for ; Wed, 5 Mar 2025 23:08:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216108; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=JGaidaXItPxvHOgKUEqHTZ0I38IyrDd5JklCvf8qG1Y=; b=Gp8HOiyXpqGjGLLcMmjluXKufEYPkxpQp+Jp0NuL0/9ZXht/w/bP9SjAlAqFzNpUMN0Se+ g6hCvGKZKS0TOWocGs/4Ky++XZq+0rAsUB7BG/5MqyPZz713KsV0CjWZIjs0ubwSHH7OMK FQbXapj6R8fD1xwpZEVXYjr7ZtLhjeo= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-266-7W8r-8dGNmCViAT1HOLdig-1; Wed, 05 Mar 2025 18:08:27 -0500 X-MC-Unique: 7W8r-8dGNmCViAT1HOLdig-1 X-Mimecast-MFC-AGG-ID: 7W8r-8dGNmCViAT1HOLdig_1741216105 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id EB601195605E; Wed, 5 Mar 2025 23:08:24 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id E4CA9300019E; Wed, 5 Mar 2025 23:08:20 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 26/33] rust: drm/kms: Add RawPlane::framebuffer() Date: Wed, 5 Mar 2025 17:59:42 -0500 Message-ID: <20250305230406.567126-27-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Returns the Framebuffer currently assigned in an atomic plane state. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/framebuffer.rs | 1 - rust/kernel/drm/kms/plane.rs | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/rust/kernel/drm/kms/framebuffer.rs b/rust/kernel/drm/kms/framebuffer.rs index 5a60b580fe0bf..99f366e8813e2 100644 --- a/rust/kernel/drm/kms/framebuffer.rs +++ b/rust/kernel/drm/kms/framebuffer.rs @@ -66,7 +66,6 @@ impl Framebuffer { /// The caller guarantews that `ptr` points to a initialized `struct drm_framebuffer` for at /// least the entire lifetime of `'a`. #[inline] - #[allow(dead_code)] pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::drm_framebuffer) -> &'a Self { // SAFETY: Our data layout is identical to drm_framebuffer unsafe { &*ptr.cast() } diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 63b453b7307fd..1d50632ae473f 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -5,7 +5,7 @@ //! C header: [`include/drm/drm_plane.h`](srctree/include/drm/drm_plane.h) use super::{ - atomic::*, crtc::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, + atomic::*, crtc::*, framebuffer::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice, }; use crate::{ @@ -646,6 +646,22 @@ fn atomic_helper_check( ) }) } + + /// Return the framebuffer currently set for this plane state + #[inline] + fn framebuffer(&self) -> Option<&Framebuffer> + where + Self::Plane: ModeObject, + D: KmsDriver, + { + // SAFETY: The layout of Framebuffer is identical to `fb` + unsafe { + self.as_raw() + .fb + .as_ref() + .map(|fb| Framebuffer::from_raw(fb)) + } + } } impl RawPlaneState for T {} From patchwork Wed Mar 5 22:59:43 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003654 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id C71DAC19F32 for ; Wed, 5 Mar 2025 23:08:42 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 303F010E85E; Wed, 5 Mar 2025 23:08:42 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="TmJHuMyH"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id E8C7B10E85E for ; Wed, 5 Mar 2025 23:08:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216120; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=2YYipArKfP/H/ebwT6dHqlh88VV98EgX7zofrorIO0I=; b=TmJHuMyHTIoaw/yq5+Ey6K+SqyyM8NTgtlxxeY8FQDmga8YsNY+4YWFJyhJR3OlBnB5S2Q MjBE6+pZj0HOVKnXxLkXwHd56z7VMCcMerFiwTiQNy9R4oM5p3PUWNb5olSnr8VX9LsT/u TNfivwGaFuZI5/boyhsy8GOFE/+hh7Y= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-64-lGH8R1nMOOuHUE3SfhubMQ-1; Wed, 05 Mar 2025 18:08:34 -0500 X-MC-Unique: lGH8R1nMOOuHUE3SfhubMQ-1 X-Mimecast-MFC-AGG-ID: lGH8R1nMOOuHUE3SfhubMQ_1741216111 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B94781801A03; Wed, 5 Mar 2025 23:08:31 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 0DC5A300019E; Wed, 5 Mar 2025 23:08:27 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 27/33] rust: drm/kms: Add DriverCrtc::atomic_begin() and atomic_flush() Date: Wed, 5 Mar 2025 17:59:43 -0500 Message-ID: <20250305230406.567126-28-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Optional trait methods for implementing the atomic_begin and atomic_flush callbacks for a CRTC. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/crtc.rs | 90 ++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index aaa208b35c3c1..131d10505ba07 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -90,8 +90,16 @@ pub trait DriverCrtc: Send + Sync + Sized { mode_set: None, mode_valid: None, mode_fixup: None, - atomic_begin: None, - atomic_flush: None, + atomic_begin: if Self::HAS_ATOMIC_BEGIN { + Some(atomic_begin_callback::) + } else { + None + }, + atomic_flush: if Self::HAS_ATOMIC_FLUSH { + Some(atomic_flush_callback::) + } else { + None + }, mode_set_nofb: None, mode_set_base: None, mode_set_base_atomic: None, @@ -132,6 +140,36 @@ fn atomic_check( ) -> Result { build_error::build_error("This should not be reachable") } + + /// The optional [`drm_crtc_helper_funcs.atomic_begin`] hook. + /// + /// This hook will be called before a set of [`Plane`] updates are performed for the given + /// [`Crtc`]. + /// + /// [`drm_crtc_helper_funcs.atomic_begin`]: srctree/include/drm/drm_modeset_helper_vtables.h + fn atomic_begin( + _crtc: &Crtc, + _old_state: &CrtcState, + _new_state: CrtcStateMutator<'_, CrtcState>, + _state: &AtomicStateMutator, + ) { + build_error::build_error("This should not be reachable") + } + + /// The optional [`drm_crtc_helper_funcs.atomic_flush`] hook. + /// + /// This hook will be called after a set of [`Plane`] updates are performed for the given + /// [`Crtc`]. + /// + /// [`drm_crtc_helper_funcs.atomic_flush`]: srctree/include/drm/drm_modeset_helper_vtables.h + fn atomic_flush( + _crtc: &Crtc, + _old_state: &CrtcState, + _new_state: CrtcStateMutator<'_, CrtcState>, + _state: &AtomicStateMutator, + ) { + build_error::build_error("This should never be reachable") + } } /// The generated C vtable for a [`DriverCrtc`]. @@ -918,3 +956,51 @@ fn vtable(&self) -> *const Self::Vtable { Ok(0) }) } + +unsafe extern "C" fn atomic_begin_callback( + crtc: *mut bindings::drm_crtc, + state: *mut bindings::drm_atomic_state, +) { + // SAFETY: + // * We're guaranteed `crtc` is of type `Crtc` via type invariants. + // * We're guaranteed by DRM that `crtc` is pointing to a valid initialized state. + let crtc = unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: We're guaranteed by DRM that `state` points to a valid instance of `drm_atomic_state` + let state = unsafe { AtomicStateMutator::new(NonNull::new_unchecked(state)) }; + + // SAFETY: We're guaranteed by DRM that both the old and new atomic state are present within + // this `drm_atomic_state` + let (old_state, new_state) = unsafe { + ( + state.get_old_crtc_state(crtc).unwrap_unchecked(), + state.get_new_crtc_state(crtc).unwrap_unchecked(), + ) + }; + + T::atomic_begin(crtc, old_state, new_state, &state); +} + +unsafe extern "C" fn atomic_flush_callback( + crtc: *mut bindings::drm_crtc, + state: *mut bindings::drm_atomic_state, +) { + // SAFETY: + // - We're guaranteed `crtc` is of type `Crtc` via type invariants. + // - We're guaranteed by DRM that `crtc` is pointing to a valid initialized state. + let crtc = unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: We're guaranteed by DRM that `state` points to a valid instance of `drm_atomic_state` + let state = unsafe { AtomicStateMutator::new(NonNull::new_unchecked(state)) }; + + // SAFETY: We're in an atomic flush callback, so we know that both the new and old state are + // present + let (old_state, new_state) = unsafe { + ( + state.get_old_crtc_state(crtc).unwrap_unchecked(), + state.get_new_crtc_state(crtc).unwrap_unchecked(), + ) + }; + + T::atomic_flush(crtc, old_state, new_state, &state); +} From patchwork Wed Mar 5 22:59:44 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003656 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 6AC84C19F32 for ; Wed, 5 Mar 2025 23:09:02 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id CC82310E863; Wed, 5 Mar 2025 23:09:01 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="SxLQxawg"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id B4DA810E863 for ; Wed, 5 Mar 2025 23:08:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216138; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=0oBqLHoJsFA0rDr7+FKi6nHZ+YcDKW3Lfid1N4qEKas=; b=SxLQxawg8YNaO1Z9LIcJqkTI4rNIBh4VdDT8BdDmkP5KmGadUymu6pG0rmbH8IW9th8vOf /9Hi/I8vUl9V0ZPa9HCqzqWVhjBfcywf+lMT9fmOmN8LgVr8jExkoFw/E/a+3n+SnuqZrI 8TQlpvL0kjU+k2G353qGgMBScZsb3Mw= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-287-OiC-7E2VMbeGGViGqm6hkg-1; Wed, 05 Mar 2025 18:08:45 -0500 X-MC-Unique: OiC-7E2VMbeGGViGqm6hkg-1 X-Mimecast-MFC-AGG-ID: OiC-7E2VMbeGGViGqm6hkg_1741216119 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 94E071800257; Wed, 5 Mar 2025 23:08:39 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 3944D300019E; Wed, 5 Mar 2025 23:08:35 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 28/33] rust: drm/kms: Add DriverCrtc::atomic_enable() and atomic_disable() Date: Wed, 5 Mar 2025 17:59:44 -0500 Message-ID: <20250305230406.567126-29-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Optional trait methods for implementing the atomic_enable and atomic_disable callbacks of a CRTC. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms/crtc.rs | 88 ++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 131d10505ba07..9026d0911aa86 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -76,8 +76,16 @@ pub trait DriverCrtc: Send + Sync + Sized { }, helper_funcs: bindings::drm_crtc_helper_funcs { - atomic_disable: None, - atomic_enable: None, + atomic_disable: if Self::HAS_ATOMIC_DISABLE { + Some(atomic_disable_callback::) + } else { + None + }, + atomic_enable: if Self::HAS_ATOMIC_ENABLE { + Some(atomic_enable_callback::) + } else { + None + }, atomic_check: if Self::HAS_ATOMIC_CHECK { Some(atomic_check_callback::) } else { @@ -170,6 +178,34 @@ fn atomic_flush( ) { build_error::build_error("This should never be reachable") } + + /// The optional [`drm_crtc_helper_funcs.atomic_enable`] hook. + /// + /// This hook will be called before enabling a [`Crtc`] in an atomic commit. + /// + /// [`drm_crtc_helper_funcs.atomic_enable`]: srctree/include/drm/drm_modeset_helper_vtables.h + fn atomic_enable( + _crtc: &Crtc, + _old_state: &CrtcState, + _new_state: CrtcStateMutator<'_, CrtcState>, + _state: &AtomicStateMutator, + ) { + build_error::build_error("This should never be reachable") + } + + /// The optional [`drm_crtc_helper_funcs.atomic_disable`] hook. + /// + /// This hook will be called before disabling a [`Crtc`] in an atomic commit. + /// + /// [`drm_crtc_helper_funcs.atomic_disable`]: srctree/include/drm/drm_modeset_helper_vtables.h + fn atomic_disable( + _crtc: &Crtc, + _old_state: &CrtcState, + _new_state: CrtcStateMutator<'_, CrtcState>, + _state: &AtomicStateMutator, + ) { + build_error::build_error("This should never be reachable") + } } /// The generated C vtable for a [`DriverCrtc`]. @@ -1004,3 +1040,51 @@ fn vtable(&self) -> *const Self::Vtable { T::atomic_flush(crtc, old_state, new_state, &state); } + +unsafe extern "C" fn atomic_enable_callback( + crtc: *mut bindings::drm_crtc, + state: *mut bindings::drm_atomic_state, +) { + // SAFETY: + // - We're guaranteed `crtc` is of type `Crtc` via type invariants. + // - We're guaranteed by DRM that `crtc` is pointing to a valid initialized state. + let crtc = unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: DRM never passes an invalid ptr for `state` + let state = unsafe { AtomicStateMutator::new(NonNull::new_unchecked(state)) }; + + // SAFETY: We're in an atomic enable callback, so we know that both the new and old state are + // present + let (old_state, new_state) = unsafe { + ( + state.get_old_crtc_state(crtc).unwrap_unchecked(), + state.get_new_crtc_state(crtc).unwrap_unchecked(), + ) + }; + + T::atomic_enable(crtc, old_state, new_state, &state); +} + +unsafe extern "C" fn atomic_disable_callback( + crtc: *mut bindings::drm_crtc, + state: *mut bindings::drm_atomic_state, +) { + // SAFETY: + // - We're guaranteed `crtc` points to a valid instance of `drm_crtc` + // - We're guaranteed that `crtc` is of type `Plane` by `DriverPlane`s type invariants. + let crtc = unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: We're guaranteed that `state` points to a valid `drm_crtc_state` by DRM + let state = unsafe { AtomicStateMutator::new(NonNull::new_unchecked(state)) }; + + // SAFETY: We're in an atomic commit callback, so we know that both the new and old state are + // present + let (old_state, new_state) = unsafe { + ( + state.get_old_crtc_state(crtc).unwrap_unchecked(), + state.get_new_crtc_state(crtc).unwrap_unchecked(), + ) + }; + + T::atomic_disable(crtc, old_state, new_state, &state); +} From patchwork Wed Mar 5 22:59:45 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003655 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 4A048C19F32 for ; Wed, 5 Mar 2025 23:08:59 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id B00C710E080; Wed, 5 Mar 2025 23:08:58 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="TIuOVivx"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id D7A5310E080 for ; Wed, 5 Mar 2025 23:08:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216133; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=lRRGIfbn1O4NQuVIAQdXUxnmr9FtR81ZdEGKL5sWc/Q=; b=TIuOVivxMh36TAknYHCVJNiLCIwCX/sFFlrTR91wlFiWIXyDhh8o4dg+PS+2i0J+P7ZZBv xCUujMIGy60rTAvgvRHMCody5lghriCe18v9cdsFaUc9gt0RdF5VSf3gTP40kpFRfmrCEk wclRRRbOG2MTE96ypzp6qfteAs6sC04= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-59-EymIt6hbNLKfbKEoENAk_A-1; Wed, 05 Mar 2025 18:08:48 -0500 X-MC-Unique: EymIt6hbNLKfbKEoENAk_A-1 X-Mimecast-MFC-AGG-ID: EymIt6hbNLKfbKEoENAk_A_1741216126 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0FE9E1809CA3; Wed, 5 Mar 2025 23:08:46 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 32191300019E; Wed, 5 Mar 2025 23:08:42 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Asahi Lina , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 29/33] rust: drm: Add Device::event_lock() Date: Wed, 5 Mar 2025 17:59:45 -0500 Message-ID: <20250305230406.567126-30-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This is just a crate-private helper to use Lock::from_raw() to provide an immutable reference to the DRM event_lock, so that it can be used like a normal rust spinlock. We'll need this for adding vblank related bindings. Signed-off-by: Lyude Paul --- rust/kernel/drm/device.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index cf063de387329..3938ceb044645 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -11,6 +11,7 @@ error::from_err_ptr, error::Result, ffi, + sync::*, types::{ARef, AlwaysRefCounted, ForeignOwnable, Opaque}, }; use core::{marker::PhantomData, ptr::NonNull}; @@ -154,6 +155,13 @@ pub fn data(&self) -> ::Borrowed<'_> { unsafe { ::from_foreign(drm.raw_data()) }; } + /// Returns a reference to the `event` spinlock + #[allow(dead_code)] + pub(crate) fn event_lock(&self) -> &SpinLockIrq<()> { + // SAFETY: `event_lock` is initialized for as long as `self` is exposed to users + unsafe { SpinLockIrq::from_raw(&mut (*self.as_raw()).event_lock) } + } + pub(crate) const fn has_kms() -> bool { ::MODE_CONFIG_OPS.is_some() } From patchwork Wed Mar 5 22:59:46 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003657 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 7F38AC19F32 for ; Wed, 5 Mar 2025 23:09:16 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id E45B110E85F; Wed, 5 Mar 2025 23:09:15 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="EmmWW1fX"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 8601110E85F for ; Wed, 5 Mar 2025 23:09:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216153; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=/ky7nPuh5GrbUWyu2d6ZrknkHnXgJb5d1NGj1huVerQ=; b=EmmWW1fXSFKG5HgcvjUSWjbE9Vra0igT9teu78UN3q7Z+YhnJyjplPkDSTR93ePIgDpXry na3o2o7Sf5ewVEZrF6MfC50jUlm8R3UGXngmJQyudzu26aOA1go8jcrkJpy2sYt8lMCCYM Ct+0jXI6RcGC8Y8SJfEkyQ1ewwV3Vvk= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-460-dvQIoEmzNHGzBncmaxMr1w-1; Wed, 05 Mar 2025 18:08:54 -0500 X-MC-Unique: dvQIoEmzNHGzBncmaxMr1w-1 X-Mimecast-MFC-AGG-ID: dvQIoEmzNHGzBncmaxMr1w_1741216132 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 66278180025E; Wed, 5 Mar 2025 23:08:52 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 0B3BB300019E; Wed, 5 Mar 2025 23:08:48 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 30/33] rust: drm/kms: Add Device::num_crtcs() Date: Wed, 5 Mar 2025 17:59:46 -0500 Message-ID: <20250305230406.567126-31-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" A binding for checking drm_device.num_crtcs. We'll need this in a moment for vblank support, since setting it up requires knowing the number of CRTCs that a driver has initialized. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 429ce28229c9e..36a0b4c4454ba 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -311,6 +311,17 @@ pub fn mode_config_lock(&self) -> ModeConfigGuard<'_, T> { // held throughout ModeConfigGuard's lifetime. ModeConfigGuard(self.mode_config_mutex().lock(), PhantomData) } + + /// Return the number of registered CRTCs + #[inline] + pub fn num_crtcs(&self) -> u32 { + // SAFETY: + // * This can only be modified during the single-threaded context before registration, so + // this is safe + // * num_crtc could be >= 0, but no less - so casting to u32 is fine (and better to prevent + // errors) + unsafe { (*self.as_raw()).mode_config.num_crtc as u32 } + } } /// A modesetting object in DRM. From patchwork Wed Mar 5 22:59:47 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003659 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 00A7BC19F32 for ; Wed, 5 Mar 2025 23:09:23 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 5E02C10E864; Wed, 5 Mar 2025 23:09:23 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="iVhomQzt"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 90D9710E864 for ; Wed, 5 Mar 2025 23:09:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216160; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=pb/uOI59msB+UZp03QgOsI5r29hZ3aXpEiiWVId3ftY=; b=iVhomQztqRHedX2XiiQ8hizMW/sAcDsU9OoU+CEgw6YewSzzES+mr9/WJy7TRNn+/J4nPz 0YAOImby/zFaIBYOxBOMb7Nv1OEMHvLBTBiPWPWZd+Qx6I7+prqLPTM3JESrmOokNdjBVb j+HAR45SgvtZgFONYoul4OC7JjfYrfs= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-119-CRxRtHnYMgi68Gp5y1qzSg-1; Wed, 05 Mar 2025 18:09:14 -0500 X-MC-Unique: CRxRtHnYMgi68Gp5y1qzSg-1 X-Mimecast-MFC-AGG-ID: CRxRtHnYMgi68Gp5y1qzSg_1741216151 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9879019560B0; Wed, 5 Mar 2025 23:09:11 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 28FC9300019E; Wed, 5 Mar 2025 23:09:07 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Greg Kroah-Hartman , Asahi Lina , Wedson Almeida Filho , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 31/33] rust: drm/kms: Add VblankSupport Date: Wed, 5 Mar 2025 17:59:47 -0500 Message-ID: <20250305230406.567126-32-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This commit adds bindings for implementing vblank support for a driver's CRTCs. These bindings are optional, to account for the fact that not all drivers have dedicated hardware vblanks. In order to accomplish this, we introduce the VblankSupport trait which can be implemented on DriverCrtc by drivers which support vblanks. This works in the same way as the main Kms trait - drivers which don't support hardware vblanks can simply pass PhantomData to the associated type on DriverCrtc. If a driver chooses to implement VblankSupport, VblankImpl will be implemented by DRM automatically - and can be passed to the VblankImpl associated type on DriverCrtc. Additionally, we gate methods which only apply to vblank-supporting drivers by introducing a VblankDriverCrtc trait that is automatically implemented by DRM for CRTC drivers implementing VblankSupport. This works basically in the same way as Kms and KmsDriver, but for CRTCs. Signed-off-by: Lyude Paul --- Notes: * One thing to keep in mind: this trait is implemented on the CRTC as opposed to the KMS driver due to the possibility that a driver may have multiple different types of CRTCs. As a result, it's not impossible that there could potentially be differences in each type's vblank hardware implementation. In theory this could lead to a driver mistakenly only implementing VblankSupport for some CRTCs and not others, which isn't really defined behavior in DRM. As such, one of the dependencies in the branch for this patch series preview is a fix to ensure that DRM disallows registering drivers that make this mistake. V3: * Update to the latest SpinlockIrq changes * Fix typo on get_vblank_timestamp() * Break statements in vblank_crtc() up a bit * Add comments around all uses of ManuallyDrop * Improve SAFETY comments * Make some unsafe scopes smaller Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 1 + rust/helpers/drm/drm.c | 2 + rust/helpers/drm/vblank.c | 8 + rust/kernel/drm/device.rs | 1 - rust/kernel/drm/kms.rs | 21 +- rust/kernel/drm/kms/crtc.rs | 27 +- rust/kernel/drm/kms/vblank.rs | 448 ++++++++++++++++++++++++++++++++ 7 files changed, 501 insertions(+), 7 deletions(-) create mode 100644 rust/helpers/drm/vblank.c create mode 100644 rust/kernel/drm/kms/vblank.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 2e80a62062fc8..a9a89e9c8adbc 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/drm/drm.c b/rust/helpers/drm/drm.c index 365f6807774d4..5d4498e01fd3e 100644 --- a/rust/helpers/drm/drm.c +++ b/rust/helpers/drm/drm.c @@ -2,7 +2,9 @@ #ifdef CONFIG_DRM_KMS_HELPER #include "atomic.c" +#include "vblank.c" #endif + #include "gem.c" #ifdef CONFIG_DRM_GEM_SHMEM_HELPER #include "gem_shmem_helper.c" diff --git a/rust/helpers/drm/vblank.c b/rust/helpers/drm/vblank.c new file mode 100644 index 0000000000000..165db7ac5b4da --- /dev/null +++ b/rust/helpers/drm/vblank.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +struct drm_vblank_crtc *rust_helper_drm_crtc_vblank_crtc(struct drm_crtc *crtc) +{ + return drm_crtc_vblank_crtc(crtc); +} diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index 3938ceb044645..2c909985b1687 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -156,7 +156,6 @@ pub fn data(&self) -> ::Borrowed<'_> { } /// Returns a reference to the `event` spinlock - #[allow(dead_code)] pub(crate) fn event_lock(&self) -> &SpinLockIrq<()> { // SAFETY: `event_lock` is initialized for as long as `self` is exposed to users unsafe { SpinLockIrq::from_raw(&mut (*self.as_raw()).event_lock) } diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 36a0b4c4454ba..38015066491f9 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -8,6 +8,7 @@ pub mod encoder; pub mod framebuffer; pub mod plane; +pub mod vblank; use crate::{ container_of, device, @@ -20,6 +21,7 @@ }; use bindings; use core::{ + cell::Cell, marker::PhantomData, ops::Deref, ptr::{self, addr_of_mut, NonNull}, @@ -168,6 +170,13 @@ pub fn from_opaque<$( $lifetime, )? $( $decl_bound_id ),* >( /// state required during the initialization process of a [`Device`]. pub struct UnregisteredKmsDevice<'a, T: Driver> { drm: &'a Device, + // TODO: Get rid of this - I think the solution we came up on the C side to just make it so that + // DRM is a bit more consistent with verifying whether all CRTCs have this implemented or not - + // meaning we don't need to keep track of this and can just make the vblank setup conditional on + // the implementation of `VblankSupport`. + // Note that this also applies to headless devices - those are literally the same but + // `dev.num_crtc()` = 0 + pub(crate) has_vblanks: Cell, } impl<'a, T: Driver> Deref for UnregisteredKmsDevice<'a, T> { @@ -185,7 +194,10 @@ impl<'a, T: Driver> UnregisteredKmsDevice<'a, T> { /// /// The caller promises that `drm` is an unregistered [`Device`]. pub(crate) unsafe fn new(drm: &'a Device) -> Self { - Self { drm } + Self { + drm, + has_vblanks: Cell::new(false), + } } } @@ -262,6 +274,11 @@ unsafe fn setup_kms(drm: &Device) -> Result { T::create_objects(&drm)?; + if drm.has_vblanks.get() { + // SAFETY: `has_vblank` is only true if CRTCs with vblank support were registered + to_result(unsafe { bindings::drm_vblank_init(drm.as_raw(), drm.num_crtcs()) })?; + } + // TODO: Eventually add a hook to customize how state readback happens, for now just reset // SAFETY: Since all static modesetting objects were created in `T::create_objects()`, and // that is the only place they can be created, this fulfills the C API requirements. @@ -312,7 +329,7 @@ pub fn mode_config_lock(&self) -> ModeConfigGuard<'_, T> { ModeConfigGuard(self.mode_config_mutex().lock(), PhantomData) } - /// Return the number of registered CRTCs + /// Return the number of registered [`Crtc`](crtc::Crtc) objects on this [`Device`]. #[inline] pub fn num_crtcs(&self) -> u32 { // SAFETY: diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 9026d0911aa86..8f07197b639f5 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -5,7 +5,7 @@ //! C header: [`include/drm/drm_crtc.h`](srctree/include/drm/drm_crtc.h) use super::{ - atomic::*, plane::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, + atomic::*, plane::*, vblank::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice, }; use crate::{ @@ -58,13 +58,13 @@ pub trait DriverCrtc: Send + Sync + Sized { cursor_set2: None, cursor_set: None, destroy: Some(crtc_destroy_callback::), - disable_vblank: None, + disable_vblank: ::VBLANK_OPS.disable_vblank, early_unregister: None, - enable_vblank: None, + enable_vblank: ::VBLANK_OPS.enable_vblank, gamma_set: None, get_crc_sources: None, get_vblank_counter: None, - get_vblank_timestamp: None, + get_vblank_timestamp: ::VBLANK_OPS.get_vblank_timestamp, late_register: None, page_flip: Some(bindings::drm_atomic_helper_page_flip), page_flip_target: None, @@ -129,6 +129,12 @@ pub trait DriverCrtc: Send + Sync + Sized { /// See [`DriverCrtcState`] for more info. type State: DriverCrtcState; + /// The driver's optional hardware vblank implementation + /// + /// See [`VblankSupport`] for more info. Drivers that don't care about this can just pass + /// [`PhantomData`]. + type VblankImpl: VblankImpl; + /// The constructor for creating a [`Crtc`] using this [`DriverCrtc`] implementation. /// /// Drivers may use this to instantiate their [`DriverCrtc`] object. @@ -298,6 +304,15 @@ impl Crtc { T as DriverCrtc, D as KmsDriver } + + pub(crate) fn get_vblank_ptr(&self) -> *mut bindings::drm_vblank_crtc { + // SAFETY: FFI Call with no special requirements + unsafe { bindings::drm_crtc_vblank_crtc(self.as_raw()) } + } + + pub(crate) const fn has_vblank() -> bool { + T::OPS.funcs.enable_vblank.is_some() + } } /// A [`Crtc`] that has not yet been registered with userspace. @@ -328,6 +343,10 @@ pub fn new<'a, 'b: 'a, PrimaryData, CursorData>( PrimaryData: DriverPlane, CursorData: DriverPlane, { + if Crtc::::has_vblank() { + dev.has_vblanks.set(true) + } + let this: Pin>> = KBox::try_pin_init( try_pin_init!(Crtc:: { crtc: Opaque::new(bindings::drm_crtc { diff --git a/rust/kernel/drm/kms/vblank.rs b/rust/kernel/drm/kms/vblank.rs new file mode 100644 index 0000000000000..da3c61dec7c04 --- /dev/null +++ b/rust/kernel/drm/kms/vblank.rs @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM KMS vblank support. +//! +//! C header: [`include/drm/drm_vblank.h`](srcfree/include/drm/drm_vblank.h) + +use super::{crtc::*, ModeObject}; +use bindings; +use core::{ + marker::*, + mem::{self, ManuallyDrop}, + ops::{Deref, Drop}, + ptr::{addr_of_mut, null_mut}, +}; +use kernel::{ + drm::device::Device, + error::{from_result, to_result}, + interrupt::LocalInterruptDisabled, + prelude::*, + time::Ktime, + types::Opaque, +}; + +/// The main trait for a driver to implement hardware vblank support for a [`Crtc`]. +/// +/// # Invariants +/// +/// C FFI callbacks generated using this trait can safely assume that input pointers to +/// [`struct drm_crtc`] are always contained within a [`Crtc`]. +/// +/// [`struct drm_crtc`]: srctree/include/drm/drm_crtc.h +pub trait VblankSupport: Sized { + /// The parent [`DriverCrtc`]. + type Crtc: VblankDriverCrtc; + + /// Enable vblank interrupts for this [`DriverCrtc`]. + fn enable_vblank( + crtc: &Crtc, + vblank_guard: &VblankGuard<'_, Self::Crtc>, + irq: &LocalInterruptDisabled, + ) -> Result; + + /// Disable vblank interrupts for this [`DriverCrtc`]. + fn disable_vblank( + crtc: &Crtc, + vblank_guard: &VblankGuard<'_, Self::Crtc>, + irq: &LocalInterruptDisabled, + ); + + /// Retrieve the current vblank timestamp for this [`Crtc`] + /// + /// If this function is being called from the driver's vblank interrupt handler, + /// `handling_vblank_irq` will be `true`. + fn get_vblank_timestamp( + crtc: &Crtc, + in_vblank_irq: bool, + ) -> Option; +} + +/// Trait used for CRTC vblank (or lack there-of) implementations. Implemented internally. +/// +/// Drivers interested in implementing vblank support should refer to [`VblankSupport`], drivers +/// that don't have vblank support can use [`PhantomData`]. +pub trait VblankImpl { + /// The parent [`DriverCrtc`]. + type Crtc: DriverCrtc; + + /// The generated [`VblankOps`]. + const VBLANK_OPS: VblankOps; +} + +/// C FFI callbacks for vblank management. +/// +/// Created internally by DRM. +#[derive(Default)] +pub struct VblankOps { + pub(crate) enable_vblank: Option i32>, + pub(crate) disable_vblank: Option, + pub(crate) get_vblank_timestamp: Option< + unsafe extern "C" fn( + crtc: *mut bindings::drm_crtc, + max_error: *mut i32, + vblank_time: *mut bindings::ktime_t, + in_vblank_irq: bool, + ) -> bool, + >, +} + +impl VblankImpl for T { + type Crtc = T::Crtc; + + const VBLANK_OPS: VblankOps = VblankOps { + enable_vblank: Some(enable_vblank_callback::), + disable_vblank: Some(disable_vblank_callback::), + get_vblank_timestamp: Some(get_vblank_timestamp_callback::), + }; +} + +impl VblankImpl for PhantomData +where + T: DriverCrtc>, +{ + type Crtc = T; + + const VBLANK_OPS: VblankOps = VblankOps { + enable_vblank: None, + disable_vblank: None, + get_vblank_timestamp: None, + }; +} + +unsafe extern "C" fn enable_vblank_callback( + crtc: *mut bindings::drm_crtc, +) -> i32 { + // SAFETY: We're guaranteed that `crtc` is of type `Crtc` by type invariants. + let crtc = unsafe { Crtc::::from_raw(crtc) }; + + // SAFETY: This callback happens with IRQs disabled + let irq = unsafe { LocalInterruptDisabled::assume_disabled() }; + + // SAFETY: This callback happens with `vbl_lock` already held + // We don't want to drop `vbl_lock` when this callback completes since DRM will do this for us, + // so wrap the `VblankGuard` in a `ManuallyDrop` + let vblank_guard = ManuallyDrop::new(unsafe { VblankGuard::new(crtc, irq) }); + + from_result(|| T::enable_vblank(crtc, &vblank_guard, irq).map(|_| 0)) +} + +unsafe extern "C" fn disable_vblank_callback(crtc: *mut bindings::drm_crtc) { + // SAFETY: We're guaranteed that `crtc` is of type `Crtc` by type invariants. + let crtc = unsafe { Crtc::::from_raw(crtc) }; + + // SAFETY: This callback happens with IRQs disabled + let irq = unsafe { LocalInterruptDisabled::assume_disabled() }; + + // SAFETY: This call happens with `vbl_lock` already held + // We don't want to drop `vbl_lock` when this callback completes since DRM will do this for us, + // so wrap the `VblankGuard` in a `ManuallyDrop` + let vblank_guard = ManuallyDrop::new(unsafe { VblankGuard::new(crtc, irq) }); + + T::disable_vblank(crtc, &vblank_guard, irq); +} + +unsafe extern "C" fn get_vblank_timestamp_callback( + crtc: *mut bindings::drm_crtc, + max_error: *mut i32, + vblank_time: *mut bindings::ktime_t, + in_vblank_irq: bool, +) -> bool { + // SAFETY: We're guaranteed `crtc` is of type `Crtc` by type invariance + let crtc = unsafe { Crtc::::from_raw(crtc) }; + + if let Some(timestamp) = T::get_vblank_timestamp(crtc, in_vblank_irq) { + // SAFETY: Both of these pointers are guaranteed by the C API to be valid + unsafe { + (*max_error) = timestamp.max_error; + (*vblank_time) = timestamp.time.to_ns(); + }; + + true + } else { + false + } +} + +/// A vblank timestamp. +/// +/// This type is used by [`VblankSupport::get_vblank_timestamp`] for the implementor to return the +/// current vblank timestamp for the hardware. +#[derive(Copy, Clone)] +pub struct VblankTimestamp { + /// The actual vblank timestamp, accuracy to within [`Self::max_error`] nanoseconds + pub time: Ktime, + + /// Maximum allowable timestamp error in nanoseconds + pub max_error: i32, +} + +/// A trait for [`DriverCrtc`] implementations with hardware vblank support. +/// +/// This trait is implemented internally by DRM for any [`DriverCrtc`] implementation that +/// implements [`VblankSupport`]. It is used to expose hardware-vblank driver exclusive methods and +/// data to users. +pub trait VblankDriverCrtc: DriverCrtc {} + +impl VblankDriverCrtc for T +where + T: DriverCrtc, + V: VblankSupport, +{ +} + +impl Crtc { + /// Retrieve a reference to the [`VblankCrtc`] for this [`Crtc`]. + pub(crate) fn vblank_crtc(&self) -> &VblankCrtc { + // SAFETY: + // - The data layouts of these types are equivalent via `VblankCrtc`s type invariants + // - We don't expose any way of calling `vblank_crtc()` before `drm_vblank_init()` has been + // called. + unsafe { VblankCrtc::from_raw(self.get_vblank_ptr()) } + } + + /// Access vblank related infrastructure for a [`Crtc`]. + /// + /// This function explicitly locks the device's vblank lock, and allows access to controlling + /// the vblank configuration for this CRTC. The lock is dropped once [`VblankGuard`] is + /// dropped. + pub fn vblank_lock<'a>(&'a self, irq: &'a LocalInterruptDisabled) -> VblankGuard<'a, T> { + // SAFETY: `vbl_lock` is initialized for as long as `Crtc` is available to users + // INVARIANT: We just acquired `vbl_lock`, fulfilling the invariants of `VblankGuard` + unsafe { bindings::spin_lock(addr_of_mut!((*self.drm_dev().as_raw()).vbl_lock)) }; + + // SAFETY: We just acquired vbl_lock above + unsafe { VblankGuard::new(self, irq) } + } + + /// Trigger a vblank event on this [`Crtc`]. + /// + /// Drivers should use this in their vblank interrupt handlers to update the vblank counter and + /// send any signals that may be pending. + /// + /// Returns whether or not the vblank event was handled. + #[inline] + pub fn handle_vblank(&self) -> bool { + // SAFETY: `as_raw()` always returns a valid pointer to an initialized drm_crtc. + unsafe { bindings::drm_crtc_handle_vblank(self.as_raw()) } + } + + /// Forbid vblank events for a [`Crtc`]. + /// + /// This function disables vblank events for a [`Crtc`], even if [`VblankRef`] objects exist. + #[inline] + pub fn vblank_off(&self) { + // SAFETY: `as_raw()` always returns a valid pointer to an initialized drm_crtc. + unsafe { bindings::drm_crtc_vblank_off(self.as_raw()) } + } + + /// Allow vblank events for a [`Crtc`]. + /// + /// This function allows users to enable vblank events and acquire [`VblankRef`] objects again. + #[inline] + pub fn vblank_on(&self) { + // SAFETY: `as_raw()` always returns a valid pointer to an initialized drm_crtc. + unsafe { bindings::drm_crtc_vblank_on(self.as_raw()) } + } + + /// Enable vblank events for a [`Crtc`]. + /// + /// Returns a [`VblankRef`] which will allow vblank events to be sent until it is dropped. Note + /// that vblank events may still be disabled by [`Self::vblank_off`]. + #[must_use = "Vblanks are only enabled until the result from this function is dropped"] + pub fn vblank_get(&self) -> Result> { + VblankRef::new(self) + } +} + +/// Common methods available on any [`CrtcState`] whose [`Crtc`] implements [`VblankSupport`]. +/// +/// This trait is implemented automatically by DRM for any [`DriverCrtc`] implementation that +/// implements [`VblankSupport`]. +pub trait RawVblankCrtcState: AsRawCrtcState { + /// Return the [`PendingVblankEvent`] for this CRTC state, if there is one. + fn get_pending_vblank_event(&mut self) -> Option> + where + Self: Sized, + { + // SAFETY: The driver is the only one that will ever modify this data, and since our + // interface follows rust's data aliasing rules that means this is safe to read + let event_ptr = unsafe { *self.as_raw() }.event; + + (!event_ptr.is_null()).then_some(PendingVblankEvent(self)) + } +} + +impl RawVblankCrtcState for T +where + T: AsRawCrtcState>, + C: VblankDriverCrtc, +{ +} + +/// A pending vblank event from an atomic state +pub struct PendingVblankEvent<'a, T: RawVblankCrtcState>(&'a mut T); + +impl<'a, T: RawVblankCrtcState> PendingVblankEvent<'a, T> { + /// Send this [`PendingVblankEvent`]. + /// + /// A [`PendingVblankEvent`] can only be sent once, so this function consumes the + /// [`PendingVblankEvent`]. + pub fn send(self) + where + T: RawVblankCrtcState>, + C: VblankDriverCrtc, + { + let crtc: &Crtc = self.0.crtc(); + let event_lock = crtc.drm_dev().event_lock(); + let _guard = event_lock.lock(); + + // SAFETY: + // - We now hold the appropriate lock to call this function + // - Vblanks are enabled as proved by `vbl_ref`, as per the C api requirements + // - Our interface is proof that `event` is non-null + unsafe { bindings::drm_crtc_send_vblank_event(crtc.as_raw(), (*self.0.as_raw()).event) }; + + // SAFETY: The mutable reference in `self.state` is proof that it is safe to mutate this, + // and DRM expects us to set this to NULL once we've sent the vblank event. + unsafe { (*self.0.as_raw()).event = null_mut() }; + } + + /// Arm this [`PendingVblankEvent`] to be sent later by the CRTC's vblank interrupt handler. + /// + /// A [`PendingVblankEvent`] can only be armed once, so this function consumes the + /// [`PendingVblankEvent`]. As well, it requires a [`VblankRef`] so that vblank interrupts + /// remain enabled until the [`PendingVblankEvent`] has been sent out by the driver's vblank + /// interrupt handler. + pub fn arm(self, vbl_ref: VblankRef<'_, C>) + where + T: RawVblankCrtcState>, + C: VblankDriverCrtc, + { + let crtc: &Crtc = self.0.crtc(); + let event_lock = crtc.drm_dev().event_lock(); + let _guard = event_lock.lock(); + + // SAFETY: + // - We now hold the appropriate lock to call this function + // - Vblanks are enabled as proved by `vbl_ref`, as per the C api requirements + // - Our interface is proof that `event` is non-null + unsafe { bindings::drm_crtc_arm_vblank_event(crtc.as_raw(), (*self.0.as_raw()).event) }; + + // SAFETY: The mutable reference in `self.state` is proof that it is safe to mutate this, + // and DRM expects us to set this to NULL once we've armed the vblank event. + unsafe { (*self.0.as_raw()).event = null_mut() }; + + // DRM took ownership of `vbl_ref` after we called `drm_crtc_arm_vblank_event` + mem::forget(vbl_ref); + } +} + +/// A borrowed vblank reference. +/// +/// This object keeps the vblank reference count for a [`Crtc`] incremented for as long as it +/// exists, enabling vblank interrupts for said [`Crtc`] until all references are dropped, or +/// [`Crtc::vblank_off`] is called - whichever comes first. +pub struct VblankRef<'a, T: VblankDriverCrtc>(&'a Crtc); + +impl Drop for VblankRef<'_, T> { + fn drop(&mut self) { + // SAFETY: as_raw() returns a valid pointer to an initialized drm_crtc + unsafe { bindings::drm_crtc_vblank_put(self.0.as_raw()) }; + } +} + +impl<'a, T: VblankDriverCrtc> VblankRef<'a, T> { + fn new(crtc: &'a Crtc) -> Result { + // SAFETY: as_raw() returns a valid pointer to an initialized drm_crtc + to_result(unsafe { bindings::drm_crtc_vblank_get(crtc.as_raw()) })?; + + Ok(Self(crtc)) + } +} + +/// The base wrapper for [`drm_vblank_crtc`]. +/// +/// Users will rarely interact with this object directly, it is a simple wrapper around +/// [`drm_vblank_crtc`] which provides access to methods and data that is not protected by a lock. +/// +/// # Invariants +/// +/// This type has an identical data layout to [`drm_vblank_crtc`]. +/// +/// [`drm_vblank_crtc`]: srctree/include/drm/drm_vblank.h +#[repr(transparent)] +pub struct VblankCrtc(Opaque, PhantomData); + +impl VblankCrtc { + pub(crate) fn as_raw(&self) -> *mut bindings::drm_vblank_crtc { + self.0.get() + } + + // SAFETY: The caller promises that `ptr` points to a valid instance of + // `bindings::drm_vblank_crtc`, and that access to this structure has been properly serialized + pub(crate) unsafe fn from_raw<'a>(ptr: *mut bindings::drm_vblank_crtc) -> &'a Self { + // SAFETY: Our data layouts are identical via #[repr(transparent)] + unsafe { &*ptr.cast() } + } + + /// Returns the [`Device`] for this [`VblankGuard`] + pub fn drm_dev(&self) -> &Device { + // SAFETY: `drm` is initialized, invariant and valid throughout our lifetime + unsafe { Device::borrow((*self.as_raw()).dev) } + } +} + +// NOTE: This type does not use a `Guard` because the mutex is not contained within the same +// structure as the relevant CRTC +/// An interface for accessing and controlling vblank related state for a [`Crtc`]. +/// +/// This type may be returned from some [`VblankSupport`] callbacks, or manually via +/// [`Crtc::vblank_lock`]. It provides access to methods and data which require +/// [`drm_device.vbl_lock`] be held. +/// +/// # Invariants +/// +/// - [`drm_device.vbl_lock`] is acquired whenever an instance of this type exists. +/// - Shares the invariants of [`VblankCrtc`]. +/// +/// [`drm_device.vbl_lock`]: srctree/include/drm/drm_device.h +#[repr(transparent)] +pub struct VblankGuard<'a, T: VblankDriverCrtc>(&'a VblankCrtc); + +impl<'a, T: VblankDriverCrtc> VblankGuard<'a, T> { + /// Construct a new [`VblankGuard`] + /// + /// # Safety + /// + /// The caller must have already acquired [`drm_device.vbl_lock`]. + /// + /// [`drm_device.vbl_lock`]: srctree/include/drm/drm_device.h + pub(crate) unsafe fn new(crtc: &'a Crtc, _irq: &'a LocalInterruptDisabled) -> Self { + // INVARIANT: The caller promises that we've acquired `vbl_lock` + Self(crtc.vblank_crtc()) + } + + /// Returns the duration of a single scanout frame in ns + pub fn frame_duration(&self) -> i32 { + // SAFETY: We hold the appropriate lock for this read via our type invariants. + unsafe { *self.as_raw() }.framedur_ns + } +} + +impl Deref for VblankGuard<'_, T> { + type Target = VblankCrtc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for VblankGuard<'_, T> { + fn drop(&mut self) { + // SAFETY: + // - We acquired this spinlock when creating this object + // - This lock is guaranteed to be initialized for as long as our DRM device is exposed to + // users. + unsafe { bindings::spin_unlock(addr_of_mut!((*self.drm_dev().as_raw()).vbl_lock)) } + } +} From patchwork Wed Mar 5 22:59:48 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003660 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 52F54C19F32 for ; Wed, 5 Mar 2025 23:09:30 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id B85E310E862; Wed, 5 Mar 2025 23:09:29 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="dsfoplQ0"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 848DB10E862 for ; Wed, 5 Mar 2025 23:09:28 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216167; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=x8KtBjLaq8lmYfyJdFyEyWTSUP/Nf3HWrVvlOKUEuiU=; b=dsfoplQ07An9ardIy8CBAle8op1k4U4OKy6k0divvwUJ4B/g+5ItpG2nDPVG+ZTddxlRr7 gcQThpiJLqJs+OlS/Nvy6KOZRP142krE56lIwyFgZIQ5MhDHgnnpD9Jxi6YaHfDbckbjiE DVGJNYxC8Qgoavv775payoro3OMX120= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-247-vGRJGGJLOYWUjH_Zhuyy5Q-1; Wed, 05 Mar 2025 18:09:23 -0500 X-MC-Unique: vGRJGGJLOYWUjH_Zhuyy5Q-1 X-Mimecast-MFC-AGG-ID: vGRJGGJLOYWUjH_Zhuyy5Q_1741216161 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 04CFD1800258; Wed, 5 Mar 2025 23:09:21 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 2751B300019E; Wed, 5 Mar 2025 23:09:17 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 32/33] rust: drm/kms: Add Kms::atomic_commit_tail Date: Wed, 5 Mar 2025 17:59:48 -0500 Message-ID: <20250305230406.567126-33-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" A quick note: this is one of my favorite bindings so far :). It sounds way overly complicated, but so far actually writing implementations of this in rust has been a breeze. Anyway: RVKMS has a slightly different atomic_commit_tail than normal, which means we need to write up some bindings for atomic_commit_tail. This is a lot more interesting then it might seem on the surface as implementing atomic_commit_tail incorrectly could result in UB. And in general, DRM has up until now relied entirely on the programmer to do this correctly through implicit ordering requirements. In the universe of rust though, we want no UB at all! To ensure this, we need to make sure that all atomic commit callbacks follow all of these requirements: * Disable/enable modeset commits must happen exactly once * A disable modeset must be committed for a resource before an enable modeset may be committed for a resource * Plane updates must happen exactly once * drm_atomic_commit_hw_done() must be called exactly once, and only after all commits have been completed. * The state may not be mutated after drm_atomic_commit_hw_done() is called * Access to the prior atomic states are revoked after drm_atomic_commit_hw_done() is called (and our "new" states become "old" states) To handle this, we introduce a number of new objects and types: tokens: * AtomicCommitTail Main object for controlling the commit_tail process * ModesetsReadyToken A single use token indicating that no modesets have been committed with the AtomicCommitTail yet * commit_modeset_disables() -> DisablesCommittedToken This function consumes the ModesetsReadyToken, commits modeset disables, and then returns a DisablesCommittedToken * commit_modeset_enables() -> EnablesCommittedToken This function consumes a DisablesCommittedToken, commits modeset enables, and then returns a EnablesCommittedToken EnablesCommittedToken - enforcing the disables -> enables order. * commit_planes() -> PlaneUpdatesCommittedToken Consumes a PlaneUpdatesReadyToken and returns a PlaneUpdatesCommittedToken. * commit_hw_done() -> CommittedAtomicState Revokes access to the AtomicCommitTailObject, and consumes both the EnablesCommittedToken and PlaneUpdatesCommitted tokens. This ensures that all modesets and plane updates have occurred exactly once. * CommittedAtomicState - main object for controlling the atomic_commit_tail after the state has been swapped in. This must be returned from the atomic_commit_tail function to prove that all of the required commits have occurred. Signed-off-by: Lyude Paul --- V3: * Fix all warnings * Minor doc fixes NOTES: * Currently this solution wouldn't be sufficient for drivers that need precise control over the order of each individual modeset or plane update. However, this should be very easy to add. Signed-off-by: Lyude Paul --- rust/kernel/drm/kms.rs | 29 ++- rust/kernel/drm/kms/atomic.rs | 359 ++++++++++++++++++++++++++++++++++ 2 files changed, 387 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 38015066491f9..e38035c024930 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -219,6 +219,29 @@ fn mode_config_info( fn create_objects(drm: &UnregisteredKmsDevice<'_, Self>) -> Result where Self: Sized; + + /// The optional [`atomic_commit_tail`] callback for this [`Device`]. + /// + /// It must return a [`CommittedAtomicState`] to prove that it has signaled completion of the hw + /// commit phase. Drivers may use this function to customize the order in which commits are + /// performed during the atomic commit phase. + /// + /// If not provided, DRM will use its own default atomic commit tail helper + /// [`drm_atomic_helper_commit_tail`]. + /// + /// [`CommittedAtomicState`]: atomic::CommittedAtomicState + /// [`atomic_commit_tail`]: srctree/include/drm/drm_modeset_helper_vtables.h + /// [`drm_atomic_helper_commit_tail`]: srctree/include/drm/drm_atomic_helpers.h + fn atomic_commit_tail<'a>( + _state: atomic::AtomicCommitTail<'a, Self>, + _modeset_token: atomic::ModesetsReadyToken<'_>, + _plane_update_token: atomic::PlaneUpdatesReadyToken<'_>, + ) -> atomic::CommittedAtomicState<'a, Self> + where + Self: Sized, + { + build_error::build_error("This function should not be reachable") + } } impl private::KmsImpl for T { @@ -238,7 +261,11 @@ impl private::KmsImpl for T { kms_helper_vtable: bindings::drm_mode_config_helper_funcs { atomic_commit_setup: None, - atomic_commit_tail: None, + atomic_commit_tail: if Self::HAS_ATOMIC_COMMIT_TAIL { + Some(atomic::commit_tail_callback::) + } else { + None + }, }, }); diff --git a/rust/kernel/drm/kms/atomic.rs b/rust/kernel/drm/kms/atomic.rs index e0a1b5b860d6f..a0022c1a5e54d 100644 --- a/rust/kernel/drm/kms/atomic.rs +++ b/rust/kernel/drm/kms/atomic.rs @@ -356,3 +356,362 @@ pub fn add_affected_planes(&self, crtc: &C) -> Result to_result(unsafe { bindings::drm_atomic_add_affected_planes(self.as_raw(), crtc.as_raw()) }) } } + +/// A token proving that no modesets for a commit have completed. +/// +/// This token is proof that no commits have yet completed, and is provided as an argument to +/// [`KmsDriver::atomic_commit_tail`]. This may be used with +/// [`AtomicCommitTail::commit_modeset_disables`]. +pub struct ModesetsReadyToken<'a>(PhantomData<&'a ()>); + +/// A token proving that modeset disables for a commit have completed. +/// +/// This token is proof that an implementor's [`KmsDriver::atomic_commit_tail`] phase has finished +/// committing any operations which disable mode objects. It is returned by +/// [`AtomicCommitTail::commit_modeset_disables`], and can be used with +/// [`AtomicCommitTail::commit_modeset_enables`] to acquire a [`EnablesCommittedToken`]. +pub struct DisablesCommittedToken<'a>(PhantomData<&'a ()>); + +/// A token proving that modeset enables for a commit have completed. +/// +/// This token is proof that an implementor's [`KmsDriver::atomic_commit_tail`] phase has finished +/// committing any operations which enable mode objects. It is returned by +/// [`AtomicCommitTail::commit_modeset_enables`]. +pub struct EnablesCommittedToken<'a>(PhantomData<&'a ()>); + +/// A token proving that no plane updates for a commit have completed. +/// +/// This token is proof that no plane updates have yet been completed within an implementor's +/// [`KmsDriver::atomic_commit_tail`] implementation, and that we are ready to begin updating planes. It +/// is provided as an argument to [`KmsDriver::atomic_commit_tail`]. +pub struct PlaneUpdatesReadyToken<'a>(PhantomData<&'a ()>); + +/// A token proving that all plane updates for a commit have completed. +/// +/// This token is proof that all plane updates within an implementor's [`KmsDriver::atomic_commit_tail`] +/// implementation have completed. It is returned by [`AtomicCommitTail::commit_planes`]. +pub struct PlaneUpdatesCommittedToken<'a>(PhantomData<&'a ()>); + +/// An [`AtomicState`] interface that allows a driver to control the [`atomic_commit_tail`] +/// callback. +/// +/// This object is provided as an argument to [`KmsDriver::atomic_commit_tail`], and represents an atomic +/// state within the commit tail phase which is still in the process of being committed to hardware. +/// It may be used to control the order in which the commit process happens. +/// +/// # Invariants +/// +/// Same as [`AtomicState`]. +/// +/// [`atomic_commit_tail`]: srctree/include/drm/drm_modeset_helper_vtables.h +pub struct AtomicCommitTail<'a, T: KmsDriver>(&'a AtomicState); + +impl<'a, T: KmsDriver> AtomicCommitTail<'a, T> { + /// Commit modesets which would disable outputs. + /// + /// This function commits any modesets which would shut down outputs, along with preparing them + /// for a new mode (if needed). + /// + /// Since it is physically impossible to disable an output multiple times, and since it is + /// logically unsound to disable an output within an atomic commit after the output was enabled + /// in the same commit - this function requires a [`ModesetsReadyToken`] to consume and returns + /// a [`DisablesCommittedToken`]. + /// + /// If compatibility with legacy CRTC helpers is desired, this + /// should be called before [`commit_planes`] which is what the default commit function does. + /// But drivers with different needs can group the modeset commits tgether and do the plane + /// commits at the end. This is useful for drivers doing runtime PM since then plane updates + /// only happen when the CRTC is actually enabled. + /// + /// [`commit_planes`]: AtomicCommitTail::commit_planes + #[inline] + #[must_use] + pub fn commit_modeset_disables<'b>( + &mut self, + _token: ModesetsReadyToken<'_>, + ) -> DisablesCommittedToken<'b> { + // SAFETY: Both `as_raw()` calls are guaranteed to return valid pointers + unsafe { + bindings::drm_atomic_helper_commit_modeset_disables( + self.0.drm_dev().as_raw(), + self.0.as_raw(), + ) + } + + DisablesCommittedToken(PhantomData) + } + + /// Commit all plane updates. + /// + /// This function performs all plane updates for the given [`AtomicCommitTail`]. Since it is + /// logically unsound to perform the same plane update more then once in a given atomic commit, + /// this function requires a [`PlaneUpdatesReadyToken`] to consume and returns a + /// [`PlaneUpdatesCommittedToken`] to prove that plane updates for the state have completed. + #[inline] + #[must_use] + pub fn commit_planes<'b>( + &mut self, + _token: PlaneUpdatesReadyToken<'_>, + flags: PlaneCommitFlags, + ) -> PlaneUpdatesCommittedToken<'b> { + // SAFETY: Both `as_raw()` calls are guaranteed to return valid pointers + unsafe { + bindings::drm_atomic_helper_commit_planes( + self.0.drm_dev().as_raw(), + self.0.as_raw(), + flags.into(), + ) + } + + PlaneUpdatesCommittedToken(PhantomData) + } + + /// Commit modesets which would enable outputs. + /// + /// This function commits any modesets in the given [`AtomicCommitTail`] which would enable + /// outputs, along with preparing them for their new modes (if needed). + /// + /// Since it is logically unsound to enable an output before any disabling modesets within the + /// same atomic commit have been performed, and physically impossible to enable the same output + /// multiple times - this function requires a [`DisablesCommittedToken`] to consume and returns + /// a [`EnablesCommittedToken`] which may be used as proof that all modesets in the state have + /// been completed. + #[inline] + #[must_use] + pub fn commit_modeset_enables<'b>( + &mut self, + _token: DisablesCommittedToken<'_>, + ) -> EnablesCommittedToken<'b> { + // SAFETY: Both `as_raw()` calls are guaranteed to return valid pointers + unsafe { + bindings::drm_atomic_helper_commit_modeset_enables( + self.0.drm_dev().as_raw(), + self.0.as_raw(), + ) + } + + EnablesCommittedToken(PhantomData) + } + + /// Fake vblank events if needed. + /// + /// Note that this is still relevant to drivers which don't implement [`VblankSupport`] for any + /// of their CRTCs. + /// + /// TODO: more doc + /// + /// [`VblankSupport`]: super::vblank::VblankSupport + pub fn fake_vblank(&mut self) { + // SAFETY: `as_raw()` is guaranteed to always return a valid pointer + unsafe { bindings::drm_atomic_helper_fake_vblank(self.0.as_raw()) } + } + + /// Signal completion of the hardware commit step. + /// + /// This swaps the atomic state into the relevant atomic state pointers and marks the hardware + /// commit step as completed. Since this step can only happen after all plane updates and + /// modesets within an [`AtomicCommitTail`] have been completed, it requires both a + /// [`EnablesCommittedToken`] and a [`PlaneUpdatesCommittedToken`] to consume. After this + /// function is called, the caller no longer has exclusive access to the underlying atomic + /// state. As such, this function consumes the [`AtomicCommitTail`] object and returns a + /// [`CommittedAtomicState`] accessor for performing post-hw commit tasks. + pub fn commit_hw_done<'b>( + self, + _modeset_token: EnablesCommittedToken<'_>, + _plane_updates_token: PlaneUpdatesCommittedToken<'_>, + ) -> CommittedAtomicState<'b, T> + where + 'a: 'b, + { + // SAFETY: we consume the `AtomicCommitTail` object, making it impossible for the user to + // mutate the state after this function has been called - which upholds the safety + // requirements of the C API allowing us to safely call this function + unsafe { bindings::drm_atomic_helper_commit_hw_done(self.0.as_raw()) }; + + CommittedAtomicState(self.0) + } +} + +// The actual raw C callback for custom atomic commit tail implementations +pub(crate) unsafe extern "C" fn commit_tail_callback( + state: *mut bindings::drm_atomic_state, +) { + // SAFETY: + // - We're guaranteed by DRM that `state` always points to a valid instance of + // `bindings::drm_atomic_state` + // - This conversion is safe via the type invariants + let state = unsafe { AtomicState::from_raw(state.cast_const()) }; + + T::atomic_commit_tail( + AtomicCommitTail(state), + ModesetsReadyToken(PhantomData), + PlaneUpdatesReadyToken(PhantomData), + ); +} + +/// An [`AtomicState`] which was just committed with [`AtomicCommitTail::commit_hw_done`]. +/// +/// This object represents an [`AtomicState`] which has been fully committed to hardware, and as +/// such may no longer be mutated as it is visible to userspace. It may be used to control what +/// happens immediately after an atomic commit finishes within the [`atomic_commit_tail`] callback. +/// +/// Since acquiring this object means that all modesetting locks have been dropped, a non-blocking +/// commit could happen at the same time an [`atomic_commit_tail`] implementer has access to this +/// object. Thus, it cannot be assumed that this object represents the current hardware state - and +/// instead only represents the final result of the [`AtomicCommitTail`] that was just committed. +/// +/// # Invariants +/// +/// It may be assumed that [`drm_atomic_helper_commit_hw_done`] has been called as long as this type +/// exists. +/// +/// [`atomic_commit_tail`]: KmsDriver::atomic_commit_tail +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_helper.h +pub struct CommittedAtomicState<'a, T: KmsDriver>(&'a AtomicState); + +impl<'a, T: KmsDriver> CommittedAtomicState<'a, T> { + /// Wait for page flips on this state to complete + pub fn wait_for_flip_done(&self) { + // SAFETY: `drm_atomic_helper_commit_hw_done` has been called via our invariants + unsafe { + bindings::drm_atomic_helper_wait_for_flip_done( + self.0.drm_dev().as_raw(), + self.0.as_raw(), + ) + } + } +} + +impl<'a, T: KmsDriver> Drop for CommittedAtomicState<'a, T> { + fn drop(&mut self) { + // SAFETY: + // * This interface represents the last atomic state accessor which could be affected as a + // result of resources from an atomic commit being cleaned up. + unsafe { + bindings::drm_atomic_helper_cleanup_planes(self.0.drm_dev().as_raw(), self.0.as_raw()) + } + } +} + +/// An enumator representing a single flag in [`PlaneCommitFlags`]. +/// +/// This is a non-exhaustive list, as the C side could add more later. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[non_exhaustive] +pub enum PlaneCommitFlag { + /// Don't notify applications of plane updates for newly-disabled planes. Drivers are encouraged + /// to set this flag by default, as otherwise they need to ignore plane updates for disabled + /// planes by hand. + ActiveOnly = (1 << 0), + /// Tell the DRM core that the display hardware requires that a [`Crtc`]'s planes must be + /// disabled when the [`Crtc`] is disabled. When not specified, + /// [`AtomicCommitTail::commit_planes`] will skip the atomic disable callbacks for a plane if + /// the [`Crtc`] in the old [`PlaneState`] needs a modesetting operation. It is still up to the + /// driver to disable said planes in their [`DriverCrtc::atomic_disable`] callback. + NoDisableAfterModeset = (1 << 1), +} + +impl BitOr for PlaneCommitFlag { + type Output = PlaneCommitFlags; + + fn bitor(self, rhs: Self) -> Self::Output { + PlaneCommitFlags(self as u32 | rhs as u32) + } +} + +impl BitOr for PlaneCommitFlag { + type Output = PlaneCommitFlags; + + fn bitor(self, rhs: PlaneCommitFlags) -> Self::Output { + PlaneCommitFlags(self as u32 | rhs.0) + } +} + +/// A bitmask for controlling the behavior of [`AtomicCommitTail::commit_planes`]. +/// +/// This corresponds to the `DRM_PLANE_COMMIT_*` flags on the C side. Note that this bitmask does +/// not discard unknown values in order to ensure that adding new flags on the C side of things does +/// not break anything in the future. +#[derive(Copy, Clone, Default, PartialEq, Eq)] +pub struct PlaneCommitFlags(u32); + +impl From for PlaneCommitFlags { + fn from(value: PlaneCommitFlag) -> Self { + Self(value as u32) + } +} + +impl From for u32 { + fn from(value: PlaneCommitFlags) -> Self { + value.0 + } +} + +impl BitOr for PlaneCommitFlags { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl BitOrAssign for PlaneCommitFlags { + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs + } +} + +impl BitAnd for PlaneCommitFlags { + type Output = PlaneCommitFlags; + + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } +} + +impl BitAndAssign for PlaneCommitFlags { + fn bitand_assign(&mut self, rhs: Self) { + *self = *self & rhs + } +} + +impl BitOr for PlaneCommitFlags { + type Output = Self; + + fn bitor(self, rhs: PlaneCommitFlag) -> Self::Output { + self | Self::from(rhs) + } +} + +impl BitOrAssign for PlaneCommitFlags { + fn bitor_assign(&mut self, rhs: PlaneCommitFlag) { + *self = *self | rhs + } +} + +impl BitAnd for PlaneCommitFlags { + type Output = PlaneCommitFlags; + + fn bitand(self, rhs: PlaneCommitFlag) -> Self::Output { + self & Self::from(rhs) + } +} + +impl BitAndAssign for PlaneCommitFlags { + fn bitand_assign(&mut self, rhs: PlaneCommitFlag) { + *self = *self & rhs + } +} + +impl PlaneCommitFlags { + /// Create a new bitmask. + pub fn new() -> Self { + Self::default() + } + + /// Check if the bitmask has the given commit flag set. + pub fn has(&self, flag: PlaneCommitFlag) -> bool { + *self & flag == flag.into() + } +} From patchwork Wed Mar 5 22:59:49 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Lyude Paul X-Patchwork-Id: 14003661 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id DC8B8C19F32 for ; Wed, 5 Mar 2025 23:10:04 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 411D610E865; Wed, 5 Mar 2025 23:10:04 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="DDj912+4"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 44C1410E865 for ; Wed, 5 Mar 2025 23:10:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741216202; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Kdb4EQdrLSuUidxpSbXS3jUnAf43WKr86VzPF4KnoRk=; b=DDj912+43UgdJ5DFcJ0bAWMBzGKjCZJmYD6T1nPcuJyyF20/ZVHCxcveEBf/oGTueGHTBA mNkKvJSYiiOtWcj85CQeXbFSgaTlT5af6mEpSn67jH/ZqwbFvdpI7waitNEjk6+ek2yT0G mfD3BDk1dA5E74pn1RF6KaF2REnvzR4= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-497-zjSxGSNpP5mcGgpp3G7ntw-1; Wed, 05 Mar 2025 18:09:45 -0500 X-MC-Unique: zjSxGSNpP5mcGgpp3G7ntw-1 X-Mimecast-MFC-AGG-ID: zjSxGSNpP5mcGgpp3G7ntw_1741216182 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 782CD1955BD2; Wed, 5 Mar 2025 23:09:42 +0000 (UTC) Received: from chopper.redhat.com (unknown [10.22.88.81]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id C7E29300019E; Wed, 5 Mar 2025 23:09:37 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org Cc: Danilo Krummrich , mcanal@igalia.com, Alice Ryhl , Maxime Ripard , Simona Vetter , Daniel Almeida , Maarten Lankhorst , Thomas Zimmermann , David Airlie , Simona Vetter , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org (open list) Subject: [RFC v3 33/33] drm: Introduce RVKMS! Date: Wed, 5 Mar 2025 17:59:49 -0500 Message-ID: <20250305230406.567126-34-lyude@redhat.com> In-Reply-To: <20250305230406.567126-1-lyude@redhat.com> References: <20250305230406.567126-1-lyude@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Now that we've added all of the bits that we need for the KMS API, it's time to introduce rvkms! This is a port of the VKMS driver to rust, with the intent of acting as an example usecase of the KMS bindings that we've come up with so far in preparation for writing a display driver for nova. Currently RVKMS is an extremely bear bones driver - it only registers a device and emulates vblanking, but it exercises a good portion of the API that we've introduced so far! Eventually I hope to introduce CRC generation and maybe writeback connectors like. Signed-off-by: Lyude Paul --- V3: * Replace platform device usage with faux device * Clean up all warnings Signed-off-by: Lyude Paul --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/rvkms/Kconfig | 3 + drivers/gpu/drm/rvkms/Makefile | 1 + drivers/gpu/drm/rvkms/connector.rs | 55 +++++++ drivers/gpu/drm/rvkms/crtc.rs | 245 +++++++++++++++++++++++++++++ drivers/gpu/drm/rvkms/encoder.rs | 31 ++++ drivers/gpu/drm/rvkms/file.rs | 19 +++ drivers/gpu/drm/rvkms/gem.rs | 29 ++++ drivers/gpu/drm/rvkms/output.rs | 36 +++++ drivers/gpu/drm/rvkms/plane.rs | 73 +++++++++ drivers/gpu/drm/rvkms/rvkms.rs | 140 +++++++++++++++++ 12 files changed, 635 insertions(+) create mode 100644 drivers/gpu/drm/rvkms/Kconfig create mode 100644 drivers/gpu/drm/rvkms/Makefile create mode 100644 drivers/gpu/drm/rvkms/connector.rs create mode 100644 drivers/gpu/drm/rvkms/crtc.rs create mode 100644 drivers/gpu/drm/rvkms/encoder.rs create mode 100644 drivers/gpu/drm/rvkms/file.rs create mode 100644 drivers/gpu/drm/rvkms/gem.rs create mode 100644 drivers/gpu/drm/rvkms/output.rs create mode 100644 drivers/gpu/drm/rvkms/plane.rs create mode 100644 drivers/gpu/drm/rvkms/rvkms.rs diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index fbef3f471bd0e..18eec6f914e52 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -336,6 +336,8 @@ source "drivers/gpu/drm/amd/amdgpu/Kconfig" source "drivers/gpu/drm/nouveau/Kconfig" +source "drivers/gpu/drm/rvkms/Kconfig" + source "drivers/gpu/drm/i915/Kconfig" source "drivers/gpu/drm/xe/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 19fb370fbc567..17db3b1202d4d 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -176,6 +176,7 @@ obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/ obj-$(CONFIG_DRM_VGEM) += vgem/ obj-$(CONFIG_DRM_VKMS) += vkms/ obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/ +obj-$(CONFIG_DRM_RVKMS) += rvkms/ obj-$(CONFIG_DRM_EXYNOS) +=exynos/ obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/ obj-$(CONFIG_DRM_GMA500) += gma500/ diff --git a/drivers/gpu/drm/rvkms/Kconfig b/drivers/gpu/drm/rvkms/Kconfig new file mode 100644 index 0000000000000..551422803b9a6 --- /dev/null +++ b/drivers/gpu/drm/rvkms/Kconfig @@ -0,0 +1,3 @@ +config DRM_RVKMS + tristate "Rust VKMS PoC driver (EXPERIMENTAL)" + depends on RUST && DRM && DRM_GEM_SHMEM_HELPER=y diff --git a/drivers/gpu/drm/rvkms/Makefile b/drivers/gpu/drm/rvkms/Makefile new file mode 100644 index 0000000000000..18e06fc3343c6 --- /dev/null +++ b/drivers/gpu/drm/rvkms/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_DRM_RVKMS) += rvkms.o diff --git a/drivers/gpu/drm/rvkms/connector.rs b/drivers/gpu/drm/rvkms/connector.rs new file mode 100644 index 0000000000000..0c56451bf69df --- /dev/null +++ b/drivers/gpu/drm/rvkms/connector.rs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +//! RVKMS's DRM Connector implementation +use super::{RvkmsDriver, DEFAULT_RES, MAX_RES}; +use core::marker::PhantomPinned; +use kernel::{ + drm::{ + device::Device, + kms::{ + connector::{self, ConnectorGuard, DriverConnectorOps}, + ModeConfigGuard, + }, + }, + prelude::*, +}; + +#[pin_data] +pub(crate) struct DriverConnector { + #[pin] + _p: PhantomPinned, +} + +#[allow(unused)] +pub(crate) type Connector = connector::Connector; +pub(crate) type UnregisteredConnector = connector::UnregisteredConnector; + +#[vtable] +impl connector::DriverConnector for DriverConnector { + #[unique] + const OPS: &'static DriverConnectorOps; + + type State = ConnectorState; + type Driver = RvkmsDriver; + type Args = (); + + fn new(_dev: &Device, _args: Self::Args) -> impl PinInit { + try_pin_init!(Self { _p: PhantomPinned }) + } + + fn get_modes( + connector: ConnectorGuard<'_, Self>, + _guard: &ModeConfigGuard<'_, Self::Driver>, + ) -> i32 { + let count = connector.add_modes_noedid(MAX_RES); + + connector.set_preferred_mode(DEFAULT_RES); + count + } +} + +#[derive(Clone, Default)] +pub(crate) struct ConnectorState; + +impl connector::DriverConnectorState for ConnectorState { + type Connector = DriverConnector; +} diff --git a/drivers/gpu/drm/rvkms/crtc.rs b/drivers/gpu/drm/rvkms/crtc.rs new file mode 100644 index 0000000000000..02e81368d9c49 --- /dev/null +++ b/drivers/gpu/drm/rvkms/crtc.rs @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 +//! RVKMS's DRM CRTC implementation. +use super::RvkmsDriver; +use core::marker::*; +use kernel::{ + drm::{ + device::Device, + kms::{ + atomic::*, + crtc::{self, DriverCrtcOps, RawCrtcState}, + vblank::*, + KmsRef, ModeObject, + }, + }, + impl_has_hr_timer, + interrupt::LocalInterruptDisabled, + new_spinlock_irq, + prelude::*, + sync::{Arc, SpinLockIrq}, + time::{hrtimer::*, Ktime}, +}; + +pub(crate) type Crtc = crtc::Crtc; +pub(crate) type UnregisteredCrtc = crtc::UnregisteredCrtc; +pub(crate) type CrtcState = crtc::CrtcState; + +#[derive(Default)] +pub(crate) struct VblankState { + /// A reference to the current VblankTimer + timer: Option>, + + /// A reference to a handle for the current VblankTimer + handle: Option>, + + /// The current frame duration in ns + /// + /// Stored separately here so it can be read safely without the vblank lock + period_ns: i32, +} + +#[pin_data] +pub(crate) struct RvkmsCrtc { + /// The current vblank emulation state + /// + /// This is uninitalized when the CRTC is disabled to prevent circular references + #[pin] + vblank_state: SpinLockIrq, +} + +#[vtable] +impl crtc::DriverCrtc for RvkmsCrtc { + #[unique] + const OPS: &'static DriverCrtcOps; + + type Args = (); + type State = RvkmsCrtcState; + type Driver = RvkmsDriver; + type VblankImpl = Self; + + fn new(_device: &Device, _args: &Self::Args) -> impl PinInit { + try_pin_init!(Self { + vblank_state <- new_spinlock_irq!(VblankState::default(), "vblank_handle_lock") + }) + } + + fn atomic_check( + crtc: &Crtc, + old_state: &CrtcState, + mut new_state: crtc::CrtcStateMutator<'_, CrtcState>, + state: &AtomicStateComposer, + ) -> Result { + state.add_affected_planes(crtc)?; + + // Create a vblank timer when enabling a CRTC, and destroy said timer when disabling to + // resolve the circular reference to CRTC it creates + if old_state.active() != new_state.active() { + new_state.vblank_timer = if new_state.active() { + Some(VblankTimer::new(crtc)?) + } else { + None + }; + } + + Ok(()) + } + + fn atomic_flush( + crtc: &Crtc, + _old_state: &CrtcState, + mut new_state: crtc::CrtcStateMutator<'_, CrtcState>, + _state: &AtomicStateMutator, + ) { + if let Some(event) = new_state.get_pending_vblank_event() { + if let Ok(vbl_ref) = crtc.vblank_get() { + event.arm(vbl_ref); + } else { + event.send(); + } + } + } + + fn atomic_enable( + crtc: &Crtc, + _old_state: &CrtcState, + new_state: crtc::CrtcStateMutator<'_, CrtcState>, + _state: &AtomicStateMutator, + ) { + // Store a reference to the newly created vblank timer for this CRTC + crtc.vblank_state.lock().timer = new_state.vblank_timer.clone(); + + crtc.vblank_on(); + } + + fn atomic_disable( + crtc: &Crtc, + _old_state: &CrtcState, + _new_state: crtc::CrtcStateMutator<'_, CrtcState>, + _state: &AtomicStateMutator, + ) { + crtc.vblank_off(); + + let mut vbl_state = crtc.vblank_state.lock(); + let handles = (vbl_state.timer.take(), vbl_state.handle.take()); + + // Since we just explicitly disabled vblanks, destroy the vblank state to resolve circular + // reference to this CRTC that it holds. + // Note that since dropping the handle will cause us to wait for the timer to finish, we + // must drop the lock before we do so. + drop(vbl_state); + drop(handles); + } +} + +impl VblankSupport for RvkmsCrtc { + type Crtc = Self; + + fn enable_vblank( + crtc: &Crtc, + vblank: &VblankGuard<'_, Self::Crtc>, + irq: &LocalInterruptDisabled, + ) -> Result { + let period_ns = vblank.frame_duration(); + let mut vbl_state = crtc.vblank_state.lock_with(irq); + + if let Some(timer) = vbl_state.timer.clone() { + vbl_state.period_ns = period_ns; + vbl_state.handle = Some(timer.start(Ktime::from_raw(period_ns as _))); + } + + Ok(()) + } + + fn disable_vblank( + crtc: &Crtc, + _vbl_guard: &VblankGuard<'_, Self::Crtc>, + irq: &LocalInterruptDisabled, + ) { + let handle = crtc.vblank_state.lock_with(irq).handle.take(); + + // Now that we're outside of the vblank lock, we can safely drop the handle + drop(handle); + } + + fn get_vblank_timestamp(crtc: &Crtc, _handling_vblank_irq: bool) -> Option { + let vbl_state = crtc.vblank_state.lock(); + + // Return the expiration of our vblank timer if we have one (if not, vblanks are disabled) + let time = vbl_state.timer.as_ref().map(|t| { + // To prevent races, we roll the hrtimer forward before we do any interrupt + // processing - this is how real hw works (the interrupt is only generated after all + // the vblank registers are updated) and what the vblank core expects. Therefore we + // need to always correct the timestamps by one frame. + t.timer.expires() - Ktime::from_nanos(vbl_state.period_ns) + }); + + Some(VblankTimestamp { + // …otherwise, just use the current time + time: time.unwrap_or_else(|| Ktime::ktime_get()), + max_error: 0, + }) + } +} + +#[derive(Clone, Default)] +pub(crate) struct RvkmsCrtcState { + /// The current hrtimer used for emulating vblank events, if there is one. + vblank_timer: Option>, +} + +impl crtc::DriverCrtcState for RvkmsCrtcState { + type Crtc = RvkmsCrtc; +} + +/// The main hrtimer structure for emulating vblanks. +#[pin_data] +pub(crate) struct VblankTimer { + /// The actual hrtimer used for sending out vblanks + #[pin] + timer: HrTimer, + + /// An owned reference to the CRTC that this [`VblankTimer`] belongs to + crtc: KmsRef, +} + +impl_has_hr_timer! { + impl HasHrTimer for VblankTimer { self.timer } +} + +impl VblankTimer { + pub(crate) fn new(crtc: &Crtc) -> Result> { + Arc::pin_init( + pin_init!(Self { + timer <- HrTimer::::new(HrTimerMode::Relative, ClockSource::Monotonic), + crtc: crtc.into(), + }), + GFP_KERNEL, + ) + } +} + +impl HrTimerCallback for VblankTimer { + type Pointer<'a> = Arc; + + fn run( + this: as RawHrTimerCallback>::CallbackTarget<'_>, + context: HrTimerCallbackContext<'_, T>, + ) -> HrTimerRestart + where + Self: Sized, + { + let period_ns = this.crtc.vblank_state.lock().period_ns; + + let overrun = context.forward_now(Ktime::from_nanos(period_ns)); + if overrun != 1 { + dev_warn!( + this.crtc.drm_dev().as_ref(), + "vblank timer overrun (expected 1, got {overrun})\n" + ); + } + + this.crtc.handle_vblank(); + + HrTimerRestart::Restart + } +} diff --git a/drivers/gpu/drm/rvkms/encoder.rs b/drivers/gpu/drm/rvkms/encoder.rs new file mode 100644 index 0000000000000..2ce1635a78ff7 --- /dev/null +++ b/drivers/gpu/drm/rvkms/encoder.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +//! RVKMS's DRM Encoder implementation +use crate::RvkmsDriver; +use core::marker::PhantomPinned; +use kernel::{ + drm::{device::Device, kms::encoder}, + prelude::*, +}; + +#[pin_data] +pub(crate) struct DriverEncoder { + #[pin] + _p: PhantomPinned, +} + +#[allow(unused)] +pub(crate) type Encoder = encoder::Encoder; +pub(crate) type UnregisteredEncoder = encoder::UnregisteredEncoder; + +#[vtable] +impl encoder::DriverEncoder for DriverEncoder { + #[unique] + const OPS: &'static encoder::DriverEncoderOps; + + type Driver = RvkmsDriver; + type Args = (); + + fn new(_device: &Device, _args: Self::Args) -> impl PinInit { + try_pin_init!(Self { _p: PhantomPinned }) + } +} diff --git a/drivers/gpu/drm/rvkms/file.rs b/drivers/gpu/drm/rvkms/file.rs new file mode 100644 index 0000000000000..ea622a364659a --- /dev/null +++ b/drivers/gpu/drm/rvkms/file.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +//! RVKMS's DRM File implementation. +use super::RvkmsDriver; + +use kernel::{ + alloc::*, + drm::{self, device::Device as DrmDevice}, + prelude::*, +}; + +pub(crate) struct File; + +impl drm::file::DriverFile for File { + type Driver = RvkmsDriver; + + fn open(_device: &DrmDevice) -> Result>> { + Box::pin_init(init!(File {}), GFP_KERNEL) + } +} diff --git a/drivers/gpu/drm/rvkms/gem.rs b/drivers/gpu/drm/rvkms/gem.rs new file mode 100644 index 0000000000000..0aa7817d4f70b --- /dev/null +++ b/drivers/gpu/drm/rvkms/gem.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 +//! RVKMS's DRM GEM implementation +use crate::{RvkmsDevice, RvkmsDriver}; +use core::sync::atomic::{AtomicU64, Ordering}; +use kernel::{drm::gem, prelude::*}; + +static GEM_ID: AtomicU64 = AtomicU64::new(0); + +/// GEM Object implementation +#[pin_data] +pub(crate) struct DriverObject { + /// ID for debugging + id: u64, +} + +pub(crate) type Object = gem::shmem::Object; + +impl gem::BaseDriverObject for DriverObject { + fn new(_dev: &RvkmsDevice, _size: usize) -> impl PinInit { + let id = GEM_ID.fetch_add(1, Ordering::Relaxed); + + pr_debug!("DriverObject::new id={id}\n"); + DriverObject { id } + } +} + +impl gem::shmem::DriverObject for DriverObject { + type Driver = RvkmsDriver; +} diff --git a/drivers/gpu/drm/rvkms/output.rs b/drivers/gpu/drm/rvkms/output.rs new file mode 100644 index 0000000000000..4b5601bea37f2 --- /dev/null +++ b/drivers/gpu/drm/rvkms/output.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +//! RVKMS's display outputs. +use crate::{ + connector::UnregisteredConnector, crtc::UnregisteredCrtc, encoder::UnregisteredEncoder, + plane::UnregisteredPlane, RvkmsDriver, +}; +use kernel::{ + drm::{ + fourcc::*, + kms::{connector, encoder, plane, UnregisteredKmsDevice}, + }, + prelude::*, +}; + +pub(crate) fn create_output(dev: &UnregisteredKmsDevice<'_, RvkmsDriver>, index: u8) -> Result { + let possible_crtcs = 1 << index; + + let primary = UnregisteredPlane::new( + dev, + possible_crtcs, + &[XRGB888], + None, + plane::Type::Primary, + None, + (), + )?; + + let _crtc = UnregisteredCrtc::new(dev, primary, Option::<&UnregisteredPlane>::None, None, ())?; + + let connector = UnregisteredConnector::new(dev, connector::Type::Virtual, ())?; + + let encoder = + UnregisteredEncoder::new(dev, encoder::Type::Virtual, possible_crtcs, 0, None, ())?; + + connector.attach_encoder(encoder) +} diff --git a/drivers/gpu/drm/rvkms/plane.rs b/drivers/gpu/drm/rvkms/plane.rs new file mode 100644 index 0000000000000..e58c4f12410ef --- /dev/null +++ b/drivers/gpu/drm/rvkms/plane.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +//! RVKMS's DRM plane implementation +use super::RvkmsDriver; +use core::marker::PhantomPinned; +use kernel::{ + drm::{ + device::Device, + kms::{ + atomic::*, + plane::{self, DriverPlaneOps, DriverPlaneState, PlaneStateMutator, RawPlaneState}, + }, + }, + prelude::*, +}; + +#[pin_data] +pub(crate) struct RvkmsPlane { + #[pin] + _p: PhantomPinned, +} + +pub(crate) type Plane = plane::Plane; +pub(crate) type UnregisteredPlane = plane::UnregisteredPlane; +pub(crate) type PlaneState = plane::PlaneState; + +#[vtable] +impl plane::DriverPlane for RvkmsPlane { + #[unique] + const OPS: &'static DriverPlaneOps; + + type State = RvkmsPlaneState; + type Driver = RvkmsDriver; + type Args = (); + + fn new(_device: &Device, _args: Self::Args) -> impl PinInit { + try_pin_init!(Self { _p: PhantomPinned }) + } + + fn atomic_check( + _plane: &Plane, + mut new_state: PlaneStateMutator<'_, PlaneState>, + _old_state: &PlaneState, + state: &AtomicStateComposer, + ) -> Result { + if new_state.framebuffer().is_none() { + return Ok(()); + } + + if let Some(crtc) = new_state.crtc() { + let crtc_state = state.add_crtc_state(crtc)?; + new_state.atomic_helper_check(&crtc_state, true, true) + } else { + // TODO: We should be printing a warning here if we have no CRTC but do have an fb + return Ok(()); + } + } + + fn atomic_update( + _plane: &Plane, + _new_state: PlaneStateMutator<'_, PlaneState>, + _old_state: &PlaneState, + _state: &AtomicStateMutator, + ) { + // TODO, no-op for now + } +} + +#[derive(Clone, Default)] +pub(crate) struct RvkmsPlaneState; + +impl DriverPlaneState for RvkmsPlaneState { + type Plane = RvkmsPlane; +} diff --git a/drivers/gpu/drm/rvkms/rvkms.rs b/drivers/gpu/drm/rvkms/rvkms.rs new file mode 100644 index 0000000000000..fda5ce72a2c1f --- /dev/null +++ b/drivers/gpu/drm/rvkms/rvkms.rs @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 +//! A port of the VKMS driver to rust. +pub(crate) mod connector; +pub(crate) mod crtc; +pub(crate) mod encoder; +pub(crate) mod file; +pub(crate) mod gem; +pub(crate) mod output; +pub(crate) mod plane; + +use core::marker::*; + +use kernel::{ + c_str, device, + drm::{ + self, drv, + kms::{atomic::*, KmsDriver, ModeConfigInfo, UnregisteredKmsDevice}, + }, + faux, + prelude::*, + str::CStr, +}; + +/// Convienence type alias for the DRM device type for this driver +pub(crate) type RvkmsDevice = drm::device::Device; + +/// The name of the driver +const NAME: &'static CStr = c_str!("rvkms"); + +/// Driver metadata +const INFO: drv::DriverInfo = drv::DriverInfo { + major: 0, + minor: 0, + patchlevel: 0, + name: &NAME, + desc: c_str!("Rust VKMS PoC"), + date: c_str!("20240115"), +}; + +/// The minimum supported resolution +const MIN_RES: (i32, i32) = (10, 10); + +/// The maximum supported resolution +const MAX_RES: (i32, i32) = (8192, 8192); + +/// The "preferred" resolution +const DEFAULT_RES: (i32, i32) = (1024, 768); + +/// DRM Driver implementation for `RvkmsDriver` +#[vtable] +impl drv::Driver for RvkmsDriver { + type Data = (); + type Object = gem::Object; + type File = file::File; + type Kms = Self; + + const INFO: drv::DriverInfo = INFO; + const FEATURES: u32 = drv::FEAT_GEM | drv::FEAT_MODESET | drv::FEAT_ATOMIC; + + kernel::declare_drm_ioctls! {} +} + +#[vtable] +impl KmsDriver for RvkmsDriver { + fn mode_config_info( + _dev: &device::Device, + _drm_data: ::Borrowed<'_>, + ) -> Result { + Ok(MODE_CONFIG_INFO) + } + + fn create_objects(drm: &UnregisteredKmsDevice<'_, Self>) -> Result + where + Self: Sized, + { + output::create_output(drm, 0) + } + + fn atomic_commit_tail<'a>( + mut state: AtomicCommitTail<'a, Self>, + modeset_token: ModesetsReadyToken<'_>, + plane_update_token: PlaneUpdatesReadyToken<'_>, + ) -> CommittedAtomicState<'a, Self> + where + Self: Sized, + { + let modeset_token = state.commit_modeset_disables(modeset_token); + + let plane_update_token = state.commit_planes(plane_update_token, Default::default()); + + let modeset_token = state.commit_modeset_enables(modeset_token); + + state.fake_vblank(); + + let state = state.commit_hw_done(modeset_token, plane_update_token); + + state.wait_for_flip_done(); + + state + } +} + +pub(crate) struct RvkmsDriver; + +const MODE_CONFIG_INFO: ModeConfigInfo = ModeConfigInfo { + min_resolution: MIN_RES, + max_resolution: MAX_RES, + max_cursor: (512, 512), + preferred_depth: 24, + preferred_fourcc: None, +}; + +pub(crate) struct RvkmsModule { + _dev: faux::Registration, + _drm: drv::Registration, +} + +impl kernel::Module for RvkmsModule { + fn init(_module: &'static ThisModule) -> Result { + pr_info!("RVKMS Loaded\n"); + + let dev = faux::Registration::new(NAME, None)?; + dev_info!(dev.as_ref(), "Setting up DRM\n"); + + let drm = drv::Registration::::new(dev.as_ref(), (), 0)?; + + Ok(Self { + _dev: dev, + _drm: drm, + }) + } +} + +module! { + type: RvkmsModule, + name: "rvkms", + author: "Lyude Paul", + description: "Rust VKMS Proof of Concept driver", + license: "GPL v2", +}