From: Mike Lothian <mike@fireburn.co.uk>
To: dri-devel@lists.freedesktop.org
Cc: "Mike Lothian" <mike@fireburn.co.uk>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Danilo Krummrich" <dakr@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Trevor Gross" <tmgross@umich.edu>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Lyude Paul" <lyude@redhat.com>,
rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v3 21/23] rust: drm: kms: read a connector's colorimetry and HDR metadata
Date: Wed, 26 Aug 2026 17:31:52 +0100 [thread overview]
Message-ID: <20260826163359.4998-22-mike@fireburn.co.uk> (raw)
In-Reply-To: <20260826163359.4998-1-mike@fireburn.co.uk>
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 <mike@fireburn.co.uk>
---
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
next prev parent reply other threads:[~2026-08-26 16:36 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-26 16:31 [PATCH v3 0/23] rust: drm: KMS abstractions for a Rust display driver Mike Lothian
2026-08-26 16:31 ` [PATCH v3 1/23] rust: drm: kms: adapt Lyude's KMS series to current DRM APIs Mike Lothian
2026-08-26 16:31 ` [PATCH v3 2/23] rust: drm: kms: tie mode-object references to their owners Mike Lothian
2026-08-26 16:31 ` [PATCH v3 3/23] rust: drm: kms: constrain connector encoder attachment Mike Lothian
2026-08-26 16:31 ` [PATCH v3 4/23] rust: drm: reject cross-device GEM handle creation Mike Lothian
2026-08-26 16:31 ` [PATCH v3 5/23] rust: drm: kms: add common state and connector helpers Mike Lothian
2026-08-26 16:31 ` [PATCH v3 6/23] rust: drm: expose HDCP 2.2 message identifiers Mike Lothian
2026-08-26 16:31 ` [PATCH v3 7/23] rust: drm: kms: add typed color and rotation properties Mike Lothian
2026-08-26 16:31 ` [PATCH v3 8/23] rust: drm: kms: add connector detect() and mode_valid() hooks Mike Lothian
2026-08-26 16:31 ` [PATCH v3 9/23] rust: drm: kms: add plane damage-clip accessors Mike Lothian
2026-08-26 16:31 ` [PATCH v3 10/23] rust: drm: framebuffer: add validated shmem scanout views Mike Lothian
2026-08-26 16:31 ` [PATCH v3 11/23] rust: drm: kms: expose checked plane geometry Mike Lothian
2026-08-26 16:31 ` [PATCH v3 12/23] rust: drm: kms: add owned CRTC and vblank references Mike Lothian
2026-08-26 16:31 ` [PATCH v3 13/23] rust: drm: kms: plane: add FB_DAMAGE_CLIPS property support Mike Lothian
2026-08-26 16:31 ` [PATCH v3 14/23] rust: drm: add a safe constructor for owned registration data Mike Lothian
2026-08-26 16:31 ` [PATCH v3 15/23] rust: drm: pin the owner while DRM files remain open Mike Lothian
2026-08-26 16:31 ` [PATCH v3 16/23] rust: drm: kms: add the plane blend-mode property Mike Lothian
2026-08-26 16:31 ` [PATCH v3 17/23] rust: drm: add an owned display mode constructor Mike Lothian
2026-08-26 16:31 ` [PATCH v3 18/23] rust: drm: expose mode flags and CTA VIC matching Mike Lothian
2026-08-26 16:31 ` [PATCH v3 19/23] rust: drm: expose CRTC mode changes Mike Lothian
2026-08-26 16:31 ` [PATCH v3 20/23] rust: drm: kms: add synthesized CVT connector modes Mike Lothian
2026-08-26 16:31 ` Mike Lothian [this message]
2026-08-26 16:31 ` [PATCH v3 22/23] rust: drm: kms: walk the CRTCs an atomic commit carries Mike Lothian
2026-08-26 16:31 ` [PATCH v3 23/23] rust: drm: kms: expose a connector's requested link depth Mike Lothian
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260826163359.4998-22-mike@fireburn.co.uk \
--to=mike@fireburn.co.uk \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=lyude@redhat.com \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=simona@ffwll.ch \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox