[PATCH v3 5/23] rust: drm: kms: add common state and connector helpers

Mike Lothian <[email protected]>
Newsgroups org.kernel.vger.rust-for-linux,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Add safe KMS helpers for hotplug events, mode timings, CRTC modes,
plane destination sizes, and EDID mode enumeration.

Propagate drm_edid_connector_update() failures and release the
temporary EDID on every path.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <[email protected]>
---
 rust/kernel/drm/kms.rs           | 18 +++++---
 rust/kernel/drm/kms/connector.rs | 35 ++++++++++++++-
 rust/kernel/drm/kms/crtc.rs      | 12 ++++-
 rust/kernel/drm/kms/encoder.rs   |  9 +---
 rust/kernel/drm/kms/modes.rs     | 77 +++++++++++++++++++++++++++++++-
 rust/kernel/drm/kms/plane.rs     | 14 +++++-
 rust/kernel/drm/kms/vblank.rs    |  2 +-
 7 files changed, 148 insertions(+), 19 deletions(-)

diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs
index 8baf9c2906f5..c10b2488af9b 100644
--- a/rust/kernel/drm/kms.rs
+++ b/rust/kernel/drm/kms.rs
@@ -77,7 +77,7 @@ unsafe fn setup_kms(_drm: &Device<Self::Driver>) -> Result<ModeConfigInfo> {
 /// generate said functions for any kind of type which the original mode object driver trait can be
 /// derived from. All conversions check the mode object's vtable. For example:
 ///
-/// ```compile_fail
+/// ```ignore
 /// impl<'a, T: DriverConnectorState> ConnectorState<T> {
 ///     impl_from_opaque_mode_obj! {
 ///         // | An optional lifetime and param-variables to declare for each function
@@ -245,10 +245,7 @@ pub trait KmsDriver: Driver {
     type Encoder: encoder::DriverEncoder<Driver = Self>;
 
     /// Return a [`ModeConfigInfo`] structure for this [`device::Device`].
-    fn mode_config_info(
-        dev: &device::Device,
-        drm_data: &Self::Data,
-    ) -> Result<ModeConfigInfo>;
+    fn mode_config_info(dev: &device::Device, drm_data: &Self::Data) -> Result<ModeConfigInfo>;
 
     /// Create mode objects like [`crtc::Crtc`], [`plane::Plane`], etc. for this device
     fn create_objects(drm: &UnregisteredKmsDevice<'_, Self>) -> Result
@@ -360,6 +357,17 @@ impl<T: Driver> private::KmsImpl for PhantomData<T> {
 
 impl<T: Driver> KmsImpl for PhantomData<T> {}
 
+impl<T: KmsDriver, C: crate::drm::device::DeviceContext> Device<T, C> {
+    /// Send a hotplug uevent to userspace, prompting it to re-probe connector state.
+    ///
+    /// This is useful for drivers which detect connector changes out of band, for example when a
+    /// dock supplies an EDID after bring-up.
+    pub fn hotplug_event(&self) {
+        // SAFETY: `self.as_raw()` is a live KMS-capable DRM device.
+        unsafe { bindings::drm_kms_helper_hotplug_event(self.as_raw()) };
+    }
+}
+
 /// Various device-wide information for a [`Device`] that is provided during initialization.
 #[derive(Copy, Clone)]
 pub struct ModeConfigInfo {
diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs
index b36d138ae950..793a7bb5bfef 100644
--- a/rust/kernel/drm/kms/connector.rs
+++ b/rust/kernel/drm/kms/connector.rs
@@ -5,7 +5,7 @@
 //! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_connector.h)
 
 use super::{
-    atomic::*, encoder::*, KmsDriver, ModeConfigGuard, ModeObject, ModeObjectVtable, Sealed
+    atomic::*, encoder::*, KmsDriver, ModeConfigGuard, ModeObject, ModeObjectVtable, Sealed,
 };
 use crate::{
     alloc::KBox,
@@ -587,6 +587,39 @@ pub fn set_preferred_mode(&self, (h_pref, w_pref): (u32, u32)) {
         // SAFETY: We hold the locks required to call this via our type invariants.
         unsafe { bindings::drm_set_preferred_mode(self.as_raw(), h_pref, w_pref) }
     }
+
+    /// Parse an EDID, update the connector information, and add its advertised modes.
+    ///
+    /// Returns the number of modes added.
+    pub fn add_edid_modes(&self, edid: &[u8]) -> Result<i32> {
+        const EDID_BASE_BLOCK_LEN: usize = 128;
+
+        if edid.len() < EDID_BASE_BLOCK_LEN {
+            return Err(EINVAL);
+        }
+
+        // SAFETY: `edid` points to `edid.len()` initialized bytes, which the helper copies.
+        let drm_edid = unsafe { bindings::drm_edid_alloc(edid.as_ptr().cast(), edid.len()) };
+        if drm_edid.is_null() {
+            return Err(ENOMEM);
+        }
+
+        // SAFETY: The connector is live and the guard holds the mode-config lock. `drm_edid`
+        // points to an allocation returned by `drm_edid_alloc` above.
+        let ret = unsafe { bindings::drm_edid_connector_update(self.as_raw(), drm_edid) };
+        if let Err(err) = to_result(ret) {
+            // SAFETY: `drm_edid` was allocated above and has not been freed.
+            unsafe { bindings::drm_edid_free(drm_edid) };
+            return Err(err);
+        }
+
+        // SAFETY: The connector information was successfully updated from this EDID above.
+        let count = unsafe { bindings::drm_edid_connector_add_modes(self.as_raw()) };
+        // SAFETY: `drm_edid` was allocated above and is no longer needed.
+        unsafe { bindings::drm_edid_free(drm_edid) };
+
+        Ok(count)
+    }
 }
 
 /// 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 683d9ee4ec25..a3217f8c55e8 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)
 
 use super::{
-    atomic::*, plane::*, vblank::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject,
-    UnregisteredKmsDevice, Sealed,
+    atomic::*, modes::DisplayMode, plane::*, vblank::*, KmsDriver, ModeObject, ModeObjectVtable,
+    Sealed, StaticModeObject, UnregisteredKmsDevice,
 };
 use crate::{
     alloc::KBox,
@@ -644,6 +644,7 @@ pub trait AsRawCrtcState: private::AsRawCrtcState {
 pub(crate) mod private {
     use super::*;
 
+    /// The raw-pointer half of [`AsRawCrtcState`](super::AsRawCrtcState).
     #[allow(unreachable_pub)]
     pub trait AsRawCrtcState {
         /// Return a raw pointer to the DRM CRTC state
@@ -678,6 +679,13 @@ fn active(&self) -> bool {
         // this access is serialized
         unsafe { (*self.as_raw()).active }
     }
+
+    /// Return the display mode programmed into this CRTC state.
+    fn mode(&self) -> &DisplayMode {
+        // SAFETY: `mode` is embedded in the CRTC state and therefore has the same lifetime. The
+        // atomic-state API serializes access while the mode can be changed.
+        unsafe { DisplayMode::as_ref(core::ptr::addr_of!((*self.as_raw()).mode)) }
+    }
 }
 impl<T: AsRawCrtcState> RawCrtcState for T {}
 
diff --git a/rust/kernel/drm/kms/encoder.rs b/rust/kernel/drm/kms/encoder.rs
index f90d139cdb04..8758a9459bcc 100644
--- a/rust/kernel/drm/kms/encoder.rs
+++ b/rust/kernel/drm/kms/encoder.rs
@@ -5,7 +5,7 @@
 //! C header: [`include/drm/drm_encoder.h`](srctree/include/drm/drm_encoder.h)
 
 use super::{
-    KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, UnregisteredKmsDevice, Sealed
+    KmsDriver, ModeObject, ModeObjectVtable, Sealed, StaticModeObject, UnregisteredKmsDevice,
 };
 use crate::{
     alloc::KBox,
@@ -15,12 +15,7 @@
     types::{NotThreadSafe, Opaque},
 };
 use bindings;
-use core::{
-    marker::*,
-    mem,
-    ops::Deref,
-    ptr::null,
-};
+use core::{marker::*, mem, ops::Deref, ptr::null};
 use macros::paste;
 
 /// A macro for generating our type ID enumerator.
diff --git a/rust/kernel/drm/kms/modes.rs b/rust/kernel/drm/kms/modes.rs
index 0f29a9c00062..cc3c486eecf1 100644
--- a/rust/kernel/drm/kms/modes.rs
+++ b/rust/kernel/drm/kms/modes.rs
@@ -1,7 +1,12 @@
 // SPDX-License-Identifier: GPL-2.0
+//!
+//! DRM display modes.
+//!
+//! C header: [`include/drm/drm_modes.h`](srctree/include/drm/drm_modes.h)
+
 use bindings;
 
-use crate::{prelude::*, types::Opaque};
+use crate::types::Opaque;
 
 /// DRM kernel-internal display mode structure.
 ///
@@ -73,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 }
     }
+
+    /// Return the horizontal active pixels.
+    #[inline]
+    pub fn hdisplay(&self) -> u16 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).hdisplay }
+    }
+
+    /// Return the start of the horizontal sync pulse.
+    #[inline]
+    pub fn hsync_start(&self) -> u16 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).hsync_start }
+    }
+
+    /// Return the end of the horizontal sync pulse.
+    #[inline]
+    pub fn hsync_end(&self) -> u16 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).hsync_end }
+    }
+
+    /// Return the total horizontal pixels including blanking.
+    #[inline]
+    pub fn htotal(&self) -> u16 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).htotal }
+    }
+
+    /// Return the vertical active scanlines.
+    #[inline]
+    pub fn vdisplay(&self) -> u16 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).vdisplay }
+    }
+
+    /// Return the start of the vertical sync pulse.
+    #[inline]
+    pub fn vsync_start(&self) -> u16 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).vsync_start }
+    }
+
+    /// Return the end of the vertical sync pulse.
+    #[inline]
+    pub fn vsync_end(&self) -> u16 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).vsync_end }
+    }
+
+    /// Return the total vertical scanlines including blanking.
+    #[inline]
+    pub fn vtotal(&self) -> u16 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).vtotal }
+    }
+
+    /// Return the pixel clock in kHz.
+    #[inline]
+    pub fn clock(&self) -> i32 {
+        // SAFETY: Reading this field is safe via the type invariants.
+        unsafe { (*self.as_raw()).clock }
+    }
+
+    /// Return the refresh rate in Hz as computed by DRM.
+    #[inline]
+    pub fn vrefresh(&self) -> i32 {
+        // SAFETY: `drm_mode_vrefresh` only reads this valid display mode.
+        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 f52f9c872de3..3a95c45b6728 100644
--- a/rust/kernel/drm/kms/plane.rs
+++ b/rust/kernel/drm/kms/plane.rs
@@ -5,8 +5,8 @@
 //! C header: [`include/drm/drm_plane.h`](srctree/include/drm/drm_plane.h)
 
 use super::{
-    atomic::*, crtc::*, framebuffer::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject,
-    UnregisteredKmsDevice, Sealed
+    atomic::*, crtc::*, framebuffer::*, KmsDriver, ModeObject, ModeObjectVtable, Sealed,
+    StaticModeObject, UnregisteredKmsDevice,
 };
 use crate::{
     alloc::KBox,
@@ -617,6 +617,16 @@ fn plane(&self) -> &Self::Plane {
         unsafe { Self::Plane::from_raw(self.as_raw().plane) }
     }
 
+    /// Return the width of this plane's destination rectangle in CRTC pixels.
+    fn crtc_w(&self) -> u32 {
+        self.as_raw().crtc_w
+    }
+
+    /// Return the height of this plane's destination rectangle in CRTC pixels.
+    fn crtc_h(&self) -> u32 {
+        self.as_raw().crtc_h
+    }
+
     /// Return the current [`OpaqueCrtc`] assigned to this plane, if there is one.
     ///
     /// The returned CRTC reference cannot outlive the plane-state borrow:
diff --git a/rust/kernel/drm/kms/vblank.rs b/rust/kernel/drm/kms/vblank.rs
index dc34e02e8ccb..a725a46110d8 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)
 
-use super::{crtc::*, ModeObject, modes::*, Sealed};
+use super::{crtc::*, modes::*, ModeObject};
 use bindings;
 use core::{
     marker::*,
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.