rust-for-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
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 19/23] rust: drm: expose CRTC mode changes
Date: Wed, 26 Aug 2026 17:31:50 +0100	[thread overview]
Message-ID: <20260826163359.4998-20-mike@fireburn.co.uk> (raw)
In-Reply-To: <20260826163359.4998-1-mike@fireburn.co.uk>

Allow Rust DRM drivers to distinguish a modeset or enable transition
from an ordinary atomic page flip. This avoids repeating mode-specific
validation on the page-flip hot path.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <mike@fireburn.co.uk>
---
 rust/kernel/drm/kms/crtc.rs | 139 +++++++++++++++++++++++++++++++++++-
 1 file changed, 138 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs
index 892f04f04e22..9e888c4e2f68 100644
--- a/rust/kernel/drm/kms/crtc.rs
+++ b/rust/kernel/drm/kms/crtc.rs
@@ -31,6 +31,17 @@
 pub struct ColorLut(bindings::drm_color_lut);
 
 impl ColorLut {
+    /// Build an entry. Mainly useful for tests and for drivers synthesising a ramp.
+    #[inline]
+    pub const fn new(red: u16, green: u16, blue: u16) -> Self {
+        Self(bindings::drm_color_lut {
+            red,
+            green,
+            blue,
+            reserved: 0,
+        })
+    }
+
     /// Red channel value.
     pub fn red(&self) -> u16 {
         self.0.red
@@ -47,6 +58,65 @@ pub fn blue(&self) -> u16 {
     }
 }
 
+/// A colour transformation matrix, as programmed through the CRTC's `CTM` property.
+///
+/// The matrix is applied to the pixel values that the degamma LUT produced, before the gamma LUT:
+///
+/// ```text
+/// out   matrix    in
+/// |R|   |0 1 2|   |R|
+/// |G| = |3 4 5| x |G|
+/// |B|   |6 7 8|   |B|
+/// ```
+#[repr(transparent)]
+pub struct ColorCtm(bindings::drm_color_ctm);
+
+impl ColorCtm {
+    /// Build a matrix from raw S31.32 **sign-magnitude** entries. Mainly useful for tests.
+    #[inline]
+    pub const fn from_raw(matrix: [u64; 9]) -> Self {
+        Self(bindings::drm_color_ctm { matrix })
+    }
+
+    /// The raw matrix, in the UAPI's S31.32 **sign-magnitude** encoding.
+    ///
+    /// Prefer [`Self::coefficient`], which decodes an entry into an ordinary signed value.
+    #[inline]
+    pub fn raw(&self) -> &[u64; 9] {
+        &self.0.matrix
+    }
+
+    /// Return matrix entry `i` as a two's-complement S31.32 fixed-point value, or [`None`] if `i`
+    /// is out of range.
+    ///
+    /// The UAPI encodes these in **sign-magnitude, not two's complement** (bit 63 is the sign and
+    /// the remaining 63 bits are the magnitude), so reading the `u64` as an `i64` silently turns
+    /// every negative coefficient into a huge positive one. Decoding here means no driver has to
+    /// remember that.
+    #[inline]
+    pub fn coefficient(&self, i: usize) -> Option<i64> {
+        let raw = *self.0.matrix.get(i)?;
+        // The magnitude is capped so it always fits a positive i64.
+        let magnitude = (raw & !(1u64 << 63)) as i64;
+        Some(if raw & (1u64 << 63) != 0 {
+            -magnitude
+        } else {
+            magnitude
+        })
+    }
+
+    /// Return all nine coefficients decoded by [`Self::coefficient`].
+    #[inline]
+    pub fn coefficients(&self) -> [i64; 9] {
+        let mut out = [0i64; 9];
+        for (i, o) in out.iter_mut().enumerate() {
+            // The index is in range by construction, so the fallback is unreachable.
+            *o = self.coefficient(i).unwrap_or(0);
+        }
+        out
+    }
+}
+
 /// The main trait for implementing the [`struct drm_crtc`] API for [`Crtc`].
 ///
 /// Any KMS driver should have at least one implementation of this type, which allows them to create
@@ -440,8 +510,28 @@ pub fn new<'a, PrimaryData, CursorData>(
     /// Call this during [`KmsDriver::probe`](crate::drm::kms::KmsDriver::probe), before the device
     /// is registered.
     pub fn enable_gamma(&self, gamma_size: u32) {
+        self.enable_color_mgmt(0, false, gamma_size)
+    }
+
+    /// Enable colour management on this CRTC, creating the `DEGAMMA_LUT`, `CTM` and `GAMMA_LUT`
+    /// properties that userspace can program.
+    ///
+    /// A size of zero suppresses the corresponding LUT property, and `has_ctm` selects whether the
+    /// `CTM` property is created. The programmed values are then readable from the CRTC state via
+    /// [`RawCrtcState::degamma_lut`], [`RawCrtcState::ctm`] and [`RawCrtcState::gamma_lut`].
+    ///
+    /// A driver with no colour hardware can still advertise these and apply them in software while
+    /// it has the pixels; compositors that colour-correct through the CRTC properties (rather than
+    /// by rewriting the framebuffer) otherwise have nowhere to put the correction on such an
+    /// output.
+    ///
+    /// Call this during [`KmsDriver::probe`](crate::drm::kms::KmsDriver::probe), before the device
+    /// is registered.
+    pub fn enable_color_mgmt(&self, degamma_size: u32, has_ctm: bool, gamma_size: u32) {
         // SAFETY: `as_raw()` is a valid, not-yet-registered CRTC.
-        unsafe { bindings::drm_crtc_enable_color_mgmt(self.as_raw(), 0, false, gamma_size) };
+        unsafe {
+            bindings::drm_crtc_enable_color_mgmt(self.as_raw(), degamma_size, has_ctm, gamma_size)
+        };
     }
 }
 
@@ -766,6 +856,12 @@ fn active(&self) -> bool {
         unsafe { (*self.as_raw()).active }
     }
 
+    /// Returns whether the mode or enable state changed in this atomic state.
+    fn mode_changed(&self) -> bool {
+        // SAFETY: The atomic-state API serializes access to this state, including its bitfields.
+        unsafe { (*self.as_raw()).mode_changed() }
+    }
+
     /// Return the display mode programmed into this CRTC state.
     fn mode(&self) -> &DisplayMode {
         // SAFETY: `mode` is embedded in the CRTC state and therefore has the same lifetime. The
@@ -793,6 +889,47 @@ fn gamma_lut(&self) -> Option<&[ColorLut]> {
         // entries valid for the state's lifetime.
         Some(unsafe { core::slice::from_raw_parts(data.cast::<ColorLut>(), n) })
     }
+
+    /// Returns the CRTC's degamma LUT for this state as an array of [`ColorLut`] entries, or
+    /// [`None`] if none is programmed. Requires a non-zero `degamma_size` to have been passed to
+    /// [`UnregisteredCrtc::enable_color_mgmt`].
+    fn degamma_lut(&self) -> Option<&[ColorLut]> {
+        // SAFETY: `as_raw()` is a valid `drm_crtc_state`.
+        let blob = unsafe { (*self.as_raw()).degamma_lut };
+        if blob.is_null() {
+            return None;
+        }
+        // SAFETY: a non-null degamma_lut blob is valid for the state's lifetime.
+        let (data, length) = unsafe { ((*blob).data, (*blob).length) };
+        let n = length / core::mem::size_of::<ColorLut>();
+        if data.is_null() || n == 0 {
+            return None;
+        }
+        // SAFETY: `ColorLut` is transparent over `drm_color_lut`; the blob holds `n` contiguous
+        // entries valid for the state's lifetime.
+        Some(unsafe { core::slice::from_raw_parts(data.cast::<ColorLut>(), n) })
+    }
+
+    /// Returns the CRTC's colour transformation matrix for this state, or [`None`] if none is
+    /// programmed. Requires `has_ctm` to have been passed to
+    /// [`UnregisteredCrtc::enable_color_mgmt`].
+    fn ctm(&self) -> Option<&ColorCtm> {
+        // SAFETY: `as_raw()` is a valid `drm_crtc_state`.
+        let blob = unsafe { (*self.as_raw()).ctm };
+        if blob.is_null() {
+            return None;
+        }
+        // SAFETY: a non-null ctm 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 of nine u64s.
+        if data.is_null() || length < core::mem::size_of::<ColorCtm>() {
+            return None;
+        }
+        // SAFETY: `ColorCtm` is transparent over `drm_color_ctm`, and the blob is at least that
+        // long and valid for the state's lifetime.
+        Some(unsafe { &*data.cast::<ColorCtm>() })
+    }
 }
 impl<T: AsRawCrtcState> RawCrtcState for T {}
 

  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 ` Mike Lothian [this message]
2026-08-26 16:31 ` [PATCH v3 20/23] rust: drm: kms: add synthesized CVT connector modes Mike Lothian
2026-08-26 16:31 ` [PATCH v3 21/23] rust: drm: kms: read a connector's colorimetry and HDR metadata Mike Lothian
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-20-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;
as well as URLs for NNTP newsgroup(s).