[PATCH v3 22/23] rust: drm: kms: walk the CRTCs an atomic commit carries
From: Mike Lothian
Date: Wed Aug 26 2026 - 13:02:58 EST
A driver enforcing a constraint its CRTCs share -- a bandwidth budget,
a clock source, a fixed pool of scanout engines -- has to weigh what
every head will be once the commit lands, and a per-CRTC callback can
only reach its own new state. Reaching the others through committed
state answers a different question: it prices each sibling at what it
is leaving rather than what it is taking, so two heads that both rise
in one commit each find the other still low, pass individually, and
break the shared limit together.
Add the walk over the CRTCs an atomic state carries, the Rust spelling
of for_each_new_crtc_in_state(), on both the state and its mutator.
The CRTC is handed over alongside its new state because the
driver-private data that names what a CRTC drives hangs off the CRTC,
not off the state.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <mike@xxxxxxxxxxxxxx>
---
rust/kernel/drm/kms/atomic.rs | 52 +++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/rust/kernel/drm/kms/atomic.rs b/rust/kernel/drm/kms/atomic.rs
index f9f91edc89c3..24daeaeca075 100644
--- a/rust/kernel/drm/kms/atomic.rs
+++ b/rust/kernel/drm/kms/atomic.rs
@@ -142,6 +142,48 @@ pub fn new_connector_state_for_crtc<C>(&self, crtc: &C) -> Option<&OpaqueConnect
None
}
+ /// Invoke `f` for every CRTC this [`AtomicState`] carries a new state for, passing the CRTC
+ /// and that state.
+ ///
+ /// This is the Rust spelling of walking `for_each_new_crtc_in_state()`.
+ ///
+ /// A driver enforcing a constraint its heads share -- a bandwidth budget, a clock source, a
+ /// fixed pool of scanout engines -- has to weigh what every head will be once the commit
+ /// lands. A per-CRTC callback that consults committed state instead sees each sibling at its
+ /// old value, so two heads that both rise in one commit each find the other still low, pass
+ /// individually, and break the shared limit together.
+ pub fn for_each_new_crtc_state<F>(&self, mut f: F)
+ where
+ F: FnMut(&Crtc<T::Crtc>, &OpaqueCrtcState<T>),
+ {
+ // SAFETY: `state` is initialized via our type invariants, and `crtcs` together with the
+ // device's `num_crtc` are invariant for as long as we hold a reference to it.
+ let (crtcs, num) = unsafe {
+ let raw = self.as_raw();
+ ((*raw).crtcs, (*(*raw).dev).mode_config.num_crtc)
+ };
+ if crtcs.is_null() || num <= 0 {
+ return;
+ }
+ for i in 0..num as usize {
+ // SAFETY: `crtcs` points to `num_crtc` initialized entries.
+ let (ptr, new_state) = unsafe { ((*crtcs.add(i)).ptr, (*crtcs.add(i)).new_state) };
+ if ptr.is_null() || new_state.is_null() {
+ continue;
+ }
+ // SAFETY: every CRTC of a `KmsDriver` device is a `Crtc<T::Crtc>`, and a non-null
+ // `new_state` is a valid `drm_crtc_state`. Both borrow from `self`, so neither can
+ // outlive the atomic state owning them.
+ let (crtc, state) = unsafe {
+ (
+ Crtc::<T::Crtc>::from_raw(ptr),
+ OpaqueCrtcState::<T>::from_raw(new_state),
+ )
+ };
+ f(crtc, state);
+ }
+ }
+
/// Return the old state of the first connector routed to `crtc` in this [`AtomicState`], if
/// any.
///
@@ -282,6 +324,16 @@ pub fn new_connector_state_for_crtc<C>(&self, crtc: &C) -> Option<&OpaqueConnect
self.state.new_connector_state_for_crtc(crtc)
}
+ /// Invoke `f` for every CRTC this state carries a new state for.
+ ///
+ /// See [`AtomicState::for_each_new_crtc_state`].
+ pub fn for_each_new_crtc_state<F>(&self, f: F)
+ where
+ F: FnMut(&Crtc<T::Crtc>, &OpaqueCrtcState<T>),
+ {
+ self.state.for_each_new_crtc_state(f)
+ }
+
/// Return the old state of the first connector routed to `crtc`, if any.
///
/// See [`AtomicState::old_connector_state_for_crtc`].