From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f49.google.com (mail-wr1-f49.google.com [209.85.221.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C54E5286409 for ; Fri, 3 Jul 2026 03:01:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047709; cv=none; b=RUqMFx0DUURHF7YtigQVxRumtUcGs9nK5B+N79Rx1e0ZqD/FShBUOL8rzgW+KD+4SrCEfAPZerGA5e8TG2igSMIcVWrx09fXuv1gz091DiL6tviMG7nUKbMJ4pMjBWT/yCTmrlyWgI7NNbmZf/42erRDfLMCbZI6R8qFMHzmPBY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047709; c=relaxed/simple; bh=9N2b4R7gb+COOjtRaxLbNiWqt8Ch2BvhG3Rc0TTPv+o=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=EH6xGAZM1XMyX3fdBdN4TDAZAZSoJnZmJwgr9F9vTcFg6Rbi/KVohvwyMkRx8GzyZ2cLVRkuMPgU8dRTVt2UppPfZanEBso5RJ5X+RajfqyuB4hOgkgsHe1YVufWMbnfIy/lyHMa0KanCabBpf2aXCcGlYx4/UAqoEa24yhsRgk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=ruq9bo46; arc=none smtp.client-ip=209.85.221.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="ruq9bo46" Received: by mail-wr1-f49.google.com with SMTP id ffacd0b85a97d-461edb387ddso94436f8f.3 for ; Thu, 02 Jul 2026 20:01:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047696; x=1783652496; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=eumkndbmpgBQmZ7zkaNHL2PfDqyAiyKIvWHnUB0r2TI=; b=ruq9bo4658a6o6QMh7l3GVDpZOJEBz/11VURa5xOjIWyQw4NXvWN7rnKDzloUyLj8c Gqt5eYwA4mWy7hSYYfnO5fUUSTLXlh14Jcj8G5yjL/Svr7ld1yTvuT3GwMAHYR2nQVfV Q7gJk+/yYbJ6tIeDwxEUuY22KcL9PDtz1KEN7N5c3WRXJYe8MYs89gYxOpRdnjCTl0EY 8//YogW8m4NTcX14iFO4PA11GECq+f+9GOAEGOzEE05TVUIP/RltLqMW3U2K1WFf5Jg5 W1t7xt1f75Jc3cdb5dMjveteXlB5HvWDo5kPHhV4s+jNi7cE6JWjV61BWQZXQAOV1VHt vCWQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047696; x=1783652496; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=eumkndbmpgBQmZ7zkaNHL2PfDqyAiyKIvWHnUB0r2TI=; b=HMVt8z0jpz6Sz8zvdIqYLgPEcaS6v1lBRHpQUqzYdjlYoJCDdw20c7/UgClgTue1Eu 9iyYpVGbyUpBiLc7CDM++7odUvzqz8WTgZPN+iXY8tYQ1sT47/7Z4QQegyCBCeGVXZht amj2KaGFMvl7DF3UrK2vfgZZXo5M/JLeXQwdPTQPtxzSKMNDcjlXWcXiQQ58DN7qqPiw 9rA7Y+oe5ETaHwoCMRurXkOxCp8X7WQMr1Ehx/PjNM3CFhXeiqd6oyg1YrhYslLAoAIq pruDW+Ln4ksfFo0ZbJR8itMIvJlQhrNbc4ZOqDhCAfY/YhNaXeGX1rBgmZbazDTvZbXF ekrg== X-Forwarded-Encrypted: i=1; AHgh+Ro65K4oF3MQIQrcLmYrmoUI5WBgizaGoJ8SwmA9wDtiKcZR6TNJqEAyyRhH/k3ALd4wgFdUsd5D6TSElsg=@vger.kernel.org X-Gm-Message-State: AOJu0Yxxze6cSkLvV/h8gkeVZPSSJzAbrLZj2Pif51LXFzLAbVb0AN/C WR293QASuU4fsk9bCHdAljIXCMqMNyIBB61uL9/TZff2zUr3n/BoJ2ZHd0hnHLJc9Q== X-Gm-Gg: AfdE7ckJ6NwSudXZ+agbBgBWOQTyMs0gFqNcFDMvGkR+qkwNOCAwb5XHMRdyqBaPKbM TQfwMHeg90j0nVYO5IBfMWRZ4Y+g5Rdifv/amPKPMksE2WtN9NpNBLyPBTMsGWk80Xb7UtUzls/ 6KB4jQ64t8t0f0uEFVAj6cBP0pJ9yO7PFIS5/+Y5Ouv1pOBsDKkydfOfnpvGPmJbLpSZz4aBeUL IQM9cr7EN2I1wn7AFeuMAWNZ4zUQ73DbejlhGL/siFy0Luc3oo6l8kWIJOQU7URszVuoV066FgY mDX5YR5ECcPbmN/v4zyrxvpkQC1pBl1Ae9ATjU6RKIJhqUWeSjkriGDdj4PTl0nHIAGvkZtyBWC 3JzqSxDq/eJVBlaAitadwqXz/T/Wx1+MYRRpQY1AbiiU1LanBTdXMQvX1gLdwGRI993/GcbquoW 5VjFVMnsZ7x+v10zbaL/hzWZoEWSb2Ji2qynrSU1EFsbm027Q0RDqttqir X-Received: by 2002:a05:6000:420e:b0:473:c18:f2cb with SMTP id ffacd0b85a97d-477af4f8f3dmr12335882f8f.18.1783047693226; Thu, 02 Jul 2026 20:01:33 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:30 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 01/18] rust: drm: kms: forward-port the safe mode-object layer onto the typestate device Date: Fri, 3 Jul 2026 04:00:48 +0100 Message-ID: <20260703030123.2814-2-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> 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: quoted-printable Port Lyude Paul's rvkms-slim safe mode-object layer (CRTC/plane/connector/ encoder/vblank/atomic state) onto her newer rust-stuck typestate Device design (ParentDevice/DeviceContext/RegistrationData), which the preceding cherry-picked commits bring in. Source: lyude/rvkms-slim + lyude/rust/rust-= stuck. Same module split as rvkms-slim (kms/{atomic,connector,crtc,encoder, framebuffer,modes,plane,vblank}.rs), same trait shape, adjusted to the typestate device's generic Ctx/State parameters. This is a mechanical type-level port, not new design -- diff against lyude/rvkms-slim to see exactly what moved. Does not build yet. The port's mode-object traits are mutually recursive in a way the current trait solver can't evaluate; the next few commits get it to a clean build. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- rust/kernel/drm/kms.rs | 395 +++++++++- rust/kernel/drm/kms/atomic.rs | 864 ++++++++++++++++++++++ rust/kernel/drm/kms/connector.rs | 997 +++++++++++++++++++++++++ rust/kernel/drm/kms/crtc.rs | 1110 ++++++++++++++++++++++++++++ rust/kernel/drm/kms/encoder.rs | 409 ++++++++++ rust/kernel/drm/kms/framebuffer.rs | 70 ++ rust/kernel/drm/kms/modes.rs | 76 ++ rust/kernel/drm/kms/plane.rs | 1095 +++++++++++++++++++++++++++ rust/kernel/drm/kms/vblank.rs | 461 ++++++++++++ 9 files changed, 5469 insertions(+), 8 deletions(-) create mode 100644 rust/kernel/drm/kms/atomic.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/framebuffer.rs create mode 100644 rust/kernel/drm/kms/modes.rs create mode 100644 rust/kernel/drm/kms/plane.rs create mode 100644 rust/kernel/drm/kms/vblank.rs diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 084ed0aebd0e..11b09d2175db 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -3,17 +3,37 @@ //! KMS driver abstractions for rust. =20 use crate::{ + container_of, drm::{ - device::Device, + device::{Device, DeviceContext}, driver::Driver, private::Sealed, Uninit, // }, error::to_result, - prelude::*, // + prelude::*, + sync::{Mutex, MutexGuard}, + types::*, }; use bindings; -use core::{marker::PhantomData, ops::Deref}; +use core::{ + cell::Cell, + marker::PhantomData, + ops::Deref, + ptr::{self, addr_of_mut, NonNull}, +}; + +// Forward-ported mode-object layer (see Documentation/gpu/rust/vino-kms-f= orward-port.md). +// Modules are declared here as each is grafted onto the rust-stuck typest= ate design. +// `modes` is self-contained and ported as-is; the rest follow per the pla= n's order. +pub mod atomic; +pub mod connector; +pub mod crtc; +pub mod encoder; +pub mod framebuffer; +pub mod modes; +pub mod plane; +pub mod vblank; =20 /// The C vtable for a [`Device`]. /// @@ -77,13 +97,21 @@ impl KmsContext for Probing {} /// This type is guaranteed to represent a [`Device`] that has not been re= gistered with userspace, /// and is in the process of setting up KMS support. It carries a [`KmsDev= iceContext`] to indicate /// which stage of the KMS setup process this [`Device`] is currently in. -pub struct NewKmsDevice<'a, T: Driver, C: KmsContext>(&'a Device, PhantomData); +pub struct NewKmsDevice<'a, T: Driver, C: KmsContext> { + drm: &'a Device, + /// Tracks whether any CRTC created during probe requested vblank supp= ort, so that + /// [`drm_vblank_init()`] can be called afterwards. Set by [`crtc::Crt= c::new()`]. + /// + /// [`drm_vblank_init()`]: srctree/include/drm/drm_vblank.h + pub(crate) has_vblanks: Cell, + _ctx: PhantomData, +} =20 impl<'a, T: Driver, C: KmsContext> Deref for NewKmsDevice<'a, T, C> { type Target =3D Device; =20 fn deref(&self) -> &Self::Target { - self.0 + self.drm } } =20 @@ -95,15 +123,56 @@ fn deref(&self) -> &Self::Target { /// [`PhantomData`]: PhantomData #[vtable] pub trait KmsDriver: Driver { + /// The driver's [`DriverConnector`](connector::DriverConnector) imple= mentation. + /// + /// TODO: This will be unneeded once we support multiple `DriverConnec= tor` implementations. + type Connector: connector::DriverConnector; + + /// The driver's [`DriverPlane`](plane::DriverPlane) implementation. + /// + /// TODO: This will be unneeded once we support multiple `DriverPlane`= implementations. + type Plane: plane::DriverPlane; + + /// The driver's [`DriverCrtc`](crtc::DriverCrtc) implementation. + /// + /// TODO: This will be unneeded once we support multiple `DriverCrtc` = implementations. + type Crtc: crtc::DriverCrtc; + + /// The driver's [`DriverEncoder`](encoder::DriverEncoder) implementat= ion. + /// + /// TODO: This will be unneeded once we support multiple `DriverEncode= r` implementations. + type Encoder: encoder::DriverEncoder; + /// Return a [`ModeConfigInfo`] structure for this [`device::Device`]. fn mode_config_info(dev: &Device) -> Result where Self: Sized; =20 /// Create mode objects like [`crtc::Crtc`], [`plane::Plane`], etc. fo= r this device - fn probe(drm: NewKmsDevice<'_, Self, Probing>) -> Result + fn probe(drm: &NewKmsDevice<'_, Self, Probing>) -> Result where Self: Sized; + + /// The optional [`atomic_commit_tail`] callback for this [`Device`]. + /// + /// It must return a [`CommittedAtomicState`](atomic::CommittedAtomicS= tate) to prove that it has + /// signaled completion of the hw commit phase. Drivers may use this f= unction to customize the + /// order in which commits are performed during the atomic commit phas= e. + /// + /// If not provided, DRM will use its own default atomic commit tail h= elper + /// `drm_atomic_helper_commit_tail`. + /// + /// [`atomic_commit_tail`]: srctree/include/drm/drm_modeset_helper_vta= bles.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") + } } =20 impl private::KmsImpl for T { @@ -123,7 +192,11 @@ impl private::KmsImpl for T { =20 kms_helper_vtable: bindings::drm_mode_config_helper_funcs { atomic_commit_setup: None, - atomic_commit_tail: None, + atomic_commit_tail: if T::HAS_ATOMIC_COMMIT_TAIL { + Some(atomic::commit_tail_callback::) + } else { + None + }, }, }); =20 @@ -156,7 +229,19 @@ unsafe fn probe_kms(drm: &Device= ) -> Result, } + +// -----------------------------------------------------------------------= ---- +// Mode-object framework (forward-ported from lyude/rvkms-slim's kms.rs). +// +// Adapted to rust-stuck's typestate device: `Device` defaults `C` to +// `Registered`, so most `Device` references port unchanged; only creat= ion +// paths (during probe) use the `Uninit` context, reached via `NewKmsDevic= e`. +// -----------------------------------------------------------------------= ---- + +/// Implement the repetitive from_opaque/try_from_opaque methods for all m= ode object and state +/// types. +/// +/// Because there are so many different ways of accessing mode objects, th= eir 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 mac= ro handles that, and can +/// generate said functions for any kind of type which the original mode o= bject driver trait can be +/// derived from. +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_as= soc:ident =3D $extra_param_match:ident ),+> ),+ + )? ; + use + $obj_trait_param:ident as $obj_trait:ident, + $drv_trait_param:ident as KmsDriver<$drv_assoc_trait:ident =3D= ...> + ) =3D> { + #[doc =3D "Try to convert `opaque` into a fully qualified `Self`."] + #[doc =3D ""] + #[doc =3D concat!("This will try to convert `opaque` into `Self` i= f it shares the same [`", + stringify!($obj_trait), "`] implementation as `Sel= f`.")] + pub fn try_from_opaque<$( $lifetime, )? $( $decl_bound_id ),* >( + opaque: $opaque + ) -> Result<$inner_ret_ty, $opaque> + where + $drv_trait_param: KmsDriver<$drv_assoc_trait =3D $obj_trait_pa= ram>, + $obj_trait_param: $obj_trait + $( , $( $extra_bound_id: $extra_trait<$( $extra_assoc =3D $ext= ra_param_match ),+> ),+ )? + { + // FIXME: What we really want to be doing here is comparing vt= able pointers, but this is + // currently blocked on getting unique vtable macros to ensure= that each vtable has a + // consistent memory pointer. + // For the time being, we simply restrict things to one object= type per driver and do a + // transmutation based on that assumption holding true. + // SAFETY: We currently only allow one object type per-driver,= so this transmute is + // always safe. + Ok(unsafe { core::mem::transmute(opaque) }) + } + + #[doc =3D "Convert `opaque` into a fully qualified `Self`."] + #[doc =3D ""] + #[doc =3D concat!("This is an infallible version of [`Self::try_fr= om_opaque`]. This ", + "function is mainly useful for drivers where only = a single [`", + stringify!($obj_trait), "`] implementation exists.= ")] + #[doc =3D ""] + #[doc =3D "# Panics"] + #[doc =3D ""] + #[doc =3D 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_param: KmsDriver<$drv_assoc_trait =3D $obj_trait_pa= ram>, + $obj_trait_param: $obj_trait + $( , $( $extra_bound_id: $extra_trait<$( $extra_assoc =3D $ext= ra_param_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; + +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)) } + } + + /// Return the number of registered [`Crtc`](crtc::Crtc) objects on th= is [`Device`]. + #[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 is always >=3D 0, so casting to u32 is fine + unsafe { (*self.as_raw()).mode_config.num_crtc as u32 } + } +} + +impl Device { + /// 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 inva= riant 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`]. +/// This type requires [`Send`] + [`Sync`] as all modesetting objects in D= RM 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 [`Mode= Object`]. + /// + /// [`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 refere= nce-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 retriev= e an owned reference to +/// such types, see [`KmsRef`]. +/// +/// # Safety +/// +/// This trait must only be implemented for modesetting objects which do n= ot 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, u= sers 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 th= is 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 =3D 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: See `From<&T>`; we keep the parent device alive for = this new reference. + self.drm_dev().inc_ref(); + + Self(self.0) + } +} + +macro_rules! impl_aref_for_mode_object { + (impl $( < $( $param:ident: $bound:ident ),+ > )? for $type:ty) =3D> { + // SAFETY: drm_mode_object_get()/put() ensure the type is ref-coun= ted according to the + // safety contract + unsafe impl $( < $( $param: $bound ),+ > )? kernel::types::AlwaysR= efCounted for $type { + #[inline] + fn inc_ref(&self) { + // SAFETY: We're guaranteed by the safety contract of `Mod= eObject` that + // `raw_mode_obj()` always returns a pointer to an initial= ized `drm_mode_object`. + unsafe { kernel::bindings::drm_mode_object_get(self.raw_mo= de_obj()) } + } + + #[inline] + unsafe fn dec_ref(obj: core::ptr::NonNull) { + // SAFETY: We're guaranteed by the safety contract of `Mod= eObject` that + // `raw_mode_obj()` always returns a pointer to an initial= ized `drm_mode_object`. + unsafe { kernel::bindings::drm_mode_object_put(obj.as_ref(= ).raw_mode_obj()) } + } + } + }; +} + +pub(super) use impl_aref_for_mode_object; + +/// A trait for any object related to a [`ModeObject`] that can return its= vtable. +/// +/// This reference will be used for checking whether an opaque representat= ion 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 rel= evant 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 +/// some modesetting operations (particularly ones related to [`connectors= `](connector)) are still +/// protected under this single lock. The lock will be dropped once this o= bject 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 al= ways `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, ()>, PhantomDa= ta); + +impl<'a, T: KmsDriver> ModeConfigGuard<'a, T> { + /// Return the [`Device`] that this [`ModeConfigGuard`] belongs to. + pub fn drm_dev(&self) -> &'a Device { + let lock: *mut bindings::mutex =3D ptr::from_ref(self.0.lock_ref()= ).cast_mut().cast(); + + // SAFETY: + // - `self` is embedded within a `drm_mode_config` via our type in= variants + // - `self.0.lock` has an equivalent data type to `mutex` via its = type invariants. + let mode_config =3D unsafe { container_of!(lock, bindings::drm_mod= e_config, mutex) }; + + // SAFETY: And that `drm_mode_config` lives in a `drm_device` via = type invariants. + unsafe { + Device::from_raw(container_of!( + mode_config, + bindings::drm_device, + mode_config + )) + } + } + + /// Assert that the given device is the owner of this mode config guar= d. + /// + /// # 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/atomic.rs b/rust/kernel/drm/kms/atomic.rs new file mode 100644 index 000000000000..cc14bff47abd --- /dev/null +++ b/rust/kernel/drm/kms/atomic.rs @@ -0,0 +1,864 @@ +// 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 [`At= omicStateMutator`]. +/// +/// # Invariants +/// +/// - The data layout of this type is identical to [`struct drm_atomic_sta= te`]. +/// - `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 giv= en pointer + /// + /// # Safety + /// + /// `ptr` must point to a valid initialized instance of [`struct drm_a= tomic_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_sta= te) -> &'a Self { + // SAFETY: Our data layout is identical + // INVARIANT: Our safety contract upholds the guarantee that `stat= e` 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::from_raw((*self.state.get()).dev) } + } + + /// Return the old atomic state for `crtc`, if it is present within th= is [`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 t= his [`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 withi= n 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(), co= nnector.as_raw()) + .as_ref() + .map(|p| C::State::from_raw(p)) + } + } +} + +// SAFETY: DRM atomic state objects are always reference counted and the g= et/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 expo= sed 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 initializ= ed `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 refe= rences to the state of +/// multiple modesetting objects at once, along with mutating multiple sta= tes 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 stru= cture 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 potenti= ally be owned by rust code + /// if a driver is performing an atomic commit internally - and thus w= ill 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 `conne= ctor` 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` ha= s 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 previous= ly added to the atomic + /// state being composed. + /// + /// Returns `None` otherwise, or if another mutator still exists for t= his 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_cr= tc_state` + let state =3D + 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 previous= ly added to the atomic + /// state being composed. + /// + /// Returns `None` otherwise, or if another mutator still exists for t= his 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_pl= ane_state`. + let state =3D + 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 previousl= y added to the atomic + /// state being composed. + /// + /// Returns `None` otherwise, or if another mutator still exists for t= his 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_co= nnector_state` + let state =3D unsafe { + bindings::drm_atomic_get_new_connector_state(self.as_raw(), co= nnector.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 =3D 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 pos= sible reference + unsafe { ManuallyDrop::drop(&mut self.0.state) } + } +} + +impl AtomicStateComposer { + /// # Safety + /// + /// The caller guarantees that `ptr` points to a valid instance of `dr= m_atomic_state`. + 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 c= omposer if it hasn't + /// already been added, and create a mutator for it. + /// + /// If a composer already exists for this `crtc`, this function return= s `Error(EBUSY)`. If + /// attempting to add the state fails, another error code will be retu= rned. + 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_sta= te` - or an error. + let state =3D 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 retur= ns `Error(EBUSY)`. If + /// attempting to add the state fails, another error code will be retu= rned. + 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_st= ate` - or an error. + let state =3D 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 t= his composer if it hasn't + /// already been added, and create a mutator for it. + /// + /// If a composer already exists for this `connector`, this function r= eturns `Error(EBUSY)`. If + /// attempting to add the state fails, another error code will be retu= rned. + 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_st= ate` - or an error. + let state =3D 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.a= s_raw(), crtc.as_raw()) }) + } +} + +/// A macro for declaring the repetitive take_all(), take_state(), etc. me= thods for atomic state +/// token types. +/// +/// It is assumed that $token_name refers to a struct that contains two me= mbers: +/// +/// - `state`: This should be the atomic state type to use +/// - The object in question. The name of this member is generated by conv= erting $obj to lowercase. +/// +/// The struct should have one lifetime ($lifetime_a) declared, and one me= ta-variable ($meta) which +/// should be bound to the Driver* trait for the given mode object. +macro_rules! impl_atomic_state_token_ops { + ( + $token_name:ident, + $state:ident, + $obj:ident, + use <$lifetime_a:lifetime, $meta:ident> + ) =3D> { + kernel::macros::paste! { + /// Create a new token. + /// + /// # Safety + /// + /// To use this function it must be known in the current conte= xt that: + /// + /// - The object has had its atomic states added to `state`. + /// - No state mutator can possibly be taken out for the objec= ts new state. + pub(crate) unsafe fn new( + [<$obj:lower>]: &$lifetime_a $obj<$meta>, + state: &$lifetime_a $state<$meta::Driver>, + ) -> Self { + Self { [<$obj:lower>], state } + } + + #[doc =3D concat!("Get the [`", stringify!($obj), "`] associat= ed with this", + " [`", stringify!($token_name), "`].")] + pub fn [<$obj:lower>](&self) -> &$lifetime_a $obj<$meta> { + self.[<$obj:lower>] + } + + /// Exchange this token for a (atomic_state, old_state, new_st= ate) tuple. + pub fn take_all(self) -> ( + &$lifetime_a $state<$meta::Driver>, + &$lifetime_a [<$obj State>]<$meta::State>, + [<$obj StateMutator>]<$lifetime_a, [<$obj State>]<$meta::S= tate>>, + ) { + let (old_state, new_state) =3D ( + self.state.[](self.[<$obj:= lower>]), + self.state.[](self.[<$obj:= lower>]), + ); + + // SAFETY: + // - Both the old and new object state are present in `sta= te` via our type + // invariants. + // - The new state is guaranteed to have no mutators taken= out via our type + // invariants. + let (old_state, new_state) =3D unsafe { + (old_state.unwrap_unchecked(), new_state.unwrap_unchec= ked()) + }; + + (self.state, old_state, new_state) + } + + #[doc =3D concat!("Exchange this token for the old [`", string= ify!($obj), "State`].")] + pub fn take_old_state(self) -> &$lifetime_a [<$obj State>]<$me= ta::State> { + let old =3D self.state.[](self= .[<$obj:lower>]); + + // SAFETY: The old state is guaranteed to be present in `s= tate` via our type + // invariants. + unsafe { old.unwrap_unchecked() } + } + + #[doc =3D concat!("Exchange this token for the new [`", string= ify!($obj), "State`].")] + pub fn take_new_state( + self + ) -> [<$obj StateMutator>]<$lifetime_a, [<$obj State>]<$meta::= State>> { + let new =3D self.state.[](self= .[<$obj:lower>]); + + // SAFETY: + // - The new state is guaranteed to be present in our `sta= te` via our type + // invariants. + // - The new state is guaranteed not to have any mutators = taken out for it via our + // type invariants. + unsafe { new.unwrap_unchecked() } + } + + #[doc =3D concat!("Exchange this token for both the old and ne= w [`", + stringify!($obj), "State`].")] + pub fn take_old_new_state(self) -> ( + &$lifetime_a [<$obj State>]<$meta::State>, + [<$obj StateMutator>]<$lifetime_a, [<$obj State>]<$meta::S= tate>>, + ) { + let (old_state, new_state) =3D ( + self.state.[](self.[<$obj:= lower>]), + self.state.[](self.[<$obj:= lower>]), + ); + + // SAFETY: + // - Both the old and new object state are present in `sta= te` via our type + // invariants. + // - The new state is guaranteed to have no mutators taken= out via our type + // invariants. + let (old_state, new_state) =3D unsafe { + (old_state.unwrap_unchecked(), new_state.unwrap_unchec= ked()) + }; + + (old_state, new_state) + } + + #[doc =3D concat!("Exchange this token for both the [`", strin= gify!($state), + "`] and the old [`", stringify!($obj), "State`= ].")] + pub fn take_state_old_state(self) -> ( + &$lifetime_a $state<$meta::Driver>, + &$lifetime_a [<$obj State>]<$meta::State>, + ) { + let old =3D self.state.[](self= .[<$obj:lower>]); + + // SAFETY: The old state is guaranteed to be present in `s= tate` via our type + // invariants. + (self.state, unsafe { old.unwrap_unchecked() }) + } + + #[doc =3D concat!("Exchange this token for both the [`", strin= gify!($state), + "`] and the new [`", stringify!($obj), "State`= ].")] + pub fn take_state_new_state(self) -> ( + &$lifetime_a $state<$meta::Driver>, + [<$obj StateMutator>]<$lifetime_a, [<$obj State>]<$meta::S= tate>>, + ) { + let new =3D self.state.[](self= .[<$obj:lower>]); + + // SAFETY: + // - The new state is guaranteed to be present in `state` = via our type + // invariants. + // - The new state is guaranteed to have no mutators taken= out via our type + // invariants. + (self.state, unsafe { new.unwrap_unchecked() }) + } + } + + #[doc =3D concat!("Exchange this token for the [`", stringify!($st= ate), "`].")] + pub fn take_state(self) -> &$lifetime_a $state<$meta::Driver> { + self.state + } + }; +} + +pub(crate) use impl_atomic_state_token_ops; + +/// A token proving that no modesets for a commit have completed. +/// +/// This token is proof that no commits have yet completed, and is provide= d 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_t= ail`] 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 [`EnablesCom= mittedToken`]. +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_t= ail`] 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 with= in an implementor's +/// [`KmsDriver::atomic_commit_tail`] implementation, and that we are read= y 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 [`K= msDriver::atomic_commit_tail`] +/// implementation have completed. It is returned by [`AtomicCommitTail::c= ommit_planes`]. +pub struct PlaneUpdatesCommittedToken<'a>(PhantomData<&'a ()>); + +/// An [`AtomicState`] interface that allows a driver to control the [`ato= mic_commit_tail`] +/// callback. +/// +/// This object is provided as an argument to [`KmsDriver::atomic_commit_t= ail`], and represents an atomic +/// state within the commit tail phase which is still in the process of be= ing committed to hardware. +/// It may be used to control the order in which the commit process happen= s. +/// +/// # 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 ti= mes, and since it is + /// logically unsound to disable an output within an atomic commit aft= er 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 defaul= t commit function does. + /// But drivers with different needs can group the modeset commits tge= ther and do the plane + /// commits at the end. This is useful for drivers doing runtime PM si= nce 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 po= inters + 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 [`AtomicCom= mitTail`]. 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 po= inters + 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 disab= ling 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 mo= desets 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 po= inters + 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 point= er + 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 pointer= s and marks the hardware + /// commit step as completed. Since this step can only happen after al= l plane updates and + /// modesets within an [`AtomicCommitTail`] have been completed, it re= quires both a + /// [`EnablesCommittedToken`] and a [`PlaneUpdatesCommittedToken`] to = consume. After this + /// function is called, the caller no longer has exclusive access to t= he underlying atomic + /// state. As such, this function consumes the [`AtomicCommitTail`] ob= ject and returns a + /// [`CommittedAtomicState`] accessor for performing post-hw commit ta= sks. + 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 imp= ossible for the user to + // mutate the state after this function has been called - which up= holds the safety + // requirements of the C API allowing us to safely call this funct= ion + 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 ins= tance of + // `bindings::drm_atomic_state` + // - This conversion is safe via the type invariants + let state =3D 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::c= ommit_hw_done`]. +/// +/// This object represents an [`AtomicState`] which has been fully committ= ed 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 [`atomi= c_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`] impleme= nter has access to this +/// object. Thus, it cannot be assumed that this object represents the cur= rent hardware state - and +/// instead only represents the final result of the [`AtomicCommitTail`] t= hat was just committed. +/// +/// # Invariants +/// +/// It may be assumed that [`drm_atomic_helper_commit_hw_done`] has been c= alled as long as this type +/// exists. +/// +/// [`atomic_commit_tail`]: KmsDriver::atomic_commit_tail +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_h= elper.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 whic= h 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 plan= es. Drivers are encouraged + /// to set this flag by default, as otherwise they need to ignore plan= e updates for disabled + /// planes by hand. + ActiveOnly =3D (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 c= allbacks for a plane if + /// the [`Crtc`] in the old [`PlaneState`] needs a modesetting operati= on. It is still up to the + /// driver to disable said planes in their [`DriverCrtc::atomic_disabl= e`] callback. + NoDisableAfterModeset =3D (1 << 1), +} + +impl BitOr for PlaneCommitFlag { + type Output =3D PlaneCommitFlags; + + fn bitor(self, rhs: Self) -> Self::Output { + PlaneCommitFlags(self as u32 | rhs as u32) + } +} + +impl BitOr for PlaneCommitFlag { + type Output =3D PlaneCommitFlags; + + fn bitor(self, rhs: PlaneCommitFlags) -> Self::Output { + PlaneCommitFlags(self as u32 | rhs.0) + } +} + +/// A bitmask for controlling the behavior of [`AtomicCommitTail::commit_p= lanes`]. +/// +/// 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 =3D 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 =3D *self | rhs + } +} + +impl BitAnd for PlaneCommitFlags { + type Output =3D 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 =3D *self & rhs + } +} + +impl BitOr for PlaneCommitFlags { + type Output =3D Self; + + fn bitor(self, rhs: PlaneCommitFlag) -> Self::Output { + self | Self::from(rhs) + } +} + +impl BitOrAssign for PlaneCommitFlags { + fn bitor_assign(&mut self, rhs: PlaneCommitFlag) { + *self =3D *self | rhs + } +} + +impl BitAnd for PlaneCommitFlags { + type Output =3D PlaneCommitFlags; + + fn bitand(self, rhs: PlaneCommitFlag) -> Self::Output { + self & Self::from(rhs) + } +} + +impl BitAndAssign for PlaneCommitFlags { + fn bitand_assign(&mut self, rhs: PlaneCommitFlag) { + *self =3D *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 =3D=3D flag.into() + } +} diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connect= or.rs new file mode 100644 index 000000000000..05ec64cf6fa2 --- /dev/null +++ b/rust/kernel/drm/kms/connector.rs @@ -0,0 +1,997 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM display connectors. +//! +//! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_conn= ector.h) + +use super::{ + atomic::*, encoder::*, KmsDriver, ModeConfigGuard, ModeObject, ModeObj= ectVtable, Sealed +}; +use crate::{ + alloc::KBox, + bindings, + drm::{device::Device, kms::{NewKmsDevice, Probing}}, + error::to_result, + prelude::*, + types::{NotThreadSafe, Opaque}, +}; +use core::{ + cell::Cell, + marker::*, + mem::{self, ManuallyDrop}, + ops::*, + ptr::{null_mut, NonNull}, + stringify, +}; +use macros::paste; + +/// A macro for generating our type ID enumerator. +macro_rules! declare_conn_types { + ($( $oldname:ident as $newname:ident ),+) =3D> { + /// 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 =3D concat!("The connector type ID for a ", stringif= y!($newname), " connector.")] + $newname =3D paste!(crate::bindings::[]) as i32 + ),+, + + // 9PinDIN is special because of the 9, making it an invalid i= dent. Just define it here + // manually since it's the only one + + /// The connector type ID for a 9PinDIN connector. + _9PinDin =3D 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, w= hich 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 us= ing a fully typed +/// [`Connector`] object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that p= assed-in +/// [`struct drm_connector`] pointers are contained within a [`Connector= `]. +/// - Any C FFI callbacks generated using this trait are guaranteed that p= assed-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 + const OPS: &'static DriverConnectorOps =3D &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: Some(bindings::drm_helper_probe_single_connector_m= odes), + 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: 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, + }, + }; + + /// The type to pass to the `args` field of [`UnregisteredConnector::n= ew`]. + /// + /// This type will be made available in in the `args` argument of [`Se= lf::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 [`DriverConne= ctor`]. + /// + /// See [`DriverConnectorState`] for more info. + type State: DriverConnectorState; + + /// The constructor for creating a [`Connector`] using this [`DriverCo= nnector`] implementation. + /// + /// Drivers may use this to instantiate their [`DriverConnector`] obje= ct. + fn new(device: &Device, args: Self::Args) -> impl PinIni= t; + + /// 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`]. +/// +/// 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 ad= dition, it also allows +/// immutable access to whatever private data is contained within an imple= mentor's +/// [`DriverConnector`] type. +/// +/// # Invariants +/// +/// - The DRM C API and our interface guarantees that only the user has mu= table 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`] typ= e. +/// - `connector` and `inner` are initialized for as long as this object i= s 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_h= elper.h +#[repr(C)] +#[pin_data] +pub struct Connector { + connector: Opaque, + #[pin] + inner: T, + #[pin] + _p: PhantomPinned, +} + +impl Sealed for Connector {} + +impl Deref for Connector { + type Target =3D T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +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 [`ModeConfi= gGuard`]. + /// + /// 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. +/// +/// # Safety +/// +/// [`as_raw()`] must always return a pointer to a valid initialized [`str= uct 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`] interf= aces that can perform +/// modesets. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// Any object implementing this trait must only be made directly availabl= e 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 initiali= zed in ::new(), so +// `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_o= bject. +unsafe impl ModeObject for Connector { + type Driver =3D T::Driver; + + fn drm_dev(&self) -> &Device { + // SAFETY: The parent device for a DRM connector will never outliv= e the connector, and this + // pointer is invariant through the lifetime of the connector + unsafe { Device::from_raw((*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` i= s initialized + unsafe { &raw mut (*self.as_raw()).base } + } +} + +// Connectors are refcounted objects. +super::impl_aref_for_mode_object! { + impl for Connector +} + +// SAFETY: `funcs` is initialized by DRM when the connector is allocated +unsafe impl ModeObjectVtable for Connector { + type Vtable =3D bindings::drm_connector_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `funcs` is initialized by DRM when the connector is all= ocated + 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 initial= ized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `dr= m_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::c= reate_objects has been +// called. +unsafe impl ModesettableConnector for Connector { + type State =3D 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 id= entical data layout. +pub struct UnregisteredConnector(Connector, NotThre= adSafe); + +// 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 share= s the same safety contract + let connector =3D 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 =3D T; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +impl UnregisteredConnector { + /// Construct a new [`UnregisteredConnector`]. + /// + /// A driver may use this to create new [`UnregisteredConnector`] obje= cts. + /// + /// [`KmsDriver::create_objects`]: kernel::drm::kms::KmsDriver::create= _objects + pub fn new<'a>( + dev: &'a NewKmsDevice<'a, T::Driver, Probing>, + type_: Type, + args: T::Args, + ) -> Result<&'a Self> { + let new: Pin>> =3D 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 ou= tlives 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::O= PS.funcs, type_ as i32) + })?; + + // SAFETY: We don't move anything + let this =3D unsafe { Pin::into_inner_unchecked(new) }; + + // We'll re-assemble the box in connector_destroy_callback() + let this =3D KBox::into_raw(this); + + // UnregisteredConnector has an equivalent data layout + let this: *mut Self =3D this.cast(); + + // 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 typ= e invariants, thus this + // function is safe to call + to_result(unsafe { + bindings::drm_connector_attach_encoder(self.as_raw(), encoder.= as_raw()) + }) + } +} + +/// 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 conn= ector 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, +) { + // SAFETY: DRM guarantees that `connector` points to a valid initializ= ed `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 co= nnector + // - This cast is safe via `DriverConnector`s type invariants. + 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 =3D unsafe { Connector::::from_raw(connector) }; + + // SAFETY: This FFI callback is only called while `mode_config.lock` i= s held + // We use ManuallyDrop here to prevent the lock from being released af= ter the callback + // completes, as that should be handled by DRM. + let guard =3D ManuallyDrop::new(unsafe { ModeConfigGuard::new(connecto= r.drm_dev()) }); + + T::get_modes(connector.guard(&guard), &guard) +} + +/// A [`struct drm_connector`] without a known [`DriverConnector`] impleme= ntation. +/// +/// This is mainly for situations where our bindings can't infer the [`Dri= verConnector`] +/// implementation for a [`struct drm_connector`] automatically. It is ide= ntical 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_con= nector` +// - Since we don't expose `OpaqueConnector` to users before it has been i= nitialized, 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_connecto= r` + unsafe { &*ptr.cast() } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::c= reate_objects has been +// called. +unsafe impl ModesettableConnector for OpaqueConnector { + type State =3D OpaqueConnectorState; +} + +// SAFETY: We don't expose OpaqueConnector to users before `base` is in= itialized 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 =3D T; + + fn drm_dev(&self) -> &Device { + // SAFETY: The parent device for a DRM connector will never outliv= e the connector, and this + // pointer is invariant through the lifetime of the connector + unsafe { Device::from_raw((*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` i= s initialized + unsafe { &mut (*self.as_raw()).base } + } +} + +super::impl_aref_for_mode_object! { + impl for OpaqueConnector +} + +// SAFETY: `funcs` is initialized by DRM when the connector is allocated +unsafe impl ModeObjectVtable for OpaqueConnector { + type Vtable =3D bindings::drm_connector_funcs; + + fn vtable(&self) -> *const Self::Vtable { + // SAFETY: `funcs` is initialized by DRM when the connector is all= ocated + 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 ha= ppen 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 =3D Connector; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +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 giv= en maximum resultion. + /// Returns how many modes were added. + pub fn add_modes_noedid(&self, (max_h, max_v): (u32, u32)) -> i32 { + // SAFETY: We hold the locks required to call this via our type in= variants. + 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): (u32, u32)) { + // SAFETY: We hold the locks required to call this via our type in= variants. + unsafe { bindings::drm_set_preferred_mode(self.as_raw(), h_pref, w= _pref) } + } +} + +/// 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 contai= ned 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_stat= e`] 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 al= so means the mutable + /// reference should never be exposed outside of this crate. + /// + /// [`struct drm_connector_state`]: srctree/include/drm/drm_connec= tor.h + unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_connector_st= ate; + } +} + +pub(super) use private::AsRawConnectorState as AsRawConnectorStatePrivate; + +/// A trait implemented for any type which can be constructed directly fro= m 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 [`struc= t 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 d= rm_connector_state`] + /// pointer. + /// + /// # Safety + /// + /// - The caller guarantees that `ptr` is contained within a valid ins= tance of `Self`. + /// - The caller guarantees that `ptr` cannot have any other reference= s 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; +} + +/// Common methods available on any type which implements [`AsRawConnector= State`]. +/// +/// 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 g= uaranteed by DRM that + // `self.state.connector` points to a valid instance of a `Connect= or` + 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 D= RM connectors. In +/// addition, it allows access to whatever private data is contained withi= n an implementor's +/// [`DriverConnectorState`] type. +/// +/// # Invariants +/// +/// - The DRM C API and our interface guarantees that only the user has mu= table 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`] typ= e. +/// - `state` and `inner` initialized for as long as this object is expose= d 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 t= ype +/// [`Connector`]. +/// +/// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_h= elper.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 p= assed-in +/// [`struct drm_connector`] pointers are contained within a [`Connector= `]. +/// - Any C FFI callbacks generated using this trait are guaranteed that p= assed-in +/// [`struct drm_connector_state`] pointers are contained within a [`Con= nectorState`]. +/// +/// [`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 =3D Connector; +} + +impl private::AsRawConnectorState for ConnectorSt= ate { + 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 =3D ptr.cast(); + + // SAFETY: + // - Our safety contract requires that `ptr` be contained within `= Self`. + // - Our safety contract requires the caller ensure that it is saf= e 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 =3D 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 } + } +} + +// SAFETY: `funcs` is initialized by DRM when the connector is allocated +unsafe impl ModeObjectVtable for ConnectorState { + type Vtable =3D 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 [`DriverConnectorStat= e`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`Dri= verConnectorState`] +/// 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 user= s. +/// - 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 mu= table 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`] typ= e. +/// +/// [`struct drm_connector_state`]: srctree/include/drm/drm_connector.h +/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_h= elper.h +#[repr(transparent)] +pub struct OpaqueConnectorState { + state: bindings::drm_connector_state, + _p: PhantomData, +} + +impl AsRawConnectorState for OpaqueConnectorState { + type Connector =3D 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_connecto= r_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_connecto= r_state` + unsafe { &mut *ptr.cast() } + } +} + +// SAFETY: See OpaqueConnector's ModeObjectVtable implementation +unsafe impl ModeObjectVtable for OpaqueConnectorState { + type Vtable =3D bindings::drm_connector_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.connector().vtable() + } +} + +/// An interface for mutating a [`Connector`]s atomic state. +/// +/// This type is typically returned by an [`AtomicStateMutator`] within co= ntexts 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 atomi= c state. + // - `state` is initialized by the time it is passed to this funct= ion. + // - We're guaranteed that `state` is compatible with `drm_connect= or` by type invariants. + let connector =3D unsafe { T::Connector::from_raw((*state.as_ptr()= ).connector) }; + let conn_mask =3D connector.mask(); + let borrowed_mask =3D mutator.borrowed_connectors.get(); + + if borrowed_mask & conn_mask =3D=3D 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 inva= riance, and we just + // confirmed by checking `borrowed_connectors` that no oth= er 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, Conn= ectorState> { + type Target =3D T; + + fn deref(&self) -> &Self::Target { + &self.state.inner + } +} + +impl<'a, T: DriverConnectorState> DerefMut for ConnectorStateMutator<'a, C= onnectorState> { + 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 =3D self.state.connector().mask(); + self.mask.set(self.mask.get() & !mask); + } +} + +impl<'a, T: FromRawConnectorState> AsRawConnectorState for ConnectorStateM= utator<'a, T> { + type Connector =3D T::Connector; +} + +impl<'a, T: FromRawConnectorState> private::AsRawConnectorState for Connec= torStateMutator<'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 =3D 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>) -> S= elf + 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 { + // SAFETY: DRM guarantees that `connector` points to a valid initializ= ed `drm_connector`. + let state =3D 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 =3D unsafe { ConnectorState::::from_raw(state) }; + + let new: Result> =3D KBox::init( + init!(ConnectorState:: { + inner: state.inner.clone(), + state: bindings::drm_connector_state { + ..Default::default() + }, + }), + GFP_KERNEL, + ); + + if let Ok(mut new) =3D new { + // SAFETY: + // - `new` provides a valid pointer to a newly allocated `drm_plan= e_state` via type + // invariants + // - This initializes `new` via memcpy() + unsafe { + bindings::__drm_atomic_helper_connector_duplicate_state(connec= tor, 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(connect= or_state) }; + + // SAFETY: + // - DRM guarantees we are the only one with access to this `drm_conne= ctor_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 =3D unsafe { (*connector).state }; + if !state.is_null() { + // SAFETY: + // - We're guaranteed `connector` is `Connector` via type invar= iants + // - We're guaranteed `state` is `ConnectorState` via type inva= riants. + unsafe { atomic_destroy_state_callback::(connector, state) } + + // SAFETY: No special requirements here, DRM expects this to be NU= LL + unsafe { (*connector).state =3D 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 =3D KBox::new(ConnectorState::::default(), GFP_KERNEL).expe= ct("Blame the API, sorry!"); + + // DRM takes ownership of the state from here, resets it, and then ass= igns it to the connector + // SAFETY: + // - DRM guarantees that `connector` points to a valid instance of `dr= m_connector`. + // - The cast to `drm_connector_state` is safe via `ConnectorState`s t= ype invariants. + unsafe { bindings::__drm_atomic_helper_connector_reset(connector, Box:= :into_raw(new).cast()) }; +} diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs new file mode 100644 index 000000000000..b9d095854ba6 --- /dev/null +++ b/rust/kernel/drm/kms/crtc.rs @@ -0,0 +1,1110 @@ +// 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::{ + atomic::*, plane::*, vblank::*, KmsDriver, ModeObject, ModeObjectVtabl= e, StaticModeObject, + NewKmsDevice, Probing, Sealed, +}; +use crate::{ + alloc::KBox, + bindings, + drm::device::Device, + error::{from_result, to_result}, + prelude::*, + types::{NotThreadSafe, Opaque}, +}; +use core::{ + cell::{Cell, UnsafeCell}, + marker::*, + mem::{self, ManuallyDrop}, + ops::{Deref, DerefMut}, + ptr::{null, null_mut, NonNull}, +}; +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, w= hich 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 p= assed-in +/// [`struct drm_crtc`] pointers are contained within a [`Crtc`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that p= assed-in +/// [`struct drm_crtc_state`] pointers are contained within a [`CrtcStat= e`]. +/// +/// [`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. + const OPS: &'static DriverCrtcOps =3D &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: ::VBLANK_OPS.d= isable_vblank, + early_unregister: None, + enable_vblank: ::VBLANK_OPS.en= able_vblank, + gamma_set: None, + get_crc_sources: None, + get_vblank_counter: 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, + 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: 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 { + None + }, + dpms: None, + commit: None, + prepare: None, + disable: None, + mode_set: None, + mode_valid: None, + mode_fixup: 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, + 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 [`Se= lf::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 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. + fn new(device: &Device, args: &Self::Args) -> impl PinIn= it; + + /// 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_mo= deset_helper_vtables.h + fn atomic_check(_check: CrtcAtomicCheck<'_, Self>) -> 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 per= formed for the given + /// [`Crtc`]. + /// + /// [`drm_crtc_helper_funcs.atomic_begin`]: srctree/include/drm/drm_mo= deset_helper_vtables.h + fn atomic_begin(_commit: CrtcAtomicCommit<'_, Self>) { + 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 perf= ormed for the given + /// [`Crtc`]. + /// + /// [`drm_crtc_helper_funcs.atomic_flush`]: srctree/include/drm/drm_mo= deset_helper_vtables.h + fn atomic_flush(_commit: CrtcAtomicCommit<'_, Self>) { + 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 c= ommit. + /// + /// [`drm_crtc_helper_funcs.atomic_enable`]: srctree/include/drm/drm_m= odeset_helper_vtables.h + fn atomic_enable(_commit: CrtcAtomicCommit<'_, Self>) { + 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(_commit: CrtcAtomicCommit<'_, Self>) { + build_error::build_error("This should never be reachable") + } +} + +/// 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 additio= n, it also allows +/// immutable access to whatever private data is contained within an imple= mentor's [`DriverCrtc`] +/// type. +/// +/// # Invariants +/// +/// - `crtc` and `inner` are initialized for as long as this object is mad= e 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, +} + +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 =3D T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// SAFETY: We don't expose Crtc to users before `base` is initialized i= n ::new(), so +// `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_o= bject. +unsafe impl ModeObject for Crtc { + type Driver =3D 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::from_raw((*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 initialize= d, so `base` is always + // initialized + unsafe { &raw mut (*self.as_raw()).base } + } +} + +// 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 =3D 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 + } + + 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. +/// +/// 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 identic= al data layout. +pub struct UnregisteredCrtc(Crtc, NotThreadSafe); + +impl UnregisteredCrtc { + /// Construct a new [`UnregisteredCrtc`]. + /// + /// A driver may use this from their [`KmsDriver::create_objects`] cal= lback 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 NewKmsDevice<'a, T::Driver, Probing>, + primary: &'a UnregisteredPlane, + cursor: Option<&'a UnregisteredPlane>, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'a Self> + where + PrimaryData: DriverPlane, + CursorData: DriverPlane, + { + if Crtc::::has_vblank() { + dev.has_vblanks.set(true) + } + + let this: Pin>> =3D 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 pin= ned + // - `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 =3D unsafe { Pin::into_inner_unchecked(this) }; + + // We'll re-assemble the box in crtc_destroy_callback() + let this =3D KBox::into_raw(this); + + // UnregisteredCrtc has an equivalent data layout + let this: *mut Self =3D 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 share= s the same safety contract + let crtc =3D 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 =3D T; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +/// A trait implemented by any type that acts as a [`struct drm_crtc`] int= erface. +/// +/// 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 t= ype. + /// + /// # 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 `dr= m_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 =3D ptr.cast(); + + // SAFETY: Our safety contract requires that `ptr` point to a vali= d intance of `Self`. + unsafe { &*ptr } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::c= reate_objects has been +// called. +unsafe impl ModesettableCrtc for Crtc { + type State =3D 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 availabl= e 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 t= his 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 obj= ects 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 {} + +/// A [`struct drm_crtc`] without a known [`DriverCrtc`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`Dri= verCrtc`] 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 t= o 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 initia= lized, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `dr= m_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::c= reate_objects has been +// called. +unsafe impl ModesettableCrtc for OpaqueCrtc { + type State =3D OpaqueCrtcState; +} + +// SAFETY: We don't expose OpaqueCrtc to users before `base` is initial= ized in Crtc::::new(), +// so `raw_mode_obj` always returns a valid pointer to a bindings::drm_mod= e_object. +unsafe impl ModeObject for OpaqueCrtc { + type Driver =3D T; + + fn drm_dev(&self) -> &Device { + // SAFETY: The parent device for a DRM connector will never outliv= e the connector, and this + // pointer is invariant through the lifetime of the connector + unsafe { Device::from_raw((*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` i= s initialized + unsafe { &raw 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 =3D 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 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 p= assed-in +/// [`struct drm_crtc`] pointers are contained within a [`Crtc`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that p= assed-in +/// [`struct drm_crtc_state`] pointers are contained within a [`CrtcStat= e`]. +/// +/// [`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 D= RM crtcs. In addition, it +/// allows access to whatever private data is contained within an implemen= tor's [`DriverCrtcState`] +/// type. +/// +/// # Invariants +/// +/// - `state` and `inner` initialized for as long as this object is expose= d to users. +/// - The data layout of this structure begins with [`struct drm_crtc_stat= e`]. +/// - 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<>`=E2=80=A6 + state: Opaque, + + // =E2=80=A6it 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 C= rtcState. In preparation + // for that possibility, we keep `T` in an UnsafeCell. + inner: UnsafeCell, +} + +impl Deref for CrtcState { + type Target =3D 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() + } +} + +// SAFETY: Shares the safety guarantee of Crtc's ModeObjectVtable impl +unsafe impl ModeObjectVtable for CrtcState { + type Vtable =3D 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 [`s= truct 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 p= arent 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 d= on't nicely follow rust's + /// data aliasing rules already. + fn as_raw(&self) -> *mut bindings::drm_crtc_state; + } +} + +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 +/// the atomic state of [`Crtc`]s. +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-al= iasing rules + // - `crtc` is guaranteed to never be NULL and is invariant throug= hout the lifetime of the + // 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 on= ly 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 {} + +/// A trait implemented for any type which can be constructed directly fro= m 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 poi= nter + /// + /// # Safety + /// + /// Callers must ensure that ptr contains a valid instance of this typ= e. + unsafe fn from_raw<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Se= lf; +} + +impl private::AsRawCrtcState for CrtcState { + #[inline] + fn as_raw(&self) -> *mut bindings::drm_crtc_state { + self.state.get() + } +} + +impl AsRawCrtcState for CrtcState { + type Crtc =3D Crtc; +} + +impl FromRawCrtcState for CrtcState { + unsafe fn from_raw<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Se= lf { + // SAFETY: Our data layout starts with `bindings::drm_crtc_state` + unsafe { &*(ptr.cast()) } + } +} + +/// A [`struct drm_crtc_state`] without a known [`DriverCrtcState`] implem= entation. +/// +/// This is mainly for situations where our bindings can't infer the [`Dri= verCrtcState`] +/// implementation for a [`struct drm_crtc_state`] automatically. It is id= entical 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 user= s. +/// - 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 =3D 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 Se= lf { + // SAFETY: Our data layout is identical to `bindings::drm_crtc_sta= te` + unsafe { &*(ptr.cast()) } + } +} + +// SAFETY: Shares the safety guarantees of OpaqueCrtc's ModeObjectVtabl= e impl +unsafe impl ModeObjectVtable for OpaqueCrtcState { + type Vtable =3D bindings::drm_crtc_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.crtc().vtable() + } +} + +/// An interface for mutating a [`Crtc`]s atomic state. +/// +/// This type is typically returned by an [`AtomicStateMutator`] within co= ntexts where it is +/// possible to safely mutate a plane's state. In order to uphold rust's d= ata-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 atom= ic state, and always + // points to a valid `Crtc` + let crtc =3D unsafe { T::Crtc::from_raw((*state.as_ptr()).crtc) }; + let crtc_mask =3D crtc.mask(); + let borrowed_mask =3D mutator.borrowed_crtcs.get(); + + if borrowed_mask & crtc_mask =3D=3D 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 =3D 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 =3D T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Our interface ensures that `self.state.inner` follows r= ust'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 r= ust'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 =3D 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 =3D ::Vtable; + + fn vtable(&self) -> *const Self::Vtable { + self.crtc().vtable() + } +} + +/// A token provided during [`atomic_check`] callbacks for accessing the c= rtc, atomic state, and new +/// and old states of the crtc. +/// +/// # Invariants +/// +/// This token is proof that the old and new atomic state of `crtc` are pr= esent in `state` and do +/// not have any mutators taken out. +/// +/// [`atomic_check`]: DriverCrtc::atomic_check +pub struct CrtcAtomicCheck<'a, T: DriverCrtc> { + state: &'a AtomicStateComposer, + crtc: &'a Crtc, +} + +impl<'a, T: DriverCrtc> CrtcAtomicCheck<'a, T> { + impl_atomic_state_token_ops!( + CrtcAtomicCheck, + AtomicStateComposer, + Crtc, + use <'a, T> + ); +} + +/// A token provided to [`DriverCrtc`] callbacks during the atomic commit = phase for accessing the +/// crtc, atomic state, new and old states of the crtc. +/// +/// # Invariants +/// +/// This token is proof that the old and new atomic state of `crtc` are pr= esent in `state` and do +/// not have any mutators taken out. +pub struct CrtcAtomicCommit<'a, T: DriverCrtc> { + state: &'a AtomicStateMutator, + crtc: &'a Crtc, +} + +impl<'a, T: DriverCrtc> CrtcAtomicCommit<'a, T> { + impl_atomic_state_token_ops!( + CrtcAtomicCommit, + AtomicStateMutator, + Crtc, + use <'a, T> + ); +} + +unsafe extern "C" fn crtc_destroy_callback(crtc: *mut bindi= ngs::drm_crtc) { + // SAFETY: DRM guarantees that `crtc` points to a valid initialized `d= rm_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 `d= rm_crtc`. + let state =3D unsafe { (*crtc).state }; + if state.is_null() { + return null_mut(); + } + + // SAFETY: This cast is safe via `DriverCrtcState`s type invariants. + let crtc =3D unsafe { Crtc::::from_raw(crtc) }; + + // SAFETY: This cast is safe via `DriverCrtcState`s type invariants. + let state =3D unsafe { CrtcState::::from_raw(state) }; + + let new: Result> =3D KBox::try_init( + try_init!(CrtcState { + inner: UnsafeCell::new((*state).clone()), + state: Opaque::new(Default::default()), + }), + GFP_KERNEL, + ); + + if let Ok(new) =3D new { + let new =3D 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.a= s_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 bi= ndings::drm_crtc) { + // SAFETY: DRM guarantees that `state` points to a valid instance of `= drm_crtc_state` + let state =3D 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 invariant= s. + unsafe { atomic_destroy_state_callback::(crtc, state) } + + // SAFETY: No special requirements here, DRM expects this to be NU= LL + unsafe { + (*crtc).state =3D null_mut(); + } + } + + // SAFETY: `crtc` is guaranteed to be of type `Crtc` by type = invariance + let crtc =3D 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> =3D 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()) }; +} + +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 initia= lized state. + let crtc =3D 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 =3D + unsafe { ManuallyDrop::new(AtomicStateComposer::new(NonNull::new_u= nchecked(state))) }; + + // SAFETY: + // - Since we're in the atomic check callback, we're guaranteed by DRM= that both the old and + // new crtc state are present in this atomic state + // - We just created the state composer above, so other composers cann= ot be taken out on the + // crtc state yet. + let check =3D unsafe { CrtcAtomicCheck::new(crtc, &state) }; + + from_result(|| { + T::atomic_check(check)?; + 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 initia= lized state. + let crtc =3D unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance of `drm_atomic_state` + let state =3D unsafe { AtomicStateMutator::new(NonNull::new_unchecked(= state)) }; + + // SAFETY: + // - Since we're in the atomic_begin callback, we're guaranteed by DRM= that both the old and new + // crtc state are resent in this atomic state. + // - We just created the state mutator above, so other mutators cannot= be taken out on the crtc + // state yet. + let commit =3D unsafe { CrtcAtomicCommit::new(crtc, &state) }; + + T::atomic_begin(commit); +} + +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 initia= lized state. + let crtc =3D unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance of `drm_atomic_state` + let state =3D unsafe { AtomicStateMutator::new(NonNull::new_unchecked(= state)) }; + + // SAFETY: + // - Since we're in the atomic_flush callback, we're guaranteed by DRM= that both the old and new + // crtc state are resent in this atomic state. + // - We just created the state mutator above, so other mutators cannot= be taken out on the crtc + // state yet. + let commit =3D unsafe { CrtcAtomicCommit::new(crtc, &state) }; + + T::atomic_flush(commit); +} + +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 initia= lized state. + let crtc =3D unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: DRM never passes an invalid ptr for `state` + let state =3D unsafe { AtomicStateMutator::new(NonNull::new_unchecked(= state)) }; + + // SAFETY: + // - Since we're in the atomic_enable callback, we're guaranteed by DR= M that both the old and + // new crtc state are present in this atomic state. + // - We just created the state mutator above, so other mutators cannot= be taken out on the crtc + // state yet. + let commit =3D unsafe { CrtcAtomicCommit::new(crtc, &state) }; + + T::atomic_enable(commit); +} + +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 `DriverPlan= e`s type invariants. + let crtc =3D unsafe { Crtc::from_raw(crtc) }; + + // SAFETY: We're guaranteed that `state` points to a valid `drm_crtc_s= tate` by DRM + let state =3D unsafe { AtomicStateMutator::new(NonNull::new_unchecked(= state)) }; + + // SAFETY: + // - Since we're in the atomic_disable callback, we're guaranteed by D= RM that both the old and + // new crtc state are present in this atomic state. + // - We just created the state mutator above, so other mutators cannot= be taken out on the crtc + // state yet. + let commit =3D unsafe { CrtcAtomicCommit::new(crtc, &state) }; + + T::atomic_disable(commit); +} diff --git a/rust/kernel/drm/kms/encoder.rs b/rust/kernel/drm/kms/encoder.rs new file mode 100644 index 000000000000..5f860faf8b61 --- /dev/null +++ b/rust/kernel/drm/kms/encoder.rs @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM encoders. +//! +//! C header: [`include/drm/drm_encoder.h`](srctree/include/drm/drm_encode= r.h) + +use super::{ + KmsDriver, ModeObject, ModeObjectVtable, NewKmsDevice, Probing, Static= ModeObject, Sealed +}; +use crate::{ + alloc::KBox, + drm::device::Device, + error::to_result, + prelude::*, + types::{NotThreadSafe, Opaque}, +}; +use bindings; +use core::{ + marker::*, + mem, + ops::Deref, + ptr::null, +}; +use macros::paste; + +/// A macro for generating our type ID enumerator. +macro_rules! declare_encoder_types { + ($( $oldname:ident as $newname:ident ),+) =3D> { + #[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 =3D concat!("The encoder type ID for a ", stringify!= ($newname), " encoder.")] + $newname =3D 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 [`E= ncoder`]. +/// +/// Any KMS driver should have at least one implementation of this type, w= hich allows them to create +/// [`Encoder`] objects. Additionally, a driver may store driver-private d= ata within the type that +/// implements [`DriverEncoder`] - and it will be made available when usin= g a fully typed +/// [`Encoder`] object. +/// +/// # Invariants +/// +/// - Any C FFI callbacks generated using this trait are guaranteed that p= assed-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. + const OPS: &'static DriverEncoderOps =3D &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 [`Se= lf::new`]. Drivers which + /// don't need this can simply pass [`()`] here. + type Args; + + /// The constructor for creating a [`Encoder`] using this [`DriverEnco= der`] implementation. + /// + /// Drivers may use this to instantiate their [`DriverEncoder`] object. + fn new(device: &Device, args: Self::Args) -> impl PinIni= t; +} + +/// 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_enco= der`]. +/// +/// [`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 th= is 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 addi= tion, it also allows +/// immutable access to whatever private data is contained within an imple= mentor'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: 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 initialize= d in ::new(), so +// `raw_mode_obj` always returns a valid pointer to a bindings::drm_mode_o= bject. +unsafe impl ModeObject for Encoder { + type Driver =3D T::Driver; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM encoders exist for as long as the device does, so t= his pointer is always + // valid + unsafe { Device::from_raw((*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 initial= ized, so `base` is always + // initialized + unsafe { &raw mut (*self.encoder.get()).base } + } +} + +// SAFETY: Encoders do not have a refcount +unsafe impl StaticModeObject for Encoder {} + +impl Deref for Encoder { + type Target =3D 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 initialize= d, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `dr= m_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_encode= r` + unsafe { &*ptr.cast() } + } +} + +// SAFETY: `funcs` is initialized when the encoder is allocated +unsafe impl ModeObjectVtable for Encoder { + type Vtable =3D 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. +/// +/// # Invariants +/// +/// - This object can only exist before its respective KMS device has been= registered. +/// - Otherwise, it inherits all invariants of [`Encoder`] and has an iden= tical 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 share= s the same safety contract + let encoder =3D 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 =3D 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`] cal= lback in order to + /// construct new [`UnregisteredEncoder`] objects. + /// + /// [`KmsDriver::create_objects`]: kernel::drm::kms::KmsDriver::create= _objects + pub fn new<'a, 'b: 'a>( + dev: &'a NewKmsDevice<'a, T::Driver, Probing>, + type_: Type, + possible_crtcs: u32, + possible_clones: u32, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'b Self> { + let this: Pin>> =3D 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 outl= ives 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 passe= d 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 =3D unsafe { Pin::into_inner_unchecked(this) }; + + // We'll re-assemble the box in encoder_destroy_callback() + let this =3D KBox::into_raw(this); + + // UnregisteredEncoder has an equivalent data layout + let this: *mut Self =3D this.cast(); + + // SAFETY: We just allocated the encoder above, so this pointer mu= st be valid + Ok(unsafe { &*this }) + } +} + +/// A [`struct drm_encoder`] without a known [`DriverEncoder`] implementat= ion. +/// +/// This is mainly for situations where our bindings can't infer the [`Dri= verEncoder`] implementation +/// for a [`struct drm_encoder`] automatically. It is identical to [`Encod= er`], 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 init= ialized in +// OpaqueEncoder::new(), so `raw_mode_obj` always returns a valid poiner t= o a +// bindings::drm_mode_object. +unsafe impl ModeObject for OpaqueEncoder { + type Driver =3D T; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM encoders exist for as long as the device does, so t= his pointer is always + // valid + unsafe { Device::from_raw((*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 initial= ized, so `base` is always + // initialized + unsafe { &raw 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_enco= der` +// - Since we don't expose `Encoder` to users before it has been initializ= ed, this and our data +// layout ensure that `as_raw()` always returns a valid pointer to a `dr= m_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 =3D 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, +) { + // 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)) }; +} diff --git a/rust/kernel/drm/kms/framebuffer.rs b/rust/kernel/drm/kms/frame= buffer.rs new file mode 100644 index 000000000000..54d0391388a9 --- /dev/null +++ b/rust/kernel/drm/kms/framebuffer.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM framebuffers. +//! +//! C header: [`include/drm/drm_framebuffer.h`](srctree/include/drm/drm_fr= amebuffer.h) + +use super::{KmsDriver, ModeObject, Sealed}; +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 use= rs. +/// - 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, Ph= antomData); + +// 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 =3D 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::from_raw((*self.0.get()).dev) } + } + + fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object { + // SAFETY: We don't expose Framebuffer to users before its init= ialized, so `base` is + // always initialized + unsafe { &raw mut (*self.0.get()).base } + } +} + +// SAFETY: References to framebuffers are safe to be accessed from any thr= ead +unsafe impl Send for Framebuffer {} +// SAFETY: References to framebuffers are safe to be accessed from any thr= ead +unsafe impl Sync for Framebuffer {} + +// For implementing ModeObject +impl 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 [`Frame= buffer`] + /// + /// # Safety + /// + /// The caller guarantews that `ptr` points to a initialized `struct d= rm_framebuffer` for at + /// least the entire lifetime of `'a`. + #[inline] + pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::drm_framebuffe= r) -> &'a Self { + // SAFETY: Our data layout is identical to drm_framebuffer + unsafe { &*ptr.cast() } + } +} diff --git a/rust/kernel/drm/kms/modes.rs b/rust/kernel/drm/kms/modes.rs new file mode 100644 index 000000000000..0e8dc434487d --- /dev/null +++ b/rust/kernel/drm/kms/modes.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +use bindings; + +use crate::types::Opaque; + +/// DRM kernel-internal display mode structure. +/// +/// This structure contains various resolution and timing information for = a given display mode in +/// DRM. +/// +/// # Invariants +/// +/// - The data layout of this structure is guaranteed to be equivalent to = that of `struct +/// drm_display_mode`. +/// - We ensure through our bindings that rust's data aliasing rules are m= aintained, ensuring it is +/// safe to read any fields inside of `self.inner`. +#[repr(transparent)] +pub struct DisplayMode { + inner: Opaque, +} + +// SAFETY: Our bindings are thread-safe via our type invariants. +unsafe impl Send for DisplayMode {} +// SAFETY: Our bindings are thread-safe via our type invariants. +unsafe impl Sync for DisplayMode {} + +impl DisplayMode { + /// Convert a raw pointer to a `struct drm_display_mode` into an immut= able [`DisplayMode`] ref. + /// + /// # SAFETY + /// + /// - The caller guarantees that `self_ptr` points to a valid initiali= zed `struct + /// drm_display_mode`. + /// - The caller must ensure that rust's data aliasing rules will not = be broken for the lifetime + /// of `'a`, e.g. no mutable references may exist while immutable re= ferences exist to Self. + #[inline] + pub(crate) unsafe fn as_ref<'a>(self_ptr: *const bindings::drm_display= _mode) -> &'a Self { + // SAFETY: The pointer is valid via our safety contract, and the d= ata layout of this struct + // is equivalent to `Self` via our type invariants. + unsafe { &*self_ptr.cast() } + } + + /// Return a raw pointer to the `struct drm_display_mode` contained wi= thin this [`DisplayMode`]. + #[inline] + pub(crate) fn as_raw(&self) -> *const bindings::drm_display_mode { + self.inner.get().cast_const() + } + + /// Retrieve the pixel clock for the adjusted display mode in kHz. + #[inline] + pub fn crtc_clock(&self) -> i32 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).crtc_clock } + } + + /// Retrieve the start of the vertical sync period for the adjusted di= splay mode. + #[inline] + pub fn crtc_vblank_start(&self) -> u16 { + unsafe { (*self.as_raw()).crtc_vblank_start } + } + + /// Retrieve the end of the vertical sync period for the adjusted disp= lay mode. + #[inline] + pub fn crtc_vblank_end(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).crtc_vblank_end } + } + + /// Retrieve the number of vertical scanlines for a full scanout frame= in this adjusted display + /// mode. + #[inline] + pub fn crtc_vtotal(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).crtc_vtotal } + } +} diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs new file mode 100644 index 000000000000..661d82273099 --- /dev/null +++ b/rust/kernel/drm/kms/plane.rs @@ -0,0 +1,1095 @@ +// 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::{ + atomic::*, crtc::*, framebuffer::*, KmsDriver, ModeObject, ModeObjectV= table, StaticModeObject, + NewKmsDevice, Probing, Sealed +}; +use crate::{ + alloc::KBox, + bindings, + drm::{device::Device, fourcc::*}, + error::{from_result, to_result, Error}, + prelude::*, + types::{NotThreadSafe, Opaque}, +}; +use core::{ + cell::Cell, + marker::*, + mem::{self, ManuallyDrop}, + ops::*, + pin::Pin, + ptr::{null, null_mut, NonNull}, +}; + +/// The main trait for implementing the [`struct drm_plane`] API for [`Pla= ne`]. +/// +/// Any KMS driver should have at least one implementation of this type, w= hich allows them to create +/// [`Plane`] objects. Additionally, a driver may store driver-private dat= a 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 p= assed-in +/// [`struct drm_plane`] pointers are contained within a [`Plane`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that p= assed-in +/// [`struct drm_plane_state`] pointers are contained within a [`PlaneSt= ate`]. +/// +/// [`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. + const OPS: &'static DriverPlaneOps =3D &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, + format_mod_supported_async: None, + }, + + helper_funcs: bindings::drm_plane_helper_funcs { + prepare_fb: None, + cleanup_fb: None, + begin_fb_access: None, + end_fb_access: 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 { + 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 [`Se= lf::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 PinIni= t; + + /// The optional [`drm_plane_helper_funcs.atomic_update`] hook for thi= s 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(_commit: PlaneAtomicCommit<'_, Self>) { + 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_m= odeset_helper_vtables.h + fn atomic_check(_check: PlaneAtomicCheck<'_, Self>) -> Result { + build_error::build_error("This should not be reachable") + } +} + +/// 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 =3D bindings::drm_plane_type_DRM_PLANE_TYPE_OVERLAY, + + /// A primary plane attached to a CRTC that is the most likely to be a= ble to light up the CRTC + /// when no scaling/cropping is used, and the plane covers the whole C= RTC. + Primary =3D bindings::drm_plane_type_DRM_PLANE_TYPE_PRIMARY, + + /// A cursor plane attached to a CRTC that is more likely to be enable= d when no scaling/cropping + /// is used, and the framebuffer has the size indicated by [`ModeConfi= gInfo::max_cursor`]. + /// + /// [`ModeConfigInfo::max_cursor`]: crate::drm::kms::ModeConfigInfo + Cursor =3D 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 additi= on, it also allows +/// immutable access to whatever private data is contained within an imple= mentor's [`DriverPlane`] +/// type. +/// +/// # Invariants +/// +/// - `plane` and `inner` are initialized for as long as this object is ma= de 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, +} + +impl Sealed for Plane {} + +impl Deref for Plane { + type Target =3D T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// SAFETY: `funcs` is initialized when the plane is allocated +unsafe impl ModeObjectVtable for Plane { + type Vtable =3D 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. +/// +/// # Invariants +/// +/// - This object can only exist before its respective KMS device has been= registered. +/// - Otherwise, it inherits all invariants of [`Plane`] and has an identi= cal 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 share= s the same safety contract + let plane =3D 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 =3D 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`] cal= lback in order to + /// construct new [`UnregisteredPlane`] objects. + /// + /// [`KmsDriver::create_objects`]: kernel::drm::kms::KmsDriver::create= _objects + pub fn new<'a, 'b: 'a>( + dev: &'a NewKmsDevice<'a, T::Driver, Probing>, + possible_crtcs: u32, + formats: &[u32], + format_modifiers: Option<&[u64]>, + type_: Type, + name: Option<&CStr>, + args: T::Args, + ) -> Result<&'b Self> { + let this: Pin>> =3D 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) =3D format_modifiers { + let mut raw =3D KVec::with_capacity(modifiers.len() + 1, GFP_K= ERNEL)?; + for modifier in modifiers { + raw.push(*modifier, GFP_KERNEL)?; + } + raw.push(FORMAT_MOD_INVALID, GFP_KERNEL)?; + + format_modifiers_raw =3D Some(raw); + } else { + format_modifiers_raw =3D 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 pin= ned + // - We just allocated the `format_modifiers_raw` vec and added th= e sentinel DRM expects + // above + // - `drm_universal_plane_init` will memcpy() the following parame= ters 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 invari= ants. + 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 =3D unsafe { Pin::into_inner_unchecked(this) }; + + // We'll re-assemble the box in plane_destroy_callback() + let this =3D KBox::into_raw(this); + + // UnregisteredPlane has an equivalent data layout + let this: *mut Self =3D 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`] in= terface. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// [`as_raw()`] must always return a valid pointer to an initialized [`st= ruct 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 `dr= m_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 =3D ptr.cast(); + + // SAFETY: Our safety contract requires that `ptr` point to a vali= d intance of `Self`. + unsafe { &*ptr } + } +} + +// SAFETY: We only expose this object to users directly after KmsDriver::c= reate_objects has been +// called. +unsafe impl ModesettablePlane for Plane { + type State =3D 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_o= bject. +unsafe impl ModeObject for Plane { + type Driver =3D T::Driver; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM planes exist for as long as the device does, so thi= s pointer is always valid + unsafe { Device::from_raw((*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 in= itialized + unsafe { &raw 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 tha= t can perform modesets. +/// +/// This is implemented internally by DRM. +/// +/// # Safety +/// +/// Any object implementing this trait must only be made directly availabl= e 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; +} + +/// 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 user= s, 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 [`struct drm_plane`] without a known [`DriverPlane`] implementation. +/// +/// This is mainly for situations where our bindings can't infer the [`Dri= verPlane`] 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_plan= e`]. +/// +/// [`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 `dr= m_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::c= reate_objects has been +// called. +unsafe impl ModesettablePlane for OpaquePlane { + type State =3D OpaquePlaneState; +} + +// SAFETY: We don't expose OpaquePlane to users before `base` is initia= lized 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 =3D T; + + fn drm_dev(&self) -> &Device { + // SAFETY: DRM planes exist for as long as the device does, so thi= s pointer is always valid + unsafe { Device::from_raw((*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 in= itialized + unsafe { &raw 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 =3D 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 [`s= truct 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 sta= tic - like the backpointer + /// to the DRM plane that owns this state. This also means the mut= able 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 fro= m 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 [`struc= t 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 S= elf; + + /// Get a mutable reference to this type from the given raw [`struct d= rm_plane_state`] pointer. + /// + /// # Safety + /// + /// - The caller guarantees that `ptr` is contained within a valid ins= tance of `Self` + /// - The caller guarantees that `ptr` cannot have any other reference= s 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; +} + +/// 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 ob= jects to users, and is + // 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 invariant= s. + 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 stat= e. + 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_m= ut()` 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 paramete= rs for scaling + bindings::DRM_PLANE_NO_SCALING as _, + can_position, + can_update_disabled, + ) + }) + } + + /// 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 {} + +/// The main interface for a [`struct drm_plane_state`]. +/// +/// This type is the main interface for dealing with the atomic state of D= RM planes. In addition, it +/// allows access to whatever private data is contained within an implemen= tor's [`DriverPlaneState`] +/// type. +/// +/// # Invariants +/// +/// - The DRM C API and our interface guarantees that only the user has mu= table 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`] typ= e. +/// - `state` and `inner` initialized for as long as this object is expose= d to users. +/// - The data layout of this structure begins with [`struct drm_plane_sta= te`]. +/// - 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_h= elper.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 p= assed-in +/// [`struct drm_plane`] pointers are contained within a [`Plane`]. +/// - Any C FFI callbacks generated using this trait are guaranteed that p= assed-in +/// [`struct drm_plane_state`] pointers are contained within a [`PlaneSt= ate`]. +/// +/// [`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 =3D 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 S= elf { + // Our data layout starts with `bindings::drm_plane_state`. + let ptr: *const Self =3D ptr.cast(); + + // SAFETY: + // - Our safety contract requires that `ptr` be contained within `= Self`. + // - Our safety contract requires the caller ensure that it is saf= e 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 =3D 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 } + } +} + +impl Deref for PlaneState { + type Target =3D T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for PlaneState { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +// SAFETY: Shares the safety guarantee of Plane's vtable impl +unsafe impl ModeObjectVtable for PlaneState { + type Vtable =3D 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`] impl= ementation. +/// +/// This is mainly for situations where our bindings can't infer the [`Dri= verPlaneState`] +/// implementation for a [`struct drm_plane_state`] automatically. It is i= dentical 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 mu= table 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`] typ= e. +/// - `state` is initialized for as long as this object is exposed to user= s. +/// - 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_h= elper.h +#[repr(transparent)] +pub struct OpaquePlaneState { + state: bindings::drm_plane_state, + _p: PhantomData, +} + +impl AsRawPlaneState for OpaquePlaneState { + type Plane =3D 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 S= elf { + // 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 =3D bindings::drm_plane_funcs; + + fn vtable(&self) -> *const Self::Vtable { + self.plane().vtable() + } +} + +/// An interface for mutating a [`Plane`]s atomic state. +/// +/// This type is typically returned by an [`AtomicStateMutator`] within co= ntexts where it is +/// possible to safely mutate a plane's state. In order to uphold rust's d= ata-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 ato= mic state, is + // initialized by this point, and we're guaranteed it is of type `= AsRawPlane` by type + // invariance + let plane =3D unsafe { T::Plane::from_raw((*state.as_ptr()).plane)= }; + let plane_mask =3D plane.mask(); + let borrowed_mask =3D mutator.borrowed_planes.get(); + + if borrowed_mask & plane_mask =3D=3D 0 { + mutator.borrowed_planes.set(borrowed_mask | plane_mask); + Some(Self { + mask: &mutator.borrowed_planes, + // SAFETY: We're guaranteed `state` is of `FromRawPlaneSta= te` by type invariance, + // and we just confirmed by checking `borrowed_planes` tha= t 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 =3D self.state.plane().mask(); + self.mask.set(self.mask.get() & !mask); + } +} + +impl<'a, T: FromRawPlaneState> AsRawPlaneState for PlaneStateMutator<'a, T= > { + type Plane =3D T::Plane; +} + +impl<'a, T: FromRawPlaneState> private::AsRawPlaneState for PlaneStateMuta= tor<'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 =3D T; + + fn deref(&self) -> &Self::Target { + &self.state.inner + } +} + +impl<'a, T: DriverPlaneState> DerefMut for PlaneStateMutator<'a, PlaneStat= e> { + 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 PlaneStateMutat= or<'a, T> +where + T: FromRawPlaneState + ModeObjectVtable, +{ + type Vtable =3D 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 + } +} + +/// A token provided during [`atomic_check`] callbacks for accessing the p= lane, atomic state, and +/// the old and new states of the plane. +/// +/// [`atomic_check`]: DriverPlane::atomic_check +pub struct PlaneAtomicCheck<'a, T: DriverPlane> { + state: &'a AtomicStateComposer, + plane: &'a Plane, +} + +impl<'a, T: DriverPlane> PlaneAtomicCheck<'a, T> { + impl_atomic_state_token_ops!( + PlaneAtomicCheck, + AtomicStateComposer, + Plane, + use <'a, T> + ); +} + +/// A token provided to [`DriverPlane`] callbacks during the atomic commit= phase for accessing the +/// plane, atomic state, new and old states of the plane. +/// +/// # Invariants +/// +/// This token is proof that the old and new atomic state of `plane` are p= resent in `state` and do +/// not have any mutators taken out. +pub struct PlaneAtomicCommit<'a, T: DriverPlane> { + state: &'a AtomicStateMutator, + plane: &'a Plane, +} + +impl<'a, T: DriverPlane> PlaneAtomicCommit<'a, T> { + impl_atomic_state_token_ops!( + PlaneAtomicCommit, + AtomicStateMutator, + Plane, + use <'a, T> + ); +} + +unsafe extern "C" fn plane_destroy_callback(plane: *mut bi= ndings::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 =3D unsafe { (*plane).state }; + if state.is_null() { + return null_mut(); + } + + // SAFETY: This cast is safe via `DriverPlaneState`s type invariants. + let state =3D unsafe { PlaneState::::from_raw(state) }; + + let new: Result> =3D KBox::try_init( + try_init!(PlaneState { + inner: state.inner.clone(), + state: bindings::drm_plane_state { + ..Default::default() + }, + }), + GFP_KERNEL, + ); + + if let Ok(mut new) =3D new { + // SAFETY: + // - `new` provides a valid pointer to a newly allocated `drm_plan= e_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 bind= ings::drm_plane) { + // SAFETY: DRM guarantees that `state` points to a valid instance of `= drm_plane_state` + let state =3D 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 invarian= ts. + unsafe { atomic_destroy_state_callback::(plane, state) } + + // SAFETY: No special requirements here, DRM expects this to be NU= LL + unsafe { + (*plane).state =3D 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 =3D + KBox::new(PlaneState::::default(), GFP_KERNEL).expect("B= lame the API, sorry!"); + + // DRM takes ownership of the state from here, resets it, and then ass= igns it to the plane + // SAFETY: + // - DRM guarantees that `plane` points to a valid instance of `drm_pl= ane`. + // - The cast to `drm_plane_state` is safe via `PlaneState`s type inva= riants. + unsafe { bindings::__drm_atomic_helper_plane_reset(plane, KBox::into_r= aw(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 invariant= s. + // - We're guaranteed by DRM that `plane` is pointing to a valid initi= alized state. + let plane =3D unsafe { Plane::from_raw(plane) }; + + // SAFETY: DRM guarantees `state` points to a valid `drm_atomic_state` + let state =3D unsafe { AtomicStateMutator::new(NonNull::new_unchecked(= state)) }; + + // SAFETY: + // - Since we're in the atomic_update callback, we're guaranteed by DR= M that both the old and new + // plane state are resent in this atomic state. + // - We just created the state mutator above, so other mutators cannot= be taken out on the plane + // state yet. + let commit =3D unsafe { PlaneAtomicCommit::new(plane, &state) }; + + T::atomic_update(commit); +} + +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 invariant= s. + // - We're guaranteed by DRM that `plane` is pointing to a valid initi= alized state. + let plane =3D unsafe { Plane::from_raw(plane) }; + + // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance 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 =3D ManuallyDrop::new(unsafe { + AtomicStateComposer::::new(NonNull::new_unchecked(state= )) + }); + + // SAFETY: + // - Since we're in the atomic check callback, we're guaranteed by DRM= that both the old and + // new plane state are present in this atomic state + // - We just created the state composer above, so other composers cann= ot be taken out on the + // plane state yet. + let check =3D unsafe { PlaneAtomicCheck::new(plane, &state) }; + + from_result(|| T::atomic_check(check).map(|_| 0)) +} diff --git a/rust/kernel/drm/kms/vblank.rs b/rust/kernel/drm/kms/vblank.rs new file mode 100644 index 000000000000..dc34e02e8ccb --- /dev/null +++ b/rust/kernel/drm/kms/vblank.rs @@ -0,0 +1,461 @@ +// 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, modes::*, Sealed}; +use bindings; +use core::{ + marker::*, + mem::{self, ManuallyDrop}, + ops::{Deref, Drop}, + ptr::null_mut, +}; +use kernel::{ + drm::device::Device, + error::{from_result, to_result}, + interrupt::LocalInterruptDisabled, + prelude::*, + time::Delta, + 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 inpu= t 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 interrup= t 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. Impleme= nted internally. +/// +/// Drivers interested in implementing vblank support should refer to [`Vb= lankSupport`], 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 =3D T::Crtc; + + const VBLANK_OPS: VblankOps =3D 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 =3D T; + + const VBLANK_OPS: VblankOps =3D 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 =3D unsafe { Crtc::::from_raw(crtc) }; + + // SAFETY: This callback happens with IRQs disabled + let irq =3D 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 =3D 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 =3D unsafe { Crtc::::from_raw(crtc) }; + + // SAFETY: This callback happens with IRQs disabled + let irq =3D 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 =3D 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 =3D unsafe { Crtc::::from_raw(crtc) }; + + if let Some(timestamp) =3D T::get_vblank_timestamp(crtc, in_vblank_irq= ) { + // SAFETY: Both of these pointers are guaranteed by the C API to b= e valid + unsafe { + (*max_error) =3D timestamp.max_error; + (*vblank_time) =3D timestamp.time.as_nanos(); + }; + + true + } else { + false + } +} + +/// A vblank timestamp. +/// +/// This type is used by [`VblankSupport::get_vblank_timestamp`] for the i= mplementor to return the +/// current vblank timestamp for the hardware. +#[derive(Copy, Clone)] +pub struct VblankTimestamp { + /// The actual vblank timestamp in nanoseconds, accuracy to within [`S= elf::max_error`] + /// nanoseconds. + pub time: Delta, + + /// Maximum allowable timestamp error in nanoseconds + pub max_error: i32, +} + +/// A trait for [`DriverCrtc`] implementations with hardware vblank suppor= t. +/// +/// This trait is implemented internally by DRM for any [`DriverCrtc`] imp= lementation that +/// implements [`VblankSupport`]. It is used to expose hardware-vblank dri= ver 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 `VblankCrt= c`s type invariants + // - We don't expose any way of calling `vblank_crtc()` before `dr= m_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 allow= s 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) -> V= blankGuard<'a, T> { + // SAFETY: `vbl_lock` is initialized for as long as `Crtc` is avai= lable to users + // INVARIANT: We just acquired `vbl_lock`, fulfilling the invarian= ts of `VblankGuard` + unsafe { bindings::spin_lock(&raw mut (*self.drm_dev().as_raw()).v= bl_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 upda= te 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 initial= ized 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 [`Vbl= ankRef`] objects exist. + #[inline] + pub fn vblank_off(&self) { + // SAFETY: `as_raw()` always returns a valid pointer to an initial= ized 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 [`V= blankRef`] objects again. + #[inline] + pub fn vblank_on(&self) { + // SAFETY: `as_raw()` always returns a valid pointer to an initial= ized 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 =3D "Vblanks are only enabled until the result from this fu= nction is dropped"] + pub fn vblank_get(&self) -> Result> { + VblankRef::new(self) + } +} + +/// Common methods available on any [`CrtcState`] whose [`Crtc`] implement= s [`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 d= ata, and since our + // interface follows rust's data aliasing rules that means this is= safe to read + let event_ptr =3D 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 c= onsumes the + /// [`PendingVblankEvent`]. + pub fn send(self) + where + T: RawVblankCrtcState>, + C: VblankDriverCrtc, + { + let crtc: &Crtc =3D self.0.crtc(); + let event_lock =3D crtc.drm_dev().event_lock(); + let _guard =3D 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(), (*sel= f.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 vbla= nk event. + unsafe { (*self.0.as_raw()).event =3D null_mut() }; + } + + /// Arm this [`PendingVblankEvent`] to be sent later by the CRTC's vbl= ank interrupt handler. + /// + /// A [`PendingVblankEvent`] can only be armed once, so this function = consumes the + /// [`PendingVblankEvent`]. As well, it requires a [`VblankRef`] so th= at 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 =3D self.0.crtc(); + let event_lock =3D crtc.drm_dev().event_lock(); + let _guard =3D 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 vbl= ank event. + unsafe { (*self.0.as_raw()).event =3D null_mut() }; + + // DRM took ownership of `vbl_ref` after we called `drm_crtc_arm_v= blank_event` + mem::forget(vbl_ref); + } +} + +/// A borrowed vblank reference. +/// +/// This object keeps the vblank reference count for a [`Crtc`] incremente= d for as long as it +/// exists, enabling vblank interrupts for said [`Crtc`] until all referen= ces 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 w= rapper 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 ou= r lifetime + unsafe { Device::from_raw((*self.as_raw()).dev) } + } +} + +// NOTE: This type does not use a `Guard` because the mutex is not contain= ed 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 ma= nually via +/// [`Crtc::vblank_lock`]. It provides access to methods and data which re= quire +/// [`drm_device.vbl_lock`] be held. +/// +/// # Invariants +/// +/// - [`drm_device.vbl_lock`] is acquired whenever an instance of this typ= e 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 LocalInterruptDi= sabled) -> 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 + } + + /// Return the vblank core's cached copy of the currently set display = mode. + /// + /// If the display is disabled, this will return `None`. + pub fn hwmode(&self) -> Option<&DisplayMode> { + // SAFETY: We hold the appropriate lock for this read via our type= invariants. + let ptr =3D unsafe { &raw const (*self.as_raw()).hwmode }; + + // SAFETY: We check here if the cached DisplayMode is Null, which = means the only other + // possibility is that the pointer points to a valid initialized d= rm_display_mode. + (!ptr.is_null()).then(|| unsafe { DisplayMode::as_ref(ptr) }) + } +} + +impl Deref for VblankGuard<'_, T> { + type Target =3D 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(&raw mut (*self.drm_dev().as_raw())= .vbl_lock) } + } +} --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f44.google.com (mail-wr1-f44.google.com [209.85.221.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 848DA285CB3 for ; Fri, 3 Jul 2026 03:01:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047699; cv=none; b=ey9ldVBEc+g9WosV4u19wb+tkF8qh3qRDx9dWNcKAuWfweEuijoXAs7OJeONEUa5+eA/IA56HVryyi0rMkvr+7uBB+IsxJ05cJcunlLY7TxkyiDEL1n9Nuncie5KKurtuYBHwDwwMdAdrd6nm9au12yojLXujHV/XRJAfH3QxXM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047699; c=relaxed/simple; bh=IsVB1jfqN1ouCll22S4o9n/7FQwB21fqN/8F90cwzN0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=S8Y4d/CCdveltFr83dVrDQfY1c48SgA8RZJ7C2CuIOotZ9lCkUA+uvm046kpAf062ucMvnQ3z9qRk+L6jh5GNWQGoCe+5B01Mbk5Vn44a927lB83f3Kbn040kqVFPi0AftEEcVeP5PLlWArFdee+onlQ86oeAugnsQIGMSfXy24= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=y0e/3ZRd; arc=none smtp.client-ip=209.85.221.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="y0e/3ZRd" Received: by mail-wr1-f44.google.com with SMTP id ffacd0b85a97d-461edb387ddso94427f8f.3 for ; Thu, 02 Jul 2026 20:01:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047696; x=1783652496; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=4zmepUPjDn3grp7kNjt5JCNExsSCDpx9IEqmKZjBkNg=; b=y0e/3ZRdVeAuU2Hf5JX7bfEoWw6jY+8cNU+1XOq9p129qYU9RWbwQnpQIQoQ7bgP9u VGbJjiXZyhofeicA3VP90LItiKwYtgciLtxhz3j7sejUwqFCXpXAjMNUpboh64txHdM0 XGOzLGnVsvq418lHBeTURh6HK1PEXJgZdQkM7Jw6dxF809Kg1P2sQinhkf/T/pNwKvCV +aZJjyb0vWXFDjeSdCOsl6Zu3nSR3VGBjAYpPQguqKP8ph7HSUJPHb0BncFiczilJybV SACFZQU9gRlDugBLVgU1NOHtl1F3F2rwtiI/mpF0E9BS1l2LaPPnAlpccgygxeuF7v/F Yb9w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047696; x=1783652496; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=4zmepUPjDn3grp7kNjt5JCNExsSCDpx9IEqmKZjBkNg=; b=AdOJ4IZXJggLuNqIMhVxxJXWXNTkplzTbfQ16prHS00bQ7aUuBcmAgcpkO0Cf5gKTy zSh7MdqzTcUVnMiaI+9n0c90d5WODMp3JxWwkrvipKXzcRwgWwDAZFpYdhrJ7R0whcxE rLTtopPhCJeXEa7TBVXp1Nrp0K9l6rhb8RNROHY+IX8Nej+j3GYjgqZAO+lXHO8bZ0bo 16fH77CmXRKKyuULdP6RUaqg5dlrtpvJBHk7un1Z9LZuSdonZqI8vhjb63pzmHwj6YhY d2/AYqVfoBgJnN8yP5bKSKMQFZGhP4qE1ifvLEZMviqAXjDNAYSCWbeybK2aaQwaq5dc gtyA== X-Forwarded-Encrypted: i=1; AHgh+RqH3g8t1sO2PCQb8/icwoBJ5tYWh1S8Qos+V08NmdyNExIP5wJ2M3/6QODRuG9m8B75C5zMU7YVgIrR2A8=@vger.kernel.org X-Gm-Message-State: AOJu0YwnPVd06jCN/FDH9zUxWta5zCxy+xv/8otXFnZw69994DuCZnja NVRpar8BExdEwx+LMlJeT4TyQdpe8SsmrJinromwEdR46veT7EDChiNx98MMsqagHA== X-Gm-Gg: AfdE7ck/PlCsDgmyBe3Zcg9AI0attoFnralHHoyCnCRWdDC+h42XgP/tTYknYNuE8VW n37SSwQOBxhWW+twE7/TcrQw73jJMpfqRfQfiCmPNalLYdIqsRIwI/DgfZj8j/UoldKScIUPpTy sS+sZm4hMbK/G50jeDyCJCWESplWDFhtLKlM4/27s12IJ8ZtSjskyNTFpFP6PXE0xSlk+nnmlin mYNT0RG70i0OhD+0W6tQeKhrjZpFr3GyyOsfaydwCNBvLdKGUlJhhOH4W+hlLoDsG/SR9XAMIIf rS3rZhb1mbQwEaafdwvrfz1pmlrKOKimnjBlwokBge0ydcDuNzGA5SoDua9H6zOIi7Zr6M1J1DH XBMjyCrRMDOt+3y+c6MaTC3YRagopUenjbi1csSs6AmnbZVrWhUCD+tewhACmiDXmoQeDSoUwuG Njv0OmtNaWpKe5NS5+sPF2jgPS+FT9ZkN0pjbQqwHg22AMZ0MqdMCkY8Wk X-Received: by 2002:adf:e194:0:b0:476:135a:135c with SMTP id ffacd0b85a97d-477aeb5bd8dmr12495575f8f.2.1783047695816; Thu, 02 Jul 2026 20:01:35 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:34 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 02/18] rust: drm: kms: adapt the port to current drm-next Date: Fri, 3 Jul 2026 04:00:49 +0100 Message-ID: <20260703030123.2814-3-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Three mechanical fixups needed to build the forward-ported layer against this tree, rather than the rvkms-slim/rust-stuck source branches it came from: - wire the KMS bindings/helpers/aref imports the port assumes into bindings_helper.h and rust/helpers/drm/{atomic,drm,vblank}.c, and fix the impl_aref_for_mode_object! macro path - rename drm_atomic_state -> drm_atomic_commit, renamed upstream since rvkms-slim was last synced - pin KmsDriver's associated types to Driver=3DSelf and tidy the probe_kms stub so it type-checks against the current Driver trait Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- rust/bindings/bindings_helper.h | 8 +++++++ rust/helpers/drm/atomic.c | 32 +++++++++++++++++++++++++ rust/helpers/drm/drm.c | 2 ++ rust/helpers/drm/vblank.c | 8 +++++++ rust/kernel/drm/kms.rs | 21 ++++++++--------- rust/kernel/drm/kms/atomic.rs | 41 +++++++++++++++++---------------- rust/kernel/drm/kms/crtc.rs | 16 ++++++------- rust/kernel/drm/kms/plane.rs | 8 +++---- 8 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 rust/helpers/drm/atomic.c create mode 100644 rust/helpers/drm/vblank.c diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index a1ea23714bdd..e41d2716fb2e 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -36,6 +36,14 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/rust/helpers/drm/atomic.c b/rust/helpers/drm/atomic.c new file mode 100644 index 000000000000..ed5e49b73c81 --- /dev/null +++ b/rust/helpers/drm/atomic.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +void rust_helper_drm_atomic_commit_get(struct drm_atomic_commit *state) +{ + drm_atomic_commit_get(state); +} + +void rust_helper_drm_atomic_commit_put(struct drm_atomic_commit *state) +{ + drm_atomic_commit_put(state); +} + +// Macros for generating one repetitive atomic state accessors (like drm_a= tomic_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_commit *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 b8700413a2b8..5d700d75c0c9 100644 --- a/rust/helpers/drm/drm.c +++ b/rust/helpers/drm/drm.c @@ -1,4 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 =20 +#include "atomic.c" #include "gem.c" #include "vma_manager.c" +#include "vblank.c" diff --git a/rust/helpers/drm/vblank.c b/rust/helpers/drm/vblank.c new file mode 100644 index 000000000000..165db7ac5b4d --- /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/kms.rs b/rust/kernel/drm/kms.rs index 11b09d2175db..a147276b3412 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -12,8 +12,7 @@ }, error::to_result, prelude::*, - sync::{Mutex, MutexGuard}, - types::*, + sync::{aref::ARef, Mutex, MutexGuard}, }; use bindings; use core::{ @@ -69,10 +68,10 @@ pub trait KmsImpl { /// /// This function may only be called once. unsafe fn probe_kms(_drm: &Device) -> Result= { - // TODO before submission: There is definitely a proper way of= doing this, but I don't - // remember what it is. - unimplemented!() - //build_error!("This should never be reachable") + // This default is only reachable for the `PhantomData` (no= n-KMS) implementor, whose + // KMS setup is never invoked. KMS drivers override it via the= `KmsDriver` blanket impl + // below, so reaching this is a build-time bug. + build_error::build_error("probe_kms called on a device without= KMS support") } } } @@ -126,22 +125,22 @@ pub trait KmsDriver: Driver { /// The driver's [`DriverConnector`](connector::DriverConnector) imple= mentation. /// /// TODO: This will be unneeded once we support multiple `DriverConnec= tor` implementations. - type Connector: connector::DriverConnector; + type Connector: connector::DriverConnector; =20 /// The driver's [`DriverPlane`](plane::DriverPlane) implementation. /// /// TODO: This will be unneeded once we support multiple `DriverPlane`= implementations. - type Plane: plane::DriverPlane; + type Plane: plane::DriverPlane; =20 /// The driver's [`DriverCrtc`](crtc::DriverCrtc) implementation. /// /// TODO: This will be unneeded once we support multiple `DriverCrtc` = implementations. - type Crtc: crtc::DriverCrtc; + type Crtc: crtc::DriverCrtc; =20 /// The driver's [`DriverEncoder`](encoder::DriverEncoder) implementat= ion. /// /// TODO: This will be unneeded once we support multiple `DriverEncode= r` implementations. - type Encoder: encoder::DriverEncoder; + type Encoder: encoder::DriverEncoder; =20 /// Return a [`ModeConfigInfo`] structure for this [`device::Device`]. fn mode_config_info(dev: &Device) -> Result @@ -486,7 +485,7 @@ macro_rules! impl_aref_for_mode_object { (impl $( < $( $param:ident: $bound:ident ),+ > )? for $type:ty) =3D> { // SAFETY: drm_mode_object_get()/put() ensure the type is ref-coun= ted according to the // safety contract - unsafe impl $( < $( $param: $bound ),+ > )? kernel::types::AlwaysR= efCounted for $type { + unsafe impl $( < $( $param: $bound ),+ > )? kernel::sync::aref::Al= waysRefCounted for $type { #[inline] fn inc_ref(&self) { // SAFETY: We're guaranteed by the safety contract of `Mod= eObject` that diff --git a/rust/kernel/drm/kms/atomic.rs b/rust/kernel/drm/kms/atomic.rs index cc14bff47abd..18dc136940f3 100644 --- a/rust/kernel/drm/kms/atomic.rs +++ b/rust/kernel/drm/kms/atomic.rs @@ -1,31 +1,32 @@ // SPDX-License-Identifier: GPL-2.0 OR MIT =20 -//! [`struct drm_atomic_state`] related bindings for rust. +//! [`struct drm_atomic_commit`] related bindings for rust. //! -//! [`struct drm_atomic_state`]: srctree/include/drm/drm_atomic.h +//! [`struct drm_atomic_commit`]: 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::*, + sync::aref::{ARef, AlwaysRefCounted}, types::*, }; use core::{cell::Cell, marker::*, mem::ManuallyDrop, ops::*, ptr::NonNull}; =20 -/// The main wrapper around [`struct drm_atomic_state`]. +/// The main wrapper around [`struct drm_atomic_commit`]. /// /// This type is usually embedded within another interface such as an [`At= omicStateMutator`]. /// /// # Invariants /// -/// - The data layout of this type is identical to [`struct drm_atomic_sta= te`]. +/// - The data layout of this type is identical to [`struct drm_atomic_com= mit`]. /// - `state` is initialized for as long as this type is exposed to users. /// -/// [`struct drm_atomic_state`]: srctree/include/drm/drm_atomic.h +/// [`struct drm_atomic_commit`]: srctree/include/drm/drm_atomic.h #[repr(transparent)] pub struct AtomicState { - pub(super) state: Opaque, + pub(super) state: Opaque, _p: PhantomData, } =20 @@ -34,18 +35,18 @@ impl AtomicState { /// /// # Safety /// - /// `ptr` must point to a valid initialized instance of [`struct drm_a= tomic_state`]. + /// `ptr` must point to a valid initialized instance of [`struct drm_a= tomic_commit`]. /// - /// [`struct drm_atomic_state`]: srctree/include/drm/drm_atomic.h + /// [`struct drm_atomic_commit`]: srctree/include/drm/drm_atomic.h #[allow(dead_code)] - pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::drm_atomic_sta= te) -> &'a Self { + pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::drm_atomic_com= mit) -> &'a Self { // SAFETY: Our data layout is identical // INVARIANT: Our safety contract upholds the guarantee that `stat= e` is initialized for as // long as this type is exposed to users. unsafe { &*ptr.cast() } } =20 - pub(crate) fn as_raw(&self) -> *mut bindings::drm_atomic_state { + pub(crate) fn as_raw(&self) -> *mut bindings::drm_atomic_commit { self.state.get() } =20 @@ -102,12 +103,12 @@ pub fn get_old_connector_state(&self, connector: &= C) -> Option<&C::State> unsafe impl AlwaysRefCounted for AtomicState { fn inc_ref(&self) { // SAFETY: `state` is initialized for as long as this type is expo= sed to users - unsafe { bindings::drm_atomic_state_get(self.state.get()) } + unsafe { bindings::drm_atomic_commit_get(self.state.get()) } } =20 unsafe fn dec_ref(obj: NonNull) { // SAFETY: `obj` contains a valid non-null pointer to an initializ= ed `Self`. - unsafe { bindings::drm_atomic_state_put(obj.as_ptr().cast()) } + unsafe { bindings::drm_atomic_commit_put(obj.as_ptr().cast()) } } } =20 @@ -141,11 +142,11 @@ impl AtomicStateMutator { /// /// # Safety /// - /// `ptr` must point to a valid `drm_atomic_state` + /// `ptr` must point to a valid `drm_atomic_commit` #[allow(dead_code)] - pub(super) unsafe fn new(ptr: NonNull) -> = Self { + pub(super) unsafe fn new(ptr: NonNull) ->= Self { Self { - // SAFETY: The data layout of `AtomicState` is identical to= drm_atomic_state + // SAFETY: The data layout of `AtomicState` is identical to= drm_atomic_commit // 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. @@ -156,7 +157,7 @@ pub(super) unsafe fn new(ptr: NonNull) -> Self { } } =20 - pub(crate) fn as_raw(&self) -> *mut bindings::drm_atomic_state { + pub(crate) fn as_raw(&self) -> *mut bindings::drm_atomic_commit { self.state.as_raw() } =20 @@ -273,8 +274,8 @@ fn drop(&mut self) { impl AtomicStateComposer { /// # Safety /// - /// The caller guarantees that `ptr` points to a valid instance of `dr= m_atomic_state`. - pub(crate) unsafe fn new(ptr: NonNull) -> = Self { + /// The caller guarantees that `ptr` points to a valid instance of `dr= m_atomic_commit`. + pub(crate) unsafe fn new(ptr: NonNull) ->= Self { // SAFETY: see `AtomicStateMutator::from_raw()` Self(unsafe { AtomicStateMutator::new(ptr) }) } @@ -681,11 +682,11 @@ pub fn commit_hw_done<'b>( =20 // 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, + state: *mut bindings::drm_atomic_commit, ) { // SAFETY: // - We're guaranteed by DRM that `state` always points to a valid ins= tance of - // `bindings::drm_atomic_state` + // `bindings::drm_atomic_commit` // - This conversion is safe via the type invariants let state =3D unsafe { AtomicState::from_raw(state.cast_const()) }; =20 diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index b9d095854ba6..0f579dc4ec75 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -995,14 +995,14 @@ impl<'a, T: DriverCrtc> CrtcAtomicCommit<'a, T> { =20 unsafe extern "C" fn atomic_check_callback( crtc: *mut bindings::drm_crtc, - state: *mut bindings::drm_atomic_state, + state: *mut bindings::drm_atomic_commit, ) -> 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 initia= lized state. let crtc =3D unsafe { Crtc::from_raw(crtc) }; =20 - // SAFETY: DRM guarantees `state` points to a valid `drm_atomic_state` + // SAFETY: DRM guarantees `state` points to a valid `drm_atomic_commit` // We use a ManuallyDrop here to avoid AtomicStateComposer dropping an= owned reference we never // acquired. let state =3D @@ -1023,14 +1023,14 @@ impl<'a, T: DriverCrtc> CrtcAtomicCommit<'a, T> { =20 unsafe extern "C" fn atomic_begin_callback( crtc: *mut bindings::drm_crtc, - state: *mut bindings::drm_atomic_state, + state: *mut bindings::drm_atomic_commit, ) { // SAFETY: // * We're guaranteed `crtc` is of type `Crtc` via type invariants. // * We're guaranteed by DRM that `crtc` is pointing to a valid initia= lized state. let crtc =3D unsafe { Crtc::from_raw(crtc) }; =20 - // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance of `drm_atomic_state` + // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance of `drm_atomic_commit` let state =3D unsafe { AtomicStateMutator::new(NonNull::new_unchecked(= state)) }; =20 // SAFETY: @@ -1045,14 +1045,14 @@ impl<'a, T: DriverCrtc> CrtcAtomicCommit<'a, T> { =20 unsafe extern "C" fn atomic_flush_callback( crtc: *mut bindings::drm_crtc, - state: *mut bindings::drm_atomic_state, + state: *mut bindings::drm_atomic_commit, ) { // SAFETY: // - We're guaranteed `crtc` is of type `Crtc` via type invariants. // - We're guaranteed by DRM that `crtc` is pointing to a valid initia= lized state. let crtc =3D unsafe { Crtc::from_raw(crtc) }; =20 - // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance of `drm_atomic_state` + // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance of `drm_atomic_commit` let state =3D unsafe { AtomicStateMutator::new(NonNull::new_unchecked(= state)) }; =20 // SAFETY: @@ -1067,7 +1067,7 @@ impl<'a, T: DriverCrtc> CrtcAtomicCommit<'a, T> { =20 unsafe extern "C" fn atomic_enable_callback( crtc: *mut bindings::drm_crtc, - state: *mut bindings::drm_atomic_state, + state: *mut bindings::drm_atomic_commit, ) { // SAFETY: // - We're guaranteed `crtc` is of type `Crtc` via type invariants. @@ -1089,7 +1089,7 @@ impl<'a, T: DriverCrtc> CrtcAtomicCommit<'a, T> { =20 unsafe extern "C" fn atomic_disable_callback( crtc: *mut bindings::drm_crtc, - state: *mut bindings::drm_atomic_state, + state: *mut bindings::drm_atomic_commit, ) { // SAFETY: // - We're guaranteed `crtc` points to a valid instance of `drm_crtc` diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 661d82273099..6f547adcfdc9 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -1048,14 +1048,14 @@ impl<'a, T: DriverPlane> PlaneAtomicCommit<'a, T> { =20 unsafe extern "C" fn atomic_update_callback( plane: *mut bindings::drm_plane, - state: *mut bindings::drm_atomic_state, + state: *mut bindings::drm_atomic_commit, ) { // SAFETY: // - We're guaranteed `plane` is of type `Plane` via type invariant= s. // - We're guaranteed by DRM that `plane` is pointing to a valid initi= alized state. let plane =3D unsafe { Plane::from_raw(plane) }; =20 - // SAFETY: DRM guarantees `state` points to a valid `drm_atomic_state` + // SAFETY: DRM guarantees `state` points to a valid `drm_atomic_commit` let state =3D unsafe { AtomicStateMutator::new(NonNull::new_unchecked(= state)) }; =20 // SAFETY: @@ -1070,14 +1070,14 @@ impl<'a, T: DriverPlane> PlaneAtomicCommit<'a, T> { =20 unsafe extern "C" fn atomic_check_callback( plane: *mut bindings::drm_plane, - state: *mut bindings::drm_atomic_state, + state: *mut bindings::drm_atomic_commit, ) -> i32 { // SAFETY: // - We're guaranteed `plane` is of type `Plane` via type invariant= s. // - We're guaranteed by DRM that `plane` is pointing to a valid initi= alized state. let plane =3D unsafe { Plane::from_raw(plane) }; =20 - // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance of `drm_atomic_state` + // SAFETY: We're guaranteed by DRM that `state` points to a valid inst= ance of `drm_atomic_commit` // We use ManuallyDrop here since AtomicStateComposer would otherwise = drop a owned reference to // the atomic state upon finishing this callback. let state =3D ManuallyDrop::new(unsafe { --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F21EA288505 for ; Fri, 3 Jul 2026 03:01:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047702; cv=none; b=LA972kJnmBnxeq1g2iI8QB6XMxTkpMJpeGojb/p0+j1aCrm+amlq+GdgoKDHfKcVs0wgBJsk8zlCVNAAMzijrU6bQ+x4aFxHZYtNEG5u8+ojg414wdey72+QzirNJF3JdfNxqWzDOTnctEu5IkVFZ1yBUBY3PFcV0lZSPyE9WuU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047702; c=relaxed/simple; bh=FGwV/fEV+NMA3Nek9ItcBPqyhi0uH3abcptvhxi9OQA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UFeowrOW0L58XedukyKdUHzTwheFPWxOhoBOaGgHCWG41czHKKfgHSzAMYAX+Uf8P+hE91BvmBIfVFhvNbRW64n5UR/MwMLWxx6Jf8v9bp3VYO3o0YoM2Ed6pz6OOrR+GszNGaXI2mWOudO4RMq5QngwHrrTuA9RHx5qC14alyc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=SjZkTblf; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="SjZkTblf" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-493ae59eca6so334215e9.1 for ; Thu, 02 Jul 2026 20:01:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047698; x=1783652498; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=P/nbseqGkYcWLUDS4YgM1HcuWcHd/PeuOx3gHx4QCk0=; b=SjZkTblffX/4nC403l0/FZxoP5zHVgb/g4UdG1NFcbZMrfsnR9yXgaUNERs01PtAVi 8A5ynQ1lHBwhbXh+NK3lkMUZ0nwya+vORJV07xWWd2dEtfftQWvJKBNrddthohJYEBum Z5kkEVrPqj8hwfH7xTCEMcLwf+K8PhHS1HduwYfDS/pmI2zzpL3JT6m9MvjE+lnPtjIy EMYuv7pMBvEBNAMqo+427q8qTN6xEH1V4460u6jk8ED3qWS4f1MtWXd8be85iKEiBqsE YRkPZhMlsOSv6h7USm0/l/bBtajAEGJOJprJj+HHhqWV+PGqz3OplqPFwuCVABDsvONx wXwA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047698; x=1783652498; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=P/nbseqGkYcWLUDS4YgM1HcuWcHd/PeuOx3gHx4QCk0=; b=QNX+lg2Rm0Ie+EpoOYAPtyEolxUScYxqmAeO19v73ifsOzzcE3frxvcc9GYLvt0iJM E/vCdIY1fCokf2QVEzwAJqFguwhWVg4c9vazaVeU7cXoLLiN/tDxi+ldQ9XNfvjChoaA 3ZiS8iCz6AzesZDkCKc7kFgHgkm6aytEavOVAG7JoZ9jt6I8cxC4k+E/Z6GHRH31wnXX jOEE+PfJsUz0M1Jd53zOsfGBxhAUOHxPYPXNaWDvFc63c/s27GZiKNkuh9vmHbFwbKIf hA8NEgGNwPpEd1VuXEbkOZ47avrx+8ACqfZz14LwHzAKxtwF/xOo85L8/TPsuZgHYxAz /lSQ== X-Forwarded-Encrypted: i=1; AFNElJ//Qnl+kL8V0eVvzsvZrdLypAinSEchy95zgERUelmk+40NcYCgvjoa2Avmny+SuPdw/jTp007hmnEvw8M=@vger.kernel.org X-Gm-Message-State: AOJu0YxAlnlLjY2OkKswoNPpYwqG0kxZv/GE3RjZi361abO+tc5Kn4xh XBoNsXWf2RYZlPgaCR0/2/kE5Gx7SheRahYhF4Xc6AFLZ88xP/4uuCpubFQiuOdzCQ== X-Gm-Gg: AfdE7clbDNa6XAEi6fLDPs8FwImr5enpsIcxmTmPi62Ls7vQ6gPBKA5+jmUI4YiM3Wb Yrf13qZef1Pn3AMdB3tT2mjoDbAcUqt9cQ0nNpA0IimVFSEzhDANT2cx9qeko7N5JnRYlGZQm7D 0Vm+NUeW6KFwcrqLIeY4VNkL18Gy+9SySUzEa/XGi/RTYxDf0GaVSSOvac0j4e2mr5uT0OJ4dKD P4bN9X3bydlvjGixF8JljvyvbYpjh+HeU+uaPnQAR8NyQkgHotkaAT5dbUQMyU79jauk4Wun21Y Bk5ryNXQ090V7/suCXGz9gEt7mfDzH8Y1dgFsUtajFA18xhcEhb8+0mrwGvh4y+fJd5+j16QDna zN18nfnCoUvf/8K3pHYCubW86Wpbhq6kAH7/lFTDO0R6bf8Y5413VFxzVxjWLdbdKzu/JU8/bSh IhCd2QtGLf0EPOqXYShCgZCdLZegV1TAmfY8J5k+h01DhSM3taMgy3S/Dg X-Received: by 2002:a05:600c:5391:b0:490:c032:ae92 with SMTP id 5b1f17b1804b1-493c3df2b42mr101737415e9.33.1783047698330; Thu, 02 Jul 2026 20:01:38 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:37 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 03/18] rust: drm: kms: break the Driver* trait well-formedness cycle Date: Fri, 3 Jul 2026 04:00:50 +0100 Message-ID: <20260703030123.2814-4-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The ported mode-object trait web (KmsDriver / DriverConnector / DriverConnectorState / VblankImpl, ...) is mutually recursive: a Modesettable* impl for Crtc needs ::Crtc: DriverCrtc, which needs ::Crtc, and so on -- an infinite projection regress the trait solver can't terminate on unless the associated-state types are pinned to State. Pin them, add the where-clauses the *StateMutator Deref/DerefMut impls now need once callers pass through the pinned types, and fix a from_opaque copy-paste bug in CrtcStateMutator that was reading Plane's fields instead of Crtc's. This still doesn't build clean: pinning trades the projection regress for a coinductive cycle in the from_opaque-on-state impls, which the current (non-next-gen) trait solver exhausts memory on instead of resolving. See the following commit. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- rust/kernel/drm/kms.rs | 28 +++++++++++++--------------- rust/kernel/drm/kms/connector.rs | 27 ++++++++++++++++++++------- rust/kernel/drm/kms/crtc.rs | 31 +++++++++++++++++++++---------- rust/kernel/drm/kms/plane.rs | 27 ++++++++++++++++++++------- rust/kernel/drm/kms/vblank.rs | 5 +++-- 5 files changed, 77 insertions(+), 41 deletions(-) diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index a147276b3412..d07389fd510c 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -122,25 +122,23 @@ fn deref(&self) -> &Self::Target { /// [`PhantomData`]: PhantomData #[vtable] pub trait KmsDriver: Driver { - /// The driver's [`DriverConnector`](connector::DriverConnector) imple= mentation. - /// - /// TODO: This will be unneeded once we support multiple `DriverConnec= tor` implementations. - type Connector: connector::DriverConnector; + // The driver's mode-object implementations. Left *unbounded* to break= a trait + // well-formedness cycle: the natural bound (`type Crtc: DriverCrtc`, etc.) + // makes every `T: KmsDriver` obligation pull in the mutually-recursiv= e Driver* web, which + // the compiler cannot evaluate (it loops in obligation processing and= exhausts memory). The + // `DriverConnector`/`DriverCrtc`/... bounds are required at the use s= ites as `where`-clauses + // instead (an assumption, not an eagerly WF-checked trait-definition = obligation). + // + // TODO: unneeded once we support multiple `Driver*` implementations p= er driver. =20 + /// The driver's [`DriverConnector`](connector::DriverConnector) imple= mentation. + type Connector; /// The driver's [`DriverPlane`](plane::DriverPlane) implementation. - /// - /// TODO: This will be unneeded once we support multiple `DriverPlane`= implementations. - type Plane: plane::DriverPlane; - + type Plane; /// The driver's [`DriverCrtc`](crtc::DriverCrtc) implementation. - /// - /// TODO: This will be unneeded once we support multiple `DriverCrtc` = implementations. - type Crtc: crtc::DriverCrtc; - + type Crtc; /// The driver's [`DriverEncoder`](encoder::DriverEncoder) implementat= ion. - /// - /// TODO: This will be unneeded once we support multiple `DriverEncode= r` implementations. - type Encoder: encoder::DriverEncoder; + type Encoder; =20 /// Return a [`ModeConfigInfo`] structure for this [`device::Device`]. fn mode_config_info(dev: &Device) -> Result diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connect= or.rs index 05ec64cf6fa2..aca7be50e583 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -139,7 +139,7 @@ pub trait DriverConnector: Send + Sync + Sized { /// The [`DriverConnectorState`] implementation for this [`DriverConne= ctor`]. /// /// See [`DriverConnectorState`] for more info. - type State: DriverConnectorState; + type State: DriverConnectorState; =20 /// The constructor for creating a [`Connector`] using this [`DriverCo= nnector`] implementation. /// @@ -700,13 +700,17 @@ pub struct 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; + /// The parent [`DriverConnector`]. Unbounded to break the trait WF cy= cle; the + /// `DriverConnector` bound is required at use sites instead. + type Connector; } =20 impl Sealed for ConnectorState {} =20 -impl AsRawConnectorState for ConnectorState { +impl AsRawConnectorState for ConnectorState +where + T::Connector: DriverConnector, +{ type Connector =3D Connector; } =20 @@ -720,7 +724,10 @@ unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_= connector_state { } } =20 -impl FromRawConnectorState for ConnectorState { +impl FromRawConnectorState for ConnectorState +where + T::Connector: DriverConnector, +{ 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 =3D ptr.cast(); @@ -859,7 +866,10 @@ pub(super) fn new( } } =20 -impl<'a, T: DriverConnectorState> Deref for ConnectorStateMutator<'a, Conn= ectorState> { +impl<'a, T: DriverConnectorState> Deref for ConnectorStateMutator<'a, Conn= ectorState> +where + T::Connector: DriverConnector, +{ type Target =3D T; =20 fn deref(&self) -> &Self::Target { @@ -867,7 +877,10 @@ fn deref(&self) -> &Self::Target { } } =20 -impl<'a, T: DriverConnectorState> DerefMut for ConnectorStateMutator<'a, C= onnectorState> { +impl<'a, T: DriverConnectorState> DerefMut for ConnectorStateMutator<'a, C= onnectorState> +where + T::Connector: DriverConnector, +{ fn deref_mut(&mut self) -> &mut Self::Target { &mut self.state.inner } diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 0f579dc4ec75..4c6cb6bbdcb8 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -124,7 +124,7 @@ pub trait DriverCrtc: Send + Sync + Sized { /// The [`DriverCrtcState`] implementation for this [`DriverCrtc`]. /// /// See [`DriverCrtcState`] for more info. - type State: DriverCrtcState; + type State: DriverCrtcState; =20 /// The driver's optional hardware vblank implementation /// @@ -561,10 +561,9 @@ impl Sealed for 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 parent CRTC driver for this CRTC state. Unbounded to break the= trait WF cycle; + /// the `DriverCrtc` bound is required at use sites instead. + type Crtc; } =20 /// The main interface for a [`struct drm_crtc_state`]. @@ -702,11 +701,17 @@ fn as_raw(&self) -> *mut bindings::drm_crtc_state { } } =20 -impl AsRawCrtcState for CrtcState { +impl AsRawCrtcState for CrtcState +where + T::Crtc: DriverCrtc, +{ type Crtc =3D Crtc; } =20 -impl FromRawCrtcState for CrtcState { +impl FromRawCrtcState for CrtcState +where + T::Crtc: DriverCrtc, +{ unsafe fn from_raw<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Se= lf { // SAFETY: Our data layout starts with `bindings::drm_crtc_state` unsafe { &*(ptr.cast()) } @@ -800,7 +805,7 @@ fn (CrtcStateMutator<'a, OpaqueCrtcState>) -> = Self where T: DriverCrtcState; use - T as DriverCrtc, + C as DriverCrtc, D as KmsDriver } } @@ -813,7 +818,10 @@ fn drop(&mut self) { } } =20 -impl<'a, T: DriverCrtcState> Deref for CrtcStateMutator<'a, CrtcState> { +impl<'a, T: DriverCrtcState> Deref for CrtcStateMutator<'a, CrtcState> +where + T::Crtc: DriverCrtc, +{ type Target =3D T; =20 fn deref(&self) -> &Self::Target { @@ -823,7 +831,10 @@ fn deref(&self) -> &Self::Target { } } =20 -impl<'a, T: DriverCrtcState> DerefMut for CrtcStateMutator<'a, CrtcState> { +impl<'a, T: DriverCrtcState> DerefMut for CrtcStateMutator<'a, CrtcState> +where + T::Crtc: DriverCrtc, +{ fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: Our interface ensures that `self.state.inner` follows r= ust's data-aliasing rules, // so this is safe diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 6f547adcfdc9..47e05fe6bee1 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -98,7 +98,7 @@ pub trait DriverPlane: Send + Sync + Sized { /// The [`DriverPlaneState`] implementation for this [`DriverPlane`]. /// /// See [`DriverPlaneState`] for more info. - type State: DriverPlaneState; + type State: DriverPlaneState; =20 /// The constructor for creating a [`Plane`] using this [`DriverPlane`= ] implementation. /// @@ -689,13 +689,17 @@ pub struct 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; + /// The type for this driver's drm_plane implementation. Unbounded to = break the trait WF + /// cycle; the `DriverPlane` bound is required at use sites instead. + type Plane; } =20 impl Sealed for PlaneState {} =20 -impl AsRawPlaneState for PlaneState { +impl AsRawPlaneState for PlaneState +where + T::Plane: DriverPlane, +{ type Plane =3D Plane; } =20 @@ -709,7 +713,10 @@ unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_= plane_state { } } =20 -impl FromRawPlaneState for PlaneState { +impl FromRawPlaneState for PlaneState +where + T::Plane: DriverPlane, +{ unsafe fn from_raw<'a>(ptr: *const bindings::drm_plane_state) -> &'a S= elf { // Our data layout starts with `bindings::drm_plane_state`. let ptr: *const Self =3D ptr.cast(); @@ -885,7 +892,10 @@ unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_= plane_state { =20 impl<'a, T: FromRawPlaneState> Sealed for PlaneStateMutator<'a, T> {} =20 -impl<'a, T: DriverPlaneState> Deref for PlaneStateMutator<'a, PlaneState> { +impl<'a, T: DriverPlaneState> Deref for PlaneStateMutator<'a, PlaneState> +where + T::Plane: DriverPlane, +{ type Target =3D T; =20 fn deref(&self) -> &Self::Target { @@ -893,7 +903,10 @@ fn deref(&self) -> &Self::Target { } } =20 -impl<'a, T: DriverPlaneState> DerefMut for PlaneStateMutator<'a, PlaneStat= e> { +impl<'a, T: DriverPlaneState> DerefMut for PlaneStateMutator<'a, PlaneStat= e> +where + T::Plane: DriverPlane, +{ fn deref_mut(&mut self) -> &mut Self::Target { &mut self.state.inner } diff --git a/rust/kernel/drm/kms/vblank.rs b/rust/kernel/drm/kms/vblank.rs index dc34e02e8ccb..534fd18f63b4 100644 --- a/rust/kernel/drm/kms/vblank.rs +++ b/rust/kernel/drm/kms/vblank.rs @@ -62,8 +62,9 @@ fn get_vblank_timestamp( /// Drivers interested in implementing vblank support should refer to [`Vb= lankSupport`], drivers /// that don't have vblank support can use [`PhantomData`]. pub trait VblankImpl { - /// The parent [`DriverCrtc`]. - type Crtc: DriverCrtc; + /// The parent [`DriverCrtc`]. Unbounded to break the trait WF cycle; = the `DriverCrtc` + /// bound is required at use sites instead. + type Crtc; =20 /// The generated [`VblankOps`]. const VBLANK_OPS: VblankOps; --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f43.google.com (mail-wr1-f43.google.com [209.85.221.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C60722C17A3 for ; Fri, 3 Jul 2026 03:01:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047705; cv=none; b=t2bjiy2a+ujFinNnhocB7NCg9f+KEyrRLeUb9wTXIcTkiQmjxAPCcoBfDVcvNuCw1+/oGcE/NKvozdJ3+O9LAQJ60ouNWvlPfjNILJ/049wH7uI1nECDGVMk5ax2twVHljlGc4AJW9hTP/pHM39DTkZjkO3c21tDeHzyB5E+pkE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047705; c=relaxed/simple; bh=EqgrFDHbbJ2MtQkYe+i0eTel4RgJhDt/VrzWaWdchLA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GrPfwGk32mndKqisV91Ak6vtyXEtc/qPKWDdfWak8VfLVlIsCvJ2C1Lx5W6ehR+8oyk2XljIzUsbloojLjeK+CGE1EWwtBrQfSrANOhVnqbZ6q4dCWxpVJsfmB2fuUJUY550rWDEuW0AGZY3UWfGymR1Mljpy2wKFtjSuSJR+fY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=Uy0dOLGM; arc=none smtp.client-ip=209.85.221.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="Uy0dOLGM" Received: by mail-wr1-f43.google.com with SMTP id ffacd0b85a97d-4631679f204so721514f8f.0 for ; Thu, 02 Jul 2026 20:01:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047701; x=1783652501; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=CX+j4GjseoiCPFw3EfmCmbTSaMoPGlBM4ySiKg5qPW4=; b=Uy0dOLGMk67IWXO+PXBAM6TKeBD33+Xivi3EINx0drgPq27dByPMLmqdUpvHhM6nyc ZTQkCLWttmbMriOSsVxVnGMvlxplsPCJSF3gwA2Skgk06wEJX8u7ujl62ku89Uyldy/l vgAvlp1gKkisYagfEPectVwBn1IOTUCaczXw9dAkPYeXWS8EGkkqwI+wcvZTKSk6E4yQ Au8Yr4vNeHRpB4QVXnmmCIWpSy0br0TwleP5Bd3M83B0XEZSb+9HdUOeZMlxdLpQ/Z4f zTlh6kVw0IPJDzJXJgmaUvqrWInafcvU2EfI/Dypt4Etph5A2NLec5+qZNnT1pwzhqJ8 PJKA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047701; x=1783652501; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=CX+j4GjseoiCPFw3EfmCmbTSaMoPGlBM4ySiKg5qPW4=; b=s4BEC61YdlF89UJQ/3xbY9wHietZq8Kj5XZvkAK4cTgAm/XgOBHpnGbVPuR69zqQaI /kw3MMm49c5lX/gg85VmCrTqQxXgKQOxIlAeVZtSD7NHyRQ4tC/lJ5x7AGv1x+woMhFo BEfo+uSWAYuCt8I7Xz2vLUholKdDXDBYHEbwP587dzLoYFoOaFztNuAC2GanEdFZ8OQR zBUOh1VLu2X8ypu3Fq6h76tXquL8/F32CikTmOtlV8GLLNJyuwYjionQYO9ejhJ5TOjl iQMT6WznOWfoubhjwTo/Lktft2YYXIbLnssQ8b1VnhjO4XwwKfnr8VBpucYR2a2geN0c UCGg== X-Forwarded-Encrypted: i=1; AHgh+RqnLtZtEQooC0nnyRsQjBOrJf+tyld/8Gw04JkLiH0AFVIWcDprOnsi2+Z8bUAdxVqUe7MSdAjEu7eciCU=@vger.kernel.org X-Gm-Message-State: AOJu0YwaMjHBcd0BgNJ6SCXGiuHUBawXWSIvWuiNZY7v852ghdC2n+ul YFtrPhWuZVlv1HpJw6jfhdHQJuVaG98cU/hVp8y4+albB/uTSg5X4USkaW6Sch/b5Q== X-Gm-Gg: AfdE7ckQlPso5fX9xvyIImzcLHpW/VrzzaxwsnqM38Vw9ZxDOlXBQi/p2IAl9PDvGHV I+WnI7H/9FUlI4zrgzzoAuZnCKSMVW6v+G4bvFxbqwrKctmWI2HDvRtPEtMqLWbNHF+3wUSIjIB EHi2WWdGEFs+OGZsOam+6Cw/Ik3fxDUDNAATpKvE8Lucac/jFUmJng3BrOXy4bNo3YATrTSxtSP 8zox+cKLXrK/NYVnLgzlKM7C6j8alEALrfgdWXz88K8W0YVsT4wnvM8hgSx5MW6Y3CZdBmefkm1 KqCPHCIvRw+q0iOKKIY0IggGXjk2pm4/NBJWnJtfW8g6Nv6phgPKXzdz2sGoQU2dP0tsaNvEiyo u9baiTEl+5QNThYTVahu7qs3yG6/b+If4k11UY08NkMRTtG1Jv7+qYI4EFTN3a75frzQSiVolj7 6ct00tHMIBJXQC1fVGkGDl5OEl+kOAKH0nBgT4VcGqQeXMyca2rDQ9K7vU X-Received: by 2002:a05:6000:41d0:b0:470:e26d:88ff with SMTP id ffacd0b85a97d-479343d1d3dmr3253927f8f.4.1783047701016; Thu, 02 Jul 2026 20:01:41 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:40 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 04/18] rust: drm: kms: build the kernel crate clean under -Znext-solver Date: Fri, 3 Jul 2026 04:00:51 +0100 Message-ID: <20260703030123.2814-5-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The coinductive cycle from the previous commit isn't a bug in this port -- it's inherent to the mutual type Driver / type State / type Crtc references in the mode-object traits, and the current trait solver can't evaluate a coinductive obligation without unbounded memory, regardless of where the bounds are placed. Disabling the unused from_opaque-on-state impls doesn't help either: their errors were masking the same explosion one level down. The next-generation trait solver (-Znext-solver) does handle it. Scope it to the kernel crate only in rust/Makefile rather than globally, so core/alloc/etc. stay on the old solver -- core.o ICEs under -Znext-solver on this toolchain, and there's no reason to pay that cost crate-wide when only kernel/ needs the new solver's coinductive support. rust/kernel.o now builds with 0 errors, safe KMS layer included. Fixes needed to get there once the solver could actually evaluate the web: restore the pinned associated-type bounds (no back-edge cuts needed under the new solver); add Device::event_lock(), ModeConfigGuard::new(); import AlwaysRefCounted/SpinLockIrq; Driver*::new() takes &Device<.., Uninit> since objects are created during probe; base drift in drm_crtc_helper_funcs (drop mode_set_base_atomic, add handle_vblank_timeout); device.rs T::Object::::ALLOC_OPS and a probe_kms Option rewrite; driver.rs captures preferred_fourcc and uses the registered device after mem::forget(drm); unused imports dropped; allow(dead_code) on the not-yet-used ModeObjectVtable. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- rust/Makefile | 6 ++-- rust/kernel/drm/device.rs | 12 ++++---- rust/kernel/drm/driver.rs | 8 ++--- rust/kernel/drm/kms.rs | 51 +++++++++++++++++++++++--------- rust/kernel/drm/kms/connector.rs | 31 ++++++------------- rust/kernel/drm/kms/crtc.rs | 35 ++++++++-------------- rust/kernel/drm/kms/encoder.rs | 4 +-- rust/kernel/drm/kms/modes.rs | 5 ++++ rust/kernel/drm/kms/plane.rs | 31 ++++++------------- rust/kernel/drm/kms/vblank.rs | 7 ++--- 10 files changed, 91 insertions(+), 99 deletions(-) diff --git a/rust/Makefile b/rust/Makefile index a870d1616c71..9aa985e21905 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -270,7 +270,7 @@ rustdoc-kernel: private is-kernel-object :=3D y rustdoc-kernel: private rustc_target_flags =3D --extern ffi --extern pin_i= nit \ --extern build_error --extern macros \ --extern bindings --extern uapi \ - --extern zerocopy --extern zerocopy_derive + --extern zerocopy --extern zerocopy_derive -Znext-solver rustdoc-kernel: $(src)/kernel/lib.rs rustdoc-core rustdoc-ffi rustdoc-macr= os \ rustdoc-pin_init rustdoc-compiler_builtins $(obj)/$(libmacros_name) \ $(obj)/bindings.o FORCE @@ -342,7 +342,7 @@ rusttestlib-pin_init: $(src)/pin-init/src/lib.rs rustte= stlib-macros \ rusttestlib-kernel: private rustc_target_flags =3D --extern ffi \ --extern build_error --extern macros --extern pin_init \ --extern bindings --extern uapi \ - --extern zerocopy --extern zerocopy_derive + --extern zerocopy --extern zerocopy_derive -Znext-solver rusttestlib-kernel: $(src)/kernel/lib.rs rusttestlib-bindings rusttestlib-= uapi \ rusttestlib-build_error rusttestlib-pin_init $(obj)/$(libmacros_name) \ $(obj)/bindings.o rusttestlib-zerocopy rusttestlib-zerocopy_derive FOR= CE @@ -764,7 +764,7 @@ $(obj)/uapi.o: $(src)/uapi/lib.rs \ =20 $(obj)/kernel.o: private rustc_target_flags =3D --extern ffi --extern pin_= init \ --extern build_error --extern macros --extern bindings --extern uapi \ - --extern zerocopy --extern zerocopy_derive + --extern zerocopy --extern zerocopy_derive -Znext-solver $(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/build_error.o $(obj)/pin_init= .o \ $(obj)/$(libmacros_name) $(obj)/bindings.o $(obj)/uapi.o \ $(obj)/zerocopy.o $(obj)/$(libzerocopy_derive_name) FORCE diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index 9641102bd87f..5b84ce18fb29 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -229,7 +229,7 @@ const fn compute_features() -> u32 { dumb_map_offset: T::Object::::ALLOC_OPS.dumb_map_offset, =20 show_fdinfo: None, - fbdev_probe: T::Object::ALLOC_OPS.fbdev_probe, + fbdev_probe: T::Object::::ALLOC_OPS.fbdev_probe, =20 major: T::INFO.major, minor: T::INFO.minor, @@ -306,15 +306,17 @@ pub fn new( // won't be called during its lifetime and that the device is unre= gistered. let drm_dev =3D unsafe { ARef::from_raw(raw_drm) }; =20 - let mode_config_info =3D drm::Device::::has_kms().then(|| { + let mode_config_info =3D if drm::Device::::has_kms() { // SAFETY: This is the only place that we call probe_kms - unsafe { T::Kms::probe_kms(&drm_dev)? } - }); + Some(unsafe { T::Kms::probe_kms(&drm_dev)? }) + } else { + None + }; =20 Ok(Self { // SAFETY: We just completed all of the initialization steps f= or this device. dev: unsafe { mem::transmute(drm_dev) }, - preferred_fourcc: mode_config_info.preferred_fourcc, + preferred_fourcc: mode_config_info.and_then(|i| i.preferred_fo= urcc), _p: NotThreadSafe, }) } diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs index c7716afabcb0..b7e80df6289e 100644 --- a/rust/kernel/drm/driver.rs +++ b/rust/kernel/drm/driver.rs @@ -10,7 +10,6 @@ devres, drm::{ self, - kms::private::KmsImpl as KmsImplPrivate, }, error::to_result, prelude::*, @@ -214,6 +213,7 @@ fn new<'bound, E>( return Err(e); } =20 + let preferred_fourcc =3D drm.preferred_fourcc; // SAFETY: We just called `drm_dev_register` above let new =3D NonNull::from(unsafe { drm.assume_ctx() }); =20 @@ -226,12 +226,12 @@ fn new<'bound, E>( =20 #[cfg(CONFIG_DRM_CLIENT =3D "y")] if drm::Device::::has_kms() { - if let Some(fourcc) =3D drm.preferred_fourcc { + if let Some(fourcc) =3D preferred_fourcc { // SAFETY: We just registered `drm` above, fulfilling the = C API requirements - unsafe { bindings::drm_client_setup_with_fourcc(drm.as_raw= (), fourcc) } + unsafe { bindings::drm_client_setup_with_fourcc(new.as_raw= (), fourcc) } } else { // SAFETY: We just registered `drm` above, fulfilling the = C API requirements - unsafe { bindings::drm_client_setup(drm.as_raw(), ptr::nul= l()) } + unsafe { bindings::drm_client_setup(new.as_raw(), ptr::nul= l()) } } } =20 diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index d07389fd510c..7c7e1d59c892 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -12,7 +12,7 @@ }, error::to_result, prelude::*, - sync::{aref::ARef, Mutex, MutexGuard}, + sync::{aref::{ARef, AlwaysRefCounted}, Mutex, MutexGuard, SpinLockIrq}, }; use bindings; use core::{ @@ -122,23 +122,25 @@ fn deref(&self) -> &Self::Target { /// [`PhantomData`]: PhantomData #[vtable] pub trait KmsDriver: Driver { - // The driver's mode-object implementations. Left *unbounded* to break= a trait - // well-formedness cycle: the natural bound (`type Crtc: DriverCrtc`, etc.) - // makes every `T: KmsDriver` obligation pull in the mutually-recursiv= e Driver* web, which - // the compiler cannot evaluate (it loops in obligation processing and= exhausts memory). The - // `DriverConnector`/`DriverCrtc`/... bounds are required at the use s= ites as `where`-clauses - // instead (an assumption, not an eagerly WF-checked trait-definition = obligation). - // - // TODO: unneeded once we support multiple `Driver*` implementations p= er driver. - /// The driver's [`DriverConnector`](connector::DriverConnector) imple= mentation. - type Connector; + /// + /// TODO: This will be unneeded once we support multiple `DriverConnec= tor` implementations. + type Connector: connector::DriverConnector; + /// The driver's [`DriverPlane`](plane::DriverPlane) implementation. - type Plane; + /// + /// TODO: This will be unneeded once we support multiple `DriverPlane`= implementations. + type Plane: plane::DriverPlane; + /// The driver's [`DriverCrtc`](crtc::DriverCrtc) implementation. - type Crtc; + /// + /// TODO: This will be unneeded once we support multiple `DriverCrtc` = implementations. + type Crtc: crtc::DriverCrtc; + /// The driver's [`DriverEncoder`](encoder::DriverEncoder) implementat= ion. - type Encoder; + /// + /// TODO: This will be unneeded once we support multiple `DriverEncode= r` implementations. + type Encoder: encoder::DriverEncoder; =20 /// Return a [`ModeConfigInfo`] structure for this [`device::Device`]. fn mode_config_info(dev: &Device) -> Result @@ -372,6 +374,13 @@ pub fn num_crtcs(&self) -> u32 { // * num_crtc is always >=3D 0, so casting to u32 is fine unsafe { (*self.as_raw()).mode_config.num_crtc as u32 } } + + /// Returns a reference to the `event` spinlock for this [`Device`]. + #[inline] + pub(crate) fn event_lock(&self) -> &SpinLockIrq<()> { + // SAFETY: `event_lock` is initialized for as long as `self` is ex= posed to users. + unsafe { SpinLockIrq::from_raw(addr_of_mut!((*self.as_raw()).event= _lock)) } + } } =20 impl Device { @@ -512,6 +521,7 @@ unsafe fn dec_ref(obj: core::ptr::NonNull) { /// /// `ModeObjectVtable::vtable()` must always return a valid pointer to the= relevant mode object's /// vtable. +#[allow(dead_code)] // Scaffolding for future vtable-pointer comparison; s= ee doc above. pub(crate) unsafe trait ModeObjectVtable { /// The type for the auto-generated vtable. type Vtable; @@ -539,6 +549,19 @@ pub(crate) unsafe trait ModeObjectVtable { pub struct ModeConfigGuard<'a, T: KmsDriver>(MutexGuard<'a, ()>, PhantomDa= ta); =20 impl<'a, T: KmsDriver> ModeConfigGuard<'a, T> { + /// Construct a new [`ModeConfigGuard`]. + /// + /// # Safety + /// + /// The caller must ensure that `drm_device.mode_config.mutex` is acqu= ired. + pub(crate) unsafe fn new(drm: &'a Device) -> Self { + // SAFETY: Our safety contract fulfills the requirements of `Mutex= Guard::new()`. + Self( + unsafe { MutexGuard::new(drm.mode_config_mutex(), ()) }, + PhantomData, + ) + } + /// Return the [`Device`] that this [`ModeConfigGuard`] belongs to. pub fn drm_dev(&self) -> &'a Device { let lock: *mut bindings::mutex =3D ptr::from_ref(self.0.lock_ref()= ).cast_mut().cast(); diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connect= or.rs index aca7be50e583..1c7c7b586340 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -10,7 +10,7 @@ use crate::{ alloc::KBox, bindings, - drm::{device::Device, kms::{NewKmsDevice, Probing}}, + drm::{device::{Device, Uninit}, kms::{NewKmsDevice, Probing}}, error::to_result, prelude::*, types::{NotThreadSafe, Opaque}, @@ -139,12 +139,12 @@ pub trait DriverConnector: Send + Sync + Sized { /// The [`DriverConnectorState`] implementation for this [`DriverConne= ctor`]. /// /// See [`DriverConnectorState`] for more info. - type State: DriverConnectorState; + type State: DriverConnectorState; =20 /// The constructor for creating a [`Connector`] using this [`DriverCo= nnector`] implementation. /// /// Drivers may use this to instantiate their [`DriverConnector`] obje= ct. - fn new(device: &Device, args: Self::Args) -> impl PinIni= t; + fn new(device: &Device, args: Self::Args) -> imp= l PinInit; =20 /// Retrieve a list of available display modes for this [`Connector`]. fn get_modes<'a>( @@ -700,17 +700,13 @@ pub struct 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`]. Unbounded to break the trait WF cy= cle; the - /// `DriverConnector` bound is required at use sites instead. - type Connector; + /// The parent [`DriverConnector`]. + type Connector: DriverConnector; } =20 impl Sealed for ConnectorState {} =20 -impl AsRawConnectorState for ConnectorState -where - T::Connector: DriverConnector, -{ +impl AsRawConnectorState for ConnectorState { type Connector =3D Connector; } =20 @@ -724,10 +720,7 @@ unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_= connector_state { } } =20 -impl FromRawConnectorState for ConnectorState -where - T::Connector: DriverConnector, -{ +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 =3D ptr.cast(); @@ -866,10 +859,7 @@ pub(super) fn new( } } =20 -impl<'a, T: DriverConnectorState> Deref for ConnectorStateMutator<'a, Conn= ectorState> -where - T::Connector: DriverConnector, -{ +impl<'a, T: DriverConnectorState> Deref for ConnectorStateMutator<'a, Conn= ectorState> { type Target =3D T; =20 fn deref(&self) -> &Self::Target { @@ -877,10 +867,7 @@ fn deref(&self) -> &Self::Target { } } =20 -impl<'a, T: DriverConnectorState> DerefMut for ConnectorStateMutator<'a, C= onnectorState> -where - T::Connector: DriverConnector, -{ +impl<'a, T: DriverConnectorState> DerefMut for ConnectorStateMutator<'a, C= onnectorState> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.state.inner } diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 4c6cb6bbdcb8..9b43501bfb58 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -11,7 +11,7 @@ use crate::{ alloc::KBox, bindings, - drm::device::Device, + drm::device::{Device, Uninit}, error::{from_result, to_result}, prelude::*, types::{NotThreadSafe, Opaque}, @@ -107,8 +107,8 @@ pub trait DriverCrtc: Send + Sync + Sized { }, mode_set_nofb: None, mode_set_base: None, - mode_set_base_atomic: None, get_scanout_position: None, + handle_vblank_timeout: None, }, }; =20 @@ -124,7 +124,7 @@ pub trait DriverCrtc: Send + Sync + Sized { /// The [`DriverCrtcState`] implementation for this [`DriverCrtc`]. /// /// See [`DriverCrtcState`] for more info. - type State: DriverCrtcState; + type State: DriverCrtcState; =20 /// The driver's optional hardware vblank implementation /// @@ -135,7 +135,7 @@ pub trait DriverCrtc: Send + Sync + Sized { /// 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 PinIn= it; + fn new(device: &Device, args: &Self::Args) -> im= pl PinInit; =20 /// The optional [`drm_crtc_helper_funcs.atomic_check`] hook for this = crtc. /// @@ -561,9 +561,10 @@ impl Sealed for 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. Unbounded to break the= trait WF cycle; - /// the `DriverCrtc` bound is required at use sites instead. - type Crtc; + /// The parent CRTC driver for this CRTC state + type Crtc: DriverCrtc + where + Self: Sized; } =20 /// The main interface for a [`struct drm_crtc_state`]. @@ -701,17 +702,11 @@ fn as_raw(&self) -> *mut bindings::drm_crtc_state { } } =20 -impl AsRawCrtcState for CrtcState -where - T::Crtc: DriverCrtc, -{ +impl AsRawCrtcState for CrtcState { type Crtc =3D Crtc; } =20 -impl FromRawCrtcState for CrtcState -where - T::Crtc: DriverCrtc, -{ +impl FromRawCrtcState for CrtcState { unsafe fn from_raw<'a>(ptr: *const bindings::drm_crtc_state) -> &'a Se= lf { // SAFETY: Our data layout starts with `bindings::drm_crtc_state` unsafe { &*(ptr.cast()) } @@ -818,10 +813,7 @@ fn drop(&mut self) { } } =20 -impl<'a, T: DriverCrtcState> Deref for CrtcStateMutator<'a, CrtcState> -where - T::Crtc: DriverCrtc, -{ +impl<'a, T: DriverCrtcState> Deref for CrtcStateMutator<'a, CrtcState> { type Target =3D T; =20 fn deref(&self) -> &Self::Target { @@ -831,10 +823,7 @@ fn deref(&self) -> &Self::Target { } } =20 -impl<'a, T: DriverCrtcState> DerefMut for CrtcStateMutator<'a, CrtcState> -where - T::Crtc: DriverCrtc, -{ +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 r= ust's data-aliasing rules, // so this is safe diff --git a/rust/kernel/drm/kms/encoder.rs b/rust/kernel/drm/kms/encoder.rs index 5f860faf8b61..47fd223a4d3a 100644 --- a/rust/kernel/drm/kms/encoder.rs +++ b/rust/kernel/drm/kms/encoder.rs @@ -9,7 +9,7 @@ }; use crate::{ alloc::KBox, - drm::device::Device, + drm::device::{Device, Uninit}, error::to_result, prelude::*, types::{NotThreadSafe, Opaque}, @@ -105,7 +105,7 @@ pub trait DriverEncoder: Send + Sync + Sized { /// The constructor for creating a [`Encoder`] using this [`DriverEnco= der`] implementation. /// /// Drivers may use this to instantiate their [`DriverEncoder`] object. - fn new(device: &Device, args: Self::Args) -> impl PinIni= t; + fn new(device: &Device, args: Self::Args) -> imp= l PinInit; } =20 /// The generated C vtable for a [`DriverEncoder`]. diff --git a/rust/kernel/drm/kms/modes.rs b/rust/kernel/drm/kms/modes.rs index 0e8dc434487d..88ea27729212 100644 --- a/rust/kernel/drm/kms/modes.rs +++ b/rust/kernel/drm/kms/modes.rs @@ -1,4 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 + +//! DRM display mode definitions ([`struct drm_display_mode`]). +//! +//! [`struct drm_display_mode`]: srctree/include/drm/drm_modes.h + use bindings; =20 use crate::types::Opaque; diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 47e05fe6bee1..375f79bd3ceb 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -11,7 +11,7 @@ use crate::{ alloc::KBox, bindings, - drm::{device::Device, fourcc::*}, + drm::{device::{Device, Uninit}, fourcc::*}, error::{from_result, to_result, Error}, prelude::*, types::{NotThreadSafe, Opaque}, @@ -98,12 +98,12 @@ pub trait DriverPlane: Send + Sync + Sized { /// The [`DriverPlaneState`] implementation for this [`DriverPlane`]. /// /// See [`DriverPlaneState`] for more info. - type State: DriverPlaneState; + type State: DriverPlaneState; =20 /// 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 PinIni= t; + fn new(device: &Device, args: Self::Args) -> imp= l PinInit; =20 /// The optional [`drm_plane_helper_funcs.atomic_update`] hook for thi= s plane. /// @@ -689,17 +689,13 @@ pub struct 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. Unbounded to = break the trait WF - /// cycle; the `DriverPlane` bound is required at use sites instead. - type Plane; + /// The type for this driver's drm_plane implementation + type Plane: DriverPlane; } =20 impl Sealed for PlaneState {} =20 -impl AsRawPlaneState for PlaneState -where - T::Plane: DriverPlane, -{ +impl AsRawPlaneState for PlaneState { type Plane =3D Plane; } =20 @@ -713,10 +709,7 @@ unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_= plane_state { } } =20 -impl FromRawPlaneState for PlaneState -where - T::Plane: DriverPlane, -{ +impl FromRawPlaneState for PlaneState { unsafe fn from_raw<'a>(ptr: *const bindings::drm_plane_state) -> &'a S= elf { // Our data layout starts with `bindings::drm_plane_state`. let ptr: *const Self =3D ptr.cast(); @@ -892,10 +885,7 @@ unsafe fn as_raw_mut(&mut self) -> &mut bindings::drm_= plane_state { =20 impl<'a, T: FromRawPlaneState> Sealed for PlaneStateMutator<'a, T> {} =20 -impl<'a, T: DriverPlaneState> Deref for PlaneStateMutator<'a, PlaneState> -where - T::Plane: DriverPlane, -{ +impl<'a, T: DriverPlaneState> Deref for PlaneStateMutator<'a, PlaneState> { type Target =3D T; =20 fn deref(&self) -> &Self::Target { @@ -903,10 +893,7 @@ fn deref(&self) -> &Self::Target { } } =20 -impl<'a, T: DriverPlaneState> DerefMut for PlaneStateMutator<'a, PlaneStat= e> -where - T::Plane: DriverPlane, -{ +impl<'a, T: DriverPlaneState> DerefMut for PlaneStateMutator<'a, PlaneStat= e> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.state.inner } diff --git a/rust/kernel/drm/kms/vblank.rs b/rust/kernel/drm/kms/vblank.rs index 534fd18f63b4..84e5cbbe923d 100644 --- a/rust/kernel/drm/kms/vblank.rs +++ b/rust/kernel/drm/kms/vblank.rs @@ -4,7 +4,7 @@ //! //! C header: [`include/drm/drm_vblank.h`](srcfree/include/drm/drm_vblank.= h) =20 -use super::{crtc::*, ModeObject, modes::*, Sealed}; +use super::{crtc::*, ModeObject, modes::*}; use bindings; use core::{ marker::*, @@ -62,9 +62,8 @@ fn get_vblank_timestamp( /// Drivers interested in implementing vblank support should refer to [`Vb= lankSupport`], drivers /// that don't have vblank support can use [`PhantomData`]. pub trait VblankImpl { - /// The parent [`DriverCrtc`]. Unbounded to break the trait WF cycle; = the `DriverCrtc` - /// bound is required at use sites instead. - type Crtc; + /// The parent [`DriverCrtc`]. + type Crtc: DriverCrtc; =20 /// The generated [`VblankOps`]. const VBLANK_OPS: VblankOps; --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f53.google.com (mail-wr1-f53.google.com [209.85.221.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9CC0D2D24B7 for ; Fri, 3 Jul 2026 03:01:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047707; cv=none; b=lLWosZdB29mbN3+ukl8FknshcSyRBFdPOyLTOkAl867vxPBd8LRH86C4MfB02Gra31oFdcpcvqMalZl7ZwPeV8UJSh84lXUBpdDZJfYUp23Hko/dOgqmUrVrPMK085qVWvEvUWlqMVd84fCXYU5bIdACR8XuWX4o9+UReqJGUa0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047707; c=relaxed/simple; bh=dB+tS1CnTM6uVgw+YPAVoV1OkGKjsFnEbQi5dhpF7ZA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XMiqVccW1ruimek9GF0Oeyax1UQ5GY/bohMOlpiwyAchQmym6TXejPJOhZOVHW4lL/+V3XsIKsMyJd6EXlu2E5MIKR0CFFPcDHo0Rqa9AmKmfgFapaZHuFVQrvszjraECuvTb3ehk4ZAcTSv78H/Hifz2vni1rmAdVhLaCHtozk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=UTFJnnV3; arc=none smtp.client-ip=209.85.221.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="UTFJnnV3" Received: by mail-wr1-f53.google.com with SMTP id ffacd0b85a97d-476a130c138so110769f8f.0 for ; Thu, 02 Jul 2026 20:01:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047704; x=1783652504; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=CRHhS8HiexEZn0tYM2xQs+F4mqPS5akyoK5HQQpQ99s=; b=UTFJnnV3Nl/gKnN20jhAz2AyDryWae6EBS6J5zVnqS+DfafaHNTJt9wIhIPwQXvIAe Rd6h4CCWCr2uTqiUrTJZjnrAxDOe0T35OkzGN4mTGxSpQWVDs3Ixwy4JZ+XH/P8AEpAk 7WgFmRFkdHDHq279CSPvwSo+T0WDv00NcrukgSVpZsLCgqHcFVRsMWXAu/rhnGxvtSwq sn4afclKyK1liYqxQMY3Uu9fn9rQtl4vOXiLEPS0vnh+B8noTmNjDENTzzRFc3DNsfVK 5R9K+HxgF7PA9fDBND3rjvrRiYxt/Ms/SUa4utGOvvisw16nISy6DbIixuhMrK2XfykM BVyg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047704; x=1783652504; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=CRHhS8HiexEZn0tYM2xQs+F4mqPS5akyoK5HQQpQ99s=; b=IrszkgZ16fGXLkZMqXCtDMVOZwe9ulj53FWrNEf0JQnXGt2DQxumYssudEMRbt55Q8 80CCpRRldu7gTiQKXw6JJMrRkZ0WI9GcK+wSSYXGPDwgpq5QRohVubOeQzRscKV6c2KM ztxfaowErNegvNRpz+oZlQdjbGVRtF8CkHaiOCi83IdnGxfWT/YkO/UR43hFQWCsfDa0 cmwKpiIeLYxoYwiv+bbFOB0Qmp5xxvO/HjzSwNt6LKAQGleEu0smTuV7DOI1TUB2CZ5q 3BEQSAHBtQjgDYa0eqQySoejHVkx9pl7SAD7n/I2ZMecJdzqqLlbSkw09oGDdYvENVUH PsUw== X-Forwarded-Encrypted: i=1; AHgh+RqoPoKqMwaU1i5oSRGFEuWcvFGyt9nvxL/HAxuTFBdRxdDzBt9ezOyjv4QQ0Fb4yOnWJ+Zb+3HYZQW3Hfg=@vger.kernel.org X-Gm-Message-State: AOJu0Yzk7yLpmO1EZk/8pE5VuvWRIQeExw160GjQh7025zjHNgoITa2N /DDs/DihMPq/nizP78QFF/BptY2xj5n5/gWxm1E5inFfHSzjrMTGd6C9O9kY9JexxA== X-Gm-Gg: AfdE7cm0gsmOk3K6/y8Zkmzmu/cfGSwNIxndYBxQhvwHx8aqZV2Tiq0WvDr7P3meCoH sJ/YBO2/KL4qsgA+iyPuOdLSltMVd4aasEX1Q/x1dVfysh55YMRnl1OjzFxGqHAvhAtoRRCV5Fo VCi683GVdegoQv1ix1t1Ic3+eb1lvY+ZuhoK6qQC5fUONgYQ5EyZCnJNrgWZXs+ODEZNWD59vgR I30FVTukNMJLZ1i1Ymg9eN3kWnSa8niqFO9LLgQAx40dVVo80BNBCg5ujRe2xEn1tZYtn76s9mP MIhoY3pnlr1Dc+eMv8qyCcp/beo+UqCNmNDxxQJiVjAoYUJiUj6auWUsnsH9Vrud2QG0wEwNAnd JuoEYvtnuYCTIGwXXPg69XmbJVyXXsUi2MDhNLR2keJkclhphlzYzy2rZRWB1YFxsdbMMuF0UeE EzdXHrHGwCvg0uhtoSi1ONUbuDGGdBMXEhHponLFFhrKrGV9F817HFjPBi X-Received: by 2002:a5d:5f56:0:b0:470:390c:1e73 with SMTP id ffacd0b85a97d-477b39839cbmr12242543f8f.18.1783047704135; Thu, 02 Jul 2026 20:01:44 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:41 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 05/18] rust: drm: expose HDCP 2.2 message definitions Date: Fri, 3 Jul 2026 04:00:52 +0100 Message-ID: <20260703030123.2814-6-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add to the bindings so Rust DRM drivers can use the canonical HDCP 2.2 message IDs (HDCP_2_2_AKE_INIT .. HDCP_2_2_REP_STREAM_READY), length constants and hdcp2_* message structs instead of redefining them. The header only pulls in , so it adds no new heavy dependencies. The Vino DisplayLink DL3 driver uses these for its HDCP 2.2 AKE/LC/SKE handshake (carried over a vendor USB transport rather than the DP/HDMI HDCP transport, so only the message definitions are reused, not the drm_hdcp_helper state machine). Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- rust/bindings/bindings_helper.h | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index e41d2716fb2e..e5cfda610781 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -34,6 +34,7 @@ =20 #include #include +#include #include #include #include --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f52.google.com (mail-wr1-f52.google.com [209.85.221.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 671BF2DCBE3 for ; Fri, 3 Jul 2026 03:01:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047711; cv=none; b=HdrRwyTxJHv/52mbtbGqTUZvwmPGXUznPi/9xDZ9v980HQ61a+ncL2BCErvq66DhI2QGz5kTjOM8oB0uVO4xicv/A9vlhSF3efwDZeGl4cVBoC1VOE11hCHGViF0LmU1RAKoETTjHF5BYrH71jMexUi9HvrCnUy6K6C07bl9Vc4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047711; c=relaxed/simple; bh=49o7iQehUzzXLjEc4rnX2yNZzvxbN0E2UjMy04lft4E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=N4sflGEV9jMYezpPwAkxKssI1nWssIXd3N+qUcQSc0VfV1SiVMDdQ8mKUHfm6V0F1q8LkxbckFc1KMn1DlpS+9oQ3GNXFDK/XIFgE2G0Hef0pMlngtjgr3sgmhs8MzQEsCIPlua3FxpYfm+Mjy1Rb0HngJbjr+yQzzUZsyAdA1s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=ygGxZp8v; arc=none smtp.client-ip=209.85.221.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="ygGxZp8v" Received: by mail-wr1-f52.google.com with SMTP id ffacd0b85a97d-47640541585so52753f8f.1 for ; Thu, 02 Jul 2026 20:01:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047707; x=1783652507; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=DO10bahdwhwvl3OteTgaG/ReLzyVlHiXZrXAAYDCZ/0=; b=ygGxZp8vO+PMorq7gZFV2IKnwKKhLLSa7bAb2r+f9hf29A9m8O8UdKGdNXbqOJiGc5 51NhtB0FIElN1ZyQz0MHzEvMj91fXBeqfoTsGf9N/KmoWjqGikl2s7IPiKzZYvCit96o 6lGbP70ruNRM/5kqueCXePD+Qhg0OB+e7FlzSqxemJx7B6ndMsrRxe/dGho2McX+tzfx z+fCASZ2JHU/+RaKFZ0AZSnEsPG0xZ9ylyAjjLroPz8ruINH1sIJrp+y8QLF3Vmffwqz SiECEmqRCG57L2Zv8SMfCVKSeD6mlPdk5DCmq/Ejgillr/fIbajS7ZmdpHhZQCFIHY35 8aXA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047707; x=1783652507; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=DO10bahdwhwvl3OteTgaG/ReLzyVlHiXZrXAAYDCZ/0=; b=T3vWNzbMbrIHIEdft0GpNHPAwsYXLVlla/Br3mNXChKgvYu91hE6Kn0wZ5lyxGXFxK z5962or/hmly+3QNnDUUxV9v/YkCNMU5X5FwSinOLlkHi5qNFsefq0tLDU9mIQhz893p bFdYttQsDnm1xqPpVRHNz0TpClnaW2wVaWa8kPxhVodNEYfX9Q96NcyrJX1ZvYCfstdV F1H2TnrEtDkQO4jEzgUNe9tR/Zat+X0LRyC8ux69JhwDgkCkHlZHN/R0Vx5vdkSVH8XH j86ueUN6x8tHgPMjaTXqGF9Ea28i/wch/gdPjU1nLqDwJPgm0GZVZ77nUPOWwLSwkcQZ yg/A== X-Forwarded-Encrypted: i=1; AHgh+RomITXpfDefy6x4qWHfFCJEGVEKuyx2oyoMvwKiqqnM+akL7Smq5iJDb0DIsSLPuLU3PDHQMz5FKaZC0eA=@vger.kernel.org X-Gm-Message-State: AOJu0YwKVDtxNULB+f7Wa6MLurQd2vdywj33XYMBCcHg/9/Mt7G9L8xk mP3FrK5dAkluTGitoVJGn0nm0MdxSn+0FdbEoSbMAUuUUyv4wL0vrKxt6IXfIVolHQ== X-Gm-Gg: AfdE7ckMjOfdNo6rvaU5qp0M3kWaeteK4Uf5lx/EE6Ypbtm2AufuCNp/CEkBmk+AoLX zntklr5bP0gx/AliOwPXF/NscHE2muAciN3JXFcB+pVxrKDkMauVRIaik32KouK3yoWrqm1h08f rXCu6CEh3fsUYlE4/hfMmpCkLF4uNrE8Hq32vG9GvLYd6mYtS9yhD23vxJWp7l6bCziiNzenV9z o7HIJVDVEKO7fUQPnJNvAmvvYM2SupGKqVtcSt7ikOxr8pAivm/lSie78ub+acUlRiqocO3s6WQ SxJBxIksGf1dlmrvJO4vcUFF+kkBug4MTRzvzfxzaES/XtSSWakvkBP9DqhLUCjUihFjXg8chFl dJsU7K01LAu9conFVhNWkbUqMZB8YBoCy8YqYyZjH7Klw4Hd6AtwNfYEDpnD8ZZz7ddBo5q5vll uJV2lzpPcrh64GNiAwehD4eYhzcguU/rA9x74faEcaGR4tFWiWTZSMlFBO X-Received: by 2002:a05:6000:5c5:b0:474:530:9d with SMTP id ffacd0b85a97d-47757797bf8mr13513555f8f.13.1783047706825; Thu, 02 Jul 2026 20:01:46 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:45 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 06/18] rust: drm: kms: add a Framebuffer::vmap() guard Date: Fri, 3 Jul 2026 04:00:53 +0100 Message-ID: <20260703030123.2814-7-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Framebuffer::vmap() maps a framebuffer's plane-0 backing pages into the kernel address space via drm_gem_fb_vmap()/vunmap(), returning an RAII FramebufferVmap guard that unmaps on drop. Only single-CPU-visible-plane framebuffers are supported (packed formats backed by GEM-shmem/CMA; multi-plane YUV or an unmapped imported dma-buf return EINVAL). This is the driver-side FbVmap guard the v1 series carried, moved into the layer proper so every KMS driver gets it instead of hand-rolling the same map/use/unmap dance. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- rust/kernel/drm/kms/framebuffer.rs | 67 +++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/kms/framebuffer.rs b/rust/kernel/drm/kms/frame= buffer.rs index 54d0391388a9..1ec6779ba7de 100644 --- a/rust/kernel/drm/kms/framebuffer.rs +++ b/rust/kernel/drm/kms/framebuffer.rs @@ -5,7 +5,12 @@ //! C header: [`include/drm/drm_framebuffer.h`](srctree/include/drm/drm_fr= amebuffer.h) =20 use super::{KmsDriver, ModeObject, Sealed}; -use crate::{drm::device::Device, types::*}; +use crate::{ + drm::device::Device, + error::{code::EINVAL, to_result}, + prelude::*, + types::*, +}; use bindings; use core::{marker::*, ptr}; =20 @@ -67,4 +72,64 @@ pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::= drm_framebuffer) -> &'a // SAFETY: Our data layout is identical to drm_framebuffer unsafe { &*ptr.cast() } } + + /// Return the raw `bindings::drm_framebuffer` for this framebuffer. + #[inline] + pub(crate) fn as_raw(&self) -> *mut bindings::drm_framebuffer { + self.0.get() + } + + /// Map this framebuffer's plane-0 backing pages into the kernel addre= ss space for CPU + /// access, for the duration of the returned guard. + /// + /// Only framebuffers with a single, CPU-visible plane are supported (= i.e. packed formats + /// backed by GEM-shmem/CMA memory, not multi-plane YUV or an IMPORTED= dma-buf without a + /// CPU mapping); other cases return `EINVAL`. + pub fn vmap(&self) -> Result> { + // SAFETY: `iosys_map` is POD (a pointer union plus a bool); all-z= ero is a valid + // "not mapped" value that `drm_gem_fb_vmap` overwrites for presen= t planes. + let mut map: [bindings::iosys_map; 4] =3D unsafe { core::mem::zero= ed() }; + let mut data_map: [bindings::iosys_map; 4] =3D unsafe { core::mem:= :zeroed() }; + // SAFETY: `self.as_raw()` is a valid, GEM-backed framebuffer for = the lifetime of `self`. + to_result(unsafe { + bindings::drm_gem_fb_vmap(self.as_raw(), map.as_mut_ptr(), dat= a_map.as_mut_ptr()) + })?; + // SAFETY: `map[0]` was just filled in by `drm_gem_fb_vmap` above. + let vaddr =3D unsafe { map[0].__bindgen_anon_1.vaddr }; + if vaddr.is_null() { + // SAFETY: balances the vmap just done, with the same `map`. + unsafe { bindings::drm_gem_fb_vunmap(self.as_raw(), map.as_mut= _ptr()) }; + return Err(EINVAL); + } + Ok(FramebufferVmap { fb: self, map, _p: PhantomData }) + } +} + +/// An RAII guard over a CPU mapping of a [`Framebuffer`]'s plane-0 backin= g pages, created by +/// [`Framebuffer::vmap`]. +/// +/// The mapping is torn down when this guard is dropped, so an early retur= n between mapping and +/// use can never leak it. +pub struct FramebufferVmap<'a, T: KmsDriver> { + fb: &'a Framebuffer, + map: [bindings::iosys_map; 4], + _p: PhantomData, +} + +impl<'a, T: KmsDriver> FramebufferVmap<'a, T> { + /// Plane 0's CPU virtual base address (guaranteed non-null for the gu= ard's lifetime). + #[inline] + pub fn as_ptr(&self) -> *const u8 { + // SAFETY: set non-null in `Framebuffer::vmap`; `vaddr` is the act= ive union member. + (unsafe { self.map[0].__bindgen_anon_1.vaddr }) as *const u8 + } +} + +impl<'a, T: KmsDriver> Drop for FramebufferVmap<'a, T> { + fn drop(&mut self) { + // SAFETY: `self.fb.as_raw()`/`self.map` are exactly the pair pass= ed to + // `drm_gem_fb_vmap` in `Framebuffer::vmap`, and the mapping has n= ot been released + // since. + unsafe { bindings::drm_gem_fb_vunmap(self.fb.as_raw(), self.map.as= _mut_ptr()) }; + } } --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2A05A2DBF75 for ; Fri, 3 Jul 2026 03:01:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047712; cv=none; b=tzl0LxETksy70kZomzuOOqJLG2f0yjawEYjEdpNn2eIb8AWhQzeCE+dclQjt3V5W3EkKdbhHY8JI93WbmlCNLbcoXyhQk+xFXJt8A2aMRegkxdgMqozTVIWgpBFEj38fMPmhPfUM4K+dke9M/W3CNRnaUb8O5ngb5UvO2hzo5xQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047712; c=relaxed/simple; bh=J/R2kUO9M0+bB41SxDDCdrF1wmpi/UNqhaoyNk+2RrE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ecfSK0lwFEwi6OjBefiQTqQYUTnNSF26dZo4kX3R/USl94TFfqW9JAcR7xQGZ518g0pNxYlHHz0eqKjxuPWGBlUmzupYtYst2BeYCuDxh2fz+D73Deixr1zCkns7657aqxu21wFjacvJXXwhF45CWdox1vBWlRRla0l77y2/Bns= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=eVmniO20; arc=none smtp.client-ip=209.85.128.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="eVmniO20" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-493c83474ddso414535e9.3 for ; Thu, 02 Jul 2026 20:01:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047708; x=1783652508; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=9gvG7drN2QAfRGCCu8odT8w4W0DWwcbaOn1tC3EwTHo=; b=eVmniO20skBCStigbIVgX1vwp3iQe1t1/KwCn8853R2eSV53KhCvH8rDJdEKINmnGl L18SEw9YlHbLEa5l4BYUhfGhdT6dyg1bws3Joxaap63uzIh0ukpc+CRm00qhEZzr1hLG Zkzho/hRPdUtnEQ5ner3yPkWnXwJrVUYOrpBmoJOyQZS+METd1b3qfl8QzRzAZ9AfmXf uVg186zMutkAgvW/i9IwdAK9cnu6d2OdJmQggaDbnaEEUHifSzv5bvkuWXt+jGlay+Qs 98TCukwG4/bsmv0qCwTX19EF4V+saSYdk23+N60QNlUJD1oyDExA/NbBkOe5gjk1dKGL Vr9g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047709; x=1783652509; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=9gvG7drN2QAfRGCCu8odT8w4W0DWwcbaOn1tC3EwTHo=; b=S2zJu6YBZfxB0T8FVcRsSo8YkbbXqeoy5nsMB1pCP33b/W8E0qTb2qrVSYtNDAhHXx /at3/3kHz0GDcETIDCc4RRucWqtRY6tHWisWr2AuuYBJcYujIehL59cGgU7fzZdTCYMZ ZhblKPt8c8mfLyVV5wNutzdKyPSjwreDd5KZ1MsRf1Zwd05xnxdSw0aUsf7hrUlqXx+6 YuwA0x4D3nKfLY5Hp9nWXnb/W5904wQtBh2G3tOA7JxChecBOqyz4ernDT+10IDtqq4Q KtpFYlU7cqi0egbvehBricMv1tfJnlhZtE60XMMT1+qPUfoYhK/KFLfkh1i/QnOjrrH8 ZKTA== X-Forwarded-Encrypted: i=1; AFNElJ+OYIeXK7hrWUVnpl9vw86SLLEqtihGCzHPxth8QJGF4nf/j3hBwMBloOxQGs6gXTbWyDSmChHlyOYHLaQ=@vger.kernel.org X-Gm-Message-State: AOJu0Yz/fhbHRJSnv1uaDmHxTPoRfFPH5XoVNjxYUnypUdAx7x27JYM/ AqwPt01M+re735PGyKbYN51zeAVGz3grx29wb9fPVyxdNsOhnuw9vnCDpkfYj/Q8Iw== X-Gm-Gg: AfdE7clQ7ZKOF/9ufMszXioKR8ey1VObzqk2RqrKLP99jN5d8K5+C8jMFpFHzK3G6XA 9CCAaCWGcK/e1OEyjTfk4/9oj7phviSy+SKmeqhxA9TuIGflzLiUjFSE2Hp0QiYkLAQPsM0PkJB rCm4sVMYq88vfx3MKwgPtLs5MrWm+myPa4K1edRPoKjaMOxMSJKt9T394OqQh1zzFo/xRdIkUFR F8YCIi8z9IMJNfYG8p4vEXR8ZXrU3vMYdjynVsuVHsEjaFJExbHMFD8Z4lWFIgzIdTWsFvOsL2k opnknhMVMvC7bIdw7E5X8DB8+RruGZsxz7BCFp6A3WJUut3QJiz74DJ8XaibLVFdncVKhiOVWv1 daZ6BmB8BOVgecu46xt3XPQ0vPozuQATZJTIt35qehLwRv4td88i4yvL6WR0G0VxW6Gde8JEogs aNCeXEa5KRl/xIgvEejYoY38hC4PQhlAujWikc/cM4P4u/zdqj6gybiHbn X-Received: by 2002:a05:600c:34c8:b0:493:c2e3:b2c1 with SMTP id 5b1f17b1804b1-493c3df75ebmr115041365e9.33.1783047708439; Thu, 02 Jul 2026 20:01:48 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:47 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 07/18] rust: drm: kms: add safe accessors for common state and connector modes Date: Fri, 3 Jul 2026 04:00:54 +0100 Message-ID: <20260703030123.2814-8-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add safe wrappers for the handful of raw drm_kms fields and helpers a real KMS driver reaches for, so drivers need no raw escape hatch: - drm::Device::hotplug_event() wraps drm_kms_helper_hotplug_event(). - RawCrtcState::mode() returns the state's programmed DisplayMode. - DisplayMode gains hdisplay()/hsync_start()/hsync_end()/htotal()/ vdisplay()/vsync_start()/vsync_end()/vtotal()/clock()/vrefresh(). - RawPlaneState::crtc_w()/crtc_h() return the destination rectangle. - ConnectorGuard::add_edid_modes() parses an EDID blob, updates the connector and adds its modes (the safe counterpart to the existing add_modes_noedid()). Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- rust/kernel/drm/kms.rs | 12 ++++++ rust/kernel/drm/kms/connector.rs | 20 +++++++++ rust/kernel/drm/kms/crtc.rs | 13 +++++- rust/kernel/drm/kms/modes.rs | 70 ++++++++++++++++++++++++++++++++ rust/kernel/drm/kms/plane.rs | 10 +++++ 5 files changed, 123 insertions(+), 2 deletions(-) diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 7c7e1d59c892..7207466094e7 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -114,6 +114,18 @@ fn deref(&self) -> &Self::Target { } } =20 +impl Device { + /// Send a hotplug uevent to userspace, prompting it to re-probe conne= ctor state. + /// + /// A thin safe wrapper over `drm_kms_helper_hotplug_event()`, for dri= vers that detect a + /// connection change out of band (for example a dock delivering an ED= ID after bring-up). + pub fn hotplug_event(&self) { + // SAFETY: `self.as_raw()` is our live `drm_device`; a KMS driver = has the KMS helper + // state this call requires. + unsafe { bindings::drm_kms_helper_hotplug_event(self.as_raw()) }; + } +} + /// 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 diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connect= or.rs index 1c7c7b586340..591153de7ab4 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -574,6 +574,26 @@ pub fn set_preferred_mode(&self, (h_pref, w_pref): (u3= 2, u32)) { // SAFETY: We hold the locks required to call this via our type in= variants. unsafe { bindings::drm_set_preferred_mode(self.as_raw(), h_pref, w= _pref) } } + + /// Parse a raw EDID blob, update the connector's display information = from it, and add the + /// modes it advertises to the connector's mode list. + /// + /// Returns how many modes were added (0 if the blob is rejected). + pub fn add_edid_modes(&self, edid: &[u8]) -> i32 { + // SAFETY: `edid` is a valid byte buffer of `edid.len()` bytes; `d= rm_edid_alloc` copies it. + let drm_edid =3D unsafe { bindings::drm_edid_alloc(edid.as_ptr().c= ast(), edid.len()) }; + if drm_edid.is_null() { + return 0; + } + // SAFETY: We hold the locks required via our type invariants; `dr= m_edid` was just + // allocated above. + unsafe { bindings::drm_edid_connector_update(self.as_raw(), drm_ed= id) }; + // SAFETY: Same invariants; adds the EDID-derived modes and return= s the count. + let n =3D unsafe { bindings::drm_edid_connector_add_modes(self.as_= raw()) }; + // SAFETY: `drm_edid` was allocated by `drm_edid_alloc` above and = is no longer needed. + unsafe { bindings::drm_edid_free(drm_edid) }; + n + } } =20 /// A trait implemented by any type which can produce a reference to a diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 9b43501bfb58..fbe113676e2f 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -5,8 +5,8 @@ //! C header: [`include/drm/drm_crtc.h`](srctree/include/drm/drm_crtc.h) =20 use super::{ - atomic::*, plane::*, vblank::*, KmsDriver, ModeObject, ModeObjectVtabl= e, StaticModeObject, - NewKmsDevice, Probing, Sealed, + atomic::*, modes::DisplayMode, plane::*, vblank::*, KmsDriver, ModeObj= ect, ModeObjectVtable, + StaticModeObject, NewKmsDevice, Probing, Sealed, }; use crate::{ alloc::KBox, @@ -643,6 +643,7 @@ pub trait AsRawCrtcState: private::AsRawCrtcState { pub(crate) mod private { use super::*; =20 + /// The raw-pointer half of [`AsRawCrtcState`](super::AsRawCrtcState). #[allow(unreachable_pub)] pub trait AsRawCrtcState { /// Return a raw pointer to the DRM CRTC state @@ -677,6 +678,14 @@ fn active(&self) -> bool { // this access is serialized unsafe { (*self.as_raw()).active } } + + /// Returns the display mode programmed into this CRTC state. + fn mode(&self) -> &DisplayMode { + // SAFETY: `mode` is embedded in `drm_crtc_state`, so it shares th= e state's lifetime, and + // is only mutated from the atomic check context - invariant beyon= d that point, so our + // interface can ensure this access is serialized. + unsafe { DisplayMode::as_ref(&(*self.as_raw()).mode) } + } } impl RawCrtcState for T {} =20 diff --git a/rust/kernel/drm/kms/modes.rs b/rust/kernel/drm/kms/modes.rs index 88ea27729212..16024855c32f 100644 --- a/rust/kernel/drm/kms/modes.rs +++ b/rust/kernel/drm/kms/modes.rs @@ -78,4 +78,74 @@ pub fn crtc_vtotal(&self) -> u16 { // SAFETY: Reading these fields is safe via our type invariants unsafe { (*self.as_raw()).crtc_vtotal } } + + /// Horizontal active pixels (`hdisplay`). + #[inline] + pub fn hdisplay(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).hdisplay } + } + + /// Horizontal sync pulse start (`hsync_start`). + #[inline] + pub fn hsync_start(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).hsync_start } + } + + /// Horizontal sync pulse end (`hsync_end`). + #[inline] + pub fn hsync_end(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).hsync_end } + } + + /// Horizontal total pixels including blanking (`htotal`). + #[inline] + pub fn htotal(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).htotal } + } + + /// Vertical active scanlines (`vdisplay`). + #[inline] + pub fn vdisplay(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).vdisplay } + } + + /// Vertical sync pulse start (`vsync_start`). + #[inline] + pub fn vsync_start(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).vsync_start } + } + + /// Vertical sync pulse end (`vsync_end`). + #[inline] + pub fn vsync_end(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).vsync_end } + } + + /// Vertical total scanlines including blanking (`vtotal`). + #[inline] + pub fn vtotal(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).vtotal } + } + + /// Pixel clock for this display mode in kHz (`clock`). + #[inline] + pub fn clock(&self) -> i32 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).clock } + } + + /// The refresh rate of this mode in Hz, as computed by `drm_mode_vref= resh()`. + #[inline] + pub fn vrefresh(&self) -> i32 { + // SAFETY: `drm_mode_vrefresh` only reads the mode, which is valid= via our invariants. + unsafe { bindings::drm_mode_vrefresh(self.as_raw()) } + } } diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 375f79bd3ceb..3863dfe1b84c 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -595,6 +595,16 @@ fn plane(&self) -> &Self::Plane { unsafe { Self::Plane::from_raw(self.as_raw().plane) } } =20 + /// The width of this plane's destination rectangle within the CRTC, i= n pixels (`crtc_w`). + fn crtc_w(&self) -> u32 { + self.as_raw().crtc_w + } + + /// The height of this plane's destination rectangle within the CRTC, = in pixels (`crtc_h`). + fn crtc_h(&self) -> u32 { + self.as_raw().crtc_h + } + /// Return the current [`OpaqueCrtc`] assigned to this plane, if there= is one. fn crtc<'a, 'b: 'a, D>(&'a self) -> Option<&'b OpaqueCrtc> where --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f45.google.com (mail-wr1-f45.google.com [209.85.221.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8A97D2EB5B8 for ; Fri, 3 Jul 2026 03:01:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047713; cv=none; b=gMN/9z4ax6A6Jp00zIAM6pf9cKqdV8BYjORqLvxTlELPH5RptVqtmmtmaOCTkf3uVwx1OYub4ktv/JwuOBGiN4qT+eos+xbPUB2IZAV43S7aPu5t3/sWx5uyz6QcDzCv77mWCuumuP23+3ZrV5ROGSldbf6zB67r9diBJ5qZRb4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047713; c=relaxed/simple; bh=6n+7Xybo+4tbChhPtCG3wdCpi2v+Kw42OPefRP/D0iY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hm4OS4vKI04KlFHFGZhUlHKa0o7wf74dRDiIEI12dqicJpBiDQXVvfQK1BNZVtNFM9KkazAAsVZaBKpQerb+LxEEKEx5fLSeJNhAeYnQAEmWSYnX4AIf/tuXFF1lLxepz8PRvngPp9Pc1AWlJQFZnkE0bMgCD3fCN8636UHlUjQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=inJjUMoN; arc=none smtp.client-ip=209.85.221.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="inJjUMoN" Received: by mail-wr1-f45.google.com with SMTP id ffacd0b85a97d-4745492ed3aso49079f8f.1 for ; Thu, 02 Jul 2026 20:01:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047710; x=1783652510; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=5X0KENn743fRuXDK4+pZpz5mtbV1fUQ2ZuaqUrbUZWQ=; b=inJjUMoN9X6KwBHqrWd2OwZ93wOzNhzHDnioXMp3HIxOzhRU81LhspJtdiMBqqpTLH 5vbWiwxlrECiBoZm4YDP1M/ZwqEms0tf7V0FEnDi77Iu3yJAIbOBbbyMRPSegd1BNyZc Bt0uAcK9UzWGsuESn1rmkzmYulHCRSiop6WKJMBjVZwAqcFnHvNbdUx2znpKz4+oLTvB qJ9YKehU3vhZ9M7KE/iedIDv2Kp/uEq/ZM5WJL1tetaVLv5BNJhaVhtuWTrZhE3rajCp AfSzwn+1fkIPfUglsHtJqkh1Cl/plrqqmyeC0YX4YZCIT2HbsxfpkG5epxzkSC1wpdJO B5aQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047710; x=1783652510; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=5X0KENn743fRuXDK4+pZpz5mtbV1fUQ2ZuaqUrbUZWQ=; b=GGsQTM91qS/7+8wmORYfunjhzriSrLdiNR+50qcIWllJDvtFqf+04yhmtOnah53qa0 ElPYEsPwk2UzlRzrt5/6qacYDzX2o2NuP1FhXm4LwbOZERGNZJ8wg5VkJ2dwjJjj2RE4 jigaEquDdCGz2yuoutRGdV99MSb7P1tnKdClxwCXE/x9+t/Z1o48kAy9Y0K2nZ1D67jR dD7aHBU2reo9XAcXO0e9+HmHffUeVHXtcfSHTeDgdQBQv+QnWuMnXXd2T73mruIBF+dS 0+Maml2/DHkyO9wyd0B3W0F45a1sFs+axp2egg0qP7zMbbr2y5aSS+rzvu0rMsUCW3M6 625w== X-Forwarded-Encrypted: i=1; AHgh+RpR0I9GE9ghwwFKb/iWA5bgi6cbqi6OoWk1WwCDu4YCQ3PfzR7oDmztVMtNQPUxn3TJfXoNlWuUDpk+Nm0=@vger.kernel.org X-Gm-Message-State: AOJu0YyAD/acxmdkb1eanMdOpSbf/xYRtOgqfKZkB/hplOBKCQdPtkMx cvrGRPL5kg00AV9QBBN2xzXk7yHY6iFASk5OCxmNj7aWSQbPi7h6jSD/1HP6goEINA== X-Gm-Gg: AfdE7cnFXqF5GuldEFGGc8vA26rhIIly+7pxMatl60OuqGlc5xJWeFHvh0xupf5NQsE u+UtqkMIT/5cRXrXFlqit/89gbnzrdRDy4kOaK+Cg2JogUM35ypEF2lAGfTG+d7NTiDf6coJfpS ITOv95oYo4FdOjeDVjeFi+POL4AMfXlqC1UFwqdSVuAGnA2vaW8Pma7b4X2wrxA5sI1TSLSQioC bcoA9RaLFT9U2+aULO1Gp3WZMooKqhcHog5Nw8R5xcZCOjD69V7DZZsUx8YDFrSsjNc2kYgVjKi Cf35Z7ScuyZIg6CfuobUFDy+PpNjuvWGHLwtvdInMP9SSuiXprfuixuDM30PR92HoiYaPAORtK1 rV+zKb7YsWOdN7tA0oVY9iPI3iT2Y1OqQeIrDsmN5uN25IsNkhgu5jkzSIqatUrTd4yXG2zhH/J dsCbmC2jTrlrUuTBKM44arWvi9Cbv4JuQNMbR0FEcKyhrxniZUkdl+L2au X-Received: by 2002:a05:6000:4816:b0:473:f495:4a44 with SMTP id ffacd0b85a97d-47759754b7fmr12833251f8f.41.1783047709918; Thu, 02 Jul 2026 20:01:49 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:49 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 08/18] rust: drm: tyr: add the Kms associated type Date: Fri, 3 Jul 2026 04:00:55 +0100 Message-ID: <20260703030123.2814-9-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" drm::Driver::Kms became a required associated type when the safe KMS mode-object layer landed; tyr doesn't do KMS, so PhantomData is the correct value (same as nova's). Base drift: this tree's tyr driver predates that requirement. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- drivers/gpu/drm/tyr/driver.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index 360f57cb0bd7..a598c61162cf 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -186,6 +186,7 @@ impl drm::Driver for TyrDrmDriver { type File =3D TyrDrmFileData; type Object =3D drm::gem::shmem::Object; type ParentDevice =3D platform::Device; + type Kms =3D core::marker::PhantomData; =20 const INFO: drm::DriverInfo =3D INFO; const FEAT_RENDER: bool =3D true; --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f51.google.com (mail-wr1-f51.google.com [209.85.221.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 324EB2BE02C for ; Fri, 3 Jul 2026 03:01:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047716; cv=none; b=HFezMHWyahZV2WDXSLVfiY3teFuV803AcBF/VAVk+HpD1Dph1ohKZQhqwYNKxv+lx3Q3amXHe/DAKEBho8FG7boZtiPK9qDLAEm8PtpSzbcEuAsoFamPkOsFtj/t4gMdJLI5/n76J5RMNXFqnzQI+spOPl6lTjvoiWoGUuSlrgY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047716; c=relaxed/simple; bh=nonzyUxIMUVhprLXf3oJxIJ6VY2D4AVh7OAMEEntesU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=e6Zm2TzefFpFhDKK/27Ep92oEJb/vxSsWXzhWAmZ+kFif+96JfbXl3JmPJw5JW0zglowTY1jYaS1CkE5nLJcmmphJTgj2uRq8gbnUMw44/gXadFuV7zU/6dns7qBDWA5wK8lrDwAp1Ez3Jvq9j8shaIjYgEQ0twehotO9GvZgN8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=w9MZlz6d; arc=none smtp.client-ip=209.85.221.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="w9MZlz6d" Received: by mail-wr1-f51.google.com with SMTP id ffacd0b85a97d-47640541585so52776f8f.1 for ; Thu, 02 Jul 2026 20:01:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047713; x=1783652513; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=kUSHh2GQ3I5KH9hTK1NKMZdfJZAKYGKGP6z5fIcheuI=; b=w9MZlz6dqCcpurNWVeNUPT2dRm54YyKaw95WFcy8jzw9JRQ5zvfBbA5CMkwjUETOHC CqFX6PkGdllMHZ/MUFSq/amzkou8JABsywHslsQkviB7Mq+yMhI00maSsd54E7W4Ly7l hZlX0/JH87ApBcqpERIBD93XzgkRYw5dhCCRQ5N9Vg6HdNGw6Ectbpc0jPPcAgP0KU/H J+dHn2Vlm21DxW1cj2kuJacHVVtnK9VzSa4xBXqxNO45hucJJ+YiHYfe7Sw7uf37aMsq 7dvr/66fJaJFMpeLV6o5hOZ8/oGkHhUPoBSBvBU6OFKg4A5uZUJq3H+sxwqmwdnUsIGl aiqw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047713; x=1783652513; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=kUSHh2GQ3I5KH9hTK1NKMZdfJZAKYGKGP6z5fIcheuI=; b=Rsdey8TyIu0YQbBStWDw0Pv1XlueKy3AJkDLOBAnGVNC3rkhKP7W+9qSL7SLW8/7qP Hehaqno+GY6vr30moGhVBfWEfkvNPSMVSTmXs//hwLcZ0jciyAXFhkBPIYNl/MGoU/Jd 33IJKi2ZwzhRDJSixT8Al+naofblRYS2UABydavrecchnW6l6dBq56Hxh0DL0s8jahri BKHQXTvuX/ZypUJicLGMTmt3GUWYvV+M4CuolW8aofLhn2vSFrmoG+2a46M498VUv112 I7qCHWrrAenNZfUXDA8iw///M3pQuLijh+ymYrqsmjFd4f/oqiOvCwR9jV+2uifUG1xd QCpg== X-Forwarded-Encrypted: i=1; AHgh+RqacC7fCi8EFScajufHBYTUWNba2Ob6t4xdMRLio4Gad0kRmLB3Ag1qyTnac0DNf6eNt1gFWLyt19fsnAg=@vger.kernel.org X-Gm-Message-State: AOJu0YwhSAZB8WeMKb8UyzFRXvPbDXjH9ABFN93/2eDBYmtfkziJeriP /RLPFuemrAH2IQhlBcC1jCrVC2BsQjpULjyPnyN4Ozob4Nw4WjiKC+8VuXn4wY3ZrQ== X-Gm-Gg: AfdE7ckhMtkaZQwrN0G+dpyYTIN/3LstzemWQIhX+aOL2Z6PuS25M+YGl0/9WYD044O G1fypFUz2aYlhon3qU5DKPxlVgtLngc90VIaCbm4MWVR1M15XDbTICPOdXoXq5cR5u1wWFkLhhj txzKf2iTlOYlTEcXCzogpr6U60KFUVPGgGFO5UpXlnJarHB+TRlv7ayBHimsGelDQySGteolqJU iOjpK52tl/DdvOQSEuoD3f1OpViFBp6LFsn2WMrI7I7wU5bgae2wQFJUtclcNaXlyvWPi1Z7pqj fP/uh+NcJfEJnhx4aWnUZyzhUj9uKlYFMFqNhclfF4Gs1gc5a7yc3HywjV8KrQM72f+AQk5rfS3 qN7eR/EIXKsSYNbbDtYJom8ZNcRNmhc1qE95RwGIUDq/ZrvjBreFKL6gYCmqVyIyHvY/1bXZ4E5 XmiJUfu5RL4cwtdYYO93IyVECzFpLtTR02WVDuDhiE791a4emrfpIFTHEI X-Received: by 2002:a05:6000:2606:b0:475:e7f7:ef7a with SMTP id ffacd0b85a97d-4775779827cmr12406067f8f.23.1783047712619; Thu, 02 Jul 2026 20:01:52 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:51 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 09/18] rust: drm: add drm_event delivery Date: Fri, 3 Jul 2026 04:00:56 +0100 Message-ID: <20260703030123.2814-10-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> 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: quoted-printable DRM drivers deliver asynchronous events to the userspace client that owns a struct drm_file (vblank completions, and driver-defined events such as the DisplayLink evdi driver's mode/cursor/dpms/ddcci notifications), which user= space reaps through the poll(2)/read(2) interface of the DRM character device. The Rust DRM abstractions had no binding for this at all. Add a drm::event module providing: - EventPayload, an unsafe marker trait for a #[repr(C)] event struct whose first field is a drm_event header (filled in by the binding); - EventChannel, which stores the receiver drm_file under the device's event_lock -- the same lock drm_event_reserve_init_locked() and drm_send_event_locked() require held -- so registering, clearing and se= nding are serialized against file close. The classic bug of reading the target file pointer outside the lock and delivering to a client that just disconnected (a NULL/UAF oops) is therefore unrepresentable. event_lock() moves from the KMS Device impl to the core drm::Device impl, s= ince events are a DRM-core concept, not a KMS one; the vblank callers are unaffe= cted. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/kernel/drm/device.rs | 19 +++- rust/kernel/drm/event.rs | 215 ++++++++++++++++++++++++++++++++++++++ rust/kernel/drm/kms.rs | 11 +- rust/kernel/drm/mod.rs | 1 + 4 files changed, 236 insertions(+), 10 deletions(-) create mode 100644 rust/kernel/drm/event.rs diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index 5b84ce18fb29..7088db9420fb 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -19,9 +19,12 @@ }, error::from_err_ptr, prelude::*, - sync::aref::{ - ARef, - AlwaysRefCounted, // + sync::{ + aref::{ + ARef, + AlwaysRefCounted, // + }, + SpinLockIrq, }, types::{ ForLt, @@ -353,6 +356,16 @@ pub(crate) fn as_raw(&self) -> *mut bindings::drm_devi= ce { self.dev.get() } =20 + /// Returns a reference to the `event` spinlock of this [`Device`]. + /// + /// This is the `struct drm_device::event_lock` that the DRM core requ= ires to be held while + /// reserving and sending events to userspace (see the [`event`](crate= ::drm::event) module). + #[inline] + pub(crate) fn event_lock(&self) -> &SpinLockIrq<()> { + // SAFETY: `event_lock` is initialized for as long as `self` is ex= posed to users. + unsafe { SpinLockIrq::from_raw(&raw mut (*self.as_raw()).event_loc= k) } + } + /// Returns a reference to the registration data with lifetime shorten= ed /// from `'static`. /// diff --git a/rust/kernel/drm/event.rs b/rust/kernel/drm/event.rs new file mode 100644 index 000000000000..39d65d2a8fb2 --- /dev/null +++ b/rust/kernel/drm/event.rs @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM event delivery to userspace. +//! +//! A DRM driver can deliver asynchronous events to the userspace client t= hat owns a +//! [`File`][crate::drm::File]. Userspace reaps them through the `poll(2)`= /`read(2)` interface of +//! the DRM character device. The C core calls this mechanism "events" and= drivers such as +//! `vkms`/`vc4`/DisplayLink's `evdi` use it to hand back vblank completio= ns, mode-change +//! notifications, cursor updates and so on. +//! +//! The C API (`drm_event_reserve_init_locked()` + `drm_send_event_locked(= )`) has one non-obvious +//! rule: the `struct drm_device::event_lock` must be held while reserving= and sending, and the +//! target `struct drm_file` must be valid for that whole critical section= . Reading the target file +//! pointer outside the lock is a classic source of use-after-free/NULL-de= ref crashes (a driver's +//! flush worker racing a client disconnect). [`EventChannel`] encodes tha= t rule in its API: the +//! receiver file is only ever touched while `event_lock` is held, so regi= stration, teardown and +//! delivery cannot race. +//! +//! C header: [`include/drm/drm_file.h`](srctree/include/drm/drm_file.h) + +use crate::{ + bindings, + drm::{ + self, + device::DeviceContext, + file::{DriverFile, File}, + Device, + }, + error::to_result, + interrupt, + prelude::*, +}; +use core::{cell::UnsafeCell, mem}; + +/// A driver-defined DRM event payload. +/// +/// This is the fixed-size structure copied verbatim to userspace when the= event is read. It must +/// begin with a [`bindings::drm_event`] header; [`EventChannel::send`] fi= lls that header's `type` +/// and `length` for you from [`EventPayload::TYPE`] and `size_of::(= )`. +/// +/// # Safety +/// +/// Implementers must guarantee that: +/// - `Self` is `#[repr(C)]` and its first field is a [`bindings::drm_even= t`], so that a +/// `*mut Self` is also a valid `*mut drm_event`; +/// - `Self` is plain-old-data: it has no [`Drop`] glue and every bit patt= ern written by the driver +/// is safe to copy to userspace (do not place kernel pointers or paddin= g holding secrets in it). +pub unsafe trait EventPayload: Copy + 'static { + /// The `struct drm_event::type` code identifying this event to usersp= ace. + const TYPE: u32; +} + +/// Backing allocation for one in-flight event. +/// +/// The [`bindings::drm_pending_event`] is deliberately the first field: o= nce handed to the DRM +/// core, the whole allocation is freed by a single `kfree()` of the `drm_= pending_event` pointer +/// (on delivery or on file close), which is only correct if it sits at of= fset 0. +#[repr(C)] +struct EventStorage { + pending: bindings::drm_pending_event, + event: T, +} + +/// A per-device channel that delivers driver events to the [`File`] regis= tered as the receiver. +/// +/// The receiver pointer is stored under the owning device's `event_lock` = =E2=80=94 the same lock the DRM +/// core requires held while queuing events =E2=80=94 so [`connect`](Self:= :connect), +/// [`disconnect`](Self::disconnect), [`notify_close`](Self::notify_close)= and [`send`](Self::send) +/// are all serialized against each other and against the core's own event= handling. This makes the +/// "send to a file that just disconnected" race unrepresentable. +/// +/// A channel is bound to exactly one [`Device`]: always pass the same dev= ice to every method. +/// Drivers must call [`notify_close`](Self::notify_close) from their `pos= tclose` hook (and/or +/// [`disconnect`](Self::disconnect) when tearing the logical connection d= own) so the receiver +/// pointer never outlives the file it refers to. +pub struct EventChannel { + /// The receiver `drm_file`, or null when no client is connected. + /// + /// Only ever accessed while the owning device's `event_lock` is held. + receiver: UnsafeCell<*mut bindings::drm_file>, +} + +// SAFETY: `receiver` is only accessed while holding the owning device's `= event_lock`, which +// serializes all access across threads. +unsafe impl Send for EventChannel {} +// SAFETY: See `Send`. +unsafe impl Sync for EventChannel {} + +impl EventChannel { + /// Create a new, disconnected channel. + pub const fn new() -> Self { + Self { + receiver: UnsafeCell::new(core::ptr::null_mut()), + } + } + + /// Register `file` as the receiver for events sent through this chann= el. + /// + /// Any previously registered file is replaced. Runs under `dev`'s `ev= ent_lock`. + pub fn connect( + &self, + dev: &Device, + file: &File, + ) { + let irq =3D interrupt::local_interrupt_disable(); + let _guard =3D dev.event_lock().lock_with(&irq); + // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. + unsafe { *self.receiver.get() =3D file.as_raw() }; + } + + /// Clear the receiver, dropping any future events on the floor until = the next + /// [`connect`](Self::connect). Runs under `dev`'s `event_lock`. + pub fn disconnect(&self, dev: &Devic= e) { + let irq =3D interrupt::local_interrupt_disable(); + let _guard =3D dev.event_lock().lock_with(&irq); + // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. + unsafe { *self.receiver.get() =3D core::ptr::null_mut() }; + } + + /// Clear the receiver if it is `file`. + /// + /// Intended to be called from the driver's `postclose` hook so the st= ored pointer never + /// outlives the file. Runs under `dev`'s `event_lock`. + pub fn notify_close( + &self, + dev: &Device, + file: &File, + ) { + let irq =3D interrupt::local_interrupt_disable(); + let _guard =3D dev.event_lock().lock_with(&irq); + // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. + unsafe { + if *self.receiver.get() =3D=3D file.as_raw() { + *self.receiver.get() =3D core::ptr::null_mut(); + } + } + } + + /// Whether a receiver is currently connected. Runs under `dev`'s `eve= nt_lock`. + pub fn is_connected(&self, dev: &Dev= ice) -> bool { + let irq =3D interrupt::local_interrupt_disable(); + let _guard =3D dev.event_lock().lock_with(&irq); + // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. + !unsafe { *self.receiver.get() }.is_null() + } + + /// Deliver `payload` to the connected receiver. + /// + /// Allocates the backing event, fills its `drm_event` header, and han= ds it to the DRM core + /// under `event_lock`. If no receiver is connected the event is silen= tly dropped and `Ok(())` + /// is returned (mirroring the C drivers, which cannot deliver to a cl= osed client). If the + /// client has no space left in its event queue this returns `Err(ENOM= EM)` and the event is + /// dropped; the caller may retry later. + pub fn send( + &self, + dev: &Device, + payload: T, + ) -> Result { + let mut storage =3D KBox::new( + EventStorage { + pending: bindings::drm_pending_event::default(), + event: payload, + }, + GFP_KERNEL, + )?; + + // Fill the `drm_event` header that begins `event`. + // SAFETY: `EventPayload` guarantees `T` begins with a `drm_event`. + let ev_ptr: *mut bindings::drm_event =3D (&raw mut storage.event).= cast(); + // SAFETY: `ev_ptr` points to the live `drm_event` header inside `= storage`. + unsafe { + (*ev_ptr).type_ =3D T::TYPE; + (*ev_ptr).length =3D mem::size_of::() as u32; + } + storage.pending.event =3D ev_ptr; + let pending: *mut bindings::drm_pending_event =3D &raw mut storage= .pending; + + let irq =3D interrupt::local_interrupt_disable(); + let guard =3D dev.event_lock().lock_with(&irq); + + // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. + let receiver =3D unsafe { *self.receiver.get() }; + if receiver.is_null() { + drop(guard); + // `storage` is dropped here, freeing the allocation. + return Ok(()); + } + + let dev_raw =3D dev.as_raw(); + // SAFETY: `dev_raw` and `receiver` are valid; `pending`/`ev_ptr` = point into a live + // allocation; `event_lock` is held as required by the C API. + let ret =3D unsafe { + bindings::drm_event_reserve_init_locked(dev_raw, receiver, pen= ding, ev_ptr) + }; + if ret !=3D 0 { + drop(guard); + // Reservation failed, so `pending` was never queued: `storage= ` frees the allocation. + return to_result(ret); + } + + // SAFETY: The event is reserved against `receiver`; hand ownershi= p of the allocation to the + // DRM core, which frees it (via `kfree` of the `drm_pending_event= ` at offset 0) once the + // event is delivered or the file is closed. `event_lock` is held = as required. + unsafe { bindings::drm_send_event_locked(dev_raw, pending) }; + let _ =3D KBox::into_raw(storage); + drop(guard); + Ok(()) + } +} + +impl Default for EventChannel { + fn default() -> Self { + Self::new() + } +} diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 7207466094e7..874a2ebc7887 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -12,7 +12,7 @@ }, error::to_result, prelude::*, - sync::{aref::{ARef, AlwaysRefCounted}, Mutex, MutexGuard, SpinLockIrq}, + sync::{aref::{ARef, AlwaysRefCounted}, Mutex, MutexGuard}, }; use bindings; use core::{ @@ -377,6 +377,9 @@ pub(crate) fn mode_config_mutex(&self) -> &Mutex<()> { unsafe { Mutex::from_raw(addr_of_mut!((*self.as_raw()).mode_config= .mutex)) } } =20 + // NOTE: `event_lock()` now lives in `drm::device` since events are a = DRM-core concept, not a + // KMS one. See [`crate::drm::Device::event_lock`] and the [`crate::dr= m::event`] module. + /// Return the number of registered [`Crtc`](crtc::Crtc) objects on th= is [`Device`]. #[inline] pub fn num_crtcs(&self) -> u32 { @@ -387,12 +390,6 @@ pub fn num_crtcs(&self) -> u32 { unsafe { (*self.as_raw()).mode_config.num_crtc as u32 } } =20 - /// Returns a reference to the `event` spinlock for this [`Device`]. - #[inline] - pub(crate) fn event_lock(&self) -> &SpinLockIrq<()> { - // SAFETY: `event_lock` is initialized for as long as `self` is ex= posed to users. - unsafe { SpinLockIrq::from_raw(addr_of_mut!((*self.as_raw()).event= _lock)) } - } } =20 impl Device { diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index 7503f298db08..77067fc00d2a 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -4,6 +4,7 @@ =20 pub mod device; pub mod driver; +pub mod event; pub mod file; pub mod fourcc; pub mod gem; --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f51.google.com (mail-wr1-f51.google.com [209.85.221.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EBF433002C8 for ; Fri, 3 Jul 2026 03:01:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047721; cv=none; b=nnyEyPuBongfVaM5xPBYI37sRFNiSK0XOx0ZO4ExIZkQTlR01Wk4SYiRJCAm5ZA8pZwbk7ZRw1OHig/FwKg8ZUOT1MwUlvj7mucjgdq5rdYUGzdG9HKgQRA3I9auH1BgjSWkPOYXVR76cvg9OL4pxEDFPj1gDgAxRWcQJ7fex4U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047721; c=relaxed/simple; bh=lnvII2yDl/xZ/dtWSIY0wkLB+CFKzpqekK5mKWkSzEg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dBjUZ8c50oxxAfl8M0FHvBDnBUQGUGTWu7ufAtUevlt+C1oiz6zo2cSqHYX7L87pgi1dQngJ8vaK2DBcTK11q3LOmg8yhoUOnNsY0KArUNXChrmiXXkZ5fXBwOBXhabFdQSpf9F380IQXsl66SX7VC5OoVv4mHDY/kgERa7dHdo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=q7QB4W8t; arc=none smtp.client-ip=209.85.221.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="q7QB4W8t" Received: by mail-wr1-f51.google.com with SMTP id ffacd0b85a97d-4703bc0a99aso41727f8f.3 for ; Thu, 02 Jul 2026 20:01:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047716; x=1783652516; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=aQKoTdpL3C59oAuYFMvqcsWz7tbnOArYDM8BYFAHWQk=; b=q7QB4W8thongzpXno0epGF5tIr/tVfB4BG6j5y6sxPoS6Fdyf1vazRX8rMZMahoBUn YWDja1t8a7+pQa4FGEGR68m5vhxlUudRLcDMa+ngvo+dbKqwP5Ubk6Y1YxlllwVHpLYj wFWvSvp7Gx3hjixoJehSN9OYC18kekYDIn222xWGQ6f9TyXnx3sXG2FjO1MG1cpn9hq3 /Gf7F03sQjgyDnwy8cq5mNezD9kbNKmpFq4rwnYrIv8cLmNndaYbXaD3J0lEisAulCGH z7dUnSfCgRZEzkGQ2HJbYF0ohhM2+gDFMQvt4aKRTtvSENz5MDsKo9iWUr6v4TXHyu9e XI1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047716; x=1783652516; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=aQKoTdpL3C59oAuYFMvqcsWz7tbnOArYDM8BYFAHWQk=; b=Exe89nJ1/RHxgtbgazmH+/uibeupxXe04/mwaOyAZTD2dNHIAtcVjG4WaxfEXBh7UJ wcP4RA9kirUlH/5s4PGyoHMoyuKFwZ0tqfSJPSHE8208MUwz9LAQQDYbIEYDPrPoxnzR 6E6+3nRIEd9Ldka5a8F6u6ObWFDF27YMU7VnWha5kkUtu3BPB4A5cVnXC0om2NyJIYH2 nhU6igVC9kENh/TRBlJh1n1fAbN+Y9WMz/yEUHW+Zopc/rApF74GGm9jt4X4zcYAF/Tv 7RXIdyQ4umr1/dSbsUCje9Si5Lm9x2dGGM6SjYTK5jpauh/AET1OaYljx3hBNCPeQ68O NjAQ== X-Forwarded-Encrypted: i=1; AHgh+RqczzMX4bhuIbp8xBokeqSxbSnZVkiWSfT1AoDKoC7L5Uh1mluZcOWv5fEDcKwJto+B/cG8O63fxHCw2KQ=@vger.kernel.org X-Gm-Message-State: AOJu0YzKRpLkxIwq3Eduqkctd/L/l+SPgnCM3DawO76m0C/rzLwRfBfF EWBHXucEUSGwm4iH3WiAM5U55Kr4jZ3h214Ptg/8G0jlajQt8QuO9pwNOE0gK+6xPQ== X-Gm-Gg: AfdE7cnMsEhdfzSxVbWjRMRfYS18No12ZpBQEmWT/d4ar7XNaq0zCPeYORxHGZmYIAT eWAaUSmSIEchMtV+nLo5DzZ8gl/4dMFL4LXJvLAdAHOiWJAzAxFPMS5yf0LxnsKdKeaB+YNx+ds VKwnqL7qQOob85y0bUvxvvfJkkoP/8FJRmyzW2dSJA+KIewm602VcsRWME7wn7MNm8RW+/DHDRq GS0Az+qCF4R97dsskhMQgljdGFvGkQPeybJuFkiSxs2OxjzQkloil7mOAL3Vn4CpJXtR/dyyInP 45FPZtE8mSuAO8eYU8cFs2/jH1udXI10ye+j5tiExJdZ6iSoc6urZP77riP2Ce9gPDdnvtJblrC l1bpFE50jW7HRRp2RU4jnrouLJn+3HHCgTyZ+l0ouG4dbxuKNduFsxW1Zv9KZPP173/LReQoFsj 8/kIw3/fsQiJj2Wd6LzAj4PdniflD5D9TWJmNnYTA0CAnyG52/KEt/Ki4+ X-Received: by 2002:a05:6000:604:b0:476:504f:788 with SMTP id ffacd0b85a97d-4775a4e3e69mr12347740f8f.61.1783047716282; Thu, 02 Jul 2026 20:01:56 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:54 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 10/18] rust: drm: allow drivers to declare ioctls from their own uAPI module Date: Fri, 3 Jul 2026 04:00:57 +0100 Message-ID: <20260703030123.2814-11-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" declare_drm_ioctls! resolves each ioctl's argument_type and DRM_IOCTL_* num= ber from the in-tree kernel::uapi crate. An out-of-tree driver with its own pri= vate DRM ioctl range (e.g. DisplayLink's evdi) cannot extend kernel::uapi, so it cannot use the macro at all. Add declare_drm_ioctls_ext!, which takes the argument type and the fully-fo= rmed ioctl number directly per entry: (handler_name, argument_type, ioctl_number, flags, user_callback) so a driver can keep its own #[repr(C)] uAPI mirror in its own crate and pa= ss crate::uapi::Foo / crate::uapi::DRM_IOCTL_FOO. The callback prototype, the compile-time argument-size assertion against the ioctl number, and the drm_dev_enter/exit critical section are identical to declare_drm_ioctls!, w= hich is left untouched (a caller-provided :path module cannot be glued to ::* / = ::Ty in a macro transcriber, so the two macros do not share an implementation). Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/kernel/drm/ioctl.rs | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/rust/kernel/drm/ioctl.rs b/rust/kernel/drm/ioctl.rs index 181918303f2f..78169798bbbc 100644 --- a/rust/kernel/drm/ioctl.rs +++ b/rust/kernel/drm/ioctl.rs @@ -176,3 +176,89 @@ macro_rules! declare_drm_ioctls { }; }; } + +/// Declare the DRM ioctls for a driver whose uAPI lives outside `kernel::= uapi`. +/// +/// [`declare_drm_ioctls!`] resolves each `argument_type` and `DRM_IOCTL_*= ` number from the +/// in-tree `kernel::uapi` crate, which an out-of-tree driver with its own= private ioctl range +/// cannot extend. This variant takes, per entry, the argument **type** an= d the fully-formed ioctl +/// **number** directly, so the driver can keep its own `#[repr(C)]` uAPI = mirror inside its own +/// crate. Each entry is: +/// +/// `(handler_name, argument_type, ioctl_number, flags, user_callback)` +/// +/// `handler_name` is any identifier (used to name the generated trampolin= e and as the debug name); +/// `argument_type` is the `#[repr(C)]` argument struct (any path, e.g. `c= rate::uapi::Foo`); +/// `ioctl_number` is the full command number (e.g. `crate::uapi::DRM_IOCT= L_FOO`, typically built +/// with [`IOWR`]/[`IOR`]/[`IOW`]/[`IO`]). `user_callback` has the exact s= ame prototype as for +/// [`declare_drm_ioctls!`], and the handler still runs inside a `drm_dev_= enter/exit` critical +/// section (returning `ENODEV` if the device has been unplugged). +/// +/// The ioctl array is indexed by the DRM core as `number - DRM_COMMAND_BA= SE`, so entries must be +/// listed in ascending, contiguous order starting at the driver's command= base. +/// +/// # Examples +/// +/// ```ignore +/// kernel::declare_drm_ioctls_ext! { +/// (EVDI_CONNECT, crate::uapi::DrmEvdiConnect, crate::uapi::DRM_IOCTL= _EVDI_CONNECT, +/// 0, evdi_connect_ioctl), +/// } +/// ``` +#[macro_export] +macro_rules! declare_drm_ioctls_ext { + ( $(($cmd:ident, $arg_ty:ty, $num:expr, $flags:expr, $func:expr)),* $(= ,)? ) =3D> { + const IOCTLS: &'static [$crate::drm::ioctl::DrmIoctlDescriptor] = =3D { + const _:() =3D { + // The argument struct size must match what the ioctl numb= er encodes, so the DRM + // core copies exactly `size_of::()` bytes = to/from userspace. + $( + ::core::assert!(::core::mem::size_of::<$arg_ty>() =3D= =3D + $crate::ioctl::_IOC_SIZE($num)); + )* + }; + + let ioctls =3D &[$( + $crate::drm::ioctl::internal::drm_ioctl_desc { + cmd: $num as u32, + func: { + #[allow(non_snake_case)] + unsafe extern "C" fn $cmd( + raw_dev: *mut $crate::drm::ioctl::internal= ::drm_device, + raw_data: *mut ::core::ffi::c_void, + raw_file: *mut $crate::drm::ioctl::interna= l::drm_file, + ) -> core::ffi::c_int { + // SAFETY: The DRM core keeps the device alive= across the callback and + // only dispatches here on a registered device. + let dev =3D unsafe { + $crate::drm::device::Device::from_raw(raw_= dev) + }; + let guard =3D match $crate::drm::device::unbin= d_guard(dev) { + Some(g) =3D> g, + None =3D> return $crate::error::code::ENOD= EV.to_errno(), + }; + // SAFETY: The ioctl argument has size `_IOC_S= IZE($num)`, asserted above + // to match `size_of::<$arg_ty>()`; `drm_ioctl= ()` guarantees the buffer + // is valid and exclusively owned for the dura= tion of this call. + let data =3D unsafe { &mut *(raw_data.cast::<$= arg_ty>()) }; + // SAFETY: This is just the DRM file structure. + let file =3D unsafe { $crate::drm::File::from_= raw(raw_file) }; + + match $func(dev, &*guard, guard.registration_d= ata(), data, file) { + Err(e) =3D> e.to_errno(), + Ok(i) =3D> i.try_into() + .unwrap_or($crate::error::code= ::ERANGE.to_errno()), + } + } + Some($cmd) + }, + flags: $flags, + name: $crate::str::as_char_ptr_in_const_context( + $crate::c_str!(::core::stringify!($cmd)), + ), + } + ),*]; + ioctls + }; + }; +} --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f42.google.com (mail-wr1-f42.google.com [209.85.221.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EE61B30100E for ; Fri, 3 Jul 2026 03:02:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047723; cv=none; b=E+IFSGSJG41gGzs6d2tu/yOtK1kWPkfCH2tysTKH6EZlB8EyNGjQm8bdCZXnXGyJhpBcvMbH1soU5ACGGq2qWFeALRNzaxxErPY4C0WxVB6e9YckRgGvTZrBYlfKhf8tpwT6kT5A8/HVEwtFF20aQBs0Eodv+FKtLPn8eyIunyY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047723; c=relaxed/simple; bh=KO0FTjJOVLLsLn7UvcJmS/2HPJY87y5uWlvw2Ys58wA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=u1+XBcNV0sw+LI7klc0NKtSyXg8AJkqISMU92wGiBtzuIxbxx9j26aegLLNPm2ZP5+CkwsyaGARnvyaXAcY95QDMvmKQ6MKSLh45mP7uHuLbAbzW+ppTxN7m85Iq3qX0YiWTRzrJ/9hLp2iTydAf+5umaxOs6DJmFKuq3yiYfQM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=LJk3GBc3; arc=none smtp.client-ip=209.85.221.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="LJk3GBc3" Received: by mail-wr1-f42.google.com with SMTP id ffacd0b85a97d-471eeac43bfso110548f8f.3 for ; Thu, 02 Jul 2026 20:02:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047719; x=1783652519; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=gWYaWvLBgitKBD1DvypUSuQXP5THaJX3kX34tELc4Xg=; b=LJk3GBc3xlogoqE95lqrRD6Poz4dyONZJFeGI/ah/ugQogyRqB970woEjmQeoLqk+8 Ywo+YvbxKpNuc8HCtsOcSeBAhb/hOvq8wYFs2UCiKMYHZLBkzY0Fde820Y3vZv5ubu1i zprkpJv07zkknfRuT03Xqa7rupWMb+9zlPCbAoQ+TnHDRR/ECJlNkYHon9bFiS9XQoQ1 eU/VXc9p2BebO9HAoXsBYbuLdc9tiQocYnSrHT8r5CjZpi01WqQnXzNoVdKFRFpvBBe5 wsIGajTSqbbspCPQi9P7t1EjPd3kkdNU3rJcGyk3/ExqxXpp+CtD+si+tXhCQkonTIXe 6Icg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047719; x=1783652519; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=gWYaWvLBgitKBD1DvypUSuQXP5THaJX3kX34tELc4Xg=; b=Zc+1Z8ovAddjm3IevbfiGoXrhIrmJqBclstwCV/lfMuUt84Amn6YnXYTUaM51mPMR2 FCZXR/eymWe5FN8OS5kL/0qSk6F6f0sHT8JLWrne/uBYMaMNrYJw59pW7+m35MxUZE2u 1xELApX0FKcH4crKn7o5abh0UgYpdFRy5zP1QyZVJm81ZhmdUuFgHGW+xbHKE3gBoWeM U9l1M2rYIv9MXnCQgKjKyDJNJUVGpesxl/oD0LDTfAbrg3hT0D/BEMoB/I2TyT2I6NX6 tPVTKjtmRPC00r5gjc0UpOhDqMQd++ZWJdt5co+B6LGWXMvXgX+eTLNjcQox897B485x dkxw== X-Forwarded-Encrypted: i=1; AHgh+RpAgtsvRxscA8bvxOrp87kWYNecPX0LohJokUKoNBctJcIfzBE6Gt5TJejuLu+is5v5yKCGp+A50CkwTn0=@vger.kernel.org X-Gm-Message-State: AOJu0Yx0RM+NDVn+pgBiQVoBYvDDs55j/eKJKMqH/eYbEou8nKXbENOW /iPrzFwdJQY2MIPHX0LVHk1gfNHb2I1kedGrLTE+78502W79UJ4LnO4JR7MphUUncWVceXFGbLM /jzM5q307 X-Gm-Gg: AfdE7cl3Z51cSUKzX/l3L0H/fsdLlOcBGWUPj1PRJYwRbJAmTJt2VTMYYM+s0qPKIWQ RAW4GvXPqZ+Ma0k69WZg6oQdnoFrS649kjVpFnrCy1AsbROAZCktug50Cp8gN0FxG0SOSDpUETp Tb7FrUz6JA91hcYRnp/TOdkc9NkRRxFC/0Oi2p9WlQk4EzVyD9nfYnsi7cB3gKyH00FTJMSMWGb GSErKPRjNNTXC2IKZFL66i4/q6rdbqyH7KIT+RkvKrmikXibE0NpUlRsMpL921ZhK18Sy5734eA MqMEvMB75d19fy2V3fhsDx/8blaMXWL2FU6YS0KRsHHEaK2a/Atz62nWdfuaEl4vbjhRAiWn6aA 57Rat9f7C2oFz0j8+thBx5ghjkP3Nm1KzHRCYpzJ0m7IZ3Og4OtwSDCo7HHDJ6jlsa7RxBmES05 6/0jq8x+EvjCXuuMx/yyo4cWAcm5auOXyI34sPvb74TW6nmkOPAO2KgZby X-Received: by 2002:a05:6000:2407:b0:475:f0c2:75a3 with SMTP id ffacd0b85a97d-4775ac9f53dmr13275064f8f.52.1783047719231; Thu, 02 Jul 2026 20:01:59 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:57 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 11/18] rust: platform: add runtime platform device creation Date: Fri, 3 Jul 2026 04:00:58 +0100 Message-ID: <20260703030123.2814-12-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The platform abstraction can register a driver (platform::Driver) but not c= reate a device for it to bind to. Virtual drivers that spawn their own devices --= for instance DisplayLink's evdi, which creates one platform device per virtual display card from a sysfs attribute or at module load -- need the provider = side. Add platform::RegisteredDevice, a thin owner around platform_device_register_full() whose Drop calls platform_device_unregister= () (which unbinds any bound driver first), plus DEVID_AUTO/DEVID_NONE constant= s and a name/id/parent/dma_mask constructor. The non-generic methods are #[inline= ] so out-of-tree modules can use them without an exported symbol. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/kernel/platform.rs | 77 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 9b362e0495d3..4c2ded2e264f 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -571,3 +571,80 @@ unsafe impl Sync for Device {} // SAFETY: Same as `Device` -- the underlying `struct platform_dev= ice` is the same; // `Bound` is a zero-sized type-state marker that does not affect thread s= afety. unsafe impl Sync for Device {} + +/// Auto-assign a platform device instance id (`PLATFORM_DEVID_AUTO`). +pub const DEVID_AUTO: i32 =3D bindings::PLATFORM_DEVID_AUTO; +/// Create a platform device with no instance id (`PLATFORM_DEVID_NONE`). +pub const DEVID_NONE: i32 =3D bindings::PLATFORM_DEVID_NONE; + +/// An owned platform device created and registered at runtime. +/// +/// This is the *provider* side of the platform bus: whereas [`Driver`] bi= nds to devices the bus +/// hands it, [`RegisteredDevice`] *creates* a `struct platform_device` an= d adds it to the bus, +/// which then binds any matching [`Driver`] (calling its `probe`). Virtua= l drivers such as +/// DisplayLink's `evdi` use this to spawn their DRM card devices on deman= d (from a sysfs `add` +/// attribute or at module init). +/// +/// [`RegisteredDevice::new`] wraps `platform_device_register_full()`; dro= pping the handle calls +/// `platform_device_unregister()`, which unbinds the bound driver and rel= eases the device. +pub struct RegisteredDevice { + pdev: NonNull, +} + +// SAFETY: `platform_device_register_full()`/`platform_device_unregister()= ` are internally +// synchronized by the driver core and safe to call from any thread; the h= andle only unregisters. +unsafe impl Send for RegisteredDevice {} +// SAFETY: See `Send`; `&RegisteredDevice` exposes only the thread-safe `D= evice`. +unsafe impl Sync for RegisteredDevice {} + +impl RegisteredDevice { + /// Create and register a new platform device. + /// + /// `name` is the platform device name, which the platform bus also us= es to match a [`Driver`]. + /// `id` is the instance id: [`DEVID_AUTO`] to auto-assign a unique on= e, [`DEVID_NONE`] for an + /// unnumbered device, or a fixed non-negative value. If `parent` is g= iven, the new device is + /// created as its child. `dma_mask` requests a coherent DMA mask of t= hat many bits (pass `0` + /// to leave the platform default). + #[inline] + pub fn new( + name: &CStr, + id: i32, + parent: Option<&device::Device>, + dma_mask: u64, + ) -> Result { + let info =3D bindings::platform_device_info { + parent: parent.map_or(core::ptr::null_mut(), |p| p.as_raw()), + name: name.as_char_ptr(), + id, + dma_mask, + ..Default::default() + }; + + // SAFETY: `info` is fully initialized (all remaining fields zeroe= d) and only borrowed for + // the duration of the call, which copies out the fields it needs. + let pdev =3D unsafe { bindings::platform_device_register_full(&inf= o) }; + let pdev =3D crate::error::from_err_ptr(pdev)?; + + Ok(Self { + // A successful `platform_device_register_full()` returns a va= lid, non-null pointer. + pdev: NonNull::new(pdev).ok_or(ENOMEM)?, + }) + } + + /// Return a reference to the created platform [`Device`]. + #[inline] + pub fn device(&self) -> &Device { + // SAFETY: `self.pdev` points to a valid, registered `struct platf= orm_device` owned by + // `self`; `Device` is a transparent wrapper over `struct platform= _device`. + unsafe { &*self.pdev.as_ptr().cast::() } + } +} + +impl Drop for RegisteredDevice { + #[inline] + fn drop(&mut self) { + // SAFETY: `self.pdev` was returned by `platform_device_register_f= ull()` and has not been + // unregistered yet; this consumes that registration exactly once. + unsafe { bindings::platform_device_unregister(self.pdev.as_ptr()) = }; + } +} --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f49.google.com (mail-wr1-f49.google.com [209.85.221.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5D2E6305666 for ; Fri, 3 Jul 2026 03:02:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047725; cv=none; b=f7r7lcU9S+r/z9JYNe+b6pn3JbsUzkKQgK8al3CfhV4illKkORjvEGVIWMugHV7x430kOg3nppOTS8yJ20TmAZeD5NZ4n8A3uzhbe25IJS1jf2bckN4JTQqG/+58HJa2L7geMR5DMpaxe2tp/2gVXOxpc/tL369ilget66LpIuU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047725; c=relaxed/simple; bh=SJ+2KGFnHCovNgowJ8J8TKTzxcye2f9b6WfXIPwMW1Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=fASu8UlhQw7WUWzxPxGw2ErZyF0pf6AA/OPmuUdqu9e08NbOPjsiWEBFJjY7BarJ5ddztukHCOrvyTRyFMRk/ulPRz0yrWNiAF2wlMBLIT9Z8q8/LCHCgBec2y6tbFUSswG5YgG1fxeno01l5BkpHwT0V2CBIKPu0GUIoG2dEeo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=KPuWr3DW; arc=none smtp.client-ip=209.85.221.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="KPuWr3DW" Received: by mail-wr1-f49.google.com with SMTP id ffacd0b85a97d-475417f010dso40544f8f.2 for ; Thu, 02 Jul 2026 20:02:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047722; x=1783652522; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=LUgBn6zBfsTbi2CLsMgn9qh9uhx+O3ZRcBIzQDpiX/c=; b=KPuWr3DWQASF5qWkn68ePpcYzO5ICfirsARHKWY54kwkbK1v7GdMVAi12c8Jjr9dmU Vl1qxGZpYrjLLHZwQ8gzNVa2VsEEaX/P//Wn5iKxw0f+3Cf0cg3CVltXjDtLlQCiLyZ+ VEclAe3vuj9j+WWDDr2VdPe+KK/uJdWKa44+yYDNpZxIF861hxAxgy4jLVjiubxdxCTW 8yPB5YP5giuTeqvUsnfhY229bo74GEoejAKZO+91tK/ErlI3ZVnim0DbuztZ9X3ncCCX a+tAk2ZtzNUT1Z1vkvalUuIdvxKyIs9eQ/4c1sJmPET1dYoyKBvgYQQ+3aEhef79rqnm xMlw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047722; x=1783652522; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=LUgBn6zBfsTbi2CLsMgn9qh9uhx+O3ZRcBIzQDpiX/c=; b=QI3wUN8TpudhbGr4Pp5N9DZ10wlSebx85rsgMipLchdsiAgrNZDNOB2JC87W64xQuX 59U+rvXoQECu4Ycal1a2/cI3MWdaEZavS2V7UZZAlTjvARthCDh0lhouSpzdqdEV/Pz2 whNU8C8ghRUnI+Yba5ADpOW55zCXb4RaqUprF+AOxbVglvwA+dPL4wOyGDZcyLHE/7Ni 1I6cKdoQ5T4n9N3htbJ5pn05gt4ffgbWUgy8n/WFiY3jMN4JczGFo+sALmFyIpmXsQeL fSZHl2rNS/xB/RL7gUZjLMzd5n3I+wxtWcpKj59FoRWkiMW9akSt5Q1mudEvccuispsn iLbg== X-Forwarded-Encrypted: i=1; AHgh+Rr8B+IfXRyaJuoV7LmpJ/npENEcmzdzEQjmMZQ5utmf4EDXTzzAA2fGsIy1wrw+tGZWs7UQXYWD2Mly2Ag=@vger.kernel.org X-Gm-Message-State: AOJu0YxlSIzfEJ5jcP6PESb0k1Vc01MHy/Ts/LpU4zg6FR+ldSg6Mj83 HcA1pHA0Df8V4UkrDZlhvGC/VhwRccnk9XXrKbit+iSvZFEJY95pzFs3YVJaHeLizA== X-Gm-Gg: AfdE7ck45xxQgjjeECqRagj40iPulM3ZVU/EogzOYygsThQ1KTQ24A7DBa5IWg8KRDY 385G+HWFHAjY9yfVr4dRPuXB+Sfeo9LiFj4xeU4IlR8W3q9CkY+6qfkWEVd0WH9PpvZDzC8bQiA eRu/Xo3wW+M2hIZQlLb5l6Z4AgckT6FwBwB3Bt0iW+g431uhZrLfal6FrOqMeCPjSrj+LpWuIQN lRp7f5CzuXJQBbhmyWuFmf703eZHXXTVKHdJ+X/wOZC5xmDcV7IDBD7EYXYSOiKbwbuWssFzoAt jAlhjcyG9uFYyyofunTfbu4RS28owFCZBc/HXTI8L3BjJlWmhT165Mfu77MBLRGPip1a2qNLKIc MSQ/E2H02u76boB/kcDFOrmzXR0E2IlZb3uIqloGLccgpmHUslDiv6z8psc+mXdAWDHdcHb6iNx /zj8Uebe81q9K8ckfKA7ozqhD6bawS+t/a6TlcdC4QkcHdqrTGw6UtxLFH X-Received: by 2002:a5d:5f54:0:b0:472:3090:d878 with SMTP id ffacd0b85a97d-477571cb785mr12685227f8f.4.1783047721804; Thu, 02 Jul 2026 20:02:01 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:59 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 12/18] rust: drm: framebuffer: add geometry accessors, refcounting and a byte-slice vmap Date: Fri, 3 Jul 2026 04:00:59 +0100 Message-ID: <20260703030123.2814-13-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> 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: quoted-printable A driver that reads back a framebuffer's pixels (e.g. DisplayLink's evdi, w= hose scanout is pulled by userspace via a GRABPIX ioctl rather than pushed to hardware) needs three things the Framebuffer binding did not provide: - width()/height()/pitch() geometry accessors; - reference counting, so the framebuffer flipped in during an atomic comm= it can be held (ARef) and mapped later from an ioctl -- add AlwaysRefCounted backed by new drm_framebuffer_get()/put() helpers; - a way to read the mapping without unsafe in the driver -- add FramebufferVmap::as_bytes(), returning the plane-0 mapping as a &[u8] of height * pitch(0) bytes, valid for the guard's lifetime. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/helpers/drm/drm.c | 1 + rust/helpers/drm/framebuffer.c | 13 +++++++ rust/kernel/drm/kms/framebuffer.rs | 54 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 rust/helpers/drm/framebuffer.c diff --git a/rust/helpers/drm/drm.c b/rust/helpers/drm/drm.c index 5d700d75c0c9..24c531841ba1 100644 --- a/rust/helpers/drm/drm.c +++ b/rust/helpers/drm/drm.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 =20 #include "atomic.c" +#include "framebuffer.c" #include "gem.c" #include "vma_manager.c" #include "vblank.c" diff --git a/rust/helpers/drm/framebuffer.c b/rust/helpers/drm/framebuffer.c new file mode 100644 index 000000000000..280538ffb82c --- /dev/null +++ b/rust/helpers/drm/framebuffer.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +void rust_helper_drm_framebuffer_get(struct drm_framebuffer *fb) +{ + drm_framebuffer_get(fb); +} + +void rust_helper_drm_framebuffer_put(struct drm_framebuffer *fb) +{ + drm_framebuffer_put(fb); +} diff --git a/rust/kernel/drm/kms/framebuffer.rs b/rust/kernel/drm/kms/frame= buffer.rs index 1ec6779ba7de..c7089b12d0d1 100644 --- a/rust/kernel/drm/kms/framebuffer.rs +++ b/rust/kernel/drm/kms/framebuffer.rs @@ -9,6 +9,7 @@ drm::device::Device, error::{code::EINVAL, to_result}, prelude::*, + sync::aref::AlwaysRefCounted, types::*, }; use bindings; @@ -46,6 +47,23 @@ fn raw_mode_obj(&self) -> *mut bindings::drm_mode_object= { } =20 // SAFETY: References to framebuffers are safe to be accessed from any thr= ead +// SAFETY: `struct drm_framebuffer` is refcounted via `drm_framebuffer_get= ()`/`drm_framebuffer_put()`, +// which are the correct increment/decrement operations for it. +unsafe impl AlwaysRefCounted for Framebuffer { + #[inline] + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference guarantees the refc= ount is non-zero. + unsafe { bindings::drm_framebuffer_get(self.as_raw()) }; + } + + #[inline] + unsafe fn dec_ref(obj: ptr::NonNull) { + // SAFETY: The safety requirements guarantee the refcount is non-z= ero; `Framebuffer` has + // the same layout as `struct drm_framebuffer`. + unsafe { bindings::drm_framebuffer_put(obj.as_ptr().cast()) }; + } +} + unsafe impl Send for Framebuffer {} // SAFETY: References to framebuffers are safe to be accessed from any thr= ead unsafe impl Sync for Framebuffer {} @@ -79,6 +97,30 @@ pub(crate) fn as_raw(&self) -> *mut bindings::drm_frameb= uffer { self.0.get() } =20 + /// The framebuffer's width in pixels. + #[inline] + pub fn width(&self) -> u32 { + // SAFETY: `as_raw()` is a valid framebuffer; `width` is set at cr= eation and immutable. + unsafe { (*self.as_raw()).width } + } + + /// The framebuffer's height in pixels. + #[inline] + pub fn height(&self) -> u32 { + // SAFETY: `as_raw()` is a valid framebuffer; `height` is set at c= reation and immutable. + unsafe { (*self.as_raw()).height } + } + + /// The byte stride (pitch) of colour plane `index`. + /// + /// A `drm_framebuffer` has at most four colour planes; `index` is mas= ked into `0..=3D3`. + #[inline] + pub fn pitch(&self, index: usize) -> u32 { + // SAFETY: `pitches` has four elements; `index & 3` keeps the acce= ss in bounds. The pitches + // are set at creation and immutable. + unsafe { (*self.as_raw()).pitches[index & 3] } + } + /// Map this framebuffer's plane-0 backing pages into the kernel addre= ss space for CPU /// access, for the duration of the returned guard. /// @@ -123,6 +165,18 @@ pub fn as_ptr(&self) -> *const u8 { // SAFETY: set non-null in `Framebuffer::vmap`; `vaddr` is the act= ive union member. (unsafe { self.map[0].__bindgen_anon_1.vaddr }) as *const u8 } + + /// Plane 0's mapped pixels as a byte slice. + /// + /// The slice covers `height * pitch(0)` bytes =E2=80=94 the full exte= nt of colour plane 0 =E2=80=94 and is + /// valid for the guard's lifetime. This lets callers read the scanout= buffer without `unsafe`. + #[inline] + pub fn as_bytes(&self) -> &[u8] { + let len =3D self.fb.height() as usize * self.fb.pitch(0) as usize; + // SAFETY: `drm_gem_fb_vmap` mapped colour plane 0 contiguously at= `as_ptr()` for at least + // `height * pitch(0)` bytes, and the mapping is live for `self`'s= lifetime. + unsafe { core::slice::from_raw_parts(self.as_ptr(), len) } + } } =20 impl<'a, T: KmsDriver> Drop for FramebufferVmap<'a, T> { --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f48.google.com (mail-wr1-f48.google.com [209.85.221.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C6B67306B0A for ; Fri, 3 Jul 2026 03:02:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047727; cv=none; b=STVkzyPiqcMmFNw++YktTlLfmjjJH1z0BGVouKaivbK1NaWnydkJOC3/47YyyU8c4g0XHErw+wO7DOMhpT/UBRHEJBvrHxY2y1mz+Z1yeDRVGfFCoqY6jKyRuB7DRB520r5DKqxyslAJUuK/n1kCKps2MHK1nMQqwFFaiW4Kl1s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047727; c=relaxed/simple; bh=kaMRhV66jMxQvqjeQ7Ar6Cr8aJCEeWtJrustTtJxVZ0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tUBPSp0FfcMpHHVDJFPxEIbA3KqtGknr64ESZoZEfWIXy7wG5qhPaQq8H9cikQfAYPGJfyW3eE5JZxs32Zd9/yzGgy17BP+GTmJO3XJEUzgC4suMU1VE4ySUWo8MfWEzGlhkffhSQOaL+qPhK20698m3YfY9zdBELR0gLK9Tz28= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=S66R4TAc; arc=none smtp.client-ip=209.85.221.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="S66R4TAc" Received: by mail-wr1-f48.google.com with SMTP id ffacd0b85a97d-46ed4f66256so81027f8f.3 for ; Thu, 02 Jul 2026 20:02:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047724; x=1783652524; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=031sIZWe4JTCBupEXr5DXAC9PJaWSnyeiEEKJDEA+7w=; b=S66R4TAcwlWQDggJCgyL/uL76w8ZbflNo0DH9z/OI80kTnixJn7tGrLJobYBcSHt9T 2rqBFSiBZZgof8jb1/y8UJvc42S/w70QCIevvJp4QCS80FCBkP3FdC1qiUAyLf/PxSTz qDQ4m2hAZEcp06PPlQMQzM1Ml7n/bLQLCvo3L3F4/lJfS4Xjt/7E+yyMQmqLmLtM4wz7 PdE3kifL0v2ZepqF/B+Le5SnFlQMNCxK0JT0z3XBMBmJRoYUUhV42OPGIcRPIxVoYBoT keHMIlrnB1907zYRYX6JN5D33yQg/T+EPLFMhrAN1bjlUWzqGTFcJmEhmQmtlzqpb7XL WdRA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047724; x=1783652524; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=031sIZWe4JTCBupEXr5DXAC9PJaWSnyeiEEKJDEA+7w=; b=FUx3eIJErweAJ05mlySPswGtsQqCIH+WGZzuUUgc7laNLZdRbTZT556OX1QwABmAqC Tj1abWEAIynNMyA8DOCrmlRno9DOh1amwPdRK2pGUyqV3KtmDMrQU3SF5Rt1Wh9eGR20 1G1svpMDIlFL8/t/K2U68rVNxqRowSuz4F4VTN/NpnXrva6bwTGj51yb6+V7J0I3GMCn +jBzAP1EXukp3fz9xhcERFt0oDVWA/tRaumSDdbqqOwdcCKu1NKx+RqOoV9c0qoVIA66 TGZRKaZYRYndRITUaMhvjtiwmSMExjq+sMoCv6qn1aiBk3HthM1Qdv7hukiMs3eMmDqW FvDw== X-Forwarded-Encrypted: i=1; AHgh+RrZ4N0JO2HG2zAUX+f98wb8NxwWgvpGoT4lH+1zp7UeQTWXUb9Y241V4vK0qPHM9gMCs793iIPaMK30p5w=@vger.kernel.org X-Gm-Message-State: AOJu0YxdROZ3D4EOFzAM+PnWw5xKoHZR3bU3TF7MijGzSH4BGgni+RxW uXMwp8xBA81dMRxYBWnEj+XQDXrNHFhw85Vil7qhVc8AzTb+4o9EatdSXf71xHbRpw== X-Gm-Gg: AfdE7cnXk4oaqz5dM2vM4BlV9U+LZE0UouaYTF9esiqBBBhfRxgBkJgoP6oFtq/3Gwd W/qLcGc3ZTtZVDZ7T0he/fL7YqR/4Gc7uqAr9dmmPUHFFXgXyWg1TKOqMhFb1IvPAPEoHRC4H/U E9MG8Kux6JcE4JAft82Mr8BtD/HZWhKN03iSolmqMD25h0Jr7I1NcfxOnNakH7BjzkNJUn/caRf 37p59/M02q8rnaFQO/60rfmklKaU4eRxeCYfWSkhKFXqv12MZjrJXWzdybXavuTt1cXwVdId941 RpNKsC6h/NWLsIorHZLoTHkhJn+eADZFF3iPu8TQUzhicO7IdPFtK1NPzakKhJZ06piO9rVFEdS 2cuWkTvNIg8VHl2c+9NbBLDJR7E9mzDm97ZYjjaXnuD/dFWP8ord2s46EmUuVYYOneI5wZ2oxQb n5ktckB080Plo/FSgNdWPHq8a/guld+wwhllkwT2DdXolW62/S06RRpSDZ+aCCsoCv/jY= X-Received: by 2002:a5d:50c9:0:b0:475:5e4b:588f with SMTP id ffacd0b85a97d-4775a2eff42mr10021826f8f.16.1783047724132; Thu, 02 Jul 2026 20:02:04 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.02.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:02:02 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 13/18] rust: i2c: add adapter-provider (bus controller) registration Date: Fri, 3 Jul 2026 04:01:00 +0100 Message-ID: <20260703030123.2814-14-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The I2C abstraction can register an I2C *client* driver (i2c::Driver), but = not provide a bus. A driver that presents a virtual I2C bus and services the transfers itself -- e.g. DisplayLink's evdi, which exposes a DDC/CI channel= so userspace monitor-control tools can reach the display -- needs the controll= er side. Add i2c::BusController (a trait with master_xfer/functionality over a driver-supplied Context) and i2c::BusAdapter, which fills a struct i2c_adap= ter whose i2c_algorithm dispatches to the trait via trampolines, calls i2c_add_adapter() on creation and i2c_del_adapter() on drop. i2c::Msg is a #[repr(transparent)] view over struct i2c_msg exposing addr/flags/is_read a= nd the payload as a (mutable) byte slice, so a transfer routine needs no unsaf= e. FUNC_I2C is exported for the common functionality() return. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/kernel/i2c.rs | 192 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index 624b971ca8b0..5cde59a3ce46 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -601,3 +601,195 @@ unsafe impl Send for Registration {} // SAFETY: `Registration` offers no interior mutability (no mutation throu= gh &self // and no mutable access is exposed) unsafe impl Sync for Registration {} + +// =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D +// I2C adapter (bus controller / provider) side +// =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D + +/// A single I2C message presented to a [`BusController`]'s transfer routi= ne. +/// +/// Wraps a `struct i2c_msg`; `#[repr(transparent)]` so a C `i2c_msg` arra= y can be viewed directly +/// as a `[Msg]`. +#[repr(transparent)] +pub struct Msg(Opaque); + +impl Msg { + /// The 7-bit (or 10-bit) target address. + #[inline] + pub fn addr(&self) -> u16 { + // SAFETY: `self.0` is a valid `i2c_msg` for the callback's durati= on. + unsafe { (*self.0.get()).addr } + } + + /// The message flags (`I2C_M_*`). + #[inline] + pub fn flags(&self) -> u16 { + // SAFETY: as above. + unsafe { (*self.0.get()).flags } + } + + /// Whether this is a read (`I2C_M_RD`) rather than a write. + #[inline] + pub fn is_read(&self) -> bool { + self.flags() & (bindings::I2C_M_RD as u16) !=3D 0 + } + + /// The message length in bytes. + #[inline] + pub fn len(&self) -> usize { + // SAFETY: as above. + unsafe { (*self.0.get()).len as usize } + } + + /// Whether the message is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() =3D=3D 0 + } + + /// The message payload of a write, as a read-only slice. + #[inline] + pub fn buf(&self) -> &[u8] { + // SAFETY: `buf` points to `len` bytes valid for the callback's du= ration. + unsafe { core::slice::from_raw_parts((*self.0.get()).buf, self.len= ()) } + } + + /// The message buffer of a read, as a mutable slice to fill with the = reply. + #[inline] + pub fn buf_mut(&mut self) -> &mut [u8] { + // SAFETY: `buf` points to `len` writable bytes valid for the call= back's duration. + unsafe { core::slice::from_raw_parts_mut((*self.0.get()).buf, self= .len()) } + } +} + +/// `I2C_FUNC_I2C`: the adapter supports plain I2C (not just SMBus) transf= ers. +pub const FUNC_I2C: u32 =3D bindings::I2C_FUNC_I2C; + +/// A Rust-implemented I2C bus adapter (the controller/provider side). +/// +/// This is the counterpart to the [`Driver`] trait above: rather than bin= ding to an I2C client on +/// an existing bus, an implementer *is* a bus and services transfers. Vir= tual buses -- e.g. the +/// DDC/CI channel DisplayLink's evdi exposes so userspace monitor-control= tools can reach the +/// display -- use this. +pub trait BusController: 'static { + /// Per-adapter driver context, made available to the transfer callbac= ks. + type Context: Send + Sync; + + /// Transfer `msgs` on the bus, returning the number of messages succe= ssfully transferred + /// (as the C `master_xfer` does), or an error. + fn master_xfer(ctx: &Self::Context, msgs: &mut [Msg]) -> Result; + + /// Report the bus functionality bitmask (`I2C_FUNC_*`). + fn functionality(ctx: &Self::Context) -> u32; +} + +/// An owned, registered I2C adapter driven by a [`BusController`]. +/// +/// [`BusAdapter::new`] fills a `struct i2c_adapter` (pointing its algorit= hm at trampolines that +/// dispatch to `T`) and calls `i2c_add_adapter()`; dropping it calls `i2c= _del_adapter()`. +#[pin_data(PinnedDrop)] +pub struct BusAdapter { + #[pin] + adapter: Opaque, + #[pin] + algo: Opaque, + ctx: T::Context, + registered: core::sync::atomic::AtomicBool, +} + +// SAFETY: the adapter is internally synchronized by the I2C core; `ctx` i= s `Send + Sync`. +unsafe impl Send for BusAdapter {} +// SAFETY: see `Send`. +unsafe impl Sync for BusAdapter {} + +impl BusAdapter { + /// # Safety + /// `adap` is a valid adapter whose `algo_data` points to a live `T::C= ontext`. + unsafe extern "C" fn xfer_trampoline( + adap: *mut bindings::i2c_adapter, + msgs: *mut bindings::i2c_msg, + num: crate::ffi::c_int, + ) -> crate::ffi::c_int { + // SAFETY: by the invariant established in `new`, `algo_data` poin= ts to the `T::Context`. + let ctx =3D unsafe { &*((*adap).algo_data as *const T::Context) }; + // SAFETY: the I2C core passes `num` valid `i2c_msg`s; `Msg` is `#= [repr(transparent)]`. + let slice =3D unsafe { core::slice::from_raw_parts_mut(msgs.cast::= (), num as usize) }; + match T::master_xfer(ctx, slice) { + Ok(n) =3D> n as crate::ffi::c_int, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// `adap` is a valid adapter whose `algo_data` points to a live `T::C= ontext`. + unsafe extern "C" fn func_trampoline(adap: *mut bindings::i2c_adapter)= -> u32 { + // SAFETY: by the invariant established in `new`, `algo_data` poin= ts to the `T::Context`. + let ctx =3D unsafe { &*((*adap).algo_data as *const T::Context) }; + T::functionality(ctx) + } + + /// Create and register a new I2C adapter named `name`, parented to `p= arent`, with driver + /// context `ctx`. + pub fn new( + name: &CStr, + parent: &device::Device, + ctx: T::Context, + ) -> Result>> { + let this =3D KBox::try_pin_init( + try_pin_init!(Self { + adapter <- Opaque::ffi_init(|slot: *mut bindings::i2c_adap= ter| { + // SAFETY: `slot` is valid, uninitialized storage; a z= eroed adapter is a valid + // starting point (self-referential fields are set bel= ow, after pinning). + unsafe { *slot =3D bindings::i2c_adapter::default() }; + }), + algo <- Opaque::ffi_init(|slot: *mut bindings::i2c_algorit= hm| { + // SAFETY: `slot` is valid, uninitialized storage. + unsafe { + *slot =3D bindings::i2c_algorithm::default(); + (*slot).__bindgen_anon_1.master_xfer =3D Some(Self= ::xfer_trampoline); + (*slot).functionality =3D Some(Self::func_trampoli= ne); + } + }), + ctx: ctx, + registered: core::sync::atomic::AtomicBool::new(false), + }), + GFP_KERNEL, + )?; + + // `this` now has a stable address; wire the self-referential adap= ter fields and register. + let adap =3D this.adapter.get(); + // Copy the name without its NUL into the fixed 48-byte array (cap= ped at 47 so the + // already-zeroed array stays NUL-terminated even if the name is o= ver-long). + let name_bytes =3D name.to_bytes(); + let n =3D core::cmp::min(name_bytes.len(), 47); + // SAFETY: `adap`/`algo`/`ctx` live for `this`'s (pinned) lifetime= ; `name_bytes` is valid + // for `n <=3D 47` bytes; `parent` is a valid device. + unsafe { + (*adap).algo =3D this.algo.get(); + (*adap).algo_data =3D (&this.ctx as *const T::Context).cast_mu= t().cast(); + core::ptr::copy_nonoverlapping( + name_bytes.as_ptr(), + (*adap).name.as_mut_ptr().cast::(), + n, + ); + (*adap).dev.parent =3D parent.as_raw(); + } + + // SAFETY: `adap` is a fully-initialized adapter. + to_result(unsafe { bindings::i2c_add_adapter(adap) })?; + this.registered + .store(true, core::sync::atomic::Ordering::Release); + Ok(this) + } +} + +#[pinned_drop] +impl PinnedDrop for BusAdapter { + fn drop(self: Pin<&mut Self>) { + if self.registered.load(core::sync::atomic::Ordering::Acquire) { + // SAFETY: the adapter was successfully registered with `i2c_a= dd_adapter` and has not + // been deleted yet. + unsafe { bindings::i2c_del_adapter(self.adapter.get()) }; + } + } +} --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 275F53093D8 for ; Fri, 3 Jul 2026 03:02:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047730; cv=none; b=KyHGizylk1xDViciSz+771lRS7Z93IWgqOVtA8lau7GCmEfHLRoRbbg8ORuTCmlMTvp17oyBS6oZURnmmpQMuCiqAP8irwjkBWlg3CmHJx83Xa7FweKXZjPZuEzIv+3Zh7EnkFZbR0ptNJAM42h/LYKi6Z+c8HVmRnb0w+6cuPo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047730; c=relaxed/simple; bh=v6gmILxfvkjXayLD55FjNvwdivBIkeFnwRzUoGkyJz0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ua2vfKLDdR8SlhsGYpGlq9L9aN3ztFgOgN/jTOiizXA2I4p0oiDClRmEpBRhCSQr/K37tYHRWujgiPhWJra/GcMxniXo00anMe0HfA6+b0D7hqPos8IuI2+e67Z6MbxjVZURVQyYW9Lpx/APlaL57No7YJBZU5pHNTo8WdPblJc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=pebhLvAF; arc=none smtp.client-ip=209.85.128.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="pebhLvAF" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-4938d5f86f3so488205e9.1 for ; Thu, 02 Jul 2026 20:02:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047726; x=1783652526; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=rwRdstWb2Ca9k/qyCFyUDUWmOvWt84KgtkInv8204cw=; b=pebhLvAFYbh0RsavtZtp4b+TZlWb13FnKfkhLcTPkzfUv3Fa6WQx4aY4mfgEdAtEGu VVqXBg5ocxL7uHzplbCTkFXUTTDyGixMW39PZxl8hmP5FFvv5l5N40cKaSeDeYCHYsfK 6Z3QUE11c9w3Ll5HNzi2yAbJK9GCRuKMexB0VAeePLJWkNI38oDnO01+cUK+anNHmMc6 tBzGmG0ZTX7avt85pAQT2s1BPK0a0nZBQgxGEy7tt2p0wUpdkDDN71BpEG0jcHQno1Dj eONII8+T1IVRy+Ii08dtYgFTca1thXmvovHGzH8N9bAuL7euzlAvthfhzbaB3AJeUPxo MjZw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047726; x=1783652526; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=rwRdstWb2Ca9k/qyCFyUDUWmOvWt84KgtkInv8204cw=; b=pEWuF80JniIFJ6Vsa4OLX6XYhPhbVSXF6+gX0q05XhKUDPNbjmxPRM28SfrmTYwipF H/ldMlZtLB7neH/Z6Ez8uE3q9HvgNnKV/iUdS4pX/pONMGxAezwvbrNDEvS5qfOyOPR/ rgo8OHx1muvDPD6WZ0SH9IRuv8n0nY8n2RULB6eF45hB8Is6yChjUUvHSbHf9GVHDOsv 1GZK3BylH5dvS73LPDTikCzwiRzics6gg96aPXEOb+1GATg5rAaK055GQULPGC9KFkZR 7ppyDuHyAZ8nY+mJYovbOh4RwoKArBCm+IJsXLZ5ctPA6KTAbNpojYtMdMTqOCU9b8NP LyIg== X-Forwarded-Encrypted: i=1; AFNElJ8c8vlFgdvFp0MlBOaPDdqfCybPkHMaYuVeT6UQ3bWH58qTFjF+qt5wAhO6nsMRY9DCR0Inr2kLLY20MnE=@vger.kernel.org X-Gm-Message-State: AOJu0YyCQc88tW47RoSpLKvKl+RVC6OPzC5opSwrwnTuCMKlxfvRMet6 U6E4tXO8KF5VWNNjTkEvn2ANe+8loii39Pj7M50H7KlrJSET3Bz7AT/UxckmBHoJYQ== X-Gm-Gg: AfdE7cmfrSGaJx3kSFx+dSP9G/zbDPNrrmo0ylZRUzbpbh/zg0UPvZMfl4Ue4EEUyAH G4k584ucIYMheurJp8iPj4LXzpM3D50wA9QB/Av7eT2Ox5YFzKtSe1v8Mcfa614ge1UBYIYeSi9 KNEqfhsbu1+AKMEuYa5Fv2egR8FjKQi9cXEkBxK8WAcqAQAYYkhme2VTU5y7JkvnnJUgmCtKUlp sHYjoyRi76/G3TDZghXExsPVHmdaPA65qOGBFtvkzXxm/TKARwVFtl0OwzOJ/etFCYNcRp0CzA7 AYuC/Z8lt8jJ6OKgm9S1jND+J0RwHjcRyDgRuhaw9d9olaTV/HphJttnFlh+Z0zwUBtGAj8iFJQ 4pgDhEfKn/hcrQEngXoMXPOJjcSWU1i2feKYkzAhcmJGQcvXUqR91lzyhceI/nurEbA8DeuvnJh 0upfuiHElHLhfTPD6Dt8qEgSC/Sgv1KS9CZRJlpZlyPudoxq8qvEn8mz41 X-Received: by 2002:a05:600c:34ca:b0:490:b26c:64ad with SMTP id 5b1f17b1804b1-493c9b4d759mr26290045e9.5.1783047726415; Thu, 02 Jul 2026 20:02:06 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.02.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:02:04 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 14/18] rust: add sysfs device attribute groups Date: Fri, 3 Jul 2026 04:01:01 +0100 Message-ID: <20260703030123.2814-15-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A driver that exposes control files under sysfs -- for instance DisplayLink= 's evdi, whose /sys/devices/evdi/{count,add,remove_all} files let the daemon c= reate and destroy virtual display cards -- had no safe way to do so from Rust. Add a sysfs module: a driver implements DeviceAttributes (an ATTRS list plus show()/store(), dispatched by attribute name) for the type it stores as a device's driver data, and AttributeGroup::register_root() creates a standal= one root device (root_device_register(), under /sys/devices/) hosting the files. Reads and writes trampoline to show()/store() with the driver data recovered from the device; the file mode (Attr::ro/wo/rw) gates access. The= group removes the files, reclaims the driver data and unregisters the device on d= rop. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/kernel/lib.rs | 1 + rust/kernel/sysfs.rs | 245 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 rust/kernel/sysfs.rs diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 8baa13079071..13729ec06333 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -128,6 +128,7 @@ pub mod std_vendor; pub mod str; pub mod sync; +pub mod sysfs; pub mod task; pub mod time; pub mod tracepoint; diff --git a/rust/kernel/sysfs.rs b/rust/kernel/sysfs.rs new file mode 100644 index 000000000000..f6df17c5013d --- /dev/null +++ b/rust/kernel/sysfs.rs @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Sysfs device attributes. +//! +//! A driver can expose a set of named sysfs files on a device by implemen= ting [`DeviceAttributes`] +//! for the type it stores as that device's driver data, and registering a= n [`AttributeGroup`]. +//! Reads and writes are dispatched by attribute name to the type's [`show= `](DeviceAttributes::show) +//! and [`store`](DeviceAttributes::store) methods; the file mode (see [`A= ttr`]) controls which are +//! reachable from userspace. +//! +//! [`AttributeGroup::register_root`] additionally creates a standalone "r= oot" device +//! (`root_device_register()`, appearing under `/sys/devices/`) to h= ost the group -- the shape +//! DisplayLink's evdi uses for its `add`/`remove`/`count` control files. +//! +//! C headers: [`include/linux/sysfs.h`](srctree/include/linux/sysfs.h), +//! [`include/linux/device.h`](srctree/include/linux/device.h) + +use crate::{ + bindings, + error::{from_err_ptr, to_result}, + page::PAGE_SIZE, + prelude::*, + types::ForeignOwnable, + ThisModule, +}; + +/// Definition of one sysfs attribute file: its name and permission mode (= e.g. `0o444` read-only, +/// `0o200` write-only, `0o644` read-write). +pub struct Attr { + /// The file name. + pub name: &'static CStr, + /// The permission bits (`umode_t`). + pub mode: u16, +} + +impl Attr { + /// A read-only (`0o444`) attribute. + pub const fn ro(name: &'static CStr) -> Self { + Self { name, mode: 0o444 } + } + /// A write-only (`0o200`) attribute. + pub const fn wo(name: &'static CStr) -> Self { + Self { name, mode: 0o200 } + } + /// A read-write (`0o644`) attribute. + pub const fn rw(name: &'static CStr) -> Self { + Self { name, mode: 0o644 } + } +} + +/// The show/store behaviour of a device's sysfs attribute group. +/// +/// Implemented by the type a device stores as its driver data. A shared r= eference to it is handed +/// to [`show`](Self::show)/[`store`](Self::store), dispatched by the attr= ibute `name`. +pub trait DeviceAttributes: Send + Sync + 'static { + /// The attributes exposed by this group. + const ATTRS: &'static [Attr]; + + /// Handle a read of attribute `name`, writing up to one page into `bu= f` and returning the + /// number of bytes written. Only called for readable (`ro`/`rw`) attr= ibutes. + fn show(&self, name: &CStr, buf: &mut [u8]) -> Result { + let _ =3D (name, buf); + Err(EINVAL) + } + + /// Handle a write of `buf` to attribute `name`. Only called for writa= ble (`wo`/`rw`) + /// attributes. + fn store(&self, name: &CStr, buf: &[u8]) -> Result { + let _ =3D (name, buf); + Err(EINVAL) + } +} + +/// A registered sysfs attribute group hosted on a `root_device_register()= ` device. +/// +/// Dropping it removes the files, reclaims the driver data, and unregiste= rs the root device. +pub struct AttributeGroup { + root: *mut bindings::device, + /// Backing storage for the `device_attribute`s handed to `device_crea= te_file()`; must stay + /// alive (and not move) for as long as the files exist, so it lives i= n this heap allocation. + attrs: KVec, + _p: core::marker::PhantomData, +} + +// SAFETY: the root device and its attributes are internally synchronized = by the driver core; `T` +// is `Send + Sync`. +unsafe impl Send for AttributeGroup {} +// SAFETY: see `Send`. +unsafe impl Sync for AttributeGroup {} + +impl AttributeGroup { + /// # Safety + /// `dev`'s driver data is a live `T` set via [`device::Device::set_dr= vdata`]. + unsafe extern "C" fn show_trampoline( + dev: *mut bindings::device, + attr: *mut bindings::device_attribute, + buf: *mut crate::ffi::c_char, + ) -> isize { + // SAFETY: `dev` is a valid device with `T` driver data (invariant= of `register_root`). + let ctx =3D unsafe { borrow_ctx::(dev) }; + // SAFETY: the attribute name is a valid C string for the callback= 's duration. + let name =3D unsafe { as_cstr((*attr).attr.name) }; + // SAFETY: sysfs guarantees `buf` is a writable page. + let slice =3D unsafe { core::slice::from_raw_parts_mut(buf.cast::<= u8>(), PAGE_SIZE) }; + match T::show(ctx, name, slice) { + Ok(n) =3D> core::cmp::min(n, PAGE_SIZE) as isize, + Err(e) =3D> e.to_errno() as isize, + } + } + + /// # Safety + /// `dev`'s driver data is a live `T` set via [`device::Device::set_dr= vdata`]. + unsafe extern "C" fn store_trampoline( + dev: *mut bindings::device, + attr: *mut bindings::device_attribute, + buf: *const crate::ffi::c_char, + count: usize, + ) -> isize { + // SAFETY: as in `show_trampoline`. + let ctx =3D unsafe { borrow_ctx::(dev) }; + // SAFETY: the attribute name is a valid C string for the callback= 's duration. + let name =3D unsafe { as_cstr((*attr).attr.name) }; + // SAFETY: sysfs guarantees `buf` holds `count` readable bytes. + let slice =3D unsafe { core::slice::from_raw_parts(buf.cast::(= ), count) }; + match T::store(ctx, name, slice) { + Ok(()) =3D> count as isize, + Err(e) =3D> e.to_errno() as isize, + } + } + + fn make_attr(a: &Attr) -> bindings::device_attribute { + let mut da: bindings::device_attribute =3D bindings::device_attrib= ute::default(); + da.attr.name =3D a.name.as_char_ptr(); + da.attr.mode =3D a.mode; + da.__bindgen_anon_1.show =3D Some(Self::show_trampoline); + da.__bindgen_anon_2.store =3D Some(Self::store_trampoline); + da + } + + /// Create a root device named `name` and expose `T`'s attributes on i= t. + /// + /// `T` becomes the device's driver data; the attribute callbacks reco= ver it by name-dispatch. + pub fn register_root( + name: &CStr, + module: &'static ThisModule, + ctx: impl PinInit, + ) -> Result> { + // Build the attribute array up front (no external resources yet, = so a failure here needs + // no cleanup). + let mut attrs: KVec =3D + KVec::with_capacity(T::ATTRS.len(), GFP_KERNEL)?; + for a in T::ATTRS { + attrs.push(Self::make_attr(a), GFP_KERNEL)?; + } + // Box the context; its foreign pointer becomes the device's drive= r data. + let ctx_ptr =3D KBox::pin_init(ctx, GFP_KERNEL)?.into_foreign(); + + // SAFETY: `name` is a valid C string; `module` is this module. + let root =3D match from_err_ptr(unsafe { + bindings::__root_device_register(name.as_char_ptr(), module.as= _ptr()) + }) { + Ok(r) =3D> r, + Err(e) =3D> { + // Reclaim the context box we have not attached anywhere y= et. + // SAFETY: `ctx_ptr` came from `into_foreign()` above and = was not consumed. + drop(unsafe { > as ForeignOwnable>::from_forei= gn(ctx_ptr) }); + return Err(e); + } + }; + // SAFETY: `root` is a freshly-registered, valid device; `ctx_ptr`= is its driver data now. + unsafe { bindings::dev_set_drvdata(root, ctx_ptr) }; + + // Register each file against its stable slot in `attrs`. + for da in attrs.iter() { + if let Err(e) =3D to_result(unsafe { bindings::device_create_f= ile(root, da) }) { + // `root_device_unregister` (device_del) removes any files= created so far; + // reclaim the context box first. + Self::teardown(root); + return Err(e); + } + } + + match KBox::new( + Self { + root, + attrs, + _p: core::marker::PhantomData, + }, + GFP_KERNEL, + ) { + Ok(group) =3D> Ok(group), + Err(e) =3D> { + Self::teardown(root); + Err(e.into()) + } + } + } + + /// Reclaim the driver-data box and unregister the root device (which = removes its files). + fn teardown(root: *mut bindings::device) { + // SAFETY: `root` is a valid registered device whose driver data i= s a `Pin>` (or + // NULL); `dev_get_drvdata` returns the pointer stashed in `regist= er_root`. + let ptr =3D unsafe { bindings::dev_get_drvdata(root) }; + if !ptr.is_null() { + // SAFETY: `ptr` came from `Pin::>::into_foreign`. + drop(unsafe { > as ForeignOwnable>::from_foreign(p= tr) }); + } + // SAFETY: `root` was created by `__root_device_register` and not = yet unregistered. + unsafe { bindings::root_device_unregister(root) }; + } +} + +impl Drop for AttributeGroup { + fn drop(&mut self) { + for da in self.attrs.iter() { + // SAFETY: each `da` was passed to `device_create_file` on `se= lf.root` and not removed + // since. + unsafe { bindings::device_remove_file(self.root, da) }; + } + // Reclaim the driver-data box and unregister the root device. + Self::teardown(self.root); + } +} + +/// Borrow the `T` driver data of the device `ptr`. +/// +/// # Safety +/// `ptr` is a valid `struct device` whose driver data is a `Pin>`= stashed by +/// [`AttributeGroup::register_root`], valid for the returned reference's = lifetime. +unsafe fn borrow_ctx<'a, T>(ptr: *mut bindings::device) -> &'a T { + // SAFETY: `ptr` is a valid device by the contract. + let data =3D unsafe { bindings::dev_get_drvdata(ptr) }; + // SAFETY: `data` is the pointer `Pin>::into_foreign` returned= , i.e. a valid `*mut T` + // live for the borrow. + unsafe { &*(data as *const T) } +} + +/// # Safety +/// `ptr` is a valid, NUL-terminated C string for the returned reference's= lifetime. +#[inline] +unsafe fn as_cstr<'a>(ptr: *const crate::ffi::c_char) -> &'a CStr { + // SAFETY: by the contract, `ptr` is a valid NUL-terminated C string. = `crate::ffi::c_char` is + // `u8` while `core::ffi::CStr::from_ptr` takes `*const i8`, so cast t= he pointer. + unsafe { CStr::from_ptr(ptr.cast()) } +} --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B33B530B509 for ; Fri, 3 Jul 2026 03:02:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047732; cv=none; b=lT3aCcU7RMG0edlDoopoMJNKCUc3J9MM2ewls24JuI/es4xQ6W+bC5CiH+puDmvCE5lAwXHpLg4NZJ/d8wdNvkCnD8Zgy7wTm6CF5c97NYuEPxx9oBKpTock6lAfu+CudbJCHyBT39Ka7ghZ65BqjF16j0Aq8Ngmj247Gwh8C3E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047732; c=relaxed/simple; bh=Nh4vxX4TO7iCe3TREKyhNo31iLUcbd6JX2zPn7QBx4c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=gmcY+KWbrkuNIX2Qt7vSyrba8mC0/H6XrdT5RiYTjoUTrLtWSoX3rLmR9QsPJkSDjfPBiqI4nF32ywbAWdVkn60yl2NQoZ6BCrMmEWL55w0D8AABLjvqIIo9LjWEJLekUcyTGyyJcUvKhZH/8UiIzdfwV359Zn283cl5XAol26g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=Yk5ejc3b; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="Yk5ejc3b" Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-493c83474ddso416015e9.3 for ; Thu, 02 Jul 2026 20:02:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047729; x=1783652529; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=oJo7TDfciSOR3fPsvfGRU1Um27ufdThVMtKgOOefwMI=; b=Yk5ejc3bnzQWdcjNAu8GSG25nF/vaLy3eZdWHP37KAc961R1r6kKmfHsi2OPhQTDm3 MEMpXiGGISEF9LnF5WWVljeGAOmshp7n/ALmrIlIHi/q8Lr0JVZtMVQB7Iwho4DPRgi5 Ab1S5s1eq0ODzxL/GEiPoOWhVN8ZnT+/LTNRC6fuUph2sRbZGfpEp2NjzPeLPrG6e+0f B3kj/OPbxOwHusCLI4BQeik11T+B8cF/eyJQ2P9atSi2L/CdFbf8oCQ4QLEOTnn97KUB dpS4Hw8DE4VMZOdoto+UG04J9BzfsDa4RfWbXdFf/rDYxhAmykufs/gRmFChy/Ej28y/ UXgA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047729; x=1783652529; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=oJo7TDfciSOR3fPsvfGRU1Um27ufdThVMtKgOOefwMI=; b=ZzUJg6bpT2mkMog7be8SYtizy5guh2RbnQhr+bBEX/PJ46dSwFkkr+mnRXtdRt6e4/ WGNsOUbTpXSdpgoTiQvc9TDsTWZA5yIjL1p108sxhxakApOW7l7bxyUcBbnD1wVyfxDL x9+mhy2LkL4VW6IldcNjRVQj6ZzRyJbWjT8eocpzsLsP2eJVCenyhjv1qJhHIE7ehg5K emZrWC9LnqlyzJ1gj7MAvhXygKbNhlJA2if4sbgrbMrM0hlANkvxiR+4xjl/wDhC77qA FyttK3on5Lc+s+OgHmXDODqPlUwfKgTId0BUhb+oLKDVljVT95y/Z4oFaIzXdVih2Ote aOKA== X-Forwarded-Encrypted: i=1; AFNElJ91t4gjqjc37XIWi1oN0chrt+fmTOKEeQ/ip0VTRSxlDpPj5gT6dfwdTAYpBrE6KAUqBUHiPQ2WIJ5km7Y=@vger.kernel.org X-Gm-Message-State: AOJu0YyBpkNgnHMhC8wnYPpA7DoBD5hoi579DpppZy3rqESTxPikJNjy x4fQXvsg4pe1OAAWKurgmukJo898glri0Af/lZHZrkkreWZ0dh1X0NyRkKe1FKfT+A== X-Gm-Gg: AfdE7cmKLEM+zhmvSwOcxlMKo0+LCX6H586ZltbwTXE+nrUk9X/ERen6i6RnPLtd1WB upHN56nijA4RWrk/slQAE5IRRGAr3iLU0EKoZ+eu6gN9AsMkPx67PqxSxMRP46X5wOefGIgGqDW JwP5bEai9qd4bzRSzbGAOfYIcgR9uSCarvmOjJzOKwR/EQBcJwn5wt1vZxD2YYp7752vj4LPrZP LIlp+XOARaSLlN9fGCmbl8i/Wh6xsHo6bcreq789db+DMLXyMX0xtLRnQ2FMLf2+Gzs4SAkEf68 oRR6Uh58X3ToIqoVnm/xlpQE4BqBkyoF6BiSh6NHS5d7ycn9RBbp0ioW9I7XIje3BIzDTYRruaU HRjJZs0pFo4HPdJGLF31SK7k5ozoIQVzXu7bqUoeP8zDea4ZDp1Uloj4oW9lkBMYi/4c3xhk3be 9EQdZ0Tlfq3dFtlLKk0k/ydzQLcB2mL0l2SbeBMLrZiL5Q+PEkbxY9BeKE X-Received: by 2002:a7b:c34e:0:b0:493:ad8a:e7fd with SMTP id 5b1f17b1804b1-493c3cd9d76mr72546515e9.14.1783047729025; Thu, 02 Jul 2026 20:02:09 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.02.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:02:07 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 15/18] rust: drm: support hardware cursor planes with sleepable event delivery Date: Fri, 3 Jul 2026 04:01:02 +0100 Message-ID: <20260703030123.2814-16-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> 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: quoted-printable A driver that forwards a hardware cursor plane's contents to userspace (e.g. DisplayLink's evdi, whose daemon composites the cursor itself) needs to han= d the client a GEM handle for the cursor bitmap on each update. drm_gem_handle_cr= eate() may sleep, so the connected file must be reachable from sleepable context -= - but drm::event::EventChannel stored the receiver under the device event_lock (a spinlock). Rework EventChannel to guard the receiver with a Mutex instead: send() hold= s it across the event_lock-protected reserve+queue (so delivery still cannot rac= e a close), and a new with_receiver() runs a closure with the connected File wh= ile holding the mutex, allowing a sleepable GEM-handle creation without the file closing underneath it (notify_close() takes the same mutex). new() now take= s a lock class so the class lives in the driver module. connect/disconnect/ notify_close/is_connected no longer need the device. Add the accessors such a cursor consumer needs: - plane state crtc_x()/crtc_y() (cursor position) and hotspot_x()/hotspot= _y(); - Framebuffer::format() (FourCC) and Framebuffer::create_handle() (a GEM = handle for plane-0 in a given file). Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/kernel/drm/event.rs | 179 +++++++++++++++-------------- rust/kernel/drm/kms/framebuffer.rs | 28 +++++ rust/kernel/drm/kms/plane.rs | 20 ++++ 3 files changed, 138 insertions(+), 89 deletions(-) diff --git a/rust/kernel/drm/event.rs b/rust/kernel/drm/event.rs index 39d65d2a8fb2..dadb38c7df20 100644 --- a/rust/kernel/drm/event.rs +++ b/rust/kernel/drm/event.rs @@ -13,8 +13,8 @@ //! target `struct drm_file` must be valid for that whole critical section= . Reading the target file //! pointer outside the lock is a classic source of use-after-free/NULL-de= ref crashes (a driver's //! flush worker racing a client disconnect). [`EventChannel`] encodes tha= t rule in its API: the -//! receiver file is only ever touched while `event_lock` is held, so regi= stration, teardown and -//! delivery cannot race. +//! receiver file lives behind a mutex and [`send`](EventChannel::send) ho= lds it across the whole +//! `event_lock`-protected reserve+queue, so registration, teardown and de= livery cannot race. //! //! C header: [`include/drm/drm_file.h`](srctree/include/drm/drm_file.h) =20 @@ -29,8 +29,9 @@ error::to_result, interrupt, prelude::*, + sync::{LockClassKey, Mutex}, }; -use core::{cell::UnsafeCell, mem}; +use core::mem; =20 /// A driver-defined DRM event payload. /// @@ -63,94 +64,101 @@ struct EventStorage { =20 /// A per-device channel that delivers driver events to the [`File`] regis= tered as the receiver. /// -/// The receiver pointer is stored under the owning device's `event_lock` = =E2=80=94 the same lock the DRM -/// core requires held while queuing events =E2=80=94 so [`connect`](Self:= :connect), -/// [`disconnect`](Self::disconnect), [`notify_close`](Self::notify_close)= and [`send`](Self::send) -/// are all serialized against each other and against the core's own event= handling. This makes the -/// "send to a file that just disconnected" race unrepresentable. +/// The receiver `drm_file` is stored behind a [`Mutex`], not the device `= event_lock`, so it can be +/// touched from sleepable context =E2=80=94 in particular so a driver can= create a GEM handle for the +/// connected client (`drm_gem_handle_create` may sleep) via [`with_receiv= er`](Self::with_receiver). +/// [`send`](Self::send) still acquires the device's `event_lock` (as the = DRM core requires) while +/// holding the mutex, so the receiver pointer is valid for the whole rese= rve+queue and cannot race +/// [`disconnect`](Self::disconnect)/[`notify_close`](Self::notify_close).= This makes the classic +/// "deliver to a file that just closed" use-after-free unrepresentable. /// -/// A channel is bound to exactly one [`Device`]: always pass the same dev= ice to every method. +/// A channel is bound to exactly one [`Device`]: always pass the same dev= ice to [`send`](Self::send). /// Drivers must call [`notify_close`](Self::notify_close) from their `pos= tclose` hook (and/or /// [`disconnect`](Self::disconnect) when tearing the logical connection d= own) so the receiver /// pointer never outlives the file it refers to. +#[pin_data] pub struct EventChannel { - /// The receiver `drm_file`, or null when no client is connected. + /// The receiver `drm_file` address, or `0` when no client is connecte= d. /// - /// Only ever accessed while the owning device's `event_lock` is held. - receiver: UnsafeCell<*mut bindings::drm_file>, + /// A `usize` (not a raw pointer) so the `Mutex` is `Send`/`Sync` on i= ts own; the value is only + /// ever turned back into a `*mut drm_file` while the mutex is held. + #[pin] + receiver: Mutex, } =20 -// SAFETY: `receiver` is only accessed while holding the owning device's `= event_lock`, which -// serializes all access across threads. -unsafe impl Send for EventChannel {} -// SAFETY: See `Send`. -unsafe impl Sync for EventChannel {} - impl EventChannel { /// Create a new, disconnected channel. - pub const fn new() -> Self { - Self { - receiver: UnsafeCell::new(core::ptr::null_mut()), - } + /// + /// `key` is the lock class for the receiver mutex; pass + /// [`static_lock_class!()`](crate::sync::static_lock_class) from the = driver so the class lives + /// in the driver module. + #[inline] + pub fn new(key: Pin<&'static LockClassKey>) -> impl PinInit { + pin_init!(Self { + receiver <- Mutex::new(0, c"EventChannel::receiver", key), + }) } =20 /// Register `file` as the receiver for events sent through this chann= el. /// - /// Any previously registered file is replaced. Runs under `dev`'s `ev= ent_lock`. - pub fn connect( - &self, - dev: &Device, - file: &File, - ) { - let irq =3D interrupt::local_interrupt_disable(); - let _guard =3D dev.event_lock().lock_with(&irq); - // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. - unsafe { *self.receiver.get() =3D file.as_raw() }; + /// Any previously registered file is replaced. + pub fn connect(&self, file: &File) { + *self.receiver.lock() =3D file.as_raw() as usize; } =20 /// Clear the receiver, dropping any future events on the floor until = the next - /// [`connect`](Self::connect). Runs under `dev`'s `event_lock`. - pub fn disconnect(&self, dev: &Devic= e) { - let irq =3D interrupt::local_interrupt_disable(); - let _guard =3D dev.event_lock().lock_with(&irq); - // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. - unsafe { *self.receiver.get() =3D core::ptr::null_mut() }; + /// [`connect`](Self::connect). + #[inline] + pub fn disconnect(&self) { + *self.receiver.lock() =3D 0; } =20 /// Clear the receiver if it is `file`. /// /// Intended to be called from the driver's `postclose` hook so the st= ored pointer never - /// outlives the file. Runs under `dev`'s `event_lock`. - pub fn notify_close( - &self, - dev: &Device, - file: &File, - ) { - let irq =3D interrupt::local_interrupt_disable(); - let _guard =3D dev.event_lock().lock_with(&irq); - // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. - unsafe { - if *self.receiver.get() =3D=3D file.as_raw() { - *self.receiver.get() =3D core::ptr::null_mut(); - } + /// outlives the file. + pub fn notify_close(&self, file: &File) { + let mut recv =3D self.receiver.lock(); + if *recv =3D=3D file.as_raw() as usize { + *recv =3D 0; } } =20 - /// Whether a receiver is currently connected. Runs under `dev`'s `eve= nt_lock`. - pub fn is_connected(&self, dev: &Dev= ice) -> bool { - let irq =3D interrupt::local_interrupt_disable(); - let _guard =3D dev.event_lock().lock_with(&irq); - // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. - !unsafe { *self.receiver.get() }.is_null() + /// Whether a receiver is currently connected. + #[inline] + pub fn is_connected(&self) -> bool { + *self.receiver.lock() !=3D 0 + } + + /// Run `f` with the connected receiver [`File`], if any, returning it= s result. + /// + /// The receiver mutex is held for the duration, so `f` may sleep (e.g= . to create a GEM handle + /// in the client's file with [`Framebuffer::create_handle`](crate::dr= m::kms::framebuffer::Framebuffer::create_handle)) + /// without the file being closed underneath it: [`notify_close`](Self= ::notify_close) takes the + /// same mutex and so blocks until `f` returns. Returns [`None`] if no= client is connected. + /// + /// # Type parameter + /// + /// `F` must be the same [`DriverFile`] type the receiver was [`connec= t`](Self::connect)ed with. + pub fn with_receiver(&self, f: impl FnOnce(&File)= -> R) -> Option { + let recv =3D self.receiver.lock(); + let raw =3D *recv as *mut bindings::drm_file; + if raw.is_null() { + return None; + } + // SAFETY: `raw` was stored from a live `File` by `connect`; it= stays valid because + // `notify_close`/`disconnect` (which clear it) take this same mut= ex, held here. + let file =3D unsafe { File::::from_raw(raw) }; + Some(f(file)) } =20 /// Deliver `payload` to the connected receiver. /// /// Allocates the backing event, fills its `drm_event` header, and han= ds it to the DRM core - /// under `event_lock`. If no receiver is connected the event is silen= tly dropped and `Ok(())` - /// is returned (mirroring the C drivers, which cannot deliver to a cl= osed client). If the - /// client has no space left in its event queue this returns `Err(ENOM= EM)` and the event is - /// dropped; the caller may retry later. + /// under `event_lock` (acquired while the receiver mutex is held). If= no receiver is connected + /// the event is silently dropped and `Ok(())` is returned (mirroring = the C drivers, which + /// cannot deliver to a closed client). If the client has no space lef= t in its event queue this + /// returns `Err(ENOMEM)` and the event is dropped; the caller may ret= ry later. pub fn send( &self, dev: &Device, @@ -175,41 +183,34 @@ pub fn send( storage.pending.event =3D ev_ptr; let pending: *mut bindings::drm_pending_event =3D &raw mut storage= .pending; =20 - let irq =3D interrupt::local_interrupt_disable(); - let guard =3D dev.event_lock().lock_with(&irq); - - // SAFETY: `receiver` is only accessed under `event_lock`, which i= s held here. - let receiver =3D unsafe { *self.receiver.get() }; + // Hold the receiver mutex across the whole reserve+queue so the f= ile can't close underneath + // us, then take `event_lock` as the C API requires. + let recv =3D self.receiver.lock(); + let receiver =3D *recv as *mut bindings::drm_file; if receiver.is_null() { - drop(guard); - // `storage` is dropped here, freeing the allocation. + // No client; `storage` is dropped here, freeing the allocatio= n. return Ok(()); } =20 let dev_raw =3D dev.as_raw(); - // SAFETY: `dev_raw` and `receiver` are valid; `pending`/`ev_ptr` = point into a live - // allocation; `event_lock` is held as required by the C API. - let ret =3D unsafe { - bindings::drm_event_reserve_init_locked(dev_raw, receiver, pen= ding, ev_ptr) - }; - if ret !=3D 0 { - drop(guard); - // Reservation failed, so `pending` was never queued: `storage= ` frees the allocation. - return to_result(ret); + let irq =3D interrupt::local_interrupt_disable(); + { + let _ev =3D dev.event_lock().lock_with(&irq); + // SAFETY: `dev_raw`/`receiver` are valid; `pending`/`ev_ptr` = point into a live + // allocation; `event_lock` is held as required by the C API. + let ret =3D unsafe { + bindings::drm_event_reserve_init_locked(dev_raw, receiver,= pending, ev_ptr) + }; + if ret !=3D 0 { + // Reservation failed, so `pending` was never queued: `sto= rage` frees it. + return to_result(ret); + } + // SAFETY: The event is reserved against `receiver`; hand owne= rship of the allocation to + // the DRM core, which frees it (via `kfree` of the `drm_pendi= ng_event` at offset 0) + // once delivered or the file closes. `event_lock` is held as = required. + unsafe { bindings::drm_send_event_locked(dev_raw, pending) }; } - - // SAFETY: The event is reserved against `receiver`; hand ownershi= p of the allocation to the - // DRM core, which frees it (via `kfree` of the `drm_pending_event= ` at offset 0) once the - // event is delivered or the file is closed. `event_lock` is held = as required. - unsafe { bindings::drm_send_event_locked(dev_raw, pending) }; let _ =3D KBox::into_raw(storage); - drop(guard); Ok(()) } } - -impl Default for EventChannel { - fn default() -> Self { - Self::new() - } -} diff --git a/rust/kernel/drm/kms/framebuffer.rs b/rust/kernel/drm/kms/frame= buffer.rs index c7089b12d0d1..e12fa7a24e15 100644 --- a/rust/kernel/drm/kms/framebuffer.rs +++ b/rust/kernel/drm/kms/framebuffer.rs @@ -121,6 +121,34 @@ pub fn pitch(&self, index: usize) -> u32 { unsafe { (*self.as_raw()).pitches[index & 3] } } =20 + /// The framebuffer's pixel format as a FourCC code (`DRM_FORMAT_*`). + #[inline] + pub fn format(&self) -> u32 { + // SAFETY: a valid framebuffer always has a non-null, immutable `f= ormat` descriptor. + unsafe { (*(*self.as_raw()).format).format } + } + + /// Create a GEM handle for this framebuffer's plane-0 backing object = in `file`'s handle space, + /// returning it. + /// + /// This lets a driver hand a userspace client (e.g. via an event) a h= andle to a buffer the + /// client did not create itself -- for instance a cursor bitmap set b= y a compositor that a + /// separate control daemon needs to read. May sleep. + pub fn create_handle( + &self, + file: &crate::drm::File, + ) -> Result { + // SAFETY: `as_raw()` is a valid framebuffer; `obj[0]` is its plan= e-0 GEM object. + let obj =3D unsafe { (*self.as_raw()).obj[0] }; + if obj.is_null() { + return Err(EINVAL); + } + let mut handle =3D 0u32; + // SAFETY: `file` and `obj` are valid; `drm_gem_handle_create` ini= tializes `handle`. + to_result(unsafe { bindings::drm_gem_handle_create(file.as_raw().c= ast(), obj, &mut handle) })?; + Ok(handle) + } + /// Map this framebuffer's plane-0 backing pages into the kernel addre= ss space for CPU /// access, for the duration of the returned guard. /// diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 3863dfe1b84c..fcaa8ba550fe 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -605,6 +605,26 @@ fn crtc_h(&self) -> u32 { self.as_raw().crtc_h } =20 + /// The X position of this plane's destination rectangle within the CR= TC (`crtc_x`). + fn crtc_x(&self) -> i32 { + self.as_raw().crtc_x + } + + /// The Y position of this plane's destination rectangle within the CR= TC (`crtc_y`). + fn crtc_y(&self) -> i32 { + self.as_raw().crtc_y + } + + /// The cursor hotspot X offset (`hotspot_x`), for a virtualized curso= r plane. + fn hotspot_x(&self) -> i32 { + self.as_raw().hotspot_x + } + + /// The cursor hotspot Y offset (`hotspot_y`), for a virtualized curso= r plane. + fn hotspot_y(&self) -> i32 { + self.as_raw().hotspot_y + } + /// Return the current [`OpaqueCrtc`] assigned to this plane, if there= is one. fn crtc<'a, 'b: 'a, D>(&'a self) -> Option<&'b OpaqueCrtc> where --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f45.google.com (mail-wr1-f45.google.com [209.85.221.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 15ECB30C152 for ; Fri, 3 Jul 2026 03:02:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047734; cv=none; b=RnuYQed2ARwbsIBv7BwRJlSsVSfEP39XJvJbl+xy26Zov6xHEK1J15eymo0XmcGfDlesbcQI+kNhAONsMZwlXvGyIZCBxv5LHx6FKTtSkAlB6bm0iBKCgvjZsNmDFtsf2hr/C/8efmklpWk0Xtb8ugqldJWoidI0bbZqsqWs4gA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047734; c=relaxed/simple; bh=1uqvohIdAq62wj82E4SBuODnbfJ/O9m3po4KKTfsvrc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GcE8vfw3MNq1PvPn+QM+yTAQdNEBJTEjQy2aRS1aMUW7+zlhtcLB19gNxY1OgcWUR86raRzlWDot75c/bgzIOqknKHScNPk9B9SJs3IV0Vjx93tnPgdPtlK79o59cmHF1UNzQuoVJV4U9yPZZAq4t4kCx6Ee/gH1ypAt8flFt1Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=ItIRHacD; arc=none smtp.client-ip=209.85.221.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="ItIRHacD" Received: by mail-wr1-f45.google.com with SMTP id ffacd0b85a97d-4758bd3731bso58492f8f.0 for ; Thu, 02 Jul 2026 20:02:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047731; x=1783652531; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=4TbX8MznRE/lSK8zDxNF2GQ6yML1Vz7QFki+Q6IYGOU=; b=ItIRHacDuZmlj03OnZ7vTpiIpCun5PYxy+8oQ9OhxHvIsbVZUaHBxEl40g6a0zlgTJ SnQ+rp4GPy6wf+XShMlWfDhHWaB8DgZUZocDe3sPofBd/b0qMyORlu1Y7BgaDalG2KGH jVPn7WoiGx2cTPwBFPq5pEW6JDqqUXb4n4e2on2Q3rBvfCcrhv0qvGAzvO+tblZHW508 9eDvQPboA/PmYMJGoKI9h4Qc4uSuLWxwXuy7haKVMkfK9ssgSXI6lx7NGm7dIbtjmFJ5 v3O2kAY6mFLM1+aNYTTOmJBx1qY3gQYABBYrpExkwwIgdsFDsPtuzECozk5Ng12mpcMR cc4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047731; x=1783652531; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=4TbX8MznRE/lSK8zDxNF2GQ6yML1Vz7QFki+Q6IYGOU=; b=s/BqR+PlDYuRNshsXW9Jftk1UH95cJ3krxyVtV6vB+Zp6RyGT+GGXmRTFiZrquDApT r25Ha5LCVg+F+d4wOziEgY/2Y9LSBfIm86dYUPdowk19iAz4s/n2uVfs2yS4yDC4h4Fv cVxZMZAfQHr/F0lT2nZUnHyfsC00qcb6fAZfrImYPB5Hvmat01BAn+Mp6sPUQHoqSbDM NZT+R808v2UMWcDCHrVWXCOaIcL3RhG733J8aY4mjuuZPpr4As5yhcoCFuu6X7V1Y7i/ CoHKU7FRotFeF11WB0LmrwfOjdiSbB2QU8+MZB0CvG983IH6Zznt/lrIMNnzsLQDq2H8 Nvwg== X-Forwarded-Encrypted: i=1; AHgh+Rpg+qjOceEfVn60DCGKJb3EYWaB1br4TqXD6Zm+e8irBQgUaEQMIwSi2hzRfUZ1MWlC/SPrReDFjNGfRbQ=@vger.kernel.org X-Gm-Message-State: AOJu0YzFknD+EDxZP0b63L3QsY1Ycd08gahGqxkH51y7I4pxSFoZkTez qf4juMgRW4WHzjhDN4RFPjMuZ/bn1QtnfVlN3bHGJerebdnUUozgGht8M4SNANVBFQ== X-Gm-Gg: AfdE7cnthbY88gHuL4snEPzVvteoxJunLw67ZxngxAG+V2UD7YBqi8KYinaJw/EPH2R 0FeLF02/OxtKnvz+9vYL5q9xUeFbc0KBbXUWBr3Bs5OKbfCkYn48TXnWmaNjOauBfTn32Fis6t/ 4CIAVtUMp9ph21a1Sajxbdyz3gXVEB6ETqm3hnXxDhHcHqz9Lc8Vkt22UoHQ2gyDa4tHFhxghKj bULTSri1lJ2bLOyRyygQBEqmRSxT9iIBlV/3c/WxGXuKntbWm90idy//AKFHVXPR1w+y0PGx2Cg xS/GqSRR0L4QagENUvj/HKUqoSY6sZbOyWi0nf770WtDHlyO+Mmzn7VLdW6lqDvNsGSo3hwy6JH 4WiJsecEXyD6TbF81sFsWi+mx9uhIx5rkakH2iYU2pkmPdfB63NpoYaFfK0gQVlY/4cLbHvyFp7 2VE+GClNiBgUO8ViAmJvE0aVL0O18cwMqujjh5i/MLj77wjDSeXJhAeiTl X-Received: by 2002:a05:6000:41fe:b0:46f:8561:fe60 with SMTP id ffacd0b85a97d-4793685fe58mr3081792f8f.17.1783047731472; Thu, 02 Jul 2026 20:02:11 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.02.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:02:09 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 16/18] rust: drm: add CRTC gamma LUT and plane rotation property bindings Date: Fri, 3 Jul 2026 04:01:03 +0100 Message-ID: <20260703030123.2814-17-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Two more mode-object properties a real driver reaches for, and the last the DisplayLink vino driver needs to match its pre-safe-KMS feature set: - CRTC gamma: UnregisteredCrtc::enable_gamma() (drm_crtc_enable_color_mgmt, creating a GAMMA_LUT property) and RawCrtcState::gamma_lut() to read the set ramp back as a &[drm_color_lut]. - Plane rotation: UnregisteredPlane::create_rotation_property() (drm_plane_create_rotation_property) and RawPlaneState::rotation() / crtc_x()/crtc_y()/hotspot_x()/hotspot_y() to read the plane's placement and orientation. drm_plane_create_rotation_property lives in , so add that to bindings_helper.h. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/kms/crtc.rs | 33 +++++++++++++++++++++++++++++++++ rust/kernel/drm/kms/plane.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index e5cfda610781..7d04abec1215 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index fbe113676e2f..b5128ae1ca95 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -356,6 +356,17 @@ pub fn new<'a, 'b: 'a, PrimaryData, CursorData>( // SAFETY: We just allocated the crtc above, so this pointer must = be valid Ok(unsafe { &*this }) } + + /// Enable colour management on this CRTC, creating a `GAMMA_LUT` prop= erty of `gamma_size` + /// entries that userspace can program (no degamma LUT, no CTM). The s= et LUT is then readable + /// from the CRTC state via [`RawCrtcState::gamma_lut`]. + /// + /// Call this during [`KmsDriver::probe`](crate::drm::kms::KmsDriver::= probe), before the device + /// is registered. + pub fn enable_gamma(&self, gamma_size: u32) { + // SAFETY: `as_raw()` is a valid, not-yet-registered CRTC. + unsafe { bindings::drm_crtc_enable_color_mgmt(self.as_raw(), 0, fa= lse, gamma_size) }; + } } =20 // SAFETY: We inherit all relevant invariants of `Crtc` @@ -686,6 +697,28 @@ fn mode(&self) -> &DisplayMode { // interface can ensure this access is serialized. unsafe { DisplayMode::as_ref(&(*self.as_raw()).mode) } } + + /// Returns the CRTC's gamma LUT for this state as an array of [`drm_c= olor_lut`] entries, or + /// [`None`] if no gamma LUT is programmed. Requires gamma to have bee= n enabled on the CRTC + /// (see [`UnregisteredCrtc::enable_gamma`]). + /// + /// [`drm_color_lut`]: bindings::drm_color_lut + fn gamma_lut(&self) -> Option<&[bindings::drm_color_lut]> { + // SAFETY: `as_raw()` is a valid `drm_crtc_state`. + let blob =3D unsafe { (*self.as_raw()).gamma_lut }; + if blob.is_null() { + return None; + } + // SAFETY: a non-null gamma_lut blob is valid for the state's life= time. + let (data, length) =3D unsafe { ((*blob).data, (*blob).length) }; + let n =3D length / core::mem::size_of::(); + if data.is_null() || n =3D=3D 0 { + return None; + } + // SAFETY: the blob's `data` holds `n` contiguous `drm_color_lut` = entries (the blob length + // is a multiple of the entry size), valid for the state's lifetim= e. + Some(unsafe { core::slice::from_raw_parts(data.cast::(), n) }) + } } impl RawCrtcState for T {} =20 diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index fcaa8ba550fe..ead875316829 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -328,6 +328,28 @@ pub fn new<'a, 'b: 'a>( // SAFETY: We just allocated the plane above, so this pointer must= be valid Ok(unsafe { &*this }) } + + /// Attach a rotation property to this plane, advertising `supported_r= otations` (a bitmask of + /// `DRM_MODE_ROTATE_*` | `DRM_MODE_REFLECT_*`) with initial value `de= fault_rotation`. The + /// selected value is then readable from the plane state via + /// [`RawPlaneState::rotation`](crate::drm::kms::plane::RawPlaneState:= :rotation). + /// + /// Call this during [`KmsDriver::probe`](crate::drm::kms::KmsDriver::= probe), before the device + /// is registered. + pub fn create_rotation_property( + &self, + default_rotation: u32, + supported_rotations: u32, + ) -> Result { + // SAFETY: `as_raw()` is a valid, not-yet-registered plane. + to_result(unsafe { + bindings::drm_plane_create_rotation_property( + self.as_raw(), + default_rotation, + supported_rotations, + ) + }) + } } =20 /// A trait implemented by any type that acts as a [`struct drm_plane`] in= terface. @@ -625,6 +647,13 @@ fn hotspot_y(&self) -> i32 { self.as_raw().hotspot_y } =20 + /// The plane's rotation/reflection (`DRM_MODE_ROTATE_*` | `DRM_MODE_R= EFLECT_*` bitmask), for a + /// plane with a rotation property (see + /// [`UnregisteredPlane::create_rotation_property`]). Defaults to `DRM= _MODE_ROTATE_0`. + fn rotation(&self) -> u32 { + self.as_raw().rotation + } + /// Return the current [`OpaqueCrtc`] assigned to this plane, if there= is one. fn crtc<'a, 'b: 'a, D>(&'a self) -> Option<&'b OpaqueCrtc> where --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wr1-f44.google.com (mail-wr1-f44.google.com [209.85.221.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 798C330CD85 for ; Fri, 3 Jul 2026 03:02:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047737; cv=none; b=u0zeD5YK5TB01I3HayP3fKkgXfqcH0LJ1PZplIOJBgmtOQQftHhDKrRawySr0wN7DpkHl/MR/HjgrDGjk7MbwBkpPJXShj0rsm3M5xaS7JfGuhvPY1RmsswvSRArTfqAEUINBYeT5ATjiFxoFGdTdx8Xl+d+EawjGLyyybkMRbM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047737; c=relaxed/simple; bh=fRfRWSbJImzHZruC9DYmRbxAwGAifNOTfVGD9HhsIVk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ud6RXzUAoJmjqM90fJmvS7Em9v96hAl+IX+uh1dyyNrwTaN9V8En6EhbhYsUYJCDAiXCWPE0LrIoLmY5r8U3h77vyWL/bwa2l5Zq5+/1/OtX41sq4SmDaGvK6sCZqFAM7qvXPpj98NucNybM0h00MhEFEeuKV81dXsZkz5/pP90= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=srvPcAMv; arc=none smtp.client-ip=209.85.221.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="srvPcAMv" Received: by mail-wr1-f44.google.com with SMTP id ffacd0b85a97d-475881b9a4bso79216f8f.3 for ; Thu, 02 Jul 2026 20:02:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047734; x=1783652534; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=676W53NrP+v1JtRit87SUFwRygfrhSqyRXaV/N96IbY=; b=srvPcAMvJV6eIG2uXRn0NaCQU2kG5pcrL+ZCt95nD1uaDp2DnBmuAfqO4+QAR8qqxr uXPXiGsg819zb/VTTyLt93QQ42IhCBIOkI8IHCI03KtN54gtCaqOftWjG04oeWipqE6A bl9+aHBvy6XbxHCHX5EwLG9PJzDfAW+2ABU6jxUegoswHK38kfNP0TYeyVsiTJicebjO 86VSPt+7TDtXcl/V8b2tB7lWH5wO2XTw2W003w4FgxpPtMXF2F61T9Ru61Tm4q7G5mIa XFrPZhojuyTQ69bj6ayZoyd6oioZZ1Vvz2OUElgaqWq8xxYSPQb2Nbs1s8td/4gekhiC vjRA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047734; x=1783652534; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=676W53NrP+v1JtRit87SUFwRygfrhSqyRXaV/N96IbY=; b=SR1k3MQR2omtqdk/gdJB1KzqssXILD/ILVQhR4T9B9wDziwuwEUsF4F8Z1Y8FaOwsI 3bfJ3xhsBYd1F00XyZ5wCcHNMC2a29ixk8+MFOsCgcUD4Bv8dTRrENNY1j67obS0qh+/ Yy6X1yb2i1xj6TLrevI8ArQJIp4bCym5s+Yap8Mk9bLDseW2t0fba/UT4/HBG3af+PaR Mm/v4MppT5osBYIaVWmX83IAX+yXx0pTejNrxylkTsW3hvojgyF8ysp9/6SStAPqPZ+U UO7ay/guSJVEFXtM2SLhsZV1GGAOne6KK/8bQDNxH6SivagofUor1M0TjP/+U2K04jk/ aVVw== X-Forwarded-Encrypted: i=1; AHgh+RoXJIaYHMlS8JrcL67HKvDETtidOYM601mg7IcH6RZMqJ8SWvaLndFkKRjzltgUsI+8X9dPvSM4zrtsvno=@vger.kernel.org X-Gm-Message-State: AOJu0YyUm5rLbEJoFwYrMC1fRscMf0YmYHBlwS9OF0NkcPbWiZcGYszV Z6WsA/cJ75c91FMwfZPY1kk8wlt/J60FMWmQSiWQ07byASoEht04JXJJ6YSN58NbsA== X-Gm-Gg: AfdE7cnweY2rbJl6lrcyds7Mm5Wl7+pRPhdKsklC4P3Mb7RSt4dxUm3C3xNCTGDo8pb PBZHkr/XBXJqe51OkamrQNDH0Y0pGtZM8NRjfIBpzesb0hfUfj2d2NCgoFvGFOQ9DEb+Ksy2h7c bpnouI02SsGYUaR8QpWcYyqM/6ArSxZNbcCsaH43i/4t/Izgx909N/LCi7wMg9uzu3HTQCeJJZl 01r0Z+wr5oIwF9A9YU34GYkDDwXvU9ub1YIl8KFzgSKCjPyAAsW59Q1O0P6gZgnAXLNGqNGefFj NO1yMYX2PntGFenIxSOuIdbJmI6vKfI/ZnOrKN2oIa/scf0z3SCqbTr53AXYQu3jokyCg3dXlTw NNtlDfQTFieLLr5ndnXswv8Wqdwf2ngcDjjf7C2VAgszRYPT5vXY9C/LKCKnrGMFf1toosk3JVd T3VeUitq+9vMYFb2PdSDKCRL6qeKJ7dANUg2jEZzg6rTjuK+F5c/tOUK3r X-Received: by 2002:a5d:5588:0:b0:475:f100:35f5 with SMTP id ffacd0b85a97d-477b5c415a8mr9292156f8f.50.1783047733914; Thu, 02 Jul 2026 20:02:13 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.02.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:02:13 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 17/18] rust: drm: kms: add connector detect() and mode_valid() hooks Date: Fri, 3 Jul 2026 04:01:04 +0100 Message-ID: <20260703030123.2814-18-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The safe connector layer only exposed get_modes(), so a driver couldn't report hotplug state or prune modes it can't drive. Add two optional DriverConnector hooks, gated the same way as the plane/CRTC optionals (registered in the vtable only when the driver overrides them): - detect(&Connector, force) -> Status, wired to drm_connector_funcs.detect, returning a new Status enum (Connected/Disconnected/Unknown mapping to enum drm_connector_status). - mode_valid(&Connector, &DisplayMode) -> ModeStatus, wired to drm_connector_helper_funcs.mode_valid, returning a new ModeStatus enum (Ok/Bad/ClockHigh/ClockLow, a common subset of enum drm_mode_status). Both leave DRM's default behaviour (always connected, all modes valid) unchanged for connectors that don't implement them. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/kernel/drm/kms/connector.rs | 98 +++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connect= or.rs index 591153de7ab4..a66243db8868 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -5,7 +5,8 @@ //! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_conn= ector.h) =20 use super::{ - atomic::*, encoder::*, KmsDriver, ModeConfigGuard, ModeObject, ModeObj= ectVtable, Sealed + atomic::*, encoder::*, modes::DisplayMode, KmsDriver, ModeConfigGuard,= ModeObject, + ModeObjectVtable, Sealed, }; use crate::{ alloc::KBox, @@ -72,6 +73,43 @@ pub enum Type { USB as Usb } =20 +/// The connection status of a [`Connector`], as returned by [`DriverConne= ctor::detect`]. +/// +/// This is identical to [`enum drm_connector_status`]. +/// +/// [`enum drm_connector_status`]: srctree/include/drm/drm_connector.h +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum Status { + /// The connector is connected to a display and a mode list can be ret= rieved. + Connected =3D bindings::drm_connector_status_connector_status_connecte= d, + /// The connector has no display attached. + Disconnected =3D bindings::drm_connector_status_connector_status_disco= nnected, + /// The connection state could not be determined (treated as connected= for probing). + Unknown =3D bindings::drm_connector_status_connector_status_unknown, +} + +/// The result of validating a display mode against a [`Connector`], as re= turned by +/// [`DriverConnector::mode_valid`]. +/// +/// This mirrors a small, commonly-used subset of [`enum drm_mode_status`]= ; use [`ModeStatus::Bad`] +/// for a generic rejection, or the clock-specific variants when a mode is= out of the driver's +/// pixel-clock range. +/// +/// [`enum drm_mode_status`]: srctree/include/drm/drm_modes.h +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(i32)] +pub enum ModeStatus { + /// The mode is usable. + Ok =3D bindings::drm_mode_status_MODE_OK, + /// The mode is rejected for an unspecified reason. + Bad =3D bindings::drm_mode_status_MODE_BAD, + /// The mode's pixel clock is above what the driver can drive. + ClockHigh =3D bindings::drm_mode_status_MODE_CLOCK_HIGH, + /// The mode's pixel clock is below what the driver can drive. + ClockLow =3D bindings::drm_mode_status_MODE_CLOCK_LOW, +} + /// The main trait for implementing the [`struct drm_connector`] API for [= `Connector`]. /// /// Any KMS driver should have at least one implementation of this type, w= hich allows them to create @@ -105,14 +143,22 @@ pub trait DriverConnector: Send + Sync + Sized { atomic_destroy_state: Some(atomic_destroy_state_callback::), destroy: Some(connector_destroy_callback::), force: None, - detect: None, + detect: if Self::HAS_DETECT { + Some(detect_callback::) + } else { + None + }, fill_modes: Some(bindings::drm_helper_probe_single_connector_m= odes), 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, + mode_valid: if Self::HAS_MODE_VALID { + Some(mode_valid_callback::) + } else { + None + }, atomic_check: None, get_modes: Some(get_modes_callback::), detect_ctx: None, @@ -151,6 +197,28 @@ fn get_modes<'a>( connector: ConnectorGuard<'a, Self>, guard: &ModeConfigGuard<'a, Self::Driver>, ) -> i32; + + /// The optional [`drm_connector_funcs.detect`] hook for this connecto= r. + /// + /// Drivers may implement this to report whether a display is currentl= y attached. If not + /// implemented, the connector is always considered connected (DRM's d= efault with no `detect` + /// hook). `force` is set when userspace explicitly requested a forced= probe. + /// + /// [`drm_connector_funcs.detect`]: srctree/include/drm/drm_connector.h + fn detect(_connector: &Connector, _force: bool) -> Status { + build_error::build_error("This should not be reachable") + } + + /// The optional [`drm_connector_helper_funcs.mode_valid`] hook for th= is connector. + /// + /// Drivers may implement this to reject modes they cannot drive (for = example, a mode whose + /// pixel clock exceeds the hardware's budget). Returning anything oth= er than [`ModeStatus::Ok`] + /// prunes the mode from the probed list. If not implemented, every mo= de is accepted. + /// + /// [`drm_connector_helper_funcs.mode_valid`]: srctree/include/drm/drm= _modeset_helper_vtables.h + fn mode_valid(_connector: &Connector, _mode: &DisplayMode) -> Mo= deStatus { + build_error::build_error("This should not be reachable") + } } =20 /// The generated C vtable for a [`DriverConnector`]. @@ -464,6 +532,30 @@ impl RawConnector for T {} T::get_modes(connector.guard(&guard), &guard) } =20 +unsafe extern "C" fn detect_callback( + connector: *mut bindings::drm_connector, + force: bool, +) -> bindings::drm_connector_status { + // SAFETY: This is safe via `DriverConnector`s type invariants. + let connector =3D unsafe { Connector::::from_raw(connector) }; + + T::detect(connector, force) as bindings::drm_connector_status +} + +unsafe extern "C" fn mode_valid_callback( + connector: *mut bindings::drm_connector, + mode: *const bindings::drm_display_mode, +) -> bindings::drm_mode_status { + // SAFETY: This is safe via `DriverConnector`s type invariants. + let connector =3D unsafe { Connector::::from_raw(connector) }; + + // SAFETY: DRM guarantees `mode` points to a valid `drm_display_mode` = for the duration of this + // callback, and only passes us shared access to it. + let mode =3D unsafe { DisplayMode::as_ref(mode) }; + + T::mode_valid(connector, mode) as bindings::drm_mode_status +} + /// A [`struct drm_connector`] without a known [`DriverConnector`] impleme= ntation. /// /// This is mainly for situations where our bindings can't infer the [`Dri= verConnector`] --=20 2.55.0 From nobody Thu Nov 5 07:32:38 2026 Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E4DAC30D3FF for ; Fri, 3 Jul 2026 03:02:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047738; cv=none; b=rsMMCnIaIuDF6sztF8ekZRc+y2kupIR9lOO4V30K5r2WPU1iKXOG7Gwvlozt3T4BCSO6bWO9GlQXbLzH76CBWuanf/HOZpYh5bPPR4pxrYpT4L+DtiX5PChF8BCjJan2C+cR4ySyHRGKRCvoftbA/Gp5ZCs3haphm7bFM17A650= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047738; c=relaxed/simple; bh=GwvU+9h7Qtl04FVjBGVuSwmPigpE3mB+Cvb7wXRtfdM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uUhIVC8h5cdlxCD4g67Fx50JtxWXuHyTmn2qqTX601YRaZIye0fLAAZoMsztTRCqh4p/QR6TyEpMQwkLVyhkV7nDpCqlqosk9XJ+yUy4NfNCLYfEkp7kix1CaAsy5/vyoywd9/seTUNRzyLKvifZHKOvoW/NUxbQqxdcnUTy81o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=cozcn3t9; arc=none smtp.client-ip=209.85.128.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="cozcn3t9" Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-493c2b3dc8bso345355e9.2 for ; Thu, 02 Jul 2026 20:02:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047735; x=1783652535; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qmuYxCXQJW79g/7IjZ0oN+EYDyNRyHCs4/1RFHrUzwg=; b=cozcn3t9bYk+iR2nNPKsnG7WqefYv0xWBD2Sy/aG5++WHSS1MydUSfVoOAa2Y/0WaM jyE7HoUupwBvg/mrSKD2TlRvL2gxlxfdtHUxS1h4h9KgBySY7y2E+Zng5fAN6clmWyEM cxZbMfI+O8te5rcszV3OCgGEHG/E4y9Mknif4JNisAvuNVBfxMq6mKgIKdjpwSAaxOjF oMvJXNiAOKrno0vM324GPiw17Ad00+I9N2le9+mqzAgQ9yiyDiaW3PNU45oqOk8a4wRA boXjTNzrWE0Z8FqjFenvdZltG62bMPW68RoW44jPhXUUSzEWbgwEgeQJY63Lop+sB2Q4 k0fw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047735; x=1783652535; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=qmuYxCXQJW79g/7IjZ0oN+EYDyNRyHCs4/1RFHrUzwg=; b=Mm4wJA5TYsJVsOpQtDp/+pv8+vRZ69oNh1to39IYwg5ltDIc+/GcbcZI4+d7qOq2QY BT6W3uK4nxkWYLMyRPQ2pogLMWcD5umS+Y+Vtyz43BjoCdQNGnTvA1Q2zfuw6RHMmVj2 p3ik1UjTms4zCtIgDrwY5kVoZ4A0F0ihWkCnIOn5bTPu4FyTPj/QhfdyuNvWB9Yketia ua3N9G2X0aAjmhHQe1VwbK3Vn7GRx27BUq6QatzDTm+Y+0dauZKRclbtG+3X7G4g8LHr Y0U+WpzJ6yTWNLrwQUOi8Bon7f686Xd6bVxuWf/xoEeDwghp/uZ4+H6CHyJrHO4DDwMf 1MEg== X-Forwarded-Encrypted: i=1; AFNElJ/+Hi1fjHEyzLJ7UUhKcInI0wSgzYRNaKKJXLVMzGShf+NBQToMQu2OHmdgjWKh75RHFaFNgQg/2NTblEI=@vger.kernel.org X-Gm-Message-State: AOJu0YzU3BaZ0qDViubAaKpM3371PpD4NDn2sDNB9+4+1I3+EOFEs30V 1gQv9u5Ixo3mUT4NOLCwF8+ZE9Tjrnp4TzvlKjS6yzDJxTai5qPRr5y9rKKbTEA3mw== X-Gm-Gg: AfdE7cmh4Y8UrowXWMR7jIEY6Ak80T0InUmZish6fdJU78Wq2mEGg5diHwAm0ptVu4R 1EQoLD4ww5PIsTJi012euv8J4RFMr4FJBjzSwNN9oV8jTXIUVAqYX6qRdLpsaVzk90wHZcpwUeX 7tmc7OKywaAW2ZCZdkpJKrku1cL5vfzuHEXtnPDLI0A0yvlmk2naGASsl0q8P1z7wrBJbJoG6CE voJi/rB23XIjmeFNBgu/yUgVbZpcB4AbG0oB/RGjUp2UImJatYpB27Bg7uw8HCVmLMmYVjglNkF 1B2LNdTevSDKaqM3T4vfe2iJCMfwbBW47TnB4wEtKnVmiqdYoHBf2V6U2AkmN676kR2Myv9bFBs mbIEwoz625poS8v67sW9dtuhcgxPI8GCY1mZJjHPC5Z5PEb2tI/IEHTA+IZvOAYi+YManQR6cRD N2Z5PDjRa8SF0ANPkRP6+oH5ndfzMAAWzfgFxiZnYjbCNugSBdP7D6mk8O X-Received: by 2002:a05:600c:a404:b0:493:b783:ad59 with SMTP id 5b1f17b1804b1-493c2b374c7mr103666075e9.14.1783047735339; Thu, 02 Jul 2026 20:02:15 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.02.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:02:14 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 18/18] rust: drm: kms: add plane damage-clip accessors Date: Fri, 3 Jul 2026 04:01:05 +0100 Message-ID: <20260703030123.2814-19-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A driver whose "scanout" is an expensive encode-and-send (like a USB display) wants to repaint only the region a client marked dirty rather than the whole framebuffer every flip. Add two RawPlaneState accessors over the client's FB_DAMAGE_CLIPS (relative to the old plane state), both intersected with the visible source area: - damage_merged(), wrapping drm_atomic_helper_damage_merged(): merges the clips into one rectangle and returns it as a new Rect type (with width()/height() helpers). None means nothing to repaint; if the client supplied no clips, the full plane rectangle is returned, so a driver can always treat Some as "repaint this box". - for_each_damage_clip(), driving drm_atomic_helper_damage_iter: invokes a closure once per individual clip (the rectangles damage_merged() collap= ses into one), so a driver that forwards each changed region separately -- = e.g. over USB -- can repaint just those rectangles instead of their bounding= box. drm_atomic_helper_damage_{merged,iter_*} live in , so add that to bindings_helper.h. Signed-off-by: Mike Lothian Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/kms/plane.rs | 105 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 7d04abec1215..b8675642c1a8 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index ead875316829..4ae11cec88a4 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -605,6 +605,49 @@ pub trait FromRawPlaneState: AsRawPlaneState { unsafe fn from_raw_mut<'a>(ptr: *mut bindings::drm_plane_state) -> &'a= mut Self; } =20 +/// A rectangle in a plane's source (pixel) space, as produced by +/// [`RawPlaneState::damage_merged`]. +/// +/// The box is inclusive on the top-left and exclusive on the bottom-right= (`[x1, x2)` by +/// `[y1, y2)`), matching [`struct drm_rect`]. +/// +/// [`struct drm_rect`]: srctree/include/drm/drm_rect.h +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Rect { + /// Left edge, inclusive. + pub x1: i32, + /// Top edge, inclusive. + pub y1: i32, + /// Right edge, exclusive. + pub x2: i32, + /// Bottom edge, exclusive. + pub y2: i32, +} + +impl Rect { + #[inline] + fn from_raw(r: &bindings::drm_rect) -> Self { + Self { + x1: r.x1, + y1: r.y1, + x2: r.x2, + y2: r.y2, + } + } + + /// The width of the rectangle in pixels. + #[inline] + pub fn width(&self) -> i32 { + self.x2 - self.x1 + } + + /// The height of the rectangle in pixels. + #[inline] + pub fn height(&self) -> i32 { + self.y2 - self.y1 + } +} + /// 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 @@ -691,6 +734,68 @@ fn atomic_helper_check( }) } =20 + /// Merge all frame-damage clips on this (new) plane state -- relative= to `old` -- into a + /// single bounding rectangle, intersected with the plane's visible so= urce area. + /// + /// Returns [`None`] when the plane is not visible or there is nothing= to update. If the client + /// supplied no explicit damage clips, the full plane rectangle is ret= urned, so a driver can + /// always treat [`Some`] as "repaint this rectangle" and fall back to= a full-frame update. + /// Coordinates are integer pixels in the plane's source space. + /// + /// [`drm_atomic_helper_damage_merged`]: srctree/include/drm/drm_damag= e_helper.h + fn damage_merged(&self, old: &impl AsRawPlaneState) -> Option { + let mut rect =3D bindings::drm_rect { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + }; + + // SAFETY: + // - `old` and `self` are valid initialized `drm_plane_state`s via= their type invariants. + // - `drm_atomic_helper_damage_merged` only reads the two states (= to gather the damage + // clips and the source rectangle) and writes the merged result = into `rect`; it does not + // mutate the plane state, so deriving a `*mut` from our shared = reference is sound. + let visible =3D unsafe { + bindings::drm_atomic_helper_damage_merged( + core::ptr::from_ref(old.as_raw()), + core::ptr::from_ref(self.as_raw()).cast_mut(), + &mut rect, + ) + }; + + visible.then(|| Rect::from_raw(&rect)) + } + + /// Invoke `f` once per frame-damage clip on this (new) plane state re= lative to `old`, each + /// intersected with the plane's visible source area -- i.e. the indiv= idual rectangles that + /// [`Self::damage_merged`] collapses into one. If the client supplied= no explicit damage clips, + /// `f` is called once with the full plane rectangle. Coordinates are = integer pixels in the + /// plane's source space. + /// + /// This lets a driver forward each changed region separately (e.g. to= a remote display) instead + /// of the bounding box of them all. + /// + /// [`drm_atomic_helper_damage_iter`]: srctree/include/drm/drm_damage_= helper.h + fn for_each_damage_clip(&self, old: &impl AsRawPlaneState, mut f: impl= FnMut(Rect)) { + let mut iter =3D bindings::drm_atomic_helper_damage_iter::default(= ); + let mut clip =3D bindings::drm_rect::default(); + // SAFETY: + // - `old` and `self` are valid initialized `drm_plane_state`s via= their type invariants. + // - `drm_atomic_helper_damage_iter_init` only reads the two state= s to set up `iter`, and + // `_next` only reads `iter` and writes `clip`; neither escapes = a pointer. + unsafe { + bindings::drm_atomic_helper_damage_iter_init( + &mut iter, + core::ptr::from_ref(old.as_raw()), + core::ptr::from_ref(self.as_raw()), + ); + while bindings::drm_atomic_helper_damage_iter_next(&mut iter, = &mut clip) { + f(Rect::from_raw(&clip)); + } + } + } + /// Return the framebuffer currently set for this plane state #[inline] fn framebuffer(&self) -> Option<&Framebuffer> --=20 2.55.0