[PATCH v3 19/23] rust: drm: expose CRTC mode changes

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]>
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 <[email protected]>
---
 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 {}
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.