[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 {}