[PATCH v3 21/23] rust: drm: kms: read a connector's colorimetry and HDR metadata

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]>
A driver that carries a transfer function to its sink needs the connector
state's colorimetry and HDR_OUTPUT_METADATA, and needs to reach the
connector state routed to a CRTC from the CRTC's own atomic callback.

Add ConnectorState::hdr_output_eotf() and
AtomicState::new_connector_state_for_crtc().

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <[email protected]>
---
 rust/kernel/drm/kms/atomic.rs    | 104 +++++++++++++++++++++++++++++++
 rust/kernel/drm/kms/connector.rs |  77 +++++++++++++++++++++++
 rust/kernel/drm/kms/crtc.rs      |  13 ++++
 3 files changed, 194 insertions(+)

diff --git a/rust/kernel/drm/kms/atomic.rs b/rust/kernel/drm/kms/atomic.rs
index 18dc136940f3..f9f91edc89c3 100644
--- a/rust/kernel/drm/kms/atomic.rs
+++ b/rust/kernel/drm/kms/atomic.rs
@@ -96,6 +96,88 @@ pub fn get_old_connector_state<C>(&self, connector: &C) -> Option<&C::State>
                 .map(|p| C::State::from_raw(p))
         }
     }
+
+    /// Return the new state of the first connector routed to `crtc` in this [`AtomicState`], if
+    /// any.
+    ///
+    /// This is the Rust spelling of walking `for_each_new_connector_in_state()` looking for
+    /// `conn_state->crtc == crtc`, which is how a CRTC callback reaches the connector properties
+    /// that describe the signal it is about to drive -- colorimetry, HDR metadata, `max bpc`.
+    /// Those live on the connector state, but the driver decisions they feed are frequently made
+    /// where only the CRTC is in hand.
+    ///
+    /// The state is returned opaquely because the caller is looking across mode objects and has
+    /// no way to name the connector's driver-private state type. A CRTC that clones to several
+    /// connectors gets the first; a driver that cares about the difference should walk the
+    /// connectors itself.
+    pub fn new_connector_state_for_crtc<C>(&self, crtc: &C) -> Option<&OpaqueConnectorState<T>>
+    where
+        C: ModesettableCrtc + ModeObject<Driver = T>,
+    {
+        let crtc_raw = crtc.as_raw();
+        // SAFETY: `state` is initialized via our type invariants, and `connectors` /
+        // `num_connector` are invariant for as long as we hold a reference to it.
+        let (connectors, num) = unsafe {
+            let raw = self.as_raw();
+            ((*raw).connectors, (*raw).num_connector)
+        };
+        if connectors.is_null() || num <= 0 {
+            return None;
+        }
+        for i in 0..num as usize {
+            // SAFETY: `connectors` points to `num_connector` initialized entries.
+            let new_state = unsafe { (*connectors.add(i)).new_state };
+            if new_state.is_null() {
+                continue;
+            }
+            // SAFETY: a non-null `new_state` is a valid `drm_connector_state` for the lifetime of
+            // the atomic state.
+            if unsafe { (*new_state).crtc } != crtc_raw {
+                continue;
+            }
+            // SAFETY: as above, and the returned reference borrows from `self`, so it cannot
+            // outlive the atomic state that owns the connector state.
+            return Some(unsafe { OpaqueConnectorState::<T>::from_raw(new_state) });
+        }
+        None
+    }
+
+    /// Return the old state of the first connector routed to `crtc` in this [`AtomicState`], if
+    /// any.
+    ///
+    /// The counterpart to [`Self::new_connector_state_for_crtc`], for a driver comparing the two
+    /// to decide whether a connector property it consumes has changed.
+    pub fn old_connector_state_for_crtc<C>(&self, crtc: &C) -> Option<&OpaqueConnectorState<T>>
+    where
+        C: ModesettableCrtc + ModeObject<Driver = T>,
+    {
+        let crtc_raw = crtc.as_raw();
+        // SAFETY: `state` is initialized via our type invariants, and `connectors` /
+        // `num_connector` are invariant for as long as we hold a reference to it.
+        let (connectors, num) = unsafe {
+            let raw = self.as_raw();
+            ((*raw).connectors, (*raw).num_connector)
+        };
+        if connectors.is_null() || num <= 0 {
+            return None;
+        }
+        for i in 0..num as usize {
+            // SAFETY: `connectors` points to `num_connector` initialized entries.
+            let old_state = unsafe { (*connectors.add(i)).old_state };
+            if old_state.is_null() {
+                continue;
+            }
+            // SAFETY: a non-null `old_state` is a valid `drm_connector_state` for the lifetime of
+            // the atomic state.
+            if unsafe { (*old_state).crtc } != crtc_raw {
+                continue;
+            }
+            // SAFETY: as above, and the returned reference borrows from `self`, so it cannot
+            // outlive the atomic state that owns the connector state.
+            return Some(unsafe { OpaqueConnectorState::<T>::from_raw(old_state) });
+        }
+        None
+    }
 }
 
 // SAFETY: DRM atomic state objects are always reference counted and the get/put functions satisfy
@@ -188,6 +270,28 @@ pub fn get_old_connector_state<C>(&self, connector: &C) -> Option<&C::State>
         self.state.get_old_connector_state(connector)
     }
 
+    /// Return the new state of the first connector routed to `crtc`, if any.
+    ///
+    /// See [`AtomicState::new_connector_state_for_crtc`]. This borrows the connector state rather
+    /// than taking a mutator out for it, so it does not participate in the mutator bookkeeping and
+    /// cannot conflict with [`Self::get_new_connector_state`].
+    pub fn new_connector_state_for_crtc<C>(&self, crtc: &C) -> Option<&OpaqueConnectorState<T>>
+    where
+        C: ModesettableCrtc + ModeObject<Driver = T>,
+    {
+        self.state.new_connector_state_for_crtc(crtc)
+    }
+
+    /// Return the old state of the first connector routed to `crtc`, if any.
+    ///
+    /// See [`AtomicState::old_connector_state_for_crtc`].
+    pub fn old_connector_state_for_crtc<C>(&self, crtc: &C) -> Option<&OpaqueConnectorState<T>>
+    where
+        C: ModesettableCrtc + ModeObject<Driver = T>,
+    {
+        self.state.old_connector_state_for_crtc(crtc)
+    }
+
     /// Retrieve the last committed atomic state for `plane` if `plane` has already been added to
     /// the atomic state being composed.
     ///
diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs
index 952c8e02777b..231857cc1f20 100644
--- a/rust/kernel/drm/kms/connector.rs
+++ b/rust/kernel/drm/kms/connector.rs
@@ -922,7 +922,84 @@ fn connector(&self) -> &Self::Connector {
         // `self.state.connector` points to a valid instance of a `Connector<T>`
         unsafe { Self::Connector::from_raw((*self.as_raw()).connector) }
     }
+
+    /// The colorimetry userspace has requested through the `Colorspace` property, as a
+    /// [`enum drm_colorspace`] value.
+    ///
+    /// Meaningful only on a connector that
+    /// [`UnregisteredConnector::attach_colorspace_property`] was called for; everything else
+    /// leaves it at `DRM_MODE_COLORIMETRY_DEFAULT`.
+    ///
+    /// [`enum drm_colorspace`]: srctree/include/drm/drm_connector.h
+    fn colorspace(&self) -> u32 {
+        self.as_raw().colorspace
+    }
+
+    /// The electro-optical transfer function from the `HDR_OUTPUT_METADATA` blob, or [`None`] if
+    /// userspace has not set one.
+    ///
+    /// This is deliberately just the curve: the rest of the infoframe is mastering-display
+    /// metadata for the sink, and a driver that only needs to know *which curve the pixels are
+    /// encoded in* should not have to reason about the union's other members or their versioning.
+    ///
+    /// [`struct hdr_output_metadata`]: srctree/include/uapi/drm/drm_mode.h
+    fn hdr_output_eotf(&self) -> Option<Eotf> {
+        let blob = self.as_raw().hdr_output_metadata;
+        if blob.is_null() {
+            return None;
+        }
+        // SAFETY: a non-null `hdr_output_metadata` blob is valid for the state's lifetime.
+        let (data, length) = unsafe { ((*blob).data, (*blob).length) };
+        // DRM validates the blob length when the property is set, but this is the boundary where
+        // a short blob would become an out-of-bounds read.
+        if data.is_null() || length < core::mem::size_of::<bindings::hdr_output_metadata>() {
+            return None;
+        }
+        // SAFETY: the blob is at least a whole `hdr_output_metadata` and lives as long as the
+        // state. `eotf` is the first byte of the only union member DRM defines.
+        let eotf = unsafe {
+            (*data.cast::<bindings::hdr_output_metadata>())
+                .__bindgen_anon_1
+                .hdmi_metadata_type1
+                .eotf
+        };
+        Some(Eotf::from_raw(eotf))
+    }
+}
+/// An electro-optical transfer function named by a `HDR_OUTPUT_METADATA` blob.
+///
+/// Mirrors the `HDMI_EOTF_*` values in [`enum hdmi_eotf`]. A driver matches on this rather than
+/// comparing against the raw constants, so the one place that has to agree with the C enum is
+/// [`Eotf::from_raw`].
+///
+/// [`enum hdmi_eotf`]: srctree/include/linux/hdmi.h
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Eotf {
+    /// Ordinary SDR gamma.
+    TraditionalGammaSdr,
+    /// The traditional HDR gamma curve.
+    TraditionalGammaHdr,
+    /// SMPTE ST 2084, i.e. PQ. What a compositor sets to drive an output in HDR10.
+    SmpteSt2084,
+    /// BT.2100 hybrid log-gamma.
+    Bt2100Hlg,
+    /// A value this kernel does not name, carried through rather than discarded.
+    Other(u8),
 }
+
+impl Eotf {
+    /// Classify the raw `eotf` byte from an infoframe.
+    fn from_raw(eotf: u8) -> Self {
+        match u32::from(eotf) {
+            bindings::hdmi_eotf_HDMI_EOTF_TRADITIONAL_GAMMA_SDR => Self::TraditionalGammaSdr,
+            bindings::hdmi_eotf_HDMI_EOTF_TRADITIONAL_GAMMA_HDR => Self::TraditionalGammaHdr,
+            bindings::hdmi_eotf_HDMI_EOTF_SMPTE_ST2084 => Self::SmpteSt2084,
+            bindings::hdmi_eotf_HDMI_EOTF_BT_2100_HLG => Self::Bt2100Hlg,
+            _ => Self::Other(eotf),
+        }
+    }
+}
+
 impl<T: AsRawConnectorState> RawConnectorState for T {}
 
 /// The main interface for a [`struct drm_connector_state`].
diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs
index 9e888c4e2f68..ec01ae0430f7 100644
--- a/rust/kernel/drm/kms/crtc.rs
+++ b/rust/kernel/drm/kms/crtc.rs
@@ -1047,6 +1047,19 @@ pub(super) fn new<D: KmsDriver>(
     }
 }
 
+impl<'a, T: FromRawCrtcState> CrtcStateMutator<'a, T> {
+    /// Require a full mode set for this CRTC.
+    ///
+    /// Called from [`DriverCrtc::atomic_check`] when something the core does not track has changed
+    /// in a way the hardware can only adopt by being reprogrammed, such as a connector property
+    /// that forms part of the signal description the driver sends to its device.
+    pub fn set_mode_changed(&mut self, changed: bool) {
+        // SAFETY: `as_raw()` is a valid `drm_crtc_state`, and holding this mutator is proof that
+        // no other reference to it exists.
+        unsafe { (*self.as_raw()).set_mode_changed(changed) };
+    }
+}
+
 impl<'a, T: DriverCrtcState> CrtcStateMutator<'a, CrtcState<T>> {
     super::impl_from_opaque_mode_obj! {
         fn <D, C>(CrtcStateMutator<'a, OpaqueCrtcState<D>>) -> Self
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.