[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