Expose a lock-scoped helper for adding a driver-synthesized CVT timing
to a connector probe result. This lets virtual and transport-backed
displays offer valid continuous-frequency modes without reaching into
DRM mode lists.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <[email protected]>
---
 rust/kernel/drm/kms/connector.rs | 84 ++++++++++++++++++++++++++++++++
 1 file changed, 84 insertions(+)

diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs
index dd126469788f..952c8e02777b 100644
--- a/rust/kernel/drm/kms/connector.rs
+++ b/rust/kernel/drm/kms/connector.rs
@@ -495,6 +495,60 @@ pub fn attach_encoder<E>(&self, encoder: 
&UnregisteredEncoder<E>) -> Result
             bindings::drm_connector_attach_encoder(self.as_raw(), 
encoder.as_raw())
         })
     }
+
+    /// Attach the HDR output metadata property to this [`Connector`].
+    ///
+    /// This property carries a blob supplied by userspace. Drivers must still 
validate and apply
+    /// the metadata in their atomic commit path before claiming that HDR 
output is supported.
+    pub fn attach_hdr_output_metadata_property(&self) {
+        // SAFETY: `self` is an initialized connector owned by this DRM 
device. The helper only
+        // attaches the mode-config-owned standard property to its mode object.
+        unsafe {
+            
bindings::drm_connector_attach_hdr_output_metadata_property(self.as_raw());
+        }
+    }
+
+    /// Create and attach the standard DP colorspace property to this 
[`Connector`].
+    ///
+    /// A zero mask asks DRM to expose every colorspace defined for 
DisplayPort. A driver must
+    /// still reject values its sink or transport cannot actually carry in its 
atomic check.
+    pub fn attach_colorspace_property(&self) -> Result {
+        to_result(unsafe { 
bindings::drm_mode_create_dp_colorspace_property(self.as_raw(), 0) })?;
+        // SAFETY: the successful create call above initialized 
`colorspace_property` for this
+        // connector; the C helper only attaches that property to this 
connector's mode object.
+        to_result(unsafe { 
bindings::drm_connector_attach_colorspace_property(self.as_raw()) })
+    }
+
+    /// Attach the standard `max bpc` range property to this [`Connector`].
+    ///
+    /// `min_bpc` and `max_bpc` are validated before conversion so callers 
cannot wrap an invalid
+    /// range through the C `int` API. DRM requires the connector to have an 
atomic state before
+    /// this helper is called; newly-created Rust connectors acquire that 
state here.
+    pub fn attach_max_bpc_property(&self, min_bpc: u32, max_bpc: u32) -> 
Result {
+        if min_bpc == 0 || min_bpc > max_bpc || max_bpc > i32::MAX as u32 {
+            return Err(EINVAL);
+        }
+
+        // `drm_connector_attach_max_bpc_property()` writes the initial bpc 
values into the
+        // connector state. `KmsDriver::create_objects()` runs before the 
mode-config-wide reset,
+        // so initialize our state through the driver's Rust reset callback 
when necessary.
+        let state = unsafe { (*self.as_raw()).state };
+        if state.is_null() {
+            // SAFETY: `self` is a newly initialized `Connector<T>` and this 
unregistered typestate
+            // prevents concurrent access. The callback creates the matching 
`ConnectorState<T>`.
+            unsafe { connector_reset_callback::<T::State>(self.as_raw()) };
+        }
+
+        // SAFETY: `self` is initialized and now owns a connector state. The 
validated bounds fit
+        // the C API's signed integer parameters, and the helper only installs 
a DRM core property.
+        to_result(unsafe {
+            bindings::drm_connector_attach_max_bpc_property(
+                self.as_raw(),
+                min_bpc as i32,
+                max_bpc as i32,
+            )
+        })
+    }
 }
 
 /// Common methods available on any type which implements [`AsRawConnector`].
@@ -723,6 +777,36 @@ pub fn set_preferred_mode(&self, (h_pref, w_pref): (u32, 
u32)) {
         unsafe { bindings::drm_set_preferred_mode(self.as_raw(), h_pref, 
w_pref) }
     }
 
+    /// Add a driver-synthesised CVT mode to this connector's probed mode list.
+    ///
+    /// For a display whose EDID declares continuous frequencies, a driver may 
legitimately offer a
+    /// timing the EDID does not itself enumerate. `reduced` selects CVT 
reduced blanking (CVT-RB),
+    /// which matters when the *pixel clock* rather than the pixel rate is the 
constrained
+    /// resource -- RB cuts the clock for the same active pixels.
+    ///
+    /// Returns `EINVAL` if the core could not build the timing.
+    pub fn add_cvt_mode(
+        &self,
+        hdisplay: i32,
+        vdisplay: i32,
+        vrefresh: i32,
+        reduced: bool,
+    ) -> Result {
+        let dev = self.drm_dev().as_raw();
+        // SAFETY: `dev` is this connector's live `drm_device`; `drm_cvt_mode` 
only computes a
+        // timing and allocates it, and we hold the mode-config lock via our 
type invariants.
+        let mode = unsafe {
+            bindings::drm_cvt_mode(dev, hdisplay, vdisplay, vrefresh, reduced, 
false, false)
+        };
+        if mode.is_null() {
+            return Err(EINVAL);
+        }
+        // SAFETY: `mode` was just allocated by `drm_cvt_mode` and ownership 
passes to the
+        // connector here; we hold the locks required to modify its mode list.
+        unsafe { bindings::drm_mode_probed_add(self.as_raw(), mode) };
+        Ok(())
+    }
+
     /// Parse an EDID, update the connector information, and add its 
advertised modes.
     ///
     /// Returns the number of modes added.

Reply via email to