[PATCH v3 20/23] rust: drm: kms: add synthesized CVT connector modes

From: Mike Lothian

Date: Wed Aug 26 2026 - 12:45:58 EST


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 <mike@xxxxxxxxxxxxxx>
---
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.