All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v7 00/34] drm: Implement state readout support
@ 2026-10-29  7:33 Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 01/34] drm/bridge-helper: Don't unlock the connection mutex after a backoff Maxime Ripard
                   ` (33 more replies)
  0 siblings, 34 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard, Laurent Pinchart,
	Jocelyn Falempe, Simona Vetter

Hi,

Here's a series that implement what i915 calls "fastboot", ie,
initializing the initial KMS state from the hardware state at boot, to
skip the first modeset if the firmware already set up the display.

This series creates the infrastructure in KMS to create that state by
relying on driver specific hooks. It also implements some infrastructure
to check during non-blocking commits that the readout helpers work
properly by reading out the state that was just committed and comparing
it to what was supposed to be committed.

This relies on another set of driver hooks to compare the entities
states, with helpers providing the default implementation.

It then implements the readout support in the TIDSS driver, and was
tested with the SK-AM62 board. This board in particular is pretty
interesting, since it relies on an DPI to HDMI bridge, and uses the
drm_bridge_connector infrastructure.

So the readout works with the current state of the art on embedded-ish
platforms.

Unlike the earlier RFCs, it now works fine: the state is picked up
properly, doesn't trigger a modeset if what was programmed is the one
the first modeset tries to pick as well, will switch properly if it
isn't, etc.

Let me know what you think,
Maxime

Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
Changes in v7:
- Add kunit tests for state readout handling
- Fix kunit test breakages
- Fix sashiko reviews
- Rename drm_atomic_sro.c to drm_atomic_state_readout.c
- Improve documentation
- Fix comparisons macros and functions
- Improve tidss readout implementation
- When committing after a commit, only readout and compare the objects
  part of that commit (and we have the locks for)
- Add kunit tests for the read-out logic
- Improve tidss read-out implementation
- Improve si902x read-out implementation
- Link to v6: https://lore.kernel.org/r/20261015-drm-state-readout-v6-0-6cc02cd10998@kernel.org

Changes in v6:
- Fix private_obj_init error handling
- Fix bridge name lifetime
- Show the bridge name in print_state
- Rebase on current drm-misc-next
- Link to v5: https://lore.kernel.org/r/20260907-drm-state-readout-v5-0-5fa1ac7a5148@kernel.org

Changes in v5:
- Implement readout for bridge/display-connector
- Implement readout for tidss encoders
- Fix sii902 readout implementation
- Add atomic_print_state implementation to tidss crtc
- Rebase over latest changes. In particular, the CRTC conversion to
  atomic_create_state and the introduction of crtc's hw_reset made me
  create a new helper and hook to trigger a device-wide hw_reset
- Add a bunch of NULL pointer checks everywhere
- Fix compare macros calls to __stringify
- Create documentation section for sro
- Fix error path in drm_atomic_sro_state_init
- Improve bisectability
- Link to v4: https://lore.kernel.org/r/20260629-drm-state-readout-v4-0-5966657980ed@kernel.org

Changes in v4:
- Dropped the HACK patch that went into the v3 resend
- Hopefully the kernel.org smtp works this time
- Link to v3: https://lore.kernel.org/r/20260626-drm-state-readout-v3-0-d5825e3299a8@kernel.org

Changes in v3:
- Rebase on top of the current drm-misc-next, with rename of
  drm_atomic_state and the various preliminary series merged.
- Use drm_printf_indent when possible
- Change the access mode for the atomic_readout parameter from 000 to
  400
- Use drm_drv_uses_atomic_modeset() instead of drm_core_check_feature()
- Introduce a hw_reset hook to deal with device hardware reset when
  needed (ie, SRO fails or is disabled)
- Improve mode flags readout
- Fix plane alpha readout
- Add sii902x state readout

- Link to v2: https://lore.kernel.org/r/20260423-drm-state-readout-v2-0-8549f87cb978@kernel.org

Changes in v2:
- Get rid of patches already applied
- Rebase on top of the current atomic_create_state work
- Use the SRO prefix everywhere
- Create our own states container structure instead of trying to plumb
  it into a drm_atomic_state
- Make a lot more use of helpers
- Add a hook to enable the hardware resources when the readout state
  becomes active
- Move all the tidss readout code into tidss_dispc.c
- Write documentation
- Add drm_private_obj and drm_bridge name for easier debugging
- Add drm_private_obj_is_bridge()
- Link to v1: https://lore.kernel.org/r/20250902-drm-state-readout-v1-0-14ad5315da3f@kernel.org

---
Maxime Ripard (34):
      drm/bridge-helper: Don't unlock the connection mutex after a backoff
      drm/tests: hdmi-state-helper: Handle EDEADLK in the 420-only test
      drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test
      drm/atomic: Fix private obj error handling
      drm/atomic: Add drm_private_obj name
      drm/bridge: Switch print_state to use bridge name
      drm/bridge: Add drm_private_obj_is_bridge()
      drm/fb-dma-helper: Handle framebuffers without a GEM object
      drm/atomic: Export drm_atomic_*_print_state
      drm/atomic_sro: Create drm_atomic_sro_state container
      drm/atomic_sro: Create kernel parameter to force or disable readout
      drm/atomic_sro: Add atomic state readout infrastructure
      drm/atomic_sro: Add function to install state into drm objects
      drm/atomic_sro: Create documentation
      drm/bridge: Handle bridges with hardware state readout
      drm/mode-config: Read out hardware state in drm_mode_config_create_state
      drm/atomic_sro: Provide helpers to implement hardware state readout
      drm/mode-config: Introduce hw_reset hook
      drm/modeset-helper: Add drm_mode_config_helper_hw_reset()
      drm/atomic_helper: Compare actual and readout states once the commit is done
      drm/atomic-state-helper: Provide comparison macros
      drm/atomic-state-helper: Provide atomic_compare_state helpers
      drm/atomic-helper: Document State Read-Out as a state initialization approach
      drm/tests: kunit: Add a drm_printer helper
      drm/tests: Add tests for the atomic state readout
      drm/encoder: Create atomic_sro_get_current_crtc hook
      drm/bridge: display-connector: Implement readout support
      drm/bridge_connector: Implement hw readout for connector
      drm/tidss: crtc: Add atomic_print_state implementation
      drm/tidss: dispc: Improve mode checking logs
      drm/tidss: Implement hw_reset
      drm/tidss: Implement readout support
      drm/tidss: encoder: Implement atomic_sro_get_current_crtc
      drm/bridge: sii902x: Implement hw state readout

 Documentation/gpu/drm-kms-helpers.rst              |   26 +
 drivers/gpu/drm/Makefile                           |    2 +
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c  |    1 +
 .../drm/arm/display/komeda/komeda_private_obj.c    |    8 +
 drivers/gpu/drm/bridge/display-connector.c         |   69 +
 drivers/gpu/drm/bridge/sii902x.c                   |   91 +-
 drivers/gpu/drm/display/drm_bridge_connector.c     |   36 +
 drivers/gpu/drm/display/drm_dp_mst_topology.c      |    2 +-
 drivers/gpu/drm/display/drm_dp_tunnel.c            |    1 +
 drivers/gpu/drm/drm_atomic.c                       |   57 +-
 drivers/gpu/drm/drm_atomic_helper.c                |   45 +
 drivers/gpu/drm/drm_atomic_state_helper.c          |   20 +-
 drivers/gpu/drm/drm_atomic_state_readout.c         |  974 +++++++++++++
 drivers/gpu/drm/drm_atomic_state_readout_helper.c  |  974 +++++++++++++
 drivers/gpu/drm/drm_bridge.c                       |  131 +-
 drivers/gpu/drm/drm_bridge_helper.c                |   35 +-
 drivers/gpu/drm/drm_fb_dma_helper.c                |    8 +
 drivers/gpu/drm/drm_internal.h                     |   13 +
 drivers/gpu/drm/drm_mode_config.c                  |   76 +-
 drivers/gpu/drm/drm_modeset_helper.c               |   31 +
 drivers/gpu/drm/ingenic/ingenic-drm-drv.c          |    1 +
 drivers/gpu/drm/ingenic/ingenic-ipu.c              |    1 +
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c            |    1 +
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c           |    1 +
 drivers/gpu/drm/omapdrm/omap_drv.c                 |    1 +
 drivers/gpu/drm/tegra/hub.c                        |    1 +
 drivers/gpu/drm/tests/Makefile                     |    1 +
 .../gpu/drm/tests/drm_atomic_state_readout_test.c  | 1501 ++++++++++++++++++++
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c |   30 +-
 drivers/gpu/drm/tests/drm_kunit_helpers.c          |   78 +
 drivers/gpu/drm/tidss/tidss_crtc.c                 |  101 ++
 drivers/gpu/drm/tidss/tidss_dispc.c                |  371 ++++-
 drivers/gpu/drm/tidss/tidss_dispc.h                |   19 +
 drivers/gpu/drm/tidss/tidss_encoder.c              |   57 +
 drivers/gpu/drm/tidss/tidss_kms.c                  |   20 +-
 drivers/gpu/drm/tidss/tidss_plane.c                |  178 +++
 drivers/gpu/drm/vc4/vc4_kms.c                      |    3 +
 include/drm/drm_atomic.h                           |   78 +-
 include/drm/drm_atomic_state_readout.h             |   59 +
 include/drm/drm_atomic_state_readout_helper.h      |  283 ++++
 include/drm/drm_bridge.h                           |   97 ++
 include/drm/drm_connector.h                        |   74 +
 include/drm/drm_crtc.h                             |   74 +
 include/drm/drm_encoder.h                          |   18 +
 include/drm/drm_kunit_helpers.h                    |    4 +
 include/drm/drm_mode_config.h                      |   54 +
 include/drm/drm_modeset_helper.h                   |    1 +
 include/drm/drm_modeset_helper_vtables.h           |   34 +
 include/drm/drm_plane.h                            |   74 +
 49 files changed, 5749 insertions(+), 66 deletions(-)
---
base-commit: 128013e9fec562672562dffd4ad468492f5915c8
change-id: 20250730-drm-state-readout-108f089c1c30

Best regards,
-- 
Maxime Ripard <mripard@kernel.org>


^ permalink raw reply	[flat|nested] 50+ messages in thread

* [PATCH v7 01/34] drm/bridge-helper: Don't unlock the connection mutex after a backoff
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:51   ` sashiko-bot
  2026-10-29  7:33 ` [PATCH v7 02/34] drm/tests: hdmi-state-helper: Handle EDEADLK in the 420-only test Maxime Ripard
                   ` (32 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

drm_bridge_helper_reset_crtc() takes the connection mutex using the
acquire context provided by the caller, and then relies on
drm_atomic_get_connector_for_encoder() and
drm_atomic_helper_reset_crtc() that both use the same context.

If either of those functions detects a deadlock, it backs off and drops
every lock held by the context, including the connection mutex taken by
drm_bridge_helper_reset_crtc() itself. The function however still
unlocked the connection mutex unconditionally on its way out, unlocking
a mutex it wasn't holding anymore.

The callers already have to release the context locks through
drm_modeset_drop_locks() and to handle the -EDEADLK error, so we can
leave the connection mutex release to them. We still need to take it
back after drm_atomic_get_connector_for_encoder() returns, since it may
have backed off, before looking at the connector state.

Fixes: 56ae62124177 ("drm/bridge: Add helper to reset bridge pipeline")
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_bridge_helper.c | 35 +++++++++++++++++++++++------------
 1 file changed, 23 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/drm_bridge_helper.c b/drivers/gpu/drm/drm_bridge_helper.c
index 0a3c8fee66b3..6998e7c41d30 100644
--- a/drivers/gpu/drm/drm_bridge_helper.c
+++ b/drivers/gpu/drm/drm_bridge_helper.c
@@ -20,4 +20,8 @@
  * apply.
  *
+ * All the locks taken by this function use @ctx: the caller is
+ * responsible for releasing them with drm_modeset_drop_locks(), and
+ * for handling an -EDEADLK error with drm_modeset_backoff().
+ *
  * Returns:
  *
@@ -34,4 +38,9 @@ int drm_bridge_helper_reset_crtc(struct drm_bridge *bridge,
 	int ret;
 
+	/*
+	 * Every lock taken with @ctx is released either by a backoff or
+	 * by the caller through drm_modeset_drop_locks(), so we must not
+	 * unlock the connection mutex ourselves.
+	 */
 	ret = drm_modeset_lock(&dev->mode_config.connection_mutex, ctx);
 	if (ret)
@@ -39,20 +48,22 @@ int drm_bridge_helper_reset_crtc(struct drm_bridge *bridge,
 
 	connector = drm_atomic_get_connector_for_encoder(encoder, ctx);
-	if (IS_ERR(connector)) {
-		ret = PTR_ERR(connector);
-		goto out;
-	}
+	if (IS_ERR(connector))
+		return PTR_ERR(connector);
 
-	if (!connector->state) {
-		ret = -EINVAL;
-		goto out;
-	}
+	/*
+	 * drm_atomic_get_connector_for_encoder() may have backed off,
+	 * releasing the connection mutex in the process. Take it back
+	 * before looking at the connector state.
+	 */
+	ret = drm_modeset_lock(&dev->mode_config.connection_mutex, ctx);
+	if (ret)
+		return ret;
+
+	if (!connector->state)
+		return -EINVAL;
 
 	crtc = connector->state->crtc;
-	ret = drm_atomic_helper_reset_crtc(crtc, ctx);
 
-out:
-	drm_modeset_unlock(&dev->mode_config.connection_mutex);
-	return ret;
+	return drm_atomic_helper_reset_crtc(crtc, ctx);
 }
 EXPORT_SYMBOL(drm_bridge_helper_reset_crtc);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 02/34] drm/tests: hdmi-state-helper: Handle EDEADLK in the 420-only test
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 01/34] drm/bridge-helper: Don't unlock the connection mutex after a backoff Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 03/34] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test Maxime Ripard
                   ` (31 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The atomic state API is allowed to return -EDEADLK whenever the
wound/wait mutex code detects a deadlock, and the caller is then
expected to back off and restart the whole sequence.

drm_test_check_hdmi_color_format_420_only() wasn't doing any of that,
and reported a spurious failure when drm_atomic_set_crtc_for_connector()
returned -EDEADLK. Since the ticket ordering depends on the execution,
this could only be observed occasionally: the structurally identical
drm_test_check_hdmi_color_format() test hasn't been observed to fail so
far.

Add the usual backoff and restart pattern.

Fixes: f7235a4805a4 ("drm/tests: hdmi: Add tests for the color_format property")
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
index 99a0fa479704..63d6f8bf1396 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -2403,4 +2403,5 @@ static void drm_test_check_hdmi_color_format_420_only(struct kunit *test)
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
 
+retry:
 	conn_state = drm_atomic_get_connector_state(state, &priv->connector);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
@@ -2409,7 +2410,11 @@ static void drm_test_check_hdmi_color_format_420_only(struct kunit *test)
 
 	ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc);
+	if (ret == -EDEADLK)
+		goto backoff;
 	KUNIT_ASSERT_EQ(test, ret, 0);
 
 	crtc_state = drm_atomic_get_crtc_state(state, priv->crtc);
+	if (IS_ERR(crtc_state) && PTR_ERR(crtc_state) == -EDEADLK)
+		goto backoff;
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
 
@@ -2421,4 +2426,6 @@ static void drm_test_check_hdmi_color_format_420_only(struct kunit *test)
 
 	ret = drm_atomic_check_only(state);
+	if (ret == -EDEADLK)
+		goto backoff;
 	KUNIT_EXPECT_EQ(test, ret, param->expected_ret);
 	if (!param->expected_ret)
@@ -2427,5 +2434,12 @@ static void drm_test_check_hdmi_color_format_420_only(struct kunit *test)
 	drm_modeset_drop_locks(&ctx);
 	drm_modeset_acquire_fini(&ctx);
-};
+
+	return;
+
+backoff:
+	drm_atomic_commit_clear(state);
+	drm_modeset_backoff(&ctx);
+	goto retry;
+}
 
 static const struct color_format_test_param hdmi_color_format_420_only_params[] = {

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 03/34] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 01/34] drm/bridge-helper: Don't unlock the connection mutex after a backoff Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 02/34] drm/tests: hdmi-state-helper: Handle EDEADLK in the 420-only test Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 04/34] drm/atomic: Fix private obj error handling Maxime Ripard
                   ` (30 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The atomic state API is allowed to return -EDEADLK whenever the
wound/wait mutex code detects a deadlock, and the caller is then
expected to back off and restart the whole sequence.

drm_test_check_hdmi_color_format() wasn't doing any of that, and
reported a spurious failure when drm_atomic_set_crtc_for_connector()
returned -EDEADLK, just like its 420-only counterpart fixed earlier.
Since the ticket ordering depends on the execution, this could only be
observed occasionally.

The assertion also aborted before the locking context could be
released, leaving connection_mutex held when the device was released,
which drm_modeset_lock_fini() then reported through
drm_mode_config_cleanup().

Add the usual backoff and restart pattern, and remove a stray
semicolon at the end of drm_test_check_hdmi_color_format_420_only().

Fixes: f7235a4805a4 ("drm/tests: hdmi: Add tests for the color_format property")
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
index 63d6f8bf1396..a4dad1f5bfc0 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -2300,4 +2300,5 @@ static void drm_test_check_hdmi_color_format(struct kunit *test)
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
 
+retry:
 	conn_state = drm_atomic_get_connector_state(state, &priv->connector);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
@@ -2306,7 +2307,11 @@ static void drm_test_check_hdmi_color_format(struct kunit *test)
 
 	ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc);
+	if (ret == -EDEADLK)
+		goto backoff;
 	KUNIT_ASSERT_EQ(test, ret, 0);
 
 	crtc_state = drm_atomic_get_crtc_state(state, priv->crtc);
+	if (IS_ERR(crtc_state) && PTR_ERR(crtc_state) == -EDEADLK)
+		goto backoff;
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
 
@@ -2318,4 +2323,6 @@ static void drm_test_check_hdmi_color_format(struct kunit *test)
 
 	ret = drm_atomic_check_only(state);
+	if (ret == -EDEADLK)
+		goto backoff;
 	KUNIT_EXPECT_EQ(test, ret, param->expected_ret);
 	KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, param->expected);
@@ -2323,4 +2330,11 @@ static void drm_test_check_hdmi_color_format(struct kunit *test)
 	drm_modeset_drop_locks(&ctx);
 	drm_modeset_acquire_fini(&ctx);
+
+	return;
+
+backoff:
+	drm_atomic_commit_clear(state);
+	drm_modeset_backoff(&ctx);
+	goto retry;
 }
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 04/34] drm/atomic: Fix private obj error handling
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (2 preceding siblings ...)
  2026-10-29  7:33 ` [PATCH v7 03/34] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:57   ` sashiko-bot
  2026-10-29  7:33 ` [PATCH v7 05/34] drm/atomic: Add drm_private_obj name Maxime Ripard
                   ` (29 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard, Laurent Pinchart

drm_atomic_private_obj_init() initializes the modeset lock and adds the
object to the privobj_list before calling atomic_create_state().

However, if atomic_create_state() fails, the function returns the error
directly without removing the object from the list or cleaning up the
lock. This leaves a dangling entry in privobj_list and a leaked lock.

Fix this by unwinding both operations in an error path.

Fixes: b962a12050a3 ("drm/atomic: integrate modeset lock with private objects")
Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic.c | 29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 9bb761eda59f..8f9b17395b90 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1030,6 +1030,15 @@ int drm_atomic_private_obj_init(struct drm_device *dev,
 {
 	struct drm_private_state *state;
+	int ret;
+
 	memset(obj, 0, sizeof(*obj));
 
+	/*
+	 * Keep the list node in a state drm_atomic_private_obj_fini()
+	 * can safely handle if we fail before adding it to the private
+	 * objects list.
+	 */
+	INIT_LIST_HEAD(&obj->head);
+
 	drm_modeset_lock_init(&obj->lock);
 
@@ -1039,10 +1048,17 @@ int drm_atomic_private_obj_init(struct drm_device *dev,
 
 	state = obj->funcs->atomic_create_state(obj);
-	if (IS_ERR(state))
-		return PTR_ERR(state);
+	if (IS_ERR(state)) {
+		ret = PTR_ERR(state);
+		goto err_list_del;
+	}
 
 	obj->state = state;
 
 	return 0;
+
+err_list_del:
+	list_del_init(&obj->head);
+	drm_modeset_lock_fini(&obj->lock);
+	return ret;
 }
 EXPORT_SYMBOL(drm_atomic_private_obj_init);
@@ -1057,4 +1073,13 @@ void
 drm_atomic_private_obj_fini(struct drm_private_obj *obj)
 {
+	/*
+	 * Some callers ignore drm_atomic_private_obj_init() failures
+	 * and unconditionally register a teardown action that calls
+	 * this function. Make it idempotent so a failed init followed
+	 * by fini does not cause a double list_del.
+	 */
+	if (list_empty(&obj->head))
+		return;
+
 	list_del(&obj->head);
 	obj->funcs->atomic_destroy_state(obj, obj->state);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 05/34] drm/atomic: Add drm_private_obj name
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (3 preceding siblings ...)
  2026-10-29  7:33 ` [PATCH v7 04/34] drm/atomic: Fix private obj error handling Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:56   ` sashiko-bot
  2026-10-29  7:33 ` [PATCH v7 06/34] drm/bridge: Switch print_state to use bridge name Maxime Ripard
                   ` (28 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

drm_private_obj does not carry a human-readable name, which makes
debug messages hard to interpret when multiple private objects are
registered on the same device.

Add a name field to drm_private_obj, passed through
drm_atomic_private_obj_init() and freed in
drm_atomic_private_obj_fini(). Update the existing callers to pass a
name.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c       |  1 +
 drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c |  8 ++++++++
 drivers/gpu/drm/display/drm_dp_mst_topology.c           |  2 +-
 drivers/gpu/drm/display/drm_dp_tunnel.c                 |  1 +
 drivers/gpu/drm/drm_atomic.c                            | 12 ++++++++++++
 drivers/gpu/drm/drm_bridge.c                            | 10 +++++++++-
 drivers/gpu/drm/ingenic/ingenic-drm-drv.c               |  1 +
 drivers/gpu/drm/ingenic/ingenic-ipu.c                   |  1 +
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c                 |  1 +
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c                |  1 +
 drivers/gpu/drm/omapdrm/omap_drv.c                      |  1 +
 drivers/gpu/drm/tegra/hub.c                             |  1 +
 drivers/gpu/drm/vc4/vc4_kms.c                           |  3 +++
 include/drm/drm_atomic.h                                |  7 ++++++-
 include/drm/drm_bridge.h                                |  2 ++
 15 files changed, 49 insertions(+), 3 deletions(-)

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 5e106b65034d..32b582f31f0e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -2314,4 +2314,5 @@ STATIC_IFN_KUNIT int amdgpu_dm_mode_config_init(struct amdgpu_device *adev)
 	drm_atomic_private_obj_init(adev_to_drm(adev),
 				    &adev->dm.atomic_obj,
+				    "amdgpu_dc_state",
 				    &dm_atomic_state_funcs);
 
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c b/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c
index 77b3f361091f..8a3c9fcf3844 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c
@@ -67,4 +67,5 @@ static int komeda_layer_obj_add(struct komeda_kms_dev *kms,
 {
 	drm_atomic_private_obj_init(&kms->base, &layer->base.obj,
+				    "komeda_layer",
 				    &komeda_layer_obj_funcs);
 	return 0;
@@ -120,4 +121,5 @@ static int komeda_scaler_obj_add(struct komeda_kms_dev *kms,
 	drm_atomic_private_obj_init(&kms->base,
 				    &scaler->base.obj,
+				    "komeda_scaler",
 				    &komeda_scaler_obj_funcs);
 	return 0;
@@ -172,4 +174,5 @@ static int komeda_compiz_obj_add(struct komeda_kms_dev *kms,
 {
 	drm_atomic_private_obj_init(&kms->base, &compiz->base.obj,
+				    "komeda_compiz",
 				    &komeda_compiz_obj_funcs);
 
@@ -226,4 +229,5 @@ static int komeda_splitter_obj_add(struct komeda_kms_dev *kms,
 	drm_atomic_private_obj_init(&kms->base,
 				    &splitter->base.obj,
+				    "komeda_splitter",
 				    &komeda_splitter_obj_funcs);
 
@@ -279,4 +283,5 @@ static int komeda_merger_obj_add(struct komeda_kms_dev *kms,
 	drm_atomic_private_obj_init(&kms->base,
 				    &merger->base.obj,
+				    "komeda_merger",
 				    &komeda_merger_obj_funcs);
 
@@ -332,4 +337,5 @@ static int komeda_improc_obj_add(struct komeda_kms_dev *kms,
 {
 	drm_atomic_private_obj_init(&kms->base, &improc->base.obj,
+				    "komeda_improc",
 				    &komeda_improc_obj_funcs);
 
@@ -385,4 +391,5 @@ static int komeda_timing_ctrlr_obj_add(struct komeda_kms_dev *kms,
 {
 	drm_atomic_private_obj_init(&kms->base, &ctrlr->base.obj,
+				    "komeda_timing_ctrlr",
 				    &komeda_timing_ctrlr_obj_funcs);
 
@@ -439,4 +446,5 @@ static int komeda_pipeline_obj_add(struct komeda_kms_dev *kms,
 {
 	drm_atomic_private_obj_init(&kms->base, &pipe->obj,
+				    "komeda_pipeline",
 				    &komeda_pipeline_obj_funcs);
 
diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c
index b7fce0bf8acc..c7c3f6b0cf76 100644
--- a/drivers/gpu/drm/display/drm_dp_mst_topology.c
+++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c
@@ -5775,5 +5775,5 @@ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr,
 	mgr->conn_base_id = conn_base_id;
 
-	drm_atomic_private_obj_init(dev, &mgr->base,
+	drm_atomic_private_obj_init(dev, &mgr->base, "drm_dp_mst_topology",
 				    &drm_dp_mst_topology_state_funcs);
 
diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c
index d29ad4116e64..391d643793eb 100644
--- a/drivers/gpu/drm/display/drm_dp_tunnel.c
+++ b/drivers/gpu/drm/display/drm_dp_tunnel.c
@@ -1733,4 +1733,5 @@ static bool init_group(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_tunnel_group
 
 	drm_atomic_private_obj_init(mgr->dev, &group->base,
+				    group->name,
 				    &tunnel_group_funcs);
 
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 8f9b17395b90..039be737fd07 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1016,4 +1016,5 @@ static void drm_atomic_plane_print_state(struct drm_printer *p,
  * @dev: DRM device this object will be attached to
  * @obj: private object
+ * @name: human-readable name for debug messages
  * @funcs: pointer to the struct of function pointers that identify the object
  * type
@@ -1027,4 +1028,5 @@ static void drm_atomic_plane_print_state(struct drm_printer *p,
 int drm_atomic_private_obj_init(struct drm_device *dev,
 				struct drm_private_obj *obj,
+				const char *name,
 				const struct drm_private_state_funcs *funcs)
 {
@@ -1045,4 +1047,10 @@ int drm_atomic_private_obj_init(struct drm_device *dev,
 	obj->dev = dev;
 	obj->funcs = funcs;
+	obj->name = kstrdup(name, GFP_KERNEL);
+	if (!obj->name) {
+		ret = -ENOMEM;
+		goto err_lock_fini;
+	}
+
 	list_add_tail(&obj->head, &dev->mode_config.privobj_list);
 
@@ -1059,4 +1067,7 @@ int drm_atomic_private_obj_init(struct drm_device *dev,
 err_list_del:
 	list_del_init(&obj->head);
+	kfree(obj->name);
+	obj->name = NULL;
+err_lock_fini:
 	drm_modeset_lock_fini(&obj->lock);
 	return ret;
@@ -1084,4 +1095,5 @@ drm_atomic_private_obj_fini(struct drm_private_obj *obj)
 	list_del(&obj->head);
 	obj->funcs->atomic_destroy_state(obj, obj->state);
+	kfree(obj->name);
 	drm_modeset_lock_fini(&obj->lock);
 }
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index e7f9e6d58927..ac65ac941691 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -318,4 +318,5 @@ static void __drm_bridge_free(struct kref *kref)
 	drm_bridge_put(bridge->next_bridge);
 
+	kfree(bridge->name);
 	kfree(bridge->container);
 }
@@ -419,4 +420,11 @@ void *__devm_drm_bridge_alloc(struct device *dev, size_t size, size_t offset,
 
 	bridge = container + offset;
+
+	bridge->name = kasprintf(GFP_KERNEL, "bridge-%s", dev_name(dev));
+	if (!bridge->name) {
+		kfree(container);
+		return ERR_PTR(-ENOMEM);
+	}
+
 	INIT_LIST_HEAD(&bridge->list);
 	bridge->container = container;
@@ -650,5 +658,5 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
 	}
 
-	drm_atomic_private_obj_init(bridge->dev, &bridge->base,
+	drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
 				    &drm_bridge_priv_state_funcs);
 
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
index fff1ba598e96..1afac110bc28 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
@@ -1405,4 +1405,5 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
 
 	drm_atomic_private_obj_init(drm, &priv->private_obj,
+				    "ingenic_drm",
 				    &ingenic_drm_private_state_funcs);
 
diff --git a/drivers/gpu/drm/ingenic/ingenic-ipu.c b/drivers/gpu/drm/ingenic/ingenic-ipu.c
index bf24882e8d9f..795f6db5ce59 100644
--- a/drivers/gpu/drm/ingenic/ingenic-ipu.c
+++ b/drivers/gpu/drm/ingenic/ingenic-ipu.c
@@ -903,4 +903,5 @@ static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
 
 	drm_atomic_private_obj_init(drm, &ipu->private_obj,
+				    "ingenic_ipu_state",
 				    &ingenic_ipu_private_state_funcs);
 
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
index da3556eb6ecc..fb3a0c847a9e 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -1156,4 +1156,5 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
 
 	drm_atomic_private_obj_init(dpu_kms->dev, &dpu_kms->global_state,
+				    "dpu_kms_global_state",
 				    &dpu_kms_global_state_funcs);
 
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
index 3934cd060b27..b53f59f25fb0 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
@@ -713,4 +713,5 @@ static int mdp5_init(struct platform_device *pdev, struct drm_device *dev)
 
 	drm_atomic_private_obj_init(mdp5_kms->dev, &mdp5_kms->glob_state,
+				    "mdp5_global_state",
 				    &mdp5_global_state_funcs);
 
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index db216c888a8f..3f66cd66443f 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -300,4 +300,5 @@ static int omap_global_obj_init(struct drm_device *dev)
 
 	drm_atomic_private_obj_init(dev, &priv->glob_obj,
+				    "omap_global",
 				    &omap_global_state_funcs);
 	return 0;
diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c
index 448f49f3a7d7..854b671dfec8 100644
--- a/drivers/gpu/drm/tegra/hub.c
+++ b/drivers/gpu/drm/tegra/hub.c
@@ -961,4 +961,5 @@ static int tegra_display_hub_init(struct host1x_client *client)
 
 	drm_atomic_private_obj_init(drm, &hub->base,
+				    "tegra_display_hub",
 				    &tegra_display_hub_state_funcs);
 
diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c
index ab551eb2da50..e43d59cff4df 100644
--- a/drivers/gpu/drm/vc4/vc4_kms.c
+++ b/drivers/gpu/drm/vc4/vc4_kms.c
@@ -118,4 +118,5 @@ static int vc4_ctm_obj_init(struct vc4_dev *vc4)
 
 	drm_atomic_private_obj_init(&vc4->base, &vc4->ctm_manager,
+				    "vc4_ctm",
 				    &vc4_ctm_state_funcs);
 
@@ -758,4 +759,5 @@ static int vc4_load_tracker_obj_init(struct vc4_dev *vc4)
 {
 	drm_atomic_private_obj_init(&vc4->base, &vc4->load_tracker,
+				    "vc4_load_tracker",
 				    &vc4_load_tracker_state_funcs);
 
@@ -849,4 +851,5 @@ static int vc4_hvs_channels_obj_init(struct vc4_dev *vc4)
 {
 	drm_atomic_private_obj_init(&vc4->base, &vc4->hvs_channels,
+				    "vc4_hvs_channels",
 				    &vc4_hvs_state_funcs);
 
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 3ae35b09c0cf..87f85a77e5f3 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -376,4 +376,9 @@ struct drm_private_obj {
 	struct drm_device *dev;
 
+	/**
+	 * @name: human-readable name for debug messages
+	 */
+	const char *name;
+
 	/**
 	 * @head: List entry used to attach a private object to a &drm_device
@@ -757,5 +762,5 @@ drm_atomic_get_connector_state(struct drm_atomic_commit *state,
 
 int drm_atomic_private_obj_init(struct drm_device *dev,
-				struct drm_private_obj *obj,
+				struct drm_private_obj *obj, const char *name,
 				const struct drm_private_state_funcs *funcs);
 void drm_atomic_private_obj_fini(struct drm_private_obj *obj);
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 1981d24a700d..54cf0d223bba 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1032,4 +1032,6 @@ struct drm_bridge {
 	/** @dev: DRM device this bridge belongs to */
 	struct drm_device *dev;
+	/** @name: human-readable name for debug messages */
+	const char *name;
 	/** @encoder: encoder to which this bridge is connected */
 	struct drm_encoder *encoder;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 06/34] drm/bridge: Switch print_state to use bridge name
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (4 preceding siblings ...)
  2026-10-29  7:33 ` [PATCH v7 05/34] drm/atomic: Add drm_private_obj name Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 07/34] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
                   ` (27 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

drm_bridge_atomic_print_priv_state() currently identifies bridges by
printing the symbol name of bridge->funcs using the %ps format
specifier. This produces output like a raw function pointer name, which
is not particularly user-friendly for debugging.

Since bridges now carry a human-readable name, switch to printing
bridge->name instead. This makes the state readout output more
consistent and easier to interpret.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_bridge.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index ac65ac941691..e237366199a7 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -570,7 +570,7 @@ drm_bridge_atomic_print_priv_state(struct drm_printer *p,
 
 	if (bridge->of_node)
-		drm_printf(p, "bridge: %ps (%pOFfc)\n", bridge->funcs, bridge->of_node);
+		drm_printf(p, "bridge: %s (%pOFfc)\n", bridge->name, bridge->of_node);
 	else
-		drm_printf(p, "bridge: %ps\n", bridge->funcs);
+		drm_printf(p, "bridge: %s\n", bridge->name);
 
 	drm_printf_indent(p, 1, "input bus configuration:");

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 07/34] drm/bridge: Add drm_private_obj_is_bridge()
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (5 preceding siblings ...)
  2026-10-29  7:33 ` [PATCH v7 06/34] drm/bridge: Switch print_state to use bridge name Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 08/34] drm/fb-dma-helper: Handle framebuffers without a GEM object Maxime Ripard
                   ` (26 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The SRO readout helpers need to distinguish bridge-backed private
objects from other private objects, because bridges require special
readout ordering through the encoder bridge chains.

Add drm_private_obj_is_bridge() which checks whether a private
object's funcs pointer matches the bridge private state funcs.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_bridge.c | 15 +++++++++++++++
 include/drm/drm_bridge.h     |  2 ++
 2 files changed, 17 insertions(+)

diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index e237366199a7..b2d77824e4be 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -589,4 +589,19 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
 };
 
+/**
+ * drm_private_obj_is_bridge - check if a private object backs a bridge
+ * @obj: private object to check
+ *
+ * RETURNS:
+ *
+ * True if @obj is the &drm_private_obj embedded in a &struct drm_bridge,
+ * false otherwise.
+ */
+bool drm_private_obj_is_bridge(struct drm_private_obj *obj)
+{
+	return obj && obj->funcs == &drm_bridge_priv_state_funcs;
+}
+EXPORT_SYMBOL(drm_private_obj_is_bridge);
+
 /**
  * drm_bridge_attach - attach the bridge to an encoder's chain
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 54cf0d223bba..a22a0bdf3251 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1235,4 +1235,6 @@ drm_priv_to_bridge(struct drm_private_obj *priv)
 }
 
+bool drm_private_obj_is_bridge(struct drm_private_obj *obj);
+
 bool drm_bridge_enter(struct drm_bridge *bridge, int *idx);
 void drm_bridge_exit(int idx);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 08/34] drm/fb-dma-helper: Handle framebuffers without a GEM object
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (6 preceding siblings ...)
  2026-10-29  7:33 ` [PATCH v7 07/34] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:33 ` [PATCH v7 09/34] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
                   ` (25 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard, Jocelyn Falempe

The state readout documentation now states that a driver should
return an error when the hardware was programmed with a
configuration it cannot represent, so that the state initialization
falls back to the regular path.

However, a readout can also succeed with objects that cannot be
fully reconstructed. The framebuffer the firmware left in the
hardware was allocated outside of the kernel memory allocator, so
drivers won't have any GEM object to attach to its planes.

drm_fb_dma_get_scanout_buffer() dereferences the GEM object of the
first plane to fill the scanout buffer description used by
drm_panic, and would thus crash during a panic happening before
userspace had a chance to replace the framebuffer read out from the
hardware state.

Return -ENODEV for such framebuffers, like we already do for
imported buffers that are not accessible from the CPU.

Signed-off-by: Maxime Ripard <mripard@kernel.org>

---

Cc: Jocelyn Falempe <jfalempe@redhat.com>
---
 drivers/gpu/drm/drm_fb_dma_helper.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/gpu/drm/drm_fb_dma_helper.c b/drivers/gpu/drm/drm_fb_dma_helper.c
index fd71969d2fb1..3a37253ab9f8 100644
--- a/drivers/gpu/drm/drm_fb_dma_helper.c
+++ b/drivers/gpu/drm/drm_fb_dma_helper.c
@@ -180,4 +180,12 @@ int drm_fb_dma_get_scanout_buffer(struct drm_plane *plane,
 	dma_obj = drm_fb_dma_get_gem_obj(fb, 0);
 
+	/*
+	 * The framebuffer has no GEM object backing its first plane,
+	 * like a framebuffer reconstructed from a hardware state
+	 * readout: we cannot access its contents.
+	 */
+	if (!dma_obj)
+		return -ENODEV;
+
 	/* Buffer should be accessible from the CPU */
 	if (drm_gem_is_imported(&dma_obj->base))

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 09/34] drm/atomic: Export drm_atomic_*_print_state
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (7 preceding siblings ...)
  2026-10-29  7:33 ` [PATCH v7 08/34] drm/fb-dma-helper: Handle framebuffers without a GEM object Maxime Ripard
@ 2026-10-29  7:33 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 10/34] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
                   ` (24 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:33 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The drm_atomic_plane_print_state(), drm_atomic_crtc_print_state(),
drm_atomic_connector_print_state(), and
drm_atomic_private_obj_print_state() functions are currently static.

The SRO infrastructure needs to call them from
drm_atomic_state_readout.c to print the readout state for debugging
purposes. Make them available by dropping the static qualifier and
declaring them in drm_internal.h.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic.c   | 16 ++++++++--------
 drivers/gpu/drm/drm_internal.h | 13 +++++++++++++
 2 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 039be737fd07..895b958c2901 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -548,6 +548,6 @@ static int drm_atomic_crtc_check(const struct drm_crtc_state *old_crtc_state,
 }
 
-static void drm_atomic_crtc_print_state(struct drm_printer *p,
-		const struct drm_crtc_state *state)
+void drm_atomic_crtc_print_state(struct drm_printer *p,
+				 const struct drm_crtc_state *state)
 {
 	struct drm_crtc *crtc = state->crtc;
@@ -947,6 +947,6 @@ static void drm_atomic_colorop_print_state(struct drm_printer *p,
 }
 
-static void drm_atomic_plane_print_state(struct drm_printer *p,
-		const struct drm_plane_state *state)
+void drm_atomic_plane_print_state(struct drm_printer *p,
+				  const struct drm_plane_state *state)
 {
 	struct drm_plane *plane = state->plane;
@@ -1473,6 +1473,6 @@ drm_atomic_get_connector_state(struct drm_atomic_commit *state,
 EXPORT_SYMBOL(drm_atomic_get_connector_state);
 
-static void drm_atomic_connector_print_state(struct drm_printer *p,
-		const struct drm_connector_state *state)
+void drm_atomic_connector_print_state(struct drm_printer *p,
+				      const struct drm_connector_state *state)
 {
 	struct drm_connector *connector = state->connector;
@@ -2119,6 +2119,6 @@ int __drm_atomic_helper_set_config(struct drm_mode_set *set,
 EXPORT_SYMBOL(__drm_atomic_helper_set_config);
 
-static void drm_atomic_private_obj_print_state(struct drm_printer *p,
-					       const struct drm_private_state *state)
+void drm_atomic_private_obj_print_state(struct drm_printer *p,
+					const struct drm_private_state *state)
 {
 	struct drm_private_obj *obj = state->obj;
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
index f893b1e3a596..815486557f51 100644
--- a/drivers/gpu/drm/drm_internal.h
+++ b/drivers/gpu/drm/drm_internal.h
@@ -40,13 +40,26 @@ struct dma_buf;
 struct iosys_map;
 struct drm_connector;
+struct drm_connector_state;
 struct drm_crtc;
+struct drm_crtc_state;
 struct drm_framebuffer;
 struct drm_gem_object;
 struct drm_master;
 struct drm_minor;
+struct drm_plane_state;
 struct drm_prime_file_private;
 struct drm_printer;
+struct drm_private_state;
 struct drm_vblank_crtc;
 
+/* drm_atomic.c */
+void drm_atomic_plane_print_state(struct drm_printer *p,
+				  const struct drm_plane_state *state);
+void drm_atomic_crtc_print_state(struct drm_printer *p,
+				 const struct drm_crtc_state *state);
+void drm_atomic_connector_print_state(struct drm_printer *p,
+				      const struct drm_connector_state *state);
+void drm_atomic_private_obj_print_state(struct drm_printer *p,
+					const struct drm_private_state *state);
 /* drm_client_event.c */
 #if defined(CONFIG_DRM_CLIENT)

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 10/34] drm/atomic_sro: Create drm_atomic_sro_state container
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (8 preceding siblings ...)
  2026-10-29  7:33 ` [PATCH v7 09/34] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:50   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 11/34] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
                   ` (23 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The SRO infrastructure needs a state container to collect the readout
states for all KMS objects. Unlike drm_atomic_commit which tracks old
and new states for atomic commits, this container only holds a single
state per object: the one read out from the hardware.

Create struct drm_atomic_sro_state with arrays for planes, CRTCs,
connectors, and private objects, along with accessors to get and set
individual object states within it.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/Makefile                   |   1 +
 drivers/gpu/drm/drm_atomic_state_readout.c | 475 +++++++++++++++++++++++++++++
 include/drm/drm_atomic_state_readout.h     |  51 ++++
 3 files changed, 527 insertions(+)

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 2c6bc093a923..e94b77497514 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -35,4 +35,5 @@ subdir-ccflags-$(CONFIG_DRM_WERROR) += -Werror
 drm-y := \
 	drm_atomic.o \
+	drm_atomic_state_readout.o \
 	drm_atomic_uapi.o \
 	drm_auth.o \
diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
new file mode 100644
index 000000000000..2f8df00e993e
--- /dev/null
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+#include <linux/module.h>
+#include <linux/sched/mm.h>
+
+#include "drm_internal.h"
+#include "drm_crtc_internal.h"
+
+struct __drm_atomic_sro_plane {
+	struct drm_plane *ptr;
+	struct drm_plane_state *state;
+};
+
+struct __drm_atomic_sro_crtc {
+	struct drm_crtc *ptr;
+	struct drm_crtc_state *state;
+};
+
+struct __drm_atomic_sro_connector {
+	struct drm_connector *ptr;
+	struct drm_connector_state *state;
+};
+
+struct __drm_atomic_sro_private_obj {
+	struct drm_private_obj *ptr;
+	struct drm_private_state *state;
+};
+
+struct drm_atomic_sro_state {
+	struct drm_device *dev;
+	struct __drm_atomic_sro_plane *planes;
+	struct __drm_atomic_sro_crtc *crtcs;
+	struct __drm_atomic_sro_connector *connectors;
+	unsigned int max_connector_index;
+	struct __drm_atomic_sro_private_obj *private_objs;
+	unsigned int num_private_objs;
+};
+
+static unsigned int max_connector_index(struct drm_device *dev)
+{
+	struct drm_connector_list_iter conn_iter;
+	struct drm_connector *connector;
+	unsigned int max = 0;
+
+	drm_connector_list_iter_begin(dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter)
+		max = max(max, connector->index);
+	drm_connector_list_iter_end(&conn_iter);
+
+	return max;
+}
+
+static unsigned int count_private_obj(struct drm_device *dev)
+{
+	struct drm_mode_config *config = &dev->mode_config;
+	struct drm_private_obj *obj;
+	unsigned int count = 0;
+
+	list_for_each_entry(obj, &config->privobj_list, head)
+		count++;
+
+	return count;
+}
+
+/**
+ * drm_atomic_sro_state_get_device - get the DRM device from an SRO state
+ * @state: SRO state
+ *
+ * RETURNS:
+ *
+ * The &struct drm_device associated with the SRO state.
+ */
+struct drm_device *
+drm_atomic_sro_state_get_device(struct drm_atomic_sro_state *state)
+{
+	return state->dev;
+}
+
+static int drm_atomic_sro_state_init(struct drm_device *dev,
+				     struct drm_atomic_sro_state *state)
+{
+	int err;
+
+	state->planes =
+		kzalloc_objs(*state->planes, dev->mode_config.num_total_plane);
+	if (!state->planes)
+		return -ENOMEM;
+
+	state->crtcs = kzalloc_objs(*state->crtcs, dev->mode_config.num_crtc);
+	if (!state->crtcs) {
+		err = -ENOMEM;
+		goto err_free_planes;
+	}
+
+	state->max_connector_index = max_connector_index(dev);
+	state->connectors = kzalloc_objs(*state->connectors,
+					 state->max_connector_index + 1);
+	if (!state->connectors) {
+		err = -ENOMEM;
+		goto err_free_crtcs;
+	}
+
+	state->private_objs =
+		kzalloc_objs(*state->private_objs, count_private_obj(dev));
+	if (!state->private_objs)  {
+		err = -ENOMEM;
+		goto err_free_connectors;
+	}
+
+	state->num_private_objs = 0;
+
+	drm_dev_get(dev);
+	state->dev = dev;
+
+	return 0;
+
+err_free_connectors:
+	kfree(state->connectors);
+err_free_crtcs:
+	kfree(state->crtcs);
+err_free_planes:
+	kfree(state->planes);
+	return err;
+}
+
+/**
+ * drm_atomic_sro_state_alloc - allocate an SRO state container
+ * @dev: DRM device to allocate the state for
+ *
+ * Allocates and initializes a &struct drm_atomic_sro_state that can hold
+ * readout states for all KMS objects on @dev.
+ *
+ * The returned state must be freed with drm_atomic_sro_state_free().
+ *
+ * RETURNS:
+ *
+ * A new &struct drm_atomic_sro_state on success, an error pointer on
+ * failure.
+ */
+struct drm_atomic_sro_state *drm_atomic_sro_state_alloc(struct drm_device *dev)
+{
+	struct drm_atomic_sro_state *state;
+	int ret;
+
+	might_alloc(GFP_KERNEL);
+
+	state = kzalloc_obj(*state);
+	if (!state)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_atomic_sro_state_init(dev, state);
+	if (ret) {
+		kfree(state);
+		return ERR_PTR(ret);
+	}
+
+	return state;
+}
+EXPORT_SYMBOL(drm_atomic_sro_state_alloc);
+
+/**
+ * drm_atomic_sro_state_free - free an SRO state container
+ * @state: SRO state to free
+ *
+ * Frees a &struct drm_atomic_sro_state previously allocated with
+ * drm_atomic_sro_state_alloc().
+ */
+void drm_atomic_sro_state_free(struct drm_atomic_sro_state *state)
+{
+	struct drm_device *dev = state->dev;
+	unsigned int i;
+
+	for (i = 0; i < state->num_private_objs; i++) {
+		struct drm_private_obj *obj = state->private_objs[i].ptr;
+		struct drm_private_state *obj_state = state->private_objs[i].state;
+
+		if (!obj || !obj_state)
+			continue;
+
+		obj->funcs->atomic_destroy_state(obj, obj_state);
+		state->private_objs[i].state = NULL;
+		state->private_objs[i].ptr = NULL;
+	}
+
+	kfree(state->private_objs);
+
+	for (i = 0; i <= state->max_connector_index; i++) {
+		struct drm_connector *conn = state->connectors[i].ptr;
+		struct drm_connector_state *conn_state =
+			state->connectors[i].state;
+
+		if (!conn)
+			continue;
+
+		if (conn_state)
+			conn->funcs->atomic_destroy_state(conn, conn_state);
+
+		state->connectors[i].state = NULL;
+		state->connectors[i].ptr = NULL;
+		drm_connector_put(conn);
+	}
+
+	kfree(state->connectors);
+
+	for (i = 0; i < state->dev->mode_config.num_crtc; i++) {
+		struct drm_crtc *crtc = state->crtcs[i].ptr;
+		struct drm_crtc_state *crtc_state =
+			state->crtcs[i].state;
+
+		if (!crtc || !crtc_state)
+			continue;
+
+		crtc->funcs->atomic_destroy_state(crtc, crtc_state);
+		state->crtcs[i].state = NULL;
+		state->crtcs[i].ptr = NULL;
+	}
+
+	kfree(state->crtcs);
+
+	for (i = 0; i < state->dev->mode_config.num_total_plane; i++) {
+		struct drm_plane *plane = state->planes[i].ptr;
+		struct drm_plane_state *plane_state =
+			state->planes[i].state;
+
+		if (!plane || !plane_state)
+			continue;
+
+		plane->funcs->atomic_destroy_state(plane, plane_state);
+		state->planes[i].state = NULL;
+		state->planes[i].ptr = NULL;
+	}
+
+	kfree(state->planes);
+	kfree(state);
+
+	drm_dev_put(dev);
+}
+EXPORT_SYMBOL(drm_atomic_sro_state_free);
+
+/**
+ * drm_atomic_sro_state_print - prints drm atomic SRO state
+ * @state: SRO state to print
+ * @p: drm printer
+ *
+ * This function prints the SRO state snapshot using the drm printer which is
+ * passed to it. This snapshot can be used for debugging purposes.
+ */
+void drm_atomic_sro_state_print(const struct drm_atomic_sro_state *state,
+				struct drm_printer *p)
+{
+	struct drm_mode_config *config = &state->dev->mode_config;
+	int i;
+
+	if (!p) {
+		drm_err(state->dev, "invalid drm printer\n");
+		return;
+	}
+
+	drm_dbg_atomic(state->dev, "Printing readout state %p\n", state);
+
+	for (i = 0; i < config->num_total_plane; i++) {
+		struct drm_plane_state *plane_state = state->planes[i].state;
+
+		if (!plane_state)
+			continue;
+
+		drm_atomic_plane_print_state(p, plane_state);
+	}
+
+	for (i = 0; i < config->num_crtc; i++) {
+		struct drm_crtc_state *crtc_state = state->crtcs[i].state;
+
+		if (!crtc_state)
+			continue;
+
+		drm_atomic_crtc_print_state(p, crtc_state);
+	}
+
+	for (i = 0; i <= state->max_connector_index; i++) {
+		struct drm_connector_state *conn_state = state->connectors[i].state;
+
+		if (!conn_state)
+			continue;
+
+		drm_atomic_connector_print_state(p, conn_state);
+	}
+
+	for (i = 0; i < state->num_private_objs; i++) {
+		struct drm_private_state *obj_state = state->private_objs[i].state;
+
+		if (!obj_state)
+			continue;
+
+		drm_atomic_private_obj_print_state(p, obj_state);
+	}
+}
+EXPORT_SYMBOL(drm_atomic_sro_state_print);
+
+/**
+ * drm_atomic_sro_get_plane_state - get the plane state from an SRO state
+ * @state: SRO state container
+ * @plane: plane to get the state for
+ *
+ * RETURNS:
+ *
+ * The &struct drm_plane_state for @plane, or NULL if not yet read out.
+ */
+struct drm_plane_state *
+drm_atomic_sro_get_plane_state(struct drm_atomic_sro_state *state,
+			       struct drm_plane *plane)
+{
+	unsigned int index = drm_plane_index(plane);
+
+	return state->planes[index].state;
+};
+EXPORT_SYMBOL(drm_atomic_sro_get_plane_state);
+
+/**
+ * drm_atomic_sro_set_plane_state - store a plane state into an SRO state
+ * @state: SRO state container
+ * @plane: plane the state belongs to
+ * @plane_state: plane state to store
+ */
+void drm_atomic_sro_set_plane_state(struct drm_atomic_sro_state *state,
+				    struct drm_plane *plane,
+				    struct drm_plane_state *plane_state)
+{
+	unsigned int index = drm_plane_index(plane);
+
+	state->planes[index].ptr = plane;
+	state->planes[index].state = plane_state;
+
+	drm_dbg_atomic(plane->dev,
+		       "Added [PLANE:%d:%s] %p state to readout state %p\n",
+		       plane->base.id, plane->name, plane_state, state);
+}
+EXPORT_SYMBOL(drm_atomic_sro_set_plane_state);
+
+/**
+ * drm_atomic_sro_get_crtc_state - get the CRTC state from an SRO state
+ * @state: SRO state container
+ * @crtc: CRTC to get the state for
+ *
+ * RETURNS:
+ *
+ * The &struct drm_crtc_state for @crtc, or NULL if not yet read out.
+ */
+struct drm_crtc_state *
+drm_atomic_sro_get_crtc_state(struct drm_atomic_sro_state *state,
+			      struct drm_crtc *crtc)
+{
+	unsigned int index = drm_crtc_index(crtc);
+
+	return state->crtcs[index].state;
+};
+EXPORT_SYMBOL(drm_atomic_sro_get_crtc_state);
+
+/**
+ * drm_atomic_sro_set_crtc_state - store a CRTC state into an SRO state
+ * @state: SRO state container
+ * @crtc: CRTC the state belongs to
+ * @crtc_state: CRTC state to store
+ */
+void drm_atomic_sro_set_crtc_state(struct drm_atomic_sro_state *state,
+				   struct drm_crtc *crtc,
+				   struct drm_crtc_state *crtc_state)
+{
+	unsigned int index = drm_crtc_index(crtc);
+
+	state->crtcs[index].ptr = crtc;
+	state->crtcs[index].state = crtc_state;
+
+	drm_dbg_atomic(state->dev,
+		       "Added [CRTC:%d:%s] %p state to readout state %p\n",
+		       crtc->base.id, crtc->name, crtc_state, state);
+}
+EXPORT_SYMBOL(drm_atomic_sro_set_crtc_state);
+
+/**
+ * drm_atomic_sro_get_connector_state - get the connector state from an SRO state
+ * @state: SRO state container
+ * @connector: connector to get the state for
+ *
+ * RETURNS:
+ *
+ * The &struct drm_connector_state for @connector, or NULL if not yet
+ * read out.
+ */
+struct drm_connector_state *
+drm_atomic_sro_get_connector_state(struct drm_atomic_sro_state *state,
+				   struct drm_connector *connector)
+{
+	unsigned int index = drm_connector_index(connector);
+
+	return state->connectors[index].state;
+};
+EXPORT_SYMBOL(drm_atomic_sro_get_connector_state);
+
+/**
+ * drm_atomic_sro_set_connector_state - store a connector state into an SRO state
+ * @state: SRO state container
+ * @conn: connector the state belongs to
+ * @conn_state: connector state to store
+ *
+ * Takes a reference on @conn which is released when the state is
+ * installed or freed.
+ */
+void drm_atomic_sro_set_connector_state(struct drm_atomic_sro_state *state,
+					struct drm_connector *conn,
+					struct drm_connector_state *conn_state)
+{
+	unsigned int index = drm_connector_index(conn);
+
+	drm_connector_get(conn);
+	state->connectors[index].ptr = conn;
+	state->connectors[index].state = conn_state;
+
+	drm_dbg_atomic(conn->dev,
+		       "Added [CONNECTOR:%d:%s] %p state to readout state %p\n",
+		       conn->base.id, conn->name, conn_state, state);
+}
+EXPORT_SYMBOL(drm_atomic_sro_set_connector_state);
+
+/**
+ * drm_atomic_sro_get_private_obj_state - get a private object state from an SRO state
+ * @state: SRO state container
+ * @obj: private object to get the state for
+ *
+ * RETURNS:
+ *
+ * The &struct drm_private_state for @obj, or NULL if not yet read out.
+ */
+struct drm_private_state *
+drm_atomic_sro_get_private_obj_state(struct drm_atomic_sro_state *state,
+				     struct drm_private_obj *obj)
+{
+	unsigned int i;
+
+	for (i = 0; i < state->num_private_objs; i++)
+		if (obj == state->private_objs[i].ptr)
+			return state->private_objs[i].state;
+
+	return NULL;
+}
+EXPORT_SYMBOL(drm_atomic_sro_get_private_obj_state);
+
+/**
+ * drm_atomic_sro_set_private_obj_state - store a private object state into an SRO state
+ * @state: SRO state container
+ * @obj: private object the state belongs to
+ * @obj_state: private object state to store
+ */
+void drm_atomic_sro_set_private_obj_state(struct drm_atomic_sro_state *state,
+					  struct drm_private_obj *obj,
+					  struct drm_private_state *obj_state)
+{
+	unsigned int index = state->num_private_objs;
+
+	state->private_objs[index].ptr = obj;
+	state->private_objs[index].state = obj_state;
+	state->num_private_objs += 1;
+
+	drm_dbg_atomic(state->dev,
+		       "Added new private object %p state %p to readout state %p\n", obj,
+		       obj_state, state);
+}
+EXPORT_SYMBOL(drm_atomic_sro_set_private_obj_state);
diff --git a/include/drm/drm_atomic_state_readout.h b/include/drm/drm_atomic_state_readout.h
new file mode 100644
index 000000000000..5a9333a05796
--- /dev/null
+++ b/include/drm/drm_atomic_state_readout.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef DRM_ATOMIC_SRO_H_
+#define DRM_ATOMIC_SRO_H_
+
+struct drm_atomic_sro_state;
+struct drm_connector;
+struct drm_connector_state;
+struct drm_crtc;
+struct drm_crtc_state;
+struct drm_device;
+struct drm_plane;
+struct drm_plane_state;
+struct drm_printer;
+struct drm_private_obj;
+struct drm_private_state;
+
+struct drm_atomic_sro_state *drm_atomic_sro_state_alloc(struct drm_device *dev);
+void drm_atomic_sro_state_free(struct drm_atomic_sro_state *state);
+void drm_atomic_sro_state_print(const struct drm_atomic_sro_state *state,
+				struct drm_printer *p);
+
+struct drm_device *
+drm_atomic_sro_state_get_device(struct drm_atomic_sro_state *state);
+
+struct drm_plane_state *
+drm_atomic_sro_get_plane_state(struct drm_atomic_sro_state *state,
+			       struct drm_plane *plane);
+void drm_atomic_sro_set_plane_state(struct drm_atomic_sro_state *state,
+				    struct drm_plane *plane,
+				    struct drm_plane_state *plane_state);
+struct drm_crtc_state *
+drm_atomic_sro_get_crtc_state(struct drm_atomic_sro_state *state,
+			      struct drm_crtc *crtc);
+void drm_atomic_sro_set_crtc_state(struct drm_atomic_sro_state *state,
+				   struct drm_crtc *crtc,
+				   struct drm_crtc_state *crtc_state);
+struct drm_connector_state *
+drm_atomic_sro_get_connector_state(struct drm_atomic_sro_state *state,
+				   struct drm_connector *connector);
+void drm_atomic_sro_set_connector_state(struct drm_atomic_sro_state *state,
+					struct drm_connector *conn,
+					struct drm_connector_state *conn_state);
+struct drm_private_state *
+drm_atomic_sro_get_private_obj_state(struct drm_atomic_sro_state *state,
+				     struct drm_private_obj *private_obj);
+void drm_atomic_sro_set_private_obj_state(struct drm_atomic_sro_state *state,
+					  struct drm_private_obj *obj,
+					  struct drm_private_state *obj_state);
+
+#endif /* DRM_ATOMIC_SRO_H_ */

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 11/34] drm/atomic_sro: Create kernel parameter to force or disable readout
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (9 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 10/34] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:46   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 12/34] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
                   ` (22 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard, Simona Vetter

The hardware state readout is useful, but might need to be disabled
in case of bugs, or its checks relaxed during development when not
all hooks are implemented yet.

Add a module parameter to control the readout behavior: it can be
disabled entirely, or the checks for missing compare or readout hooks
can be skipped independently.

Suggested-by: Simona Vetter <simona@ffwll.ch>
Reviewed-by: Simona Vetter <simona.vetter@ffwll.ch>
Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic_state_readout.c | 36 ++++++++++++++++++++++++++++++
 include/drm/drm_atomic_state_readout.h     |  2 ++
 2 files changed, 38 insertions(+)

diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
index 2f8df00e993e..e93b0e4f0839 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -15,4 +15,40 @@
 #include "drm_crtc_internal.h"
 
+enum drm_atomic_readout_status {
+	DRM_ATOMIC_READOUT_DISABLED = 0,
+	DRM_ATOMIC_READOUT_ENABLED,
+	DRM_ATOMIC_READOUT_SKIP_MISSING_COMPARE,
+	DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT,
+};
+
+static unsigned int atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
+module_param_unsafe(atomic_readout, uint, 0400);
+MODULE_PARM_DESC(atomic_readout,
+		 "Enable Hardware State Readout (0 = disabled, 1 = enabled, 2 = ignore missing compares, 3 = ignore missing readouts and compares, default = 1)");
+
+/**
+ * drm_atomic_sro_device_can_readout - check if a device supports hardware state readout
+ * @dev: DRM device to check
+ *
+ * Verifies that the device is an atomic driver, that readout is
+ * enabled, and that all KMS objects implement the relevant hooks.
+ *
+ * RETURNS:
+ *
+ * True if the device supports full hardware state readout, false
+ * otherwise.
+ */
+bool drm_atomic_sro_device_can_readout(struct drm_device *dev)
+{
+	if (!drm_drv_uses_atomic_modeset(dev))
+		return false;
+
+	if (atomic_readout == DRM_ATOMIC_READOUT_DISABLED)
+		return false;
+
+	return true;
+}
+EXPORT_SYMBOL(drm_atomic_sro_device_can_readout);
+
 struct __drm_atomic_sro_plane {
 	struct drm_plane *ptr;
diff --git a/include/drm/drm_atomic_state_readout.h b/include/drm/drm_atomic_state_readout.h
index 5a9333a05796..6e5262384c71 100644
--- a/include/drm/drm_atomic_state_readout.h
+++ b/include/drm/drm_atomic_state_readout.h
@@ -16,4 +16,6 @@ struct drm_private_obj;
 struct drm_private_state;
 
+bool drm_atomic_sro_device_can_readout(struct drm_device *dev);
+
 struct drm_atomic_sro_state *drm_atomic_sro_state_alloc(struct drm_device *dev);
 void drm_atomic_sro_state_free(struct drm_atomic_sro_state *state);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 12/34] drm/atomic_sro: Add atomic state readout infrastructure
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (10 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 11/34] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 13/34] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
                   ` (21 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

In order to enable drivers to read their initial state from the
hardware, each KMS object needs a hook to fill a pre-allocated state
from the hardware registers.

Introduce the atomic_sro_readout_state hook in every KMS object funcs
vtable: drm_crtc_funcs, drm_plane_funcs, drm_connector_funcs,
drm_bridge_funcs, and drm_private_state_funcs.

Also introduce the drm_mode_config_funcs.atomic_sro_readout_state and
drm_mode_config_helper_funcs.atomic_sro_build_state hooks, which are the
top-level entry points for drivers and helpers respectively.

Finally, add drm_atomic_sro_can_readout() to verify that all objects on
a device implement the readout hook, and wire it into
drm_atomic_sro_device_can_readout().

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic_state_readout.c | 64 ++++++++++++++++++++++++++++++
 include/drm/drm_atomic.h                   | 30 ++++++++++++++
 include/drm/drm_bridge.h                   | 36 +++++++++++++++++
 include/drm/drm_connector.h                | 31 +++++++++++++++
 include/drm/drm_crtc.h                     | 31 +++++++++++++++
 include/drm/drm_mode_config.h              | 22 ++++++++++
 include/drm/drm_modeset_helper_vtables.h   | 31 +++++++++++++++
 include/drm/drm_plane.h                    | 31 +++++++++++++++
 8 files changed, 276 insertions(+)

diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
index e93b0e4f0839..de40e7e2231c 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -7,4 +7,5 @@
 #include <drm/drm_crtc.h>
 #include <drm/drm_drv.h>
+#include <drm/drm_modeset_helper_vtables.h>
 #include <drm/drm_plane.h>
 #include <drm/drm_print.h>
@@ -27,4 +28,61 @@ MODULE_PARM_DESC(atomic_readout,
 		 "Enable Hardware State Readout (0 = disabled, 1 = enabled, 2 = ignore missing compares, 3 = ignore missing readouts and compares, default = 1)");
 
+static bool drm_atomic_sro_can_readout(struct drm_device *dev)
+{
+	struct drm_crtc *crtc;
+	struct drm_plane *plane;
+	struct drm_connector *connector;
+	struct drm_private_obj *privobj;
+	struct drm_connector_list_iter conn_iter;
+
+	if (!dev->mode_config.helper_private ||
+	    !dev->mode_config.helper_private->atomic_sro_build_state)
+		return false;
+
+	if (atomic_readout == DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT)
+		return true;
+
+	if (!dev->mode_config.funcs->atomic_sro_readout_state)
+		return false;
+
+	drm_for_each_privobj(privobj, dev) {
+		if (!privobj->funcs->atomic_sro_readout_state) {
+			drm_dbg_atomic(dev,
+				       "Private object %s missing readout callback",
+				       privobj->name);
+			return false;
+		}
+	}
+
+	drm_for_each_plane(plane, dev) {
+		if (!plane->funcs->atomic_sro_readout_state) {
+			drm_dbg_atomic(dev, "Plane %s missing readout callback",
+				       plane->name);
+			return false;
+		}
+	}
+
+	drm_for_each_crtc(crtc, dev) {
+		if (!crtc->funcs->atomic_sro_readout_state) {
+			drm_dbg_atomic(dev, "CRTC %s missing readout callback",
+				       crtc->name);
+			return false;
+		}
+	}
+
+	drm_connector_list_iter_begin(dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
+		if (!connector->funcs->atomic_sro_readout_state) {
+			drm_dbg_atomic(dev, "Connector %s missing readout callback",
+				       connector->name);
+			drm_connector_list_iter_end(&conn_iter);
+			return false;
+		}
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	return true;
+}
+
 /**
  * drm_atomic_sro_device_can_readout - check if a device supports hardware state readout
@@ -41,4 +99,6 @@ MODULE_PARM_DESC(atomic_readout,
 bool drm_atomic_sro_device_can_readout(struct drm_device *dev)
 {
+	bool ret;
+
 	if (!drm_drv_uses_atomic_modeset(dev))
 		return false;
@@ -47,4 +107,8 @@ bool drm_atomic_sro_device_can_readout(struct drm_device *dev)
 		return false;
 
+	ret = drm_atomic_sro_can_readout(dev);
+	if (!ret)
+		return false;
+
 	return true;
 }
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 87f85a77e5f3..42923f960b30 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -315,4 +315,34 @@ struct drm_private_state_funcs {
 				     struct drm_private_state *state);
 
+	/**
+	 * @atomic_sro_readout_state:
+	 *
+	 * Initializes @obj_state to reflect the current hardware state
+	 * of the private object.
+	 *
+	 * It is meant to be used by drivers that want to implement
+	 * flicker-free boot (also called fastboot by i915) and allows
+	 * to initialize the atomic state from the hardware state left
+	 * by the firmware.
+	 *
+	 * It is used at initialization time, so drivers must make sure
+	 * that the power state is sensible when accessing the hardware.
+	 *
+	 * Drivers are expected to return a negative error code if the
+	 * hardware was programmed with a configuration they don't
+	 * support. The framework will then abort the readout and fall
+	 * back to the regular initialization path.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * 0 on success, a negative error code otherwise.
+	 */
+	int (*atomic_sro_readout_state)(struct drm_private_obj *obj,
+					struct drm_atomic_sro_state *state,
+					struct drm_private_state *obj_state);
+
 	/**
 	 * @atomic_print_state:
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index a22a0bdf3251..ef044ad27d2c 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -281,4 +281,40 @@ struct drm_bridge_funcs {
 				    struct drm_atomic_commit *state);
 
+	/**
+	 * @atomic_sro_readout_state:
+	 *
+	 * Initializes @bridge_state to reflect the current hardware
+	 * state of the bridge.
+	 *
+	 * It is meant to be used by drivers that want to implement
+	 * flicker-free boot (also called fastboot by i915) and allows
+	 * to initialize the atomic state from the hardware state left
+	 * by the firmware.
+	 *
+	 * It is used at initialization time, so drivers must make sure
+	 * that the power state is sensible when accessing the hardware.
+	 *
+	 * The @crtc_state and @conn_state parameters point to the CRTC
+	 * and connector states for the pipeline this bridge is part
+	 * of.
+	 *
+	 * Drivers are expected to return a negative error code if the
+	 * hardware was programmed with a configuration they don't
+	 * support. The framework will then abort the readout and fall
+	 * back to the regular initialization path.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * 0 on success, a negative error code otherwise.
+	 */
+	int (*atomic_sro_readout_state)(struct drm_bridge *bridge,
+					struct drm_atomic_sro_state *state,
+					struct drm_bridge_state *bridge_state,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state);
+
 	/**
 	 * @atomic_duplicate_state:
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index f656ed8324d3..f70b3aff259d 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -36,4 +36,5 @@
 #include <uapi/drm/drm_mode.h>
 
+struct drm_atomic_sro_state;
 struct drm_connector_helper_funcs;
 struct drm_modeset_acquire_ctx;
@@ -1814,4 +1815,34 @@ struct drm_connector_funcs {
 	struct drm_connector_state *(*atomic_create_state)(struct drm_connector *connector);
 
+	/**
+	 * @atomic_sro_readout_state:
+	 *
+	 * Initializes @conn_state to reflect the current hardware
+	 * state of the connector.
+	 *
+	 * It is meant to be used by drivers that want to implement
+	 * flicker-free boot (also called fastboot by i915) and allows
+	 * to initialize the atomic state from the hardware state left
+	 * by the firmware.
+	 *
+	 * It is used at initialization time, so drivers must make sure
+	 * that the power state is sensible when accessing the hardware.
+	 *
+	 * Drivers are expected to return a negative error code if the
+	 * hardware was programmed with a configuration they don't
+	 * support. The framework will then abort the readout and fall
+	 * back to the regular initialization path.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * 0 on success, a negative error code otherwise.
+	 */
+	int (*atomic_sro_readout_state)(struct drm_connector *connector,
+					struct drm_atomic_sro_state *state,
+					struct drm_connector_state *conn_state);
+
 	/**
 	 * @atomic_duplicate_state:
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index be5dca538d87..52365638b06f 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -36,4 +36,5 @@
 #include <drm/drm_mode_config.h>
 
+struct drm_atomic_sro_state;
 struct drm_connector;
 struct drm_device;
@@ -643,4 +644,34 @@ struct drm_crtc_funcs {
 	struct drm_crtc_state *(*atomic_create_state)(struct drm_crtc *crtc);
 
+	/**
+	 * @atomic_sro_readout_state:
+	 *
+	 * Initializes @crtc_state to reflect the current hardware
+	 * state of the CRTC.
+	 *
+	 * It is meant to be used by drivers that want to implement
+	 * flicker-free boot (also called fastboot by i915) and allows
+	 * to initialize the atomic state from the hardware state left
+	 * by the firmware.
+	 *
+	 * It is used at initialization time, so drivers must make sure
+	 * that the power state is sensible when accessing the hardware.
+	 *
+	 * Drivers are expected to return a negative error code if the
+	 * hardware was programmed with a configuration they don't
+	 * support. The framework will then abort the readout and fall
+	 * back to the regular initialization path.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * 0 on success, a negative error code otherwise.
+	 */
+	int (*atomic_sro_readout_state)(struct drm_crtc *crtc,
+					struct drm_atomic_sro_state *state,
+					struct drm_crtc_state *crtc_state);
+
 	/**
 	 * @atomic_duplicate_state:
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 084517beb924..dd555f8727ad 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -322,4 +322,26 @@ struct drm_mode_config_funcs {
 	 */
 	void (*atomic_state_free)(struct drm_atomic_commit *state);
+
+	/**
+	 * @atomic_sro_readout_state:
+	 *
+	 * This optional hook is called at initialization time to read
+	 * out the hardware state and initialize the DRM objects' atomic
+	 * states from it.
+	 *
+	 * When implemented, the framework will prefer hardware state
+	 * readout over creating pristine default states, enabling the
+	 * first modeset to be skipped if the firmware already set up
+	 * the display (flicker-free boot).
+	 *
+	 * If the readout fails, for example because the hardware was
+	 * programmed with a configuration the driver doesn't support,
+	 * the state initialization will fall back to the regular path.
+	 *
+	 * RETURNS:
+	 *
+	 * 0 on success, a negative error code otherwise.
+	 */
+	 int (*atomic_sro_readout_state)(struct drm_device *dev);
 };
 
diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
index 60b30cc26940..6858f0052637 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -1598,4 +1598,35 @@ struct drm_mode_config_helper_funcs {
 	 */
 	int (*atomic_commit_setup)(struct drm_atomic_commit *state);
+
+	/**
+	 * @atomic_sro_build_state:
+	 *
+	 * Builds a &struct drm_atomic_sro_state from the current hardware
+	 * state by calling the atomic_sro_readout_state hooks on the KMS
+	 * objects to read out.
+	 *
+	 * This hook is called both at initialization time, to create the
+	 * initial state from what the firmware programmed, and after
+	 * blocking commits, to read back the hardware state and compare it
+	 * to what was committed.
+	 *
+	 * When @commit is NULL, the state of every object in the device is
+	 * read out, and the caller must hold all the modeset locks, as
+	 * drm_mode_config_create_initial_state() does.
+	 *
+	 * When @commit is not NULL, only the objects that have a state in
+	 * @commit are read out. The caller must hold the locks of the
+	 * objects in @commit through its acquire context, and the readout
+	 * hooks must not access the state of any object outside of the
+	 * commit, since its locks aren't held.
+	 *
+	 * RETURNS:
+	 *
+	 * A &struct drm_atomic_sro_state on success, an error pointer
+	 * otherwise.
+	 */
+	struct drm_atomic_sro_state *
+	(*atomic_sro_build_state)(struct drm_device *dev,
+				  struct drm_atomic_commit *commit);
 };
 
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index 410d7b02b77a..d16dc057adf6 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -35,4 +35,5 @@
 enum drm_panic_type;
 
+struct drm_atomic_sro_state;
 struct drm_crtc;
 struct drm_plane_size_hint;
@@ -395,4 +396,34 @@ struct drm_plane_funcs {
 	struct drm_plane_state *(*atomic_create_state)(struct drm_plane *plane);
 
+	/**
+	 * @atomic_sro_readout_state:
+	 *
+	 * Initializes @plane_state to reflect the current hardware
+	 * state of the plane.
+	 *
+	 * It is meant to be used by drivers that want to implement
+	 * flicker-free boot (also called fastboot by i915) and allows
+	 * to initialize the atomic state from the hardware state left
+	 * by the firmware.
+	 *
+	 * It is used at initialization time, so drivers must make sure
+	 * that the power state is sensible when accessing the hardware.
+	 *
+	 * Drivers are expected to return a negative error code if the
+	 * hardware was programmed with a configuration they don't
+	 * support. The framework will then abort the readout and fall
+	 * back to the regular initialization path.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * 0 on success, a negative error code otherwise.
+	 */
+	int (*atomic_sro_readout_state)(struct drm_plane *plane,
+					struct drm_atomic_sro_state *state,
+					struct drm_plane_state *plane_state);
+
 	/**
 	 * @atomic_duplicate_state:

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 13/34] drm/atomic_sro: Add function to install state into drm objects
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (11 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 12/34] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 14/34] drm/atomic_sro: Create documentation Maxime Ripard
                   ` (20 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

Once the SRO state has been built from hardware, it needs to be
installed as the current state of each KMS object.

Add drm_atomic_sro_install_state() which walks through the SRO state
container and assigns each readout state to its object's state
pointer. Before each assignment, it calls the optional
atomic_sro_install_state hook to give drivers a chance to acquire the
resources they need to keep the hardware state active.

Also introduce the atomic_sro_install_state hook in the plane, CRTC,
connector, and private object funcs vtables.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic_state_readout.c | 145 ++++++++++++++++++++++++++++-
 include/drm/drm_atomic.h                   |  18 ++++
 include/drm/drm_atomic_state_readout.h     |   2 +
 include/drm/drm_connector.h                |  18 ++++
 include/drm/drm_crtc.h                     |  18 ++++
 include/drm/drm_plane.h                    |  18 ++++
 6 files changed, 218 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
index de40e7e2231c..a5bc77b3e492 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -272,5 +272,6 @@ EXPORT_SYMBOL(drm_atomic_sro_state_alloc);
  *
  * Frees a &struct drm_atomic_sro_state previously allocated with
- * drm_atomic_sro_state_alloc().
+ * drm_atomic_sro_state_alloc(). Any states that have not been
+ * installed via drm_atomic_sro_install_state() are also freed.
  */
 void drm_atomic_sro_state_free(struct drm_atomic_sro_state *state)
@@ -574,2 +575,144 @@ void drm_atomic_sro_set_private_obj_state(struct drm_atomic_sro_state *state,
 }
 EXPORT_SYMBOL(drm_atomic_sro_set_private_obj_state);
+
+/**
+ * drm_atomic_sro_install_state - install readout state into DRM objects
+ * @state: SRO state to install
+ *
+ * Takes a &struct drm_atomic_sro_state built by
+ * &drm_mode_config_helper_funcs.atomic_sro_build_state and installs its
+ * contents as the current state of each DRM object (setting
+ * &drm_crtc.state, &drm_plane.state, &drm_connector.state and
+ * &drm_private_obj.state).
+ *
+ * For each object, the optional atomic_sro_install_state hook is
+ * called before the state pointer is updated, allowing drivers to
+ * perform any needed action.
+ */
+void drm_atomic_sro_install_state(struct drm_atomic_sro_state *state)
+{
+	unsigned int i;
+
+	for (i = 0; i <= state->max_connector_index; i++) {
+		const struct drm_connector_funcs *conn_funcs;
+		struct drm_connector_state *conn_state;
+		struct drm_connector *conn;
+
+		conn = state->connectors[i].ptr;
+		if (!conn)
+			continue;
+
+		conn_funcs = conn->funcs;
+		if (!conn_funcs)
+			continue;
+
+		if (conn->state) {
+			conn_funcs->atomic_destroy_state(conn, conn->state);
+			conn->state = NULL;
+		}
+
+		conn_state = state->connectors[i].state;
+		if (!conn_state)
+			continue;
+
+		if (conn_funcs->atomic_sro_install_state)
+			conn_funcs->atomic_sro_install_state(conn, conn_state);
+
+		conn->state = conn_state;
+		state->connectors[i].state = NULL;
+		state->connectors[i].ptr = NULL;
+		drm_connector_put(conn);
+	}
+
+	for (i = 0; i < state->dev->mode_config.num_crtc; i++) {
+		const struct drm_crtc_funcs *crtc_funcs;
+		struct drm_crtc_state *crtc_state;
+		struct drm_crtc *crtc;
+
+		crtc = state->crtcs[i].ptr;
+		if (!crtc)
+			continue;
+
+		crtc_funcs = crtc->funcs;
+		if (!crtc_funcs)
+			continue;
+
+		if (crtc->state) {
+			crtc_funcs->atomic_destroy_state(crtc, crtc->state);
+			crtc->state = NULL;
+		}
+
+		crtc_state = state->crtcs[i].state;
+		if (!crtc_state)
+			continue;
+
+		if (crtc_funcs->atomic_sro_install_state)
+			crtc_funcs->atomic_sro_install_state(crtc, crtc_state);
+
+		crtc->state = crtc_state;
+		state->crtcs[i].state = NULL;
+		state->crtcs[i].ptr = NULL;
+	}
+
+	for (i = 0; i < state->dev->mode_config.num_total_plane; i++) {
+		const struct drm_plane_funcs *plane_funcs;
+		struct drm_plane_state *plane_state;
+		struct drm_plane *plane;
+
+		plane = state->planes[i].ptr;
+		if (!plane)
+			continue;
+
+		plane_funcs = plane->funcs;
+		if (!plane_funcs)
+			continue;
+
+		if (plane->state) {
+			plane_funcs->atomic_destroy_state(plane, plane->state);
+			plane->state = NULL;
+		}
+
+		plane_state = state->planes[i].state;
+		if (!plane_state)
+			continue;
+
+		if (plane_funcs->atomic_sro_install_state)
+			plane_funcs->atomic_sro_install_state(plane, plane_state);
+
+		plane->state = plane_state;
+		state->planes[i].state = NULL;
+		state->planes[i].ptr = NULL;
+	}
+
+	for (i = 0; i < count_private_obj(state->dev); i++) {
+		const struct drm_private_state_funcs *obj_funcs;
+		struct drm_private_state *obj_state;
+		struct drm_private_obj *obj;
+
+		obj = state->private_objs[i].ptr;
+		if (!obj)
+			continue;
+
+		obj_funcs = obj->funcs;
+		if (!obj_funcs)
+			continue;
+
+		if (obj->state) {
+			obj_funcs->atomic_destroy_state(obj, obj->state);
+			obj->state = NULL;
+		}
+
+		obj_state = state->private_objs[i].state;
+		if (!obj_state)
+			continue;
+
+		if (obj_funcs->atomic_sro_install_state)
+			obj_funcs->atomic_sro_install_state(obj, obj_state);
+
+		obj->state = obj_state;
+		state->private_objs[i].state = NULL;
+		state->private_objs[i].ptr = NULL;
+	}
+	state->num_private_objs = 0;
+}
+EXPORT_SYMBOL(drm_atomic_sro_install_state);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 42923f960b30..7a66e35257d8 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -345,4 +345,22 @@ struct drm_private_state_funcs {
 					struct drm_private_state *obj_state);
 
+	/**
+	 * @atomic_sro_install_state:
+	 *
+	 * This optional hook is called when a state read out from
+	 * hardware is about to be installed as the private object's
+	 * current state.
+	 *
+	 * It allows drivers to acquire the resources needed to keep
+	 * the current hardware state active, such as power domains,
+	 * clocks, or interrupts.
+	 *
+	 * This hook cannot fail. It is called during
+	 * drm_atomic_sro_install_state(), which is part of the
+	 * hardware state readout initialization sequence.
+	 */
+	void (*atomic_sro_install_state)(struct drm_private_obj *obj,
+					 struct drm_private_state *obj_state);
+
 	/**
 	 * @atomic_print_state:
diff --git a/include/drm/drm_atomic_state_readout.h b/include/drm/drm_atomic_state_readout.h
index 6e5262384c71..195154850ab4 100644
--- a/include/drm/drm_atomic_state_readout.h
+++ b/include/drm/drm_atomic_state_readout.h
@@ -51,3 +51,5 @@ void drm_atomic_sro_set_private_obj_state(struct drm_atomic_sro_state *state,
 					  struct drm_private_state *obj_state);
 
+void drm_atomic_sro_install_state(struct drm_atomic_sro_state *state);
+
 #endif /* DRM_ATOMIC_SRO_H_ */
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index f70b3aff259d..2d8639a870b3 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1845,4 +1845,22 @@ struct drm_connector_funcs {
 					struct drm_connector_state *conn_state);
 
+	/**
+	 * @atomic_sro_install_state:
+	 *
+	 * This optional hook is called when a state read out from
+	 * hardware is about to be installed as the connector's current
+	 * state.
+	 *
+	 * It allows drivers to acquire the resources needed to keep
+	 * the current hardware state active, such as power domains,
+	 * clocks, or interrupts.
+	 *
+	 * This hook cannot fail. It is called during
+	 * drm_atomic_sro_install_state(), which is part of the
+	 * hardware state readout initialization sequence.
+	 */
+	void (*atomic_sro_install_state)(struct drm_connector *conn,
+					 struct drm_connector_state *conn_state);
+
 	/**
 	 * @atomic_duplicate_state:
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 52365638b06f..ed69a2733a34 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -674,4 +674,22 @@ struct drm_crtc_funcs {
 					struct drm_crtc_state *crtc_state);
 
+	/**
+	 * @atomic_sro_install_state:
+	 *
+	 * This optional hook is called when a state read out from
+	 * hardware is about to be installed as the CRTC's current
+	 * state.
+	 *
+	 * It allows drivers to acquire the resources needed to keep
+	 * the current hardware state active, such as power domains,
+	 * clocks, or interrupts.
+	 *
+	 * This hook cannot fail. It is called during
+	 * drm_atomic_sro_install_state(), which is part of the
+	 * hardware state readout initialization sequence.
+	 */
+	void (*atomic_sro_install_state)(struct drm_crtc *crtc,
+					 struct drm_crtc_state *crtc_state);
+
 	/**
 	 * @atomic_duplicate_state:
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index d16dc057adf6..381d31d5c3e2 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -426,4 +426,22 @@ struct drm_plane_funcs {
 					struct drm_plane_state *plane_state);
 
+	/**
+	 * @atomic_sro_install_state:
+	 *
+	 * This optional hook is called when a state read out from
+	 * hardware is about to be installed as the plane's current
+	 * state.
+	 *
+	 * It allows drivers to acquire the resources needed to keep
+	 * the current hardware state active, such as power domains,
+	 * clocks, or interrupts.
+	 *
+	 * This hook cannot fail. It is called during
+	 * drm_atomic_sro_install_state(), which is part of the
+	 * hardware state readout initialization sequence.
+	 */
+	void (*atomic_sro_install_state)(struct drm_plane *plane,
+					 struct drm_plane_state *plane_state);
+
 	/**
 	 * @atomic_duplicate_state:

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 14/34] drm/atomic_sro: Create documentation
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (12 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 13/34] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 15/34] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
                   ` (19 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The SRO infrastructure has grown to span multiple files and hooks
but lacks an overview section explaining the overall design and how
drivers should integrate with it.

Add a DOC: overview section to drm_atomic_state_readout.c describing the
various SRO phases and how drivers wire up the callbacks.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 Documentation/gpu/drm-kms-helpers.rst      | 17 ++++++++++++++
 drivers/gpu/drm/drm_atomic_state_readout.c | 36 ++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+)

diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index d64f941d34f5..b75bde712faf 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -83,4 +83,21 @@ Atomic State Helper Reference
    :export:
 
+.. _drm_atomic_sro:
+
+Atomic State Read-Out
+---------------------
+
+.. kernel-doc:: drivers/gpu/drm/drm_atomic_state_readout.c
+   :doc: overview
+
+Atomic State Read-Out Reference
+--------------------------------
+
+.. kernel-doc:: include/drm/drm_atomic_state_readout.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/drm_atomic_state_readout.c
+   :export:
+
 GEM Atomic Helper Reference
 ---------------------------
diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
index a5bc77b3e492..413823579ed5 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -16,4 +16,40 @@
 #include "drm_crtc_internal.h"
 
+/**
+ * DOC: overview
+ *
+ * The atomic State Read-Out (SRO) infrastructure allows drivers to
+ * initialize the KMS atomic state from the hardware state left by the
+ * firmware at boot, rather than programming a new state. This enables
+ * flicker-free boot (also called "fastboot" by i915): if the
+ * firmware already configured the display, the first userspace
+ * modeset can be skipped when the requested mode matches.
+ *
+ * The SRO lifecycle has two phases. The first phase is the readout
+ * itself: at driver registration time, each KMS object (CRTCs, planes,
+ * connectors, bridges, private objects) has its
+ * atomic_sro_readout_state hook called to populate a
+ * &struct drm_atomic_sro_state from hardware registers.
+ *
+ * The hardware may have been programmed with a configuration that
+ * the driver doesn't support. The readout hooks are then expected
+ * to return a negative error code, and the state initialization
+ * will fall back to the regular path.
+ *
+ * The second phase is the installation. Once all states have been read
+ * out, drm_atomic_sro_install_state() walks through the
+ * &struct drm_atomic_sro_state and assigns each readout state as the
+ * object's current state. Before doing so, it calls the optional
+ * atomic_sro_install_state hook on each object. This gives drivers a
+ * chance to acquire the resources needed to keep the hardware state
+ * active, such as power domains, clocks, or interrupts. This hook
+ * cannot fail.
+ *
+ * Drivers integrate with SRO by implementing the readout hooks in their
+ * object funcs vtables and setting the
+ * &drm_mode_config_funcs.atomic_sro_readout_state and
+ * &drm_mode_config_helper_funcs.atomic_sro_build_state callbacks.
+ */
+
 enum drm_atomic_readout_status {
 	DRM_ATOMIC_READOUT_DISABLED = 0,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 15/34] drm/bridge: Handle bridges with hardware state readout
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (13 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 14/34] drm/atomic_sro: Create documentation Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:59   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 16/34] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
                   ` (18 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

Bridges use drm_private_obj for their atomic state, but their readout
needs special handling compared to other private objects.

Bridge drivers need the CRTC and connector states for their pipeline
to properly read out their own state. Since bridge registration does
not guarantee ordering, the readout must traverse each encoder's
bridge chain to ensure each bridge can query the state of preceding
bridges.

Add a drm_bridge_atomic_readout_priv_state() wrapper that looks up
the connector and CRTC states for the bridge's encoder, then forwards
to the bridge's atomic_sro_readout_state hook. Create a separate
drm_private_state_funcs vtable for bridges that support SRO so that
drm_atomic_sro_device_can_readout() can properly discriminate bridges
with and without SRO support.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_bridge.c | 89 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 86 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index b2d77824e4be..5977157d8e67 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -31,4 +31,5 @@
 
 #include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_atomic_state_readout.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_debugfs.h>
@@ -562,4 +563,71 @@ drm_bridge_atomic_create_priv_state(struct drm_private_obj *obj)
 }
 
+static struct drm_connector_state *
+find_connector_state_for_encoder(struct drm_atomic_sro_state *state,
+				struct drm_encoder *encoder)
+{
+	struct drm_connector_list_iter conn_iter;
+	struct drm_connector *connector;
+
+	drm_connector_list_iter_begin(drm_atomic_sro_state_get_device(state),
+				      &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
+		struct drm_connector_state *conn_state =
+			drm_atomic_sro_get_connector_state(state, connector);
+
+		if (WARN_ON(!conn_state))
+			continue;
+
+		if (encoder == conn_state->best_encoder) {
+			drm_connector_list_iter_end(&conn_iter);
+			return conn_state;
+		}
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	return NULL;
+}
+
+static int
+drm_bridge_atomic_readout_priv_state(struct drm_private_obj *obj,
+				     struct drm_atomic_sro_state *state,
+				     struct drm_private_state *priv_state)
+{
+	struct drm_bridge *bridge = drm_priv_to_bridge(obj);
+	struct drm_encoder *encoder = bridge->encoder;
+	const struct drm_bridge_funcs *bridge_funcs = bridge->funcs;
+	struct drm_crtc_state *crtc_state;
+	struct drm_bridge_state *bridge_state;
+	struct drm_connector_state *conn_state;
+	int ret;
+
+	/*
+	 * If the bridge's encoder has no routed connector or no
+	 * active CRTC, the pipeline is disabled and there is nothing
+	 * to read out. Keep the pristine default state created by
+	 * &drm_mode_config_helper_funcs.atomic_sro_build_state.
+	 */
+	conn_state = find_connector_state_for_encoder(state, encoder);
+	if (!conn_state)
+		return 0;
+
+	if (!conn_state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_sro_get_crtc_state(state, conn_state->crtc);
+	if (!crtc_state)
+		return 0;
+
+	bridge_state = drm_priv_to_bridge_state(priv_state);
+	if (bridge_funcs->atomic_sro_readout_state) {
+		ret = bridge_funcs->atomic_sro_readout_state(
+			bridge, state, bridge_state, crtc_state, conn_state);
+		if (WARN_ON(ret))
+			return ret;
+	}
+
+	return 0;
+}
+
 static void
 drm_bridge_atomic_print_priv_state(struct drm_printer *p,
@@ -589,4 +657,12 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
 };
 
+static const struct drm_private_state_funcs drm_bridge_priv_state_funcs_with_sro = {
+	.atomic_sro_readout_state = drm_bridge_atomic_readout_priv_state,
+	.atomic_create_state = drm_bridge_atomic_create_priv_state,
+	.atomic_duplicate_state = drm_bridge_atomic_duplicate_priv_state,
+	.atomic_destroy_state = drm_bridge_atomic_destroy_priv_state,
+	.atomic_print_state = drm_bridge_atomic_print_priv_state,
+};
+
 /**
  * drm_private_obj_is_bridge - check if a private object backs a bridge
@@ -600,5 +676,7 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
 bool drm_private_obj_is_bridge(struct drm_private_obj *obj)
 {
-	return obj && obj->funcs == &drm_bridge_priv_state_funcs;
+	return obj &&
+	       (obj->funcs == &drm_bridge_priv_state_funcs ||
+		obj->funcs == &drm_bridge_priv_state_funcs_with_sro);
 }
 EXPORT_SYMBOL(drm_private_obj_is_bridge);
@@ -673,6 +751,11 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
 	}
 
-	drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
-				    &drm_bridge_priv_state_funcs);
+	if (bridge->funcs &&
+	    bridge->funcs->atomic_sro_readout_state)
+		drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
+					    &drm_bridge_priv_state_funcs_with_sro);
+	else
+		drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
+					    &drm_bridge_priv_state_funcs);
 
 	return 0;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 16/34] drm/mode-config: Read out hardware state in drm_mode_config_create_state
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (14 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 15/34] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 17/34] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
                   ` (17 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

drm_mode_config_create_state() creates the initial state for all
KMS objects at driver registration time.

Call the drm_mode_config_funcs.atomic_sro_readout_state hook from it
so that the initial state is populated from the hardware rather than
being left as a pristine default.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_mode_config.c | 70 ++++++++++++++++++++++++++++++---------
 1 file changed, 55 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index c1e993a78702..9f242e2654c8 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -25,4 +25,5 @@
 
 #include <drm/drm_atomic.h>
+#include <drm/drm_atomic_state_readout.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_encoder.h>
@@ -32,4 +33,5 @@
 #include <drm/drm_mode_config.h>
 #include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_modeset_lock.h>
 #include <drm/drm_print.h>
 #include <drm/drm_colorop.h>
@@ -323,19 +325,5 @@ void drm_mode_config_reset(struct drm_device *dev)
 EXPORT_SYMBOL(drm_mode_config_reset);
 
-/**
- * drm_mode_config_create_initial_state - Allocates the initial state
- * @dev: drm device
- *
- * This functions creates the initial state for all the objects. Drivers
- * can use this in e.g. probe to initialize their software state.
- *
- * It has two main differences with drm_mode_config_reset(): the reset()
- * hooks aren't called and thus the hardware will be left untouched, but
- * also the &drm_private_obj structures will be initialized as opposed
- * to drm_mode_config_reset() that skips them.
- *
- * Returns: 0 on success, negative error value on failure.
- */
-int drm_mode_config_create_initial_state(struct drm_device *dev)
+static int drm_mode_config_create_state(struct drm_device *dev)
 {
 	struct drm_crtc *crtc;
@@ -409,4 +397,56 @@ int drm_mode_config_create_initial_state(struct drm_device *dev)
 	return 0;
 }
+
+/**
+ * drm_mode_config_create_initial_state - Allocates the initial state
+ * @dev: drm device
+ *
+ * This functions creates the initial state for all the objects. Drivers
+ * can use this in e.g. probe to initialize their state.
+ *
+ * It creates the initial state in several steps. It first tries to
+ * read-out the initial state from the hardware for all objects if state
+ * read-out is enabled and the driver supports it. Then, if state
+ * read-out isn't supported or fails, it creates a new, pristine state
+ * using the objects atomic_create_state callback.
+ *
+ * For historical reasons, drm_mode_config_reset() is similar, but
+ * doesn't support state read-out, and will always reset the hardware.
+ * It will also ignore all &drm_private_obj.
+ *
+ * The function holds all the modeset locks for the duration of the
+ * readout and the state creation, so drivers don't need to guarantee
+ * any kind of exclusivity themselves.
+ *
+ * Returns: 0 on success, negative error value on failure.
+ */
+int drm_mode_config_create_initial_state(struct drm_device *dev)
+{
+	struct drm_modeset_acquire_ctx ctx;
+	int ret;
+
+	/*
+	 * The initial state is usually created while the device is
+	 * being initialized and nothing can access it concurrently,
+	 * but holding all the modeset locks puts the readout, the
+	 * installation of its result and the creation of the pristine
+	 * states under the same locking contract as the regular
+	 * atomic paths.
+	 */
+	DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret);
+
+	if (dev->mode_config.funcs->atomic_sro_readout_state) {
+		ret = dev->mode_config.funcs->atomic_sro_readout_state(dev);
+		if (!ret)
+			goto out;
+	}
+
+	ret = drm_mode_config_create_state(dev);
+
+out:
+	DRM_MODESET_LOCK_ALL_END(dev, ctx, ret);
+
+	return ret;
+}
 EXPORT_SYMBOL(drm_mode_config_create_initial_state);
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 17/34] drm/atomic_sro: Provide helpers to implement hardware state readout
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (15 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 16/34] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  8:02   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 18/34] drm/mode-config: Introduce hw_reset hook Maxime Ripard
                   ` (16 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

Drivers implementing SRO need to iterate over their KMS objects,
allocate a pristine state for each, call the readout hook, and store
the result in the SRO state container.

Provide helpers for each object type
(drm_atomic_helper_sro_readout_planes_state(),
drm_atomic_helper_sro_readout_crtcs_state(), etc.) and a top-level
drm_atomic_helper_sro_build_state() that calls them in sequence.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 Documentation/gpu/drm-kms-helpers.rst             |   9 +
 drivers/gpu/drm/Makefile                          |   1 +
 drivers/gpu/drm/drm_atomic_state_readout.c        |   5 +-
 drivers/gpu/drm/drm_atomic_state_readout_helper.c | 681 ++++++++++++++++++++++
 include/drm/drm_atomic_state_readout_helper.h     |  35 ++
 include/drm/drm_modeset_helper_vtables.h          |   3 +
 6 files changed, 733 insertions(+), 1 deletion(-)

diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index b75bde712faf..a6dd8dbb3218 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -100,4 +100,13 @@ Atomic State Read-Out Reference
    :export:
 
+Atomic State Read-Out Helpers Reference
+----------------------------------------
+
+.. kernel-doc:: include/drm/drm_atomic_state_readout_helper.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/drm_atomic_state_readout_helper.c
+   :export:
+
 GEM Atomic Helper Reference
 ---------------------------
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index e94b77497514..7c8b778f4d5e 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -144,4 +144,5 @@ drm_kms_helper-y := \
 	drm_atomic_helper.o \
 	drm_atomic_state_helper.o \
+	drm_atomic_state_readout_helper.o \
 	drm_bridge_helper.o \
 	drm_crtc_helper.o \
diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
index 413823579ed5..2c17fa09c5f0 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -49,5 +49,8 @@
  * object funcs vtables and setting the
  * &drm_mode_config_funcs.atomic_sro_readout_state and
- * &drm_mode_config_helper_funcs.atomic_sro_build_state callbacks.
+ * &drm_mode_config_helper_funcs.atomic_sro_build_state callbacks. The
+ * default helpers drm_atomic_helper_sro_readout_state() and
+ * drm_atomic_helper_sro_build_state() handle the standard readout
+ * sequence.
  */
 
diff --git a/drivers/gpu/drm/drm_atomic_state_readout_helper.c b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
new file mode 100644
index 000000000000..a0b0337df2a0
--- /dev/null
+++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
@@ -0,0 +1,681 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_atomic_state_readout_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_colorop.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include <linux/gfp.h>
+#include <linux/sched/mm.h>
+
+static int
+drm_atomic_helper_sro_readout_plane_state(struct drm_device *dev,
+					  struct drm_atomic_sro_state *state,
+					  struct drm_plane *plane)
+{
+	const struct drm_plane_funcs *plane_funcs = plane->funcs;
+	struct drm_plane_state *plane_state;
+	int ret;
+
+	if (!plane_funcs->atomic_sro_readout_state) {
+		drm_warn(dev, "No plane readout implementation.");
+		return 0;
+	}
+
+	drm_dbg_kms(dev, "Initializing Plane %s state.\n", plane->name);
+
+	plane_state = plane_funcs->atomic_create_state(plane);
+	if (drm_WARN_ON(dev, IS_ERR(plane_state)))
+		return PTR_ERR(plane_state);
+
+	ret = plane_funcs->atomic_sro_readout_state(plane, state,
+						    plane_state);
+	if (drm_WARN_ON(dev, ret)) {
+		plane_funcs->atomic_destroy_state(plane, plane_state);
+		return ret;
+	}
+
+	drm_atomic_sro_set_plane_state(state, plane, plane_state);
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_commit_planes_state(struct drm_device *dev,
+						  struct drm_atomic_sro_state *state,
+						  struct drm_atomic_commit *commit)
+{
+	struct drm_plane_state *new_plane_state;
+	struct drm_plane *plane;
+	unsigned int i;
+	int ret;
+
+	if (!commit)
+		return -EINVAL;
+
+	for_each_new_plane_in_state(commit, plane, new_plane_state, i) {
+		ret = drm_atomic_helper_sro_readout_plane_state(dev, state, plane);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_readout_planes_state - read out all plane states from hardware
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ * @commit: optional atomic commit limiting the objects to read out
+ *
+ * Iterates over the planes that have a state in @commit when it isn't
+ * NULL, or over all the planes of @dev otherwise. For each, allocates
+ * a state via atomic_create_state, and calls the plane's
+ * atomic_sro_readout_state hook to fill it from hardware.
+ *
+ * RETURNS:
+ *
+ * 0 on success, a negative error code otherwise.
+ */
+int
+drm_atomic_helper_sro_readout_planes_state(struct drm_device *dev,
+					   struct drm_atomic_sro_state *state,
+					   struct drm_atomic_commit *commit)
+{
+	struct drm_plane *plane;
+	int ret;
+
+	might_alloc(GFP_KERNEL);
+
+	if (commit)
+		return drm_atomic_helper_sro_readout_commit_planes_state(dev, state, commit);
+
+	drm_for_each_plane(plane, dev) {
+		ret = drm_atomic_helper_sro_readout_plane_state(dev, state, plane);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_crtc_state(struct drm_device *dev,
+					 struct drm_atomic_sro_state *state,
+					 struct drm_crtc *crtc)
+{
+	const struct drm_crtc_funcs *crtc_funcs = crtc->funcs;
+	struct drm_crtc_state *crtc_state;
+	int ret;
+
+	if (!crtc_funcs->atomic_sro_readout_state) {
+		drm_warn(dev, "No CRTC readout implementation.");
+		return 0;
+	}
+
+	drm_dbg_kms(dev, "Initializing CRTC %s state.\n", crtc->name);
+
+	crtc_state = crtc_funcs->atomic_create_state(crtc);
+	if (drm_WARN_ON(dev, IS_ERR(crtc_state)))
+		return PTR_ERR(crtc_state);
+
+	ret = crtc_funcs->atomic_sro_readout_state(crtc, state, crtc_state);
+	if (drm_WARN_ON(dev, ret)) {
+		crtc_funcs->atomic_destroy_state(crtc, crtc_state);
+		return ret;
+	}
+
+	drm_atomic_sro_set_crtc_state(state, crtc, crtc_state);
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_commit_crtcs_state(struct drm_device *dev,
+						 struct drm_atomic_sro_state *state,
+						 struct drm_atomic_commit *commit)
+{
+	struct drm_crtc_state *new_crtc_state;
+	struct drm_crtc *crtc;
+	unsigned int i;
+	int ret;
+
+	if (!commit)
+		return -EINVAL;
+
+	for_each_new_crtc_in_state(commit, crtc, new_crtc_state, i) {
+		ret = drm_atomic_helper_sro_readout_crtc_state(dev, state, crtc);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_readout_crtcs_state - read out all CRTC states from hardware
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ * @commit: optional atomic commit limiting the objects to read out
+ *
+ * Iterates over the CRTCs that have a state in @commit when it isn't
+ * NULL, or over all the CRTCs of @dev otherwise. For each, allocates a
+ * state via atomic_create_state, and calls the CRTC's
+ * atomic_sro_readout_state hook to fill it from hardware.
+ *
+ * RETURNS:
+ *
+ * 0 on success, a negative error code otherwise.
+ */
+int
+drm_atomic_helper_sro_readout_crtcs_state(struct drm_device *dev,
+					  struct drm_atomic_sro_state *state,
+					  struct drm_atomic_commit *commit)
+{
+	struct drm_crtc *crtc;
+	int ret;
+
+	might_alloc(GFP_KERNEL);
+
+	if (commit)
+		return drm_atomic_helper_sro_readout_commit_crtcs_state(dev, state, commit);
+
+	drm_for_each_crtc(crtc, dev) {
+		ret = drm_atomic_helper_sro_readout_crtc_state(dev, state, crtc);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_connector_state(struct drm_device *dev,
+					      struct drm_atomic_sro_state *state,
+					      struct drm_connector *connector)
+{
+	const struct drm_connector_funcs *conn_funcs = connector->funcs;
+	struct drm_connector_state *conn_state;
+	int ret;
+
+	if (!conn_funcs->atomic_sro_readout_state) {
+		drm_warn(dev, "No connector readout implementation.");
+		return 0;
+	}
+
+	drm_dbg_kms(dev, "Initializing connector %s state.\n",
+		    connector->name);
+
+	conn_state = conn_funcs->atomic_create_state(connector);
+	if (drm_WARN_ON(dev, IS_ERR(conn_state)))
+		return PTR_ERR(conn_state);
+
+	ret = conn_funcs->atomic_sro_readout_state(connector, state,
+						   conn_state);
+	if (drm_WARN_ON(dev, ret)) {
+		conn_funcs->atomic_destroy_state(connector, conn_state);
+		return ret;
+	}
+
+	drm_atomic_sro_set_connector_state(state, connector, conn_state);
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_commit_connectors_state(struct drm_device *dev,
+						      struct drm_atomic_sro_state *state,
+						      struct drm_atomic_commit *commit)
+{
+	struct drm_connector_state *new_connector_state;
+	struct drm_connector *connector;
+	unsigned int i;
+	int ret;
+
+	if (!commit)
+		return -EINVAL;
+
+	for_each_new_connector_in_state(commit, connector, new_connector_state, i) {
+		ret = drm_atomic_helper_sro_readout_connector_state(dev, state, connector);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_readout_connectors_state - read out all connector states from hardware
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ * @commit: optional atomic commit limiting the objects to read out
+ *
+ * Iterates over the connectors that have a state in @commit when it
+ * isn't NULL, or over all the connectors of @dev otherwise. For each,
+ * allocates a state via atomic_create_state, and calls the connector's
+ * atomic_sro_readout_state hook to fill it from hardware.
+ *
+ * RETURNS:
+ *
+ * 0 on success, a negative error code otherwise.
+ */
+int
+drm_atomic_helper_sro_readout_connectors_state(struct drm_device *dev,
+					       struct drm_atomic_sro_state *state,
+					       struct drm_atomic_commit *commit)
+{
+	struct drm_connector_list_iter conn_iter;
+	struct drm_connector *connector;
+	int ret;
+
+	might_alloc(GFP_KERNEL);
+
+	if (commit)
+		return drm_atomic_helper_sro_readout_commit_connectors_state(dev, state, commit);
+
+	drm_connector_list_iter_begin(dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
+		ret = drm_atomic_helper_sro_readout_connector_state(dev, state, connector);
+		if (ret) {
+			drm_connector_list_iter_end(&conn_iter);
+			return ret;
+		}
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_bridge_state(struct drm_device *dev,
+					   struct drm_atomic_sro_state *state,
+					   struct drm_bridge *bridge)
+{
+	struct drm_private_obj *bridge_obj = &bridge->base;
+	const struct drm_private_state_funcs *bridge_obj_funcs =
+		bridge_obj->funcs;
+	struct drm_private_state *bridge_obj_state;
+	int ret;
+
+	/*
+	 * The same bridge chain can be reached through several
+	 * connectors of the commit. This happens for instance when a
+	 * commit disables a connector and routes another one to its
+	 * encoder: the chain is then reached through the old state of
+	 * the connector being disabled and through the new state of
+	 * the connector taking over. Don't read a bridge out twice.
+	 */
+	if (drm_atomic_sro_get_private_obj_state(state, bridge_obj))
+		return 0;
+
+	if (!bridge_obj_funcs->atomic_sro_readout_state) {
+		drm_warn(dev, "No bridge %s readout implementation.",
+			 bridge->name);
+		return 0;
+	}
+
+	drm_dbg_kms(dev, "Initializing Bridge %s state", bridge->name);
+
+	bridge_obj_state = bridge_obj_funcs->atomic_create_state(bridge_obj);
+	if (drm_WARN_ON(dev, IS_ERR(bridge_obj_state)))
+		return PTR_ERR(bridge_obj_state);
+
+	ret = bridge_obj_funcs->atomic_sro_readout_state(bridge_obj, state,
+						       bridge_obj_state);
+	if (drm_WARN_ON(dev, ret)) {
+		bridge_obj_funcs->atomic_destroy_state(bridge_obj,
+						       bridge_obj_state);
+		return ret;
+	}
+
+	drm_atomic_sro_set_private_obj_state(state, bridge_obj,
+					     bridge_obj_state);
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_bridge_chain_state(struct drm_device *dev,
+						 struct drm_atomic_sro_state *state,
+						 struct drm_encoder *encoder)
+{
+	int ret;
+
+	drm_for_each_bridge_in_chain(encoder, bridge) {
+		ret = drm_atomic_helper_sro_readout_bridge_state(dev, state, bridge);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_commit_bridges_state(struct drm_device *dev,
+						   struct drm_atomic_sro_state *state,
+						   struct drm_atomic_commit *commit)
+{
+	struct drm_connector_state *old_conn_state, *new_conn_state;
+	struct drm_connector *connector;
+	unsigned int i;
+
+	if (!commit)
+		return -EINVAL;
+
+	/*
+	 * Bridges can only be part of a commit through the connector
+	 * they are routed to, either the one they were routed to before
+	 * the commit or the one they are routed to after. Walking the
+	 * chains of the encoders of the committed connectors thus
+	 * covers all the bridges the commit can contain.
+	 */
+	for_each_oldnew_connector_in_state(commit, connector, old_conn_state, new_conn_state, i) {
+		struct drm_encoder *old_encoder = old_conn_state->best_encoder;
+		struct drm_encoder *new_encoder = new_conn_state->best_encoder;
+		int ret;
+
+		if (old_encoder) {
+			ret = drm_atomic_helper_sro_readout_bridge_chain_state(dev,
+									       state,
+									       old_encoder);
+			if (ret)
+				return ret;
+		}
+
+		if (new_encoder && new_encoder != old_encoder) {
+			ret = drm_atomic_helper_sro_readout_bridge_chain_state(dev,
+									       state,
+									       new_encoder);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_readout_bridges_state - read out all bridge states from hardware
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ * @commit: optional atomic commit limiting the objects to read out
+ *
+ * Iterates over the bridge chains of the encoders of the connectors
+ * that have a state in @commit when it isn't NULL, or over all the
+ * encoders of @dev otherwise. For each bridge, allocates a state via
+ * atomic_create_state, and calls the bridge's atomic_sro_readout_state
+ * hook to fill it from hardware.
+ *
+ * Bridges are handled separately from other private objects because
+ * bridge registration does not guarantee ordering. Traversing the
+ * encoder bridge chains ensures each bridge can query the state of
+ * preceding bridges in its chain.
+ *
+ * RETURNS:
+ *
+ * 0 on success, a negative error code otherwise.
+ */
+int
+drm_atomic_helper_sro_readout_bridges_state(struct drm_device *dev,
+					    struct drm_atomic_sro_state *state,
+					    struct drm_atomic_commit *commit)
+{
+	struct drm_encoder *encoder;
+	int ret;
+
+	might_alloc(GFP_KERNEL);
+
+	/*
+	 * It works a bit differently for bridges. Indeed they rely on a
+	 * drm_private_obj and drm_private_state, but bridge
+	 * registration doesn't guarantee ordering.
+	 *
+	 * In order for each bridge callback to be able to query the
+	 * previous bridge state, we thus need to read out the bridge
+	 * state by looking at each encoder and then traversing its
+	 * bridge list.
+	 *
+	 * And we'll then readout all the non-bridge drm_private_obj
+	 * later.
+	 */
+
+	if (commit)
+		return drm_atomic_helper_sro_readout_commit_bridges_state(dev, state, commit);
+
+	drm_for_each_encoder(encoder, dev) {
+		ret = drm_atomic_helper_sro_readout_bridge_chain_state(dev, state, encoder);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_private_obj_state(struct drm_device *dev,
+						struct drm_atomic_sro_state *state,
+						struct drm_private_obj *privobj)
+{
+	const struct drm_private_state_funcs *priv_funcs = privobj->funcs;
+	struct drm_private_state *priv_state;
+	int ret;
+
+	/*
+	 * The bridges are read out by
+	 * drm_atomic_helper_sro_readout_bridges_state() instead, in
+	 * bridge chain order.
+	 */
+	if (drm_private_obj_is_bridge(privobj))
+		return 0;
+
+	if (!priv_funcs->atomic_sro_readout_state) {
+		drm_warn(dev, "No private object %s readout implementation.",
+			 privobj->name);
+		return 0;
+	}
+
+	drm_dbg_kms(dev, "Initializing Private Object %s state", privobj->name);
+
+	priv_state = priv_funcs->atomic_create_state(privobj);
+	if (drm_WARN_ON(dev, IS_ERR(priv_state)))
+		return PTR_ERR(priv_state);
+
+	ret = priv_funcs->atomic_sro_readout_state(privobj, state,
+						   priv_state);
+	if (drm_WARN_ON(dev, ret)) {
+		priv_funcs->atomic_destroy_state(privobj, priv_state);
+		return ret;
+	}
+
+	drm_atomic_sro_set_private_obj_state(state, privobj, priv_state);
+
+	return 0;
+}
+
+static int
+drm_atomic_helper_sro_readout_commit_private_objs_state(struct drm_device *dev,
+							struct drm_atomic_sro_state *state,
+							struct drm_atomic_commit *commit)
+{
+	struct drm_private_state *new_obj_state;
+	struct drm_private_obj *obj;
+	unsigned int i;
+	int ret;
+
+	if (!commit)
+		return -EINVAL;
+
+	for_each_new_private_obj_in_state(commit, obj, new_obj_state, i) {
+		ret = drm_atomic_helper_sro_readout_private_obj_state(dev, state, obj);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_readout_private_objs_state - read out non-bridge private object states
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ * @commit: optional atomic commit limiting the objects to read out
+ *
+ * Iterates over the private objects that have a state in @commit when
+ * it isn't NULL, or over all the private objects of @dev otherwise. For
+ * each, allocates a state via atomic_create_state, and calls the
+ * object's atomic_sro_readout_state hook to fill it from hardware.
+ *
+ * RETURNS:
+ *
+ * 0 on success, a negative error code otherwise.
+ */
+int
+drm_atomic_helper_sro_readout_private_objs_state(struct drm_device *dev,
+						 struct drm_atomic_sro_state *state,
+						 struct drm_atomic_commit *commit)
+{
+	struct drm_private_obj *obj;
+	int ret;
+
+	might_alloc(GFP_KERNEL);
+
+	if (commit)
+		return drm_atomic_helper_sro_readout_commit_private_objs_state(dev, state, commit);
+
+	drm_for_each_privobj(obj, dev) {
+		ret = drm_atomic_helper_sro_readout_private_obj_state(dev, state, obj);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_build_state - build an SRO state from hardware
+ * @dev: DRM device to build the state for
+ * @commit: optional atomic commit limiting the objects to read out
+ *
+ * Allocates a &struct drm_atomic_sro_state and calls the readout hooks
+ * for CRTCs, planes, connectors, bridges, and private objects in
+ * sequence to fill it from the current hardware state.
+ *
+ * When @commit is NULL, the state of every object in the device is
+ * read out, and the caller must hold all the modeset locks. When it
+ * isn't NULL, only the objects that have a state in @commit are read
+ * out, and the caller must hold the locks of the objects in @commit.
+ *
+ * This is the default implementation for
+ * &drm_mode_config_helper_funcs.atomic_sro_build_state.
+ *
+ * RETURNS:
+ *
+ * A &struct drm_atomic_sro_state on success, an error pointer otherwise.
+ */
+struct drm_atomic_sro_state *
+drm_atomic_helper_sro_build_state(struct drm_device *dev,
+				  struct drm_atomic_commit *commit)
+{
+	struct drm_atomic_sro_state *state;
+	struct drm_printer p = drm_dbg_printer(dev, DRM_UT_ATOMIC, NULL);
+	int ret;
+
+	might_alloc(GFP_KERNEL);
+
+	drm_dbg_kms(dev, "Starting to build atomic state from hardware state.\n");
+
+	state = drm_atomic_sro_state_alloc(dev);
+	if (drm_WARN_ON(dev, IS_ERR(state)))
+		return state;
+
+	ret = drm_atomic_helper_sro_readout_crtcs_state(dev, state, commit);
+	if (ret)
+		goto err_state_free;
+
+	ret = drm_atomic_helper_sro_readout_planes_state(dev, state, commit);
+	if (ret)
+		goto err_state_free;
+
+	ret = drm_atomic_helper_sro_readout_connectors_state(dev, state,
+							     commit);
+	if (ret)
+		goto err_state_free;
+
+	ret = drm_atomic_helper_sro_readout_bridges_state(dev, state, commit);
+	if (ret)
+		goto err_state_free;
+
+	ret = drm_atomic_helper_sro_readout_private_objs_state(dev, state,
+							       commit);
+	if (ret)
+		goto err_state_free;
+
+	drm_atomic_sro_state_print(state, &p);
+
+	return state;
+
+err_state_free:
+	drm_atomic_sro_state_free(state);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(drm_atomic_helper_sro_build_state);
+
+/**
+ * drm_atomic_helper_sro_readout_state - default &drm_mode_config_funcs.atomic_sro_readout_state implementation
+ * @dev: DRM device to read out state for
+ *
+ * Checks if the device supports hardware state readout, builds the SRO
+ * state via &drm_mode_config_helper_funcs.atomic_sro_build_state,
+ * installs it as the current state of all DRM objects, and frees the
+ * SRO container.
+ *
+ * The caller must hold all the modeset locks of @dev, as
+ * drm_mode_config_create_initial_state(), the usual caller, does.
+ *
+ * RETURNS:
+ *
+ * 0 on success, -ENOTSUPP if the device does not support readout, a
+ * negative error code otherwise.
+ */
+int drm_atomic_helper_sro_readout_state(struct drm_device *dev)
+{
+	const struct drm_mode_config_helper_funcs *funcs =
+		dev->mode_config.helper_private;
+	struct drm_atomic_sro_state *state;
+	struct drm_colorop *colorop;
+
+	if (!drm_atomic_sro_device_can_readout(dev)) {
+		drm_info(dev, "Device doesn't support hardware state readout.");
+		return -ENOTSUPP;
+	}
+
+	state = funcs->atomic_sro_build_state(dev, NULL);
+	if (IS_ERR(state))
+		return PTR_ERR(state);
+
+	/* TODO: Add colorop state readout support */
+	drm_for_each_colorop(colorop, dev) {
+		struct drm_colorop_state *colorop_state;
+
+		colorop_state = drm_atomic_helper_colorop_create_state(colorop);
+		if (IS_ERR(colorop_state)) {
+			drm_atomic_sro_state_free(state);
+			return PTR_ERR(colorop_state);
+		}
+
+		colorop->state = colorop_state;
+	}
+
+	drm_atomic_sro_install_state(state);
+	drm_atomic_sro_state_free(state);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_atomic_helper_sro_readout_state);
diff --git a/include/drm/drm_atomic_state_readout_helper.h b/include/drm/drm_atomic_state_readout_helper.h
new file mode 100644
index 000000000000..d685644ec25a
--- /dev/null
+++ b/include/drm/drm_atomic_state_readout_helper.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef DRM_ATOMIC_SRO_HELPER_H_
+#define DRM_ATOMIC_SRO_HELPER_H_
+
+struct drm_atomic_commit;
+struct drm_atomic_sro_state;
+struct drm_device;
+
+int
+drm_atomic_helper_sro_readout_planes_state(struct drm_device *dev,
+					   struct drm_atomic_sro_state *state,
+					   struct drm_atomic_commit *commit);
+int
+drm_atomic_helper_sro_readout_crtcs_state(struct drm_device *dev,
+					  struct drm_atomic_sro_state *state,
+					  struct drm_atomic_commit *commit);
+int
+drm_atomic_helper_sro_readout_connectors_state(struct drm_device *dev,
+					       struct drm_atomic_sro_state *state,
+					       struct drm_atomic_commit *commit);
+int
+drm_atomic_helper_sro_readout_bridges_state(struct drm_device *dev,
+					    struct drm_atomic_sro_state *state,
+					    struct drm_atomic_commit *commit);
+int
+drm_atomic_helper_sro_readout_private_objs_state(struct drm_device *dev,
+						 struct drm_atomic_sro_state *state,
+						 struct drm_atomic_commit *commit);
+struct drm_atomic_sro_state *
+drm_atomic_helper_sro_build_state(struct drm_device *dev,
+				  struct drm_atomic_commit *commit);
+int drm_atomic_helper_sro_readout_state(struct drm_device *dev);
+
+#endif /* DRM_ATOMIC_SRO_HELPER_H_ */
diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
index 6858f0052637..e72f4f781df8 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -1621,4 +1621,7 @@ struct drm_mode_config_helper_funcs {
 	 * commit, since its locks aren't held.
 	 *
+	 * The default implementation is
+	 * drm_atomic_helper_sro_build_state().
+	 *
 	 * RETURNS:
 	 *

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 18/34] drm/mode-config: Introduce hw_reset hook
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (16 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 17/34] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 19/34] drm/modeset-helper: Add drm_mode_config_helper_hw_reset() Maxime Ripard
                   ` (15 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

drm_mode_config_create_initial_state() tries to read out the current
hardware state to build the initial atomic state. If state readout is
disabled or fails, it falls back to creating pristine software state
using the objects' atomic_create_state callbacks.

However, creating pristine state without resetting the hardware leaves
the hardware and software states out of sync. The legacy
drm_mode_config_reset() handles this by calling per-object .reset
callbacks that both reset the hardware and allocate new state, but these
two concerns are intertwined and cannot be reused independently.

Introduce a new hw_reset hook in drm_mode_config_funcs that only
performs the hardware reset, without allocating any software state. Call
it in drm_mode_config_create_initial_state() after state readout fails,
right before falling back to drm_mode_config_create_state(). This
separates the hardware reset from state creation, allowing drivers that
implement state readout to share their reset logic with the fallback
path.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_mode_config.c |  8 +++++++-
 include/drm/drm_mode_config.h     | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index 9f242e2654c8..1b70a15765c6 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -408,5 +408,6 @@ static int drm_mode_config_create_state(struct drm_device *dev)
  * read-out the initial state from the hardware for all objects if state
  * read-out is enabled and the driver supports it. Then, if state
- * read-out isn't supported or fails, it creates a new, pristine state
+ * read-out isn't supported or fails, it resets the hardware using
+ * &drm_mode_config_funcs.hw_reset() and creates a new, pristine state
  * using the objects atomic_create_state callback.
  *
@@ -442,4 +443,9 @@ int drm_mode_config_create_initial_state(struct drm_device *dev)
 	}
 
+	/* Reset the hardware first ... */
+	if (dev->mode_config.funcs->hw_reset)
+		dev->mode_config.funcs->hw_reset(dev);
+
+	/* ... then allocate a new pristine state */
 	ret = drm_mode_config_create_state(dev);
 
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index dd555f8727ad..ce9e48824dee 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -110,4 +110,36 @@ struct drm_mode_config_funcs {
 					   const struct drm_display_mode *mode);
 
+	/**
+	 * @hw_reset:
+	 *
+	 * Optional hook for device-specific hardware reset.
+	 *
+	 * Unlike drm_mode_config_reset(), which both resets hardware
+	 * and creates new software state through per-object .reset
+	 * callbacks, this hook only resets the hardware to a known good
+	 * state without touching the software state at all.
+	 *
+	 * This hook is called by
+	 * drm_mode_config_create_initial_state() when the hardware
+	 * state readout (atomic_sro_readout_state) is not supported
+	 * or fails. The call sequence is:
+	 *
+	 * 1. Try atomic_sro_readout_state() to read the hardware state
+	 * 2. If that fails, call hw_reset() to put the hardware in a
+	 *    known state
+	 * 3. Create pristine software state via atomic_create_state
+	 *    callbacks
+	 *
+	 * Drivers that implement state readout and need a hardware
+	 * reset on the fallback path should implement this hook.
+	 * Drivers that don't implement state readout at all can keep
+	 * using drm_mode_config_reset() instead.
+	 *
+	 * This hook is optional. If not provided, the fallback path
+	 * will create software state without resetting the hardware
+	 * first.
+	 */
+	void (*hw_reset)(struct drm_device *dev);
+
 	/**
 	 * @atomic_check:

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 19/34] drm/modeset-helper: Add drm_mode_config_helper_hw_reset()
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (17 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 18/34] drm/mode-config: Introduce hw_reset hook Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 20/34] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
                   ` (14 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

drm_mode_config_create_initial_state() calls
&drm_mode_config_funcs.hw_reset when the hardware state readout fails,
to put the hardware in a known-good state before creating pristine
software state.

Provide drm_mode_config_helper_hw_reset() as a default implementation of
&drm_mode_config_funcs.hw_reset that resets all the objects requiring
it.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_modeset_helper.c | 31 +++++++++++++++++++++++++++++++
 include/drm/drm_modeset_helper.h     |  1 +
 2 files changed, 32 insertions(+)

diff --git a/drivers/gpu/drm/drm_modeset_helper.c b/drivers/gpu/drm/drm_modeset_helper.c
index cca623eb23e5..1a49ab8a0e41 100644
--- a/drivers/gpu/drm/drm_modeset_helper.c
+++ b/drivers/gpu/drm/drm_modeset_helper.c
@@ -263,2 +263,33 @@ int drm_mode_config_helper_resume(struct drm_device *dev)
 }
 EXPORT_SYMBOL(drm_mode_config_helper_resume);
+
+/**
+ * drm_mode_config_helper_hw_reset - default &drm_mode_config_funcs.hw_reset implementation
+ * @dev: DRM device
+ *
+ * Resets all KMS objects that provide a hardware reset hook to a
+ * known-good state, without touching the software state.
+ *
+ * Drivers can assign this function as their
+ * &drm_mode_config_funcs.hw_reset implementation.
+ */
+void drm_mode_config_helper_hw_reset(struct drm_device *dev)
+{
+	struct drm_crtc *crtc;
+
+	drm_for_each_crtc(crtc, dev) {
+		const struct drm_crtc_helper_funcs *helper_funcs =
+			crtc->helper_private;
+
+		if (helper_funcs && helper_funcs->hw_reset)
+			helper_funcs->hw_reset(crtc);
+	}
+
+	/*
+	 * TODO: Reset encoders once the semantics of
+	 * encoder->funcs->reset have been clarified. Some drivers
+	 * (e.g. i915) reset both hardware and software state, while
+	 * others (e.g. loongson) only reset hardware registers.
+	 */
+}
+EXPORT_SYMBOL(drm_mode_config_helper_hw_reset);
diff --git a/include/drm/drm_modeset_helper.h b/include/drm/drm_modeset_helper.h
index 7e3d4c5a7f66..bd4193fbd907 100644
--- a/include/drm/drm_modeset_helper.h
+++ b/include/drm/drm_modeset_helper.h
@@ -43,4 +43,5 @@ int drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
 int drm_mode_config_helper_suspend(struct drm_device *dev);
 int drm_mode_config_helper_resume(struct drm_device *dev);
+void drm_mode_config_helper_hw_reset(struct drm_device *dev);
 
 #endif

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 20/34] drm/atomic_helper: Compare actual and readout states once the commit is done
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (18 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 19/34] drm/modeset-helper: Add drm_mode_config_helper_hw_reset() Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  8:05   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 21/34] drm/atomic-state-helper: Provide comparison macros Maxime Ripard
                   ` (13 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The new atomic state readout infrastructure can be hard to test because
getting access to every firmware variation is hard, but also because
most firmware setup will be pretty basic and won't test a wide range of
features. Noticing whether it was successful or not is also not very
convenient.

In order to make it easier, we can however provide some infrastructure
to read out a new state every time a blocking commit is made, and
compare the readout one with the committed one.

To do so, we introduce a new hook for every state,
atomic_sro_compare_state, that takes two state instances and is supposed
to return whether they are identical or not.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic_helper.c        |  45 ++++++
 drivers/gpu/drm/drm_atomic_state_readout.c | 223 ++++++++++++++++++++++++++++-
 drivers/gpu/drm/drm_bridge.c               |   3 +-
 include/drm/drm_atomic.h                   |  23 +++
 include/drm/drm_atomic_state_readout.h     |   4 +
 include/drm/drm_bridge.h                   |  22 +++
 include/drm/drm_connector.h                |  22 +++
 include/drm/drm_crtc.h                     |  22 +++
 include/drm/drm_plane.h                    |  22 +++
 9 files changed, 382 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 9d006f98413a..412864b22db6 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -33,4 +33,5 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout.h>
 #include <drm/drm_atomic_uapi.h>
 #include <drm/drm_blend.h>
@@ -2088,4 +2089,48 @@ static void commit_tail(struct drm_atomic_commit *state, bool nonblock)
 	drm_atomic_helper_commit_cleanup_done(state);
 
+	/*
+	 * drm_atomic_sro_readout_and_compare() will compare the @state new
+	 * states with whatever it will read from the hardware.
+	 *
+	 * Since drm_atomic_helper_swap_state() has already been called, this
+	 * means that new states have now been installed in their respective
+	 * object state pointer, and their access must be protected by their
+	 * respective locks.
+	 *
+	 * If we're doing a non-blocking commit however, the locks will be
+	 * dropped as soon as we return from drm_atomic_nonblocking_commit()
+	 * through drm_modeset_drop_locks(), while this function will be
+	 * scheduled to execute later on. This means that by the time we get
+	 * here, we probably have dropped the locks that protect the new states
+	 * and it's not safe to access them anymore.
+	 *
+	 * Thus, we can only safely call drm_atomic_sro_readout_and_compare() if
+	 * the commit is blocking, and we still hold all the locks.
+	 *
+	 * The readout is limited to the objects that have a state in
+	 * the commit, which are exactly the objects whose locks we
+	 * hold, so it can't race with a concurrent commit touching
+	 * the other objects.
+	 *
+	 * The other thing to consider is that it will take time to readout and
+	 * compare all the states, and we don't want to delay fence signalling
+	 * since some other part of the kernel or hardware might be waiting on
+	 * us. Fortunately, the fence is signalled through
+	 * &drm_crtc_commit.flip_done and drm_crtc_send_vblank_event() ->
+	 * drm_send_event_helper() with drm_pending_event.completion being set
+	 * to drm_crtc_commit.flip_done in drm_atomic_helper_setup_commit().
+	 *
+	 * We later wait for flip_done in
+	 * drm_atomic_helper_wait_for_flip_done(), which is typically part of
+	 * drm_mode_config_helper_funcs.atomic_commit_tail, or the equivalent
+	 * drm_atomic_helper_wait_for_vblank() which is part of
+	 * drm_atomic_helper_commit_tail().
+	 *
+	 * Either way, we're past that point here, so we wouldn't delay the
+	 * fence signalling.
+	 */
+	if (!nonblock)
+		drm_atomic_sro_readout_and_compare(state);
+
 	drm_atomic_commit_put(state);
 }
diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
index 2c17fa09c5f0..f29d127f99f8 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -26,5 +26,5 @@
  * modeset can be skipped when the requested mode matches.
  *
- * The SRO lifecycle has two phases. The first phase is the readout
+ * The SRO lifecycle has three phases. The first phase is the readout
  * itself: at driver registration time, each KMS object (CRTCs, planes,
  * connectors, bridges, private objects) has its
@@ -46,6 +46,15 @@
  * cannot fail.
  *
- * Drivers integrate with SRO by implementing the readout hooks in their
- * object funcs vtables and setting the
+ * The third phase is the comparison. Because getting access to every
+ * firmware variation is hard, and most firmware setups will be fairly
+ * basic, the framework also provides a way to verify the readout
+ * implementation during normal operation. After every blocking commit,
+ * the framework reads out a fresh state from hardware and compares it
+ * to the committed state using the atomic_sro_compare_state hooks. Any
+ * mismatch is reported, which allows to catch readout bugs without
+ * needing specific firmware configurations.
+ *
+ * Drivers integrate with SRO by implementing the readout and compare
+ * hooks in their object funcs vtables and setting the
  * &drm_mode_config_funcs.atomic_sro_readout_state and
  * &drm_mode_config_helper_funcs.atomic_sro_build_state callbacks. The
@@ -124,4 +133,55 @@ static bool drm_atomic_sro_can_readout(struct drm_device *dev)
 }
 
+static bool drm_atomic_sro_can_compare(struct drm_device *dev)
+{
+	struct drm_crtc *crtc;
+	struct drm_plane *plane;
+	struct drm_connector *connector;
+	struct drm_connector_list_iter conn_iter;
+	struct drm_private_obj *privobj;
+
+	if (atomic_readout == DRM_ATOMIC_READOUT_SKIP_MISSING_COMPARE ||
+	    atomic_readout == DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT)
+		return true;
+
+	drm_for_each_privobj(privobj, dev) {
+		if (!privobj->funcs->atomic_sro_compare_state) {
+			drm_dbg_atomic(dev,
+				       "Private object %s missing compare callback",
+				       privobj->name);
+			return false;
+		}
+	}
+
+	drm_for_each_plane(plane, dev) {
+		if (!plane->funcs->atomic_sro_compare_state) {
+			drm_dbg_atomic(dev, "Plane %s missing compare callback",
+				       plane->name);
+			return false;
+		}
+	}
+
+	drm_for_each_crtc(crtc, dev) {
+		if (!crtc->funcs->atomic_sro_compare_state) {
+			drm_dbg_atomic(dev, "CRTC %s missing compare callback",
+				       crtc->name);
+			return false;
+		}
+	}
+
+	drm_connector_list_iter_begin(dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
+		if (!connector->funcs->atomic_sro_compare_state) {
+			drm_dbg_atomic(dev, "Connector %s missing compare callback",
+				       connector->name);
+			drm_connector_list_iter_end(&conn_iter);
+			return false;
+		}
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	return true;
+}
+
 /**
  * drm_atomic_sro_device_can_readout - check if a device supports hardware state readout
@@ -150,4 +210,8 @@ bool drm_atomic_sro_device_can_readout(struct drm_device *dev)
 		return false;
 
+	ret = drm_atomic_sro_can_compare(dev);
+	if (!ret)
+		return false;
+
 	return true;
 }
@@ -756,2 +820,155 @@ void drm_atomic_sro_install_state(struct drm_atomic_sro_state *state)
 }
 EXPORT_SYMBOL(drm_atomic_sro_install_state);
+
+/**
+ * drm_atomic_sro_readout_and_compare - verify committed state against hardware
+ * @commit: the atomic commit we just performed
+ *
+ * Reads out a fresh &struct drm_atomic_sro_state from the hardware,
+ * limited to the objects that have a state in @commit, and compares it
+ * to @commit using the atomic_sro_compare_state hooks. Any mismatches
+ * are reported through the DRM error printer.
+ *
+ * This is called after blocking commits to verify that the readout
+ * implementation is correct. It must be called while the locks of the
+ * objects in @commit are still held, so before the caller drops them
+ * through the acquire context of @commit.
+ *
+ * Since only the objects of @commit are read out, and those are the
+ * objects whose locks are held, the readout can't race with a
+ * concurrent commit touching other objects.
+ *
+ * RETURNS:
+ *
+ * True if the committed state and the hardware state are identical,
+ * false otherwise.
+ */
+bool drm_atomic_sro_readout_and_compare(struct drm_atomic_commit *commit)
+{
+	struct drm_device *dev = commit->dev;
+	const struct drm_mode_config_helper_funcs *funcs =
+		dev->mode_config.helper_private;
+	struct drm_printer p = drm_err_printer(dev, NULL);
+	struct drm_private_state *new_obj_state;
+	struct drm_private_obj *obj;
+	struct drm_plane_state *new_plane_state;
+	struct drm_plane *plane;
+	struct drm_crtc_state *new_crtc_state;
+	struct drm_crtc *crtc;
+	struct drm_connector_state *new_conn_state;
+	struct drm_connector *conn;
+	struct drm_atomic_sro_state *readout_state;
+	unsigned int i;
+	bool identical = true;
+
+	might_alloc(GFP_KERNEL);
+
+	if (!drm_atomic_sro_device_can_readout(dev))
+		return true;
+
+	readout_state = funcs->atomic_sro_build_state(dev, commit);
+	if (WARN_ON(IS_ERR(readout_state)))
+		return false;
+
+	for_each_new_plane_in_state(commit, plane, new_plane_state, i) {
+		const struct drm_plane_funcs *plane_funcs =
+			plane->funcs;
+		struct drm_plane_state *readout_plane_state;
+
+		readout_plane_state = drm_atomic_sro_get_plane_state(readout_state, plane);
+		if (!readout_plane_state) {
+			identical = false;
+			continue;
+		}
+
+		if (!plane_funcs->atomic_sro_compare_state)
+			continue;
+
+		if (!plane_funcs->atomic_sro_compare_state(plane,
+							   &p,
+							   new_plane_state,
+							   readout_plane_state)) {
+			drm_warn(dev, "[PLANE:%d:%s] Committed and Readout PLANE state don't match\n",
+				 plane->base.id, plane->name);
+			identical = false;
+			continue;
+		}
+	}
+
+	for_each_new_crtc_in_state(commit, crtc, new_crtc_state, i) {
+		const struct drm_crtc_funcs *crtc_funcs = crtc->funcs;
+		struct drm_crtc_state *readout_crtc_state;
+
+		readout_crtc_state = drm_atomic_sro_get_crtc_state(readout_state, crtc);
+		if (!readout_crtc_state) {
+			identical = false;
+			continue;
+		}
+
+		if (!crtc_funcs->atomic_sro_compare_state)
+			continue;
+
+		if (!crtc_funcs->atomic_sro_compare_state(crtc,
+							  &p,
+							  new_crtc_state,
+							  readout_crtc_state)) {
+			drm_warn(dev, "[CRTC:%d:%s] Committed and Readout CRTC state don't match\n",
+				 crtc->base.id, crtc->name);
+			identical = false;
+			continue;
+		}
+	}
+
+	for_each_new_connector_in_state(commit, conn, new_conn_state, i) {
+		const struct drm_connector_funcs *conn_funcs =
+			conn->funcs;
+		struct drm_connector_state *readout_conn_state;
+
+		readout_conn_state = drm_atomic_sro_get_connector_state(readout_state, conn);
+		if (!readout_conn_state) {
+			identical = false;
+			continue;
+		}
+
+		if (!conn_funcs->atomic_sro_compare_state)
+			continue;
+
+		if (!conn_funcs->atomic_sro_compare_state(conn,
+							  &p,
+							  new_conn_state,
+							  readout_conn_state)) {
+			drm_warn(dev, "[CONNECTOR:%d:%s] Committed and Readout connector state don't match\n",
+				 conn->base.id, conn->name);
+			identical = false;
+			continue;
+		}
+	}
+
+	for_each_new_private_obj_in_state(commit, obj, new_obj_state, i) {
+		const struct drm_private_state_funcs *obj_funcs = obj->funcs;
+		struct drm_private_state *readout_obj_state;
+
+		readout_obj_state = drm_atomic_sro_get_private_obj_state(readout_state, obj);
+		if (!readout_obj_state) {
+			identical = false;
+			continue;
+		}
+
+		if (!obj_funcs->atomic_sro_compare_state)
+			continue;
+
+		if (!obj_funcs->atomic_sro_compare_state(obj,
+							 &p,
+							 new_obj_state,
+							 readout_obj_state)) {
+			drm_warn(dev, "Committed and Readout private object state don't match\n");
+			identical = false;
+			continue;
+		}
+	}
+
+	drm_atomic_sro_state_free(readout_state);
+
+	return identical;
+}
+EXPORT_SYMBOL(drm_atomic_sro_readout_and_compare);
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 5977157d8e67..1c16e637b97e 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -752,5 +752,6 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
 
 	if (bridge->funcs &&
-	    bridge->funcs->atomic_sro_readout_state)
+	    bridge->funcs->atomic_sro_readout_state &&
+	    bridge->funcs->atomic_sro_compare_state)
 		drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
 					    &drm_bridge_priv_state_funcs_with_sro);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 7a66e35257d8..6d3aef9bf9d3 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -363,4 +363,27 @@ struct drm_private_state_funcs {
 					 struct drm_private_state *obj_state);
 
+	/**
+	 * @atomic_sro_compare_state:
+	 *
+	 * Compares two &struct drm_private_state instances and reports
+	 * any mismatches through @p.
+	 *
+	 * It is called after blocking commits to verify that the
+	 * committed state matches what can be read back from the
+	 * hardware. Drivers subclassing the state should implement this
+	 * to compare their driver-private fields.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * True if the states are identical, false otherwise.
+	 */
+	bool (*atomic_sro_compare_state)(struct drm_private_obj *obj,
+					 struct drm_printer *p,
+					 struct drm_private_state *a,
+					 struct drm_private_state *b);
+
 	/**
 	 * @atomic_print_state:
diff --git a/include/drm/drm_atomic_state_readout.h b/include/drm/drm_atomic_state_readout.h
index 195154850ab4..6529774d9ca8 100644
--- a/include/drm/drm_atomic_state_readout.h
+++ b/include/drm/drm_atomic_state_readout.h
@@ -4,4 +4,7 @@
 #define DRM_ATOMIC_SRO_H_
 
+#include <linux/types.h>
+
+struct drm_atomic_commit;
 struct drm_atomic_sro_state;
 struct drm_connector;
@@ -52,4 +55,5 @@ void drm_atomic_sro_set_private_obj_state(struct drm_atomic_sro_state *state,
 
 void drm_atomic_sro_install_state(struct drm_atomic_sro_state *state);
+bool drm_atomic_sro_readout_and_compare(struct drm_atomic_commit *commit);
 
 #endif /* DRM_ATOMIC_SRO_H_ */
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index ef044ad27d2c..030467e2cb1e 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -317,4 +317,26 @@ struct drm_bridge_funcs {
 					struct drm_connector_state *conn_state);
 
+	/**
+	 * @atomic_sro_compare_state:
+	 *
+	 * Compares two &struct drm_bridge_state instances and reports
+	 * any mismatches through @p.
+	 *
+	 * It is called after blocking commits to verify that the
+	 * committed state matches what can be read back from the
+	 * hardware.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * True if the states are identical, false otherwise.
+	 */
+	bool (*atomic_sro_compare_state)(struct drm_bridge *bridge,
+					 struct drm_printer *p,
+					 struct drm_bridge_state *a,
+					 struct drm_bridge_state *b);
+
 	/**
 	 * @atomic_duplicate_state:
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 2d8639a870b3..3892a20a6cca 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1907,4 +1907,26 @@ struct drm_connector_funcs {
 				     struct drm_connector_state *state);
 
+	/**
+	 * @atomic_sro_compare_state:
+	 *
+	 * Compares two &struct drm_connector_state instances and
+	 * reports any mismatches through @p.
+	 *
+	 * It is called after blocking commits to verify that the
+	 * committed state matches what can be read back from the
+	 * hardware.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * True if the states are identical, false otherwise.
+	 */
+	bool (*atomic_sro_compare_state)(struct drm_connector *connector,
+					 struct drm_printer *p,
+					 struct drm_connector_state *a,
+					 struct drm_connector_state *b);
+
 	/**
 	 * @atomic_set_property:
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index ed69a2733a34..e5662ddb61fb 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -736,4 +736,26 @@ struct drm_crtc_funcs {
 				     struct drm_crtc_state *state);
 
+	/**
+	 * @atomic_sro_compare_state:
+	 *
+	 * Compares two &struct drm_crtc_state instances and reports
+	 * any mismatches through @p.
+	 *
+	 * It is called after blocking commits to verify that the
+	 * committed state matches what can be read back from the
+	 * hardware.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * True if the states are identical, false otherwise.
+	 */
+	bool (*atomic_sro_compare_state)(struct drm_crtc *crtc,
+					 struct drm_printer *p,
+					 struct drm_crtc_state *a,
+					 struct drm_crtc_state *b);
+
 	/**
 	 * @atomic_set_property:
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index 381d31d5c3e2..0a18d26eb397 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -488,4 +488,26 @@ struct drm_plane_funcs {
 				     struct drm_plane_state *state);
 
+	/**
+	 * @atomic_sro_compare_state:
+	 *
+	 * Compares two &struct drm_plane_state instances and reports
+	 * any mismatches through @p.
+	 *
+	 * It is called after blocking commits to verify that the
+	 * committed state matches what can be read back from the
+	 * hardware.
+	 *
+	 * This hook is mandatory for drivers implementing SRO, but can
+	 * be left unassigned otherwise.
+	 *
+	 * RETURNS:
+	 *
+	 * True if the states are identical, false otherwise.
+	 */
+	bool (*atomic_sro_compare_state)(struct drm_plane *plane,
+					 struct drm_printer *p,
+					 struct drm_plane_state *a,
+					 struct drm_plane_state *b);
+
 	/**
 	 * @atomic_set_property:

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 21/34] drm/atomic-state-helper: Provide comparison macros
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (19 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 20/34] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 22/34] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
                   ` (12 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

In order to make the implementation of atomic_sro_compare_state easier
and reduce the boilerplate, introduce a set of STATE_CHECK_* macros to
compare state field values depending on their type.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic_state_readout_helper.c |  30 +++
 include/drm/drm_atomic_state_readout_helper.h     | 220 ++++++++++++++++++++++
 2 files changed, 250 insertions(+)

diff --git a/drivers/gpu/drm/drm_atomic_state_readout_helper.c b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
index a0b0337df2a0..33f0f78c9b26 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
@@ -680,2 +680,32 @@ int drm_atomic_helper_sro_readout_state(struct drm_device *dev)
 }
 EXPORT_SYMBOL(drm_atomic_helper_sro_readout_state);
+
+/**
+ * drm_atomic_helper_print_state_mismatch - report a state comparison mismatch
+ * @p: printer to report through
+ * @name: human-readable name of the object (e.g. plane or CRTC name)
+ * @field: name of the mismatching field
+ * @format: printf-style format string describing the mismatch
+ *
+ * Helper used by atomic_sro_compare_state implementations and the
+ * STATE_CHECK_* macros to report a field-level mismatch between the
+ * committed state and the state read back from hardware.
+ */
+void __printf(4, 5)
+drm_atomic_helper_print_state_mismatch(struct drm_printer *p,
+				       const char *name,
+				       const char *field,
+				       const char *format, ...)
+{
+	struct va_format vaf;
+	va_list args;
+
+	va_start(args, format);
+	vaf.fmt = format;
+	vaf.va = &args;
+
+	drm_printf(p, "%s configuration mismatch in %s %pV\n", name, field, &vaf);
+
+	va_end(args);
+}
+EXPORT_SYMBOL(drm_atomic_helper_print_state_mismatch);
diff --git a/include/drm/drm_atomic_state_readout_helper.h b/include/drm/drm_atomic_state_readout_helper.h
index d685644ec25a..d4e941ab7452 100644
--- a/include/drm/drm_atomic_state_readout_helper.h
+++ b/include/drm/drm_atomic_state_readout_helper.h
@@ -4,7 +4,10 @@
 #define DRM_ATOMIC_SRO_HELPER_H_
 
+#include <linux/string_choices.h>
+
 struct drm_atomic_commit;
 struct drm_atomic_sro_state;
 struct drm_device;
+struct drm_printer;
 
 int
@@ -33,3 +36,220 @@ drm_atomic_helper_sro_build_state(struct drm_device *dev,
 int drm_atomic_helper_sro_readout_state(struct drm_device *dev);
 
+void __printf(4, 5)
+drm_atomic_helper_print_state_mismatch(struct drm_printer *p,
+				       const char *name,
+				       const char *field,
+				       const char *format, ...);
+
+#define STATE_CHECK_BOOL(r, p, n, sa, sb, f)				\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, bool),			\
+			      __stringify(f) " is not a bool");		\
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %s, got %s", \
+							       str_yes_no(sa->f), \
+							       str_yes_no(sb->f)); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_DISPLAY_MODE(r, p, n, sa, sb, f)			\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, struct drm_display_mode), \
+			      __stringify(f) " is not a drm_display_mode structure"); \
+		if (!drm_mode_equal(&sa->f, &sb->f)) {			\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected " DRM_MODE_FMT ", got " DRM_MODE_FMT, \
+							       DRM_MODE_ARG(&sa->f), \
+							       DRM_MODE_ARG(&sb->f)); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_INFOFRAME(r, p, n, sa, sb, f)			\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, union hdmi_infoframe), \
+			      __stringify(f) " is not an hdmi_infoframe union"); \
+		if (memcmp(&sa->f, &sb->f, sizeof(union hdmi_infoframe))) { \
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "infoframes don't match"); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_FORMAT_INFO(r, p, n, sa, sb, f)			\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, const struct drm_format_info *), \
+			      __stringify(f) " is not a drm_format_info pointer"); \
+		if (sa->f != sb->f) {			\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %p4cc, got %p4cc", \
+							       sa->f ? &sa->f->format : NULL, \
+							       sb->f ? &sb->f->format : NULL); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_PROPERTY_BLOB(r, p, n, sa, sb, f)			\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, struct drm_property_blob *), \
+			      __stringify(f) " is not a drm_property_blob pointer"); \
+		if (sa->f != sb->f &&					\
+		    (!sa->f || !sb->f ||				\
+		     sa->f->length != sb->f->length ||			\
+		     memcmp(sa->f->data, sb->f->data, sa->f->length))) { \
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "blobs don't match"); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_PTR(r, p, n, sa, sb, f)				\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %p, got %p",	\
+							       sa->f, sb->f); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_S32(r, p, n, sa, sb, f)				\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, (s32)0),		\
+			      __stringify(f) " is not an s32");		\
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %d, got %d", \
+							       sa->f, sb->f); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_S32_X(r, p, n, sa, sb, f)				\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, (s32)0),		\
+			      __stringify(f) " is not an s32");		\
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %x, got %x", \
+							       sa->f, sb->f); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_U16(r, p, n, sa, sb, f)				\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, (u16)0),		\
+			      __stringify(f) " is not a u16");		\
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %u, got %u", \
+							       sa->f, sb->f); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_U32(r, p, n, sa, sb, f)				\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, (u32)0),		\
+			      __stringify(f) " is not a u32");		\
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %u, got %u", \
+							       sa->f, sb->f); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_U32_16_16(r, p, n, sa, sb, f)			\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, (u32)0),		\
+			      __stringify(f) " is not a u32");		\
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %d.%06u, got %d.%06u", \
+							       sa->f >> 16, ((sa->f & 0xffff) * 15625) >> 10, \
+							       sb->f >> 16, ((sb->f & 0xffff) * 15625) >> 10); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_U32_X(r, p, n, sa, sb, f)				\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, (u32)0),		\
+			      __stringify(f) " is not a u32");		\
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %08x, got %08x", \
+							       sa->f, sb->f); \
+			r = false;					\
+		}							\
+	} while (0)
+
+#define STATE_CHECK_U64(r, p, n, sa, sb, f)				\
+	do {								\
+		static_assert(__same_type(sa->f, sb->f),		\
+			      __stringify(f) " field types don't match"); \
+		static_assert(__same_type(sa->f, (u64)0),		\
+			      __stringify(f) " is not a u64");		\
+		if (sa->f != sb->f) {					\
+			drm_atomic_helper_print_state_mismatch(p,	\
+							       n,	\
+							       __stringify(f), \
+							       "expected %llu, got %llu", \
+							       sa->f, sb->f); \
+			r = false;					\
+		}							\
+	} while (0)
+
 #endif /* DRM_ATOMIC_SRO_HELPER_H_ */

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 22/34] drm/atomic-state-helper: Provide atomic_compare_state helpers
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (20 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 21/34] drm/atomic-state-helper: Provide comparison macros Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  8:07   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 23/34] drm/atomic-helper: Document State Read-Out as a state initialization approach Maxime Ripard
                   ` (11 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

Now that we have introduced some new infrastructure to compare state
instances, provide helpers for the default states.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic_state_readout_helper.c | 263 ++++++++++++++++++++++
 drivers/gpu/drm/drm_bridge.c                      |  16 ++
 include/drm/drm_atomic_state_readout_helper.h     |  28 +++
 include/drm/drm_bridge.h                          |   5 +-
 include/drm/drm_connector.h                       |   5 +-
 include/drm/drm_crtc.h                            |   5 +-
 include/drm/drm_plane.h                           |   5 +-
 7 files changed, 323 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic_state_readout_helper.c b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
index 33f0f78c9b26..c4089afbc946 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
@@ -9,4 +9,5 @@
 #include <drm/drm_crtc.h>
 #include <drm/drm_encoder.h>
+#include <drm/drm_framebuffer.h>
 #include <drm/drm_modeset_helper_vtables.h>
 #include <drm/drm_plane.h>
@@ -681,4 +682,266 @@ int drm_atomic_helper_sro_readout_state(struct drm_device *dev)
 EXPORT_SYMBOL(drm_atomic_helper_sro_readout_state);
 
+static bool drm_atomic_helper_fb_compare(struct drm_printer *p,
+					 struct drm_framebuffer *expected,
+					 struct drm_framebuffer *actual)
+{
+	unsigned int i;
+	bool ret = true;
+
+	STATE_CHECK_FORMAT_INFO(ret, p, "framebuffer", expected, actual, format);
+
+	for (i = 0; i < expected->format->num_planes; i++) {
+		STATE_CHECK_U32(ret, p, "framebuffer", expected, actual, pitches[i]);
+		STATE_CHECK_U32(ret, p, "framebuffer", expected, actual, offsets[i]);
+	}
+
+	STATE_CHECK_U64(ret, p, "framebuffer", expected, actual, modifier);
+	STATE_CHECK_U32(ret, p, "framebuffer", expected, actual, width);
+	STATE_CHECK_U32(ret, p, "framebuffer", expected, actual, height);
+	STATE_CHECK_S32_X(ret, p, "framebuffer", expected, actual, flags);
+
+	return ret;
+}
+
+/**
+ * drm_atomic_helper_plane_compare_state - default &drm_plane_funcs.atomic_sro_compare_state hook
+ * @plane: the plane being compared
+ * @p: printer for reporting mismatches
+ * @expected: the committed &struct drm_plane_state
+ * @actual: the &struct drm_plane_state read out from hardware
+ *
+ * Compares the base &struct drm_plane_state fields of @actual to
+ * @expected and reports any mismatches through @p. Drivers subclassing
+ * the plane state should call this first, then compare their own fields.
+ *
+ * RETURNS:
+ *
+ * True if the states are identical, false otherwise.
+ */
+bool drm_atomic_helper_plane_compare_state(struct drm_plane *plane,
+					   struct drm_printer *p,
+					   struct drm_plane_state *expected,
+					   struct drm_plane_state *actual)
+{
+	bool ret = true;
+
+	STATE_CHECK_PTR(ret, p, plane->name, expected, actual, plane);
+	STATE_CHECK_PTR(ret, p, plane->name, expected, actual, crtc);
+
+	/* Both disabled, geometry is meaningless. */
+	if (!expected->fb && !actual->fb)
+		return ret;
+
+	/* One enabled and the other disabled, that's a mismatch. */
+	if (!expected->fb || !actual->fb) {
+		drm_atomic_helper_print_state_mismatch(p,
+						       plane->name,
+						       "fb",
+						       "expected framebuffer is %s, got %s",
+						       expected->fb ? "non-NULL" : "NULL",
+						       actual->fb ? "non-NULL" : "NULL");
+		return false;
+	}
+
+	/* Both enabled, compare framebuffer and geometry. */
+	if (!drm_atomic_helper_fb_compare(p, expected->fb, actual->fb))
+		ret = false;
+
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, crtc_x);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, crtc_y);
+	STATE_CHECK_U32(ret, p, plane->name, expected, actual, crtc_w);
+	STATE_CHECK_U32(ret, p, plane->name, expected, actual, crtc_h);
+	STATE_CHECK_U32_16_16(ret, p, plane->name, expected, actual, src_x);
+	STATE_CHECK_U32_16_16(ret, p, plane->name, expected, actual, src_y);
+	STATE_CHECK_U32_16_16(ret, p, plane->name, expected, actual, src_w);
+	STATE_CHECK_U32_16_16(ret, p, plane->name, expected, actual, src_h);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, hotspot_x);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, hotspot_y);
+	STATE_CHECK_U16(ret, p, plane->name, expected, actual, alpha);
+	STATE_CHECK_U16(ret, p, plane->name, expected, actual, pixel_blend_mode);
+	STATE_CHECK_U32(ret, p, plane->name, expected, actual, rotation);
+	STATE_CHECK_U32(ret, p, plane->name, expected, actual, zpos);
+	STATE_CHECK_U32(ret, p, plane->name, expected, actual, normalized_zpos);
+	STATE_CHECK_U32(ret, p, plane->name, expected, actual, color_encoding);
+	STATE_CHECK_U32(ret, p, plane->name, expected, actual, color_range);
+
+	STATE_CHECK_PROPERTY_BLOB(ret, p, plane->name, expected, actual, fb_damage_clips);
+	STATE_CHECK_BOOL(ret, p, plane->name, expected, actual, ignore_damage_clips);
+	STATE_CHECK_PTR(ret, p, plane->name, expected, actual, color_pipeline);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, src.x1);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, src.x2);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, src.y1);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, src.y2);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, dst.x1);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, dst.x2);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, dst.y1);
+	STATE_CHECK_S32(ret, p, plane->name, expected, actual, dst.y2);
+	STATE_CHECK_BOOL(ret, p, plane->name, expected, actual, visible);
+	STATE_CHECK_U32(ret, p, plane->name, expected, actual, scaling_filter);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_atomic_helper_plane_compare_state);
+
+/**
+ * drm_atomic_helper_crtc_compare_state - default &drm_crtc_funcs.atomic_sro_compare_state hook
+ * @crtc: the CRTC being compared
+ * @p: printer for reporting mismatches
+ * @expected: the committed &struct drm_crtc_state
+ * @actual: the &struct drm_crtc_state read out from hardware
+ *
+ * Compares the base &struct drm_crtc_state fields of @actual to
+ * @expected and reports any mismatches through @p. Drivers subclassing
+ * the CRTC state should call this first, then compare their own fields.
+ *
+ * RETURNS:
+ *
+ * True if the states are identical, false otherwise.
+ */
+bool drm_atomic_helper_crtc_compare_state(struct drm_crtc *crtc,
+					  struct drm_printer *p,
+					  struct drm_crtc_state *expected,
+					  struct drm_crtc_state *actual)
+{
+	bool ret = true;
+
+	STATE_CHECK_PTR(ret, p, crtc->name, expected, actual, crtc);
+	STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, enable);
+	STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, active);
+
+	/* Both inactive, mode and properties are meaningless. */
+	if (!expected->active && !actual->active)
+		return ret;
+
+	/* One active and the other inactive, that's a mismatch. */
+	if (!expected->active || !actual->active)
+		return false;
+
+	/* Both active, compare everything. */
+	STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, no_vblank);
+	STATE_CHECK_U32(ret, p, crtc->name, expected, actual, plane_mask);
+	STATE_CHECK_U32(ret, p, crtc->name, expected, actual, connector_mask);
+	STATE_CHECK_U32(ret, p, crtc->name, expected, actual, encoder_mask);
+
+	STATE_CHECK_DISPLAY_MODE(ret, p, crtc->name, expected, actual, mode);
+	STATE_CHECK_DISPLAY_MODE(ret, p, crtc->name, expected, actual, adjusted_mode);
+	STATE_CHECK_PROPERTY_BLOB(ret, p, crtc->name, expected, actual, mode_blob);
+	STATE_CHECK_PROPERTY_BLOB(ret, p, crtc->name, expected, actual, degamma_lut);
+	STATE_CHECK_PROPERTY_BLOB(ret, p, crtc->name, expected, actual, ctm);
+	STATE_CHECK_PROPERTY_BLOB(ret, p, crtc->name, expected, actual, gamma_lut);
+	STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, vrr_enabled);
+	STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, self_refresh_active);
+	STATE_CHECK_U32(ret, p, crtc->name, expected, actual, scaling_filter);
+	STATE_CHECK_U64(ret, p, crtc->name, expected, actual, background_color);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_atomic_helper_crtc_compare_state);
+
+/**
+ * drm_atomic_helper_connector_compare_state - default &drm_connector_funcs.atomic_sro_compare_state hook
+ * @conn: the connector being compared
+ * @p: printer for reporting mismatches
+ * @expected: the committed &struct drm_connector_state
+ * @actual: the &struct drm_connector_state read out from hardware
+ *
+ * Compares the base &struct drm_connector_state fields of @actual to
+ * @expected and reports any mismatches through @p. Drivers subclassing
+ * the connector state should call this first, then compare their own
+ * fields.
+ *
+ * RETURNS:
+ *
+ * True if the states are identical, false otherwise.
+ */
+bool drm_atomic_helper_connector_compare_state(struct drm_connector *conn,
+					       struct drm_printer *p,
+					       struct drm_connector_state *expected,
+					       struct drm_connector_state *actual)
+{
+	bool ret = true;
+
+	STATE_CHECK_PTR(ret, p, conn->name, expected, actual, connector);
+	STATE_CHECK_PTR(ret, p, conn->name, expected, actual, crtc);
+	STATE_CHECK_PTR(ret, p, conn->name, expected, actual, best_encoder);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, link_status);
+
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, tv.select_subconnector);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, tv.subconnector);
+
+	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, self_refresh_aware);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, picture_aspect_ratio);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, content_type);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdcp_content_type);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, scaling_mode);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, content_protection);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, colorspace);
+
+	/*
+	 * NOTE: We can't check max_bpc, max_requested_bpc and
+	 * color_format because they are userspace requests negotiated
+	 * during atomic_check into output_bpc and output_format. The
+	 * hardware only stores the negotiated result.
+	 */
+
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, privacy_screen_sw_state);
+	STATE_CHECK_PROPERTY_BLOB(ret, p, conn->name, expected, actual, hdr_output_metadata);
+
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdmi.broadcast_rgb);
+	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.avi.set);
+	STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.avi.data);
+	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.hdr_drm.set);
+	STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.hdr_drm.data);
+	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.spd.set);
+	STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.spd.data);
+	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.hdmi.set);
+	STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.hdmi.data);
+	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.is_limited_range);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdmi.output_bpc);
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdmi.output_format);
+	STATE_CHECK_U64(ret, p, conn->name, expected, actual, hdmi.tmds_char_rate);
+
+	/*
+	 * hdmi.scrambler_needed is derived from hdmi.tmds_char_rate, so
+	 * comparing tmds_char_rate already covers it.
+	 */
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_atomic_helper_connector_compare_state);
+
+/**
+ * drm_atomic_helper_bridge_compare_state - default &drm_bridge_funcs.atomic_sro_compare_state hook
+ * @bridge: the bridge being compared
+ * @p: printer for reporting mismatches
+ * @expected: the committed &struct drm_bridge_state
+ * @actual: the &struct drm_bridge_state read out from hardware
+ *
+ * Compares the base &struct drm_bridge_state fields of @actual to
+ * @expected and reports any mismatches through @p. Drivers subclassing
+ * the bridge state should call this first, then compare their own
+ * fields.
+ *
+ * RETURNS:
+ *
+ * True if the states are identical, false otherwise.
+ */
+bool drm_atomic_helper_bridge_compare_state(struct drm_bridge *bridge,
+					    struct drm_printer *p,
+					    struct drm_bridge_state *expected,
+					    struct drm_bridge_state *actual)
+{
+	bool ret = true;
+
+	STATE_CHECK_PTR(ret, p, bridge->name, expected, actual, bridge);
+	STATE_CHECK_U32_X(ret, p, bridge->name, expected, actual, input_bus_cfg.format);
+	STATE_CHECK_U32_X(ret, p, bridge->name, expected, actual, input_bus_cfg.flags);
+	STATE_CHECK_U32_X(ret, p, bridge->name, expected, actual, output_bus_cfg.format);
+	STATE_CHECK_U32_X(ret, p, bridge->name, expected, actual, output_bus_cfg.flags);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_atomic_helper_bridge_compare_state);
+
 /**
  * drm_atomic_helper_print_state_mismatch - report a state comparison mismatch
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 1c16e637b97e..45a34bbb8f14 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -630,4 +630,19 @@ drm_bridge_atomic_readout_priv_state(struct drm_private_obj *obj,
 }
 
+static bool drm_bridge_atomic_compare_priv_state(struct drm_private_obj *obj,
+						 struct drm_printer *p,
+						 struct drm_private_state *a,
+						 struct drm_private_state *b)
+{
+	struct drm_bridge *bridge = drm_priv_to_bridge(obj);
+	struct drm_bridge_state *state_a = drm_priv_to_bridge_state(a);
+	struct drm_bridge_state *state_b = drm_priv_to_bridge_state(b);
+
+	if (bridge->funcs->atomic_sro_compare_state)
+		return bridge->funcs->atomic_sro_compare_state(bridge, p, state_a, state_b);
+	else
+		return false;
+}
+
 static void
 drm_bridge_atomic_print_priv_state(struct drm_printer *p,
@@ -658,4 +673,5 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
 
 static const struct drm_private_state_funcs drm_bridge_priv_state_funcs_with_sro = {
+	.atomic_sro_compare_state = drm_bridge_atomic_compare_priv_state,
 	.atomic_sro_readout_state = drm_bridge_atomic_readout_priv_state,
 	.atomic_create_state = drm_bridge_atomic_create_priv_state,
diff --git a/include/drm/drm_atomic_state_readout_helper.h b/include/drm/drm_atomic_state_readout_helper.h
index d4e941ab7452..2dd871787837 100644
--- a/include/drm/drm_atomic_state_readout_helper.h
+++ b/include/drm/drm_atomic_state_readout_helper.h
@@ -8,5 +8,13 @@
 struct drm_atomic_commit;
 struct drm_atomic_sro_state;
+struct drm_bridge;
+struct drm_bridge_state;
+struct drm_connector;
+struct drm_connector_state;
+struct drm_crtc;
+struct drm_crtc_state;
 struct drm_device;
+struct drm_plane;
+struct drm_plane_state;
 struct drm_printer;
 
@@ -36,4 +44,24 @@ drm_atomic_helper_sro_build_state(struct drm_device *dev,
 int drm_atomic_helper_sro_readout_state(struct drm_device *dev);
 
+bool drm_atomic_helper_connector_compare_state(struct drm_connector *connector,
+					       struct drm_printer *p,
+					       struct drm_connector_state *expected,
+					       struct drm_connector_state *actual);
+
+bool drm_atomic_helper_crtc_compare_state(struct drm_crtc *crtc,
+					  struct drm_printer *p,
+					  struct drm_crtc_state *expected,
+					  struct drm_crtc_state *actual);
+
+bool drm_atomic_helper_plane_compare_state(struct drm_plane *plane,
+					   struct drm_printer *p,
+					   struct drm_plane_state *expected,
+					   struct drm_plane_state *actual);
+
+bool drm_atomic_helper_bridge_compare_state(struct drm_bridge *bridge,
+					    struct drm_printer *p,
+					    struct drm_bridge_state *expected,
+					    struct drm_bridge_state *actual);
+
 void __printf(4, 5)
 drm_atomic_helper_print_state_mismatch(struct drm_printer *p,
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 030467e2cb1e..436dfffb25ad 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -325,5 +325,8 @@ struct drm_bridge_funcs {
 	 * It is called after blocking commits to verify that the
 	 * committed state matches what can be read back from the
-	 * hardware.
+	 * hardware. Drivers subclassing the state should implement this
+	 * to compare their driver-private fields, calling
+	 * drm_atomic_helper_bridge_compare_state() first for the base
+	 * state fields.
 	 *
 	 * This hook is mandatory for drivers implementing SRO, but can
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 3892a20a6cca..6d10b9543a78 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1915,5 +1915,8 @@ struct drm_connector_funcs {
 	 * It is called after blocking commits to verify that the
 	 * committed state matches what can be read back from the
-	 * hardware.
+	 * hardware. Drivers subclassing the state should implement this
+	 * to compare their driver-private fields, calling
+	 * drm_atomic_helper_connector_compare_state() first for the
+	 * base state fields.
 	 *
 	 * This hook is mandatory for drivers implementing SRO, but can
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index e5662ddb61fb..a327d858c465 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -744,5 +744,8 @@ struct drm_crtc_funcs {
 	 * It is called after blocking commits to verify that the
 	 * committed state matches what can be read back from the
-	 * hardware.
+	 * hardware. Drivers subclassing the state should implement this
+	 * to compare their driver-private fields, calling
+	 * drm_atomic_helper_crtc_compare_state() first for the base
+	 * state fields.
 	 *
 	 * This hook is mandatory for drivers implementing SRO, but can
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index 0a18d26eb397..05d73102fef4 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -496,5 +496,8 @@ struct drm_plane_funcs {
 	 * It is called after blocking commits to verify that the
 	 * committed state matches what can be read back from the
-	 * hardware.
+	 * hardware. Drivers subclassing the state should implement this
+	 * to compare their driver-private fields, calling
+	 * drm_atomic_helper_plane_compare_state() first for the base
+	 * state fields.
 	 *
 	 * This hook is mandatory for drivers implementing SRO, but can

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 23/34] drm/atomic-helper: Document State Read-Out as a state initialization approach
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (21 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 22/34] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 24/34] drm/tests: kunit: Add a drm_printer helper Maxime Ripard
                   ` (10 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The "atomic state reset and initialization" DOC section describes the
problem of needing correct atomic state at driver load and after
suspend, but only mentions the reset approach and a vague reference to
i915's read-out as an alternative.

Now that the State Read-Out (SRO) infrastructure provides a generic
solution, update the documentation to describe both approaches and
cross-reference the SRO section. Also mention
drm_mode_config_create_initial_state() which combines both.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_atomic_state_helper.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 267f44330629..c5bf70e6ad53 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -48,11 +48,19 @@
  * and correct atomic software state for all connectors, CRTCs and planes
  * available. Which is a bit a problem on driver load and also after system
- * suspend. One way to solve this is to have a hardware state read-out
- * infrastructure which reconstructs the full software state (e.g. the i915
- * driver).
+ * suspend. There are two ways to solve this:
  *
- * The simpler solution is to just reset the software state to everything off,
- * which is easiest to do by calling drm_mode_config_reset(). To facilitate this
- * the atomic helpers provide default reset implementations for all hooks.
+ * The first is to use the :ref:`State Read-Out (SRO) <drm_atomic_sro>`
+ * infrastructure to reconstruct the full software state from hardware
+ * registers. This enables flicker-free boot when the firmware already
+ * configured the display.
+ *
+ * The simpler alternative is to just reset the software state to everything
+ * off, which is easiest to do by calling drm_mode_config_reset(). To
+ * facilitate this the atomic helpers provide default reset implementations for
+ * all hooks.
+ *
+ * drm_mode_config_create_initial_state() combines both approaches: it tries
+ * SRO first, and falls back to a hardware reset followed by
+ * drm_mode_config_create_state().
  *
  * On the upside the precise state tracking of atomic simplifies system suspend

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 24/34] drm/tests: kunit: Add a drm_printer helper
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (22 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 23/34] drm/atomic-helper: Document State Read-Out as a state initialization approach Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:56   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 25/34] drm/tests: Add tests for the atomic state readout Maxime Ripard
                   ` (9 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

Some DRM helpers report the state of an object through a drm_printer,
most notably to report which fields of two states differ when comparing
them.

Tests exercising such code paths need to pass a drm_printer as well, and
would rather have its output show up in the KTAP log of the test than
anywhere else, so that a failure comes with the report that caused it.

Add drm_kunit_helper_printer() that returns a drm_printer whose messages
are logged through kunit_log() at the LOGLEVEL_* level requested by the
caller.

Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tests/drm_kunit_helpers.c | 78 +++++++++++++++++++++++++++++++
 include/drm/drm_kunit_helpers.h           |  4 ++
 2 files changed, 82 insertions(+)

diff --git a/drivers/gpu/drm/tests/drm_kunit_helpers.c b/drivers/gpu/drm/tests/drm_kunit_helpers.c
index 63d024946500..fad911772ee5 100644
--- a/drivers/gpu/drm/tests/drm_kunit_helpers.c
+++ b/drivers/gpu/drm/tests/drm_kunit_helpers.c
@@ -9,4 +9,5 @@
 #include <drm/drm_kunit_helpers.h>
 #include <drm/drm_managed.h>
+#include <drm/drm_print.h>
 
 #include <kunit/device.h>
@@ -19,4 +20,81 @@
 #define KUNIT_DEVICE_NAME	"drm-kunit-mock-device"
 
+struct drm_printer_ctx {
+	struct drm_printer	p;
+	int			level;
+	struct kunit		*test;
+};
+
+/*
+ * kunit_log() is a macro that only accepts a compile-time log level,
+ * so the level has to be dispatched to the matching kunit_log() call.
+ */
+static void drm_kunit_printf(struct drm_printer *p, struct va_format *vaf)
+{
+	struct drm_printer_ctx *ctx = p->arg;
+
+	switch (ctx->level) {
+	case LOGLEVEL_EMERG:
+		kunit_log(KERN_EMERG, ctx->test, "%pV", vaf);
+		break;
+	case LOGLEVEL_ALERT:
+		kunit_log(KERN_ALERT, ctx->test, "%pV", vaf);
+		break;
+	case LOGLEVEL_CRIT:
+		kunit_log(KERN_CRIT, ctx->test, "%pV", vaf);
+		break;
+	case LOGLEVEL_ERR:
+		kunit_log(KERN_ERR, ctx->test, "%pV", vaf);
+		break;
+	case LOGLEVEL_WARNING:
+		kunit_log(KERN_WARNING, ctx->test, "%pV", vaf);
+		break;
+	case LOGLEVEL_NOTICE:
+		kunit_log(KERN_NOTICE, ctx->test, "%pV", vaf);
+		break;
+	case LOGLEVEL_DEBUG:
+		kunit_log(KERN_DEBUG, ctx->test, "%pV", vaf);
+		break;
+	case LOGLEVEL_INFO:
+	default:
+		kunit_log(KERN_INFO, ctx->test, "%pV", vaf);
+		break;
+	}
+}
+
+/**
+ * drm_kunit_helper_printer - Creates a &drm_printer logging into a KUnit test log
+ * @test: The test context object
+ * @level: The log level to log the messages at, one of the KERN_* constants
+ *
+ * Creates a &struct drm_printer that will log anything printed through
+ * it, using kunit_log(), both to the kernel log at the @level log
+ * level and to the log of the KUnit test @test.
+ *
+ * This is the KUnit counterpart of the drm_print.h printers like
+ * drm_info_printer() or drm_err_printer(), and @level should be the
+ * log level of the printer it replaces.
+ *
+ * Returns:
+ * A pointer to the &drm_printer, or an ERR_PTR() otherwise.
+ */
+struct drm_printer *
+drm_kunit_helper_printer(struct kunit *test, int level)
+{
+	struct drm_printer_ctx *ctx;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return ERR_PTR(-ENOMEM);
+
+	ctx->level = level;
+	ctx->p.printfn = drm_kunit_printf;
+	ctx->p.arg = ctx;
+	ctx->test = test;
+
+	return &ctx->p;
+}
+EXPORT_SYMBOL_GPL(drm_kunit_helper_printer);
+
 static const struct drm_mode_config_funcs drm_mode_config_funcs = {
 	.atomic_check	= drm_atomic_helper_check,
diff --git a/include/drm/drm_kunit_helpers.h b/include/drm/drm_kunit_helpers.h
index e653bc7b1e40..d38614d87678 100644
--- a/include/drm/drm_kunit_helpers.h
+++ b/include/drm/drm_kunit_helpers.h
@@ -16,6 +16,10 @@ struct drm_device;
 struct drm_plane_funcs;
 struct drm_plane_helper_funcs;
+struct drm_printer;
 struct kunit;
 
+struct drm_printer *drm_kunit_helper_printer(struct kunit *test,
+					     int level);
+
 struct device *drm_kunit_helper_alloc_device(struct kunit *test);
 void drm_kunit_helper_free_device(struct kunit *test, struct device *dev);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 25/34] drm/tests: Add tests for the atomic state readout
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (23 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 24/34] drm/tests: kunit: Add a drm_printer helper Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  8:03   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 26/34] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
                   ` (8 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The atomic state readout framework allows a driver to read the state of
its hardware into atomic states, either to create the initial state of
a device whose outputs have already been enabled, or to compare a
commit against the current hardware state.

Add a KUnit test suite covering:

- the drm_atomic_sro_state container API: allocation, per-object get
 and set, and installation of the read-out states into a drm_device;
- drm_atomic_helper_sro_readout_state(), including the cases where
 the readout isn't possible (-ENOTSUPP) because the mode
 configuration or helper hooks are missing, and error propagation
 from the readout hooks;
- drm_atomic_sro_readout_and_compare() and the per-object state
 comparison helpers for planes, CRTCs, connectors and bridges,
 including the mismatch reporting through a drm_printer;
- drm_mode_config_create_initial_state(), including its error paths
 and the fallback to the regular initialization path when the
 readout isn't available.

The tests are built on a mock DRM device whose per-object function
tables either implement or omit the readout hooks, with dedicated
variants to inject errors from them.

Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tests/Makefile                     |    1 +
 .../gpu/drm/tests/drm_atomic_state_readout_test.c  | 1501 ++++++++++++++++++++
 2 files changed, 1502 insertions(+)

diff --git a/drivers/gpu/drm/tests/Makefile b/drivers/gpu/drm/tests/Makefile
index 42207df1d1c8..b11a07094b32 100644
--- a/drivers/gpu/drm/tests/Makefile
+++ b/drivers/gpu/drm/tests/Makefile
@@ -7,4 +7,5 @@ obj-$(CONFIG_DRM_KUNIT_TEST) += \
 	drm_atomic_test.o \
 	drm_atomic_commit_test.o \
+	drm_atomic_state_readout_test.o \
 	drm_bridge_test.o \
 	drm_cmdline_parser_test.o \
diff --git a/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c b/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c
new file mode 100644
index 000000000000..ada512f7b32b
--- /dev/null
+++ b/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c
@@ -0,0 +1,1501 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kunit test for the DRM atomic state readout infrastructure
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_atomic_state_readout_helper.h>
+#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_kunit_helpers.h>
+#include <drm/drm_mode_config.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include <kunit/test.h>
+
+#include <linux/media-bus-format.h>
+
+/*
+ * Those tests assume that the atomic_readout module parameter of
+ * drm_atomic_state_readout is left to its default value (enabled).
+ */
+
+struct sro_test_priv {
+	struct drm_device drm;
+	struct drm_plane *plane;
+	struct drm_crtc *crtc;
+	struct drm_encoder encoder;
+	struct drm_connector connector;
+	struct drm_private_obj private_obj;
+	struct drm_bridge bridge;
+
+	unsigned int connector_destroy_count;
+	unsigned int private_obj_destroy_count;
+	bool connector_readout_called;
+	bool hw_reset_called;
+	int readout_ret;
+
+	/* KUnit context, for the hooks that can't get it as an argument */
+	struct kunit *test;
+};
+
+/*
+ * The tests run with the mock hardware state described by those
+ * constants.
+ */
+#define SRO_TEST_CRTC_X		42
+#define SRO_TEST_CRTC_Y		24
+#define SRO_TEST_MODE_VIC	16	/* 1920x1080@60Hz */
+#define SRO_TEST_OTHER_MODE_VIC	4	/* 640x480@60Hz */
+
+static int mock_plane_sro_readout_state(struct drm_plane *plane,
+					struct drm_atomic_sro_state *state,
+					struct drm_plane_state *plane_state)
+{
+	plane_state->visible = true;
+	plane_state->crtc_x = SRO_TEST_CRTC_X;
+	plane_state->crtc_y = SRO_TEST_CRTC_Y;
+
+	return 0;
+}
+
+static const struct drm_plane_funcs mock_plane_with_sro_funcs = {
+	.atomic_create_state		= drm_atomic_helper_plane_create_state,
+	.atomic_destroy_state		= drm_atomic_helper_plane_destroy_state,
+	.atomic_duplicate_state		= drm_atomic_helper_plane_duplicate_state,
+	.atomic_sro_readout_state	= mock_plane_sro_readout_state,
+	.atomic_sro_compare_state	= drm_atomic_helper_plane_compare_state,
+};
+
+static int mock_crtc_sro_readout_state(struct drm_crtc *crtc,
+				       struct drm_atomic_sro_state *state,
+				       struct drm_crtc_state *crtc_state)
+{
+	struct sro_test_priv *priv =
+		container_of(crtc->dev, struct sro_test_priv, drm);
+	struct drm_display_mode *mode;
+	int ret;
+
+	mode = drm_kunit_display_mode_from_cea_vic(priv->test, crtc->dev,
+						   SRO_TEST_MODE_VIC);
+	if (!mode)
+		return -EINVAL;
+
+	/*
+	 * drm_atomic_set_mode_for_crtc() also sets the mode blob an
+	 * enabled CRTC needs to pass the atomic checks.
+	 */
+	ret = drm_atomic_set_mode_for_crtc(crtc_state, mode);
+	if (ret)
+		return ret;
+
+	crtc_state->active = true;
+
+	/* The connector read out is routed to that CRTC */
+	crtc_state->connector_mask = drm_connector_mask(&priv->connector);
+
+	return 0;
+}
+
+static const struct drm_crtc_funcs mock_crtc_with_sro_funcs = {
+	.atomic_create_state		= drm_atomic_helper_crtc_create_state,
+	.atomic_destroy_state		= drm_atomic_helper_crtc_destroy_state,
+	.atomic_duplicate_state		= drm_atomic_helper_crtc_duplicate_state,
+	.atomic_sro_readout_state	= mock_crtc_sro_readout_state,
+	.atomic_sro_compare_state	= drm_atomic_helper_crtc_compare_state,
+};
+
+static int mock_connector_sro_readout_state(struct drm_connector *conn,
+					    struct drm_atomic_sro_state *state,
+					    struct drm_connector_state *conn_state)
+{
+	struct sro_test_priv *priv =
+		container_of(conn, struct sro_test_priv, connector);
+	struct drm_crtc *crtc;
+
+	priv->connector_readout_called = true;
+
+	/*
+	 * The CRTC states must be read out before the connectors so
+	 * that the connector readout hooks can find them.
+	 */
+	drm_for_each_crtc(crtc, conn->dev) {
+		if (!drm_atomic_sro_get_crtc_state(state, crtc))
+			continue;
+
+		/*
+		 * drm_atomic_set_crtc_for_connector() takes a connector
+		 * reference when setting the crtc pointer, which is
+		 * later released by
+		 * __drm_atomic_helper_connector_destroy_state(). The
+		 * readout bypasses that path, so take the reference
+		 * explicitly.
+		 */
+		drm_connector_get(conn);
+		conn_state->crtc = crtc;
+		conn_state->best_encoder = &priv->encoder;
+	}
+
+	if (!conn_state->crtc)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void mock_connector_destroy_state(struct drm_connector *conn,
+					 struct drm_connector_state *conn_state)
+{
+	struct sro_test_priv *priv =
+		container_of(conn, struct sro_test_priv, connector);
+
+	priv->connector_destroy_count++;
+
+	drm_atomic_helper_connector_destroy_state(conn, conn_state);
+}
+
+static const struct drm_connector_funcs mock_connector_with_sro_funcs = {
+	.atomic_create_state		= drm_atomic_helper_connector_create_state,
+	.atomic_destroy_state		= mock_connector_destroy_state,
+	.atomic_duplicate_state		= drm_atomic_helper_connector_duplicate_state,
+	.atomic_sro_readout_state	= mock_connector_sro_readout_state,
+	.atomic_sro_compare_state	= drm_atomic_helper_connector_compare_state,
+};
+
+static int mock_connector_sro_readout_err(struct drm_connector *conn,
+					  struct drm_atomic_sro_state *state,
+					  struct drm_connector_state *conn_state)
+{
+	return -EINVAL;
+}
+
+static const struct drm_connector_funcs mock_connector_with_err_funcs = {
+	.atomic_create_state		= drm_atomic_helper_connector_create_state,
+	.atomic_destroy_state		= mock_connector_destroy_state,
+	.atomic_duplicate_state		= drm_atomic_helper_connector_duplicate_state,
+	.atomic_sro_readout_state	= mock_connector_sro_readout_err,
+	.atomic_sro_compare_state	= drm_atomic_helper_connector_compare_state,
+};
+
+static const struct drm_connector_helper_funcs mock_connector_helper_funcs = {
+};
+
+/*
+ * The private object state is subclassed so that the tests can check
+ * that it went through the readout hook.
+ */
+struct mock_privobj_state {
+	struct drm_private_state base;
+	bool readout;
+};
+
+static inline struct mock_privobj_state *
+to_mock_privobj_state(struct drm_private_state *state)
+{
+	return container_of(state, struct mock_privobj_state, base);
+}
+
+static struct drm_private_state *
+mock_private_obj_create_state(struct drm_private_obj *obj)
+{
+	struct mock_privobj_state *privobj_state;
+
+	privobj_state = kzalloc_obj(*privobj_state);
+	if (!privobj_state)
+		return ERR_PTR(-ENOMEM);
+
+	__drm_atomic_helper_private_obj_create_state(obj,
+						     &privobj_state->base);
+
+	return &privobj_state->base;
+}
+
+static void mock_private_obj_destroy_state(struct drm_private_obj *obj,
+					   struct drm_private_state *obj_state)
+{
+	struct sro_test_priv *priv =
+		container_of(obj, struct sro_test_priv, private_obj);
+
+	priv->private_obj_destroy_count++;
+
+	kfree(obj_state);
+}
+
+static int mock_private_obj_sro_readout_state(struct drm_private_obj *obj,
+					      struct drm_atomic_sro_state *state,
+					      struct drm_private_state *obj_state)
+{
+	to_mock_privobj_state(obj_state)->readout = true;
+
+	return 0;
+}
+
+static bool mock_private_obj_compare_state(struct drm_private_obj *obj,
+					   struct drm_printer *p,
+					   struct drm_private_state *expected,
+					   struct drm_private_state *actual)
+{
+	return to_mock_privobj_state(expected)->readout ==
+		to_mock_privobj_state(actual)->readout;
+}
+
+static const struct drm_private_state_funcs mock_private_obj_with_sro_funcs = {
+	.atomic_create_state		= mock_private_obj_create_state,
+	.atomic_destroy_state		= mock_private_obj_destroy_state,
+	.atomic_sro_readout_state	= mock_private_obj_sro_readout_state,
+	.atomic_sro_compare_state	= mock_private_obj_compare_state,
+};
+
+static const struct drm_bridge_funcs mock_bridge_funcs = {
+	.atomic_create_state		= drm_atomic_helper_bridge_create_state,
+	.atomic_destroy_state		= drm_atomic_helper_bridge_destroy_state,
+};
+
+static void mock_private_obj_fini(void *ptr)
+{
+	drm_atomic_private_obj_fini(ptr);
+}
+
+static void mock_hw_reset(struct drm_device *drm)
+{
+	struct sro_test_priv *priv =
+		container_of(drm, struct sro_test_priv, drm);
+
+	priv->hw_reset_called = true;
+}
+
+static int mock_modeset_commit(struct drm_device *drm,
+			       struct drm_atomic_commit *commit,
+			       bool nonblock)
+{
+	struct sro_test_priv *priv =
+		container_of(drm, struct sro_test_priv, drm);
+
+	KUNIT_FAIL(priv->test, "unexpected modeset");
+
+	return 0;
+}
+
+/*
+ * The atomic_commit hook is a tripwire: reading out the hardware
+ * state and installing it is a state read, not a modeset, so nothing
+ * must ever go through the atomic commit machinery. atomic_check is
+ * only there so that a commit doesn't crash before reaching the
+ * tripwire.
+ */
+static const struct drm_mode_config_funcs mock_mode_config_with_sro_funcs = {
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = mock_modeset_commit,
+	.atomic_sro_readout_state = drm_atomic_helper_sro_readout_state,
+	.hw_reset = mock_hw_reset,
+};
+
+static int mock_device_readout(struct drm_device *drm)
+{
+	struct sro_test_priv *priv =
+		container_of(drm, struct sro_test_priv, drm);
+	struct drm_atomic_sro_state *sro_state;
+	struct drm_crtc_state *crtc_state;
+
+	if (priv->readout_ret)
+		return priv->readout_ret;
+
+	sro_state = drm_atomic_sro_state_alloc(drm);
+	if (IS_ERR(sro_state))
+		return PTR_ERR(sro_state);
+
+	crtc_state = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	if (IS_ERR(crtc_state)) {
+		drm_atomic_sro_state_free(sro_state);
+		return PTR_ERR(crtc_state);
+	}
+
+	crtc_state->enable = true;
+	crtc_state->active = true;
+
+	drm_atomic_sro_set_crtc_state(sro_state, priv->crtc, crtc_state);
+	drm_atomic_sro_install_state(sro_state);
+	drm_atomic_sro_state_free(sro_state);
+
+	return 0;
+}
+
+static const struct drm_mode_config_funcs mock_mode_config_with_sro_hw_reset_funcs = {
+	.atomic_sro_readout_state = mock_device_readout,
+	.hw_reset = mock_hw_reset,
+};
+
+static const struct drm_mode_config_funcs mock_mode_config_without_sro_with_hw_reset_funcs = {
+	.hw_reset = mock_hw_reset,
+};
+
+static const struct drm_mode_config_helper_funcs mock_mode_config_helper_without_sro_funcs = {
+};
+
+static const struct drm_mode_config_helper_funcs mock_mode_config_helper_with_sro_funcs = {
+	.atomic_sro_build_state = drm_atomic_helper_sro_build_state,
+};
+
+static struct drm_atomic_sro_state *
+mock_build_state_error(struct drm_device *dev,
+		       struct drm_atomic_commit *commit)
+{
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct drm_mode_config_helper_funcs mock_mode_config_helper_with_err_funcs = {
+	.atomic_sro_build_state = mock_build_state_error,
+};
+
+/*
+ * Passing NULL for any of the object funcs will make the KUnit
+ * helpers use their defaults, which don't implement the state
+ * readout hooks.
+ */
+static struct sro_test_priv *
+mock_device_create(struct kunit *test,
+		   const struct drm_mode_config_funcs *mode_config_funcs,
+		   const struct drm_mode_config_helper_funcs *helper_funcs,
+		   const struct drm_plane_funcs *plane_funcs,
+		   const struct drm_crtc_funcs *crtc_funcs,
+		   const struct drm_connector_funcs *connector_funcs)
+{
+	struct sro_test_priv *priv;
+	struct drm_device *drm;
+	struct device *dev;
+	int ret;
+
+	dev = drm_kunit_helper_alloc_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+	priv = drm_kunit_helper_alloc_drm_device(test, dev,
+						 struct sro_test_priv, drm,
+						 DRIVER_MODESET |
+						 DRIVER_ATOMIC);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+	drm = &priv->drm;
+
+	priv->test = test;
+
+	drm->mode_config.funcs = mode_config_funcs;
+	drm->mode_config.helper_private = helper_funcs;
+
+	priv->plane = drm_kunit_helper_create_primary_plane(test, drm,
+							    plane_funcs,
+							    NULL,
+							    NULL, 0, NULL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->plane);
+
+	priv->crtc = drm_kunit_helper_create_crtc(test, drm,
+						  priv->plane, NULL,
+						  crtc_funcs,
+						  NULL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->crtc);
+
+	ret = drmm_encoder_init(drm, &priv->encoder, NULL,
+				DRM_MODE_ENCODER_NONE, NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	priv->encoder.possible_crtcs = drm_crtc_mask(priv->crtc);
+
+	ret = drmm_connector_init(drm, &priv->connector,
+				  connector_funcs,
+				  DRM_MODE_CONNECTOR_VIRTUAL, NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	drm_connector_helper_add(&priv->connector,
+				 &mock_connector_helper_funcs);
+
+	drm_connector_attach_encoder(&priv->connector, &priv->encoder);
+
+	drm_atomic_private_obj_init(drm, &priv->private_obj,
+				    "test private obj",
+				    &mock_private_obj_with_sro_funcs);
+	kunit_add_action_or_reset(test, mock_private_obj_fini,
+				  &priv->private_obj);
+
+	return priv;
+}
+
+static struct sro_test_priv *
+mock_device_with_sro_create(struct kunit *test)
+{
+	return mock_device_create(test,
+				  &mock_mode_config_with_sro_funcs,
+				  &mock_mode_config_helper_with_sro_funcs,
+				  &mock_plane_with_sro_funcs,
+				  &mock_crtc_with_sro_funcs,
+				  &mock_connector_with_sro_funcs);
+}
+
+/*
+ * State readout container tests
+ */
+
+/*
+ * Test that a state container can be allocated and freed for a device,
+ * and that it reports the device it was created for.
+ */
+static void drm_test_sro_state_alloc_free(struct kunit *test)
+{
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_atomic_sro_state *sro_state;
+
+	sro_state = drm_atomic_sro_state_alloc(&priv->drm);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state);
+
+	KUNIT_EXPECT_PTR_EQ(test, drm_atomic_sro_state_get_device(sro_state),
+			    &priv->drm);
+
+	drm_atomic_sro_state_free(sro_state);
+}
+
+/*
+ * Test that objects with no state stored in the container report no
+ * state.
+ */
+static void drm_test_sro_state_get_unset(struct kunit *test)
+{
+	struct drm_connector *connector;
+	struct drm_private_obj *obj;
+	struct sro_test_priv *priv;
+	struct drm_atomic_sro_state *sro_state;
+	struct drm_plane_state *plane_state;
+	struct drm_crtc_state *crtc_state;
+	struct drm_connector_state *conn_state;
+	struct drm_private_state *obj_state;
+
+	priv = mock_device_with_sro_create(test);
+	connector = &priv->connector;
+	obj = &priv->private_obj;
+
+	sro_state = drm_atomic_sro_state_alloc(&priv->drm);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state);
+
+	/* No state has been stored yet */
+	plane_state = drm_atomic_sro_get_plane_state(sro_state, priv->plane);
+	KUNIT_EXPECT_NULL(test, plane_state);
+
+	crtc_state = drm_atomic_sro_get_crtc_state(sro_state, priv->crtc);
+	KUNIT_EXPECT_NULL(test, crtc_state);
+
+	conn_state = drm_atomic_sro_get_connector_state(sro_state, connector);
+	KUNIT_EXPECT_NULL(test, conn_state);
+
+	obj_state = drm_atomic_sro_get_private_obj_state(sro_state, obj);
+	KUNIT_EXPECT_NULL(test, obj_state);
+
+	drm_atomic_sro_state_free(sro_state);
+}
+
+/*
+ * Test that states stored in the container can be retrieved from it,
+ * and that the container destroys the states that haven't been
+ * installed when it is freed.
+ */
+static void drm_test_sro_state_set_get(struct kunit *test)
+{
+	struct drm_connector *connector;
+	struct drm_private_obj *obj;
+	struct sro_test_priv *priv;
+	struct drm_atomic_sro_state *sro_state;
+	struct drm_plane_state *plane_state, *plane_readback;
+	struct drm_crtc_state *crtc_state, *crtc_readback;
+	struct drm_connector_state *conn_state, *conn_readback;
+	struct drm_private_state *obj_state, *obj_readback;
+
+	priv = mock_device_with_sro_create(test);
+	connector = &priv->connector;
+	obj = &priv->private_obj;
+
+	sro_state = drm_atomic_sro_state_alloc(&priv->drm);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state);
+
+	plane_state = priv->plane->funcs->atomic_create_state(priv->plane);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state);
+
+	crtc_state = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	conn_state = connector->funcs->atomic_create_state(connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	obj_state = mock_private_obj_create_state(obj);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, obj_state);
+
+	drm_atomic_sro_set_plane_state(sro_state, priv->plane, plane_state);
+	drm_atomic_sro_set_crtc_state(sro_state, priv->crtc, crtc_state);
+	drm_atomic_sro_set_connector_state(sro_state, connector, conn_state);
+	drm_atomic_sro_set_private_obj_state(sro_state, obj, obj_state);
+
+	plane_readback = drm_atomic_sro_get_plane_state(sro_state, priv->plane);
+	KUNIT_EXPECT_PTR_EQ(test, plane_readback, plane_state);
+
+	crtc_readback = drm_atomic_sro_get_crtc_state(sro_state, priv->crtc);
+	KUNIT_EXPECT_PTR_EQ(test, crtc_readback, crtc_state);
+
+	conn_readback = drm_atomic_sro_get_connector_state(sro_state,
+							   connector);
+	KUNIT_EXPECT_PTR_EQ(test, conn_readback, conn_state);
+
+	obj_readback = drm_atomic_sro_get_private_obj_state(sro_state, obj);
+	KUNIT_EXPECT_PTR_EQ(test, obj_readback, obj_state);
+
+	drm_atomic_sro_state_free(sro_state);
+
+	/* The uninstalled states have been destroyed by the container */
+	KUNIT_EXPECT_EQ(test, priv->connector_destroy_count, 1);
+	KUNIT_EXPECT_EQ(test, priv->private_obj_destroy_count, 1);
+}
+
+/*
+ * Test that drm_atomic_sro_install_state() makes the read out states
+ * the current states of the objects and transfers their ownership:
+ * freeing the container afterwards doesn't destroy them.
+ */
+static void drm_test_sro_state_install(struct kunit *test)
+{
+	struct drm_connector *connector;
+	struct drm_private_obj *obj;
+	struct sro_test_priv *priv;
+	struct drm_atomic_sro_state *sro_state;
+	struct drm_plane_state *plane_state;
+	struct drm_crtc_state *crtc_state;
+	struct drm_connector_state *conn_state;
+	struct drm_private_state *obj_state;
+
+	priv = mock_device_with_sro_create(test);
+	connector = &priv->connector;
+	obj = &priv->private_obj;
+
+	sro_state = drm_atomic_sro_state_alloc(&priv->drm);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state);
+
+	plane_state = priv->plane->funcs->atomic_create_state(priv->plane);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state);
+
+	crtc_state = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+	crtc_state->enable = true;
+	crtc_state->active = true;
+
+	conn_state = connector->funcs->atomic_create_state(connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	obj_state = mock_private_obj_create_state(obj);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, obj_state);
+
+	drm_atomic_sro_set_plane_state(sro_state, priv->plane, plane_state);
+	drm_atomic_sro_set_crtc_state(sro_state, priv->crtc, crtc_state);
+	drm_atomic_sro_set_connector_state(sro_state, connector, conn_state);
+	drm_atomic_sro_set_private_obj_state(sro_state, obj, obj_state);
+
+	drm_atomic_sro_install_state(sro_state);
+
+	/* The read out states are now owned by the objects */
+	KUNIT_EXPECT_PTR_EQ(test, priv->plane->state, plane_state);
+	KUNIT_EXPECT_PTR_EQ(test, priv->crtc->state, crtc_state);
+	KUNIT_EXPECT_PTR_EQ(test, connector->state, conn_state);
+	KUNIT_EXPECT_PTR_EQ(test, obj->state, obj_state);
+	KUNIT_EXPECT_TRUE(test, priv->crtc->state->active);
+
+	/*
+	 * Freeing the container after the installation must not destroy
+	 * the installed states.
+	 */
+	drm_atomic_sro_state_free(sro_state);
+
+	KUNIT_EXPECT_PTR_EQ(test, priv->plane->state, plane_state);
+	KUNIT_EXPECT_PTR_EQ(test, priv->crtc->state, crtc_state);
+	KUNIT_EXPECT_PTR_EQ(test, connector->state, conn_state);
+	KUNIT_EXPECT_PTR_EQ(test, obj->state, obj_state);
+
+	/*
+	 * The connector had no state before the installation, while the
+	 * state the private object was initialized with by
+	 * drm_atomic_private_obj_init() has been destroyed when
+	 * replaced by the read out one.
+	 */
+	KUNIT_EXPECT_EQ(test, priv->connector_destroy_count, 0);
+	KUNIT_EXPECT_EQ(test, priv->private_obj_destroy_count, 1);
+}
+
+/*
+ * Test that installing a state on an object that already has one
+ * destroys the state that was installed before.
+ */
+static void drm_test_sro_state_install_replaces_state(struct kunit *test)
+{
+	struct drm_connector *connector;
+	struct sro_test_priv *priv;
+	struct drm_atomic_sro_state *sro_state;
+	struct drm_connector_state *old_state, *new_state;
+
+	priv = mock_device_with_sro_create(test);
+	connector = &priv->connector;
+
+	old_state = connector->funcs->atomic_create_state(connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+	connector->state = old_state;
+
+	sro_state = drm_atomic_sro_state_alloc(&priv->drm);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state);
+
+	new_state = connector->funcs->atomic_create_state(connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+	drm_atomic_sro_set_connector_state(sro_state, connector, new_state);
+
+	drm_atomic_sro_install_state(sro_state);
+	drm_atomic_sro_state_free(sro_state);
+
+	KUNIT_EXPECT_PTR_EQ(test, connector->state, new_state);
+
+	/* The state that was installed before has been destroyed */
+	KUNIT_EXPECT_EQ(test, priv->connector_destroy_count, 1);
+}
+
+/*
+ * Test that objects with no state in the container are left untouched
+ * by the installation.
+ */
+static void drm_test_sro_state_install_partial(struct kunit *test)
+{
+	struct drm_private_state *initial_obj_state;
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_atomic_sro_state *sro_state;
+	struct drm_crtc_state *crtc_state;
+
+	/* The private object is initialized with a state of its own */
+	initial_obj_state = priv->private_obj.state;
+	KUNIT_ASSERT_NOT_NULL(test, initial_obj_state);
+
+	sro_state = drm_atomic_sro_state_alloc(&priv->drm);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state);
+
+	crtc_state = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	drm_atomic_sro_set_crtc_state(sro_state, priv->crtc, crtc_state);
+
+	drm_atomic_sro_install_state(sro_state);
+	drm_atomic_sro_state_free(sro_state);
+
+	KUNIT_EXPECT_PTR_EQ(test, priv->crtc->state, crtc_state);
+	KUNIT_EXPECT_NULL(test, priv->plane->state);
+	KUNIT_EXPECT_NULL(test, priv->connector.state);
+
+	/* The private object has been left untouched */
+	KUNIT_EXPECT_PTR_EQ(test, priv->private_obj.state, initial_obj_state);
+	KUNIT_EXPECT_EQ(test, priv->private_obj_destroy_count, 0);
+}
+
+static struct kunit_case drm_atomic_sro_state_tests[] = {
+	KUNIT_CASE(drm_test_sro_state_alloc_free),
+	KUNIT_CASE(drm_test_sro_state_get_unset),
+	KUNIT_CASE(drm_test_sro_state_set_get),
+	KUNIT_CASE(drm_test_sro_state_install),
+	KUNIT_CASE(drm_test_sro_state_install_replaces_state),
+	KUNIT_CASE(drm_test_sro_state_install_partial),
+	{ }
+};
+
+static struct kunit_suite drm_atomic_sro_state_test_suite = {
+	.name = "drm_atomic_sro_state",
+	.test_cases = drm_atomic_sro_state_tests,
+};
+
+/*
+ * Readout tests
+ */
+
+/*
+ * Test that a successful readout calls the readout hook of every
+ * object, including the private objects, and installs the states it
+ * built as the current states of the objects. The connectors are read
+ * out after the CRTCs so that their hooks can look the CRTC states up.
+ */
+static void drm_test_sro_readout_state(struct kunit *test)
+{
+	struct mock_privobj_state *privobj_state;
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	int ret;
+
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	/* The states read out have been installed on the objects */
+	KUNIT_EXPECT_TRUE(test, priv->crtc->state->enable);
+	KUNIT_EXPECT_TRUE(test, priv->crtc->state->active);
+	KUNIT_EXPECT_TRUE(test, priv->plane->state->visible);
+	KUNIT_EXPECT_EQ(test, priv->plane->state->crtc_x, SRO_TEST_CRTC_X);
+	KUNIT_EXPECT_EQ(test, priv->plane->state->crtc_y, SRO_TEST_CRTC_Y);
+	KUNIT_EXPECT_PTR_EQ(test, priv->connector.state->crtc, priv->crtc);
+
+	KUNIT_ASSERT_NOT_NULL(test, priv->private_obj.state);
+	privobj_state = to_mock_privobj_state(priv->private_obj.state);
+	KUNIT_EXPECT_TRUE(test, privobj_state->readout);
+}
+
+/*
+ * Test that a device without an atomic_sro_build_state helper can't be
+ * read out, and that nothing is installed.
+ */
+static void drm_test_sro_readout_state_no_helper(struct kunit *test)
+{
+	struct sro_test_priv *priv;
+	int ret;
+
+	/*
+	 * Everything is in place for the readout except the
+	 * atomic_sro_build_state helper.
+	 */
+	priv = mock_device_create(test,
+				  &mock_mode_config_with_sro_funcs,
+				  &mock_mode_config_helper_without_sro_funcs,
+				  &mock_plane_with_sro_funcs,
+				  &mock_crtc_with_sro_funcs,
+				  &mock_connector_with_sro_funcs);
+
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP);
+
+	/* Nothing has been installed */
+	KUNIT_EXPECT_NULL(test, priv->crtc->state);
+	KUNIT_EXPECT_NULL(test, priv->plane->state);
+}
+
+/*
+ * Test that a device whose mode configuration doesn't implement the
+ * atomic_sro_readout_state hook can't be read out either.
+ */
+static void drm_test_sro_readout_state_no_hook(struct kunit *test)
+{
+	struct sro_test_priv *priv;
+	int ret;
+
+	/*
+	 * Everything is in place for the readout except the
+	 * atomic_sro_readout_state mode configuration hook.
+	 */
+	priv = mock_device_create(test,
+				  &mock_mode_config_without_sro_with_hw_reset_funcs,
+				  &mock_mode_config_helper_with_sro_funcs,
+				  &mock_plane_with_sro_funcs,
+				  &mock_crtc_with_sro_funcs,
+				  &mock_connector_with_sro_funcs);
+
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP);
+
+	/* Nothing has been installed */
+	KUNIT_EXPECT_NULL(test, priv->crtc->state);
+	KUNIT_EXPECT_NULL(test, priv->plane->state);
+}
+
+/*
+ * Test that a device where an object doesn't implement the readout
+ * hooks can't be read out either.
+ */
+static void drm_test_sro_readout_state_missing_hooks(struct kunit *test)
+{
+	struct sro_test_priv *priv;
+	int ret;
+
+	/*
+	 * The default KUnit helpers object implementations don't have
+	 * any readout hook.
+	 */
+	priv = mock_device_create(test,
+				  &mock_mode_config_with_sro_funcs,
+				  &mock_mode_config_helper_with_sro_funcs,
+				  NULL, NULL,
+				  &mock_connector_with_sro_funcs);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP);
+
+	KUNIT_EXPECT_NULL(test, priv->crtc->state);
+	KUNIT_EXPECT_NULL(test, priv->plane->state);
+}
+
+/*
+ * Test that an error from atomic_sro_build_state() is reported to the
+ * caller as-is, and that no state has been installed.
+ */
+static void drm_test_sro_readout_state_error(struct kunit *test)
+{
+	struct sro_test_priv *priv;
+	int ret;
+
+	priv = mock_device_create(test,
+				  &mock_mode_config_with_sro_funcs,
+				  &mock_mode_config_helper_with_err_funcs,
+				  &mock_plane_with_sro_funcs,
+				  &mock_crtc_with_sro_funcs,
+				  &mock_connector_with_sro_funcs);
+
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+	/* Nothing has been installed */
+	KUNIT_EXPECT_NULL(test, priv->crtc->state);
+	KUNIT_EXPECT_NULL(test, priv->plane->state);
+}
+
+/*
+ * Test that drm_atomic_sro_readout_and_compare() builds a fresh state
+ * from the hardware and compares it to the commit: a commit state
+ * duplicated from the state read out of the hardware is identical,
+ * while a modified one is reported as a mismatch.
+ */
+static void drm_test_sro_readout_and_compare(struct kunit *test)
+{
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_atomic_commit *commit;
+	struct sro_test_priv *priv;
+	struct drm_crtc_state *crtc_state;
+	bool identical;
+	int ret;
+
+	priv = mock_device_with_sro_create(test);
+
+	/* Install the mock hardware state */
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+	commit = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, commit);
+
+	crtc_state = drm_atomic_get_crtc_state(commit, priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	/*
+	 * The commit state has been duplicated from the state read out
+	 * of the mock hardware, so the two are identical.
+	 */
+	identical = drm_atomic_sro_readout_and_compare(commit);
+	KUNIT_EXPECT_TRUE(test, identical);
+
+	crtc_state->active = false;
+	identical = drm_atomic_sro_readout_and_compare(commit);
+	KUNIT_EXPECT_FALSE(test, identical);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Test that the readout and compare of a commit is scoped to the
+ * objects of the commit: the objects that the commit doesn't contain
+ * are neither read out nor compared.
+ */
+static void drm_test_sro_readout_and_compare_scope(struct kunit *test)
+{
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_atomic_commit *commit;
+	struct sro_test_priv *priv;
+	struct drm_crtc_state *crtc_state;
+	bool identical;
+	int ret;
+
+	priv = mock_device_with_sro_create(test);
+
+	/* Install the mock hardware state */
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	/*
+	 * The connector readout hook sets connector_readout_called
+	 * when it runs. Reset it so that we can check whether it runs
+	 * again.
+	 */
+	priv->connector_readout_called = false;
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+	commit = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, commit);
+
+	/*
+	 * The commit only contains a state for the CRTC, duplicated
+	 * from the state read out of the mock hardware, so the two
+	 * are identical.
+	 */
+	crtc_state = drm_atomic_get_crtc_state(commit, priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	identical = drm_atomic_sro_readout_and_compare(commit);
+	KUNIT_EXPECT_TRUE(test, identical);
+
+	/*
+	 * The connector isn't part of the commit, so its readout hook
+	 * must not have run.
+	 */
+	KUNIT_EXPECT_FALSE(test, priv->connector_readout_called);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Test that the state installed by the readout lets the first commit
+ * tell whether a modeset is needed: a commit asking for the
+ * configuration already programmed in the hardware doesn't need one,
+ * and a commit changing the configuration does.
+ */
+static void drm_test_sro_readout_no_modeset(struct kunit *test)
+{
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_atomic_commit *commit;
+	struct sro_test_priv *priv;
+	struct drm_crtc_state *crtc_state;
+	struct drm_display_mode *mode;
+	int ret;
+
+	priv = mock_device_with_sro_create(test);
+
+	ret = drm_mode_config_create_initial_state(&priv->drm);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	/*
+	 * A commit with the same configuration as the hardware
+	 * doesn't need a modeset.
+	 */
+	drm_modeset_acquire_init(&ctx, 0);
+
+	commit = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, commit);
+
+	crtc_state = drm_atomic_get_crtc_state(commit, priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	ret = drm_atomic_check_only(commit);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_FALSE(test, crtc_state->mode_changed);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+
+	/* A commit with a different configuration needs a modeset. */
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry:
+	commit = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, commit);
+
+	crtc_state = drm_atomic_get_crtc_state(commit, priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm,
+						   SRO_TEST_OTHER_MODE_VIC);
+	KUNIT_ASSERT_NOT_NULL(test, mode);
+
+	ret = drm_atomic_set_mode_for_crtc(crtc_state, mode);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	commit->allow_modeset = true;
+
+	ret = drm_atomic_check_only(commit);
+	if (ret == -EDEADLK)
+		goto backoff;
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_TRUE(test, crtc_state->mode_changed);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+
+	return;
+
+backoff:
+	drm_atomic_commit_clear(commit);
+	drm_modeset_backoff(&ctx);
+	goto retry;
+}
+
+static struct kunit_case drm_atomic_sro_readout_tests[] = {
+	KUNIT_CASE(drm_test_sro_readout_state),
+	KUNIT_CASE(drm_test_sro_readout_state_no_helper),
+	KUNIT_CASE(drm_test_sro_readout_state_no_hook),
+	KUNIT_CASE(drm_test_sro_readout_state_missing_hooks),
+	KUNIT_CASE(drm_test_sro_readout_state_error),
+	KUNIT_CASE(drm_test_sro_readout_and_compare),
+	KUNIT_CASE(drm_test_sro_readout_and_compare_scope),
+	KUNIT_CASE(drm_test_sro_readout_no_modeset),
+	{ }
+};
+
+static struct kunit_suite drm_atomic_sro_readout_test_suite = {
+	.name = "drm_atomic_sro_readout",
+	.test_cases = drm_atomic_sro_readout_tests,
+};
+
+/*
+ * Compare helper tests
+ */
+
+/*
+ * Test that two identical plane states compare as equal, without
+ * reporting any mismatch.
+ */
+static void drm_test_sro_compare_plane_identical(struct kunit *test)
+{
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_plane_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	state1 = priv->plane->funcs->atomic_create_state(priv->plane);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = priv->plane->funcs->atomic_create_state(priv->plane);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	identical = drm_atomic_helper_plane_compare_state(priv->plane, p,
+							  state1, state2);
+	KUNIT_EXPECT_TRUE(test, identical);
+}
+
+/*
+ * Test that plane states differing by a single field compare as
+ * different, and that the mismatching field is reported through the
+ * printer.
+ */
+static void drm_test_sro_compare_plane_mismatch(struct kunit *test)
+{
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_plane_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	state1 = priv->plane->funcs->atomic_create_state(priv->plane);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = priv->plane->funcs->atomic_create_state(priv->plane);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	state2->crtc = priv->crtc;
+
+	identical = drm_atomic_helper_plane_compare_state(priv->plane, p,
+							  state1, state2);
+	KUNIT_EXPECT_FALSE(test, identical);
+}
+
+/*
+ * Test that two identical active CRTC states compare as equal.
+ */
+static void drm_test_sro_compare_crtc_identical(struct kunit *test)
+{
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_crtc_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	state1 = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	state1->enable = true;
+	state1->active = true;
+	state2->enable = true;
+	state2->active = true;
+
+	identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p,
+							 state1, state2);
+	KUNIT_EXPECT_TRUE(test, identical);
+}
+
+/*
+ * Test that two inactive CRTCs compare as equal even though their
+ * modes differ: the modes are meaningless while inactive and must not
+ * be compared.
+ */
+static void drm_test_sro_compare_crtc_inactive(struct kunit *test)
+{
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_display_mode *mode1, *mode2;
+	struct drm_crtc_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	state1 = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	/* Two different but valid modes */
+	mode1 = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode1);
+
+	mode2 = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 4);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode2);
+
+	/*
+	 * Both CRTCs are inactive: the modes are meaningless and must
+	 * not be compared.
+	 */
+	drm_mode_copy(&state1->mode, mode1);
+	drm_mode_copy(&state2->mode, mode2);
+
+	identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p,
+							 state1, state2);
+	KUNIT_EXPECT_TRUE(test, identical);
+}
+
+/*
+ * Test that an active CRTC and an inactive one compare as different.
+ */
+static void drm_test_sro_compare_crtc_active_mismatch(struct kunit *test)
+{
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_crtc_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	state1 = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = priv->crtc->funcs->atomic_create_state(priv->crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	state1->enable = true;
+	state1->active = true;
+
+	identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p,
+							 state1, state2);
+	KUNIT_EXPECT_FALSE(test, identical);
+}
+
+/*
+ * Test that two identical connector states compare as equal.
+ */
+static void drm_test_sro_compare_connector_identical(struct kunit *test)
+{
+	struct drm_connector *connector;
+	struct sro_test_priv *priv;
+	struct drm_connector_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	priv = mock_device_with_sro_create(test);
+	connector = &priv->connector;
+
+	state1 = connector->funcs->atomic_create_state(connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = connector->funcs->atomic_create_state(connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	identical =
+		drm_atomic_helper_connector_compare_state(connector, p,
+							  state1, state2);
+	KUNIT_EXPECT_TRUE(test, identical);
+}
+
+/*
+ * Test that connector states whose CRTCs differ compare as different.
+ */
+static void drm_test_sro_compare_connector_mismatch(struct kunit *test)
+{
+	struct drm_connector *connector;
+	struct sro_test_priv *priv;
+	struct drm_connector_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	priv = mock_device_with_sro_create(test);
+	connector = &priv->connector;
+
+	state1 = connector->funcs->atomic_create_state(connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = connector->funcs->atomic_create_state(connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	state2->crtc = priv->crtc;
+
+	identical =
+		drm_atomic_helper_connector_compare_state(connector, p,
+							  state1, state2);
+	KUNIT_EXPECT_FALSE(test, identical);
+}
+
+/*
+ * Test that two identical bridge states compare as equal.
+ */
+static void drm_test_sro_compare_bridge_identical(struct kunit *test)
+{
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_bridge_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	priv->bridge.funcs = &mock_bridge_funcs;
+	priv->bridge.name = "test bridge";
+
+	state1 = drm_atomic_helper_bridge_create_state(&priv->bridge);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = drm_atomic_helper_bridge_create_state(&priv->bridge);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	identical = drm_atomic_helper_bridge_compare_state(&priv->bridge, p,
+							   state1, state2);
+	KUNIT_EXPECT_TRUE(test, identical);
+
+	priv->bridge.funcs->atomic_destroy_state(&priv->bridge, state1);
+	priv->bridge.funcs->atomic_destroy_state(&priv->bridge, state2);
+}
+
+/*
+ * Test that bridge states whose input bus formats differ compare as
+ * different.
+ */
+static void drm_test_sro_compare_bridge_mismatch(struct kunit *test)
+{
+	struct sro_test_priv *priv = mock_device_with_sro_create(test);
+	struct drm_bridge_state *state1, *state2;
+	struct drm_printer *p;
+	bool identical;
+
+	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
+
+	priv->bridge.funcs = &mock_bridge_funcs;
+	priv->bridge.name = "test bridge";
+
+	state1 = drm_atomic_helper_bridge_create_state(&priv->bridge);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
+
+	state2 = drm_atomic_helper_bridge_create_state(&priv->bridge);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
+
+	state2->input_bus_cfg.format = MEDIA_BUS_FMT_RGB888_1X24;
+
+	identical = drm_atomic_helper_bridge_compare_state(&priv->bridge, p,
+							   state1, state2);
+	KUNIT_EXPECT_FALSE(test, identical);
+
+	priv->bridge.funcs->atomic_destroy_state(&priv->bridge, state1);
+	priv->bridge.funcs->atomic_destroy_state(&priv->bridge, state2);
+}
+
+static struct kunit_case drm_atomic_sro_compare_tests[] = {
+	KUNIT_CASE(drm_test_sro_compare_plane_identical),
+	KUNIT_CASE(drm_test_sro_compare_plane_mismatch),
+	KUNIT_CASE(drm_test_sro_compare_crtc_identical),
+	KUNIT_CASE(drm_test_sro_compare_crtc_inactive),
+	KUNIT_CASE(drm_test_sro_compare_crtc_active_mismatch),
+	KUNIT_CASE(drm_test_sro_compare_connector_identical),
+	KUNIT_CASE(drm_test_sro_compare_connector_mismatch),
+	KUNIT_CASE(drm_test_sro_compare_bridge_identical),
+	KUNIT_CASE(drm_test_sro_compare_bridge_mismatch),
+	{ }
+};
+
+static struct kunit_suite drm_atomic_sro_compare_test_suite = {
+	.name = "drm_atomic_sro_compare",
+	.test_cases = drm_atomic_sro_compare_tests,
+};
+
+/*
+ * drm_mode_config_create_initial_state() tests
+ */
+
+/*
+ * Test that when the readout succeeds,
+ * drm_mode_config_create_initial_state() installs the states it read
+ * out and doesn't reset the hardware.
+ */
+static void drm_test_mode_config_initial_state_readout(struct kunit *test)
+{
+	struct sro_test_priv *priv;
+	int ret;
+
+	priv = mock_device_create(test,
+				  &mock_mode_config_with_sro_hw_reset_funcs,
+				  &mock_mode_config_helper_without_sro_funcs,
+				  &mock_plane_with_sro_funcs,
+				  &mock_crtc_with_sro_funcs,
+				  &mock_connector_with_sro_funcs);
+
+	ret = drm_mode_config_create_initial_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	/* The read out state has been installed */
+	KUNIT_EXPECT_TRUE(test, priv->crtc->state->enable);
+	KUNIT_EXPECT_TRUE(test, priv->crtc->state->active);
+
+	/* No fallback to a hardware reset has been needed */
+	KUNIT_EXPECT_FALSE(test, priv->hw_reset_called);
+}
+
+/*
+ * Test that when the driver reports the hardware configuration as
+ * unsupported, drm_mode_config_create_initial_state() falls back to
+ * the regular initialization path: the hardware is reset and pristine
+ * states are created.
+ */
+static void drm_test_mode_config_initial_state_readout_error(struct kunit *test)
+{
+	struct mock_privobj_state *privobj_state;
+	struct sro_test_priv *priv;
+	int ret;
+
+	priv = mock_device_create(test,
+				  &mock_mode_config_with_sro_hw_reset_funcs,
+				  &mock_mode_config_helper_without_sro_funcs,
+				  &mock_plane_with_sro_funcs,
+				  &mock_crtc_with_sro_funcs,
+				  &mock_connector_with_sro_funcs);
+
+	/* The driver reports the hardware configuration as unsupported */
+	priv->readout_ret = -EINVAL;
+
+	ret = drm_mode_config_create_initial_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	/* The regular initialization path has been taken */
+	KUNIT_EXPECT_TRUE(test, priv->hw_reset_called);
+
+	/* A pristine state has been created for all the objects */
+	KUNIT_ASSERT_NOT_NULL(test, priv->crtc->state);
+	KUNIT_EXPECT_FALSE(test, priv->crtc->state->enable);
+	KUNIT_EXPECT_FALSE(test, priv->crtc->state->active);
+	KUNIT_ASSERT_NOT_NULL(test, priv->plane->state);
+	KUNIT_ASSERT_NOT_NULL(test, priv->connector.state);
+
+	KUNIT_ASSERT_NOT_NULL(test, priv->private_obj.state);
+	privobj_state = to_mock_privobj_state(priv->private_obj.state);
+	KUNIT_EXPECT_FALSE(test, privobj_state->readout);
+}
+
+/*
+ * Test that when an object readout hook reports the hardware
+ * configuration as unsupported, drm_mode_config_create_initial_state()
+ * falls back to the regular initialization path.
+ */
+static void
+drm_test_mode_config_initial_state_object_readout_error(struct kunit *test)
+{
+	struct sro_test_priv *priv;
+	int ret;
+
+	priv = mock_device_create(test,
+				  &mock_mode_config_with_sro_funcs,
+				  &mock_mode_config_helper_with_sro_funcs,
+				  &mock_plane_with_sro_funcs,
+				  &mock_crtc_with_sro_funcs,
+				  &mock_connector_with_err_funcs);
+
+
+	/*
+	 * The object readout hooks report their errors through a
+	 * WARN_ON, which is expected here.
+	 */
+	kunit_warning_suppress(test) {
+		ret = drm_mode_config_create_initial_state(&priv->drm);
+		KUNIT_EXPECT_EQ(test, ret, 0);
+		KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+	}
+
+	/* The regular initialization path has been taken */
+	KUNIT_EXPECT_TRUE(test, priv->hw_reset_called);
+
+	/* A pristine state has been created for all the objects */
+	KUNIT_ASSERT_NOT_NULL(test, priv->crtc->state);
+	KUNIT_EXPECT_FALSE(test, priv->crtc->state->enable);
+	KUNIT_EXPECT_FALSE(test, priv->crtc->state->active);
+	KUNIT_ASSERT_NOT_NULL(test, priv->plane->state);
+	KUNIT_ASSERT_NOT_NULL(test, priv->connector.state);
+}
+
+/*
+ * Test that a driver without readout support takes the regular
+ * initialization path as well.
+ */
+static void drm_test_mode_config_initial_state_no_readout(struct kunit *test)
+{
+	struct sro_test_priv *priv;
+	int ret;
+
+	priv = mock_device_create(test,
+				  &mock_mode_config_without_sro_with_hw_reset_funcs,
+				  &mock_mode_config_helper_without_sro_funcs,
+				  &mock_plane_with_sro_funcs,
+				  &mock_crtc_with_sro_funcs,
+				  &mock_connector_with_sro_funcs);
+
+	ret = drm_mode_config_create_initial_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	/* The regular initialization path has been taken */
+	KUNIT_EXPECT_TRUE(test, priv->hw_reset_called);
+
+	KUNIT_ASSERT_NOT_NULL(test, priv->crtc->state);
+	KUNIT_EXPECT_FALSE(test, priv->crtc->state->enable);
+	KUNIT_EXPECT_FALSE(test, priv->crtc->state->active);
+	KUNIT_ASSERT_NOT_NULL(test, priv->plane->state);
+	KUNIT_ASSERT_NOT_NULL(test, priv->connector.state);
+}
+
+static struct kunit_case drm_mode_config_initial_state_tests[] = {
+	KUNIT_CASE(drm_test_mode_config_initial_state_readout),
+	KUNIT_CASE(drm_test_mode_config_initial_state_readout_error),
+	KUNIT_CASE(drm_test_mode_config_initial_state_object_readout_error),
+	KUNIT_CASE(drm_test_mode_config_initial_state_no_readout),
+	{ }
+};
+
+static struct kunit_suite drm_mode_config_initial_state_test_suite = {
+	.name = "drm_mode_config_initial_state",
+	.test_cases = drm_mode_config_initial_state_tests,
+};
+
+kunit_test_suites(
+	&drm_atomic_sro_compare_test_suite,
+	&drm_atomic_sro_readout_test_suite,
+	&drm_atomic_sro_state_test_suite,
+	&drm_mode_config_initial_state_test_suite,
+);
+
+MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
+MODULE_DESCRIPTION("Kunit test for the DRM atomic state readout");
+MODULE_LICENSE("GPL");

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 26/34] drm/encoder: Create atomic_sro_get_current_crtc hook
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (24 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 25/34] drm/tests: Add tests for the atomic state readout Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 27/34] drm/bridge: display-connector: Implement readout support Maxime Ripard
                   ` (7 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

In order for drivers to implement drm_connectors
atomic_sro_readout_state hooks, they need to query the hardware and
lookup the CRTC to set drm_connector_state.crtc.

It should be easy enough for drivers that are tightly integrated from
the CRTC to the connectors, but if the driver uses bridges, there's no
coupling between the CRTC and encoder, and the bridge driver.

The only thing the bridge has access to is the encoder, but the
relationship between a CRTC and an encoder isn't a fixed mapping at the
framework level, and thus the bridge can't deduce which CRTC is feeding
its encoder.

Create a new hook for encoders to implement to return the
CRTC they are currently connected to.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 include/drm/drm_encoder.h | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/include/drm/drm_encoder.h b/include/drm/drm_encoder.h
index eded7c34481a..7f41ec0a68dc 100644
--- a/include/drm/drm_encoder.h
+++ b/include/drm/drm_encoder.h
@@ -90,4 +90,22 @@ struct drm_encoder_funcs {
 	 */
 	void (*debugfs_init)(struct drm_encoder *encoder, struct dentry *root);
+
+	/**
+	 * @atomic_sro_get_current_crtc:
+	 *
+	 * This optional hook is called during hardware state readout
+	 * to determine which CRTC is currently driving this encoder.
+	 *
+	 * It is needed by the bridge connector readout code to
+	 * establish the CRTC-encoder-connector association from the
+	 * hardware state, since the relationship between a CRTC and
+	 * an encoder is not a fixed mapping at the framework level.
+	 *
+	 * RETURNS:
+	 *
+	 * The CRTC currently associated with the encoder if enabled,
+	 * NULL otherwise.
+	 */
+	struct drm_crtc *(*atomic_sro_get_current_crtc)(struct drm_encoder *encoder);
 };
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 27/34] drm/bridge: display-connector: Implement readout support
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (25 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 26/34] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 28/34] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
                   ` (6 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The display-connector bridge is the last bridge in the chain on
platforms using drm_bridge_connector, and is responsible for the
physical connector interface.

Implement the atomic_sro_readout_state hook and wire up the default
drm_atomic_helper_bridge_compare_state() as the compare hook.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/bridge/display-connector.c | 69 ++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
index eb3412ec30a0..781684da1da4 100644
--- a/drivers/gpu/drm/bridge/display-connector.c
+++ b/drivers/gpu/drm/bridge/display-connector.c
@@ -15,5 +15,8 @@
 #include <linux/workqueue.h>
 
+#include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_edid.h>
@@ -214,4 +217,68 @@ static u32 *display_connector_get_input_bus_fmts(struct drm_bridge *bridge,
 }
 
+/*
+ * Reconstruct the bridge bus configuration from the previous bridge's
+ * readout state and the connector's display_info.
+ *
+ * During normal atomic check, drm_atomic_bridge_chain_select_bus_fmts()
+ * and drm_atomic_bridge_propagate_bus_flags() walk the bridge chain to
+ * negotiate formats and propagate flags. During SRO readout, those
+ * functions do not run, so each bridge must reconstruct its own state.
+ *
+ * The SRO infrastructure traverses bridges from first to last, so the
+ * previous bridge's state is already available. The input bus
+ * configuration is taken from the previous bridge's output. The output
+ * bus format comes from the connector's display_info, since this bridge
+ * is the last in the chain and acts as a passthrough to the connector.
+ * Bus flags are also taken from display_info, matching the default
+ * propagation behavior of drm_atomic_bridge_propagate_bus_flags().
+ */
+static int
+display_connector_readout_state(struct drm_bridge *bridge,
+				struct drm_atomic_sro_state *state,
+				struct drm_bridge_state *bridge_state,
+				struct drm_crtc_state *crtc_state,
+				struct drm_connector_state *conn_state)
+{
+	struct drm_display_info *di = &conn_state->connector->display_info;
+	struct drm_bridge *prev_bridge __free(drm_bridge_put) =
+		drm_bridge_get_prev_bridge(bridge);
+
+	if (prev_bridge) {
+		struct drm_private_state *prev_priv_state =
+			drm_atomic_sro_get_private_obj_state(state,
+							     &prev_bridge->base);
+
+		if (prev_priv_state) {
+			struct drm_bridge_state *prev_bridge_state =
+				drm_priv_to_bridge_state(prev_priv_state);
+
+			bridge_state->input_bus_cfg.format =
+				prev_bridge_state->output_bus_cfg.format;
+			bridge_state->input_bus_cfg.flags =
+				prev_bridge_state->output_bus_cfg.flags;
+			goto out_input;
+		}
+	}
+
+	/*
+	 * The previous bridge might not have readout support, so we
+	 * can't treat a missing state as an error.
+	 */
+	bridge_state->input_bus_cfg.format = MEDIA_BUS_FMT_FIXED;
+	bridge_state->input_bus_cfg.flags = di->bus_flags;
+
+out_input:
+
+	if (di->num_bus_formats && di->bus_formats)
+		bridge_state->output_bus_cfg.format = di->bus_formats[0];
+	else
+		bridge_state->output_bus_cfg.format = MEDIA_BUS_FMT_FIXED;
+
+	bridge_state->output_bus_cfg.flags = di->bus_flags;
+
+	return 0;
+}
+
 static const struct drm_bridge_funcs display_connector_bridge_funcs = {
 	.attach = display_connector_attach,
@@ -226,4 +293,6 @@ static const struct drm_bridge_funcs display_connector_bridge_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
 	.atomic_create_state = drm_atomic_helper_bridge_create_state,
+	.atomic_sro_readout_state	= display_connector_readout_state,
+	.atomic_sro_compare_state	= drm_atomic_helper_bridge_compare_state,
 };
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 28/34] drm/bridge_connector: Implement hw readout for connector
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (26 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 27/34] drm/bridge: display-connector: Implement readout support Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  8:04   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 29/34] drm/tidss: crtc: Add atomic_print_state implementation Maxime Ripard
                   ` (5 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

drm_bridge_connector allows to create a generic connector from a list of
bridges.

However, it's a somewhat virtual connector, and relies on the bridges to
implement its various capabilities.

What we actually want though is for the last bridge implementing
hardware readout to fill the connector state from its own state.

Thus, implement a new op for bridge_connector to allow just
that.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 36 ++++++++++++++++++++++++++
 include/drm/drm_bridge.h                       | 32 +++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 2202e6044444..13a0faa115e0 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -12,4 +12,5 @@
 
 #include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
@@ -68,4 +69,12 @@ struct drm_bridge_connector {
 	 */
 	struct drm_encoder *encoder;
+	/**
+	 * @bridge_connector_hw_readout:
+	 *
+	 * The last bridge in the chain (closest to the connector) that
+	 * provides hardware state readout support, if any (see
+	 * &DRM_BRIDGE_OP_CONNECTOR_HW_READOUT).
+	 */
+	struct drm_bridge *bridge_connector_hw_readout;
 	/**
 	 * @bridge_edid:
@@ -245,4 +254,24 @@ drm_bridge_connector_create_state(struct drm_connector *connector)
 }
 
+static int
+drm_bridge_connector_readout_state(struct drm_connector *connector,
+				   struct drm_atomic_sro_state *state,
+				   struct drm_connector_state *conn_state)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *readout =
+		bridge_connector->bridge_connector_hw_readout;
+	int ret;
+
+	if (readout) {
+		ret = readout->funcs->atomic_sro_connector_readout(readout, state, conn_state);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static enum drm_connector_color_format
 drm_bridge_connector_color_format(const struct drm_connector_state *conn_state)
@@ -271,4 +300,6 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = {
 	.fill_modes = drm_helper_probe_single_connector_modes,
 	.atomic_create_state = drm_bridge_connector_create_state,
+	.atomic_sro_readout_state = drm_bridge_connector_readout_state,
+	.atomic_sro_compare_state = drm_atomic_helper_connector_compare_state,
 	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
@@ -828,4 +859,5 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
 	struct drm_bridge_connector *bridge_connector = (struct drm_bridge_connector *)data;
 
+	drm_bridge_put(bridge_connector->bridge_connector_hw_readout);
 	drm_bridge_put(bridge_connector->bridge_edid);
 	drm_bridge_put(bridge_connector->bridge_hpd);
@@ -897,4 +929,8 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 			connector->ycbcr_420_allowed = false;
 
+		if (bridge->ops & DRM_BRIDGE_OP_CONNECTOR_HW_READOUT) {
+			drm_bridge_put(bridge_connector->bridge_connector_hw_readout);
+			bridge_connector->bridge_connector_hw_readout = drm_bridge_get(bridge);
+		}
 		/*
 		 * Ensure the last bridge declares OP_EDID or OP_MODES or both.
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 436dfffb25ad..b73f3424ed62 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -943,4 +943,29 @@ struct drm_bridge_funcs {
 				    bool enable, int direction);
 
+	/**
+	 * @atomic_sro_connector_readout:
+	 *
+	 * This optional hook initializes the &struct drm_connector_state
+	 * based on hardware state.
+	 *
+	 * It is implemented by bridges that set the
+	 * %DRM_BRIDGE_OP_CONNECTOR_HW_READOUT flag in their
+	 * &drm_bridge.ops. When using drm_bridge_connector, the last
+	 * bridge in the chain with this flag set will have its hook
+	 * called to fill the connector state.
+	 *
+	 * Drivers are expected to return a negative error code if the
+	 * hardware was programmed with a configuration they don't
+	 * support. The framework will then abort the readout and fall
+	 * back to the regular initialization path.
+	 *
+	 * RETURNS:
+	 *
+	 * 0 on success, a negative error code otherwise.
+	 */
+	int (*atomic_sro_connector_readout)(struct drm_bridge *bridge,
+					    struct drm_atomic_sro_state *state,
+					    struct drm_connector_state *conn_state);
+
 	/**
 	 * @debugfs_init:
@@ -1083,4 +1108,11 @@ enum drm_bridge_ops {
 	 */
 	DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME = BIT(10),
+	/**
+	 * @DRM_BRIDGE_OP_CONNECTOR_HW_READOUT: The bridge supports the
+	 * &drm_bridge_funcs.atomic_sro_connector_readout callback to
+	 * fill the connector state from the bridge's own hardware state
+	 * during state readout.
+	 */
+	DRM_BRIDGE_OP_CONNECTOR_HW_READOUT = BIT(11),
 };
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 29/34] drm/tidss: crtc: Add atomic_print_state implementation
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (27 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 28/34] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 30/34] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
                   ` (4 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The tidss CRTC subclasses drm_crtc_state with bus_format and bus_flags
fields that are set during atomic check and state readout. These fields
are not visible in the standard atomic state dumps, making it harder to
debug bus format negotiation issues.

Add an atomic_print_state hook to expose them in debugfs and kernel log
state dumps.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tidss/tidss_crtc.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
index 400329aa2200..3da49f77fa08 100644
--- a/drivers/gpu/drm/tidss/tidss_crtc.c
+++ b/drivers/gpu/drm/tidss/tidss_crtc.c
@@ -402,4 +402,13 @@ static void tidss_crtc_destroy(struct drm_crtc *crtc)
 }
 
+static void tidss_crtc_atomic_print_state(struct drm_printer *p,
+					  const struct drm_crtc_state *state)
+{
+	const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+
+	drm_printf_indent(p, 1, "bus_format=0x%08x\n", tstate->bus_format);
+	drm_printf_indent(p, 1, "bus_flags=0x%08x\n", tstate->bus_flags);
+}
+
 static const struct drm_crtc_funcs tidss_crtc_funcs = {
 	.destroy = tidss_crtc_destroy,
@@ -409,4 +418,5 @@ static const struct drm_crtc_funcs tidss_crtc_funcs = {
 	.atomic_duplicate_state = tidss_crtc_duplicate_state,
 	.atomic_destroy_state = tidss_crtc_destroy_state,
+	.atomic_print_state = tidss_crtc_atomic_print_state,
 	.enable_vblank = tidss_crtc_enable_vblank,
 	.disable_vblank = tidss_crtc_disable_vblank,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 30/34] drm/tidss: dispc: Improve mode checking logs
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (28 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 29/34] drm/tidss: crtc: Add atomic_print_state implementation Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 31/34] drm/tidss: Implement hw_reset Maxime Ripard
                   ` (3 subsequent siblings)
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The dispc_vp_mode_valid() function checks whether a mode can be handled
by the display controller.

There's a whole bunch of criteria, and it's not clear when a rejection
happens why it did. Add logs on each rejection criterion to make it
clearer.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tidss/tidss_dispc.c | 45 +++++++++++++++++++++++++++++--------
 1 file changed, 36 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
index 2a0ec11a6c90..5943a5f25432 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1305,4 +1305,6 @@ static int check_pixel_clock(struct dispc_device *dispc, u32 hw_videoport,
 			     unsigned long clock)
 {
+	struct tidss_device *tidss = dispc->tidss;
+	struct drm_device *dev = &tidss->ddev;
 	unsigned long round_clock;
 
@@ -1319,6 +1321,9 @@ static int check_pixel_clock(struct dispc_device *dispc, u32 hw_videoport,
 	 * use the same 5% check here.
 	 */
-	if (dispc_pclk_diff(clock, round_clock) > 5)
+	if (dispc_pclk_diff(clock, round_clock) > 5) {
+		drm_dbg(dev, "Pixel clock %lu differs from rounded clock %lu by more than 5%%.",
+			clock, round_clock);
 		return -EINVAL;
+	}
 
 	return 0;
@@ -1329,4 +1334,6 @@ enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
 					 const struct drm_display_mode *mode)
 {
+	struct tidss_device *tidss = dispc->tidss;
+	struct drm_device *dev = &tidss->ddev;
 	u32 hsw, hfp, hbp, vsw, vfp, vbp;
 	enum dispc_vp_bus_type bus_type;
@@ -1334,12 +1341,18 @@ enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
 	bus_type = dispc->feat->vp_bus_type[hw_videoport];
 
-	if (WARN_ON(bus_type == DISPC_VP_TIED_OFF))
+	if (WARN_ON(bus_type == DISPC_VP_TIED_OFF)) {
+		drm_dbg(dev, "Invalid bus type.");
 		return MODE_BAD;
+	}
 
-	if (mode->hdisplay > 4096)
+	if (mode->hdisplay > 4096) {
+		drm_dbg(dev, "Number of active horizontal pixels above hardware limits.");
 		return MODE_BAD;
+	}
 
-	if (mode->vdisplay > 4096)
+	if (mode->vdisplay > 4096) {
+		drm_dbg(dev, "Number of active vertical lines above hardware limits.");
 		return MODE_BAD;
+	}
 
 	if (check_pixel_clock(dispc, hw_videoport, mode->clock * 1000))
@@ -1347,6 +1360,8 @@ enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
 
 	/* TODO: add interlace support */
-	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
+		drm_dbg(dev, "Interlace modes not supported.");
 		return MODE_NO_INTERLACE;
+	}
 
 	/*
@@ -1358,6 +1373,8 @@ enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
 	 * But for simplicity we enforce that always.
 	 */
-	if ((mode->hdisplay % 2) != 0)
+	if ((mode->hdisplay % 2) != 0) {
+		drm_dbg(dev, "Number of active horizontal pixels must be even.");
 		return MODE_BAD_HVALUE;
+	}
 
 	hfp = mode->hsync_start - mode->hdisplay;
@@ -1371,10 +1388,18 @@ enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
 	if (hsw < 1 || hsw > 256 ||
 	    hfp < 1 || hfp > 4096 ||
-	    hbp < 1 || hbp > 4096)
+	    hbp < 1 || hbp > 4096) {
+		drm_dbg(dev,
+			"Horizontal blanking or sync outside of hardware limits (fp: %u, sw: %u, bp: %u).",
+			hfp, hsw, hbp);
 		return MODE_BAD_HVALUE;
+	}
 
 	if (vsw < 1 || vsw > 256 ||
-	    vfp > 4095 || vbp > 4095)
+	    vfp > 4095 || vbp > 4095) {
+		drm_dbg(dev,
+			"Vertical blanking or sync outside of hardware limits (fp: %u, sw: %u, bp: %u).",
+			vfp, vsw, vbp);
 		return MODE_BAD_VVALUE;
+	}
 
 	if (dispc->memory_bandwidth_limit) {
@@ -1386,6 +1411,8 @@ enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
 		bandwidth = div_u64(bandwidth, mode->htotal * mode->vtotal);
 
-		if (dispc->memory_bandwidth_limit < bandwidth)
+		if (dispc->memory_bandwidth_limit < bandwidth) {
+			drm_dbg(dev, "Required memory bandwidth outside of hardware limits.");
 			return MODE_BAD;
+		}
 	}
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 31/34] drm/tidss: Implement hw_reset
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (29 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 30/34] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  8:09   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 32/34] drm/tidss: Implement readout support Maxime Ripard
                   ` (2 subsequent siblings)
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The dispc hardware needs to be put in a known-good state when the
driver starts. This was previously done unconditionally during
dispc_init(), but it should only be performed on the reset path when
implementing state readout.

Rename dispc_init_hw() to dispc_reset_hw(), export it, and remove
the call from dispc_init(). Implement &drm_mode_config_funcs.hw_reset
through tidss_hw_reset() which performs the dispc soft reset and then
calls drm_mode_config_helper_hw_reset() to reset the rest of the
objects.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tidss/tidss_dispc.c |  6 +-----
 drivers/gpu/drm/tidss/tidss_dispc.h |  1 +
 drivers/gpu/drm/tidss/tidss_kms.c   | 14 ++++++++++++++
 3 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
index 5943a5f25432..fd33edd80977 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -2910,5 +2910,5 @@ static int dispc_softreset(struct dispc_device *dispc)
 }
 
-static int dispc_init_hw(struct dispc_device *dispc)
+int dispc_reset_hw(struct dispc_device *dispc)
 {
 	struct device *dev = dispc->dev;
@@ -3060,8 +3060,4 @@ int dispc_init(struct tidss_device *tidss)
 			     &dispc->memory_bandwidth_limit);
 
-	r = dispc_init_hw(dispc);
-	if (r)
-		return r;
-
 	tidss->dispc = dispc;
 
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
index 2183254f1ab7..e51a2088b40c 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.h
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -146,4 +146,5 @@ void dispc_plane_enable(struct dispc_device *dispc, u32 hw_plane, bool enable);
 const u32 *dispc_plane_formats(struct dispc_device *dispc, unsigned int *len);
 
+int dispc_reset_hw(struct dispc_device *dispc);
 int dispc_init(struct tidss_device *tidss);
 void dispc_remove(struct tidss_device *tidss);
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
index 70c14c3be10d..3b97c08c4f71 100644
--- a/drivers/gpu/drm/tidss/tidss_kms.c
+++ b/drivers/gpu/drm/tidss/tidss_kms.c
@@ -11,4 +11,5 @@
 #include <drm/drm_of.h>
 #include <drm/drm_panel.h>
+#include <drm/drm_print.h>
 #include <drm/drm_vblank.h>
 
@@ -117,8 +118,21 @@ static int tidss_atomic_check(struct drm_device *ddev,
 }
 
+static void tidss_hw_reset(struct drm_device *dev)
+{
+	struct tidss_device *tidss = to_tidss(dev);
+	int ret;
+
+	ret = dispc_reset_hw(tidss->dispc);
+	if (ret)
+		drm_warn(dev, "Couldn't reset dispc");
+
+	drm_mode_config_helper_hw_reset(dev);
+}
+
 static const struct drm_mode_config_funcs mode_config_funcs = {
 	.fb_create = drm_gem_fb_create,
 	.atomic_check = tidss_atomic_check,
 	.atomic_commit = drm_atomic_helper_commit,
+	.hw_reset = tidss_hw_reset,
 };
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 32/34] drm/tidss: Implement readout support
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (30 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 31/34] drm/tidss: Implement hw_reset Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  8:22   ` sashiko-bot
  2026-10-29  7:34 ` [PATCH v7 33/34] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 34/34] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

With the hardware readout infrastructure now in place in the KMS
framework, we can provide it for the tidss driver.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tidss/tidss_crtc.c    |  91 ++++++++++
 drivers/gpu/drm/tidss/tidss_dispc.c   | 320 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tidss/tidss_dispc.h   |  18 ++
 drivers/gpu/drm/tidss/tidss_encoder.c |  42 +++++
 drivers/gpu/drm/tidss/tidss_kms.c     |   6 +-
 drivers/gpu/drm/tidss/tidss_plane.c   | 178 +++++++++++++++++++
 6 files changed, 654 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
index 3da49f77fa08..4409f8c9114b 100644
--- a/drivers/gpu/drm/tidss/tidss_crtc.c
+++ b/drivers/gpu/drm/tidss/tidss_crtc.c
@@ -5,6 +5,10 @@
  */
 
+#include <linux/clk.h>
+
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout_helper.h>
+#include <drm/drm_atomic_uapi.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_gem_dma_helper.h>
@@ -371,4 +375,88 @@ static struct drm_crtc_state *tidss_crtc_create_state(struct drm_crtc *crtc)
 }
 
+static int tidss_crtc_readout_state(struct drm_crtc *crtc,
+				    struct drm_atomic_sro_state *state,
+				    struct drm_crtc_state *crtc_state)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = to_tidss(ddev);
+	struct dispc_device *dispc = tidss->dispc;
+	struct tidss_crtc_state *tstate = to_tidss_crtc_state(crtc_state);
+	struct tidss_crtc *tcrtc =
+		to_tidss_crtc(crtc);
+	struct drm_display_mode mode = {};
+	int ret;
+
+	tidss_runtime_get(tidss);
+
+	if (!dispc_vp_is_enabled(dispc, tcrtc->hw_videoport))
+		goto out;
+
+	tstate->base.active = 1;
+	tstate->base.enable = 1;
+
+	ret = dispc_vp_readout_mode(dispc, tcrtc->hw_videoport, &mode);
+	if (ret)
+		goto err_runtime_put;
+
+	ret = drm_atomic_set_mode_for_crtc(&tstate->base, &mode);
+	if (WARN_ON(ret))
+		goto err_runtime_put;
+
+	drm_mode_copy(&tstate->base.adjusted_mode, &mode);
+
+	tstate->bus_flags = dispc_vp_get_bus_flags(dispc, tcrtc->hw_videoport);
+	ret = dispc_vp_get_bus_format(dispc, tcrtc->hw_videoport,
+				      &tstate->bus_format);
+	if (ret)
+		goto err_runtime_put;
+
+	/*
+	 * The active connectors and planes will be filled by their
+	 * respective readout callbacks.
+	 */
+
+out:
+	tidss_runtime_put(tidss);
+	return 0;
+
+err_runtime_put:
+	tidss_runtime_put(tidss);
+	return ret;
+}
+
+static void tidss_crtc_install_state(struct drm_crtc *crtc,
+				     struct drm_crtc_state *crtc_state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = to_tidss(ddev);
+	int ret;
+
+	tidss_runtime_get(tidss);
+
+	ret = dispc_vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
+	if (ret != 0)
+		return;
+
+	/* Turn vertical blanking interrupt reporting on. */
+	drm_crtc_vblank_on(crtc);
+}
+
+static bool tidss_crtc_compare_state(struct drm_crtc *crtc,
+				     struct drm_printer *p,
+				     struct drm_crtc_state *expected,
+				     struct drm_crtc_state *actual)
+{
+	struct tidss_crtc_state *t_expected = to_tidss_crtc_state(expected);
+	struct tidss_crtc_state *t_actual = to_tidss_crtc_state(actual);
+	bool ret = drm_atomic_helper_crtc_compare_state(crtc, p, expected, actual);
+
+	STATE_CHECK_U32_X(ret, p, crtc->name, t_expected, t_actual, bus_format);
+	STATE_CHECK_U32_X(ret, p, crtc->name, t_expected, t_actual, bus_flags);
+
+	return ret;
+}
+
 static struct drm_crtc_state *tidss_crtc_duplicate_state(struct drm_crtc *crtc)
 {
@@ -416,4 +504,7 @@ static const struct drm_crtc_funcs tidss_crtc_funcs = {
 	.page_flip = drm_atomic_helper_page_flip,
 	.atomic_create_state = tidss_crtc_create_state,
+	.atomic_sro_readout_state = tidss_crtc_readout_state,
+	.atomic_sro_install_state = tidss_crtc_install_state,
+	.atomic_sro_compare_state = tidss_crtc_compare_state,
 	.atomic_duplicate_state = tidss_crtc_duplicate_state,
 	.atomic_destroy_state = tidss_crtc_destroy_state,
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
index fd33edd80977..6ef7e62a58c5 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1209,4 +1209,10 @@ void dispc_vp_enable(struct dispc_device *dispc, u32 hw_videoport)
 }
 
+bool dispc_vp_is_enabled(struct dispc_device *dispc, u32 hw_videoport)
+{
+	return FIELD_GET(DISPC_VP_CONTROL_ENABLE_MASK,
+			 dispc_vp_read(dispc, hw_videoport, DISPC_VP_CONTROL));
+}
+
 void dispc_vp_disable(struct dispc_device *dispc, u32 hw_videoport)
 {
@@ -1462,4 +1468,226 @@ int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport,
 }
 
+static unsigned long calc_pixel_clock_hz(unsigned int htotal,
+					 unsigned int vtotal,
+					 unsigned int refresh,
+					 unsigned int freq_div)
+{
+	u64 rate = (u64)htotal * vtotal * refresh;
+
+	return div_u64(rate * 1000, freq_div);
+}
+
+static const unsigned int refresh_tries[] = {30, 50, 60};
+static const unsigned int refresh_factors_tries[] = {1000, 1001};
+
+static unsigned int dispc_find_closest_refresh_rate_from_clk(struct drm_device *dev,
+							     struct clk *clk,
+							     unsigned int htotal,
+							     unsigned int vtotal,
+							     unsigned long *pixel_clock_hz)
+{
+	unsigned long actual_clk_rate = clk_get_rate(clk);
+	unsigned long best_clk_rate = 0;
+	unsigned long best_rate_diff = ULONG_MAX;
+	unsigned int best_refresh = 0;
+	unsigned int i, j;
+
+	drm_dbg(dev, "Actual clock rate is %lu\n", actual_clk_rate);
+
+	for (i = 0; i < ARRAY_SIZE(refresh_tries); i++) {
+		for (j = 0; j < ARRAY_SIZE(refresh_factors_tries); j++) {
+			unsigned int try_refresh = refresh_tries[i];
+			unsigned int try_factor = refresh_factors_tries[j];
+			unsigned long try_clk_rate = calc_pixel_clock_hz(htotal,
+									 vtotal,
+									 try_refresh,
+									 try_factor);
+			unsigned long diff;
+
+			drm_dbg(dev, "Evaluating refresh %u, factor %u, rate %lu\n",
+				try_refresh, try_factor, try_clk_rate);
+
+			if (try_clk_rate == actual_clk_rate) {
+				drm_dbg(dev, "Found exact match. Stopping.\n");
+				best_refresh = try_refresh;
+				best_clk_rate = try_clk_rate;
+				goto out;
+			}
+
+
+			diff = abs_diff(actual_clk_rate, try_clk_rate);
+			if (diff < best_rate_diff) {
+				drm_dbg(dev, "Found new candidate. Difference is %lu\n", diff);
+				best_refresh = try_refresh;
+				best_clk_rate = try_clk_rate;
+				best_rate_diff = diff;
+			}
+		}
+	}
+
+	/*
+	 * The video port clock doesn't match any standard refresh rate,
+	 * so the mode we would report would be plain wrong. Report the
+	 * configuration as unsupported by returning 0, so that the
+	 * caller falls back on the regular initialization path.
+	 *
+	 * We tolerate the same 5% difference that
+	 * dispc_vp_set_clk_rate() accepts when programming the clock.
+	 */
+	if (dispc_pclk_diff(best_clk_rate, actual_clk_rate) > 5) {
+		drm_dbg(dev, "Clock rate %lu doesn't match any standard refresh rate\n",
+			actual_clk_rate);
+		return 0;
+	}
+
+out:
+	drm_dbg(dev, "Best candidate is %u Hz, pixel clock rate %lu Hz",
+		best_refresh, best_clk_rate);
+
+	if (pixel_clock_hz)
+		*pixel_clock_hz = best_clk_rate;
+
+	return best_refresh;
+}
+
+int dispc_vp_readout_mode(struct dispc_device *dispc,
+			  u32 hw_videoport,
+			  struct drm_display_mode *mode)
+{
+	struct tidss_device *tidss = dispc->tidss;
+	struct drm_device *dev = &tidss->ddev;
+	unsigned long pixel_clock;
+	unsigned int refresh;
+	u16 hdisplay, hfp, hsw, hbp;
+	u16 vdisplay, vfp, vsw, vbp;
+	u32 val;
+
+	val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_SIZE_SCREEN);
+	hdisplay = FIELD_GET(DISPC_VP_SIZE_SCREEN_HDISPLAY_MASK, val) + 1;
+	vdisplay = FIELD_GET(DISPC_VP_SIZE_SCREEN_VDISPLAY_MASK, val) + 1;
+
+	mode->hdisplay = hdisplay;
+	mode->vdisplay = vdisplay;
+
+	val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_TIMING_H);
+	hsw = FIELD_GET(DISPC_VP_TIMING_H_SYNC_PULSE_MASK, val) + 1;
+	hfp = FIELD_GET(DISPC_VP_TIMING_H_FRONT_PORCH_MASK, val) + 1;
+	hbp = FIELD_GET(DISPC_VP_TIMING_H_BACK_PORCH_MASK, val) + 1;
+
+	mode->hsync_start = hdisplay + hfp;
+	mode->hsync_end = hdisplay + hfp + hsw;
+	mode->htotal = hdisplay + hfp + hsw + hbp;
+
+	val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_TIMING_V);
+	vsw = FIELD_GET(DISPC_VP_TIMING_V_SYNC_PULSE_MASK, val) + 1;
+	vfp = FIELD_GET(DISPC_VP_TIMING_V_FRONT_PORCH_MASK, val);
+	vbp = FIELD_GET(DISPC_VP_TIMING_V_BACK_PORCH_MASK, val);
+
+	mode->vsync_start = vdisplay + vfp;
+	mode->vsync_end = vdisplay + vfp + vsw;
+	mode->vtotal = vdisplay + vfp + vsw + vbp;
+
+	refresh = dispc_find_closest_refresh_rate_from_clk(dev,
+							   dispc->vp_clk[hw_videoport],
+							   mode->htotal,
+							   mode->vtotal,
+							   &pixel_clock);
+	if (!refresh)
+		return -EINVAL;
+
+	mode->clock = pixel_clock / 1000;
+
+	val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_POL_FREQ);
+	if (FIELD_GET(DISPC_VP_POL_FREQ_IVS_MASK, val))
+		mode->flags |= DRM_MODE_FLAG_NVSYNC;
+	else
+		mode->flags |= DRM_MODE_FLAG_PVSYNC;
+
+	if (FIELD_GET(DISPC_VP_POL_FREQ_IHS_MASK, val))
+		mode->flags |= DRM_MODE_FLAG_NHSYNC;
+	else
+		mode->flags |= DRM_MODE_FLAG_PHSYNC;
+
+	mode->type |= DRM_MODE_TYPE_DRIVER;
+	drm_mode_set_name(mode);
+	drm_mode_set_crtcinfo(mode, 0);
+
+	return 0;
+}
+
+int dispc_vp_get_bus_format(struct dispc_device *dispc, u32 hw_videoport,
+			    u32 *bus_fmt)
+{
+	static const u32 datalines_to_width[] = { 12, 16, 18, 24, 30, 36 };
+	struct tidss_device *tidss = dispc->tidss;
+	struct drm_device *dev = &tidss->ddev;
+	u32 datalines, data_width;
+	unsigned int i;
+
+	datalines = VP_REG_GET(dispc, hw_videoport, DISPC_VP_CONTROL,
+			       DISPC_VP_CONTROL_DATALINES_MASK);
+
+	if (datalines < ARRAY_SIZE(datalines_to_width)) {
+		data_width = datalines_to_width[datalines];
+	} else {
+		drm_dbg(dev, "Unknown DATALINES encoding %u on %s\n",
+			datalines, dispc->feat->vp_name[hw_videoport]);
+		return -EINVAL;
+	}
+
+	if (dispc->feat->vp_bus_type[hw_videoport] == DISPC_VP_OLDI_AM65X) {
+		u32 oldi_cfg = dispc_vp_read(dispc, hw_videoport,
+					     DISPC_VP_DSS_OLDI_CFG);
+		u32 map = FIELD_GET(DISPC_VP_DSS_OLDI_CFG_MAP_MASK, oldi_cfg);
+
+		for (i = 0; i < ARRAY_SIZE(dispc_bus_formats); i++) {
+			if (dispc_bus_formats[i].is_oldi_fmt &&
+			    dispc_bus_formats[i].data_width == data_width &&
+			    dispc_bus_formats[i].am65x_oldi_mode_reg_val == map) {
+				*bus_fmt = dispc_bus_formats[i].bus_fmt;
+				return 0;
+			}
+		}
+
+		drm_dbg(dev, "Unknown OLDI mapping %u on %s\n", map,
+			dispc->feat->vp_name[hw_videoport]);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(dispc_bus_formats); i++) {
+		if (!dispc_bus_formats[i].is_oldi_fmt &&
+		    dispc_bus_formats[i].data_width == data_width) {
+			*bus_fmt = dispc_bus_formats[i].bus_fmt;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+u32 dispc_vp_get_bus_flags(struct dispc_device *dispc, u32 hw_videoport)
+{
+	u32 flags = 0;
+	u32 val;
+
+	val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_POL_FREQ);
+	if (FIELD_GET(DISPC_VP_POL_FREQ_IPC_MASK, val))
+		flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+	else
+		flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
+
+	if (FIELD_GET(DISPC_VP_POL_FREQ_IEO_MASK, val))
+		flags |= DRM_BUS_FLAG_DE_LOW;
+	else
+		flags |= DRM_BUS_FLAG_DE_HIGH;
+
+	if (FIELD_GET(DISPC_VP_POL_FREQ_RF_MASK, val))
+		flags |= DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE;
+	else
+		flags |= DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE;
+
+	return flags;
+}
+
 /* OVR */
 static void dispc_k2g_ovr_set_plane(struct dispc_device *dispc,
@@ -2107,4 +2335,15 @@ static const struct {
 };
 
+static u32 dispc_plane_find_fourcc_by_dss_code(u8 code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(dispc_color_formats); ++i)
+		if (dispc_color_formats[i].dss_code == code)
+			return dispc_color_formats[i].fourcc;
+
+	return 0;
+}
+
 static void dispc_plane_set_pixel_format(struct dispc_device *dispc,
 					 u32 hw_plane, u32 fourcc)
@@ -2272,4 +2511,79 @@ void dispc_plane_setup(struct dispc_device *dispc, u32 hw_plane,
 }
 
+void dispc_plane_get_picture_size(struct dispc_device *dispc, u32 hw_plane,
+				  unsigned int *width, unsigned int *height)
+{
+	u32 val;
+	u16 w, h;
+
+	val = dispc_vid_read(dispc, hw_plane, DISPC_VID_PICTURE_SIZE);
+	w = FIELD_GET(DISPC_VID_PICTURE_SIZE_MEMSIZEX_MASK, val);
+	h = FIELD_GET(DISPC_VID_PICTURE_SIZE_MEMSIZEY_MASK, val);
+
+	*width = w + 1;
+	*height = h + 1;
+}
+
+void dispc_plane_get_size(struct dispc_device *dispc, u32 hw_plane,
+			  unsigned int *width, unsigned int *height)
+{
+	bool lite = dispc->feat->vid_info[hw_plane].is_lite;
+
+	if (!lite) {
+		u32 val = dispc_vid_read(dispc, hw_plane, DISPC_VID_SIZE);
+		*width = FIELD_GET(DISPC_VID_SIZE_SIZEX_MASK, val) + 1;
+		*height = FIELD_GET(DISPC_VID_SIZE_SIZEY_MASK, val) + 1;
+	} else {
+		dispc_plane_get_picture_size(dispc, hw_plane, width, height);
+	}
+}
+
+const struct drm_format_info *
+dispc_plane_get_current_format(struct dispc_device *dispc, u32 hw_plane)
+{
+	const struct drm_format_info *info;
+	u32 fourcc;
+	u32 val;
+
+	val = dispc_vid_read(dispc, hw_plane, DISPC_VID_ATTRIBUTES);
+	fourcc = dispc_plane_find_fourcc_by_dss_code(
+		FIELD_GET(DISPC_VID_ATTRIBUTES_FORMAT_MASK, val));
+	if (!fourcc)
+		return ERR_PTR(-EINVAL);
+
+	info = drm_format_info(fourcc);
+	if (!info)
+		return ERR_PTR(-EINVAL);
+
+	return info;
+}
+
+u8 dispc_plane_get_global_alpha(struct dispc_device *dispc, u32 hw_plane)
+{
+	return FIELD_GET(DISPC_VID_GLOBAL_ALPHA_GLOBALALPHA_MASK,
+			 dispc_vid_read(dispc, hw_plane,
+					DISPC_VID_GLOBAL_ALPHA));
+}
+
+u32 dispc_plane_get_pixel_inc(struct dispc_device *dispc, u32 hw_plane)
+{
+	return dispc_vid_read(dispc, hw_plane, DISPC_VID_PIXEL_INC);
+}
+
+u32 dispc_plane_get_row_inc(struct dispc_device *dispc, u32 hw_plane)
+{
+	return dispc_vid_read(dispc, hw_plane, DISPC_VID_ROW_INC);
+}
+
+u16 dispc_plane_get_blend_mode(struct dispc_device *dispc, u32 hw_plane)
+{
+	u32 val = dispc_vid_read(dispc, hw_plane, DISPC_VID_ATTRIBUTES);
+
+	if (FIELD_GET(DISPC_VID_ATTRIBUTES_PREMULTIPLYALPHA_MASK, val))
+		return DRM_MODE_BLEND_PREMULTI;
+	else
+		return DRM_MODE_BLEND_COVERAGE;
+}
+
 void dispc_plane_enable(struct dispc_device *dispc, u32 hw_plane, bool enable)
 {
@@ -2278,4 +2592,10 @@ void dispc_plane_enable(struct dispc_device *dispc, u32 hw_plane, bool enable)
 }
 
+bool dispc_plane_is_enabled(struct dispc_device *dispc, u32 hw_plane)
+{
+	return FIELD_GET(DISPC_VID_ATTRIBUTES_ENABLE_MASK,
+			 dispc_vid_read(dispc, hw_plane, DISPC_VID_ATTRIBUTES));
+}
+
 static u32 dispc_vid_get_fifo_size(struct dispc_device *dispc, u32 hw_plane)
 {
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
index e51a2088b40c..0496dec09310 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.h
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -118,4 +118,5 @@ void dispc_vp_prepare(struct dispc_device *dispc, u32 hw_videoport,
 		      const struct drm_crtc_state *state);
 void dispc_vp_enable(struct dispc_device *dispc, u32 hw_videoport);
+bool dispc_vp_is_enabled(struct dispc_device *dispc, u32 hw_videoport);
 void dispc_vp_disable(struct dispc_device *dispc, u32 hw_videoport);
 void dispc_vp_unprepare(struct dispc_device *dispc, u32 hw_videoport);
@@ -133,4 +134,10 @@ int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport,
 void dispc_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
 		    const struct drm_crtc_state *state, bool newmodeset);
+int dispc_vp_readout_mode(struct dispc_device *dispc,
+			  u32 hw_videoport,
+			  struct drm_display_mode *mode);
+int dispc_vp_get_bus_format(struct dispc_device *dispc, u32 hw_videoport,
+			    u32 *bus_fmt);
+u32 dispc_vp_get_bus_flags(struct dispc_device *dispc, u32 hw_videoport);
 
 int dispc_runtime_suspend(struct dispc_device *dispc);
@@ -143,5 +150,16 @@ void dispc_plane_setup(struct dispc_device *dispc, u32 hw_plane,
 		       struct drm_plane_state *state,
 		       u32 hw_videoport);
+void dispc_plane_get_picture_size(struct dispc_device *dispc, u32 hw_plane,
+				  unsigned int *width, unsigned int *height);
+void dispc_plane_get_size(struct dispc_device *dispc, u32 hw_plane,
+			  unsigned int *width, unsigned int *height);
+const struct drm_format_info *
+dispc_plane_get_current_format(struct dispc_device *dispc, u32 hw_plane);
+u8 dispc_plane_get_global_alpha(struct dispc_device *dispc, u32 hw_plane);
+u32 dispc_plane_get_pixel_inc(struct dispc_device *dispc, u32 hw_plane);
+u32 dispc_plane_get_row_inc(struct dispc_device *dispc, u32 hw_plane);
+u16 dispc_plane_get_blend_mode(struct dispc_device *dispc, u32 hw_plane);
 void dispc_plane_enable(struct dispc_device *dispc, u32 hw_plane, bool enable);
+bool dispc_plane_is_enabled(struct dispc_device *dispc, u32 hw_plane);
 const u32 *dispc_plane_formats(struct dispc_device *dispc, unsigned int *len);
 
diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c
index 10dbcc6cdf6a..5034e601ac86 100644
--- a/drivers/gpu/drm/tidss/tidss_encoder.c
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -6,6 +6,8 @@
 
 #include <linux/export.h>
+#include <linux/media-bus-format.h>
 
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
@@ -74,4 +76,42 @@ static int tidss_bridge_atomic_check(struct drm_bridge *bridge,
 }
 
+static int tidss_readout_state(struct drm_bridge *bridge,
+			       struct drm_atomic_sro_state *state,
+			       struct drm_bridge_state *bridge_state,
+			       struct drm_crtc_state *crtc_state,
+			       struct drm_connector_state *conn_state)
+{
+	struct tidss_crtc_state *tcrtc_state = to_tidss_crtc_state(crtc_state);
+
+	/*
+	 * Reconstruct the bridge bus configuration from the CRTC readout state.
+	 *
+	 * During normal atomic check,
+	 * drm_atomic_bridge_chain_select_bus_fmts() and
+	 * drm_atomic_bridge_propagate_bus_flags() walk the bridge chain to
+	 * negotiate formats and propagate flags. During SRO readout, those
+	 * functions do not run, so each bridge must reconstruct its own
+	 * state.
+	 *
+	 * This bridge is the first in the chain, so the CRTC state (read out
+	 * before bridges) is used as the source. The output bus format is
+	 * taken from the CRTC's bus_format, which during normal atomic check
+	 * is set by tidss_bridge_atomic_check() to the next bridge's
+	 * negotiated input format. The input bus format is
+	 * MEDIA_BUS_FMT_FIXED since this bridge does not implement
+	 * atomic_get_input_bus_fmts. Bus flags are taken from the CRTC
+	 * state, which reads them from hardware registers during its own
+	 * readout.
+	 */
+
+	bridge_state->input_bus_cfg.format = MEDIA_BUS_FMT_FIXED;
+	bridge_state->output_bus_cfg.format = tcrtc_state->bus_format;
+
+	bridge_state->output_bus_cfg.flags = tcrtc_state->bus_flags;
+	bridge_state->input_bus_cfg.flags = tcrtc_state->bus_flags;
+
+	return 0;
+}
+
 static const struct drm_bridge_funcs tidss_bridge_funcs = {
 	.attach				= tidss_bridge_attach,
@@ -80,4 +120,6 @@ static const struct drm_bridge_funcs tidss_bridge_funcs = {
 	.atomic_duplicate_state		= drm_atomic_helper_bridge_duplicate_state,
 	.atomic_destroy_state		= drm_atomic_helper_bridge_destroy_state,
+	.atomic_sro_readout_state	= tidss_readout_state,
+	.atomic_sro_compare_state	= drm_atomic_helper_bridge_compare_state,
 };
 
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
index 3b97c08c4f71..b0b69b853111 100644
--- a/drivers/gpu/drm/tidss/tidss_kms.c
+++ b/drivers/gpu/drm/tidss/tidss_kms.c
@@ -7,4 +7,6 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_gem_framebuffer_helper.h>
@@ -65,5 +67,6 @@ static void tidss_atomic_commit_tail(struct drm_atomic_commit *old_state)
 
 static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = {
-	.atomic_commit_tail = tidss_atomic_commit_tail,
+	.atomic_commit_tail	= tidss_atomic_commit_tail,
+	.atomic_sro_build_state = drm_atomic_helper_sro_build_state,
 };
 
@@ -134,4 +137,5 @@ static const struct drm_mode_config_funcs mode_config_funcs = {
 	.atomic_check = tidss_atomic_check,
 	.atomic_commit = drm_atomic_helper_commit,
+	.atomic_sro_readout_state = drm_atomic_helper_sro_readout_state,
 	.hw_reset = tidss_hw_reset,
 };
diff --git a/drivers/gpu/drm/tidss/tidss_plane.c b/drivers/gpu/drm/tidss/tidss_plane.c
index b0d5d6959b88..ebdb63f8e8f1 100644
--- a/drivers/gpu/drm/tidss/tidss_plane.c
+++ b/drivers/gpu/drm/tidss/tidss_plane.c
@@ -7,4 +7,6 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_blend.h>
 #include <drm/drm_crtc.h>
@@ -13,4 +15,5 @@
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_panic_helper.h>
 
@@ -177,4 +180,177 @@ static const struct drm_plane_helper_funcs tidss_primary_plane_helper_funcs = {
 };
 
+static const struct drm_framebuffer_funcs tidss_plane_readout_fb_funcs = {
+	.destroy	= drm_gem_fb_destroy,
+};
+
+static struct drm_framebuffer *tidss_plane_readout_fb(struct drm_plane *plane)
+{
+	struct drm_device *ddev = plane->dev;
+	struct tidss_device *tidss = to_tidss(ddev);
+	struct dispc_device *dispc = tidss->dispc;
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+	const struct drm_format_info *info;
+	struct drm_framebuffer *fb;
+	int ret;
+
+	fb = kzalloc_obj(*fb);
+	if (!fb)
+		return ERR_PTR(-ENOMEM);
+
+	fb->dev = plane->dev;
+
+	info = dispc_plane_get_current_format(dispc, tplane->hw_plane_id);
+	if (IS_ERR(info)) {
+		ret = PTR_ERR(info);
+		goto err_free_fb;
+	}
+
+	// TODO: Figure out YUV and multiplanar formats, along with
+	// the color encoding and range
+	if (info->is_yuv) {
+		ret = -EINVAL;
+		goto err_free_fb;
+	}
+
+	fb->format = info;
+
+	dispc_plane_get_picture_size(dispc, tplane->hw_plane_id, &fb->width,
+				     &fb->height);
+
+	/*
+	 * A pixel increment of 1 means that the DMA engine fetches every
+	 * pixel of the source buffer. Any other value means that
+	 * horizontal decimation is enabled, and the buffer geometry
+	 * cannot be reconstructed from the registers.
+	 */
+	if (dispc_plane_get_pixel_inc(dispc, tplane->hw_plane_id) != 1) {
+		ret = -EINVAL;
+		goto err_free_fb;
+	}
+
+	/*
+	 * The row increment register holds the extra bytes between the
+	 * end of a row of the picture and the beginning of the next one,
+	 * which includes any padding the firmware might have programmed.
+	 */
+	fb->pitches[0] = fb->width * (drm_format_info_bpp(info, 0) / 8) +
+			 dispc_plane_get_row_inc(dispc, tplane->hw_plane_id) - 1;
+
+	/*
+	 * TODO: The DMA base address has the offsets baked in, and there
+	 * is no backing buffer to recover them from.
+	 */
+	fb->offsets[0] = 0;
+
+	ret = drm_framebuffer_init(plane->dev, fb, &tidss_plane_readout_fb_funcs);
+	if (ret) {
+		kfree(fb);
+		return ERR_PTR(ret);
+	}
+
+	return fb;
+
+err_free_fb:
+	kfree(fb);
+	return ERR_PTR(ret);
+}
+
+static struct drm_crtc *tidss_plane_readout_crtc(struct drm_plane *plane)
+{
+	struct drm_device *dev = plane->dev;
+
+	/* TODO: Add support for multiple CRTCs */
+	if (dev->num_crtcs != 1)
+		return ERR_PTR(-EINVAL);
+
+	return list_first_entry(&dev->mode_config.crtc_list, struct drm_crtc, head);
+}
+
+static int tidss_plane_atomic_readout_state(struct drm_plane *plane,
+					    struct drm_atomic_sro_state *state,
+					    struct drm_plane_state *plane_state)
+{
+	struct drm_device *ddev = plane->dev;
+	struct tidss_device *tidss = to_tidss(ddev);
+	struct dispc_device *dispc = tidss->dispc;
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+	struct drm_crtc_state *crtc_state;
+	struct drm_framebuffer *fb;
+	struct drm_crtc *crtc;
+	unsigned int in_w, in_h;
+	int ret;
+
+	tidss_runtime_get(tidss);
+
+	if (!dispc_plane_is_enabled(dispc, tplane->hw_plane_id))
+		goto out;
+
+	fb = tidss_plane_readout_fb(plane);
+	if (IS_ERR(fb)) {
+		ret = PTR_ERR(fb);
+		goto err_runtime_pm;
+	}
+
+	crtc = tidss_plane_readout_crtc(plane);
+	if (IS_ERR(crtc)) {
+		drm_framebuffer_put(fb);
+		ret = PTR_ERR(crtc);
+		goto err_runtime_pm;
+	}
+
+	plane_state->fb = fb;
+	plane_state->crtc = crtc;
+	plane_state->visible = true;
+
+	dispc_plane_get_picture_size(dispc, tplane->hw_plane_id, &in_w, &in_h);
+	plane_state->src_w = in_w << 16;
+	plane_state->src_h = in_h << 16;
+
+	dispc_plane_get_size(dispc, tplane->hw_plane_id, &plane_state->crtc_w,
+			     &plane_state->crtc_h);
+
+	// TODO: Handle crtc_x/crtc_x/src_x/src_y
+	// crtc_x/crtc_y are handled by DISPC_OVR_ATTRIBUTES / OVR1_DSS_ATTRIBUTES
+
+	// TODO: Handle zpos, see DISPC_OVR_ATTRIBUTES / OVR1_DSS_ATTRIBUTES
+
+	plane_state->src.x1 = 0;
+	plane_state->src.x2 = plane_state->src_w;
+	plane_state->src.y1 = 0;
+	plane_state->src.y2 = plane_state->src_h;
+	plane_state->dst.x1 = 0;
+	plane_state->dst.x2 = plane_state->crtc_w;
+	plane_state->dst.y1 = 0;
+	plane_state->dst.y2 = plane_state->crtc_h;
+
+	/*
+	 * The global alpha register is 8 bits wide and is programmed
+	 * from the high byte of the 16 bits alpha property.
+	 * Replicating the byte is the only inverse that preserves the
+	 * fraction of full opacity (0x00 -> 0x0000, 0xff -> 0xffff)
+	 * and round-trips through a program-then-readout cycle.
+	 */
+	plane_state->alpha =
+		dispc_plane_get_global_alpha(dispc, tplane->hw_plane_id) * 0x0101;
+	plane_state->pixel_blend_mode =
+		dispc_plane_get_blend_mode(dispc, tplane->hw_plane_id);
+
+	crtc_state = drm_atomic_sro_get_crtc_state(state, crtc);
+	if (!crtc_state) {
+		ret = -ENODEV;
+		goto err_runtime_pm;
+	}
+
+	crtc_state->plane_mask |= drm_plane_mask(plane);
+
+out:
+	tidss_runtime_put(tidss);
+	return 0;
+
+err_runtime_pm:
+	tidss_runtime_put(tidss);
+	return ret;
+}
+
 static const struct drm_plane_funcs tidss_plane_funcs = {
 	.update_plane = drm_atomic_helper_update_plane,
@@ -182,4 +358,6 @@ static const struct drm_plane_funcs tidss_plane_funcs = {
 	.destroy = drm_plane_destroy,
 	.atomic_create_state = drm_atomic_helper_plane_create_state,
+	.atomic_sro_readout_state = tidss_plane_atomic_readout_state,
+	.atomic_sro_compare_state = drm_atomic_helper_plane_compare_state,
 	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 33/34] drm/tidss: encoder: Implement atomic_sro_get_current_crtc
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (31 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 32/34] drm/tidss: Implement readout support Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  7:34 ` [PATCH v7 34/34] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
  33 siblings, 0 replies; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

The tidss driver doesn't really implement anything with encoders, it
just relies on simple encoders, bridges and drm_bridge_connector.

In order to figure out the CRTC -> connector association from the
hardware state, we do need encoder support though, through the
get_current_crtc callback.

Since the tidss encoders are always connected to a single CRTC, we don't
really need to read the hardware state though, we can simply return the
one we know we are always connected to.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tidss/tidss_encoder.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c
index 5034e601ac86..1ca054a3cd87 100644
--- a/drivers/gpu/drm/tidss/tidss_encoder.c
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -124,5 +124,20 @@ static const struct drm_bridge_funcs tidss_bridge_funcs = {
 };
 
+static struct drm_crtc *tidss_encoder_get_current_crtc(struct drm_encoder *encoder)
+{
+	struct drm_crtc *crtc;
+
+	WARN_ON(hweight32(encoder->possible_crtcs) > 1);
+
+	drm_for_each_crtc(crtc, encoder->dev) {
+		if (encoder->possible_crtcs == (1 << drm_crtc_index(crtc)))
+			return crtc;
+	}
+
+	return NULL;
+}
+
 static const struct drm_encoder_funcs tidss_encoder_funcs = {
+	.atomic_sro_get_current_crtc = tidss_encoder_get_current_crtc,
 	.destroy = drm_encoder_cleanup,
 };

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v7 34/34] drm/bridge: sii902x: Implement hw state readout
  2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
                   ` (32 preceding siblings ...)
  2026-10-29  7:34 ` [PATCH v7 33/34] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
@ 2026-10-29  7:34 ` Maxime Ripard
  2026-10-29  8:12   ` sashiko-bot
  33 siblings, 1 reply; 50+ messages in thread
From: Maxime Ripard @ 2026-10-29  7:34 UTC (permalink / raw)
  To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen
  Cc: Devarsh Thakkar, dri-devel, Maxime Ripard

Implement the hardware state readout for the sii902x bridge
now that all the infrastructure is in place.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/bridge/sii902x.c | 91 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
index e067c0636e7f..ab5ffcf6882f 100644
--- a/drivers/gpu/drm/bridge/sii902x.c
+++ b/drivers/gpu/drm/bridge/sii902x.c
@@ -23,4 +23,6 @@
 
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_drv.h>
@@ -538,4 +540,87 @@ sii902x_bridge_mode_valid(struct drm_bridge *bridge,
 }
 
+static int
+sii902x_bridge_connector_readout(struct drm_bridge *bridge,
+				 struct drm_atomic_sro_state *state,
+				 struct drm_connector_state *conn_state)
+{
+	struct sii902x *sii902x = bridge_to_sii902x(bridge);
+	struct drm_connector *connector = conn_state->connector;
+	struct drm_crtc_state *crtc_state;
+	struct drm_encoder *encoder;
+	struct drm_crtc *crtc;
+	int ret;
+
+	ret = regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN);
+	/* I2C error */
+	if (ret < 0)
+		return ret;
+	/* Device is powered down, nothing to read out */
+	if (ret)
+		return 0;
+
+	encoder = bridge->encoder;
+	if (!encoder->funcs || !encoder->funcs->atomic_sro_get_current_crtc)
+		return -ENODEV;
+
+	crtc = encoder->funcs->atomic_sro_get_current_crtc(encoder);
+	if (!crtc)
+		return -ENODEV;
+
+	crtc_state = drm_atomic_sro_get_crtc_state(state, crtc);
+	if (!crtc_state)
+		return -ENODEV;
+
+	crtc_state->encoder_mask |= drm_encoder_mask(encoder);
+	crtc_state->connector_mask |= drm_connector_mask(connector);
+
+	/*
+	 * drm_atomic_set_crtc_for_connector() takes a connector reference
+	 * when setting the crtc pointer, which is later released by
+	 * __drm_atomic_helper_connector_destroy_state(). SRO readout
+	 * bypasses that path, so take the reference explicitly.
+	 */
+	drm_connector_get(connector);
+	conn_state->crtc = crtc;
+	conn_state->best_encoder = encoder;
+
+	return 0;
+}
+
+static int sii902x_bridge_readout_state(struct drm_bridge *bridge,
+					struct drm_atomic_sro_state *state,
+					struct drm_bridge_state *bridge_state,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state)
+{
+	struct sii902x *sii902x = bridge_to_sii902x(bridge);
+	u32 input_format;
+
+	if (regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN))
+		return 0;
+
+	switch (sii902x->bus_width) {
+	case 16:
+		input_format = MEDIA_BUS_FMT_RGB565_1X16;
+		break;
+	case 18:
+		input_format = MEDIA_BUS_FMT_RGB666_1X18;
+		break;
+	case 24:
+		input_format = MEDIA_BUS_FMT_RGB888_1X24;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	bridge_state->input_bus_cfg.format = input_format;
+	bridge_state->input_bus_cfg.flags = bridge->timings->input_bus_flags;
+
+	/* The sii902x is a passthrough transmitter, output format equals input. */
+	bridge_state->output_bus_cfg.format = input_format;
+
+	return 0;
+}
+
 static const struct drm_bridge_funcs sii902x_bridge_funcs = {
 	.attach = sii902x_bridge_attach,
@@ -548,4 +633,7 @@ static const struct drm_bridge_funcs sii902x_bridge_funcs = {
 	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+	.atomic_sro_compare_state = drm_atomic_helper_bridge_compare_state,
+	.atomic_sro_readout_state = sii902x_bridge_readout_state,
+	.atomic_sro_connector_readout = sii902x_bridge_connector_readout,
 	.atomic_get_input_bus_fmts = sii902x_bridge_atomic_get_input_bus_fmts,
 	.atomic_check = sii902x_bridge_atomic_check,
@@ -1134,5 +1222,6 @@ static int sii902x_init(struct sii902x *sii902x)
 	sii902x->bridge.of_node = dev->of_node;
 	sii902x->bridge.timings = &default_sii902x_timings;
-	sii902x->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID;
+	sii902x->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID |
+			      DRM_BRIDGE_OP_CONNECTOR_HW_READOUT;
 	sii902x->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 11/34] drm/atomic_sro: Create kernel parameter to force or disable readout
  2026-10-29  7:34 ` [PATCH v7 11/34] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
@ 2026-10-29  7:46   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  7:46 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] drm/atomic_sro: missing kernel-parameters.txt entry for drm.atomic_readout
--

commit c7a4855501de73acc47469e5832c891678cde200
Author: Maxime Ripard <mripard@kernel.org>

drm/atomic_sro: Create kernel parameter to force or disable readout

This commit adds the atomic_readout module parameter to control hardware
state readout behavior and introduces drm_atomic_sro_device_can_readout()
to check whether readout is supported and enabled on a DRM device.

> diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
> index 2f8df00e993eb..e93b0e4f0839e 100644
> --- a/drivers/gpu/drm/drm_atomic_state_readout.c
> +++ b/drivers/gpu/drm/drm_atomic_state_readout.c
> @@ -14,6 +14,42 @@
>  #include "drm_internal.h"
>  #include "drm_crtc_internal.h"
>  
> +enum drm_atomic_readout_status {
> +	DRM_ATOMIC_READOUT_DISABLED = 0,
> +	DRM_ATOMIC_READOUT_ENABLED,
> +	DRM_ATOMIC_READOUT_SKIP_MISSING_COMPARE,
> +	DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT,
> +};
> +
> +static unsigned int atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
> +module_param_unsafe(atomic_readout, uint, 0400);
> +MODULE_PARM_DESC(atomic_readout,
> +		 "Enable Hardware State Readout (0 = disabled, 1 = enabled, 2 = ignore missing compares, 3 = ignore missing readouts and compares, default = 1)");

[Severity: Low]
Since drivers/gpu/drm/drm_atomic_state_readout.c is built into the drm
module whenever CONFIG_DRM is enabled, module_param_unsafe(atomic_readout,
...) exposes drm.atomic_readout on the kernel command line.

Should this new boot and module parameter also be documented in
Documentation/admin-guide/kernel-parameters.txt?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=11

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 10/34] drm/atomic_sro: Create drm_atomic_sro_state container
  2026-10-29  7:34 ` [PATCH v7 10/34] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
@ 2026-10-29  7:50   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  7:50 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] drm/atomic_sro: missing EXPORT_SYMBOL() on drm_atomic_sro_state_get_device()
--

commit 272e19a2c59ccd440923a43421c7657b21dcc87e
Author: Maxime Ripard <mripard@kernel.org>

drm/atomic_sro: Create drm_atomic_sro_state container

This commit introduces the struct drm_atomic_sro_state container to hold
hardware readout states for planes, CRTCs, connectors, and private objects,
along with helper functions to allocate, free, print, and access those
states.

> diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
> new file mode 100644
> index 0000000000000..2f8df00e993eb
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_atomic_state_readout.c
> @@ -0,0 +1,475 @@

[ ... ]

> +/**
> + * drm_atomic_sro_state_get_device - get the DRM device from an SRO state
> + * @state: SRO state
> + *
> + * RETURNS:
> + *
> + * The &struct drm_device associated with the SRO state.
> + */
> +struct drm_device *
> +drm_atomic_sro_state_get_device(struct drm_atomic_sro_state *state)
> +{
> +	return state->dev;
> +}

[Severity: Low]
Should drm_atomic_sro_state_get_device() have an EXPORT_SYMBOL() here?

struct drm_atomic_sro_state is an opaque structure defined privately in
drivers/gpu/drm/drm_atomic_state_readout.c, and
drm_atomic_sro_state_get_device() is declared in
include/drm/drm_atomic_state_readout.h alongside the other public SRO state
helpers. Unlike all of the other public functions in this file, it is not
exported with EXPORT_SYMBOL(drm_atomic_sro_state_get_device).

When a loadable module calls drm_atomic_sro_state_get_device(), such as the
KUnit test in drivers/gpu/drm/tests/drm_atomic_state_readout_test.c when
built with CONFIG_DRM_KUNIT_TEST=m, will this cause a modpost undefined
symbol failure?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=10

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 01/34] drm/bridge-helper: Don't unlock the connection mutex after a backoff
  2026-10-29  7:33 ` [PATCH v7 01/34] drm/bridge-helper: Don't unlock the connection mutex after a backoff Maxime Ripard
@ 2026-10-29  7:51   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  7:51 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] drm/bridge-helper: lock drop and NULL deref in drm_bridge_helper_reset_crtc()
--

commit 110ed6cb32b618d9ec32a80d2d60952c2bc006ae
Author: Maxime Ripard <mripard@kernel.org>

drm/bridge-helper: Don't unlock the connection mutex after a backoff

This commit removes the unconditional connection_mutex unlock in
drm_bridge_helper_reset_crtc() and re-acquires connection_mutex after calling
drm_atomic_get_connector_for_encoder(), leaving lock release to the caller via
drm_modeset_drop_locks() or drm_modeset_backoff().

> diff --git a/drivers/gpu/drm/drm_bridge_helper.c b/drivers/gpu/drm/drm_bridge_helper.c
> index 0a3c8fee66b32..6998e7c41d30e 100644
> --- a/drivers/gpu/drm/drm_bridge_helper.c
> +++ b/drivers/gpu/drm/drm_bridge_helper.c

[ ... ]

> @@ -33,26 +37,33 @@ int drm_bridge_helper_reset_crtc(struct drm_bridge *bridge,
>  	struct drm_crtc *crtc;
>  	int ret;
>  
> +	/*
> +	 * Every lock taken with @ctx is released either by a backoff or
> +	 * by the caller through drm_modeset_drop_locks(), so we must not
> +	 * unlock the connection mutex ourselves.
> +	 */
>  	ret = drm_modeset_lock(&dev->mode_config.connection_mutex, ctx);
>  	if (ret)
>  		return ret;
>  
>  	connector = drm_atomic_get_connector_for_encoder(encoder, ctx);
> -	if (IS_ERR(connector)) {
> -		ret = PTR_ERR(connector);
> -		goto out;
> -	}
> +	if (IS_ERR(connector))
> +		return PTR_ERR(connector);
> +
> +	/*
> +	 * drm_atomic_get_connector_for_encoder() may have backed off,
> +	 * releasing the connection mutex in the process. Take it back
> +	 * before looking at the connector state.
> +	 */
> +	ret = drm_modeset_lock(&dev->mode_config.connection_mutex, ctx);
> +	if (ret)
> +		return ret;
>  
> -	if (!connector->state) {
> -		ret = -EINVAL;
> -		goto out;
> -	}
> +	if (!connector->state)
> +		return -EINVAL;
>  
>  	crtc = connector->state->crtc;
> -	ret = drm_atomic_helper_reset_crtc(crtc, ctx);
>  
> -out:
> -	drm_modeset_unlock(&dev->mode_config.connection_mutex);
> -	return ret;
> +	return drm_atomic_helper_reset_crtc(crtc, ctx);

[Severity: High]
Does drm_atomic_get_connector_for_encoder() actually back off on deadlock, or
does it unconditionally unlock connection_mutex before returning?

Looking at drivers/gpu/drm/drm_atomic.c:drm_atomic_get_connector_for_encoder(),
neither it nor drm_atomic_helper_reset_crtc() calls drm_modeset_backoff().
Instead, drm_atomic_get_connector_for_encoder() acquires connection_mutex
(which succeeds as a no-op via -EALREADY when already held in ctx) and then
unconditionally drops it at the end of the function:

drivers/gpu/drm/drm_atomic.c:drm_atomic_get_connector_for_encoder() {
	ret = drm_modeset_lock(&dev->mode_config.connection_mutex, ctx);
	if (ret)
		return ERR_PTR(ret);

	drm_connector_list_iter_begin(dev, &conn_iter);
	drm_for_each_connector_iter(connector, &conn_iter) {
		if (!connector->state)
			continue;

		if (encoder == connector->state->best_encoder) {
			out_connector = connector;
			break;
		}
	}
	drm_connector_list_iter_end(&conn_iter);
	drm_modeset_unlock(&dev->mode_config.connection_mutex);

	return out_connector;
}

Because drm_atomic_get_connector_for_encoder() drops both the connector list
iterator reference and connection_mutex before returning, there is an unlocked
window before drm_bridge_helper_reset_crtc() re-acquires connection_mutex. Also,
if drm_atomic_get_connector_for_encoder() finds no matching connector and
returns ERR_PTR(-EINVAL), connection_mutex has already been unlocked even if
the caller held it in ctx.

If a concurrent modeset or atomic commit runs during that unlocked window (for
example, while sn65dsi83_reset_work() -> sn65dsi83_reset_pipe() ->
drm_bridge_helper_reset_crtc() is executing) and disables the connector or
reassigns it to another encoder, connector->state remains non-NULL while
connector->state->best_encoder and connector->state->crtc can become NULL or
point to a different pipeline.

When drm_bridge_helper_reset_crtc() re-locks connection_mutex, it only checks
if (!connector->state) and neither re-verifies connector->state->best_encoder
== encoder nor checks that crtc is non-NULL before calling
drm_atomic_helper_reset_crtc(crtc, ctx):

drivers/gpu/drm/drm_atomic_helper.c:drm_atomic_helper_reset_crtc() {
	...
	state = drm_atomic_commit_alloc(crtc->dev);
	if (!state)
		return -ENOMEM;
	...
}

Can this race cause a NULL pointer dereference on crtc->dev in
drm_atomic_helper_reset_crtc(), or reset the wrong CRTC?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=1

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 24/34] drm/tests: kunit: Add a drm_printer helper
  2026-10-29  7:34 ` [PATCH v7 24/34] drm/tests: kunit: Add a drm_printer helper Maxime Ripard
@ 2026-10-29  7:56   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  7:56 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] drm/tests: wrong constant family in drm_kunit_helper_printer() kernel-doc
--

commit 886820e203ca83b1374826ea99ea6f649c1fd41e
Author: Maxime Ripard <mripard@kernel.org>

drm/tests: kunit: Add a drm_printer helper

This commit adds drm_kunit_helper_printer() to create a drm_printer that
forwards messages to kunit_log() at a specified log level.

> diff --git a/drivers/gpu/drm/tests/drm_kunit_helpers.c b/drivers/gpu/drm/tests/drm_kunit_helpers.c
> index 63d0249465005..fad911772ee59 100644
> --- a/drivers/gpu/drm/tests/drm_kunit_helpers.c
> +++ b/drivers/gpu/drm/tests/drm_kunit_helpers.c
> @@ -8,6 +8,7 @@
>  #include <drm/drm_fourcc.h>
>  #include <drm/drm_kunit_helpers.h>
>  #include <drm/drm_managed.h>
> +#include <drm/drm_print.h>
>  
>  #include <kunit/device.h>
>  #include <kunit/resource.h>
> @@ -18,6 +19,83 @@
>  
>  #define KUNIT_DEVICE_NAME	"drm-kunit-mock-device"
>  
> +struct drm_printer_ctx {
> +	struct drm_printer	p;
> +	int			level;
> +	struct kunit		*test;
> +};
> +
> +/*
> + * kunit_log() is a macro that only accepts a compile-time log level,
> + * so the level has to be dispatched to the matching kunit_log() call.
> + */
> +static void drm_kunit_printf(struct drm_printer *p, struct va_format *vaf)
> +{
> +	struct drm_printer_ctx *ctx = p->arg;
> +
> +	switch (ctx->level) {
> +	case LOGLEVEL_EMERG:
> +		kunit_log(KERN_EMERG, ctx->test, "%pV", vaf);
> +		break;

[ ... ]

> +/**
> + * drm_kunit_helper_printer - Creates a &drm_printer logging into a KUnit test log
> + * @test: The test context object
> + * @level: The log level to log the messages at, one of the KERN_* constants

[Severity: Low]
Should the kernel-doc for @level in drm_kunit_helper_printer() refer to the
LOGLEVEL_* constants instead of KERN_*?

The KERN_* macros are string literals, whereas drm_kunit_helper_printer()
takes an int level and drm_kunit_printf() switches on ctx->level against
LOGLEVEL_EMERG through LOGLEVEL_INFO. Passing a KERN_* constant here will
trigger a pointer-to-integer conversion warning and fall through to the
default case in drm_kunit_printf().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=24

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 05/34] drm/atomic: Add drm_private_obj name
  2026-10-29  7:33 ` [PATCH v7 05/34] drm/atomic: Add drm_private_obj name Maxime Ripard
@ 2026-10-29  7:56   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  7:56 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/bridge: NULL bridge->name causes drm_atomic_private_obj_init() failure
- [Medium] drm/dp_tunnel: empty group->name duplicated in init_group()
--

commit 4d9e18eb9a2836a0d646f48abba1d11dfdce7d31
Author: Maxime Ripard <mripard@kernel.org>

drm/atomic: Add drm_private_obj name

This commit adds a human-readable name field to drm_private_obj,
allocated in drm_atomic_private_obj_init() and freed in
drm_atomic_private_obj_fini(), and updates existing callers to pass a
name.

> diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c
> index d29ad4116e641..391d643793eb3 100644
> --- a/drivers/gpu/drm/display/drm_dp_tunnel.c
> +++ b/drivers/gpu/drm/display/drm_dp_tunnel.c
> @@ -1732,6 +1732,7 @@ static bool init_group(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_tunnel_group
>  	INIT_LIST_HEAD(&group->tunnels);
>  
>  	drm_atomic_private_obj_init(mgr->dev, &group->base,
> +				    group->name,
>  				    &tunnel_group_funcs);
>  
>  	return true;

[Severity: Medium]
Is group->name populated when init_group() runs?

In drm_dp_tunnel_mgr_create(), mgr->groups is zero-allocated via
kzalloc_objs() immediately before init_group() is called on each element:

drivers/gpu/drm/display/drm_dp_tunnel.c:drm_dp_tunnel_mgr_create() {
	mgr->groups = kzalloc_objs(*mgr->groups, max_group_count);
	...
	for (i = 0; i < max_group_count; i++) {
		if (!init_group(mgr, &mgr->groups[i])) {
}

Because drm_atomic_private_obj_init() duplicates the passed string with
kstrdup(), group->base.name is initialized to "" while group->name is only
formatted later when a tunnel is added in lookup_or_alloc_group():

drivers/gpu/drm/display/drm_dp_tunnel.c:lookup_or_alloc_group() {
	...
	snprintf(group->name, sizeof(group->name), "%d:%d:*",
		 tunnel_group_drv_id(drv_group_id) & ((1 << DP_GROUP_ID_BITS) - 1),
		 tunnel_group_id(drv_group_id) & ((1 << DP_USB4_DRIVER_ID_BITS) - 1));
}

Won't this leave group->base.name permanently set to an empty string for
every DP tunnel group?

> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> index e7f9e6d589272..ac65ac9416919 100644
> --- a/drivers/gpu/drm/drm_bridge.c
> +++ b/drivers/gpu/drm/drm_bridge.c
> @@ -317,6 +317,7 @@ static void __drm_bridge_free(struct kref *kref)
>  
>  	drm_bridge_put(bridge->next_bridge);
>  
> +	kfree(bridge->name);
>  	kfree(bridge->container);
>  }
>  
> @@ -418,6 +419,13 @@ void *__devm_drm_bridge_alloc(struct device *dev, size_t size, size_t offset,
>  		return ERR_PTR(-ENOMEM);
>  
>  	bridge = container + offset;
> +
> +	bridge->name = kasprintf(GFP_KERNEL, "bridge-%s", dev_name(dev));
> +	if (!bridge->name) {
> +		kfree(container);
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
>  	INIT_LIST_HEAD(&bridge->list);
>  	bridge->container = container;
>  	bridge->funcs = funcs;
> @@ -649,7 +657,7 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
>  			goto err_reset_bridge;
>  	}
>  
> -	drm_atomic_private_obj_init(bridge->dev, &bridge->base,
> +	drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
>  				    &drm_bridge_priv_state_funcs);
>  
>  	return 0;

[Severity: High]
What happens in drm_bridge_attach() if a bridge was not allocated via
devm_drm_bridge_alloc() and therefore has bridge->name == NULL?

For example, ingenic_drm_bind() allocates struct ingenic_drm_bridge via
drmm_encoder_alloc() and attaches &ib->bridge:

drivers/gpu/drm/ingenic/ingenic-drm-drv.c:ingenic_drm_bind() {
		ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
					NULL, DRM_MODE_ENCODER_DPI, NULL);
		...
		ib->bridge.funcs = &ingenic_drm_bridge_funcs;
		ib->next_bridge = bridge;

		ret = drm_bridge_attach(encoder, &ib->bridge, NULL,
					DRM_BRIDGE_ATTACH_NO_CONNECTOR);
}

When bridge->name is NULL, kstrdup(name, GFP_KERNEL) in
drm_atomic_private_obj_init() returns NULL and aborts with -ENOMEM before
adding bridge->base to dev->mode_config.privobj_list or calling
atomic_create_state() to initialize bridge->base.state:

drivers/gpu/drm/drm_atomic.c:drm_atomic_private_obj_init() {
	obj->name = kstrdup(name, GFP_KERNEL);
	if (!obj->name) {
		ret = -ENOMEM;
		goto err_lock_fini;
	}
}

Because drm_bridge_attach() ignores the return value of
drm_atomic_private_obj_init(), attach succeeds with bridge->base.state ==
NULL, causing subsequent atomic commits via
drm_atomic_add_encoder_bridges() -> drm_atomic_get_bridge_state() ->
drm_atomic_helper_bridge_duplicate_state() to trigger
WARN_ON(!bridge->base.state) and fail with -ENOMEM. Can
drm_atomic_private_obj_init() or drm_bridge_attach() handle a NULL bridge
name gracefully?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=5

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 04/34] drm/atomic: Fix private obj error handling
  2026-10-29  7:33 ` [PATCH v7 04/34] drm/atomic: Fix private obj error handling Maxime Ripard
@ 2026-10-29  7:57   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  7:57 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] drm/atomic: non-idempotent list_del() in drm_atomic_private_obj_fini()
--

commit 72c7eb0713fd45624f9e5b2b360640bc1c381470
Author: Maxime Ripard <mripard@kernel.org>

drm/atomic: Fix private obj error handling

This commit fixes drm_atomic_private_obj_init() to remove the object from
privobj_list and clean up the modeset lock if atomic_create_state() fails. It
also adds a list_empty() check to drm_atomic_private_obj_fini() to avoid a
double list_del() when teardown runs after a failed initialization.

> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 9bb761eda59f4..8f9b17395b905 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c

[ ... ]

> @@ -1056,6 +1072,15 @@ EXPORT_SYMBOL(drm_atomic_private_obj_init);
>  void
>  drm_atomic_private_obj_fini(struct drm_private_obj *obj)
>  {
> +	/*
> +	 * Some callers ignore drm_atomic_private_obj_init() failures
> +	 * and unconditionally register a teardown action that calls
> +	 * this function. Make it idempotent so a failed init followed
> +	 * by fini does not cause a double list_del.
> +	 */
> +	if (list_empty(&obj->head))
> +		return;
> +
>  	list_del(&obj->head);

[Severity: Medium]
Should drm_atomic_private_obj_fini() in drivers/gpu/drm/drm_atomic.c use
list_del_init(&obj->head) instead of list_del(&obj->head) so that calling
drm_atomic_private_obj_fini() more than once is truly idempotent?

Because list_del(&obj->head) poisons obj->head.next with LIST_POISON1, a
second call to drm_atomic_private_obj_fini() after a successful
drm_atomic_private_obj_init() will see list_empty(&obj->head) evaluate to
false. It will then execute list_del(&obj->head) on the poisoned list node and
call obj->funcs->atomic_destroy_state(obj, obj->state) a second time on
already-freed state (as well as kfree(obj->name) in the subsequent commit
adding drm_private_obj names).

In drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c, this happens when
dpu_kms_hw_init() succeeds in dpu_kms_global_obj_init() ->
drm_atomic_private_obj_init() and then fails at a later initialization step:

dpu_kms_hw_init()
  error:
    _dpu_kms_hw_destroy(dpu_kms)
      dpu_kms_global_obj_fini(dpu_kms)
        drm_atomic_private_obj_fini(&dpu_kms->global_state)

When the error propagates back to msm_drm_init() in
drivers/gpu/drm/msm/msm_drv.c, teardown calls _dpu_kms_hw_destroy() a second
time:

msm_drm_init()
  msm_drm_uninit()
    msm_drm_kms_uninit()
      dpu_kms_destroy()
        _dpu_kms_hw_destroy(dpu_kms)
          dpu_kms_global_obj_fini(dpu_kms)
            drm_atomic_private_obj_fini(&dpu_kms->global_state)

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=4

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 15/34] drm/bridge: Handle bridges with hardware state readout
  2026-10-29  7:34 ` [PATCH v7 15/34] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
@ 2026-10-29  7:59   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  7:59 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] drm/bridge: spurious WARN_ON in find_connector_state_for_encoder()
--

commit f1fba7a56f1912874e4674dececcad53e42b5232
Author: Maxime Ripard <mripard@kernel.org>

drm/bridge: Handle bridges with hardware state readout

This commit adds hardware state readout support for DRM bridges by introducing
drm_bridge_atomic_readout_priv_state() to look up the associated connector and
CRTC states and forward them to the bridge's atomic_sro_readout_state hook. It
also introduces a separate drm_private_state_funcs vtable so
drm_atomic_sro_device_can_readout() can distinguish bridges with and without
SRO support.

> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> index b2d77824e4bed..5977157d8e679 100644
> --- a/drivers/gpu/drm/drm_bridge.c
> +++ b/drivers/gpu/drm/drm_bridge.c
[ ... ]
> @@ -561,6 +562,73 @@ drm_bridge_atomic_create_priv_state(struct drm_private_obj *obj)
>  	return &state->base;
>  }
>  
> +static struct drm_connector_state *
> +find_connector_state_for_encoder(struct drm_atomic_sro_state *state,
> +				struct drm_encoder *encoder)
> +{
> +	struct drm_connector_list_iter conn_iter;
> +	struct drm_connector *connector;
> +
> +	drm_connector_list_iter_begin(drm_atomic_sro_state_get_device(state),
> +				      &conn_iter);
> +	drm_for_each_connector_iter(connector, &conn_iter) {
> +		struct drm_connector_state *conn_state =
> +			drm_atomic_sro_get_connector_state(state, connector);
> +
> +		if (WARN_ON(!conn_state))
> +			continue;

[Severity: Medium]
Can this WARN_ON(!conn_state) trigger spuriously when state readout only
populates a subset of connectors?

When drm_atomic_sro_readout_and_compare() builds an SRO state for a blocking
commit via drm_atomic_helper_sro_build_state() with a non-NULL commit, only
the connectors included in that commit have their states populated in
state->connectors[]:

include/drm/drm_modeset_helper_vtables.h:
	 * When @commit is not NULL, only the objects that have a state in
	 * @commit are read out. The caller must hold the locks of the
	 * objects in @commit through its acquire context, and the readout
	 * hooks must not access the state of any object outside of the
	 * commit, since its locks aren't held.

Similarly, connectors without readout support are skipped when atomic_readout
is set to DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT.

Because find_connector_state_for_encoder() iterates over all connectors on the
drm_device via drm_for_each_connector_iter(), any connector on a
multi-connector device that was not part of the commit (or when a commit
disables the encoder's connector routing) will have a NULL conn_state and
trigger WARN_ON(!conn_state). By contrast, drm_bridge_atomic_readout_priv_state()
checks if (!crtc_state) without warning.

Additionally, iterating the live device connector list passes every connector
to drm_atomic_sro_get_connector_state():

drivers/gpu/drm/drm_atomic_state_readout.c:drm_atomic_sro_get_connector_state() {
	unsigned int index = drm_connector_index(connector);

	return state->connectors[index].state;
}

Could this also read past state->connectors[] if a dynamically registered
connector has an index greater than state->max_connector_index, or should
find_connector_state_for_encoder() iterate only over the connectors populated
in struct drm_atomic_sro_state (for example via for_each_connector_in_sro_state())
without WARN_ON(!conn_state)?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=15

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 17/34] drm/atomic_sro: Provide helpers to implement hardware state readout
  2026-10-29  7:34 ` [PATCH v7 17/34] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
@ 2026-10-29  8:02   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  8:02 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Medium] drm/atomic_sro: unexported drm_atomic_helper_colorop_create_state() call
- [Medium] drm/atomic_sro: missing EXPORT_SYMBOL() on per-object readout helpers
- [Medium] drm/atomic_sro: colorop state leak in drm_atomic_helper_sro_readout_state()
- [Medium] drm/atomic_sro: missing fallback state when object readout hook is absent
- [Medium] drm/atomic_sro: bridge readout WARN_ON() on partial commit connector state
--

commit dadf54d14092f977607e2034fd03f065196831b7
Author: Maxime Ripard <mripard@kernel.org>

drm/atomic_sro: Provide helpers to implement hardware state readout

This commit introduces helper functions in drm_kms_helper to iterate over KMS
objects, allocate pristine states, and invoke their hardware state readout
callbacks, along with top-level helpers to build and install the initial
readout state.

> diff --git a/drivers/gpu/drm/drm_atomic_state_readout_helper.c b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
> new file mode 100644
> index 0000000000000..a0b0337df2a00
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
> @@ -0,0 +1,681 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_state_readout.h>
> +#include <drm/drm_atomic_state_readout_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_colorop.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_print.h>
> +
> +#include <linux/gfp.h>
> +#include <linux/sched/mm.h>
> +
> +static int
> +drm_atomic_helper_sro_readout_plane_state(struct drm_device *dev,
> +					  struct drm_atomic_sro_state *state,
> +					  struct drm_plane *plane)
> +{
> +	const struct drm_plane_funcs *plane_funcs = plane->funcs;
> +	struct drm_plane_state *plane_state;
> +	int ret;
> +
> +	if (!plane_funcs->atomic_sro_readout_state) {
> +		drm_warn(dev, "No plane readout implementation.");
> +		return 0;
> +	}

[Severity: Medium]
When atomic_readout is set to DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT,
drm_atomic_sro_device_can_readout() allows readout to proceed even if some
objects do not implement atomic_sro_readout_state.

If drm_atomic_helper_sro_readout_plane_state() (and the corresponding CRTC,
connector, bridge, and private object helpers below) returns 0 here without
allocating a default state via atomic_create_state() and storing it in state,
drm_atomic_helper_sro_readout_state() will succeed and cause
drm_mode_config_create_initial_state() to skip drm_mode_config_create_state():

drivers/gpu/drm/drm_mode_config.c:drm_mode_config_create_initial_state() {
	if (dev->mode_config.funcs->atomic_sro_readout_state) {
		ret = dev->mode_config.funcs->atomic_sro_readout_state(dev);
		if (!ret)
			goto out;
	}

	ret = drm_mode_config_create_state(dev);
}

Won't that leave any object lacking atomic_sro_readout_state with a NULL
state pointer after initialization (and also trigger WARN_ON(!conn_state) in
find_connector_state_for_encoder() during bridge readout if a connector was
skipped)?

> +
> +	drm_dbg_kms(dev, "Initializing Plane %s state.\n", plane->name);
> +
> +	plane_state = plane_funcs->atomic_create_state(plane);
> +	if (drm_WARN_ON(dev, IS_ERR(plane_state)))
> +		return PTR_ERR(plane_state);
> +
> +	ret = plane_funcs->atomic_sro_readout_state(plane, state,
> +						    plane_state);
> +	if (drm_WARN_ON(dev, ret)) {
> +		plane_funcs->atomic_destroy_state(plane, plane_state);
> +		return ret;
> +	}
> +
> +	drm_atomic_sro_set_plane_state(state, plane, plane_state);
> +
> +	return 0;
> +}

[ ... ]

> +int
> +drm_atomic_helper_sro_readout_planes_state(struct drm_device *dev,
> +					   struct drm_atomic_sro_state *state,
> +					   struct drm_atomic_commit *commit)
> +{
> +	struct drm_plane *plane;
> +	int ret;
> +
> +	might_alloc(GFP_KERNEL);
> +
> +	if (commit)
> +		return drm_atomic_helper_sro_readout_commit_planes_state(dev, state, commit);
> +
> +	drm_for_each_plane(plane, dev) {
> +		ret = drm_atomic_helper_sro_readout_plane_state(dev, state, plane);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}

[Severity: Medium]
Should drm_atomic_helper_sro_readout_planes_state() and the other four
per-object helpers (drm_atomic_helper_sro_readout_crtcs_state(),
drm_atomic_helper_sro_readout_connectors_state(),
drm_atomic_helper_sro_readout_bridges_state(), and
drm_atomic_helper_sro_readout_private_objs_state()) have EXPORT_SYMBOL()?

They are declared in include/drm/drm_atomic_state_readout_helper.h and pulled
into Documentation/gpu/drm-kms-helpers.rst via :export:, but without
EXPORT_SYMBOL() modular drivers implementing a custom atomic_sro_build_state
callback will fail to link at modpost.

[ ... ]

> +static int
> +drm_atomic_helper_sro_readout_commit_connectors_state(struct drm_device *dev,
> +						      struct drm_atomic_sro_state *state,
> +						      struct drm_atomic_commit *commit)
> +{
> +	struct drm_connector_state *new_connector_state;
> +	struct drm_connector *connector;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!commit)
> +		return -EINVAL;
> +
> +	for_each_new_connector_in_state(commit, connector, new_connector_state, i) {
> +		ret = drm_atomic_helper_sro_readout_connector_state(dev, state, connector);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}

[Severity: Medium]
When drm_atomic_helper_sro_build_state() is called with a non-NULL commit that
only includes a subset of the device's connectors,
drm_atomic_helper_sro_readout_commit_connectors_state() only populates
connector states in state for the connectors in that commit.

Later in drm_atomic_helper_sro_build_state(),
drm_atomic_helper_sro_readout_bridges_state() reads out the bridges for the
committed connectors' encoders. For bridges using
drm_bridge_atomic_readout_priv_state(), this calls
find_connector_state_for_encoder() in drivers/gpu/drm/drm_bridge.c:

	drm_for_each_connector_iter(connector, &conn_iter) {
		struct drm_connector_state *conn_state =
			drm_atomic_sro_get_connector_state(state, connector);

		if (WARN_ON(!conn_state))
			continue;

Doesn't this trigger WARN_ON(!conn_state) for every connector on the device
that was not part of the commit?

[ ... ]

> +int drm_atomic_helper_sro_readout_state(struct drm_device *dev)
> +{
> +	const struct drm_mode_config_helper_funcs *funcs =
> +		dev->mode_config.helper_private;
> +	struct drm_atomic_sro_state *state;
> +	struct drm_colorop *colorop;
> +
> +	if (!drm_atomic_sro_device_can_readout(dev)) {
> +		drm_info(dev, "Device doesn't support hardware state readout.");
> +		return -ENOTSUPP;
> +	}
> +
> +	state = funcs->atomic_sro_build_state(dev, NULL);
> +	if (IS_ERR(state))
> +		return PTR_ERR(state);
> +
> +	/* TODO: Add colorop state readout support */
> +	drm_for_each_colorop(colorop, dev) {
> +		struct drm_colorop_state *colorop_state;
> +
> +		colorop_state = drm_atomic_helper_colorop_create_state(colorop);

[Severity: Medium]
Does this build when CONFIG_DRM_KMS_HELPER=m?

drm_atomic_state_readout_helper.o is linked into drm_kms_helper.ko in
drivers/gpu/drm/Makefile, while drm_atomic_helper_colorop_create_state() is
defined in drivers/gpu/drm/drm_colorop.c (part of drm.ko) without
EXPORT_SYMBOL(), which causes a modpost undefined symbol error.

> +		if (IS_ERR(colorop_state)) {
> +			drm_atomic_sro_state_free(state);
> +			return PTR_ERR(colorop_state);
> +		}
> +
> +		colorop->state = colorop_state;
> +	}

[Severity: Medium]
Can this loop leak colorop->state?

The drm_plane_colorop_*_init() helpers in drivers/gpu/drm/drm_colorop.c call
drm_colorop_reset(), which allocates and populates colorop->state during
initialization. In drivers/gpu/drm/drm_mode_config.c,
drm_mode_config_create_state() skips colorops that already have a state:

	drm_for_each_colorop(colorop, dev) {
		struct drm_colorop_state *colorop_state;

		if (colorop->state)
			continue;

		colorop_state = drm_atomic_helper_colorop_create_state(colorop);

Assigning colorop->state unconditionally here without checking or destroying
an existing colorop->state (and without cleaning up earlier iterations if a
later drm_atomic_helper_colorop_create_state() call fails) appears to leak the
previously allocated struct drm_colorop_state.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=17

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 25/34] drm/tests: Add tests for the atomic state readout
  2026-10-29  7:34 ` [PATCH v7 25/34] drm/tests: Add tests for the atomic state readout Maxime Ripard
@ 2026-10-29  8:03   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  8:03 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] drm/tests: state leaks in drm_atomic_sro_compare KUnit tests
- [Low] drm/tests: call to unexported drm_atomic_sro_state_get_device()
--

commit f4486adadf6731a0cd596ded17489fc32e1af61a
Author: Maxime Ripard <mripard@kernel.org>

drm/tests: Add tests for the atomic state readout

This commit adds a KUnit test suite for the DRM atomic state readout
infrastructure, covering the drm_atomic_sro_state container API, state
readout helpers, state comparison helpers, and initial state creation.

> diff --git a/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c b/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c
> new file mode 100644
> index 0000000000000..ada512f7b32bb
> --- /dev/null
> +++ b/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c

[ ... ]

> +static void drm_test_sro_state_alloc_free(struct kunit *test)
> +{
> +	struct sro_test_priv *priv = mock_device_with_sro_create(test);
> +	struct drm_atomic_sro_state *sro_state;
> +
> +	sro_state = drm_atomic_sro_state_alloc(&priv->drm);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state);
> +
> +	KUNIT_EXPECT_PTR_EQ(test, drm_atomic_sro_state_get_device(sro_state),
> +			    &priv->drm);

[Severity: Low]
Will building with CONFIG_DRM_KUNIT_TEST=m fail at modpost here?

In drivers/gpu/drm/drm_atomic_state_readout.c,
drm_atomic_sro_state_get_device() is defined without EXPORT_SYMBOL(),
unlike the other drm_atomic_sro_* functions:

drivers/gpu/drm/drm_atomic_state_readout.c:
struct drm_device *
drm_atomic_sro_state_get_device(struct drm_atomic_sro_state *state)
{
	return state->dev;
}

When drm_atomic_state_readout_test is built as a module under
CONFIG_DRM_KUNIT_TEST=m, calling drm_atomic_sro_state_get_device() from
drm_test_sro_state_alloc_free() results in an undefined symbol error.

> +
> +	drm_atomic_sro_state_free(sro_state);
> +}

[ ... ]

> +static void drm_test_sro_compare_plane_identical(struct kunit *test)
> +{
> +	struct sro_test_priv *priv = mock_device_with_sro_create(test);
> +	struct drm_plane_state *state1, *state2;
> +	struct drm_printer *p;
> +	bool identical;
> +
> +	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
> +
> +	state1 = priv->plane->funcs->atomic_create_state(priv->plane);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
> +
> +	state2 = priv->plane->funcs->atomic_create_state(priv->plane);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
> +
> +	identical = drm_atomic_helper_plane_compare_state(priv->plane, p,
> +							  state1, state2);
> +	KUNIT_EXPECT_TRUE(test, identical);
> +}
> +
> +/*
> + * Test that plane states differing by a single field compare as
> + * different, and that the mismatching field is reported through the
> + * printer.
> + */
> +static void drm_test_sro_compare_plane_mismatch(struct kunit *test)
> +{
> +	struct sro_test_priv *priv = mock_device_with_sro_create(test);
> +	struct drm_plane_state *state1, *state2;
> +	struct drm_printer *p;
> +	bool identical;
> +
> +	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
> +
> +	state1 = priv->plane->funcs->atomic_create_state(priv->plane);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
> +
> +	state2 = priv->plane->funcs->atomic_create_state(priv->plane);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
> +
> +	state2->crtc = priv->crtc;
> +
> +	identical = drm_atomic_helper_plane_compare_state(priv->plane, p,
> +							  state1, state2);
> +	KUNIT_EXPECT_FALSE(test, identical);
> +}
> +
> +/*
> + * Test that two identical active CRTC states compare as equal.
> + */
> +static void drm_test_sro_compare_crtc_identical(struct kunit *test)
> +{
> +	struct sro_test_priv *priv = mock_device_with_sro_create(test);
> +	struct drm_crtc_state *state1, *state2;
> +	struct drm_printer *p;
> +	bool identical;
> +
> +	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
> +
> +	state1 = priv->crtc->funcs->atomic_create_state(priv->crtc);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
> +
> +	state2 = priv->crtc->funcs->atomic_create_state(priv->crtc);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
> +
> +	state1->enable = true;
> +	state1->active = true;
> +	state2->enable = true;
> +	state2->active = true;
> +
> +	identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p,
> +							 state1, state2);
> +	KUNIT_EXPECT_TRUE(test, identical);
> +}
> +
> +/*
> + * Test that two inactive CRTCs compare as equal even though their
> + * modes differ: the modes are meaningless while inactive and must not
> + * be compared.
> + */
> +static void drm_test_sro_compare_crtc_inactive(struct kunit *test)
> +{
> +	struct sro_test_priv *priv = mock_device_with_sro_create(test);
> +	struct drm_display_mode *mode1, *mode2;
> +	struct drm_crtc_state *state1, *state2;
> +	struct drm_printer *p;
> +	bool identical;
> +
> +	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
> +
> +	state1 = priv->crtc->funcs->atomic_create_state(priv->crtc);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
> +
> +	state2 = priv->crtc->funcs->atomic_create_state(priv->crtc);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
> +
> +	/* Two different but valid modes */
> +	mode1 = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode1);
> +
> +	mode2 = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 4);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode2);
> +
> +	/*
> +	 * Both CRTCs are inactive: the modes are meaningless and must
> +	 * not be compared.
> +	 */
> +	drm_mode_copy(&state1->mode, mode1);
> +	drm_mode_copy(&state2->mode, mode2);
> +
> +	identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p,
> +							 state1, state2);
> +	KUNIT_EXPECT_TRUE(test, identical);
> +}
> +
> +/*
> + * Test that an active CRTC and an inactive one compare as different.
> + */
> +static void drm_test_sro_compare_crtc_active_mismatch(struct kunit *test)
> +{
> +	struct sro_test_priv *priv = mock_device_with_sro_create(test);
> +	struct drm_crtc_state *state1, *state2;
> +	struct drm_printer *p;
> +	bool identical;
> +
> +	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
> +
> +	state1 = priv->crtc->funcs->atomic_create_state(priv->crtc);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
> +
> +	state2 = priv->crtc->funcs->atomic_create_state(priv->crtc);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
> +
> +	state1->enable = true;
> +	state1->active = true;
> +
> +	identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p,
> +							 state1, state2);
> +	KUNIT_EXPECT_FALSE(test, identical);
> +}
> +
> +/*
> + * Test that two identical connector states compare as equal.
> + */
> +static void drm_test_sro_compare_connector_identical(struct kunit *test)
> +{
> +	struct drm_connector *connector;
> +	struct sro_test_priv *priv;
> +	struct drm_connector_state *state1, *state2;
> +	struct drm_printer *p;
> +	bool identical;
> +
> +	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
> +
> +	priv = mock_device_with_sro_create(test);
> +	connector = &priv->connector;
> +
> +	state1 = connector->funcs->atomic_create_state(connector);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
> +
> +	state2 = connector->funcs->atomic_create_state(connector);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
> +
> +	identical =
> +		drm_atomic_helper_connector_compare_state(connector, p,
> +							  state1, state2);
> +	KUNIT_EXPECT_TRUE(test, identical);
> +}
> +
> +/*
> + * Test that connector states whose CRTCs differ compare as different.
> + */
> +static void drm_test_sro_compare_connector_mismatch(struct kunit *test)
> +{
> +	struct drm_connector *connector;
> +	struct sro_test_priv *priv;
> +	struct drm_connector_state *state1, *state2;
> +	struct drm_printer *p;
> +	bool identical;
> +
> +	p = drm_kunit_helper_printer(test, LOGLEVEL_ERR);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
> +
> +	priv = mock_device_with_sro_create(test);
> +	connector = &priv->connector;
> +
> +	state1 = connector->funcs->atomic_create_state(connector);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1);
> +
> +	state2 = connector->funcs->atomic_create_state(connector);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2);
> +
> +	state2->crtc = priv->crtc;
> +
> +	identical =
> +		drm_atomic_helper_connector_compare_state(connector, p,
> +							  state1, state2);
> +	KUNIT_EXPECT_FALSE(test, identical);
> +}

[Severity: Medium]
Do these seven plane, CRTC, and connector comparison tests leak state1 and
state2?

In drm_test_sro_compare_plane_identical(),
drm_test_sro_compare_plane_mismatch(),
drm_test_sro_compare_crtc_identical(),
drm_test_sro_compare_crtc_inactive(),
drm_test_sro_compare_crtc_active_mismatch(),
drm_test_sro_compare_connector_identical(), and
drm_test_sro_compare_connector_mismatch(), state1 and state2 are allocated
via ->atomic_create_state() (using unmanaged kzalloc_obj()) and are not
installed on the objects or in a drm_atomic_sro_state container. Unlike
drm_test_sro_compare_bridge_identical() and
drm_test_sro_compare_bridge_mismatch() below, these tests return without
calling ->atomic_destroy_state() on state1 and state2.

Also, in drm_test_sro_compare_connector_mismatch(), state2->crtc is set
to priv->crtc without taking a connector reference via
drm_connector_get(connector). When destroying state2 via
connector->funcs->atomic_destroy_state(connector, state2),
__drm_atomic_helper_connector_destroy_state() will call
drm_connector_put(state->connector) because state2->crtc is non-NULL, so
either drm_connector_get(connector) should be called when setting
state2->crtc or state2->crtc should be cleared before destroying state2.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=25

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 28/34] drm/bridge_connector: Implement hw readout for connector
  2026-10-29  7:34 ` [PATCH v7 28/34] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
@ 2026-10-29  8:04   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  8:04 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] drm/bridge_connector: unconditional SRO hooks with no-op readout when unsupported
--

commit b75d5f60d55bc26fbc8505149b2ac1fa7543a9a1
Author: Maxime Ripard <mripard@kernel.org>

drm/bridge_connector: Implement hw readout for connector

This commit adds hardware state readout support to drm_bridge_connector by
introducing DRM_BRIDGE_OP_CONNECTOR_HW_READOUT and the
atomic_sro_connector_readout callback on drm_bridge_funcs.

> diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
> index 81f3e26f6fdf5..45806ef077081 100644
> --- a/drivers/gpu/drm/display/drm_bridge_connector.c
> +++ b/drivers/gpu/drm/display/drm_bridge_connector.c

[ ... ]

> @@ -244,6 +253,26 @@ drm_bridge_connector_create_state(struct drm_connector *connector)
>  	return conn_state;
>  }
>  
> +static int
> +drm_bridge_connector_readout_state(struct drm_connector *connector,
> +				   struct drm_atomic_sro_state *state,
> +				   struct drm_connector_state *conn_state)
> +{
> +	struct drm_bridge_connector *bridge_connector =
> +		to_drm_bridge_connector(connector);
> +	struct drm_bridge *readout =
> +		bridge_connector->bridge_connector_hw_readout;
> +	int ret;
> +
> +	if (readout) {
> +		ret = readout->funcs->atomic_sro_connector_readout(readout, state, conn_state);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
>  static enum drm_connector_color_format
>  drm_bridge_connector_color_format(const struct drm_connector_state *conn_state)
>  {
> @@ -270,6 +299,8 @@ drm_bridge_connector_color_format(const struct drm_connector_state *conn_state)
>  static const struct drm_connector_funcs drm_bridge_connector_funcs = {
>  	.fill_modes = drm_helper_probe_single_connector_modes,
>  	.atomic_create_state = drm_bridge_connector_create_state,
> +	.atomic_sro_readout_state = drm_bridge_connector_readout_state,
> +	.atomic_sro_compare_state = drm_atomic_helper_connector_compare_state,

[Severity: High]
Does unconditionally setting .atomic_sro_readout_state and
.atomic_sro_compare_state on drm_bridge_connector_funcs while returning 0 in
drm_bridge_connector_readout_state() when bridge_connector_hw_readout is NULL
cause a regression for bridge chains without DRM_BRIDGE_OP_CONNECTOR_HW_READOUT?

In drivers/gpu/drm/drm_atomic_state_readout.c, drm_atomic_sro_can_readout()
and drm_atomic_sro_can_compare() determine whether a connector supports state
readout by checking whether its callbacks are populated:

drivers/gpu/drm/drm_atomic_state_readout.c:drm_atomic_sro_can_readout() {
	...
	drm_connector_list_iter_begin(dev, &conn_iter);
	drm_for_each_connector_iter(connector, &conn_iter) {
		if (!connector->funcs->atomic_sro_readout_state) {
			drm_dbg_atomic(dev, "Connector %s missing readout callback",
				       connector->name);
			drm_connector_list_iter_end(&conn_iter);
			return false;
		}
	}
	drm_connector_list_iter_end(&conn_iter);
	...
}

Because drm_bridge_connector_funcs always defines both callbacks, every
drm_bridge_connector is treated as supporting hardware state readout even when
no bridge in the chain sets DRM_BRIDGE_OP_CONNECTOR_HW_READOUT (leaving
bridge_connector->bridge_connector_hw_readout NULL).

When state readout runs on a pipeline where bridge_connector_hw_readout is
NULL (for example, when bridges implement private state readout without any
bridge setting DRM_BRIDGE_OP_CONNECTOR_HW_READOUT, on a multi-connector device
where only one bridge chain supports connector readout, or when
drm.atomic_readout is set to DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT),
drm_bridge_connector_readout_state() silently returns 0 and leaves
conn_state->crtc and conn_state->best_encoder set to NULL.

During initial readout in drm_mode_config_create_initial_state() ->
drm_atomic_helper_sro_readout_state() -> drm_atomic_helper_sro_build_state(),
this causes drm_bridge_atomic_readout_priv_state() in
drivers/gpu/drm/drm_bridge.c to skip reading out all bridges on that encoder's
chain because find_connector_state_for_encoder() matches encoder against
conn_state->best_encoder:

drivers/gpu/drm/drm_bridge.c:drm_bridge_atomic_readout_priv_state() {
	...
	conn_state = find_connector_state_for_encoder(state, encoder);
	if (!conn_state)
		return 0;

	if (!conn_state->crtc)
		return 0;
	...
}

It also installs an inconsistent atomic state via
drm_atomic_sro_install_state() with an active CRTC (crtc_state->active == true
and crtc_state->connector_mask == 0) and a disconnected connector state
instead of failing readout and falling back to hw_reset(). Furthermore, during
post-commit verification in commit_tail() ->
drm_atomic_sro_readout_and_compare(), every blocking commit enabling that
connector compares the committed active state against the unpopulated readout
state via drm_atomic_helper_connector_compare_state(),
drm_atomic_helper_crtc_compare_state(), and
drm_atomic_helper_bridge_compare_state(), triggering state mismatch errors.

Should drm_bridge_connector_init() only expose a drm_connector_funcs table
with .atomic_sro_readout_state and .atomic_sro_compare_state when
bridge_connector->bridge_connector_hw_readout is non-NULL (similar to how
drm_bridge_attach() selects between drm_bridge_priv_state_funcs and
drm_bridge_priv_state_funcs_with_sro)?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=28

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 20/34] drm/atomic_helper: Compare actual and readout states once the commit is done
  2026-10-29  7:34 ` [PATCH v7 20/34] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
@ 2026-10-29  8:05   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  8:05 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] drm/atomic_sro: partial commit readout triggers WARN_ON(!conn_state) in bridge readout
--

commit baa08bb6dbe7a515f8de48108d95e19b64d5be4d
Author: Maxime Ripard <mripard@kernel.org>

drm/atomic_helper: Compare actual and readout states once the commit is done

This commit adds the atomic_sro_compare_state hook across KMS objects and
introduces drm_atomic_sro_readout_and_compare(), which is called from
commit_tail() after blocking commits to read out hardware state and compare
it against the committed state.

> diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
> index 2c17fa09c5f08..f29d127f99f81 100644
> --- a/drivers/gpu/drm/drm_atomic_state_readout.c
> +++ b/drivers/gpu/drm/drm_atomic_state_readout.c

[ ... ]

> +bool drm_atomic_sro_readout_and_compare(struct drm_atomic_commit *commit)
> +{
> +	struct drm_device *dev = commit->dev;
> +	const struct drm_mode_config_helper_funcs *funcs =
> +		dev->mode_config.helper_private;
> +	struct drm_printer p = drm_err_printer(dev, NULL);
> +	struct drm_private_state *new_obj_state;
> +	struct drm_private_obj *obj;
> +	struct drm_plane_state *new_plane_state;
> +	struct drm_plane *plane;
> +	struct drm_crtc_state *new_crtc_state;
> +	struct drm_crtc *crtc;
> +	struct drm_connector_state *new_conn_state;
> +	struct drm_connector *conn;
> +	struct drm_atomic_sro_state *readout_state;
> +	unsigned int i;
> +	bool identical = true;
> +
> +	might_alloc(GFP_KERNEL);
> +
> +	if (!drm_atomic_sro_device_can_readout(dev))
> +		return true;
> +
> +	readout_state = funcs->atomic_sro_build_state(dev, commit);
> +	if (WARN_ON(IS_ERR(readout_state)))
> +		return false;

[Severity: Medium]
When commit_tail() calls drm_atomic_sro_readout_and_compare() for a blocking
commit that only includes a subset of connectors on a multi-connector device
with bridges, won't funcs->atomic_sro_build_state(dev, commit) trigger a
WARN_ON(!conn_state) in find_connector_state_for_encoder()?

When commit is non-NULL, drm_atomic_helper_sro_readout_connectors_state()
calls drm_atomic_helper_sro_readout_commit_connectors_state() in
drivers/gpu/drm/drm_atomic_state_readout_helper.c, which only populates
readout_state for connectors present in commit:

	for_each_new_connector_in_state(commit, connector, new_connector_state, i) {
		ret = drm_atomic_helper_sro_readout_connector_state(dev, state, connector);
		if (ret)
			return ret;
	}

Any other connector on dev has NULL state in readout_state.

Then drm_atomic_helper_sro_readout_bridges_state() calls
drm_atomic_helper_sro_readout_commit_bridges_state(), which walks the bridge
chains of old_conn_state->best_encoder and new_conn_state->best_encoder and
invokes drm_bridge_atomic_readout_priv_state() ->
find_connector_state_for_encoder() in drivers/gpu/drm/drm_bridge.c:

	drm_for_each_connector_iter(connector, &conn_iter) {
		struct drm_connector_state *conn_state =
			drm_atomic_sro_get_connector_state(state, connector);

		if (WARN_ON(!conn_state))
			continue;

		if (encoder == conn_state->best_encoder) {
			drm_connector_list_iter_end(&conn_iter);
			return conn_state;
		}
	}

Because find_connector_state_for_encoder() iterates over all connectors on
the device, visiting any connector not included in commit (either an
uncommitted connector preceding the active connector in the connector list,
or any uncommitted connector when disabling a connector so old_encoder is no
longer routed to any connector) hits WARN_ON(!conn_state).

Also, when a commit disables a connector, drm_atomic_helper_check_modeset()
adds old_conn_state->best_encoder's bridges to commit by duplicating their
previous active bridge state, whereas drm_bridge_atomic_readout_priv_state()
leaves the readout bridge state as a pristine default state when
find_connector_state_for_encoder(state, old_encoder) returns NULL. Won't
that cause drm_atomic_sro_readout_and_compare() to report a false mismatch
on the bridge private object state when a connector is disabled?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=20

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 22/34] drm/atomic-state-helper: Provide atomic_compare_state helpers
  2026-10-29  7:34 ` [PATCH v7 22/34] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
@ 2026-10-29  8:07   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  8:07 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] drm/atomic: drm_atomic_helper_connector_compare_state() checks inactive/request state
- [Medium] drm/atomic: unconditional infoframe data check in connector state compare
- [Medium] drm/atomic-state-helper: plane state compare checks transient damage fields
- [Medium] drm/atomic-state-helper: false enable mismatch on DPMS OFF in drm_atomic_helper_crtc_compare_state()
--

commit 0c2a50c5386965dc9fab1a71259f23b6b087098f
Author: Maxime Ripard <mripard@kernel.org>

drm/atomic-state-helper: Provide atomic_compare_state helpers

This commit adds default atomic_sro_compare_state helper functions for
planes, CRTCs, connectors, and bridges to compare committed software states
against states read back from hardware.

> diff --git a/drivers/gpu/drm/drm_atomic_state_readout_helper.c b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
> index 33f0f78c9b265..c4089afbc946e 100644
> --- a/drivers/gpu/drm/drm_atomic_state_readout_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c

[ ... ]

> @@ -680,6 +681,268 @@ int drm_atomic_helper_sro_readout_state(struct drm_device *dev)
> +bool drm_atomic_helper_plane_compare_state(struct drm_plane *plane,
> +					   struct drm_printer *p,
> +					   struct drm_plane_state *expected,
> +					   struct drm_plane_state *actual)
> +{

[ ... ]

> +	STATE_CHECK_U32(ret, p, plane->name, expected, actual, color_encoding);
> +	STATE_CHECK_U32(ret, p, plane->name, expected, actual, color_range);
> +
> +	STATE_CHECK_PROPERTY_BLOB(ret, p, plane->name, expected, actual, fb_damage_clips);
> +	STATE_CHECK_BOOL(ret, p, plane->name, expected, actual, ignore_damage_clips);

[Severity: Medium]
Should fb_damage_clips and ignore_damage_clips be excluded from
drm_atomic_helper_plane_compare_state()?

In struct drm_plane_state, fb_damage_clips is a transient per-commit blob
describing damaged rectangles relative to the previous frame, which
__drm_atomic_helper_plane_duplicate_state() explicitly resets on duplication:

drivers/gpu/drm/drm_atomic_state_helper.c:__drm_atomic_helper_plane_duplicate_state() {
	...
	state->fence = NULL;
	state->commit = NULL;
	state->fb_damage_clips = NULL;
	state->color_mgmt_changed = false;
	...
}

Similarly, ignore_damage_clips is an internal software flag used by
drm_atomic_helper_damage_iter_init(). Neither field is stored in display
hardware registers, so the readout state allocated via atomic_create_state()
and populated by atomic_sro_readout_state() in
drm_atomic_sro_readout_and_compare() will always have actual->fb_damage_clips
set to NULL and actual->ignore_damage_clips set to false.

When a blocking atomic commit includes FB_DAMAGE_CLIPS (or comes through
drm_atomic_helper_dirtyfb()) or a driver sets ignore_damage_clips, won't
commit_tail() -> drm_atomic_sro_readout_and_compare() ->
drm_atomic_helper_plane_compare_state() report false state mismatches on
these two fields?

> +	STATE_CHECK_PTR(ret, p, plane->name, expected, actual, color_pipeline);

[ ... ]

> +bool drm_atomic_helper_crtc_compare_state(struct drm_crtc *crtc,
> +					  struct drm_printer *p,
> +					  struct drm_crtc_state *expected,
> +					  struct drm_crtc_state *actual)
> +{
> +	bool ret = true;
> +
> +	STATE_CHECK_PTR(ret, p, crtc->name, expected, actual, crtc);
> +	STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, enable);
> +	STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, active);
> +
> +	/* Both inactive, mode and properties are meaningless. */
> +	if (!expected->active && !actual->active)
> +		return ret;

[Severity: Medium]
Won't checking enable before the !expected->active && !actual->active early
return trigger a false mismatch when a CRTC is in DPMS off?

When userspace blanks a display via DPMS off
(drm_atomic_connector_commit_dpms() or an atomic commit setting ACTIVE=0
while keeping MODE_ID set), the committed state has expected->enable = true
and expected->active = false, while drm_atomic_helper_commit_crtc_disable()
powers down the CRTC hardware.

When commit_tail() -> drm_atomic_sro_readout_and_compare() reads back the
hardware state (for example via tidss_crtc_readout_state()), the hardware
CRTC is disabled so readout leaves both actual->enable = false and
actual->active = false.

Because STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, enable) runs
before the inactive check returns ret, ret is set to false and a mismatch is
logged even though both states are inactive. Should enable only be compared
after verifying that the CRTC is active?

> +
> +	/* One active and the other inactive, that's a mismatch. */
> +	if (!expected->active || !actual->active)
> +		return false;

[ ... ]

> +bool drm_atomic_helper_connector_compare_state(struct drm_connector *conn,
> +					       struct drm_printer *p,
> +					       struct drm_connector_state *expected,
> +					       struct drm_connector_state *actual)
> +{
> +	bool ret = true;
> +
> +	STATE_CHECK_PTR(ret, p, conn->name, expected, actual, connector);
> +	STATE_CHECK_PTR(ret, p, conn->name, expected, actual, crtc);
> +	STATE_CHECK_PTR(ret, p, conn->name, expected, actual, best_encoder);
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, link_status);
> +
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, tv.select_subconnector);
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, tv.subconnector);
> +
> +	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, self_refresh_aware);
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, picture_aspect_ratio);
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, content_type);
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdcp_content_type);
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, scaling_mode);
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, content_protection);
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, colorspace);
> +
> +	/*
> +	 * NOTE: We can't check max_bpc, max_requested_bpc and
> +	 * color_format because they are userspace requests negotiated
> +	 * during atomic_check into output_bpc and output_format. The
> +	 * hardware only stores the negotiated result.
> +	 */
> +
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, privacy_screen_sw_state);
> +	STATE_CHECK_PROPERTY_BLOB(ret, p, conn->name, expected, actual, hdr_output_metadata);
> +
> +	STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdmi.broadcast_rgb);

[Severity: Medium]
There appear to be two issues with how connector and HDMI state fields are
checked in drm_atomic_helper_connector_compare_state():

First, unlike drm_atomic_helper_plane_compare_state() (which returns early
when !expected->fb && !actual->fb) and drm_atomic_helper_crtc_compare_state()
(which returns early when !expected->active && !actual->active),
drm_atomic_helper_connector_compare_state() continues comparing all connector
and hdmi.* fields even when the connector is disabled (!expected->crtc &&
!actual->crtc). When a commit disables a previously active connector,
__drm_atomic_helper_connector_duplicate_state() copies the old state via
memcpy(), and drm_atomic_helper_connector_hdmi_check() returns early without
clearing the old hdmi.* fields:

drivers/gpu/drm/display/drm_hdmi_state_helper.c:drm_atomic_helper_connector_hdmi_check() {
	...
	if (!new_conn_state->crtc || !new_conn_state->best_encoder)
		return 0;

	ret = hdmi_compute_config(connector, new_conn_state, mode);
	if (ret)
		return ret;

	new_conn_state->hdmi.is_limited_range = hdmi_is_limited_range(connector, new_conn_state);
	...
}

Meanwhile, hardware readout creates a pristine default state via
atomic_create_state() and leaves it untouched when the connector is
disconnected from a CRTC (for example in sii902x_bridge_connector_readout()),
causing false mismatches on retained fields like hdmi.output_bpc,
hdmi.output_format, hdmi.tmds_char_rate, and hdmi.infoframes.*.

Second, even when the connector is active, hdmi.broadcast_rgb is a userspace
request (like max_bpc and color_format mentioned in the comment above) that
drm_atomic_helper_connector_hdmi_check() resolves into hdmi.is_limited_range
and the AVI infoframe quantization range. Since hardware only stores the
negotiated quantization range and atomic_create_state() initializes
actual->hdmi.broadcast_rgb to DRM_HDMI_BROADCAST_RGB_AUTO, won't setting
Broadcast RGB to Full or Limited always fail comparison?

> +	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.avi.set);
> +	STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.avi.data);
> +	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.hdr_drm.set);
> +	STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.hdr_drm.data);
> +	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.spd.set);
> +	STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.spd.data);
> +	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.hdmi.set);
> +	STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.hdmi.data);

[Severity: Medium]
Should STATE_CHECK_INFOFRAME() only be called when the corresponding
infoframe's set field is true in both expected and actual?

When a connector state is duplicated via
__drm_atomic_helper_connector_duplicate_state(), the previous
hdmi.infoframes.*.data bytes are copied with memcpy(). If an infoframe that
was previously enabled (such as hdr_drm when hdr_output_metadata is removed,
or the HDMI vendor infoframe) is not generated in a subsequent commit,
helpers like hdmi_generate_hdr_infoframe() and
hdmi_generate_hdmi_vendor_infoframe() only clear infoframe->set without
zeroing infoframe->data:

drivers/gpu/drm/display/drm_hdmi_state_helper.c:hdmi_generate_hdr_infoframe() {
	...
	infoframe->set = false;

	if (!connector->hdmi.funcs->hdr_drm.write_infoframe)
		return 0;

	if (connector->max_bpc < 10)
		return 0;

	if (!conn_state->hdr_output_metadata)
		return 0;
	...
}

In drm_atomic_sro_readout_and_compare(), the readout state is freshly
zero-allocated via atomic_create_state(), so actual->hdmi.infoframes.*.data
is all zeros when set is false, whereas expected->hdmi.infoframes.*.data
still holds stale bytes from the earlier commit. Comparing data with memcmp()
when set is false will report a mismatch even though the infoframe is
disabled in both states.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=22

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 31/34] drm/tidss: Implement hw_reset
  2026-10-29  7:34 ` [PATCH v7 31/34] drm/tidss: Implement hw_reset Maxime Ripard
@ 2026-10-29  8:09   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  8:09 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/tidss: moving dispc_reset_hw() to tidss_hw_reset() breaks hardware reset
- [High] drm/tidss: dispc_reset_hw() never called on probe and fails when PM enabled
--

commit b4ac51170032bf695c274ab86a54a2de58078be4
Author: Maxime Ripard <mripard@kernel.org>

drm/tidss: Implement hw_reset

This commit renames dispc_init_hw() to dispc_reset_hw(), removes its
unconditional call from dispc_init(), and wires it up to
drm_mode_config_funcs.hw_reset via tidss_hw_reset().

> diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
> index 5943a5f254321..fd33edd809770 100644
> --- a/drivers/gpu/drm/tidss/tidss_dispc.c
> +++ b/drivers/gpu/drm/tidss/tidss_dispc.c
> @@ -2909,7 +2909,7 @@ static int dispc_softreset(struct dispc_device *dispc)
>  	return 0;
>  }
>  
> -static int dispc_init_hw(struct dispc_device *dispc)
> +int dispc_reset_hw(struct dispc_device *dispc)
>  {
>  	struct device *dev = dispc->dev;
>  	int ret;
> @@ -3059,10 +3059,6 @@ int dispc_init(struct tidss_device *tidss)
>  	of_property_read_u32(dispc->dev->of_node, "max-memory-bandwidth",
>  			     &dispc->memory_bandwidth_limit);
>  
> -	r = dispc_init_hw(dispc);
> -	if (r)
> -		return r;
> -
>  	tidss->dispc = dispc;
>  
>  	return 0;

[Severity: High]
Does removing dispc_init_hw() from dispc_init() leave the DISPC hardware
unreset during probe and before tidss_irq_install() registers the interrupt
handler?

In drivers/gpu/drm/tidss/tidss_drv.c, tidss_probe() calls
drm_mode_config_reset(ddev) rather than
drm_mode_config_create_initial_state(ddev):

drivers/gpu/drm/tidss/tidss_drv.c:tidss_probe() {
	...
	ret = tidss_irq_install(ddev, irq);
	if (ret) {
		dev_err(dev, "tidss_irq_install failed: %d\n", ret);
		goto err_runtime_suspend;
	}

	drm_kms_helper_poll_init(ddev);

	drm_mode_config_reset(ddev);
	...
}

Because drm_mode_config_reset() in drivers/gpu/drm/drm_mode_config.c only
invokes per-CRTC helper_funcs->hw_reset and does not call
dev->mode_config.funcs->hw_reset(dev) (which is only called by
drm_mode_config_create_initial_state()), tidss_hw_reset() and
dispc_reset_hw() are never called during probe. This also leaves any active
bootloader display or interrupt state unmasked when tidss_irq_install()
requests the IRQ line.

[ ... ]

> diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
> index 70c14c3be10d5..3b97c08c4f714 100644
> --- a/drivers/gpu/drm/tidss/tidss_kms.c
> +++ b/drivers/gpu/drm/tidss/tidss_kms.c
> @@ -116,10 +117,23 @@ static int tidss_atomic_check(struct drm_device *ddev,
>  	return 0;
>  }
>  
> +static void tidss_hw_reset(struct drm_device *dev)
> +{
> +	struct tidss_device *tidss = to_tidss(dev);
> +	int ret;
> +
> +	ret = dispc_reset_hw(tidss->dispc);
> +	if (ret)
> +		drm_warn(dev, "Couldn't reset dispc");
> +
> +	drm_mode_config_helper_hw_reset(dev);
> +}
> +
>  static const struct drm_mode_config_funcs mode_config_funcs = {
>  	.fb_create = drm_gem_fb_create,
>  	.atomic_check = tidss_atomic_check,
>  	.atomic_commit = drm_atomic_helper_commit,
> +	.hw_reset = tidss_hw_reset,
>  };

[Severity: High]
When tidss_hw_reset() is called during mode_config initialization (such as via
drm_mode_config_create_initial_state()), won't dispc_reset_hw() fail with
-EAGAIN when CONFIG_PM is enabled, or wipe out programmed register state when
CONFIG_PM is disabled?

In drivers/gpu/drm/tidss/tidss_drv.c, tidss_probe() enables runtime PM (or
manually calls dispc_runtime_resume() when !CONFIG_PM) before modeset
initialization and reset:

drivers/gpu/drm/tidss/tidss_drv.c:tidss_probe() {
	...
	pm_runtime_enable(dev);

	pm_runtime_set_autosuspend_delay(dev, 1000);
	pm_runtime_use_autosuspend(dev);

#ifndef CONFIG_PM
	/* If we don't have PM, we need to call resume manually */
	dispc_runtime_resume(tidss->dispc);
#endif

	ret = tidss_modeset_init(tidss);
	...
}

However, dispc_reset_hw() uses pm_runtime_set_active() and
pm_runtime_set_suspended(), which only work while runtime PM is disabled:

drivers/gpu/drm/tidss/tidss_dispc.c:dispc_reset_hw() {
	struct device *dev = dispc->dev;
	int ret;

	ret = pm_runtime_set_active(dev);
	if (ret) {
		dev_err(dev, "Failed to set DSS PM to active\n");
		return ret;
	}
	...
}

Once pm_runtime_enable(dev) has been called (so dev->power.disable_depth == 0
and dev->power.runtime_error == 0), __pm_runtime_set_status() in
drivers/base/power/runtime.c rejects pm_runtime_set_active(dev) with -EAGAIN:

drivers/base/power/runtime.c:__pm_runtime_set_status() {
	...
	if (dev->power.runtime_error || dev->power.disable_depth)
		dev->power.disable_depth++;
	else
		error = -EAGAIN;
	...
}

As a result, under CONFIG_PM=y, dispc_reset_hw() aborts immediately before
enabling dispc->fclk or calling dispc_softreset(). Conversely, under
CONFIG_PM=n, dispc_runtime_resume() and tidss_irq_install() have already
configured the initial registers and interrupt masks before tidss_hw_reset()
runs, so executing dispc_softreset() at that point resets DSS_SYSCONFIG and
wipes out the programmed register and IRQ state.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=31

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 34/34] drm/bridge: sii902x: Implement hw state readout
  2026-10-29  7:34 ` [PATCH v7 34/34] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
@ 2026-10-29  8:12   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  8:12 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/bridge: sii902x: missing mutex lock in readout callbacks
- [Medium] drm/bridge: sii902x: ignored regmap_test_bits() error in readout_state()
--

commit ce74a42cab42e6bb481c531e7732368f37a0e27c
Author: Maxime Ripard <mripard@kernel.org>

drm/bridge: sii902x: Implement hw state readout

This commit implements hardware state readout callbacks for the sii902x
bridge driver and enables DRM_BRIDGE_OP_CONNECTOR_HW_READOUT.

> diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
> index e067c0636e7f5..ab5ffcf6882f2 100644
> --- a/drivers/gpu/drm/bridge/sii902x.c
> +++ b/drivers/gpu/drm/bridge/sii902x.c
[ ... ]
> @@ -537,6 +539,89 @@ sii902x_bridge_mode_valid(struct drm_bridge *bridge,
>  	return MODE_OK;
>  }
>  
> +static int
> +sii902x_bridge_connector_readout(struct drm_bridge *bridge,
> +				 struct drm_atomic_sro_state *state,
> +				 struct drm_connector_state *conn_state)
> +{
> +	struct sii902x *sii902x = bridge_to_sii902x(bridge);
> +	struct drm_connector *connector = conn_state->connector;
> +	struct drm_crtc_state *crtc_state;
> +	struct drm_encoder *encoder;
> +	struct drm_crtc *crtc;
> +	int ret;
> +
> +	ret = regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN);

[Severity: High]
Should sii902x_bridge_connector_readout() and sii902x_bridge_readout_state()
hold sii902x->mutex around their regmap_test_bits() calls?

In drivers/gpu/drm/bridge/sii902x.c, sii902x_regmap_config disables regmap's
internal locking:

static const struct regmap_config sii902x_regmap_config = {
	.reg_bits = 8,
	.val_bits = 8,
	.disable_locking = true, /* struct sii902x mutex should be enough */
	...
};

All other post-init regmap callers in this driver (such as
sii902x_interrupt(), sii902x_detect(), sii902x_edid_read(),
sii902x_bridge_mode_set(), and the audio callbacks) serialize access via
sii902x->mutex.

Without holding sii902x->mutex here, an unlocked regmap_test_bits() during
initial state readout or post-commit drm_atomic_sro_readout_and_compare()
can race with concurrent HPD interrupt handling, DDC operations, or ALSA
audio callbacks and clobber regmap's shared map->work_buf or interleave I2C
sequences.

> +	/* I2C error */
> +	if (ret < 0)
> +		return ret;
> +	/* Device is powered down, nothing to read out */
> +	if (ret)
> +		return 0;
[ ... ]
> +static int sii902x_bridge_readout_state(struct drm_bridge *bridge,
> +					struct drm_atomic_sro_state *state,
> +					struct drm_bridge_state *bridge_state,
> +					struct drm_crtc_state *crtc_state,
> +					struct drm_connector_state *conn_state)
> +{
> +	struct sii902x *sii902x = bridge_to_sii902x(bridge);
> +	u32 input_format;
> +
> +	if (regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN))
> +		return 0;

[Severity: Medium]
What happens in sii902x_bridge_readout_state() if regmap_test_bits() fails
with a negative error code?

Because a negative errno is non-zero, the if condition evaluates to true and
sii902x_bridge_readout_state() returns 0 as though the device were powered
down, leaving bridge_state->input_bus_cfg and bridge_state->output_bus_cfg
unpopulated.

Should this check for ret < 0 and propagate the error like
sii902x_bridge_connector_readout() does above?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=34

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v7 32/34] drm/tidss: Implement readout support
  2026-10-29  7:34 ` [PATCH v7 32/34] drm/tidss: Implement readout support Maxime Ripard
@ 2026-10-29  8:22   ` sashiko-bot
  0 siblings, 0 replies; 50+ messages in thread
From: sashiko-bot @ 2026-10-29  8:22 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] drm/tidss: tidss_crtc_install_state() enables inactive CRTCs and leaks PM
- [High] drm/tidss: missing drm_mode_config_create_initial_state() in tidss_probe()
- [High] drm/tidss: GEM-less readout fb fails prepare_fb() and zeroes DMA address
- [Medium] drm/tidss: tidss_crtc_compare_state() checks bus config on inactive CRTCs
- [Medium] drm/tidss: plane readout incomplete state and -EINVAL trigger WARN_ON on commit
- [Medium] drm/tidss: incomplete crtc_state->plane_mask readout on partial commits
--

commit 8f81eb167182a1d35a4ac3bf95240f371e285879
Author: Maxime Ripard <mripard@kernel.org>

drm/tidss: Implement readout support

This commit implements hardware state readout and state comparison callbacks
for CRTCs, bridges, and planes in the tidss driver using the DRM atomic state
readout helpers.

> diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
> index 3da49f77fa080..4409f8c9114bb 100644
> --- a/drivers/gpu/drm/tidss/tidss_crtc.c
> +++ b/drivers/gpu/drm/tidss/tidss_crtc.c
[ ... ]
> @@ -370,6 +374,90 @@ static struct drm_crtc_state *tidss_crtc_create_state(struct drm_crtc *crtc)
>  	return &tstate->base;
>  }
>  
> +static int tidss_crtc_readout_state(struct drm_crtc *crtc,
> +				    struct drm_atomic_sro_state *state,
> +				    struct drm_crtc_state *crtc_state)
> +{
> +	struct drm_device *ddev = crtc->dev;
> +	struct tidss_device *tidss = to_tidss(ddev);
> +	struct dispc_device *dispc = tidss->dispc;
> +	struct tidss_crtc_state *tstate = to_tidss_crtc_state(crtc_state);
> +	struct tidss_crtc *tcrtc =
> +		to_tidss_crtc(crtc);
> +	struct drm_display_mode mode = {};
> +	int ret;
> +
> +	tidss_runtime_get(tidss);
> +
> +	if (!dispc_vp_is_enabled(dispc, tcrtc->hw_videoport))
> +		goto out;
> +
> +	tstate->base.active = 1;
> +	tstate->base.enable = 1;
> +
> +	ret = dispc_vp_readout_mode(dispc, tcrtc->hw_videoport, &mode);
> +	if (ret)
> +		goto err_runtime_put;
> +
> +	ret = drm_atomic_set_mode_for_crtc(&tstate->base, &mode);
> +	if (WARN_ON(ret))
> +		goto err_runtime_put;
> +
> +	drm_mode_copy(&tstate->base.adjusted_mode, &mode);
> +
> +	tstate->bus_flags = dispc_vp_get_bus_flags(dispc, tcrtc->hw_videoport);
> +	ret = dispc_vp_get_bus_format(dispc, tcrtc->hw_videoport,
> +				      &tstate->bus_format);
> +	if (ret)
> +		goto err_runtime_put;
> +
> +	/*
> +	 * The active connectors and planes will be filled by their
> +	 * respective readout callbacks.
> +	 */

[Severity: Medium]
What happens to crtc_state->plane_mask (as well as connector_mask and
encoder_mask) during post-commit verification in
drm_atomic_sro_readout_and_compare() when a blocking commit includes the CRTC
but only a subset of its active planes?

When drm_atomic_sro_readout_and_compare(commit) calls
drm_atomic_helper_sro_build_state(dev, commit) with a non-NULL commit,
drm_atomic_helper_sro_readout_planes_state() only invokes
atomic_sro_readout_state() for planes present in that commit.

If two planes are active on a CRTC and userspace performs a blocking page
flip or property update on only one of them (without changing x/y or zpos, so
tidss_atomic_check() does not call drm_atomic_add_affected_planes()),
tidss_plane_atomic_check() adds the CRTC to the commit via
drm_atomic_get_crtc_state(), but the second active plane is not in the commit.

Because tidss_crtc_readout_state() leaves crtc_state->plane_mask at 0 and
relies on tidss_plane_atomic_readout_state() to OR in drm_plane_mask(plane),
won't the untouched plane's bit be missing from the readout CRTC state's
plane_mask, causing drm_atomic_helper_crtc_compare_state() to report a
plane_mask mismatch?

> +
> +out:
> +	tidss_runtime_put(tidss);
> +	return 0;
> +
> +err_runtime_put:
> +	tidss_runtime_put(tidss);
> +	return ret;
> +}
> +
> +static void tidss_crtc_install_state(struct drm_crtc *crtc,
> +				     struct drm_crtc_state *crtc_state)
> +{
> +	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
> +	struct drm_device *ddev = crtc->dev;
> +	struct tidss_device *tidss = to_tidss(ddev);
> +	int ret;
> +
> +	tidss_runtime_get(tidss);
> +
> +	ret = dispc_vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
> +	if (ret != 0)
> +		return;
> +
> +	/* Turn vertical blanking interrupt reporting on. */
> +	drm_crtc_vblank_on(crtc);
> +}

[Severity: High]
Should tidss_crtc_install_state() check whether crtc_state->active is set
before enabling runtime PM, the videoport clock, and vblank reporting?

When a videoport is disabled in hardware (!dispc_vp_is_enabled()),
tidss_crtc_readout_state() jumps to out and returns 0 with
crtc_state->active == false, and drm_atomic_helper_sro_readout_crtc_state()
stores that inactive state in state->crtcs[i].state.

Later, drm_atomic_sro_install_state() iterates over all CRTCs and invokes
atomic_sro_install_state() for every non-NULL crtc_state:

drivers/gpu/drm/drm_atomic_state_readout.c:drm_atomic_sro_install_state() {
    ...
		crtc_state = state->crtcs[i].state;
		if (!crtc_state)
			continue;

		if (crtc_funcs->atomic_sro_install_state)
			crtc_funcs->atomic_sro_install_state(crtc, crtc_state);
    ...
}

For an inactive CRTC, won't tidss_crtc_install_state() unconditionally call
tidss_runtime_get(), dispc_vp_enable_clk(), and drm_crtc_vblank_on(), leaking
both the runtime PM reference and the videoport clock enable count because
crtc->state->active is false (so tidss_crtc_atomic_disable() will not run to
release them)?

Also, if dispc_vp_enable_clk() returns a non-zero error in
tidss_crtc_install_state(), doesn't the early return leak the runtime PM
reference acquired by tidss_runtime_get(tidss)?

> +
> +static bool tidss_crtc_compare_state(struct drm_crtc *crtc,
> +				     struct drm_printer *p,
> +				     struct drm_crtc_state *expected,
> +				     struct drm_crtc_state *actual)
> +{
> +	struct tidss_crtc_state *t_expected = to_tidss_crtc_state(expected);
> +	struct tidss_crtc_state *t_actual = to_tidss_crtc_state(actual);
> +	bool ret = drm_atomic_helper_crtc_compare_state(crtc, p, expected, actual);
> +
> +	STATE_CHECK_U32_X(ret, p, crtc->name, t_expected, t_actual, bus_format);
> +	STATE_CHECK_U32_X(ret, p, crtc->name, t_expected, t_actual, bus_flags);
> +
> +	return ret;
> +}

[Severity: Medium]
Can tidss_crtc_compare_state() report false mismatches for bus_format and
bus_flags when an active CRTC is disabled?

When a CRTC is disabled in a blocking commit, tidss_crtc_duplicate_state()
copies the previous active state's non-zero bus_format and bus_flags into the
new state:

drivers/gpu/drm/tidss/tidss_crtc.c:tidss_crtc_duplicate_state() {
    ...
	state->bus_format = current_state->bus_format;
	state->bus_flags = current_state->bus_flags;
    ...
}

During post-commit readout in tidss_crtc_readout_state(),
!dispc_vp_is_enabled() is true and jumps to out before reading bus_flags or
bus_format, leaving them 0 in t_actual.

While drm_atomic_helper_crtc_compare_state() returns early when both
expected->active and actual->active are false:

drivers/gpu/drm/drm_atomic_state_readout_helper.c:drm_atomic_helper_crtc_compare_state() {
    ...
	/* Both inactive, mode and properties are meaningless. */
	if (!expected->active && !actual->active)
		return ret;
    ...
}

tidss_crtc_compare_state() still checks bus_format and bus_flags
unconditionally. Should those checks be skipped when the CRTC is inactive?

> diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
> index 3b97c08c4f714..b0b69b8531111 100644
> --- a/drivers/gpu/drm/tidss/tidss_kms.c
> +++ b/drivers/gpu/drm/tidss/tidss_kms.c
[ ... ]
> @@ -133,6 +136,7 @@ static const struct drm_mode_config_funcs mode_config_funcs = {
>  	.fb_create = drm_gem_fb_create,
>  	.atomic_check = tidss_atomic_check,
>  	.atomic_commit = drm_atomic_helper_commit,
> +	.atomic_sro_readout_state = drm_atomic_helper_sro_readout_state,
>  	.hw_reset = tidss_hw_reset,
>  };

[Severity: High]
How are mode_config_funcs.atomic_sro_readout_state (and tidss_crtc_install_state)
and the fallback mode_config_funcs.hw_reset reached during probe?

In drivers/gpu/drm/drm_mode_config.c, atomic_sro_readout_state and hw_reset
are only invoked by drm_mode_config_create_initial_state():

drivers/gpu/drm/drm_mode_config.c:drm_mode_config_create_initial_state() {
    ...
	if (dev->mode_config.funcs->atomic_sro_readout_state) {
		ret = dev->mode_config.funcs->atomic_sro_readout_state(dev);
		if (!ret)
			goto out;
	}

	/* Reset the hardware first ... */
	if (dev->mode_config.funcs->hw_reset)
		dev->mode_config.funcs->hw_reset(dev);
    ...
}

However, tidss_probe() in drivers/gpu/drm/tidss/tidss_drv.c still calls
drm_mode_config_reset(ddev):

drivers/gpu/drm/tidss/tidss_drv.c:tidss_probe() {
    ...
	drm_kms_helper_poll_init(ddev);

	drm_mode_config_reset(ddev);

	ret = drm_dev_register(ddev, 0);
    ...
}

Because drm_mode_config_reset() only allocates blank software states without
calling atomic_sro_readout_state or hw_reset, isn't initial state readout
(along with the hardware reset moved into tidss_hw_reset()) never executed at
probe time?

Furthermore, if tidss_probe() is updated to call
drm_mode_config_create_initial_state(ddev) after pm_runtime_enable(dev), won't
dispc_reset_hw() fail when readout falls back to hw_reset because
pm_runtime_set_active(dev) returns -EAGAIN when runtime PM is already enabled?

> diff --git a/drivers/gpu/drm/tidss/tidss_plane.c b/drivers/gpu/drm/tidss/tidss_plane.c
> index b0d5d6959b885..ebdb63f8e8f17 100644
> --- a/drivers/gpu/drm/tidss/tidss_plane.c
> +++ b/drivers/gpu/drm/tidss/tidss_plane.c
[ ... ]
> @@ -176,11 +179,186 @@ static const struct drm_plane_helper_funcs tidss_primary_plane_helper_funcs = {
>  	.get_scanout_buffer = drm_fb_dma_get_scanout_buffer,
>  };
>  
> +static const struct drm_framebuffer_funcs tidss_plane_readout_fb_funcs = {
> +	.destroy	= drm_gem_fb_destroy,
> +};
> +
> +static struct drm_framebuffer *tidss_plane_readout_fb(struct drm_plane *plane)
> +{
> +	struct drm_device *ddev = plane->dev;
> +	struct tidss_device *tidss = to_tidss(ddev);
> +	struct dispc_device *dispc = tidss->dispc;
> +	struct tidss_plane *tplane = to_tidss_plane(plane);
> +	const struct drm_format_info *info;
> +	struct drm_framebuffer *fb;
> +	int ret;
> +
> +	fb = kzalloc_obj(*fb);
> +	if (!fb)
> +		return ERR_PTR(-ENOMEM);
> +
> +	fb->dev = plane->dev;
> +
> +	info = dispc_plane_get_current_format(dispc, tplane->hw_plane_id);
> +	if (IS_ERR(info)) {
> +		ret = PTR_ERR(info);
> +		goto err_free_fb;
> +	}
> +
> +	// TODO: Figure out YUV and multiplanar formats, along with
> +	// the color encoding and range
> +	if (info->is_yuv) {
> +		ret = -EINVAL;
> +		goto err_free_fb;
> +	}
> +
> +	fb->format = info;
> +
> +	dispc_plane_get_picture_size(dispc, tplane->hw_plane_id, &fb->width,
> +				     &fb->height);
> +
> +	/*
> +	 * A pixel increment of 1 means that the DMA engine fetches every
> +	 * pixel of the source buffer. Any other value means that
> +	 * horizontal decimation is enabled, and the buffer geometry
> +	 * cannot be reconstructed from the registers.
> +	 */
> +	if (dispc_plane_get_pixel_inc(dispc, tplane->hw_plane_id) != 1) {
> +		ret = -EINVAL;
> +		goto err_free_fb;
> +	}
> +
> +	/*
> +	 * The row increment register holds the extra bytes between the
> +	 * end of a row of the picture and the beginning of the next one,
> +	 * which includes any padding the firmware might have programmed.
> +	 */
> +	fb->pitches[0] = fb->width * (drm_format_info_bpp(info, 0) / 8) +
> +			 dispc_plane_get_row_inc(dispc, tplane->hw_plane_id) - 1;
> +
> +	/*
> +	 * TODO: The DMA base address has the offsets baked in, and there
> +	 * is no backing buffer to recover them from.
> +	 */
> +	fb->offsets[0] = 0;
> +
> +	ret = drm_framebuffer_init(plane->dev, fb, &tidss_plane_readout_fb_funcs);
> +	if (ret) {
> +		kfree(fb);
> +		return ERR_PTR(ret);
> +	}
> +
> +	return fb;
> +
> +err_free_fb:
> +	kfree(fb);
> +	return ERR_PTR(ret);
> +}

[Severity: High]
What happens on a subsequent atomic commit if a plane still references the
framebuffer created by tidss_plane_readout_fb() (for example, when updating
plane properties or when tidss_atomic_check() adds affected planes on the
CRTC)?

Because tidss_plane_readout_fb() allocates fb with fb->obj[0] == NULL and
neither tidss_plane_helper_funcs nor tidss_primary_plane_helper_funcs sets a
custom .prepare_fb callback, drm_atomic_helper_prepare_planes() defaults to
drm_gem_plane_helper_prepare_fb() on this DRIVER_GEM driver. Won't
drm_gem_fb_get_obj(state->fb, 0) trigger drm_WARN_ON_ONCE(dev, !fb->obj[plane])
and fail the commit with -EINVAL?

In addition, if dispc_plane_setup() is called while state->fb is a GEM-less
readout framebuffer:

drivers/gpu/drm/tidss/tidss_dispc.c:dispc_plane_setup() {
    ...
	dma_addr_t dma_addr = drm_fb_dma_get_gem_addr(state->fb, state, 0);
    ...
	dispc_vid_write(dispc, hw_plane, DISPC_VID_BA_0, dma_addr & 0xffffffff);
	dispc_vid_write(dispc, hw_plane, DISPC_VID_BA_EXT_0, (u64)dma_addr >> 32);
    ...
}

drm_fb_dma_get_gem_addr() returns 0 when fb->obj[0] is NULL, which would
overwrite DISPC_VID_BA_0 and DISPC_VID_BA_EXT_0 with 0x0.

> +
> +static struct drm_crtc *tidss_plane_readout_crtc(struct drm_plane *plane)
> +{
> +	struct drm_device *dev = plane->dev;
> +
> +	/* TODO: Add support for multiple CRTCs */
> +	if (dev->num_crtcs != 1)
> +		return ERR_PTR(-EINVAL);
> +
> +	return list_first_entry(&dev->mode_config.crtc_list, struct drm_crtc, head);
> +}
> +
> +static int tidss_plane_atomic_readout_state(struct drm_plane *plane,
> +					    struct drm_atomic_sro_state *state,
> +					    struct drm_plane_state *plane_state)
> +{
[ ... ]
> +	dispc_plane_get_size(dispc, tplane->hw_plane_id, &plane_state->crtc_w,
> +			     &plane_state->crtc_h);
> +
> +	// TODO: Handle crtc_x/crtc_x/src_x/src_y
> +	// crtc_x/crtc_y are handled by DISPC_OVR_ATTRIBUTES / OVR1_DSS_ATTRIBUTES
> +
> +	// TODO: Handle zpos, see DISPC_OVR_ATTRIBUTES / OVR1_DSS_ATTRIBUTES
> +
> +	plane_state->src.x1 = 0;
> +	plane_state->src.x2 = plane_state->src_w;
> +	plane_state->src.y1 = 0;
> +	plane_state->src.y2 = plane_state->src_h;
> +	plane_state->dst.x1 = 0;
> +	plane_state->dst.x2 = plane_state->crtc_w;
> +	plane_state->dst.y1 = 0;
> +	plane_state->dst.y2 = plane_state->crtc_h;

[Severity: Medium]
Won't the unimplemented cases in tidss_plane_readout_fb(),
tidss_plane_readout_crtc(), and tidss_plane_atomic_readout_state() trigger
kernel warnings and state mismatch errors during normal runtime commits?

When all bridges and connectors in the pipeline implement readout callbacks,
drm_atomic_sro_device_can_readout() returns true and every blocking atomic
commit runs drm_atomic_sro_readout_and_compare() in commit_tail().

If userspace commits a YUV plane (info->is_yuv), enables horizontal
decimation (dispc_plane_get_pixel_inc() != 1), or runs on a multi-CRTC SoC
(dev->num_crtcs != 1), tidss_plane_atomic_readout_state() returns -EINVAL,
which triggers drm_WARN_ON(dev, ret) in
drm_atomic_helper_sro_readout_plane_state() and WARN_ON(IS_ERR(readout_state))
in drm_atomic_sro_readout_and_compare().

Similarly, if a blocking commit positions a plane at non-zero crtc_x/crtc_y or
src_x/src_y, or uses a non-zero zpos/normalized_zpos, leaving those fields at
0 in tidss_plane_atomic_readout_state() will cause
drm_atomic_helper_plane_compare_state() to log state mismatch errors on every
such commit.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-drm-state-readout-v7-0-25e5163d75c3@kernel.org?part=32

^ permalink raw reply	[flat|nested] 50+ messages in thread

end of thread, other threads:[~2026-10-29  8:22 UTC | newest]

Thread overview: 50+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-29  7:33 [PATCH v7 00/34] drm: Implement state readout support Maxime Ripard
2026-10-29  7:33 ` [PATCH v7 01/34] drm/bridge-helper: Don't unlock the connection mutex after a backoff Maxime Ripard
2026-10-29  7:51   ` sashiko-bot
2026-10-29  7:33 ` [PATCH v7 02/34] drm/tests: hdmi-state-helper: Handle EDEADLK in the 420-only test Maxime Ripard
2026-10-29  7:33 ` [PATCH v7 03/34] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test Maxime Ripard
2026-10-29  7:33 ` [PATCH v7 04/34] drm/atomic: Fix private obj error handling Maxime Ripard
2026-10-29  7:57   ` sashiko-bot
2026-10-29  7:33 ` [PATCH v7 05/34] drm/atomic: Add drm_private_obj name Maxime Ripard
2026-10-29  7:56   ` sashiko-bot
2026-10-29  7:33 ` [PATCH v7 06/34] drm/bridge: Switch print_state to use bridge name Maxime Ripard
2026-10-29  7:33 ` [PATCH v7 07/34] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
2026-10-29  7:33 ` [PATCH v7 08/34] drm/fb-dma-helper: Handle framebuffers without a GEM object Maxime Ripard
2026-10-29  7:33 ` [PATCH v7 09/34] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 10/34] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
2026-10-29  7:50   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 11/34] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
2026-10-29  7:46   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 12/34] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 13/34] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 14/34] drm/atomic_sro: Create documentation Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 15/34] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
2026-10-29  7:59   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 16/34] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 17/34] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
2026-10-29  8:02   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 18/34] drm/mode-config: Introduce hw_reset hook Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 19/34] drm/modeset-helper: Add drm_mode_config_helper_hw_reset() Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 20/34] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
2026-10-29  8:05   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 21/34] drm/atomic-state-helper: Provide comparison macros Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 22/34] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
2026-10-29  8:07   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 23/34] drm/atomic-helper: Document State Read-Out as a state initialization approach Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 24/34] drm/tests: kunit: Add a drm_printer helper Maxime Ripard
2026-10-29  7:56   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 25/34] drm/tests: Add tests for the atomic state readout Maxime Ripard
2026-10-29  8:03   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 26/34] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 27/34] drm/bridge: display-connector: Implement readout support Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 28/34] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
2026-10-29  8:04   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 29/34] drm/tidss: crtc: Add atomic_print_state implementation Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 30/34] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 31/34] drm/tidss: Implement hw_reset Maxime Ripard
2026-10-29  8:09   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 32/34] drm/tidss: Implement readout support Maxime Ripard
2026-10-29  8:22   ` sashiko-bot
2026-10-29  7:34 ` [PATCH v7 33/34] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
2026-10-29  7:34 ` [PATCH v7 34/34] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
2026-10-29  8:12   ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.