Received: by 2002:ab2:6857:0:b0:1ef:ffd0:ce49 with SMTP id l23csp1438531lqp; Fri, 22 Mar 2024 15:15:21 -0700 (PDT) X-Forwarded-Encrypted: i=3; AJvYcCXEDoZ9oIRJB9i/jOT6AHLfd9QEQ4ws8ffq0VPF/hRM0t6q0ffu9/CZUKLvju5XzitRXTFE4MUmpXO5Gjb25fT5cJHKhziKnou5T1aPrw== X-Google-Smtp-Source: AGHT+IGAkZikfAuIk7VnuG75y7hGVefOTRTH/hq4sjVIYp18mJA2cIb8uo7KImJchm2k15Vi/KkI X-Received: by 2002:a50:bb6b:0:b0:56b:d92f:32c6 with SMTP id y98-20020a50bb6b000000b0056bd92f32c6mr676507ede.5.1711145721369; Fri, 22 Mar 2024 15:15:21 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1711145721; cv=pass; d=google.com; s=arc-20160816; b=RZdRSajH6NojEU7BxfgmdYhDywDpgrVpHBMvLJX5cOQQOHvm8PCmbUf8HXLBk4GtTI KQeiDOrdXlq66baZ2vSSuGeR+j4gkpBvpoti+VbVYPEDSYYsLBFoquyUji6grMiThzlL uhcyI9S50Sc87ixWbEEpUOyWaHMACvCmIvDK08j7w5q686tm6r5Ku8OIAOxkt8LR3vIL fJevxevWvpOKXhOh4GUJ4dSX/bdMB6lCrJdJtU6g4d4RBRsfYouF02Nlb9e52dLDCX7A 21vWMeqtgukyP120nLpujSQPyodeQfpnIVYH7JL1+4o5Hp3HzbvS4kdDEpgf61OF1vad OsCA== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=content-transfer-encoding:mime-version:list-unsubscribe :list-subscribe:list-id:precedence:references:in-reply-to:message-id :date:subject:cc:to:from:dkim-signature; bh=wB343G/URjvp9S/F5TMB5/43KB5zndfAK18UMcVICOA=; fh=k4eRbtC2PlAoJFSIiewVbphvPbgl7Gf99Rh/0Hn/n98=; b=S06V0HSZEJGd6pQihbfv/L7jxjTBBNWQ0Xzr2xnvdRFxj6G5GfEcj/F3e00MAHEEgg tNF5AGemrOLP+WoT5g/+NZhlDAe0LFuW1/SiwXdKd1xiKWov9ATavXMOf8Urf7L80rI0 oH6DLn4QjXAXTuhOWHFO5a8iKlOu4rIIveLqpd3cnXAaU8q3AKIsgbnfEc4t1hymPdcC jhMuGmENoPSj9BdVBafQlamUQl4qYAhy3p46VuTx2gdde1ibOoxSRElUFSDxWpabFfUW zgiwGrbCxFlAMGtgsnmieT23v04OVHOUNfSj+CMeK+iPfkXHEQClHdU/HrmPnRzxoW6A c8vQ==; dara=google.com ARC-Authentication-Results: i=2; mx.google.com; dkim=pass header.i=@redhat.com header.s=mimecast20190719 header.b=KVg8DpJE; arc=pass (i=1 spf=pass spfdomain=redhat.com dkim=pass dkdomain=redhat.com dmarc=pass fromdomain=redhat.com); spf=pass (google.com: domain of linux-kernel+bounces-112062-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) smtp.mailfrom="linux-kernel+bounces-112062-linux.lists.archive=gmail.com@vger.kernel.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=redhat.com Return-Path: Received: from am.mirrors.kernel.org (am.mirrors.kernel.org. [2604:1380:4601:e00::3]) by mx.google.com with ESMTPS id m26-20020aa7c2da000000b0056ba008b299si225655edp.506.2024.03.22.15.15.21 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 22 Mar 2024 15:15:21 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel+bounces-112062-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) client-ip=2604:1380:4601:e00::3; Authentication-Results: mx.google.com; dkim=pass header.i=@redhat.com header.s=mimecast20190719 header.b=KVg8DpJE; arc=pass (i=1 spf=pass spfdomain=redhat.com dkim=pass dkdomain=redhat.com dmarc=pass fromdomain=redhat.com); spf=pass (google.com: domain of linux-kernel+bounces-112062-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) smtp.mailfrom="linux-kernel+bounces-112062-linux.lists.archive=gmail.com@vger.kernel.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=redhat.com Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by am.mirrors.kernel.org (Postfix) with ESMTPS id A54D21F23D01 for ; Fri, 22 Mar 2024 22:15:20 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id A410E8173D; Fri, 22 Mar 2024 22:15:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="KVg8DpJE" Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 341EE43AB5 for ; Fri, 22 Mar 2024 22:15:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711145711; cv=none; b=P/Fq8Vsmo8QvUxzRYsZZ2pkjj6I9WG5th9CHkVRmAZ0gbR1lUa7nkREjllDwenLNC8f3gdBgzkeGV6fK7oq8Kytitq7G+ZQkc8bPwFCx1RqtfG7xNZjB3WhN5cQSoNRlGMpRRk8wcxtXQK241To3IN5/cZ53WBAvQz7Tu49HZe0= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711145711; c=relaxed/simple; bh=NlVQ/jhrlnRkhJvPwPnzDrkTfqCIFDIT4l1P9uMnngE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=hNw+L8fFksg/8WoreOBz/pt7EMvQ7N1aKH6eNeK2sUmnUW1k7XrOaZU1tlG1hQNPvBkvI/hzLTpE4zL/wByI7M7ct86UJIy4j2MlUnWbmIbPXxvupsQcOtj/jESTtKL69D5WSj6pVbuoL4tPsFSbPFrWPuGXH5ct802n2wu4nHQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=KVg8DpJE; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1711145706; 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=wB343G/URjvp9S/F5TMB5/43KB5zndfAK18UMcVICOA=; b=KVg8DpJE4H27h9Bh7Vfu9hPHKZc3LwSjDXoF0zGLqBtk26B/11qIJ2Ou5EPOXwoLOgZpLl 64teYChz0H6ZzAa6vrbLbugxElV3E1OjFmWZYAMPvJF2pr3lz0WGFcHiZPcaUSjx1QjOgA vpBlQ1Xg8ObQdUbTsJiBrfT7bFu3L2A= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-444-7FBJ-4FAOeW-Wk7awtYhhQ-1; Fri, 22 Mar 2024 18:15:02 -0400 X-MC-Unique: 7FBJ-4FAOeW-Wk7awtYhhQ-1 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) (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 mimecast-mx02.redhat.com (Postfix) with ESMTPS id D3B888007AF; Fri, 22 Mar 2024 22:15:01 +0000 (UTC) Received: from emerald.redhat.com (unknown [10.22.8.130]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8CE928173; Fri, 22 Mar 2024 22:15:00 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org Cc: Miguel Ojeda , Alex Gaynor , Wedson Almeida Filho , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Asahi Lina , Martin Rodriguez Reboredo , FUJITA Tomonori , Danilo Krummrich , linux-kernel@vger.kernel.org (open list), rust-for-linux@vger.kernel.org (open list:RUST) Subject: [PATCH 1/4] WIP: rust: Add basic KMS bindings Date: Fri, 22 Mar 2024 18:03:33 -0400 Message-ID: <20240322221305.1403600-2-lyude@redhat.com> In-Reply-To: <20240322221305.1403600-1-lyude@redhat.com> References: <20240322221305.1403600-1-lyude@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.5 Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 4 + rust/helpers.c | 17 ++ rust/kernel/drm/device.rs | 2 + rust/kernel/drm/drv.rs | 115 +++++++-- rust/kernel/drm/kms.rs | 146 +++++++++++ rust/kernel/drm/kms/connector.rs | 404 +++++++++++++++++++++++++++++++ rust/kernel/drm/kms/crtc.rs | 300 +++++++++++++++++++++++ rust/kernel/drm/kms/encoder.rs | 175 +++++++++++++ rust/kernel/drm/kms/plane.rs | 300 +++++++++++++++++++++++ rust/kernel/drm/mod.rs | 1 + 10 files changed, 1448 insertions(+), 16 deletions(-) create mode 100644 rust/kernel/drm/kms.rs create mode 100644 rust/kernel/drm/kms/connector.rs create mode 100644 rust/kernel/drm/kms/crtc.rs create mode 100644 rust/kernel/drm/kms/encoder.rs create mode 100644 rust/kernel/drm/kms/plane.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index a712efecdb1a9..5856afbe6e8f6 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -6,12 +6,16 @@ * Sorted alphabetically. */ +#include #include #include +#include #include #include +#include #include #include +#include #include #include #include diff --git a/rust/helpers.c b/rust/helpers.c index 69fc66164c785..bf9b299f4597f 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -20,6 +20,7 @@ * Sorted alphabetically. */ +#include #include #include #include @@ -284,6 +285,22 @@ int rust_helper_drm_gem_shmem_object_mmap(struct drm_gem_object *obj, struct vm_ EXPORT_SYMBOL_GPL(rust_helper_drm_gem_shmem_object_mmap); #endif + +#ifdef CONFIG_DRM_KMS_HELPER + +void rust_helper_drm_connector_get(struct drm_connector *connector) +{ + drm_connector_get(connector); +} +EXPORT_SYMBOL_GPL(rust_helper_drm_connector_get); + +void rust_helper_drm_connector_put(struct drm_connector *connector) +{ + drm_connector_put(connector); +} +EXPORT_SYMBOL_GPL(rust_helper_drm_connector_put); + +#endif /* CONFIG_DRM_KMS_HELPER */ #endif void rust_helper_pci_set_drvdata(struct pci_dev *pdev, void *data) diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index 6176e2e879d0b..07bc8ed50eae0 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -20,6 +20,8 @@ pub struct Device { } impl Device { + pub const HAS_KMS: bool = T::FEATURES & drm::drv::FEAT_MODESET != 0; + #[allow(dead_code, clippy::mut_from_ref)] pub(crate) unsafe fn raw_mut(&self) -> &mut bindings::drm_device { unsafe { &mut *self.drm.get() } diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs index fa9ce64a5080c..308f0a117f546 100644 --- a/rust/kernel/drm/drv.rs +++ b/rust/kernel/drm/drv.rs @@ -5,9 +5,13 @@ //! C header: [`include/linux/drm/drm_drv.h`](../../../../include/linux/drm/drm_drv.h) use crate::{ - bindings, device, drm, + bindings, device, + drm::{ + self, + kms, + }, error::code::*, - error::from_err_ptr, + error::{from_err_ptr, to_result}, error::{Error, Result}, prelude::*, private::Sealed, @@ -15,6 +19,7 @@ types::{ARef, ForeignOwnable}, ThisModule, sync::Arc, + init::Zeroable, }; use core::{ marker::{PhantomData, PhantomPinned}, @@ -150,7 +155,11 @@ pub trait Driver { /// The struct which contains both the driver's fops and vtable /// /// These live in the same structure since it needs to be self-referential, so having them in their -/// own structure allows us to pin this struct without pinning the [`Registration`] object +/// own structure allows us to pin this struct without pinning the [`Registration`] object. +/// +/// Drivers should not need to create this structure themselves, as it will be created for them by +/// DRM. As well: this object is a temporary holdover until we can generate the DRM fops and vtable +/// in a const function (which should be possible once const mem::zeroed becomes stable). #[pin_data] pub struct DriverOps { #[pin] @@ -225,8 +234,10 @@ macro_rules! drm_legacy_fields { #[allow(clippy::crate_in_macro_def)] #[macro_export] macro_rules! new_drm_registration { - ($type:ty, $parent:expr) => {{ - $crate::drm::drv::Registration::<$type>::new($parent, &crate::THIS_MODULE) + ($type:ty, $parent:expr, $mode_config_info:expr) => {{ + $crate::drm::drv::Registration::<$type>::new( + $parent, $mode_config_info, &crate::THIS_MODULE + ) }}; } @@ -249,6 +260,8 @@ pub struct RegistrationInfo { drm: ARef>, } +unsafe impl Zeroable for bindings::drm_mode_config { } + impl Registration { const VTABLE: bindings::drm_driver = drm_legacy_fields! { load: None, @@ -282,28 +295,89 @@ impl Registration { fops: core::ptr::null_mut(), }; + const KMS_VTABLE: bindings::drm_mode_config_funcs = bindings::drm_mode_config_funcs { + atomic_check: None, // TODO + // TODO TODO: There are other possibilities then this function, but we need + // to write up more bindings before we can support those + fb_create: Some(bindings::drm_gem_fb_create), + mode_valid: None, // TODO + atomic_commit: Some(bindings::drm_atomic_helper_commit), + get_format_info: None, + atomic_state_free: None, + atomic_state_alloc: None, + atomic_state_clear: None, + output_poll_changed: None, + }; + + const KMS_HELPER_VTABLE: bindings::drm_mode_config_helper_funcs = + bindings::drm_mode_config_helper_funcs { + atomic_commit_setup: None, // TODO + atomic_commit_tail: None, // TODO + }; + + pub const HAS_KMS: bool = T::FEATURES & FEAT_MODESET != 0; + /// Creates a new [`Registration`] but does not register it yet. /// - /// It is allowed to move. - /// XXX: Write up a macro for calling this, since we now handle as much init here as possible to - /// avoid having to handle it after we've moved away the Registration object + /// It is allowed to move. Note that `mode_confg_info` must be provided for a device to be + /// initialized with KMS. pub fn new( parent: &dyn device::RawDevice, + mode_config_info: Option, module: &'static ThisModule, ) -> Result { - let registered = Arc::try_new(AtomicBool::new(false))?; - let ops = DriverOps::try_new(Self::VTABLE, module)?; + // mode_config_info must be passed for KMS drivers. We do this check up here so we don't + // have to worry about leaking raw_drm + // XXX: Would love to know a way to do this at compile-time instead… + if Self::HAS_KMS != mode_config_info.is_some() { + parent.pr_err( + if Self::HAS_KMS { + format_args!("KMS drivers must specify mode_config_info for new devices") + } else { + format_args!("mode_config_info is only for KMS drivers (see drm::drv::Driver::FEATURES)") + } + ); - let raw_drm = unsafe { bindings::drm_dev_alloc(&ops.vtable, parent.raw_device()) }; - if T::FEATURES & FEAT_MODESET != 0 { - unsafe { bindings::drmm_mode_config_init(raw_drm) }; + return Err(EINVAL); } - let raw_drm = NonNull::new(from_err_ptr(raw_drm)? as *mut _).ok_or(ENOMEM)?; + let registered = Arc::try_new(AtomicBool::new(false))?; + let ops = DriverOps::try_new(Self::VTABLE, module)?; + + // SAFETY: FFI call with no special requirements + let raw_drm: NonNull = + from_err_ptr(unsafe { bindings::drm_dev_alloc(&ops.vtable, parent.raw_device()) }) + .and_then(|p| NonNull::new(p).ok_or(ENOMEM))? + .cast(); // The reference count is one, and now we take ownership of that reference as a // drm::device::Device. - let drm = unsafe { ARef::from_raw(raw_drm) }; + let drm: ARef> = unsafe { ARef::from_raw(raw_drm.cast()) }; + + // Finally, setup KMS - we do this at the end to avoid leaking raw_drm if something fails + if Self::HAS_KMS { + // SAFETY: We made sure at the start of this function that mode_config_info is Some + let mode_config_info = unsafe { mode_config_info.unwrap_unchecked() }; + + // SAFETY: We just allocated the device, and it's safe to zero-initialize this + unsafe { + (*drm.drm.get()).mode_config = bindings::drm_mode_config { + funcs: &Self::KMS_VTABLE, + helper_private: &Self::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: FFI call with no special requirements + unsafe { to_result(bindings::drmm_mode_config_init(drm.drm.get())) }?; + } Ok(Self { drm, @@ -324,6 +398,8 @@ pub fn registration_info(&self) -> RegistrationInfo { /// Registers a DRM device with the rest of the kernel. /// + /// For KMS drivers, this also calls `bindings::drm_mode_config_reset()` before registration. + /// /// Users are encouraged to use the [`drm_device_register!()`] macro because it automatically /// picks up the current module. pub fn register( @@ -390,7 +466,14 @@ fn drop(&mut self) { let data_pointer = unsafe { self.drm.raw_mut().dev_private }; // SAFETY: Since `registered` is true, `self.drm` is both valid and registered. - unsafe { bindings::drm_dev_unregister(self.drm.raw_mut()) }; + unsafe { + let raw_drm = self.drm.raw_mut(); + + bindings::drm_dev_unregister(raw_drm); + if Self::HAS_KMS { + bindings::drm_atomic_helper_shutdown(raw_drm); + } + }; // Free data as well. // SAFETY: `data_pointer` was returned by `into_foreign` during registration. diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs new file mode 100644 index 0000000000000..b55d14415367a --- /dev/null +++ b/rust/kernel/drm/kms.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! KMS driver abstractions for rust. + +pub mod connector; +pub mod crtc; +pub mod encoder; +pub mod plane; + +use crate::{ + drm::{drv, device::Device}, + prelude::*, + types::ARef, + private::Sealed +}; +use core::{ + ops::Deref, + ptr, +}; +use bindings; + +#[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, +} + +// TODO: I am not totally sure about this. Ideally, I'd like a nice way of hiding KMS-specific +// functions for DRM drivers which don't implement KMS - so that we don't have to have a bunch of +// random modesetting functions all over the DRM device trait. But, unfortunately I don't know of +// any nice way of doing that yet :( + +/// An atomic KMS driver implementation +pub trait KmsDriver: drv::Driver { } + +impl Device { + pub fn mode_config_reset(&self) { + // SAFETY: The previous build assertion ensures this can only be called for devices with KMS + // support, which means mode_config is initialized + unsafe { bindings::drm_mode_config_reset(self.drm.get()) } + } +} + +/// Main trait for a modesetting object in DRM +pub trait ModeObject: Sealed + Send + Sync { + /// The parent driver for this ModeObject + type Driver: KmsDriver; + + /// Return the `drv::Device` for this `ModeObject` + fn drm_dev(&self) -> &Device; +} + +/// A trait for modesetting objects which don't come with their own reference-counting. +/// +/// Objects without a reference count share the lifetime of their parent DRM device +/// +/// SAFETY: This trait must not be implemented for modesetting objects which have a refcount +/// already, as otherwise `KmsRef` can't safely guarantee the object will stay alive. +pub unsafe trait StaticModeObject: ModeObject {} + +/// An owned reference to a non-reference counted modesetting object. +/// +/// In KMS: some modesetting objects aren't reference counted and instead share the drm device's +/// lifetime. In order to allow rust drivers access to "owned" references to objects which are +/// guaranteed to remain valid, we provide a smart-pointer that holds both a pointer to the +/// modesetting object, and an owned refcount from its owning device - ensuring that the object +/// remains valid for as long as this reference exists. +pub struct KmsRef { + dev: ARef>, + object: *const T, +} + +// SAFETY: Owned references to DRM device are thread-safe, and object will survive as long as we +// have said owned references +unsafe impl Send for KmsRef {} +unsafe impl Sync for KmsRef {} + +impl From<&T> for KmsRef { + fn from(value: &T) -> Self { + Self { + dev: value.drm_dev().into(), + object: value, + } + } +} + +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.object) } + } +} + +impl Clone for KmsRef { + fn clone(&self) -> Self { + Self { + dev: self.dev.clone(), + object: self.object + } + } +} + +/// A mode config guard. +/// +/// This is an exclusive primitive that represents when `bindings::drm_device.mode_config.lock` is +/// held - as some modesetting operations (particularly ones related to connectors) are still +/// protected under this single lock. So long as this object is held, it is guaranteed that the lock +/// is held. +pub struct ModeConfigGuard<'a, T: KmsDriver> { + owner: &'a Device, + owned: bool, +} + +impl<'a, T: KmsDriver> ModeConfigGuard<'a, T> { + /// Create a "borrowed" mode config guard. + /// + /// This is primarily for situations in the DRM bindings where we know that the mode_config lock + /// is held, but we aren't the ones who initially acquired it. Dropping this mode config guard + /// is a no-op. + /// + /// SAFETY: The caller must ensure that the mode_config lock is acquired throughout the lifetime + /// of this object. + unsafe fn new_borrowed(dev: &'a Device) -> Self { + Self { + owner: dev, + owned: false, + } + } + + /// 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. + pub(crate) fn assert_owner(&self, dev: &Device) { + assert!(ptr::eq(self.owner, dev)) + } +} diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs new file mode 100644 index 0000000000000..88dfa946d306b --- /dev/null +++ b/rust/kernel/drm/kms/connector.rs @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! Rust bindings for DRM connectors + +use crate::{ + bindings, + sync::ArcBorrow, + drm::{ + drv::{Driver, FEAT_MODESET}, + device::Device, + }, + types::{AlwaysRefCounted, Opaque, ARef}, + prelude::*, + init::Zeroable, + error::{to_result, from_result}, + build_error, +}; +use core::{ + marker::PhantomPinned, + ptr::null_mut, + mem, + ptr::{self, NonNull}, + ffi::*, + ops::Deref, +}; +use super::{ + ModeObject, + ModeConfigGuard, + encoder::{Encoder, DriverEncoder}, + KmsDriver, +}; +use macros::pin_data; + +// XXX: This is :\, figure out a better way at some point? +pub use bindings::{ + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_VGA, + DRM_MODE_CONNECTOR_DVII, + DRM_MODE_CONNECTOR_DVID, + DRM_MODE_CONNECTOR_DVIA, + DRM_MODE_CONNECTOR_Composite, + DRM_MODE_CONNECTOR_SVIDEO, + DRM_MODE_CONNECTOR_LVDS, + DRM_MODE_CONNECTOR_Component, + DRM_MODE_CONNECTOR_9PinDIN, + DRM_MODE_CONNECTOR_DisplayPort, + DRM_MODE_CONNECTOR_HDMIA, + DRM_MODE_CONNECTOR_HDMIB, + DRM_MODE_CONNECTOR_TV, + DRM_MODE_CONNECTOR_eDP, + DRM_MODE_CONNECTOR_VIRTUAL, + DRM_MODE_CONNECTOR_DSI, + DRM_MODE_CONNECTOR_DPI, + DRM_MODE_CONNECTOR_WRITEBACK, + DRM_MODE_CONNECTOR_SPI, + DRM_MODE_CONNECTOR_USB, +}; + +/// A DRM connector implementation +pub trait DriverConnector: Send + Sync + Sized { + /// The return type of the new() function. Should be `impl PinInit`. + /// TODO: Remove this when return_position_impl_trait_in_trait is stable. + type Initializer: PinInit; + + /// The data type to use for passing incoming arguments for new `Connector` instances + /// Drivers which don't care about this can just use `()` + type Args; + + /// The parent driver for this DRM connector implementation + type Driver: KmsDriver; + + /// The atomic state implementation for this DRM connector implementation + type State: DriverConnectorState; + + /// Create a new instance of the private driver data struct for this connector in-place + fn new(dev: &Device, args: Self::Args) -> Self::Initializer; + + /// Retrieve a list of available display modes for this connector + fn get_modes<'a>( + connector: ConnectorGuard<'a, Self>, + guard: &ModeConfigGuard<'a, Self::Driver> + ) -> i32; +} + +/// A DRM connector +#[repr(C)] +#[pin_data] +pub struct Connector { + connector: Opaque, + #[pin] + inner: T, + #[pin] + _p: PhantomPinned +} + +impl crate::private::Sealed for Connector { } + +// SAFETY: DRM expects this struct to be zero-initialized +unsafe impl Zeroable for bindings::drm_connector { } + +// SAFETY: Connector.connector is not exposed to users by default, and our accessors ensure we only +// perform thread-safe operations for this object +unsafe impl Send for Connector { } +unsafe impl Sync for Connector { } + +unsafe impl AlwaysRefCounted for Connector { + fn inc_ref(&self) { + unsafe { bindings::drm_connector_get(self.raw_mut_ptr()) } + } + + unsafe fn dec_ref(obj: core::ptr::NonNull) { + unsafe { bindings::drm_connector_put(obj.as_ref().raw_mut_ptr()) } + } +} + +impl Deref for Connector { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl ModeObject for Connector { + 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 { &*((*self.raw_mut_ptr()).dev.cast()) } + } +} + +impl Connector { + const FUNCS: bindings::drm_connector_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: Some(bindings::drm_helper_probe_single_connector_modes), + debugfs_init: None, + oob_hotplug_event: None, + atomic_duplicate_state: Some(atomic_duplicate_state_callback::), + }; + + const HELPER_FUNCS: bindings::drm_connector_helper_funcs = bindings::drm_connector_helper_funcs { + mode_valid: None, + atomic_check: None, + get_modes: Some(get_modes_callback::), + 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, + }; + + pub fn new( + dev: &Device, + type_: u32, + args: T::Args, + ) -> Result> { + let new: Pin> = Box::try_pin_init(try_pin_init!(Self { + connector: Opaque::new(bindings::drm_connector { + helper_private: &Self::HELPER_FUNCS, + ..Default::default() + }), + inner <- T::new(dev, args), + _p: PhantomPinned + }))?; + + // SAFETY: FFI call with no special safety requirements + to_result(unsafe { + bindings::drm_connector_init( + dev.drm.get(), + new.raw_mut_ptr(), + &Self::FUNCS, + type_ as i32 + ) + })?; + + // Convert the connector into an ARef so the caller has proper ownership over a refcount to + // it. Also, the Box we consume here will be reconstructed in connector_destroy_callback() + // once the connector's refcount drops to zero. + // SAFETY: We currently hold ownership of the Box containing the connector and it's + // refcount. As well, this operation will not move the contents of the Box. + Ok(unsafe { + ARef::from_raw(NonNull::new_unchecked(Box::into_raw(Pin::into_inner_unchecked(new)))) + }) + } + + pub(super) unsafe fn raw_mut_ptr(&self) -> *mut bindings::drm_connector { + self.connector.get() + } + + pub(super) unsafe fn from_raw_ptr<'a>(ptr: *const bindings::drm_connector) -> &'a Self { + unsafe { &*(ptr as *mut Self) } + } + + /// 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 mode config guard for this connector's parent DRM device + pub fn guard<'a>(&'a self, guard: &ModeConfigGuard<'a, T::Driver>) -> ConnectorGuard<'a, T> { + guard.assert_owner(self.drm_dev()); + ConnectorGuard { connector: self } + } + + /// Attach an encoder to this connector, this should only be done before registration + #[must_use] + pub fn attach_encoder(&self, encoder: &Encoder) -> Result { + // SAFETY: FFI call with no special requirements + to_result(unsafe { + bindings::drm_connector_attach_encoder(self.raw_mut_ptr(), encoder.raw_mut_ptr()) + }) + } +} + +unsafe extern "C" fn connector_destroy_callback( + connector: *mut bindings::drm_connector, +) { + // SAFETY: Connector has to point to a valid drm_connector, as its function table is the only + // existing entrypoint to this function + unsafe { + bindings::drm_connector_unregister(connector); + bindings::drm_connector_cleanup(connector); + }; + + // SAFETY: We always create connectors in boxes, and since we are running from this connector's + // destructor we are guaranteed to have the last remaining reference. Furthermore, we're + // guaranteed by type invariance that the contents of the box are of type Connector. + unsafe { drop(Box::from_raw(connector as *mut Connector)) }; +} + +unsafe extern "C" fn get_modes_callback( + connector: *mut bindings::drm_connector, +) -> c_int { + // SAFETY: We're guaranteed by type invariants that connector is of type Connector, and + // connector must point to a valid instance of Connector as it's the only entry-point to this + // callback. + let connector = unsafe { Connector::::from_raw_ptr(connector) }; + + // SAFETY: This FFI callback is only called while the mode config lock is held + let guard = unsafe { ModeConfigGuard::new_borrowed(connector.drm_dev()) }; + + T::get_modes(connector.guard(&guard), &guard) +} + +/// A privileged smart-pointer for `Connector` which proves that the owner currently is protected +/// under the `bindings::drm_device.mode_config.mutex` lock and provides access to data and methods +/// protected under said lock. +#[derive(Copy, Clone)] +pub struct ConnectorGuard<'a, T: DriverConnector> { + connector: &'a Connector, +} + +impl Deref for ConnectorGuard<'_, T> { + type Target = Connector; + + fn deref(&self) -> &Self::Target { + self.connector + } +} + +impl<'a, T: DriverConnector> ConnectorGuard<'a, T> { + pub fn add_modes_noedid(&self, (max_h, max_v): (i32, i32)) -> i32 { + unsafe { bindings::drm_add_modes_noedid(self.raw_mut_ptr(), max_h, max_v) } + } + + pub fn set_preferred_mode(&self, (h_pref, w_pref): (i32, i32)) { + unsafe { bindings::drm_set_preferred_mode(self.raw_mut_ptr(), h_pref, w_pref) } + } +} + +#[derive(Default)] +#[repr(C)] +pub struct ConnectorState { + state: bindings::drm_connector_state, + inner: T, +} + +/// The trait for a driver's atomic DRM connector state +pub trait DriverConnectorState: Clone + Default + Sized { + type Connector: DriverConnector; +} + +impl ConnectorState { + /// Consume this struct without dropping it, and return a pointer to its base + /// `drm_connector_state` which can be handed off to DRM. + fn into_raw(self: Box) -> *mut bindings::drm_connector_state { + let this = Box::into_raw(self); + + unsafe { &mut (*this).state } + } + + /// Consume a raw pointer and recover the original `Box>` + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `ConnectorState` + unsafe fn from_raw(ptr: *mut bindings::drm_connector_state) -> Box { + unsafe { Box::from_raw(ptr as *mut _) } + } + + /// Obtain a reference back to the `ConnectorState` + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `ConnectorState`. + unsafe fn as_ref<'a>(ptr: *const bindings::drm_connector_state) -> &'a Self { + unsafe { &*(ptr as *const _) } + } + + /// Obtain a mutable reference back to the ConnectorState + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `ConnectorState`, and + /// that no other references to this `ConnectorState` exist for the lifetime of this + /// reference + unsafe fn as_mut_ref<'a>(ptr: *mut bindings::drm_connector_state) -> &'a mut Self { + unsafe { &mut *(ptr as *mut _) } + } + + /// Obtain a mutable pointer to the base connector state, for use in FFI calls + unsafe fn as_mut_ptr(&mut self) -> *mut bindings::drm_connector_state { + &mut self.state + } +} + +unsafe impl Zeroable for bindings::drm_connector_state {} + +unsafe extern "C" fn atomic_duplicate_state_callback( + connector: *mut bindings::drm_connector +) -> *mut bindings::drm_connector_state +{ + // SAFETY: `connector` has to point to a valid instance of drm_connector, since it holds the vtable for + // this function - which is the only possible entrypoint the caller could have used + let state = unsafe { (*connector).state }; + if state.is_null() { + return null_mut(); + } + + // SAFETY: We just verified that `state` is non-null, and we're guaranteed by our bindings that + // `state` is of type `ConnectorState`. + let state = unsafe { ConnectorState::::as_ref(state) }; + + let mut new: Result>> = Box::try_init(try_init!(ConnectorState:: { + state: bindings::drm_connector_state { ..Default::default() }, + inner: state.inner.clone() + })); + + if let Ok(mut new) = new { + // SAFETY: Just a lil' FFI call, nothing special here + unsafe { + bindings::__drm_atomic_helper_connector_duplicate_state(connector, new.as_mut_ptr()) + }; + + new.into_raw() + } else { + null_mut() + } +} + +unsafe extern "C" fn atomic_destroy_state_callback( + _connector: *mut bindings::drm_connector, + connector_state: *mut bindings::drm_connector_state +) { + // SAFETY: This callback wouldn't be called unless there a connector state to destroy + unsafe { bindings::__drm_atomic_helper_connector_destroy_state(connector_state) }; + + // SAFETY: We're guaranteed by type invariants that connector_state is of type + // ConnectorState, and since this is the destructor callback for DRM - we're guaranteed to + // hold the only remaining reference to this state + unsafe { drop(ConnectorState::::from_raw(connector_state)) }; +} + +unsafe extern "C" fn connector_reset_callback( + connector: *mut bindings::drm_connector, +) { + // SAFETY: The only entrypoint to this function lives in `connector` so it must be valid, and + let state = unsafe { (*connector).state }; + if !state.is_null() { + // SAFETY: We're guaranteed by type invariance that this connector's state is of type + // DriverConnectorState + unsafe { atomic_destroy_state_callback::(connector, state) } + } + + // Unfortunately, this is the best we can do at the moment as this FFI callback was mistakenly + // presumed to be infallible :( + let new = Box::try_new(ConnectorState::::default()).expect("Blame the API, sorry!"); + + // SAFETY: DRM takes ownership of the state from here and assigns it to the connector + unsafe { bindings::__drm_atomic_helper_connector_reset(connector, new.into_raw()) }; +} diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs new file mode 100644 index 0000000000000..3d072028a4884 --- /dev/null +++ b/rust/kernel/drm/kms/crtc.rs @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! KMS driver abstractions for rust. + +use super::{ + plane::*, + ModeObject, + StaticModeObject, + KmsDriver +}; +use crate::{ + bindings, + drm::{drv::Driver, device::Device}, + device, + prelude::*, + types::Opaque, + init::Zeroable, + sync::Arc, + error::to_result, +}; +use core::{ + cell::UnsafeCell, + marker::PhantomPinned, + ptr::{null, null_mut}, + ops::Deref, +}; +use macros::vtable; + +/// A typed KMS CRTC with a specific driver. +#[repr(C)] +#[pin_data] +pub struct Crtc { + // The FFI drm_crtc object + pub(super) crtc: Opaque, + /// The driver's private inner data + #[pin] + inner: T, + #[pin] + _p: PhantomPinned, +} + +/// KMS CRTC object functions, which must be implemented by drivers. +pub trait DriverCrtc: Send + Sync + Sized { + /// The return type of the new() function. Should be `impl PinInit`. + /// TODO: Remove this when return_position_impl_trait_in_trait is stable. + type Initializer: PinInit; + + /// The data type to use for passing incoming arguments for new `Crtc` instances + /// Drivers which don't care about this can just use `()` + type Args; + + /// The parent driver implementation + type Driver: KmsDriver; + + /// The type for this driver's drm_crtc_state implementation + type State: DriverCrtcState; + + /// Create a new CRTC for this driver + fn new(device: &Device, args: Self::Args) -> Self::Initializer; +} + +unsafe impl Zeroable for bindings::drm_crtc { } + +impl crate::private::Sealed for Crtc {} + +// SAFETY: Crtc.crtc is not exposed to users by default, and our accessors ensure we only perform +// thread-safe operations for this object +unsafe impl Send for Crtc { } +unsafe impl Sync for Crtc { } + +impl Deref for Crtc { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +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 { &*((*self.raw_mut_ptr()).dev as *const _) } + } +} + +// SAFETY: CRTCs are non-refcounted modesetting objects +unsafe impl StaticModeObject for Crtc { } + +impl Crtc { + /// The actual C vtable for drm_crtc_funcs + const FUNCS: bindings::drm_crtc_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, // TODO + 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, + }; + + /// The actual C vtable for drm_crtc_helper_funcs + const HELPER_FUNCS: bindings::drm_crtc_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, + }; + + pub fn new<'a, P, C>( + dev: &'a Device, + primary: &'a Plane

, + cursor: Option<&'a Plane>, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'a Self> + where + P: DriverPlane, + C: DriverPlane + { + let this = Box::try_pin_init(try_pin_init!(Self { + crtc: Opaque::new(bindings::drm_crtc { + helper_private: &Self::HELPER_FUNCS, + ..Default::default() + }), + inner <- T::new(dev, args), + _p: PhantomPinned, + }))?; + + to_result(unsafe { + bindings::drm_crtc_init_with_planes( + dev.drm.get(), + this.raw_mut_ptr(), + primary.raw_mut_ptr(), + cursor.map_or(null_mut(), |c| c.raw_mut_ptr()), + &Self::FUNCS, + name.map_or(null(), |n| n.as_char_ptr()) + ) + })?; + + // Convert the box into a raw pointer, we'll re-assemble it in crtc_destroy_callback() + // SAFETY: We don't move anything + Ok(unsafe { &*Box::into_raw(Pin::into_inner_unchecked(this)) }) + } + + pub(super) fn raw_mut_ptr(&self) -> *mut bindings::drm_crtc { + self.crtc.get() + } +} + +unsafe extern "C" fn crtc_destroy_callback( + crtc: *mut bindings::drm_crtc +) { + // SAFETY: FFI call with no special requirements + unsafe { bindings::drm_crtc_cleanup(crtc) }; + + // SAFETY: We're guaranteed by type invariants this plane is contained within an Box> + unsafe { drop(Box::from_raw(crtc as *mut Crtc)) }; +} + +unsafe impl Zeroable for bindings::drm_crtc_state { } + +pub trait DriverCrtcState: Clone + Default + Sized { + type Crtc: DriverCrtc; +} + +#[derive(Default)] +#[repr(C)] +pub struct CrtcState { + state: bindings::drm_crtc_state, + inner: T, +} + +impl CrtcState { + /// Consume this struct without dropping it, and return a pointer to its base `drm_crtc_state` + /// which can be handed off to DRM. + fn into_raw(self: Box) -> *mut bindings::drm_crtc_state { + let this = Box::into_raw(self); + + unsafe { &mut (*this).state } + } + + /// Consume a raw pointer and recover the original `Box>` + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `CrtcState` + unsafe fn from_raw(ptr: *mut bindings::drm_crtc_state) -> Box { + unsafe { Box::from_raw(ptr as *mut _) } + } + + /// Obtain a reference back to the `CrtcState` + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `CrtcState`. + unsafe fn as_ref<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Self { + unsafe { &*(ptr as *const _) } + } + + /// Obtain a mutable reference back to the CrtcState + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `CrtcState`, and that + /// no other references to this `CrtcState` exist for the lifetime of this reference + unsafe fn as_mut_ref<'a>(ptr: *mut bindings::drm_crtc_state) -> &'a mut Self { + unsafe { &mut *(ptr as *mut _) } + } + + /// Obtain a mutable pointer to the base plane state, for use in FFI calls + unsafe fn as_mut_ptr(&mut self) -> *mut bindings::drm_crtc_state { + &mut self.state + } +} + +unsafe extern "C" fn atomic_duplicate_state_callback( + crtc: *mut bindings::drm_crtc +) -> *mut bindings::drm_crtc_state { + // SAFETY: `crtc` has to point to a valid instance of drm_crtc, since it holds the vtable for + // this function - which is the only possible entrypoint the caller could have used + let state = unsafe { (*crtc).state }; + if state.is_null() { + return null_mut(); + } + + // SAFETY: We just verified that `state` is non-null, and we're guaranteed by our bindings that + // `state` is of type `CrtcState`. + let state = unsafe { CrtcState::::as_ref(state) }; + + let mut new: Result>> = Box::try_init(try_init!(CrtcState:: { + state: bindings::drm_crtc_state { ..Default::default() }, + inner: state.inner.clone() + })); + + if let Ok(mut new) = new { + unsafe { bindings::__drm_atomic_helper_crtc_duplicate_state(crtc, new.as_mut_ptr()) } + + new.into_raw() + } else { + null_mut() + } +} + +unsafe extern "C" fn atomic_destroy_state_callback( + _crtc: *mut bindings::drm_crtc, + crtc_state: *mut bindings::drm_crtc_state, +) { + // SAFETY: This callback wouldn't be called unless there a CRTC state to destroy + unsafe { bindings::__drm_atomic_helper_crtc_destroy_state(crtc_state) }; + // + // SAFETY: We're guaranteed by type invariants that crtc_state is of type CrtcState, and + // since this is the destructor callback for DRM - we're guaranteed to hold the only remaining + // reference to this state + drop(unsafe { CrtcState::::from_raw(crtc_state) }); +} + +unsafe extern "C" fn crtc_reset_callback( + crtc: *mut bindings::drm_crtc, +) { + // SAFETY: The only entrypoint to this function lives in `crtc` so it must be valid, and + let state = unsafe { (*crtc).state }; + if !state.is_null() { + // SAFETY: We're guaranteed by type invariance that this crtc's state is of type + // DriverConnectorState + unsafe { atomic_destroy_state_callback::(crtc, state) } + } + + // Unfortunately, this is the best we can do at the moment as this FFI callback was mistakenly + // presumed to be infallible :( + let new = Box::try_new(CrtcState::::default()).expect("Blame the API, sorry!"); + + // SAFETY: DRM takes ownership of the state from here and assigns it to the crtc + unsafe { bindings::__drm_atomic_helper_crtc_reset(crtc, new.into_raw()) }; +} diff --git a/rust/kernel/drm/kms/encoder.rs b/rust/kernel/drm/kms/encoder.rs new file mode 100644 index 0000000000000..7a5bc0ca1577b --- /dev/null +++ b/rust/kernel/drm/kms/encoder.rs @@ -0,0 +1,175 @@ +// TODO: License stuff + +//! drm_encoder abstractions for rust + +use crate::{ + drm::{ + device::Device, + drv::Driver, + }, + prelude::*, + sync::Arc, + types::Opaque, + init::Zeroable, + error::to_result, +}; +use core::{ + marker::PhantomPinned, + ptr::null, + ops::Deref, +}; +use super::{ModeObject, StaticModeObject, KmsDriver}; +use bindings; + +pub use bindings::{ + DRM_MODE_ENCODER_NONE, + DRM_MODE_ENCODER_DAC, + DRM_MODE_ENCODER_TMDS, + DRM_MODE_ENCODER_LVDS, + DRM_MODE_ENCODER_TVDAC, + DRM_MODE_ENCODER_VIRTUAL, + DRM_MODE_ENCODER_DSI, + DRM_MODE_ENCODER_DPMST, + DRM_MODE_ENCODER_DPI, +}; + +/// A DRM encoder (`drm_encoder`) +/// +/// This is the main struct for DRM encoders, which may also hold any private data specified by the +/// driver. +#[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, +} + +/// The main trait for KMS drivers to implement for their display encoders. +pub trait DriverEncoder: Send + Sync + Sized { + /// The return type of the new() function. Should be `impl PinInit`. + /// TODO: Remove this when return_position_impl_trait_in_trait is stable. + type Initializer: PinInit; + + /// The parent driver for this drm_encoder implementation + type Driver: KmsDriver; + + /// The type used for passing arguments to the driver's constructor + /// Drivers which don't care about this can just use `()` + type Args; + + /// Create a new encoder for this driver + fn new(device: &Device, args: Self::Args) -> Self::Initializer; +} + +impl crate::private::Sealed for Encoder {} + +unsafe impl Zeroable for bindings::drm_encoder {} + +// SAFETY: Encoder.encoder is not exposed to users by default, and our accessors ensure we only +// perform thread-safe operations for this object +unsafe impl Send for Encoder { } +unsafe impl Sync for Encoder { } + +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 { &*((*self.raw_mut_ptr()).dev.cast()) } + } +} + +// 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 + } +} + +impl Encoder { + const FUNCS: bindings::drm_encoder_funcs = bindings::drm_encoder_funcs { + reset: None, + destroy: Some(encoder_destroy_callback::), + late_register: None, + early_unregister: None, + debugfs_init: None, + }; + + const HELPER_FUNCS: bindings::drm_encoder_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, + }; + + pub fn new<'a>( + dev: &'a Device, + type_: u32, + possible_crtcs: u32, + possible_clones: u32, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'a Self> { + let this: Pin> = Box::try_pin_init(try_pin_init!(Self { + encoder: Opaque::new(bindings::drm_encoder { + helper_private: &Self::HELPER_FUNCS, + possible_crtcs, + possible_clones, + ..Default::default() + }), + inner <- T::new(dev, args), + _p: PhantomPinned + }))?; + + // SAFETY: FFI call with no special requirements + to_result(unsafe { + bindings::drm_encoder_init( + dev.drm.get(), + this.raw_mut_ptr(), + &Self::FUNCS, + type_ as _, + name.map_or(null(), |n| n.as_char_ptr()) + ) + })?; + + // Convert the box into a raw pointer, we'll re-assemble it in encoder_destroy_callback() + // SAFETY: We don't move anything + Ok(unsafe { &*Box::into_raw(Pin::into_inner_unchecked(this)) }) + } + + pub(crate) unsafe fn raw_mut_ptr(&self) -> *mut bindings::drm_encoder { + self.encoder.get() + } +} + +unsafe extern "C" fn encoder_destroy_callback( + encoder: *mut bindings::drm_encoder +) { + // SAFETY: encoder contains the only possible entrypoint to this function, so the pointer must + // be valid + unsafe { bindings::drm_encoder_cleanup(encoder) }; + + // Reclaim ownership of the reference we took in Encoder::::new() so we can drop it + // SAFETY: We always create encoders in Arc, and we're guaranteed by type invariants that + // this encoder is a Encoder + unsafe { drop(Box::from_raw(encoder as *mut Encoder)) }; +} diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs new file mode 100644 index 0000000000000..78c8e370b997c --- /dev/null +++ b/rust/kernel/drm/kms/plane.rs @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! KMS atomic plane abstractions for rust. + +use alloc::boxed::Box; +use crate::{ + bindings, + drm::{device::Device, drv::Driver}, + error::{to_result, from_err_ptr, Error}, + init::Zeroable, + prelude::*, + types::{ARef, Opaque}, + sync::{Arc, ArcBorrow}, + init::InPlaceInit, + offset_of, +}; +use core::{ + cell::UnsafeCell, + mem::{self, size_of, MaybeUninit}, + ptr::{NonNull, null, null_mut, addr_of_mut}, + marker::PhantomPinned, + ops::Deref, + ffi::c_int, +}; +use macros::pin_data; +use super::{KmsDriver, ModeObject, StaticModeObject}; + +/// The main structure containing a drm_plane that is exposed to callers. It is intentionally +/// impossible to acquire a mutable reference to this structure, and as such this structure should +/// only be exposed through immutable references. +#[repr(C)] +#[pin_data] +pub struct Plane { + /// The FFI drm_plane object + pub(super) plane: Opaque, + /// The driver's private inner data + #[pin] + inner: T, + #[pin] + _p: PhantomPinned, +} + +unsafe impl Zeroable for bindings::drm_plane {} + +// SAFETY: Plane.plane is not exposed to users by default, and our accessors ensure we only +// perform thread-safe operations for this object +unsafe impl Send for Plane { } +unsafe impl Sync for Plane { } + +/// The main trait for implementing the drm_plane API. This contains the various trait methods that +/// need to be implemented by a driver. The private driver data for the plane is contained in +/// whatever struct the driver defines which implements this trait. +pub trait DriverPlane: Send + Sync + Sized { + /// The return type of the new() function. Should be `impl PinInit`. + /// TODO: Remove this when return_position_impl_trait_in_trait is stable. + type Initializer: PinInit; + + /// The data type to use for passing incoming arguments for new `Plane` instances + /// Drivers which don't care about this can just use `()` + type Args; + + /// The parent driver implementation + type Driver: KmsDriver; + + /// The type for this driver's drm_plane_state implementation + type State: DriverPlaneState; + + /// Create a new plane for this driver + fn new(device: &Device, args: Self::Args) -> Self::Initializer; +} + +impl crate::private::Sealed for Plane {} + +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 { &*((*self.raw_mut_ptr()).dev as *const _) } + } +} + +// SAFETY: Planes do not have a refcount +unsafe impl StaticModeObject for Plane { } + +impl Deref for Plane { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Plane { + /// The actual C vtable for drm_plane_funcs + const FUNCS: bindings::drm_plane_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, // TODO someday + atomic_get_property: None, // TODO someday + late_register: None, // TODO someday + early_unregister: None, // TODO someday + atomic_print_state: None, // TODO: Display someday??? + format_mod_supported: None // TODO someday + }; + + const HELPER_FUNCS: bindings::drm_plane_helper_funcs = bindings::drm_plane_helper_funcs { + prepare_fb: None, // TODO someday? + cleanup_fb: None, // TODO someday? + begin_fb_access: None, // TODO: someday? + end_fb_access: None, // TODO: someday? + atomic_check: None, // TODO + atomic_update: None, // TODO + atomic_enable: None, // TODO + atomic_disable: None, // TODO + atomic_async_check: None, // TODO + atomic_async_update: None, // TODO + }; + + pub fn new<'a>( + dev: &'a Device, + possible_crtcs: u32, + formats: &'static [u32], + format_modifiers: Option<&'static [u64]>, + type_: PlaneType, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'a Self> { + let this: Pin> = Box::try_pin_init(try_pin_init!(Self { + plane: Opaque::new(bindings::drm_plane { + helper_private: &Self::HELPER_FUNCS, + ..Default::default() + }), + inner <- T::new(dev, args), + _p: PhantomPinned + }))?; + + // SAFETY: FFI call with no special requirements + to_result(unsafe { + bindings::drm_universal_plane_init( + dev.drm.get(), + this.raw_mut_ptr(), + possible_crtcs, + &Self::FUNCS, + formats.as_ptr(), + formats.len() as _, + format_modifiers.map_or(null(), |f| f.as_ptr()), + type_ as _, + name.map_or(null(), |n| n.as_char_ptr()) + ) + })?; + + // Convert the box into a raw pointer, we'll re-assemble it in plane_destroy_callback() + // SAFETY: We don't move anything + Ok(unsafe { &*Box::into_raw(Pin::into_inner_unchecked(this)) }) + } + + pub(super) fn raw_mut_ptr(&self) -> *mut bindings::drm_plane { + self.plane.get() + } +} + +unsafe extern "C" fn plane_destroy_callback( + plane: *mut bindings::drm_plane +) { + // SAFETY: plane contains the only possible entrypoint to this function, so the pointer must be + // valid + unsafe { bindings::drm_plane_cleanup(plane) }; + + // Reclaim ownership of the reference we took in Plane::::new() so we can drop it + // SAFETY: We're guaranteed by type invariants this plane is contained within an Box> + unsafe { drop(Box::from_raw(plane as *mut Plane)) }; +} + +#[derive(Default)] +#[repr(C)] +pub struct PlaneState { + state: bindings::drm_plane_state, + inner: T, +} + +/// Traits which must be implemented by KMS drivers for DRM planes. +pub trait DriverPlaneState: Clone + Default + Sized { + /// The type for this driver's drm_plane implementation + type Plane: DriverPlane; +} + +impl PlaneState { + /// Consume this struct without dropping it, and return a pointer to its base `drm_plane_state` + /// which can be handed off to DRM. + fn into_raw(self: Box) -> *mut bindings::drm_plane_state { + let this = Box::into_raw(self); + + unsafe { &mut (*this).state } + } + + /// Consume a raw pointer and recover the original `Box>` + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `PlaneState` + unsafe fn from_raw(ptr: *mut bindings::drm_plane_state) -> Box { + unsafe { Box::from_raw(ptr as *mut _) } + } + + /// Obtain a reference back to the `PlaneState` + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `PlaneState`. + unsafe fn as_ref<'a>(ptr: *const bindings::drm_plane_state) -> &'a Self { + unsafe { &*(ptr as *const _) } + } + + /// Obtain a mutable reference back to the PlaneState + /// + /// SAFETY: Callers must ensure that ptr contains a valid instance of `PlaneState`, and that + /// no other references to this `PlaneState` exist for the lifetime of this reference + unsafe fn as_mut_ref<'a>(ptr: *mut bindings::drm_plane_state) -> &'a mut Self { + unsafe { &mut *(ptr as *mut _) } + } + + /// Obtain a mutable pointer to the base plane state, for use in FFI calls + unsafe fn as_mut_ptr(&mut self) -> *mut bindings::drm_plane_state { + &mut self.state + } +} + +unsafe impl Zeroable for bindings::drm_plane_state { } + +unsafe extern "C" fn atomic_duplicate_state_callback( + plane: *mut bindings::drm_plane +) -> *mut bindings::drm_plane_state +{ + // SAFETY: `plane` has to point to a valid instance of drm_plane, since it holds the vtable for + // this function - which is the only possible entrypoint the caller could have used + let state = unsafe { (*plane).state }; + if state.is_null() { + return null_mut(); + } + + // SAFETY: We just verified that `state` is non-null, and we're guaranteed by our bindings that + // `state` is of type `PlaneState`. + let state = unsafe { PlaneState::::as_ref(state) }; + + let mut new: Result>> = Box::try_init(try_init!(PlaneState:: { + state: bindings::drm_plane_state { ..Default::default() }, + inner: state.inner.clone() + })); + + if let Ok(mut new) = new { + // SAFETY: Just a lil' FFI call, nothing special here + unsafe { bindings::__drm_atomic_helper_plane_duplicate_state(plane, new.as_mut_ptr()) }; + + new.into_raw() + } else { + null_mut() + } +} + +unsafe extern "C" fn atomic_destroy_state_callback( + _plane: *mut bindings::drm_plane, + plane_state: *mut bindings::drm_plane_state +) { + // SAFETY: This callback wouldn't be called unless there a plane state to destroy + unsafe { bindings::__drm_atomic_helper_plane_destroy_state(plane_state) }; + + // SAFETY: We're guaranteed by type invariants that plane_state is of type PlaneState, and + // since this is the destructor callback for DRM - we're guaranteed to hold the only remaining + // reference to this state + unsafe { drop(PlaneState::::from_raw(plane_state)) }; +} + +unsafe extern "C" fn plane_reset_callback( + plane: *mut bindings::drm_plane, +) { + // SAFETY: The only entrypoint to this function lives in `plane` so it must be valid, and + let state = unsafe { (*plane).state }; + if !state.is_null() { + // SAFETY: We're guaranteed by type invariance that this plane's state is of type + // DriverPlaneState + unsafe { atomic_destroy_state_callback::(plane, state) } + } + + // Unfortunately, this is the best we can do at the moment as this FFI callback was mistakenly + // presumed to be infallible :( + let new = Box::try_new(PlaneState::::default()).expect("Blame the API, sorry!"); + + // SAFETY: DRM takes ownership of the state from here and assigns it to the plane + unsafe { bindings::__drm_atomic_helper_plane_reset(plane, new.into_raw()) }; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum PlaneType { + OVERLAY = bindings::drm_plane_type_DRM_PLANE_TYPE_OVERLAY, + PRIMARY = bindings::drm_plane_type_DRM_PLANE_TYPE_PRIMARY, + CURSOR = bindings::drm_plane_type_DRM_PLANE_TYPE_CURSOR, +} 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; -- 2.43.0