* [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag
@ 2026-11-12 16:31 Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 1/9] drm/amd/display: Fix planes atomic_create_state implementation Maxime Ripard
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, amd-gfx, Daniel Stone, Dave Stevenson,
Maíra Canal, Raspberry Pi Kernel Maintenance, Rob Clark,
Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang, freedreno,
Oarora Etimis
Hi,
Userspace currently has no atomic way to bring a display pipeline back
to a pristine state. A compositor that wants to start from a known
baseline must explicitly set every property on every KMS object to its
default value, which requires tracking which properties exist and what
their defaults are. This is fragile and must be updated every time a
new property is added to the kernel.
This series introduces a new DRM_MODE_ATOMIC_RESET flag for the
atomic ioctl. When set, the kernel fills the commit with default
states for all KMS objects before applying the properties supplied in
the request. Properties not explicitly included remain at their
defaults. This allows userspace to describe the desired end state
declaratively, without caring about the current state or the full set
of properties.
A driver-specific property is added to vkms planes for testing.
Standard properties are impractical for this: compositors like Mutter
enumerate all known properties, making it hard to isolate the reset
behavior in a test.
Mutter has a working implementation using the new flag, with a
vkms-based test exercising the reset behavior:
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/5309
And we have IGT tests as well:
https://lore.kernel.org/r/20261112162147.766264-1-mripard@kernel.org/
Let me know what you think,
Maxime
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
Changes in v10:
- Stop trying to make partial lease work, they aren't meant to exist, so
we can simply error out when they happen
- More tests for zpos and color management
- Fix DPU1 issues reported by sashiko
- Link to v9: https://lore.kernel.org/r/20261110-drm-reset-state-flag-v9-0-ddb0e26324a0@kernel.org
Changes in v9:
- Add a flag to know if leases have been revoked or are empty
- Handle the situation where a leased plane or connector being reset
would disable an unleased CRTC
- Add more kunit tests to cover the new issues reported by sashiko
- Fix reset-related bugs in amdgpu, vc4 and msm DPU
- Add a vc4 kunit test for its reset related bug
- Link to v8: https://lore.kernel.org/r/20261106-drm-reset-state-flag-v8-0-50fd046b705f@kernel.org
Changes in v8:
- Fix more leases issues
- Drop applied patches
- Link to v7: https://lore.kernel.org/r/20261030-drm-reset-state-flag-v7-0-4e55eaeae683@kernel.org
Changes in v7:
- Fix a whole bunch of sashiko issues to deal with color_mgmt_changed,
leases, partially leased devices (like a plane leased but it's crtc
isn't) and the interaction with PAGE_FLIP_EVENT better
- Fix crtc create_state implementation for ssd130x
- Actually make it possible for a compositor to recreate the same whole
state without passing ALLOW_MODESET
- Fix EDEADLK bugs with kunit tests
- Don't report DRM_CAP_ATOMIC_RESET on !atomic drivers
- Fix error handling in vkms for the new property
- Add more kunit tests for the new corner cases
- Link to v6: https://lore.kernel.org/r/20261022-drm-reset-state-flag-v6-0-f36d640b7e76@kernel.org
Changes in v6:
- If using a lease, only considered the leased objects
- Fix amdgpu plane atomic_create_state implementation
- Link to v5: https://lore.kernel.org/r/20261016-drm-reset-state-flag-v5-0-dad2d64b693b@kernel.org
Changes in v5:
- Fix bisection issue
- Improve the documentation
- Fix connector hotplugging issue
- Added kunit tests for drm_atomic_commit_fill_with_defaults()
- Link to v4: https://lore.kernel.org/r/20260918-drm-reset-state-flag-v4-0-5ad106370f05@kernel.org
Changes in v4:
- Improve the drm_atomic_get_private_obj_state and
drm_atomic_get_connector_state array reallocations
- Return void on functions to insert an object state into a commit when
it cannot fail
- Rename drm_atomic_can_create_state() into
drm_atomic_implements_create_state()
- Move capability check from drm_atomic_commit_fill_with_defaults to its
caller
- Added IGT test
- Link to v3: https://lore.kernel.org/r/20260908-drm-reset-state-flag-v3-0-905570f387a4@kernel.org
Changes in v3:
- Rebased on top of the latest atomic_create_state series
- Sebastian Wick created and tested this work with mutter and vkms
- Fix colorop locking
- Link to v2: https://lore.kernel.org/r/20260812-drm-reset-state-flag-v2-0-e96ce13317dd@kernel.org
Changes in v2:
- Fix bisection
- Add capability to let userspace know if it can reset the state
- Link to v1: https://lore.kernel.org/r/20260708-drm-reset-state-flag-v1-0-c37dc985485d@kernel.org
---
Maxime Ripard (8):
drm/amd/display: Fix planes atomic_create_state implementation
drm/atomic: Add drm_atomic_implements_create_state() helper
drm/atomic: Allow filling a commit with pristine object states
drm/tests: atomic: Add tests for drm_atomic_commit_fill_with_defaults()
drm/amd/display: Take over the current stream and dc_state
drm/vc4: Carry the HVS channel over to created CRTC states
drm/msm/dpu1: Carry the mixers over to created CRTC states
drm/vkms: Add driver-specific plane property for testing
Sebastian Wick (1):
drm/atomic-uapi: Add DRM_MODE_ATOMIC_RESET flag
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 50 +++
.../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 3 -
drivers/gpu/drm/drm_atomic.c | 337 ++++++++++++++++
drivers/gpu/drm/drm_atomic_uapi.c | 44 ++-
drivers/gpu/drm/drm_ioctl.c | 5 +
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c | 23 ++
drivers/gpu/drm/tests/drm_atomic_test.c | 435 ++++++++++++++++++++-
drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c | 105 +++++
drivers/gpu/drm/vc4/vc4_kms.c | 16 +-
drivers/gpu/drm/vkms/vkms_drv.h | 3 +
drivers/gpu/drm/vkms/vkms_plane.c | 63 ++-
include/drm/drm_atomic.h | 4 +
include/uapi/drm/drm.h | 10 +
include/uapi/drm/drm_mode.h | 22 +-
14 files changed, 1107 insertions(+), 13 deletions(-)
---
base-commit: d2a8cee91bfa8616f713c7c8f539a81e8e3554ea
change-id: 20260708-drm-reset-state-flag-2fb2b5711f97
Best regards,
--
Maxime Ripard <mripard@kernel.org>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 1/9] drm/amd/display: Fix planes atomic_create_state implementation
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 2/9] drm/atomic: Add drm_atomic_implements_create_state() helper Maxime Ripard
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, amd-gfx
Commit 2bcec01cf105 ("drm/amdgpu_dm: Convert to atomic_create_state")
converted the amdgpu planes to use atomic_create_state instead of reset.
Unlike reset, atomic_create_state is meant to allocate a new default
state without any side effect on the hardware or software state.
However, during the conversion we forgot to remove the call to destroy
the existing plane state if any. While this was expected from a reset
implementation, it's not for an atomic_create_state one. Remove it.
Fixes: 2bcec01cf105 ("drm/amdgpu_dm: Convert to atomic_create_state")
Reviewed-by: Leo Li <sunpeng.li@amd.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Harry Wentland <harry.wentland@amd.com>
To: Leo Li <sunpeng.li@amd.com>
To: Rodrigo Siqueira <siqueira@igalia.com>
To: Alex Deucher <alexander.deucher@amd.com>
To: Christian König <christian.koenig@amd.com>
Cc: amd-gfx@lists.freedesktop.org
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
index 91398230d408..31101c810bac 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
@@ -1801,13 +1801,10 @@ STATIC_IFN_KUNIT struct drm_plane_state *amdgpu_dm_plane_drm_plane_create_state(
kfree(amdgpu_state->plane_info);
kfree(amdgpu_state);
return ERR_PTR(-ENOMEM);
}
- if (plane->state)
- plane->funcs->atomic_destroy_state(plane, plane->state);
-
__drm_atomic_helper_plane_state_init(&amdgpu_state->base, plane);
amdgpu_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
amdgpu_state->hdr_mult = AMDGPU_HDR_MULT_DEFAULT;
amdgpu_state->shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
amdgpu_state->blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 2/9] drm/atomic: Add drm_atomic_implements_create_state() helper
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 1/9] drm/amd/display: Fix planes atomic_create_state implementation Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 3/9] drm/atomic: Allow filling a commit with pristine object states Maxime Ripard
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard
The atomic reset path will need to create pristine default states from
scratch for every plane, CRTC, and connector. This requires all of them
to implement the atomic_create_state hook.
Introduce a drm_atomic_implements_create_state() helper that iterates
over all planes, CRTCs, and connectors and returns whether they all
provide the hook. Color operations are excluded since they always use
drm_atomic_helper_colorop_create_state() directly.
This will be used both as a precondition before filling a commit with
default states, and to report the device capability to userspace.
Reviewed-by: Daniel Stone <daniels@collabora.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic.c | 49 ++++++++++++++++++++++++++++++++++++++++++++
include/drm/drm_atomic.h | 2 ++
2 files changed, 51 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 81591173265d..7a77051096c9 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1598,10 +1598,59 @@ drm_atomic_get_new_bridge_state(const struct drm_atomic_commit *state,
return drm_priv_to_bridge_state(obj_state);
}
EXPORT_SYMBOL(drm_atomic_get_new_bridge_state);
+/**
+ * drm_atomic_implements_create_state - check if a device supports creating pristine states
+ * @dev: DRM device
+ *
+ * Check whether every plane, CRTC, and connector in @dev implements the
+ * &drm_plane_funcs.atomic_create_state, &drm_crtc_funcs.atomic_create_state,
+ * and &drm_connector_funcs.atomic_create_state hooks respectively. These hooks
+ * are required to create default states from scratch rather than duplicating
+ * the current state.
+ *
+ * Color operations are not checked because they always use
+ * drm_atomic_helper_colorop_create_state() and do not have a per-driver hook.
+ *
+ * Returns:
+ * True if all objects implement atomic_create_state, false otherwise.
+ */
+bool drm_atomic_implements_create_state(struct drm_device *dev)
+{
+ struct drm_connector_list_iter conn_iter;
+ struct drm_connector *connector;
+ struct drm_plane *plane;
+ struct drm_crtc *crtc;
+
+ /*
+ * colorops don't have an atomic_create_state hook but
+ * drm_atomic_helper_colorop_create_state()
+ */
+
+ drm_for_each_plane(plane, dev)
+ if (!plane->funcs->atomic_create_state)
+ return false;
+
+ drm_for_each_crtc(crtc, dev)
+ if (!crtc->funcs->atomic_create_state)
+ return false;
+
+ drm_connector_list_iter_begin(dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ if (!connector->funcs->atomic_create_state) {
+ drm_connector_list_iter_end(&conn_iter);
+ return false;
+ }
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+ return true;
+}
+EXPORT_SYMBOL(drm_atomic_implements_create_state);
+
/**
* drm_atomic_add_encoder_bridges - add bridges attached to an encoder
* @state: atomic state
* @encoder: DRM encoder
*
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 3ae35b09c0cf..72c3d420f40c 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -700,10 +700,12 @@ int drm_crtc_commit_wait(struct drm_crtc_commit *commit);
struct drm_atomic_commit * __must_check
drm_atomic_commit_alloc(struct drm_device *dev);
void drm_atomic_commit_clear(struct drm_atomic_commit *state);
+bool drm_atomic_implements_create_state(struct drm_device *dev);
+
/**
* drm_atomic_commit_get - acquire a reference to the atomic state
* @state: The atomic state
*
* Returns a new reference to the @state
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 3/9] drm/atomic: Allow filling a commit with pristine object states
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 1/9] drm/amd/display: Fix planes atomic_create_state implementation Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 2/9] drm/atomic: Add drm_atomic_implements_create_state() helper Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 4/9] drm/tests: atomic: Add tests for drm_atomic_commit_fill_with_defaults() Maxime Ripard
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard
The upcoming DRM_MODE_ATOMIC_RESET flag will need to create an atomic
commit that brings the entire device back to a pristine state, as if
no configuration had ever been applied.
Create drm_atomic_commit_fill_with_defaults() which iterates over all
CRTCs, planes, connectors, and color operations in the device and
inserts a fresh default state for each one into the commit. This uses
the atomic_create_state() hooks rather than atomic_duplicate_state(),
since atomic_create_state() provides exactly this pristine state on a
per-object basis.
A lessee only gets the objects of its lease reset, the other ones
keeping their current state. Partial pipelines aren't supported
though: detaching a plane or a connector from its CRTC, and disabling
a CRTC driving them, both need the two sides in the commit, so a lease
that splits a pipeline is rejected with -ENOENT. validate_lease() is
expected to keep that from happening, since it requires a lease to
hold at least a CRTC, a connector and a plane, but it only looks at
the object types, not at what they are bound to.
Part of the state the atomic helpers maintain is only recomputed when
something changed, which a reset re-applying the same configuration
doesn't trigger. The CRTC adjusted_mode and the connector best_encoder
are therefore carried over like a duplicated state would, and the
CRTCs ask for a zpos normalization.
The color management changes are reported through color_mgmt_changed
for the same reason: the userspace property handlers only set it when
a property write changes the value stored in the new state, so neither
restoring a default nor leaving a property untouched would, even
though the reset clears whatever is currently programmed.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic.c | 285 +++++++++++++++++++++++++++++++++++++++++++
include/drm/drm_atomic.h | 2 +
2 files changed, 287 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 7a77051096c9..4fcce3bb0aa1 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1647,10 +1647,295 @@ bool drm_atomic_implements_create_state(struct drm_device *dev)
return true;
}
EXPORT_SYMBOL(drm_atomic_implements_create_state);
+/**
+ * drm_atomic_commit_fill_with_defaults - populate a commit with pristine states
+ * @file_priv: DRM file the reset was requested from, or NULL
+ * @commit: atomic commit to fill
+ *
+ * Iterate over all CRTCs, planes, connectors, and color operations in
+ * the device and insert a freshly created default state for each one
+ * into @commit. The states are created through the atomic_create_state()
+ * hooks, producing the same initial state the driver starts with rather
+ * than a copy of the current hardware state.
+ *
+ * In the default state, all objects are disabled: CRTCs are inactive,
+ * planes are disabled, and connectors are unbound. Driver-specific
+ * properties such as color pipeline settings or max_bpc may carry
+ * non-zero defaults appropriate for basic display output.
+ *
+ * If @file_priv is a lessee, only the CRTCs, planes and connectors part
+ * of its lease are reset, and the other objects keep their current
+ * state. @file_priv is optional: if NULL, it is considered to not hold
+ * any lease, and all objects are reset.
+ *
+ * Partial pipelines aren't supported. Resetting a plane or a connector
+ * detaches it from the CRTC it is bound to, and resetting a CRTC
+ * disables the planes and connectors bound to it, neither of which the
+ * atomic helpers can sort out with only one side of the pair in the
+ * commit. A lease covering a plane or a connector without the CRTC it
+ * is bound to, or a CRTC without all the planes and connectors bound
+ * to it, would thus require reaching outside of it, so the reset is
+ * rejected instead.
+ *
+ * Such a lease shouldn't exist in the first place: validate_lease()
+ * requires a lease to hold at least a CRTC, a connector and a plane,
+ * so leases are meant to describe whole pipelines. It only looks at
+ * the object types though, not at what they are bound to, so the
+ * reset still has to guard against it.
+ *
+ * This is meant to be used with the %DRM_MODE_ATOMIC_RESET flag, which
+ * needs to bring the device back to a known baseline before applying
+ * userspace property changes on top.
+ *
+ * Returns:
+ * 0 on success, -ENOENT if a leased plane or connector is bound to a
+ * CRTC that isn't leased, or a negative error code on failure.
+ */
+int drm_atomic_commit_fill_with_defaults(struct drm_file *file_priv,
+ struct drm_atomic_commit *commit)
+{
+ struct drm_device *dev = commit->dev;
+ struct drm_mode_config *config = &dev->mode_config;
+ struct drm_crtc *crtc;
+ struct drm_plane *plane, *crtc_plane;
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+ struct drm_colorop *colorop;
+ int ret;
+
+ drm_WARN_ON(dev, !commit->acquire_ctx);
+
+ /*
+ * Private objects are ignored because none have userspace
+ * properties we might want to reset. atomic_check
+ * implementations will derive or infer their private obj state
+ * from the state that will end up being committed anyway.
+ */
+ drm_for_each_colorop(colorop, dev) {
+ struct drm_colorop_state *colorop_state;
+
+ if (!drm_lease_held(file_priv, colorop->plane->base.id))
+ continue;
+
+ ret = drm_modeset_lock(&colorop->plane->mutex, commit->acquire_ctx);
+ if (ret)
+ return ret;
+
+ colorop_state = drm_atomic_helper_colorop_create_state(colorop);
+ if (IS_ERR(colorop_state))
+ return PTR_ERR(colorop_state);
+
+ drm_atomic_commit_set_colorop_state(commit, colorop, colorop_state);
+ }
+
+ drm_for_each_plane(plane, dev) {
+ struct drm_plane_state *plane_state;
+
+ if (!drm_lease_held(file_priv, plane->base.id))
+ continue;
+
+ ret = drm_modeset_lock(&plane->mutex, commit->acquire_ctx);
+ if (ret)
+ return ret;
+
+ /*
+ * The pristine state detaches the plane from the CRTC it
+ * is enabled on, which the atomic helpers can only sort
+ * out with that CRTC in the commit. Resetting a plane
+ * enabled on a CRTC that isn't leased would thus require
+ * touching an object the lease doesn't cover, so report it
+ * like any other access outside of the lease.
+ */
+ if (plane->state->crtc &&
+ !drm_lease_held(file_priv, plane->state->crtc->base.id)) {
+ drm_dbg_atomic(dev,
+ "[PLANE:%d:%s] is enabled on [CRTC:%d:%s], which isn't leased\n",
+ plane->base.id, plane->name,
+ plane->state->crtc->base.id,
+ plane->state->crtc->name);
+ return -ENOENT;
+ }
+
+ plane_state = plane->funcs->atomic_create_state(plane);
+ if (IS_ERR(plane_state))
+ return PTR_ERR(plane_state);
+
+ /*
+ * Same thing for the planes: report a color pipeline change
+ * compared to the current state.
+ */
+ plane_state->color_mgmt_changed =
+ plane->state->color_pipeline != plane_state->color_pipeline;
+
+ drm_atomic_commit_set_plane_state(commit, plane, plane_state);
+ }
+
+ drm_for_each_crtc(crtc, dev) {
+ struct drm_crtc_state *crtc_state;
+
+ if (!drm_lease_held(file_priv, crtc->base.id))
+ continue;
+
+ ret = drm_modeset_lock(&crtc->mutex, commit->acquire_ctx);
+ if (ret)
+ return ret;
+
+ /*
+ * The reset disables the CRTC, which the planes enabled on
+ * it can't stay bound to. Disabling them is only an option
+ * for the ones the lease covers, so a CRTC driving a plane
+ * it doesn't is rejected like the other way around.
+ */
+ drm_for_each_plane_mask(crtc_plane, dev, crtc->state->plane_mask) {
+ if (drm_lease_held(file_priv, crtc_plane->base.id))
+ continue;
+
+ drm_dbg_atomic(dev,
+ "[CRTC:%d:%s] drives [PLANE:%d:%s], which isn't leased\n",
+ crtc->base.id, crtc->name,
+ crtc_plane->base.id, crtc_plane->name);
+ return -ENOENT;
+ }
+
+ crtc_state = crtc->funcs->atomic_create_state(crtc);
+ if (IS_ERR(crtc_state))
+ return PTR_ERR(crtc_state);
+
+ /*
+ * The userspace property handlers only set
+ * color_mgmt_changed when a property write changes the
+ * value stored in the new state. Since the pristine states
+ * we create here are already set to their default values,
+ * restoring those defaults wouldn't set the flag, and
+ * neither would leaving the properties untouched, even
+ * though the commit clears whatever LUT or CTM is
+ * currently programmed. Compare with the current state
+ * so that the drivers reprogram the color management
+ * hardware.
+ */
+ crtc_state->color_mgmt_changed =
+ crtc->state->degamma_lut != crtc_state->degamma_lut ||
+ crtc->state->ctm != crtc_state->ctm ||
+ crtc->state->gamma_lut != crtc_state->gamma_lut;
+
+ /*
+ * adjusted_mode is derived from the mode by the atomic
+ * helpers rather than set by userspace, and mode_fixup()
+ * only recomputes it when the mode or the connectors
+ * changed. A duplicated state carries it over, so nothing
+ * needs to be recomputed when nothing changes, but the
+ * pristine states we create here would be left with a
+ * zeroed adjusted_mode on a CRTC that stays enabled across
+ * the commit. Carry it over too, so that the drivers and
+ * the vblank timestamping constants keep seeing the
+ * timings currently programmed.
+ */
+ drm_mode_copy(&crtc_state->adjusted_mode,
+ &crtc->state->adjusted_mode);
+
+ /*
+ * The planes come back with the default zpos, which the
+ * atomic helpers only normalize when the planes bound to a
+ * CRTC or one of their zpos changed. Re-applying the same
+ * configuration on top of the reset wouldn't trigger
+ * either, so ask for a normalization explicitly.
+ */
+ crtc_state->zpos_changed = true;
+
+ drm_atomic_commit_set_crtc_state(commit, crtc, crtc_state);
+ }
+
+ ret = drm_modeset_lock(&config->connection_mutex, commit->acquire_ctx);
+ if (ret)
+ return ret;
+
+ drm_connector_list_iter_begin(dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ struct drm_connector_state *connector_state;
+
+ if (!drm_lease_held(file_priv, connector->base.id)) {
+ /*
+ * Same as the planes of a leased CRTC: the reset
+ * disables that CRTC, and a connector the lease
+ * doesn't cover can't be unbound from it.
+ */
+ if (connector->state->crtc &&
+ drm_lease_held(file_priv,
+ connector->state->crtc->base.id)) {
+ drm_dbg_atomic(dev,
+ "[CONNECTOR:%d:%s] isn't leased, but [CRTC:%d:%s] it is bound to is\n",
+ connector->base.id, connector->name,
+ connector->state->crtc->base.id,
+ connector->state->crtc->name);
+ drm_connector_list_iter_end(&conn_iter);
+ return -ENOENT;
+ }
+
+ continue;
+ }
+
+ /*
+ * Same as the planes above: unbinding the connector from
+ * an unleased CRTC would need that CRTC in the commit.
+ */
+ if (connector->state->crtc &&
+ !drm_lease_held(file_priv, connector->state->crtc->base.id)) {
+ drm_dbg_atomic(dev,
+ "[CONNECTOR:%d:%s] is bound to [CRTC:%d:%s], which isn't leased\n",
+ connector->base.id, connector->name,
+ connector->state->crtc->base.id,
+ connector->state->crtc->name);
+ drm_connector_list_iter_end(&conn_iter);
+ return -ENOENT;
+ }
+
+ /*
+ * drm_atomic_implements_create_state() already checked
+ * this, but connectors can be hotplugged between that
+ * lockless check and the iteration here.
+ */
+ if (!connector->funcs->atomic_create_state) {
+ drm_connector_list_iter_end(&conn_iter);
+ return -EOPNOTSUPP;
+ }
+
+ connector_state = connector->funcs->atomic_create_state(connector);
+ if (IS_ERR(connector_state)) {
+ drm_connector_list_iter_end(&conn_iter);
+ ret = PTR_ERR(connector_state);
+ return ret;
+ }
+
+ /*
+ * best_encoder is a routing information maintained by the
+ * atomic helpers rather than a userspace property, and it
+ * is only carried over to a new state when that state is a
+ * duplicate of the current one. Since
+ * update_connector_routing() uses it to detect the commits
+ * that keep the connector routing unchanged, carry the
+ * encoder currently in use over so that a reset on which
+ * userspace re-applies the exact same configuration doesn't
+ * require a modeset.
+ */
+ connector_state->best_encoder = connector->state->best_encoder;
+
+ ret = drm_atomic_commit_set_connector_state(commit, connector, connector_state);
+ if (ret) {
+ connector->funcs->atomic_destroy_state(connector, connector_state);
+ drm_connector_list_iter_end(&conn_iter);
+ return ret;
+ }
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_atomic_commit_fill_with_defaults);
+
/**
* drm_atomic_add_encoder_bridges - add bridges attached to an encoder
* @state: atomic state
* @encoder: DRM encoder
*
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 72c3d420f40c..cac74d43a34d 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -701,10 +701,12 @@ int drm_crtc_commit_wait(struct drm_crtc_commit *commit);
struct drm_atomic_commit * __must_check
drm_atomic_commit_alloc(struct drm_device *dev);
void drm_atomic_commit_clear(struct drm_atomic_commit *state);
bool drm_atomic_implements_create_state(struct drm_device *dev);
+int drm_atomic_commit_fill_with_defaults(struct drm_file *file_priv,
+ struct drm_atomic_commit *commit);
/**
* drm_atomic_commit_get - acquire a reference to the atomic state
* @state: The atomic state
*
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 4/9] drm/tests: atomic: Add tests for drm_atomic_commit_fill_with_defaults()
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
` (2 preceding siblings ...)
2026-11-12 16:31 ` [PATCH v10 3/9] drm/atomic: Allow filling a commit with pristine object states Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 5/9] drm/amd/display: Take over the current stream and dc_state Maxime Ripard
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard, Daniel Stone
drm_atomic_commit_fill_with_defaults() is used by the
DRM_MODE_ATOMIC_RESET flag to bring all objects back to their
pristine state. However, it has no dedicated test coverage.
The first two tests check the reset cycle: that a reset returns an
enabled output to a fully disabled state, and that committing the
default-filled state succeeds.
The four others check the derived state the reset has to take care
of, since the atomic helpers only recompute it when something
changed: the color management changes reported through
color_mgmt_changed on the CRTC and on the plane, the zpos
normalization being asked for, and re-applying the same
configuration on top of the reset not requiring a modeset.
Suggested-by: Daniel Stone <daniel@fooishbar.org>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/tests/drm_atomic_test.c | 435 +++++++++++++++++++++++++++++++-
1 file changed, 433 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/tests/drm_atomic_test.c b/drivers/gpu/drm/tests/drm_atomic_test.c
index 541a0062f143..7527bab46eb2 100644
--- a/drivers/gpu/drm/tests/drm_atomic_test.c
+++ b/drivers/gpu/drm/tests/drm_atomic_test.c
@@ -1,15 +1,20 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Kunit test for drm_atomic functions
*/
+
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_auth.h>
+#include <drm/drm_colorop.h>
#include <drm/drm_encoder.h>
+#include <drm/drm_file.h>
#include <drm/drm_kunit_helpers.h>
#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_property.h>
#include <kunit/test.h>
struct drm_atomic_test_priv {
struct drm_device drm;
@@ -20,10 +25,14 @@ struct drm_atomic_test_priv {
};
static const struct drm_connector_helper_funcs drm_atomic_init_connector_helper_funcs = {
};
+static const struct drm_colorop_funcs drm_atomic_test_colorop_funcs = {
+ .destroy = drm_colorop_destroy,
+};
+
static const struct drm_connector_funcs drm_atomic_init_connector_funcs = {
.atomic_create_state = drm_atomic_helper_connector_create_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
};
@@ -138,16 +147,438 @@ static void drm_test_drm_atomic_get_connector_for_encoder(struct kunit *test)
static struct kunit_case drm_atomic_get_connector_for_encoder_tests[] = {
KUNIT_CASE(drm_test_drm_atomic_get_connector_for_encoder),
{ }
};
-
static struct kunit_suite drm_atomic_get_connector_for_encoder_test_suite = {
.name = "drm_test_atomic_get_connector_for_encoder",
.test_cases = drm_atomic_get_connector_for_encoder_tests,
};
-kunit_test_suite(drm_atomic_get_connector_for_encoder_test_suite);
+/*
+ * Test that after enabling a CRTC and connector, filling with defaults
+ * produces a state where all objects are disabled: CRTC is inactive,
+ * plane is disabled, and connector is unbound.
+ *
+ * This is done without any lease, so all objects are expected to be
+ * reset.
+ *
+ * The plane is not explicitly enabled with a framebuffer here, so
+ * this validates that fill_with_defaults produces a consistent
+ * disabled state rather than testing an active plane teardown.
+ */
+static void drm_test_check_fill_with_defaults(struct kunit *test)
+{
+ struct drm_atomic_test_priv *priv;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_connector_state *new_conn_state;
+ struct drm_crtc_state *new_crtc_state;
+ struct drm_plane_state *new_plane_state;
+ struct drm_atomic_commit *state;
+ struct drm_display_mode *mode;
+ struct drm_device *drm;
+ int ret;
+
+ priv = create_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+ drm = &priv->drm;
+
+ mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode);
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+retry_enable:
+ ret = drm_kunit_helper_enable_crtc_connector(test, drm,
+ priv->crtc,
+ &priv->connector,
+ mode, &ctx);
+ if (ret == -EDEADLK) {
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_enable;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_fill:
+ ret = drm_atomic_commit_fill_with_defaults(NULL, state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_fill;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_crtc_state);
+ KUNIT_EXPECT_FALSE(test, new_crtc_state->enable);
+ KUNIT_EXPECT_FALSE(test, new_crtc_state->active);
+
+ new_plane_state = drm_atomic_get_new_plane_state(state, priv->plane);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_plane_state);
+ KUNIT_EXPECT_NULL(test, new_plane_state->crtc);
+ KUNIT_EXPECT_NULL(test, new_plane_state->fb);
+
+ new_conn_state = drm_atomic_get_new_connector_state(state, &priv->connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state);
+ KUNIT_EXPECT_NULL(test, new_conn_state->crtc);
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Test that after enabling a CRTC and connector, fill_with_defaults
+ * produces a state that can be successfully committed.
+ *
+ * This is done without any lease, so all objects are reset.
+ *
+ * The plane is not explicitly enabled with a framebuffer, so this
+ * validates that fill_with_defaults produces a consistent disabled
+ * state rather than testing an active plane teardown.
+ */
+static void drm_test_check_fill_with_defaults_commit(struct kunit *test)
+{
+ struct drm_atomic_test_priv *priv;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_atomic_commit *state;
+ struct drm_display_mode *mode;
+ struct drm_device *drm;
+ int ret;
+
+ priv = create_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+ drm = &priv->drm;
+
+ mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode);
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+retry_enable:
+ ret = drm_kunit_helper_enable_crtc_connector(test, drm,
+ priv->crtc,
+ &priv->connector,
+ mode, &ctx);
+ if (ret == -EDEADLK) {
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_enable;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_fill:
+ ret = drm_atomic_commit_fill_with_defaults(NULL, state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_fill;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ state->allow_modeset = true;
+
+ ret = drm_atomic_commit(state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_fill;
+ }
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Test that filling with defaults asks for a zpos normalization on the
+ * CRTCs, since drm_atomic_normalize_zpos() would otherwise skip the
+ * planes that keep their zpos across the reset and leave them with the
+ * un-normalized zpos a pristine state comes with.
+ */
+static void drm_test_check_fill_with_defaults_zpos(struct kunit *test)
+{
+ struct drm_atomic_test_priv *priv;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_crtc_state *new_crtc_state;
+ struct drm_atomic_commit *state;
+ struct drm_device *drm;
+ int ret;
+
+ priv = create_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+ drm = &priv->drm;
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_fill:
+ ret = drm_atomic_commit_fill_with_defaults(NULL, state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_fill;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_crtc_state);
+ KUNIT_EXPECT_TRUE(test, new_crtc_state->zpos_changed);
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Test that filling with defaults reports the color pipeline being
+ * cleared from a plane through color_mgmt_changed on its new state, so
+ * that drivers reprogram the color management hardware.
+ */
+static void drm_test_check_fill_with_defaults_color_pipeline(struct kunit *test)
+{
+ struct drm_atomic_test_priv *priv;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_plane_state *new_plane_state;
+ struct drm_atomic_commit *state;
+ struct drm_colorop *colorop;
+ struct drm_device *drm;
+ int ret;
+
+ priv = create_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+ drm = &priv->drm;
+
+ /* Freed through the funcs destroy hook by drm_mode_config_cleanup() */
+ colorop = kzalloc_obj(*colorop);
+ KUNIT_ASSERT_NOT_NULL(test, colorop);
+
+ ret = drm_plane_colorop_curve_1d_init(drm, colorop, priv->plane,
+ &drm_atomic_test_colorop_funcs,
+ BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF),
+ DRM_COLOROP_FLAG_ALLOW_BYPASS);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /*
+ * Put a color pipeline on the current plane state, like userspace
+ * setting the COLOR_PIPELINE property would.
+ */
+ priv->plane->state->color_pipeline = colorop;
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_fill:
+ ret = drm_atomic_commit_fill_with_defaults(NULL, state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_fill;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ new_plane_state = drm_atomic_get_new_plane_state(state, priv->plane);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_plane_state);
+ KUNIT_EXPECT_NULL(test, new_plane_state->color_pipeline);
+ KUNIT_EXPECT_TRUE(test, new_plane_state->color_mgmt_changed);
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Test that filling with defaults reports the color management changes
+ * on the new states through color_mgmt_changed, so that drivers
+ * reprogram the color management hardware when a LUT is being cleared
+ * by the reset.
+ */
+static void drm_test_check_fill_with_defaults_color_mgmt(struct kunit *test)
+{
+ struct drm_atomic_test_priv *priv;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_crtc_state *new_crtc_state;
+ struct drm_plane_state *new_plane_state;
+ struct drm_atomic_commit *state;
+ struct drm_property_blob *gamma_lut;
+ struct drm_device *drm;
+ u32 lut = 0;
+ int ret;
+
+ priv = create_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+ drm = &priv->drm;
+
+ gamma_lut = drm_property_create_blob(drm, sizeof(lut), &lut);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gamma_lut);
+
+ /*
+ * Attach a gamma LUT to the current CRTC state, like userspace
+ * setting the GAMMA_LUT property would. The state takes ownership
+ * of the blob reference.
+ */
+ priv->crtc->state->gamma_lut = gamma_lut;
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_fill:
+ ret = drm_atomic_commit_fill_with_defaults(NULL, state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_fill;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_crtc_state);
+ KUNIT_EXPECT_NULL(test, new_crtc_state->gamma_lut);
+ KUNIT_EXPECT_TRUE(test, new_crtc_state->color_mgmt_changed);
+
+ new_plane_state = drm_atomic_get_new_plane_state(state, priv->plane);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_plane_state);
+ KUNIT_EXPECT_FALSE(test, new_plane_state->color_mgmt_changed);
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Test that a reset on which userspace re-applies the exact same
+ * configuration doesn't need a modeset, and thus doesn't require the
+ * DRM_MODE_ATOMIC_ALLOW_MODESET flag.
+ */
+static void drm_test_check_fill_with_defaults_no_modeset(struct kunit *test)
+{
+ struct drm_atomic_test_priv *priv;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_crtc_state *new_crtc_state;
+ struct drm_connector_state *new_conn_state;
+ struct drm_atomic_commit *state;
+ struct drm_display_mode *mode;
+ struct drm_device *drm;
+ int ret;
+
+ priv = create_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+ drm = &priv->drm;
+
+ mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode);
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+retry_enable:
+ ret = drm_kunit_helper_enable_crtc_connector(test, drm,
+ priv->crtc,
+ &priv->connector,
+ mode, &ctx);
+ if (ret == -EDEADLK) {
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_enable;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry:
+ ret = drm_atomic_commit_fill_with_defaults(NULL, state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /* Re-apply the exact same configuration on top of the reset */
+ new_conn_state = drm_atomic_get_new_connector_state(state, &priv->connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state);
+
+ ret = drm_atomic_set_crtc_for_connector(new_conn_state, priv->crtc);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_crtc_state);
+
+ ret = drm_atomic_set_mode_for_crtc(new_crtc_state, mode);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ new_crtc_state->enable = true;
+ new_crtc_state->active = true;
+
+ /* Modesets aren't allowed and shouldn't be needed */
+ state->allow_modeset = false;
+
+ ret = drm_atomic_check_only(state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry;
+ }
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_FALSE(test, new_crtc_state->active_changed);
+ KUNIT_EXPECT_FALSE(test, new_crtc_state->connectors_changed);
+ KUNIT_EXPECT_FALSE(test, new_crtc_state->mode_changed);
+ KUNIT_EXPECT_FALSE(test, drm_atomic_crtc_needs_modeset(new_crtc_state));
+
+ /*
+ * mode_fixup() only derives adjusted_mode when the mode or the
+ * connectors changed, so a CRTC that stays enabled across the
+ * commit must have kept the timings it was already running
+ * instead of the zeroed mode a pristine state comes with.
+ */
+ KUNIT_EXPECT_TRUE(test, drm_mode_equal(&new_crtc_state->adjusted_mode,
+ &new_crtc_state->mode));
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+}
+
+static struct kunit_case drm_atomic_fill_with_defaults_tests[] = {
+ KUNIT_CASE(drm_test_check_fill_with_defaults),
+ KUNIT_CASE(drm_test_check_fill_with_defaults_commit),
+ KUNIT_CASE(drm_test_check_fill_with_defaults_color_mgmt),
+ KUNIT_CASE(drm_test_check_fill_with_defaults_color_pipeline),
+ KUNIT_CASE(drm_test_check_fill_with_defaults_zpos),
+ KUNIT_CASE(drm_test_check_fill_with_defaults_no_modeset),
+ { }
+};
+
+static struct kunit_suite drm_atomic_fill_with_defaults_test_suite = {
+ .name = "drm_atomic_commit_fill_with_defaults",
+ .test_cases = drm_atomic_fill_with_defaults_tests,
+};
+
+kunit_test_suites(&drm_atomic_get_connector_for_encoder_test_suite,
+ &drm_atomic_fill_with_defaults_test_suite);
MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
MODULE_DESCRIPTION("Kunit test for drm_atomic functions");
MODULE_LICENSE("GPL");
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 5/9] drm/amd/display: Take over the current stream and dc_state
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
` (3 preceding siblings ...)
2026-11-12 16:31 ` [PATCH v10 4/9] drm/tests: atomic: Add tests for drm_atomic_commit_fill_with_defaults() Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 6/9] drm/vc4: Carry the HVS channel over to created CRTC states Maxime Ripard
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, amd-gfx
drm_atomic_commit_fill_with_defaults() builds its commits out of
states created through atomic_create_state() instead of duplicating
the current state. A created state comes with no stream nor dc_state,
while a duplicated state carries the ones in use, with their
reference.
The update passes of amdgpu_dm_atomic_check() release the stream and
dc_state in use and clear the pointer of the new state, on top of the
reference the current state drops when it gets destroyed. Those
releases are only balanced if the new state brought a reference of
its own, so a commit that disables or reconfigures a live CRTC or
plane through a created state over-releases and frees the stream
early, leading to a use-after-free when the current state gets
destroyed. The paths that keep a CRTC or a plane enabled without a
modeset also work on the stream and dc_state through the new state,
and hit BUG_ON(dm_new_crtc_state->stream == NULL) since it never had
one.
Take the stream and dc_state of the current state over, with a
reference, at the beginning of amdgpu_dm_atomic_check(), like a
duplicated state would have carried them. This has to happen before
the update passes clear the stream of the new states: past that
point, a state whose stream was cleared can't be told apart from a
created one.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Harry Wentland <harry.wentland@amd.com>
To: Leo Li <sunpeng.li@amd.com>
To: Rodrigo Siqueira <siqueira@igalia.com>
To: Alex Deucher <alexander.deucher@amd.com>
To: Christian König <christian.koenig@amd.com>
Cc: amd-gfx@lists.freedesktop.org
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 50 +++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 0ea45ab12119..b2eddb87dd5c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -6279,10 +6279,60 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_check(struct drm_device *dev,
struct drm_dp_mst_topology_state *mst_state;
struct dsc_mst_fairness_vars vars[MAX_PIPES] = {0};
trace_amdgpu_dm_atomic_check_begin(state);
+ /*
+ * A CRTC or a plane that stays enabled keeps its stream and
+ * dc_state: a duplicated state carries them over, with their
+ * reference, but a state created through atomic_create_state()
+ * comes with neither. Take them over from the current state,
+ * with a reference, like a duplicated state would.
+ *
+ * The reference is needed when the update passes below
+ * reconfigure the CRTC or plane: they release the stream and
+ * dc_state in use and clear the pointer of the new state then,
+ * on top of the reference the current state drops when it gets
+ * destroyed, so that release is only balanced if the new state
+ * brought a reference of its own. The pointer is needed when
+ * they don't: the paths that keep a CRTC or a plane enabled
+ * without a modeset work on the stream and dc_state through
+ * the new state.
+ *
+ * A stream or a dc_state in the current state means the CRTC
+ * or the plane is enabled, since they are cleared when it gets
+ * disabled, so the conditions below already single out the
+ * live objects. They deliberately don't check whether this
+ * commit keeps them enabled on top of that: the update passes
+ * release the stream and dc_state of the CRTC or plane being
+ * disabled too, so their new state has to bring the reference
+ * just the same.
+ *
+ * This has to run before the update passes below clear the
+ * stream of the new states: past that point, a state whose
+ * stream was cleared can't be told apart from a created one.
+ */
+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
+ dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
+ dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
+
+ if (!dm_new_crtc_state->stream && dm_old_crtc_state->stream) {
+ dm_new_crtc_state->stream = dm_old_crtc_state->stream;
+ dc_stream_retain(dm_new_crtc_state->stream);
+ }
+ }
+
+ for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) {
+ struct dm_plane_state *dm_old_plane_state = to_dm_plane_state(old_plane_state);
+ struct dm_plane_state *dm_new_plane_state = to_dm_plane_state(new_plane_state);
+
+ if (!dm_new_plane_state->dc_state && dm_old_plane_state->dc_state) {
+ dm_new_plane_state->dc_state = dm_old_plane_state->dc_state;
+ dc_plane_state_retain(dm_new_plane_state->dc_state);
+ }
+ }
+
ret = drm_atomic_helper_check_modeset(dev, state);
if (ret) {
drm_dbg_atomic(dev, "drm_atomic_helper_check_modeset() failed: %pe\n", ERR_PTR(ret));
goto fail;
}
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 6/9] drm/vc4: Carry the HVS channel over to created CRTC states
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
` (4 preceding siblings ...)
2026-11-12 16:31 ` [PATCH v10 5/9] drm/amd/display: Take over the current stream and dc_state Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 7/9] drm/msm/dpu1: Carry the mixers " Maxime Ripard
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
drm_atomic_commit_fill_with_defaults() builds its commits out of
states created through atomic_create_state() instead of duplicating
the current state. A created CRTC state comes with its
assigned_channel left to VC4_HVS_CHANNEL_DISABLED, while a duplicated
state carries the channel currently in use.
vc4_pv_muxing_atomic_check() skips the CRTCs whose enablement doesn't
change, so a CRTC that stays enabled with a created state keeps its
channel unset, and vc4_core_clock_atomic_check() then indexes
hvs_new_state->fifo_state[] with VC4_HVS_CHANNEL_DISABLED (-1U), far
outside of the array.
Copy the channel of the current state over in the skip branch, like a
duplicated state would have carried it. This is a no-op for duplicated
states, which already have the channel set.
Add a KUnit test that enables an output, fills a state with defaults
and re-applies the same configuration, and checks that the CRTC kept
its channel.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Dave Stevenson <dave.stevenson@raspberrypi.com>
To: Maíra Canal <mcanal@igalia.com>
Cc: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
---
drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c | 105 +++++++++++++++++++++++++
drivers/gpu/drm/vc4/vc4_kms.c | 16 +++-
2 files changed, 119 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c b/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c
index 7a54f46acff9..c868d2cd1b3d 100644
--- a/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c
+++ b/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c
@@ -1119,14 +1119,119 @@ drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable_too_many_crtc_state(struct ku
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
}
+/*
+ * Test that a CRTC that stays enabled with a state created through
+ * atomic_create_state(), like the ones drm_atomic_commit_fill_with_defaults()
+ * builds for a reset, keeps the HVS channel it was assigned:
+ * vc4_pv_muxing_atomic_check() skips the CRTCs whose enablement doesn't
+ * change, so their new state has to carry the channel over from the
+ * current state, the way a duplicated state would, for
+ * vc4_core_clock_atomic_check() to see a valid channel.
+ */
+static void
+drm_test_vc5_pv_muxing_bugs_created_state_keeps_channel(struct kunit *test)
+{
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_atomic_commit *state;
+ struct vc4_crtc_state *new_vc4_crtc_state;
+ struct drm_device *drm;
+ struct vc4_dev *vc4;
+ unsigned int old_channel;
+ int ret;
+
+ vc4 = vc5_mock_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4);
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+ drm = &vc4->base;
+retry_first:
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+ ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_first;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = drm_atomic_check_only(state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_first;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ new_vc4_crtc_state = get_vc4_crtc_state_for_encoder(test, state,
+ VC4_ENCODER_TYPE_HDMI0);
+ KUNIT_ASSERT_NOT_NULL(test, new_vc4_crtc_state);
+
+ old_channel = new_vc4_crtc_state->assigned_channel;
+ KUNIT_ASSERT_NE(test, old_channel, VC4_HVS_CHANNEL_DISABLED);
+
+ /* Make the enabled state the current one */
+ ret = drm_atomic_helper_swap_state(state, false);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+retry_second:
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+ /*
+ * Fill the state with defaults, like a DRM_MODE_ATOMIC_RESET
+ * commit would, then re-apply the exact same configuration.
+ */
+ ret = drm_atomic_commit_fill_with_defaults(NULL, state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_second;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_second;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = drm_atomic_check_only(state);
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry_second;
+ }
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ new_vc4_crtc_state = get_vc4_crtc_state_for_encoder(test, state,
+ VC4_ENCODER_TYPE_HDMI0);
+ KUNIT_ASSERT_NOT_NULL(test, new_vc4_crtc_state);
+
+ KUNIT_EXPECT_EQ(test, new_vc4_crtc_state->assigned_channel, old_channel);
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+}
+
static struct kunit_case vc5_pv_muxing_bugs_tests[] = {
KUNIT_CASE(drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable),
KUNIT_CASE(drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable_too_many_crtc_state),
KUNIT_CASE(drm_test_vc5_pv_muxing_bugs_stable_fifo),
+ KUNIT_CASE(drm_test_vc5_pv_muxing_bugs_created_state_keeps_channel),
{}
};
static struct kunit_suite vc5_pv_muxing_bugs_test_suite = {
.name = "vc5-pv-muxing-bugs",
diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c
index ab551eb2da50..562f2d1f45a4 100644
--- a/drivers/gpu/drm/vc4/vc4_kms.c
+++ b/drivers/gpu/drm/vc4/vc4_kms.c
@@ -970,15 +970,27 @@ static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
drm_dbg(dev, "%s: Trying to find a channel.\n", crtc->name);
/* Nothing to do here, let's skip it */
if (old_crtc_state->enable == new_crtc_state->enable) {
- if (new_crtc_state->enable)
+ /*
+ * A CRTC that stays enabled keeps its channel. A
+ * duplicated state carries it over, but a state
+ * created through atomic_create_state() comes with
+ * the channel unset, so copy it from the current
+ * state to make sure the core clock check and the
+ * commit see the channel in use.
+ */
+ if (new_crtc_state->enable) {
+ new_vc4_crtc_state->assigned_channel =
+ old_vc4_crtc_state->assigned_channel;
+
drm_dbg(dev, "%s: Already enabled, reusing channel %d.\n",
crtc->name, new_vc4_crtc_state->assigned_channel);
- else
+ } else {
drm_dbg(dev, "%s: Disabled, ignoring.\n", crtc->name);
+ }
continue;
}
/* Muxing will need to be modified, mark it as such */
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 7/9] drm/msm/dpu1: Carry the mixers over to created CRTC states
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
` (5 preceding siblings ...)
2026-11-12 16:31 ` [PATCH v10 6/9] drm/vc4: Carry the HVS channel over to created CRTC states Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 8/9] drm/atomic-uapi: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 9/9] drm/vkms: Add driver-specific plane property for testing Maxime Ripard
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard, Rob Clark, Dmitry Baryshkov, Abhinav Kumar,
Jessica Zhang, freedreno
drm_atomic_commit_fill_with_defaults() builds its commits out of
states created through atomic_create_state() instead of duplicating
the current state. A created CRTC state comes with no mixers, while a
duplicated state carries the ones currently assigned, since the
duplication copies the whole state.
dpu_crtc_atomic_check() only goes through dpu_crtc_assign_resources()
when the mode, the connectors or the color management properties
changed, so a CRTC that keeps its configuration with a created state
runs with no mixers at all.
Copy the mixers of the current state over when the new state has
none, like a duplicated state would have carried them. This is a
no-op for duplicated states, which already have them.
Only do so for a CRTC that stays enabled. dpu_crtc_assign_resources()
returns right after dpu_rm_release() for a disabled one, leaving the
mixers it just released behind in the state it was given, and those
must not be handed over to a new state where the consumers that don't
check the CRTC enablement could reach hardware reassigned since.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Rob Clark <robin.clark@oss.qualcomm.com>
To: Dmitry Baryshkov <lumag@kernel.org>
Cc: Abhinav Kumar <abhinav.kumar@linux.dev>
Cc: Jessica Zhang <jesszhan0024@gmail.com>
Cc: freedreno@lists.freedesktop.org
---
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
index 77a2d1f6700d..a99f6761035f 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
@@ -1538,10 +1538,33 @@ static int dpu_crtc_atomic_check(struct drm_crtc *crtc,
if (crtc_state->mode_changed || crtc_state->connectors_changed ||
crtc_state->color_mgmt_changed) {
rc = dpu_crtc_assign_resources(crtc, crtc_state);
if (rc < 0)
return rc;
+ } else if (crtc_state->enable && !cstate->num_mixers) {
+ struct dpu_crtc_state *old_cstate =
+ to_dpu_crtc_state(drm_atomic_get_old_crtc_state(state,
+ crtc));
+
+ /*
+ * A CRTC that keeps its configuration keeps its mixers.
+ * A duplicated state carries them over, but a state
+ * created through atomic_create_state() comes with
+ * none, so copy them from the current state like a
+ * duplicated state would, to make sure the checks and
+ * the commit see the mixers in use.
+ *
+ * Only do so for a CRTC that stays enabled:
+ * dpu_crtc_assign_resources() returns right after
+ * dpu_rm_release() for a disabled one, leaving the mixers
+ * it just released behind in the state it was given.
+ */
+ if (old_cstate->num_mixers) {
+ cstate->num_mixers = old_cstate->num_mixers;
+ memcpy(cstate->mixers, old_cstate->mixers,
+ sizeof(cstate->mixers));
+ }
}
if (crtc_state->planes_changed || crtc_state->zpos_changed) {
if (dpu_use_virtual_planes)
rc = dpu_crtc_reassign_planes(crtc, crtc_state);
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 8/9] drm/atomic-uapi: Add DRM_MODE_ATOMIC_RESET flag
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
` (6 preceding siblings ...)
2026-11-12 16:31 ` [PATCH v10 7/9] drm/msm/dpu1: Carry the mixers " Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 9/9] drm/vkms: Add driver-specific plane property for testing Maxime Ripard
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard
From: Sebastian Wick <sebastian.wick@redhat.com>
Userspace currently has no atomic way to reset all KMS object states
to their defaults. To bring a display pipeline to a known state, a
compositor must explicitly set every property on every object, which
requires tracking which properties exist and what their defaults are.
Introduce DRM_MODE_ATOMIC_RESET (0x0800) which, when passed to the
atomic ioctl, fills the commit with default states for all KMS
objects before applying the properties supplied in the request.
Properties not explicitly included in the commit remain at their
defaults (CRTCs inactive, planes disabled, connectors unbound, and
so on).
The flag cannot be combined with DRM_MODE_PAGE_FLIP_ASYNC, since a
full state reset is incompatible with an async flip.
Since the commit contains every CRTC of the device or lease, including
the ones that are disabled before and after it, no page flip event is
attached to those: they can't report a flip, and drm_atomic_crtc_check()
would reject the whole commit. Such a CRTC doesn't count towards the
CRTCs that can signal either, so a commit that leaves them all disabled
while asking for an event is still rejected instead of waiting for an
event that would never come.
Signed-off-by: Sebastian Wick <sebastian.wick@redhat.com>
Co-developed-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic.c | 3 +++
drivers/gpu/drm/drm_atomic_uapi.c | 44 ++++++++++++++++++++++++++++++++++++---
drivers/gpu/drm/drm_ioctl.c | 5 +++++
include/uapi/drm/drm.h | 10 +++++++++
include/uapi/drm/drm_mode.h | 22 +++++++++++++++++++-
5 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 4fcce3bb0aa1..da3ac3367177 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1611,10 +1611,13 @@ EXPORT_SYMBOL(drm_atomic_get_new_bridge_state);
* the current state.
*
* Color operations are not checked because they always use
* drm_atomic_helper_colorop_create_state() and do not have a per-driver hook.
*
+ * This is used to report the %DRM_CAP_ATOMIC_RESET capability to userspace,
+ * and as a precondition in drm_atomic_commit_fill_with_defaults().
+ *
* Returns:
* True if all objects implement atomic_create_state, false otherwise.
*/
bool drm_atomic_implements_create_state(struct drm_device *dev)
{
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index 1eebcf6f5e05..fe6d8cc533bc 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -1438,24 +1438,43 @@ static int prepare_signaling(struct drm_device *dev,
if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY)
return 0;
for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
s32 __user *fence_ptr;
+ bool page_flip_event;
fence_ptr = get_out_fence_for_crtc(crtc_state->state, crtc);
- if (arg->flags & DRM_MODE_PAGE_FLIP_EVENT || fence_ptr) {
+ page_flip_event = arg->flags & DRM_MODE_PAGE_FLIP_EVENT;
+
+ /*
+ * A commit with the DRM_MODE_ATOMIC_RESET flag contains
+ * every CRTC of the device or lease, even the ones that are
+ * disabled before and after the commit. Those can't report
+ * a page flip, and drm_atomic_crtc_check() would reject the
+ * whole commit, so don't attach any event to them.
+ */
+ if (page_flip_event && !fence_ptr &&
+ (arg->flags & DRM_MODE_ATOMIC_RESET) &&
+ !crtc_state->active) {
+ struct drm_crtc_state *old_crtc_state;
+
+ old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
+ page_flip_event = old_crtc_state->active;
+ }
+
+ if (page_flip_event || fence_ptr) {
struct drm_pending_vblank_event *e;
e = create_vblank_event(crtc, arg->user_data);
if (!e)
return -ENOMEM;
crtc_state->event = e;
}
- if (arg->flags & DRM_MODE_PAGE_FLIP_EVENT) {
+ if (page_flip_event) {
struct drm_pending_vblank_event *e = crtc_state->event;
ret = drm_event_reserve_init(dev, file_priv, &e->base,
&e->event.base);
if (ret) {
@@ -1492,11 +1511,12 @@ static int prepare_signaling(struct drm_device *dev,
}
crtc_state->event->base.fence = fence;
}
- c++;
+ if (page_flip_event)
+ c++;
}
for_each_new_connector_in_state(state, conn, conn_state, i) {
struct drm_writeback_connector *wb_conn;
struct drm_out_fence_state *f;
@@ -1671,10 +1691,17 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
drm_dbg_atomic(dev,
"commit failed: page-flip event requested with test-only commit\n");
return -EINVAL;
}
+ if ((arg->flags & DRM_MODE_ATOMIC_RESET) &&
+ (arg->flags & DRM_MODE_PAGE_FLIP_ASYNC)) {
+ drm_dbg_atomic(dev,
+ "commit failed: reset cannot be combined with async flip\n");
+ return -EINVAL;
+ }
+
state = drm_atomic_commit_alloc(dev);
if (!state)
return -ENOMEM;
drm_modeset_acquire_init(&ctx, DRM_MODESET_ACQUIRE_INTERRUPTIBLE);
@@ -1686,10 +1713,21 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
copied_objs = 0;
copied_props = 0;
fence_state = NULL;
num_fences = 0;
+ if (arg->flags & DRM_MODE_ATOMIC_RESET) {
+ if (!drm_atomic_implements_create_state(dev)) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ ret = drm_atomic_commit_fill_with_defaults(file_priv, state);
+ if (ret)
+ goto out;
+ }
+
for (i = 0; i < arg->count_objs; i++) {
uint32_t obj_id, count_props;
struct drm_mode_object *obj;
if (get_user(obj_id, objs_ptr + copied_objs)) {
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 9039a39c4324..f666a13da6bf 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -31,10 +31,11 @@
#include <linux/export.h>
#include <linux/nospec.h>
#include <linux/pci.h>
#include <linux/uaccess.h>
+#include <drm/drm_atomic.h>
#include <drm/drm_auth.h>
#include <drm/drm_crtc.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_ioctl.h>
@@ -302,10 +303,14 @@ static int drm_getcap(struct drm_device *dev, void *data, struct drm_file *file_
break;
case DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP:
req->value = drm_core_check_feature(dev, DRIVER_ATOMIC) &&
dev->mode_config.async_page_flip;
break;
+ case DRM_CAP_ATOMIC_RESET:
+ req->value = drm_core_check_feature(dev, DRIVER_ATOMIC) &&
+ drm_atomic_implements_create_state(dev);
+ break;
default:
return -EINVAL;
}
return 0;
}
diff --git a/include/uapi/drm/drm.h b/include/uapi/drm/drm.h
index bc7ef7684099..b6e2f2edd122 100644
--- a/include/uapi/drm/drm.h
+++ b/include/uapi/drm/drm.h
@@ -793,10 +793,20 @@ struct drm_gem_change_handle {
* If set to 1, the driver supports &DRM_MODE_PAGE_FLIP_ASYNC for atomic
* commits.
*/
#define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15
+/**
+ * DRM_CAP_ATOMIC_RESET
+ *
+ * If set to 1, the driver supports the &DRM_MODE_ATOMIC_RESET flag in
+ * &DRM_IOCTL_MODE_ATOMIC commits. When supported, userspace can pass that
+ * flag to reset all KMS object states to their defaults before applying
+ * property changes.
+ */
+#define DRM_CAP_ATOMIC_RESET 0x16
+
/* DRM_IOCTL_GET_CAP ioctl argument type */
struct drm_get_cap {
__u64 capability;
__u64 value;
};
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index ed1a660a3dfd..571bb7c6804d 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -1326,10 +1326,29 @@ struct drm_mode_destroy_dumb {
* To the best of the driver's knowledge, visual artifacts are guaranteed to
* not appear when this flag is not set. Some sinks might display visual
* artifacts outside of the driver's control.
*/
#define DRM_MODE_ATOMIC_ALLOW_MODESET 0x0400
+/**
+ * DRM_MODE_ATOMIC_RESET
+ *
+ * Reset all KMS object states (CRTCs, planes, connectors, color operations)
+ * to their default values before applying the properties in this commit.
+ * Properties not explicitly included in the commit will remain at their
+ * defaults (CRTCs inactive, planes disabled, connectors unbound, etc.).
+ *
+ * CRTCs that are disabled before and after the commit won't send any
+ * &DRM_MODE_PAGE_FLIP_EVENT.
+ *
+ * On a lease, only the objects part of that lease are reset. Partial
+ * pipelines aren't supported: if one of the leased planes or connectors
+ * is bound to a CRTC that isn't leased, or if a leased CRTC drives a
+ * plane or a connector that isn't, the commit fails with -ENOENT.
+ *
+ * This flag cannot be combined with &DRM_MODE_PAGE_FLIP_ASYNC.
+ */
+#define DRM_MODE_ATOMIC_RESET 0x0800
/**
* DRM_MODE_ATOMIC_FLAGS
*
* Bitfield of flags accepted by the &DRM_IOCTL_MODE_ATOMIC IOCTL in
@@ -1338,11 +1357,12 @@ struct drm_mode_destroy_dumb {
#define DRM_MODE_ATOMIC_FLAGS (\
DRM_MODE_PAGE_FLIP_EVENT |\
DRM_MODE_PAGE_FLIP_ASYNC |\
DRM_MODE_ATOMIC_TEST_ONLY |\
DRM_MODE_ATOMIC_NONBLOCK |\
- DRM_MODE_ATOMIC_ALLOW_MODESET)
+ DRM_MODE_ATOMIC_ALLOW_MODESET |\
+ DRM_MODE_ATOMIC_RESET)
struct drm_mode_atomic {
__u32 flags;
__u32 count_objs;
__u64 objs_ptr;
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 9/9] drm/vkms: Add driver-specific plane property for testing
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
` (7 preceding siblings ...)
2026-11-12 16:31 ` [PATCH v10 8/9] drm/atomic-uapi: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
@ 2026-11-12 16:31 ` Maxime Ripard
8 siblings, 0 replies; 10+ messages in thread
From: Maxime Ripard @ 2026-11-12 16:31 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie,
Simona Vetter, Louis Chauvet, Haneen Mohammed, Melissa Wen
Cc: Daniel Stone, Harry Wentland, Jocelyn Falempe, Jonas Ådahl,
Michel Dänzer, Pekka Paalanen, Sebastian Wick, Simon Ser,
Victoria Brekenfeld, Xaver Hugl, dri-devel, linux-kernel,
Maxime Ripard, Oarora Etimis
The DRM_MODE_ATOMIC_RESET flag allows userspace to reset all object
states to their defaults before applying new property values. Testing
that this mechanism works correctly requires a property that the test
can set to a non-default value and then verify is properly reset.
Using a standard property for this is problematic: compositors like
Mutter enumerate and track all known properties, making it difficult
to isolate the reset behavior from the compositor's own property
management.
Add a "vkms-test-do-not-use-in-prod" range property to vkms planes.
The property is stored in vkms_plane_state, duplicated across state
copies, and initialized from its registered default in
atomic_create_state via drm_object_property_get_default_value().
Reviewed-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Oarora Etimis <oaroraetimis@gmail.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/vkms/vkms_drv.h | 3 ++
drivers/gpu/drm/vkms/vkms_plane.c | 63 +++++++++++++++++++++++++++++++++++++--
2 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
index dea6cad3f1b9..384616095b45 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.h
+++ b/drivers/gpu/drm/vkms/vkms_drv.h
@@ -144,20 +144,23 @@ struct conversion_matrix {
* @base: base plane state
* @frame_info: data required for composing computation
* @pixel_read_line: function to read a pixel line in this plane. The creator of a
* struct vkms_plane_state must ensure that this pointer is valid
* @conversion_matrix: matrix used for yuv formats to convert to rgb
+ * @vkms_test_do_not_use_value: driver-specific property for testing only
*/
struct vkms_plane_state {
struct drm_shadow_plane_state base;
struct vkms_frame_info frame_info;
pixel_read_line_t pixel_read_line;
struct conversion_matrix conversion_matrix;
+ unsigned int vkms_test_do_not_use_value;
};
struct vkms_plane {
struct drm_plane base;
+ struct drm_property *vkms_test_do_not_use_property;
};
struct vkms_color_lut {
struct drm_color_lut *base;
size_t lut_length;
diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c
index fd3d34dcf0fe..42513cb462ac 100644
--- a/drivers/gpu/drm/vkms/vkms_plane.c
+++ b/drivers/gpu/drm/vkms/vkms_plane.c
@@ -9,14 +9,18 @@
#include <drm/drm_colorop.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_print.h>
+#include <drm/drm_property.h>
#include "vkms_drv.h"
#include "vkms_formats.h"
+#define to_vkms_plane(target) \
+ container_of_const(target, struct vkms_plane, base)
+
static const u32 vkms_formats[] = {
DRM_FORMAT_ARGB8888,
DRM_FORMAT_ABGR8888,
DRM_FORMAT_BGRA8888,
DRM_FORMAT_RGBA8888,
@@ -52,16 +56,19 @@ static const u32 vkms_formats[] = {
};
static struct drm_plane_state *
vkms_plane_duplicate_state(struct drm_plane *plane)
{
+ struct vkms_plane_state *old_state = to_vkms_plane_state(plane->state);
struct vkms_plane_state *vkms_state;
vkms_state = kzalloc_obj(*vkms_state);
if (!vkms_state)
return NULL;
+ vkms_state->vkms_test_do_not_use_value = old_state->vkms_test_do_not_use_value;
+
__drm_gem_duplicate_shadow_plane_state(plane, &vkms_state->base);
return &vkms_state->base.base;
}
@@ -83,28 +90,69 @@ static void vkms_plane_destroy_state(struct drm_plane *plane,
kfree(vkms_state);
}
static struct drm_plane_state *vkms_plane_create_state(struct drm_plane *plane)
{
+ struct vkms_plane *vkms_plane = to_vkms_plane(plane);
struct vkms_plane_state *vkms_state;
+ u64 val;
vkms_state = kzalloc_obj(*vkms_state);
if (!vkms_state) {
DRM_ERROR("Cannot allocate vkms_plane_state\n");
return ERR_PTR(-ENOMEM);
}
__drm_gem_shadow_plane_state_init(plane, &vkms_state->base);
+ if (!drm_object_property_get_default_value(&plane->base,
+ vkms_plane->vkms_test_do_not_use_property,
+ &val))
+ vkms_state->vkms_test_do_not_use_value = val;
+
return &(&vkms_state->base)->base;
}
+static int vkms_plane_atomic_set_property(struct drm_plane *plane,
+ struct drm_plane_state *state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ struct vkms_plane *vkms_plane = to_vkms_plane(plane);
+ struct vkms_plane_state *vkms_state = to_vkms_plane_state(state);
+
+ if (property == vkms_plane->vkms_test_do_not_use_property) {
+ vkms_state->vkms_test_do_not_use_value = val;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int vkms_plane_atomic_get_property(struct drm_plane *plane,
+ const struct drm_plane_state *state,
+ struct drm_property *property,
+ uint64_t *val)
+{
+ const struct vkms_plane_state *vkms_state = to_vkms_plane_state(state);
+ const struct vkms_plane *vkms_plane = to_vkms_plane(plane);
+
+ if (property == vkms_plane->vkms_test_do_not_use_property) {
+ *val = vkms_state->vkms_test_do_not_use_value;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
static const struct drm_plane_funcs vkms_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
- .atomic_create_state = vkms_plane_create_state,
- .atomic_duplicate_state = vkms_plane_duplicate_state,
+ .atomic_set_property = vkms_plane_atomic_set_property,
+ .atomic_get_property = vkms_plane_atomic_get_property,
+ .atomic_create_state = vkms_plane_create_state,
+ .atomic_duplicate_state = vkms_plane_duplicate_state,
.atomic_destroy_state = vkms_plane_destroy_state,
};
static void vkms_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
@@ -281,10 +329,21 @@ struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) |
BIT(DRM_COLOR_YCBCR_FULL_RANGE),
DRM_COLOR_YCBCR_BT601,
DRM_COLOR_YCBCR_FULL_RANGE);
+ plane->vkms_test_do_not_use_property =
+ drm_property_create_range(dev, 0,
+ "vkms-test-do-not-use-in-prod",
+ 0, U32_MAX);
+ if (!plane->vkms_test_do_not_use_property)
+ return ERR_PTR(-ENOMEM);
+
+ drm_object_attach_property(&plane->base.base,
+ plane->vkms_test_do_not_use_property,
+ 0);
+
if (vkms_config_plane_get_default_pipeline(plane_cfg))
vkms_initialize_colorops(&plane->base);
return plane;
}
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-12 16:32 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-12 16:31 [PATCH v10 0/9] drm: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 1/9] drm/amd/display: Fix planes atomic_create_state implementation Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 2/9] drm/atomic: Add drm_atomic_implements_create_state() helper Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 3/9] drm/atomic: Allow filling a commit with pristine object states Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 4/9] drm/tests: atomic: Add tests for drm_atomic_commit_fill_with_defaults() Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 5/9] drm/amd/display: Take over the current stream and dc_state Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 6/9] drm/vc4: Carry the HVS channel over to created CRTC states Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 7/9] drm/msm/dpu1: Carry the mixers " Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 8/9] drm/atomic-uapi: Add DRM_MODE_ATOMIC_RESET flag Maxime Ripard
2026-11-12 16:31 ` [PATCH v10 9/9] drm/vkms: Add driver-specific plane property for testing Maxime Ripard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®