@@ -142,6 +142,26 @@ fn mode_config_info(
/// Create mode objects like [`crtc::Crtc`], [`plane::Plane`], etc. for this device
fn create_objects(drm: &UnregisteredKmsDevice<'_, Self::Driver>) -> Result;
+
+ /// The optional [`atomic_commit_tail`] callback for this [`Device`].
+ ///
+ /// It must return a [`CommittedAtomicState`] to prove that it has signaled completion of the hw
+ /// commit phase. Drivers may use this function to customize the order in which commits are
+ /// performed during the atomic commit phase.
+ ///
+ /// If not provided, DRM will use its own default atomic commit tail helper
+ /// [`drm_atomic_helper_commit_tail`].
+ ///
+ /// [`CommittedAtomicState`]: atomic::CommittedAtomicState
+ /// [`atomic_commit_tail`]: srctree/include/drm/drm_modeset_helper_vtables.h
+ /// [`drm_atomic_helper_commit_tail`]: srctree/include/drm/drm_atomic_helpers.h
+ fn atomic_commit_tail<'a>(
+ state: atomic::AtomicCommitTail<'a, Self::Driver>,
+ _modeset_token: atomic::ModesetsReadyToken<'_>,
+ _plane_update_token: atomic::PlaneUpdatesReadyToken<'_>
+ ) -> atomic::CommittedAtomicState<'a, Self::Driver> {
+ build_error::build_error("This function should not be reachable")
+ }
}
impl<T: Kms> private::KmsImpl for T {
@@ -164,7 +184,12 @@ impl<T: Kms> private::KmsImpl for T {
kms_helper_vtable: bindings::drm_mode_config_helper_funcs {
atomic_commit_setup: None, // TODO
- atomic_commit_tail: None, // TODO
+ atomic_commit_tail:
+ if Self::HAS_ATOMIC_COMMIT_TAIL {
+ Some(atomic::commit_tail_callback::<Self>)
+ } else {
+ None
+ },
},
});
@@ -14,14 +14,14 @@
private::Sealed
};
use core::{
- marker::*,
- ptr::NonNull,
cell::Cell,
ffi::*,
- slice,
- ops::*,
- mem::ManuallyDrop,
iter::Iterator,
+ marker::*,
+ mem::ManuallyDrop,
+ ops::*,
+ ptr::NonNull,
+ slice
};
use super::{
crtc::*,
@@ -372,6 +372,361 @@ pub fn add_affected_planes(&self, crtc: &impl AsRawCrtc<Driver = T>) -> Result {
}
}
+/// A token proving that no modesets for a commit have completed.
+///
+/// This token is proof that no commits have yet completed, and is provided as an argument to
+/// [`Kms::atomic_commit_tail`]. This may be used with
+/// [`AtomicCommitTail::commit_modeset_disables`].
+pub struct ModesetsReadyToken<'a>(PhantomData<&'a ()>);
+
+/// A token proving that modeset disables for a commit have completed.
+///
+/// This token is proof that an implementor's [`Kms::atomic_commit_tail`] phase has finished
+/// committing any operations which disable mode objects. It is returned by
+/// [`AtomicCommitTail::commit_modeset_disables`], and can be used with
+/// [`AtomicCommitTail::commit_modeset_enables`] to acquire a [`EnablesCommittedToken`].
+pub struct DisablesCommittedToken<'a>(PhantomData<&'a ()>);
+
+/// A token proving that modeset enables for a commit have completed.
+///
+/// This token is proof that an implementor's [`Kms::atomic_commit_tail`] phase has finished
+/// committing any operations which enable mode objects. It is returned by
+/// [`AtomicCommitTail::commit_modeset_enables`].
+pub struct EnablesCommittedToken<'a>(PhantomData<&'a ()>);
+
+/// A token proving that no plane updates for a commit have completed.
+///
+/// This token is proof that no plane updates have yet been completed within an implementor's
+/// [`Kms::atomic_commit_tail`] implementation, and that we are ready to begin updating planes. It
+/// is provided as an argument to [`Kms::atomic_commit_tail`].
+pub struct PlaneUpdatesReadyToken<'a>(PhantomData<&'a ()>);
+
+/// A token proving that all plane updates for a commit have completed.
+///
+/// This token is proof that all plane updates within an implementor's [`Kms::atomic_commit_tail`]
+/// implementation have completed. It is returned by [`AtomicCommitTail::commit_planes`].
+pub struct PlaneUpdatesCommittedToken<'a>(PhantomData<&'a ()>);
+
+/// An [`AtomicState`] interface that allows a driver to control the [`atomic_commit_tail`]
+/// callback.
+///
+/// This object is provided as an argument to [`Kms::atomic_commit_tail`], and represents an atomic
+/// state within the commit tail phase which is still in the process of being committed to hardware.
+/// It may be used to control the order in which the commit process happens.
+///
+/// # Invariants
+///
+/// Same as [`AtomicState`].
+///
+/// [`atomic_commit_tail`]: srctree/include/drm/drm_modeset_helper_vtables.h
+pub struct AtomicCommitTail<'a, T: KmsDriver>(&'a AtomicState<T>);
+
+impl<'a, T: KmsDriver> AtomicCommitTail<'a, T> {
+ /// Commit modesets which would disable outputs.
+ ///
+ /// This function commits any modesets which would shut down outputs, along with preparing them
+ /// for a new mode (if needed).
+ ///
+ /// Since it is physically impossible to disable an output multiple times, and since it is
+ /// logically unsound to disable an output within an atomic commit after the output was enabled
+ /// in the same commit - this function requires a [`ModesetsReadyToken`] to consume and returns
+ /// a [`DisablesCommittedToken`].
+ ///
+ /// If compatibility with legacy CRTC helpers is desired, this
+ /// should be called before [`commit_planes`] which is what the default commit function does.
+ /// But drivers with different needs can group the modeset commits tgether and do the plane
+ /// commits at the end. This is useful for drivers doing runtime PM since then plane updates
+ /// only happen when the CRTC is actually enabled.
+ ///
+ /// [`commit_planes`]: AtomicCommitTail::commit_planes
+ #[inline]
+ #[must_use]
+ pub fn commit_modeset_disables<'b>(
+ &mut self,
+ _token: ModesetsReadyToken<'_>,
+ ) -> DisablesCommittedToken<'b> {
+ // SAFETY: Both `as_raw()` calls are guaranteed to return valid pointers
+ unsafe {
+ bindings::drm_atomic_helper_commit_modeset_disables(
+ self.0.drm_dev().as_raw(),
+ self.0.as_raw()
+ )
+ }
+
+ DisablesCommittedToken(PhantomData)
+ }
+
+ /// Commit all plane updates.
+ ///
+ /// This function performs all plane updates for the given [`AtomicCommitTail`]. Since it is
+ /// logically unsound to perform the same plane update more then once in a given atomic commit,
+ /// this function requires a [`PlaneUpdatesReadyToken`] to consume and returns a
+ /// [`PlaneUpdatesCommittedToken`] to prove that plane updates for the state have completed.
+ #[inline]
+ #[must_use]
+ pub fn commit_planes<'b>(
+ &mut self,
+ _token: PlaneUpdatesReadyToken<'_>,
+ flags: PlaneCommitFlags
+ ) -> PlaneUpdatesCommittedToken<'b> {
+ // SAFETY: Both `as_raw()` calls are guaranteed to return valid pointers
+ unsafe {
+ bindings::drm_atomic_helper_commit_planes(
+ self.0.drm_dev().as_raw(),
+ self.0.as_raw(),
+ flags.into()
+ )
+ }
+
+ PlaneUpdatesCommittedToken(PhantomData)
+ }
+
+ /// Commit modesets which would enable outputs.
+ ///
+ /// This function commits any modesets in the given [`AtomicCommitTail`] which would enable
+ /// outputs, along with preparing them for their new modes (if needed).
+ ///
+ /// Since it is logically unsound to enable an output before any disabling modesets within the
+ /// same atomic commit have been performed, and physically impossible to enable the same output
+ /// multiple times - this function requires a [`DisablesCommittedToken`] to consume and returns
+ /// a [`EnablesCommittedToken`] which may be used as proof that all modesets in the state have
+ /// been completed.
+ #[inline]
+ #[must_use]
+ pub fn commit_modeset_enables<'b>(
+ &mut self,
+ _token: DisablesCommittedToken<'_>
+ ) -> EnablesCommittedToken<'b> {
+ // SAFETY: Both `as_raw()` calls are guaranteed to return valid pointers
+ unsafe {
+ bindings::drm_atomic_helper_commit_modeset_enables(
+ self.0.drm_dev().as_raw(),
+ self.0.as_raw()
+ )
+ }
+
+ EnablesCommittedToken(PhantomData)
+ }
+
+ /// Fake VBLANK events if needed
+ ///
+ /// Note that this is still relevant to drivers which don't implement [`VblankSupport`] for any
+ /// of their CRTCs.
+ ///
+ /// TODO: more doc
+ ///
+ /// [`VblankSupport`]: super::vblank::VblankSupport
+ pub fn fake_vblank(&mut self) {
+ // SAFETY: `as_raw()` is guaranteed to always return a valid pointer
+ unsafe { bindings::drm_atomic_helper_fake_vblank(self.0.as_raw()) }
+ }
+
+ /// Signal completion of the hardware commit step.
+ ///
+ /// This swaps the atomic state into the relevant atomic state pointers and marks the hardware
+ /// commit step as completed. Since this step can only happen after all plane updates and
+ /// modesets within an [`AtomicCommitTail`] have been completed, it requires both a
+ /// [`EnablesCommittedToken`] and a [`PlaneUpdatesCommittedToken`] to consume. After this
+ /// function is called, the caller no longer has exclusive access to the underlying atomic
+ /// state. As such, this function consumes the [`AtomicCommitTail`] object and returns a
+ /// [`CommittedAtomicState`] accessor for performing post-hw commit tasks.
+ pub fn commit_hw_done<'b>(
+ self,
+ _modeset_token: EnablesCommittedToken<'_>,
+ _plane_updates_token: PlaneUpdatesCommittedToken<'_>,
+ ) -> CommittedAtomicState<'b, T>
+ where
+ 'a: 'b
+ {
+ // SAFETY: we consume the `AtomicCommitTail` object, making it impossible for the user to
+ // mutate the state after this function has been called - which upholds the safety
+ // requirements of the C API allowing us to safely call this function
+ unsafe { bindings::drm_atomic_helper_commit_hw_done(self.0.as_raw()) };
+
+ CommittedAtomicState(self.0)
+ }
+}
+
+// The actual raw C callback for custom atomic commit tail implementations
+pub(crate) unsafe extern "C" fn commit_tail_callback<T: Kms>(
+ state: *mut bindings::drm_atomic_state
+) {
+ // SAFETY:
+ // * We're guaranteed by DRM that `state` always points to a valid instance of
+ // `bindings::drm_atomic_state`
+ // * This conversion is safe via the type invariants
+ let state = unsafe { AtomicState::<T::Driver>::from_raw(state.cast_const()) };
+
+ T::atomic_commit_tail(
+ AtomicCommitTail(state),
+ ModesetsReadyToken(PhantomData),
+ PlaneUpdatesReadyToken(PhantomData),
+ );
+}
+
+/// An [`AtomicState`] which was just committed with [`AtomicCommitTail::commit_hw_done`].
+///
+/// This object represents an [`AtomicState`] which has been fully committed to hardware, and as
+/// such may no longer be mutated as it is visible to userspace. It may be used to control what
+/// happens immediately after an atomic commit finishes within the [`atomic_commit_tail`] callback.
+///
+/// Since acquiring this object means that all modesetting locks have been dropped, a non-blocking
+/// commit could happen at the same time an [`atomic_commit_tail`] implementer has access to this
+/// object. Thus, it cannot be assumed that this object represents the current hardware state - and
+/// instead only represents the final result of the [`AtomicCommitTail`] that was just committed.
+///
+/// # Invariants
+///
+/// It may be assumed that [`drm_atomic_helper_commit_hw_done`] has been called as long as this type
+/// exists.
+///
+/// [`atomic_commit_tail`]: Kms::atomic_commit_tail
+/// [`drm_atomic_helper_commit_hw_done`]: srctree/include/drm/drm_atomic_helper.h
+pub struct CommittedAtomicState<'a, T: KmsDriver>(&'a AtomicState<T>);
+
+impl<'a, T: KmsDriver> CommittedAtomicState<'a, T> {
+ /// Wait for page flips on this state to complete
+ pub fn wait_for_flip_done(&self) {
+ // SAFETY: `drm_atomic_helper_commit_hw_done` has been called via our invariants
+ unsafe {
+ bindings::drm_atomic_helper_wait_for_flip_done(
+ self.0.drm_dev().as_raw(),
+ self.0.as_raw()
+ )
+ }
+ }
+}
+
+impl<'a, T: KmsDriver> Drop for CommittedAtomicState<'a, T> {
+ fn drop(&mut self) {
+ // SAFETY:
+ // * This interface represents the last atomic state accessor which could be affected as a
+ // result of resources from an atomic commit being cleaned up.
+ unsafe {
+ bindings::drm_atomic_helper_cleanup_planes(
+ self.0.drm_dev().as_raw(),
+ self.0.as_raw()
+ )
+ }
+ }
+}
+
+/// An enumerator representing all the possible flags in a [`PlaneCommitFlags`] mask
+///
+/// This is a non-exhaustive list, as the C side could add more later.
+///
+/// TODO: this idea kinda sick we should add some macros for this :3c
+#[derive(Copy, Clone, PartialEq, Eq)]
+#[repr(u32)]
+pub enum PlaneCommitFlag {
+ ActiveOnly = (1 << 0),
+ NoDisableAfterModeset = (1 << 1),
+}
+
+impl BitOr for PlaneCommitFlag {
+ type Output = PlaneCommitFlags;
+
+ fn bitor(self, rhs: Self) -> Self::Output {
+ PlaneCommitFlags(self as u32 | rhs as u32)
+ }
+}
+
+impl BitOr<PlaneCommitFlags> for PlaneCommitFlag {
+ type Output = PlaneCommitFlags;
+
+ fn bitor(self, rhs: PlaneCommitFlags) -> Self::Output {
+ PlaneCommitFlags(self as u32 | rhs.0)
+ }
+}
+
+/// A bitmask for controlling the behavior of [`AtomicCommitTail::commit_planes`]
+///
+/// This corresponds to the `DRM_PLANE_COMMIT_*` flags on the C side. Note that this bitmask does
+/// not discard unknown values in order to ensure that adding new flags on the C side of things does
+/// not break anything in the future.
+#[derive(Copy, Clone, Default, PartialEq, Eq)]
+pub struct PlaneCommitFlags(u32);
+
+impl From<PlaneCommitFlag> for PlaneCommitFlags {
+ fn from(value: PlaneCommitFlag) -> Self {
+ Self(value as u32)
+ }
+}
+
+impl From<PlaneCommitFlags> for u32 {
+ fn from(value: PlaneCommitFlags) -> Self {
+ value.0
+ }
+}
+
+impl BitOr for PlaneCommitFlags {
+ type Output = Self;
+
+ fn bitor(self, rhs: Self) -> Self::Output {
+ Self(self.0 | rhs.0)
+ }
+}
+
+impl BitOrAssign for PlaneCommitFlags {
+ fn bitor_assign(&mut self, rhs: Self) {
+ *self = *self | rhs
+ }
+}
+
+impl BitAnd for PlaneCommitFlags {
+ type Output = PlaneCommitFlags;
+
+ fn bitand(self, rhs: Self) -> Self::Output {
+ Self(self.0 & rhs.0)
+ }
+}
+
+impl BitAndAssign for PlaneCommitFlags {
+ fn bitand_assign(&mut self, rhs: Self) {
+ *self = *self & rhs
+ }
+}
+
+impl BitOr<PlaneCommitFlag> for PlaneCommitFlags {
+ type Output = Self;
+
+ fn bitor(self, rhs: PlaneCommitFlag) -> Self::Output {
+ self | Self::from(rhs)
+ }
+}
+
+impl BitOrAssign<PlaneCommitFlag> for PlaneCommitFlags {
+ fn bitor_assign(&mut self, rhs: PlaneCommitFlag) {
+ *self = *self | rhs
+ }
+}
+
+impl BitAnd<PlaneCommitFlag> for PlaneCommitFlags {
+ type Output = PlaneCommitFlags;
+
+ fn bitand(self, rhs: PlaneCommitFlag) -> Self::Output {
+ self & Self::from(rhs)
+ }
+}
+
+impl BitAndAssign<PlaneCommitFlag> for PlaneCommitFlags {
+ fn bitand_assign(&mut self, rhs: PlaneCommitFlag) {
+ *self = *self & rhs
+ }
+}
+
+impl PlaneCommitFlags {
+ /// Create a new bitmask
+ fn new() -> Self {
+ Self::default()
+ }
+
+ /// Check if the bitmask has the given commit flag set
+ fn has(&self, flag: PlaneCommitFlag) -> bool {
+ *self & flag == flag.into()
+ }
+}
+
/// An iterator which goes through each DRM plane currently in an atomic state.
///
/// Note that this iterator will return [`OpaquePlane`]s, because it's entirely possible for a