All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v8 00/39] drm: Implement state readout support
@ 2026-11-06 13:14 Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc() Maxime Ripard
                   ` (38 more replies)
  0 siblings, 39 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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, Paul Cercueil,
	Luca Ceresoli, 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 v8:
- Convert ingenic to properly allocated bridge
- Improve locking
- Export more functions needed to compile helpers as modules
- Fix idempotency of drm_atomic_private_obj_fini
- Fix DP-MST private objects name
- Handle framebuffers without a GEM object in drm_gem_plane_helper_prepare_fb
- Improve SRO container accessors safety
- Add documentation for kernel parameter
- Drop some easy to trigger WARN calls
- Handle plane, connector and encoder masks better when doing a partial
  update (and readout)
- Only compare the states if they are both active, other comparisons
  improvements
- More kunit tests to test kernel parameter behaviour, DPMS
- Fix memomry leaks in kunit tests
- Fix bridge_connector of bridges with and whithout SRO support
- Improve runtime_pm handling for tidss hw_reset
- Tentative support for tidss OLDI and multiple plane and CRTCs
- Improve error handling for sii902x
- Implement readout state for panel bridge 
- Link to v7: https://lore.kernel.org/r/20261029-drm-state-readout-v7-0-25e5163d75c3@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 (39):
      drm/ingenic: Convert to devm_drm_bridge_alloc()
      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/colorop: Export drm_atomic_helper_colorop_create_state()
      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/gem-atomic-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
      drm/panel: Implement state readout support
      fixup! drm/tidss: Implement readout support

 Documentation/admin-guide/kernel-parameters.txt    |   12 +
 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                   |  120 +-
 drivers/gpu/drm/display/drm_bridge_connector.c     |   65 +-
 drivers/gpu/drm/display/drm_dp_mst_topology.c      |    2 +-
 drivers/gpu/drm/display/drm_dp_tunnel.c            |   21 +-
 drivers/gpu/drm/drm_atomic.c                       |   92 +-
 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         | 1058 ++++++++++++
 drivers/gpu/drm/drm_atomic_state_readout_helper.c  | 1125 +++++++++++++
 drivers/gpu/drm/drm_bridge.c                       |  140 +-
 drivers/gpu/drm/drm_bridge_helper.c                |   47 +-
 drivers/gpu/drm/drm_colorop.c                      |    1 +
 drivers/gpu/drm/drm_fb_dma_helper.c                |    8 +
 drivers/gpu/drm/drm_gem_atomic_helper.c            |   14 +-
 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/drm_panel.c                        |  123 ++
 drivers/gpu/drm/ingenic/ingenic-drm-drv.c          |   16 +-
 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  | 1739 ++++++++++++++++++++
 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                 |  119 ++
 drivers/gpu/drm/tidss/tidss_dispc.c                |  525 +++++-
 drivers/gpu/drm/tidss/tidss_dispc.h                |   22 +
 drivers/gpu/drm/tidss/tidss_drv.c                  |   22 +-
 drivers/gpu/drm/tidss/tidss_encoder.c              |   57 +
 drivers/gpu/drm/tidss/tidss_kms.c                  |   20 +-
 drivers/gpu/drm/tidss/tidss_oldi.c                 |  138 ++
 drivers/gpu/drm/tidss/tidss_plane.c                |  205 +++
 drivers/gpu/drm/vc4/vc4_kms.c                      |    3 +
 include/drm/drm_atomic.h                           |   81 +
 include/drm/drm_atomic_state_readout.h             |   66 +
 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 +
 55 files changed, 6836 insertions(+), 123 deletions(-)
---
base-commit: d2a8cee91bfa8616f713c7c8f539a81e8e3554ea
change-id: 20250730-drm-state-readout-108f089c1c30
prerequisite-message-id: 20261105-drm-no-more-config-reset-v1-0-1f58d1e33380@kernel.org
prerequisite-patch-id: 3fd105fbfdbb4eee1f8ae9682b359477d5c2d610
prerequisite-patch-id: ed1d1dd244e64b18c5682800b546ebb2d0f44ec8
prerequisite-patch-id: 6b00324107d28bfe6ccd346730c6370d8f8362c4
prerequisite-patch-id: bac8bfa53c3c77a832500f711322530e39d26c74
prerequisite-patch-id: 60002e0ea0141b4051f2062152244b545450ffdf
prerequisite-patch-id: a82ef0b22909efd3a2f1a779e27ad60b08554203
prerequisite-patch-id: 4d4db6fb993ce12a03f5f22e07b060defc28b7d8
prerequisite-patch-id: 7a716ac0b6df5a2d9423bfa09457e7c2ae11e35f
prerequisite-patch-id: 35c60176b5a45a76a30285767fba4f83c5059868
prerequisite-patch-id: 23ef4052a7a932ec4611b640d4323e0f93760a6d
prerequisite-patch-id: f6a08bc40145c9a61508a37fe632e007c58566b9
prerequisite-patch-id: 942ec51b8579c4a7d5652d390dbcaa6fe1ef35bb
prerequisite-patch-id: cac9b8740c50ed002bdb7df8471c4493a53948b0
prerequisite-patch-id: 083d50c81b3609bc6756a2b9c89b30c5918dbcd3
prerequisite-patch-id: 97dd764d28e34d6c1fd346602931c1688aa4fe8d
prerequisite-patch-id: 566c8a35a9734a8e8a254afcc0fbd9765c980334
prerequisite-patch-id: a2ae7568eef2833217f55eb9528e2f846c73f993
prerequisite-patch-id: 4bdc653ad663151585ed7d71ed9f70ac702301fa
prerequisite-patch-id: ee3f427f087dde1290f0c0a3e821379427552961
prerequisite-patch-id: d14cd6008b88c7c9b61ca5241c961c4747a6fdfe
prerequisite-patch-id: c642e4fcebdbfc0ab86bba68c957ad5bbe62ef58
prerequisite-patch-id: 71afb7db95568dfa686ab670039ba60faeb91914
prerequisite-patch-id: 6557c88b83cd8d2ded0e4d70378c9a868659fea0
prerequisite-patch-id: 4020d12428794416f75edb398d84a6a14905f588
prerequisite-patch-id: a4806b41921baa3b8a3039d5431e099138fe6fa6
prerequisite-patch-id: 7a742e65aa5d69a733376641d0eb45281239b866
prerequisite-patch-id: ba3e625c15aa8e34747292c95ea9158b0cb98b34
prerequisite-patch-id: ba92010fead3c007e2cbaeb15bce922eb304ccac
prerequisite-patch-id: 2fc97bb7478092919001b21d2455a73373a5ba73
prerequisite-patch-id: 97a28c8af17b043429e3b40325576f74518ff802
prerequisite-patch-id: 37ae6417556eea439aab01725cfd920c1107e630
prerequisite-patch-id: 3f8725771832d7cccd8c88a33b4fac362a2a1221
prerequisite-patch-id: 5d51033ca88aa46e8347396cdc17fd5473636c13
prerequisite-patch-id: 983be3e35801efe3052f0983d728a9703e7ded88
prerequisite-patch-id: 20f874f2f1b44d9a27824a2e3944477fc3c02af5
prerequisite-patch-id: fc7f1510621f228d42bf1666c06f34926f3e10f4
prerequisite-patch-id: b9e987299142a89ca3a7bfb5b0335a215c7cdd52
prerequisite-patch-id: 1d11dea3090f6b72322c3827bd260ed3d12e1201
prerequisite-patch-id: 4182aa31cbc4b985e3a628c3dc9a2fd039188f7e
prerequisite-patch-id: 7b7f07b1c57398f185a93d6b3212394753292be7
prerequisite-patch-id: e6bd5afbc9593e93203caa0ae9f08545511e2bf2
prerequisite-patch-id: c6d466588800f94684ebf9fb785ce085510c8f93
prerequisite-patch-id: 5f8a9d6fd20c05d708df6424102575251d32331f
prerequisite-patch-id: 18c2d823dc5b44514c0b2278986933a1a9834f02
prerequisite-patch-id: a8c4944302cbea92690957fd18162ca303757874
prerequisite-patch-id: 962e6cc26e53a3319302fd4c2aae2bc4b593f4be
prerequisite-patch-id: 366fc63bc744ea4a2af4f16befaa1d7e8c66bf84
prerequisite-patch-id: 33c4fd1e1ccd1eac9fde961a0f7ed8509e713c7e
prerequisite-patch-id: 496318ad0a87ec138780d4a50a2f8a49ca126ca6
prerequisite-patch-id: f85ec023f5ab9903987d867c330bde94b3c24c55
prerequisite-patch-id: 34fc06cb69e34ec4d67015187969e8d2c0c56660
prerequisite-patch-id: 3b789a4e47c47f855c44c125a75de3a79709d411
prerequisite-patch-id: b03787ca5a3d60d4ad5682313424a114c1862fb0
prerequisite-patch-id: 4ec079edf0025f849b1b8c7e3beb47db057ab6d2
prerequisite-patch-id: b44382d38528c92150f2d7cac5722cf8e09c0fab
prerequisite-patch-id: 02e42a505f17dbf5bc3e85b55d5df716e3819141
prerequisite-patch-id: faf2c686c253faa5a81801f78ba955d3ae8c0c4f
prerequisite-patch-id: 6407fec511e4d3bf785c3be581b828abac884ba7
prerequisite-patch-id: 1a9fc394fdf6b19434652a4c137a0ea819702325
prerequisite-patch-id: 6de415eec4ab2f2b8dec1f194fb89cfe01568f9b
prerequisite-patch-id: 73e1360c2be46882c0ca58d6d60f0c394f1d47f4
prerequisite-patch-id: fada6f5ef59f7119d5b202b64ce2184795428d7c
prerequisite-patch-id: 7f31438e1f326350688f7aa7169038a7a33b4ddc
prerequisite-patch-id: a9940a88c19f95573008a9e2bd950a43c12b7e3c
prerequisite-patch-id: 2d5e46c029b523f6d6f13a0716e56267691757cf
prerequisite-patch-id: 7aa60266913380f85e46f8dc71885356e3d7902a
prerequisite-patch-id: cd12813a72a4c558660f8f8d093928533ddbf5ee
prerequisite-patch-id: 186129fe8188f5b7cb6fef2701400fc6772aaf2e
prerequisite-patch-id: c99a84af1b60e1c8f80168706f5ed600a6eb4553
prerequisite-patch-id: 598515adbecc5c14e8ffe631206a906fb2c9f59c
prerequisite-patch-id: cf84de09ca65f4577268e9a4a8a166436735b7da
prerequisite-patch-id: cfd9ec749b0f3c78e0a64a73756f978bfd853391
prerequisite-patch-id: 87104eb556b1cef43ded0979f0b806f35ec819bb
prerequisite-patch-id: 5536c46e78e53cc31224b2730ca5fb454cd17761
prerequisite-patch-id: 48e3546e357a8e68e4abaeb2faf1081afb8f3ae3
prerequisite-patch-id: be78356c5aabcd0e68c61b2bc785c73a0c2edff9
prerequisite-patch-id: 7192a8aa88903ca758655dea8eaac94b98481e82
prerequisite-patch-id: f28910d02bfc6e11c1ecc2ce03db57ccaba0d185
prerequisite-patch-id: 48fc2a91c98aac131457deed8d9c2965eaffd1fb
prerequisite-patch-id: 0c829edd4d3d8cb4765d649411dec1124fd20fbd
prerequisite-patch-id: e994569657dc0537d6d3b9f7cd4d919ba2602126
prerequisite-patch-id: bce46caeb9616651be519c0474855ce9341de611
prerequisite-patch-id: a2a70a62d2684c072e3927abeb7a9e86dbec759b

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


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

* [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc()
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:33   ` sashiko-bot
  2026-11-06 13:55   ` Luca Ceresoli
  2026-11-06 13:14 ` [PATCH v8 02/39] drm/bridge-helper: Don't unlock the connection mutex after a backoff Maxime Ripard
                   ` (37 subsequent siblings)
  38 siblings, 2 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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, Paul Cercueil,
	Luca Ceresoli

The ingenic driver allocates a structure holding both the DPI encoder
and the source bridge of its pipeline, and allocates it as a whole
through drmm_encoder_alloc(). The bridge is thus not known to
devm_drm_bridge_alloc(), which drm_bridge_attach() already warns
about.

The bridge private object also gets its name from the devm bridge
allocation, and drm_bridge_attach() will soon report a failure of
that initialization instead of ignoring it, so a bridge allocated any
other way would prevent the driver from probing.

Allocate the structure through devm_drm_bridge_alloc(), making the
bridge the primary object of the allocation, and initialise the
embedded encoder in place with drmm_encoder_init(). Both objects
stay in the same structure, so the container_of() conversions keep
working, and the bridge funcs assignment moves into the allocator
call.

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

---

To: Paul Cercueil <paul@crapouillou.net>
Cc: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
index 63198fd59498..8cad8dcfae60 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
@@ -1304,18 +1304,25 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
 		}
 
-		ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
-					NULL, DRM_MODE_ENCODER_DPI, NULL);
+		ib = devm_drm_bridge_alloc(dev, struct ingenic_drm_bridge,
+					   bridge, &ingenic_drm_bridge_funcs);
 		if (IS_ERR(ib)) {
 			ret = PTR_ERR(ib);
-			dev_err(dev, "Failed to init encoder: %d\n", ret);
+			dev_err(dev, "Failed to allocate bridge: %d\n", ret);
 			goto err_drvdata;
 		}
 
 		encoder = &ib->encoder;
+
+		ret = drmm_encoder_init(drm, encoder, NULL,
+					DRM_MODE_ENCODER_DPI, NULL);
+		if (ret) {
+			dev_err(dev, "Failed to init encoder: %d\n", ret);
+			goto err_drvdata;
+		}
+
 		encoder->possible_crtcs = drm_crtc_mask(&priv->crtc);
 
 		drm_encoder_helper_add(encoder, &ingenic_drm_encoder_helper_funcs);
 
-		ib->bridge.funcs = &ingenic_drm_bridge_funcs;
 		ib->next_bridge = bridge;
 

-- 
2.55.0


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

* [PATCH v8 02/39] drm/bridge-helper: Don't unlock the connection mutex after a backoff
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc() Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:39   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 03/39] drm/tests: hdmi-state-helper: Handle EDEADLK in the 420-only test Maxime Ripard
                   ` (36 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 47 +++++++++++++++++++++++++++----------
 1 file changed, 35 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/drm_bridge_helper.c b/drivers/gpu/drm/drm_bridge_helper.c
index 0a3c8fee66b3..2efa4e1bc4b7 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,34 @@ 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() drops the connection
+	 * mutex on its way out, whether or not it was held through @ctx,
+	 * so we need to 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;
+
+	/*
+	 * A concurrent atomic commit may have run while the connection
+	 * mutex was released above, and re-routed the connector to
+	 * another encoder or CRTC. Make sure the connector is still
+	 * tied to our encoder before resetting anything.
+	 */
+	if (connector->state->best_encoder != encoder)
+		return -EINVAL;
 
 	crtc = connector->state->crtc;
-	ret = drm_atomic_helper_reset_crtc(crtc, ctx);
+	if (!crtc)
+		return -EINVAL;
 
-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] 60+ messages in thread

* [PATCH v8 03/39] drm/tests: hdmi-state-helper: Handle EDEADLK in the 420-only test
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc() Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 02/39] drm/bridge-helper: Don't unlock the connection mutex after a backoff Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 04/39] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test Maxime Ripard
                   ` (35 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 04/39] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (2 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 03/39] drm/tests: hdmi-state-helper: Handle EDEADLK in the 420-only test Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:32   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 05/39] drm/colorop: Export drm_atomic_helper_colorop_create_state() Maxime Ripard
                   ` (34 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 05/39] drm/colorop: Export drm_atomic_helper_colorop_create_state()
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (3 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 04/39] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 06/39] drm/atomic: Fix private obj error handling Maxime Ripard
                   ` (33 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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_atomic_helper_colorop_create_state() is only called by
drm_mode_config_create_state(), which lives in the same module, so it
never needed to be exported.

The atomic state readout helpers we'll introduce call it
from drm_kms_helper to allocate the colorop states. Without the
export, building the kernel with CONFIG_DRM_KMS_HELPER=m fails at
modpost with an undefined symbol.

Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_colorop.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c
index 95da03d6c65c..319557d1436f 100644
--- a/drivers/gpu/drm/drm_colorop.c
+++ b/drivers/gpu/drm/drm_colorop.c
@@ -649,4 +649,5 @@ drm_atomic_helper_colorop_create_state(struct drm_colorop *colorop)
 	return state;
 }
+EXPORT_SYMBOL(drm_atomic_helper_colorop_create_state);
 
 /**

-- 
2.55.0


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

* [PATCH v8 06/39] drm/atomic: Fix private obj error handling
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (4 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 05/39] drm/colorop: Export drm_atomic_helper_colorop_create_state() Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:29   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 07/39] drm/atomic: Add drm_private_obj name Maxime Ripard
                   ` (32 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 81591173265d..a1f18fc96b4c 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1063,6 +1063,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);
 
@@ -1072,10 +1081,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);
@@ -1090,5 +1106,16 @@ void
 drm_atomic_private_obj_fini(struct drm_private_obj *obj)
 {
-	list_del(&obj->head);
+	/*
+	 * Some callers ignore drm_atomic_private_obj_init() failures
+	 * and unconditionally register a teardown action that calls
+	 * this function. Use list_del_init() rather than list_del() so
+	 * that calling drm_atomic_private_obj_fini() twice, after both
+	 * a failed and a successful drm_atomic_private_obj_init(),
+	 * remains safe.
+	 */
+	if (list_empty(&obj->head))
+		return;
+
+	list_del_init(&obj->head);
 	obj->funcs->atomic_destroy_state(obj, obj->state);
 	drm_modeset_lock_fini(&obj->lock);

-- 
2.55.0


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

* [PATCH v8 07/39] drm/atomic: Add drm_private_obj name
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (5 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 06/39] drm/atomic: Fix private obj error handling Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:35   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 08/39] drm/bridge: Switch print_state to use bridge name Maxime Ripard
                   ` (31 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 +
 .../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            | 21 ++++++++++-
 drivers/gpu/drm/drm_atomic.c                       | 43 ++++++++++++++++++++++
 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                           | 10 +++++
 include/drm/drm_bridge.h                           |  2 +
 15 files changed, 103 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 0ea45ab12119..a92c58efe21a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -2302,4 +2302,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..5f30f82b2ebb 100644
--- a/drivers/gpu/drm/display/drm_dp_tunnel.c
+++ b/drivers/gpu/drm/display/drm_dp_tunnel.c
@@ -390,4 +390,12 @@ lookup_or_alloc_group(struct drm_dp_tunnel_mgr *mgr, u8 drv_group_id)
 		 tunnel_group_id(drv_group_id) & ((1 << DP_USB4_DRIVER_ID_BITS) - 1));
 
+	/*
+	 * The private object was named after the group index when it
+	 * was initialized, since the group identifier wasn't known
+	 * yet. Refresh it now that we have one. The name is only used
+	 * for debug messages, so ignore any allocation failure.
+	 */
+	drm_atomic_private_obj_set_name(&group->base, group->name);
+
 	return group;
 }
@@ -1728,9 +1736,20 @@ EXPORT_SYMBOL(drm_dp_tunnel_atomic_get_new_state);
 static bool init_group(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_tunnel_group *group)
 {
+	char name[32];
+
 	group->mgr = mgr;
 	group->available_bw = -1;
 	INIT_LIST_HEAD(&group->tunnels);
 
-	drm_atomic_private_obj_init(mgr->dev, &group->base,
+	/*
+	 * The group identifier, and thus the group name, is only known
+	 * once a tunnel has been added to the group. Name the private
+	 * object after the group index until then,
+	 * lookup_or_alloc_group() will refresh it.
+	 */
+	snprintf(name, sizeof(name), "dp-tunnel-group-%d",
+		 (int)(group - mgr->groups));
+
+	drm_atomic_private_obj_init(mgr->dev, &group->base, name,
 				    &tunnel_group_funcs);
 
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index a1f18fc96b4c..63647dd3204f 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1049,4 +1049,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
@@ -1060,4 +1061,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)
 {
@@ -1078,4 +1080,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);
 
@@ -1092,4 +1100,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;
@@ -1119,4 +1130,5 @@ drm_atomic_private_obj_fini(struct drm_private_obj *obj)
 	list_del_init(&obj->head);
 	obj->funcs->atomic_destroy_state(obj, obj->state);
+	kfree(obj->name);
 	drm_modeset_lock_fini(&obj->lock);
 }
@@ -1152,4 +1164,35 @@ static int drm_atomic_commit_set_private_obj_state(struct drm_atomic_commit *com
 }
 
+/**
+ * drm_atomic_private_obj_set_name - update the name of a private object
+ * @obj: private object
+ * @name: new human-readable name for debug messages
+ *
+ * Replaces the name of @obj previously set at
+ * drm_atomic_private_obj_init() time. This is meant for callers that
+ * only get a meaningful name well after the object has been
+ * initialized.
+ *
+ * RETURNS:
+ * Zero on success, a negative error code on allocation failure. The
+ * name is only used for debug messages, so callers can safely ignore
+ * the return value.
+ */
+int drm_atomic_private_obj_set_name(struct drm_private_obj *obj,
+				    const char *name)
+{
+	char *new_name;
+
+	new_name = kstrdup(name, GFP_KERNEL);
+	if (!new_name)
+		return -ENOMEM;
+
+	kfree(obj->name);
+	obj->name = new_name;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_atomic_private_obj_set_name);
+
 /**
  * drm_atomic_get_private_obj_state - get private object state
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 8ae2f9b1db44..11aaa4ae5102 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -319,4 +319,5 @@ static void __drm_bridge_free(struct kref *kref)
 	drm_bridge_put(bridge->next_bridge);
 
+	kfree(bridge->name);
 	kfree(bridge->container);
 }
@@ -420,4 +421,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;
@@ -655,5 +663,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 8cad8dcfae60..ea24c8666191 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
@@ -1412,4 +1412,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..f55c7a1371ae 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
@@ -758,7 +763,12 @@ 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,
+				const char *name,
 				const struct drm_private_state_funcs *funcs);
+
 void drm_atomic_private_obj_fini(struct drm_private_obj *obj);
 
+int drm_atomic_private_obj_set_name(struct drm_private_obj *obj,
+				    const char *name);
+
 struct drm_private_state * __must_check
 drm_atomic_get_private_obj_state(struct drm_atomic_commit *state,
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] 60+ messages in thread

* [PATCH v8 08/39] drm/bridge: Switch print_state to use bridge name
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (6 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 07/39] drm/atomic: Add drm_private_obj name Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 09/39] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
                   ` (30 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 11aaa4ae5102..aed3fcf0e180 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -573,7 +573,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] 60+ messages in thread

* [PATCH v8 09/39] drm/bridge: Add drm_private_obj_is_bridge()
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (7 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 08/39] drm/bridge: Switch print_state to use bridge name Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 10/39] drm/fb-dma-helper: Handle framebuffers without a GEM object Maxime Ripard
                   ` (29 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 aed3fcf0e180..b6cdbc172f68 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -592,4 +592,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] 60+ messages in thread

* [PATCH v8 10/39] drm/fb-dma-helper: Handle framebuffers without a GEM object
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (8 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 09/39] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:32   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 11/39] drm/gem-atomic-helper: " Maxime Ripard
                   ` (28 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 11/39] drm/gem-atomic-helper: Handle framebuffers without a GEM object
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (9 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 10/39] drm/fb-dma-helper: Handle framebuffers without a GEM object Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 12/39] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
                   ` (27 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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

A framebuffer reconstructed from a hardware state readout describes a
buffer that the firmware allocated, outside of the kernel memory
allocator, so its planes have no GEM object attached.

drm_gem_plane_helper_prepare_fb() retrieves the GEM object of every
plane through drm_gem_fb_get_obj() to extract the implicit fences from
its reservation. For a readout framebuffer, this triggers
drm_WARN_ON_ONCE(!fb->obj[plane]), returns NULL, and fails the whole
commit with -EINVAL.

This can be hit as soon as the state readout installs such a
framebuffer on a plane: any commit that includes the plane without
userspace providing a new framebuffer will duplicate its current
state, readout framebuffer included, and run prepare_fb() on it.
Blanking the display through DPMS right after boot is such a case,
since it pulls the plane in through drm_atomic_add_affected_planes().

Access the framebuffer obj[] array directly and skip the planes
without a GEM object: there is no reservation to extract implicit
fences from anyway. This mirrors what drm_fb_dma_get_scanout_buffer()
does for such framebuffers.

Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_gem_atomic_helper.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/drm_gem_atomic_helper.c b/drivers/gpu/drm/drm_gem_atomic_helper.c
index c952cfed84c1..bab354d12fe7 100644
--- a/drivers/gpu/drm/drm_gem_atomic_helper.c
+++ b/drivers/gpu/drm/drm_gem_atomic_helper.c
@@ -138,11 +138,15 @@ int drm_gem_plane_helper_prepare_fb(struct drm_plane *plane,
 
 	for (i = 0; i < state->fb->format->num_planes; ++i) {
-		struct drm_gem_object *obj = drm_gem_fb_get_obj(state->fb, i);
+		struct drm_gem_object *obj = state->fb->obj[i];
 		struct dma_fence *new;
 
-		if (!obj) {
-			ret = -EINVAL;
-			goto error;
-		}
+		/*
+		 * A framebuffer reconstructed from a hardware state
+		 * readout has no GEM object backing its planes, as it
+		 * describes a buffer allocated outside of the kernel:
+		 * there is no implicit fencing to wait on.
+		 */
+		if (!obj)
+			continue;
 
 		ret = dma_resv_get_singleton(obj->resv, usage, &new);

-- 
2.55.0


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

* [PATCH v8 12/39] drm/atomic: Export drm_atomic_*_print_state
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (10 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 11/39] drm/gem-atomic-helper: " Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 13/39] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
                   ` (26 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 63647dd3204f..9fcd91aef154 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -559,6 +559,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;
@@ -980,6 +980,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;
@@ -1567,6 +1567,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;
@@ -2213,6 +2213,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] 60+ messages in thread

* [PATCH v8 13/39] drm/atomic_sro: Create drm_atomic_sro_state container
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (11 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 12/39] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 14/39] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
                   ` (25 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 490 +++++++++++++++++++++++++++++
 include/drm/drm_atomic_state_readout.h     |  51 +++
 3 files changed, 542 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..0993d4cdfc2d
--- /dev/null
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -0,0 +1,490 @@
+// 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;
+}
+EXPORT_SYMBOL(drm_atomic_sro_state_get_device);
+
+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);
+
+	if (index >= state->dev->mode_config.num_total_plane)
+		return NULL;
+
+	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);
+
+	if (index >= state->dev->mode_config.num_crtc)
+		return NULL;
+
+	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);
+
+	/*
+	 * The connectors array is sized at allocation time. A
+	 * connector registered since then has no state in the
+	 * container.
+	 */
+	if (index > state->max_connector_index)
+		return NULL;
+
+	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] 60+ messages in thread

* [PATCH v8 14/39] drm/atomic_sro: Create kernel parameter to force or disable readout
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (12 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 13/39] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:31   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 15/39] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
                   ` (24 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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>
---
 Documentation/admin-guide/kernel-parameters.txt | 12 ++++++++++
 drivers/gpu/drm/drm_atomic_state_readout.c      | 29 +++++++++++++++++++++++++
 include/drm/drm_atomic_state_readout.h          |  9 ++++++++
 3 files changed, 50 insertions(+)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 0b3c140babd9..59ff44c49f31 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1378,4 +1378,16 @@ Kernel parameters
 			any connectors not explicitly specified.
 
+	drm.atomic_readout=	[DRM]
+			Control the KMS atomic hardware state readout
+			(SRO), used to initialize the atomic state from
+			the state the firmware left the hardware in at
+			boot instead of programming a new one.
+			0: disable the state readout entirely,
+			1: enable it (default),
+			2: ignore the objects missing a comparison
+			   implementation,
+			3: ignore the objects missing a readout or
+			   comparison implementation.
+
 	dscc4.setup=	[NET]
 
diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
index 0993d4cdfc2d..4ae435236f82 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -15,4 +15,33 @@
 #include "drm_crtc_internal.h"
 
+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..955e2de9d5b1 100644
--- a/include/drm/drm_atomic_state_readout.h
+++ b/include/drm/drm_atomic_state_readout.h
@@ -16,4 +16,13 @@ struct drm_private_obj;
 struct drm_private_state;
 
+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,
+};
+
+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] 60+ messages in thread

* [PATCH v8 15/39] drm/atomic_sro: Add atomic state readout infrastructure
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (13 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 14/39] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 16/39] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
                   ` (23 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 4ae435236f82..07ddc315b13d 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>
@@ -20,4 +21,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
@@ -34,4 +92,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;
@@ -40,4 +100,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 f55c7a1371ae..8cb46c78d8df 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] 60+ messages in thread

* [PATCH v8 16/39] drm/atomic_sro: Add function to install state into drm objects
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (14 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 15/39] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:33   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 17/39] drm/atomic_sro: Create documentation Maxime Ripard
                   ` (22 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 07ddc315b13d..de0f27186bea 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -266,5 +266,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)
@@ -582,2 +583,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 8cb46c78d8df..481a5742a78c 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 955e2de9d5b1..e1e705f94f66 100644
--- a/include/drm/drm_atomic_state_readout.h
+++ b/include/drm/drm_atomic_state_readout.h
@@ -58,3 +58,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] 60+ messages in thread

* [PATCH v8 17/39] drm/atomic_sro: Create documentation
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (15 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 16/39] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 18/39] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
                   ` (21 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 de0f27186bea..d0f8665860ff 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.
+ */
+
 static unsigned int atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
 module_param_unsafe(atomic_readout, uint, 0400);

-- 
2.55.0


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

* [PATCH v8 18/39] drm/bridge: Handle bridges with hardware state readout
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (16 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 17/39] drm/atomic_sro: Create documentation Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:33   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 19/39] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
                   ` (20 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 98 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 95 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index b6cdbc172f68..c0224dcc8aa5 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>
@@ -565,4 +566,76 @@ 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);
+
+		/*
+		 * The connector may not have a state in the readout
+		 * state: when the readout is limited to a commit, only
+		 * the connectors of that commit are read out.
+		 */
+		if (!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 (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static void
 drm_bridge_atomic_print_priv_state(struct drm_printer *p,
@@ -592,4 +665,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
@@ -603,5 +684,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);
@@ -678,6 +761,15 @@ 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)
+		ret = drm_atomic_private_obj_init(bridge->dev, &bridge->base,
+						  bridge->name,
+						  &drm_bridge_priv_state_funcs_with_sro);
+	else
+		ret = drm_atomic_private_obj_init(bridge->dev, &bridge->base,
+						  bridge->name,
+						  &drm_bridge_priv_state_funcs);
+	if (ret)
+		goto err_reset_bridge;
 
 	return 0;

-- 
2.55.0


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

* [PATCH v8 19/39] drm/mode-config: Read out hardware state in drm_mode_config_create_state
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (17 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 18/39] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:33   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 20/39] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
                   ` (19 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 20/39] drm/atomic_sro: Provide helpers to implement hardware state readout
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (18 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 19/39] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:44   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 21/39] drm/mode-config: Introduce hw_reset hook Maxime Ripard
                   ` (18 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 789 ++++++++++++++++++++++
 include/drm/drm_atomic_state_readout_helper.h     |  35 +
 include/drm/drm_modeset_helper_vtables.h          |   3 +
 6 files changed, 841 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 d0f8665860ff..b91a8f71b78b 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..1b6cf304702c
--- /dev/null
+++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
@@ -0,0 +1,789 @@
+// 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) {
+		/*
+		 * Missing readout implementations are only allowed
+		 * when drm.atomic_readout skips them, but the object
+		 * still needs a valid state: fall back to a pristine
+		 * state, like drm_mode_config_create_state() would.
+		 */
+		drm_warn(dev, "No plane readout implementation.");
+
+		plane_state = plane_funcs->atomic_create_state(plane);
+		if (drm_WARN_ON(dev, IS_ERR(plane_state)))
+			return PTR_ERR(plane_state);
+
+		drm_atomic_sro_set_plane_state(state, plane, plane_state);
+		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 (ret) {
+		drm_dbg_kms(dev, "Plane %s readout failed: %pe\n",
+			    plane->name, ERR_PTR(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;
+}
+EXPORT_SYMBOL(drm_atomic_helper_sro_readout_planes_state);
+
+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) {
+		/*
+		 * Missing readout implementations are only allowed
+		 * when drm.atomic_readout skips them, but the object
+		 * still needs a valid state: fall back to a pristine
+		 * state, like drm_mode_config_create_state() would.
+		 */
+		drm_warn(dev, "No CRTC readout implementation.");
+
+		crtc_state = crtc_funcs->atomic_create_state(crtc);
+		if (drm_WARN_ON(dev, IS_ERR(crtc_state)))
+			return PTR_ERR(crtc_state);
+
+		drm_atomic_sro_set_crtc_state(state, crtc, crtc_state);
+		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 (ret) {
+		drm_dbg_kms(dev, "CRTC %s readout failed: %pe\n",
+			    crtc->name, ERR_PTR(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, *readout_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;
+
+		/*
+		 * A commit can include a CRTC without including all the
+		 * planes and connectors enabled on it, a plane property
+		 * update for instance, but the readout CRTC state needs a
+		 * complete plane_mask, connector_mask and encoder_mask to
+		 * compare against the committed one.
+		 *
+		 * Only the objects of the commit are read out, as their
+		 * locks are the only ones held, so report the objects the
+		 * commit doesn't touch as enabled: the commit doesn't
+		 * affect them, and the objects it does affect are
+		 * verified by their own comparison hooks.
+		 */
+		readout_crtc_state = drm_atomic_sro_get_crtc_state(state, crtc);
+		if (readout_crtc_state) {
+			readout_crtc_state->plane_mask |= new_crtc_state->plane_mask;
+			readout_crtc_state->connector_mask |= new_crtc_state->connector_mask;
+			readout_crtc_state->encoder_mask |= new_crtc_state->encoder_mask;
+		}
+	}
+
+	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;
+}
+EXPORT_SYMBOL(drm_atomic_helper_sro_readout_crtcs_state);
+
+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) {
+		/*
+		 * Missing readout implementations are only allowed
+		 * when drm.atomic_readout skips them, but the object
+		 * still needs a valid state: fall back to a pristine
+		 * state, like drm_mode_config_create_state() would.
+		 */
+		drm_warn(dev, "No connector readout implementation.");
+
+		conn_state = conn_funcs->atomic_create_state(connector);
+		if (drm_WARN_ON(dev, IS_ERR(conn_state)))
+			return PTR_ERR(conn_state);
+
+		drm_atomic_sro_set_connector_state(state, connector, conn_state);
+		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 (ret) {
+		drm_dbg_kms(dev, "Connector %s readout failed: %pe\n",
+			    connector->name, ERR_PTR(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;
+}
+EXPORT_SYMBOL(drm_atomic_helper_sro_readout_connectors_state);
+
+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) {
+		/*
+		 * Missing readout implementations are only allowed
+		 * when drm.atomic_readout skips them, but the object
+		 * still needs a valid state: fall back to a pristine
+		 * state, like drm_mode_config_create_state() would.
+		 */
+		drm_warn(dev, "No bridge %s readout implementation.",
+			 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);
+
+		drm_atomic_sro_set_private_obj_state(state, bridge_obj,
+						     bridge_obj_state);
+		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 (ret) {
+		drm_dbg_kms(dev, "Bridge %s readout failed: %pe\n",
+			    bridge->name, ERR_PTR(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;
+}
+EXPORT_SYMBOL(drm_atomic_helper_sro_readout_bridges_state);
+
+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) {
+		/*
+		 * Missing readout implementations are only allowed
+		 * when drm.atomic_readout skips them, but the object
+		 * still needs a valid state: fall back to a pristine
+		 * state, like drm_mode_config_create_state() would.
+		 */
+		drm_warn(dev, "No private object %s readout implementation.",
+			 privobj->name);
+
+		priv_state = priv_funcs->atomic_create_state(privobj);
+		if (drm_WARN_ON(dev, IS_ERR(priv_state)))
+			return PTR_ERR(priv_state);
+
+		drm_atomic_sro_set_private_obj_state(state, privobj, priv_state);
+		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 (ret) {
+		drm_dbg_kms(dev, "Private object %s readout failed: %pe\n",
+			    privobj->name, ERR_PTR(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;
+}
+EXPORT_SYMBOL(drm_atomic_helper_sro_readout_private_objs_state);
+
+/**
+ * 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)) {
+		drm_warn(dev, "Couldn't read out the hardware state: %pe\n",
+			 state);
+		return PTR_ERR(state);
+	}
+
+	/* TODO: Add colorop state readout support */
+	drm_for_each_colorop(colorop, dev) {
+		struct drm_colorop_state *colorop_state;
+
+		/*
+		 * The colorop states are already initialized when the
+		 * colorops are created. Only create a pristine state for
+		 * the ones that don't have one, like
+		 * drm_mode_config_create_state() does.
+		 */
+		if (colorop->state)
+			continue;
+
+		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] 60+ messages in thread

* [PATCH v8 21/39] drm/mode-config: Introduce hw_reset hook
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (19 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 20/39] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 22/39] drm/modeset-helper: Add drm_mode_config_helper_hw_reset() Maxime Ripard
                   ` (17 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 22/39] drm/modeset-helper: Add drm_mode_config_helper_hw_reset()
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (20 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 21/39] drm/mode-config: Introduce hw_reset hook Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 23/39] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
                   ` (16 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 23/39] drm/atomic_helper: Compare actual and readout states once the commit is done
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (21 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 22/39] drm/modeset-helper: Add drm_mode_config_helper_hw_reset() Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 24/39] drm/atomic-state-helper: Provide comparison macros Maxime Ripard
                   ` (15 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 291 ++++++++++++++++++++++++++++-
 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, 450 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 b91a8f71b78b..d34fc9bb138b 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
@@ -117,4 +126,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
@@ -143,4 +203,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;
 }
@@ -764,2 +828,223 @@ void drm_atomic_sro_install_state(struct drm_atomic_sro_state *state)
 }
 EXPORT_SYMBOL(drm_atomic_sro_install_state);
+
+/*
+ * Whether @crtc drives a picture once @commit has been applied.
+ *
+ * A commit can leave an object attached to a CRTC it just turned off:
+ * a DPMS off commit only clears &drm_crtc_state.active and leaves the
+ * connectors bound to their CRTC. The hardware has nothing to read out
+ * for such a pipeline, so there is nothing to compare either.
+ */
+static bool
+drm_atomic_sro_crtc_is_active(struct drm_atomic_commit *commit,
+			      struct drm_crtc *crtc)
+{
+	struct drm_crtc_state *crtc_state;
+
+	if (!crtc)
+		return false;
+
+	crtc_state = drm_atomic_get_new_crtc_state(commit, crtc);
+	if (!crtc_state)
+		crtc_state = crtc->state;
+
+	return crtc_state && crtc_state->active;
+}
+
+static bool
+drm_atomic_sro_bridge_pipeline_active(struct drm_private_obj *obj,
+				       struct drm_atomic_commit *commit)
+{
+	struct drm_connector_state *new_conn_state;
+	struct drm_connector *conn;
+	struct drm_bridge *bridge;
+	unsigned int i;
+
+	bridge = drm_priv_to_bridge(obj);
+
+	for_each_new_connector_in_state(commit, conn, new_conn_state, i) {
+		if (new_conn_state->best_encoder == bridge->encoder &&
+		    drm_atomic_sro_crtc_is_active(commit, new_conn_state->crtc))
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * 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 (IS_ERR(readout_state)) {
+		drm_dbg_kms(dev, "Couldn't read out the state to compare: %pe\n",
+			    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;
+
+		/*
+		 * The connector routing and its negotiated properties
+		 * can't be read out of a pipeline that isn't driving a
+		 * picture anymore. A connector left on in hardware
+		 * after the commit turned its CRTC off is caught by the
+		 * CRTC comparison above.
+		 */
+		if (!drm_atomic_sro_crtc_is_active(commit, new_conn_state->crtc))
+			continue;
+
+		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;
+
+		/*
+		 * A commit disabling a pipeline keeps duplicated, stale
+		 * states for the bridges of the old pipeline, while
+		 * their readout is a pristine state since there is
+		 * nothing to read out from powered down hardware.
+		 * Skip the comparison for those.
+		 */
+		if (drm_private_obj_is_bridge(obj) &&
+		    !drm_atomic_sro_bridge_pipeline_active(obj, commit))
+			continue;
+
+		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 c0224dcc8aa5..b183002b7b3d 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -762,5 +762,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)
 		ret = drm_atomic_private_obj_init(bridge->dev, &bridge->base,
 						  bridge->name,
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 481a5742a78c..4c7a65723228 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 e1e705f94f66..5d71d2918def 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;
@@ -59,4 +62,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] 60+ messages in thread

* [PATCH v8 24/39] drm/atomic-state-helper: Provide comparison macros
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (22 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 23/39] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 25/39] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
                   ` (14 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 1b6cf304702c..4156f3a27f04 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
@@ -788,2 +788,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] 60+ messages in thread

* [PATCH v8 25/39] drm/atomic-state-helper: Provide atomic_compare_state helpers
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (23 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 24/39] drm/atomic-state-helper: Provide comparison macros Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:44   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 26/39] drm/atomic-helper: Document State Read-Out as a state initialization approach Maxime Ripard
                   ` (13 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 306 ++++++++++++++++++++++
 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, 366 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 4156f3a27f04..57376c9d4014 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>
@@ -789,4 +790,309 @@ 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);
+
+	/*
+	 * NOTE: We can't check fb_damage_clips and ignore_damage_clips
+	 * because they are transient, per-commit software fields that
+	 * are reset when the state is duplicated and never stored in
+	 * the hardware.
+	 */
+
+	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, 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. enable is only checked at
+	 * this point: a DPMS off commit keeps enable set while the
+	 * hardware, and thus the readout, reports an inactive CRTC.
+	 */
+	STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, enable);
+
+	/* 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);
+
+	/*
+	 * NOTE: We can't check mode_blob: its payload also carries the
+	 * mode type and name, and neither can be reconstructed from the
+	 * hardware. The modes themselves are covered by the checks
+	 * above.
+	 */
+	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);
+
+	/*
+	 * Both unbound, the routing and the negotiated properties are
+	 * meaningless: a disabled connector keeps the values it was
+	 * last enabled with, while a state read out from a disabled
+	 * pipeline is pristine.
+	 */
+	if (!expected->crtc && !actual->crtc)
+		return ret;
+
+	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,
+	 * color_format or hdmi.broadcast_rgb because they are
+	 * userspace requests negotiated during atomic_check into
+	 * output_bpc, output_format and hdmi.is_limited_range. The
+	 * hardware only stores the negotiated result.
+	 */
+
+	STATE_CHECK_U32(ret, p, conn->name, expected, actual, privacy_screen_sw_state);
+
+	/*
+	 * NOTE: We can't check hdr_output_metadata: it's a userspace
+	 * request, and the hardware only stores the resulting HDR
+	 * infoframe, compared below.
+	 */
+
+	/*
+	 * NOTE: The infoframe payloads are only compared when the
+	 * infoframe is enabled in both states: the disabled ones keep
+	 * stale data from earlier commits around in the expected
+	 * state, while the readout state is pristine.
+	 */
+	STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.avi.set);
+	if (expected->hdmi.infoframes.avi.set && 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);
+	if (expected->hdmi.infoframes.hdr_drm.set && 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);
+	if (expected->hdmi.infoframes.spd.set && 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);
+	if (expected->hdmi.infoframes.hdmi.set && 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 b183002b7b3d..81174015309f 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -638,4 +638,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,
@@ -666,4 +681,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] 60+ messages in thread

* [PATCH v8 26/39] drm/atomic-helper: Document State Read-Out as a state initialization approach
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (24 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 25/39] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 27/39] drm/tests: kunit: Add a drm_printer helper Maxime Ripard
                   ` (12 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 27/39] drm/tests: kunit: Add a drm_printer helper
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (25 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 26/39] drm/atomic-helper: Document State Read-Out as a state initialization approach Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 28/39] drm/tests: Add tests for the atomic state readout Maxime Ripard
                   ` (11 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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..5fdffbf78245 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 LOGLEVEL_* 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] 60+ messages in thread

* [PATCH v8 28/39] drm/tests: Add tests for the atomic state readout
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (26 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 27/39] drm/tests: kunit: Add a drm_printer helper Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:44   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 29/39] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
                   ` (10 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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/drm_atomic_state_readout.c         |   10 +-
 drivers/gpu/drm/tests/Makefile                     |    1 +
 .../gpu/drm/tests/drm_atomic_state_readout_test.c  | 1739 ++++++++++++++++++++
 3 files changed, 1749 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
index d34fc9bb138b..a66b010afbc9 100644
--- a/drivers/gpu/drm/drm_atomic_state_readout.c
+++ b/drivers/gpu/drm/drm_atomic_state_readout.c
@@ -13,4 +13,6 @@
 #include <linux/sched/mm.h>
 
+#include <kunit/visibility.h>
+
 #include "drm_internal.h"
 #include "drm_crtc_internal.h"
@@ -64,8 +66,14 @@
  */
 
-static unsigned int atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
+/*
+ * Exposed for the KUnit tests, which exercise the various modes the
+ * parameter can be set to. They must restore the default value, as
+ * the tests of every suite run in the same kernel instance.
+ */
+VISIBLE_IF_KUNIT 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)");
+EXPORT_SYMBOL_IF_KUNIT(atomic_readout);
 
 static bool drm_atomic_sro_can_readout(struct drm_device *dev)
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..f2bc6d52c5b2
--- /dev/null
+++ b/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c
@@ -0,0 +1,1739 @@
+// 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).
+ *
+ * The drm_atomic_sro_readout_mode suite tests the other values the
+ * parameter can take, and its init/exit hooks save and restore it
+ * around every test case.
+ */
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+extern unsigned int atomic_readout;
+
+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;
+
+	/*
+	 * Whether the mock hardware drives a picture. The readout
+	 * hooks report a disabled pipeline when it isn't set, like a
+	 * driver reading a powered down encoder would.
+	 */
+	bool hw_active;
+
+	/* 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;
+
+	/* The pipeline is off, there's nothing to read out. */
+	if (!priv->hw_active)
+		return 0;
+
+	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,
+};
+
+/*
+ * Same as mock_crtc_with_sro_funcs, but without the comparison hook:
+ * used to exercise the modes that tolerate missing comparison
+ * implementations.
+ */
+static const struct drm_crtc_funcs mock_crtc_with_sro_no_compare_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,
+};
+
+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 pipeline is off, there's nothing to read out. */
+	if (!priv->hw_active)
+		return 0;
+
+	/*
+	 * 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_sro_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_sro_readout_err(struct drm_device *dev)
+{
+	return -EINVAL;
+}
+
+static const struct drm_mode_config_funcs mock_mode_config_with_sro_err_funcs = {
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = mock_modeset_commit,
+	.atomic_sro_readout_state = mock_sro_readout_err,
+	.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;
+
+	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;
+	priv->hw_active = true;
+
+	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;
+}
+
+/*
+ * Test that blanking a pipeline isn't reported as a mismatch. A DPMS
+ * off commit only clears drm_crtc_state.active and leaves the
+ * connector bound to its CRTC, while the hardware readout of a
+ * disabled pipeline reports an unbound connector: the two states
+ * differ, but there is nothing to compare for a pipeline that doesn't
+ * drive a picture anymore.
+ */
+static void drm_test_sro_readout_and_compare_blanked(struct kunit *test)
+{
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_connector_state *conn_state;
+	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, with the pipeline enabled */
+	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);
+
+	conn_state = drm_atomic_get_connector_state(commit, &priv->connector);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	/*
+	 * A DPMS off commit only clears active: the connector is still
+	 * bound to its CRTC in the committed state.
+	 */
+	crtc_state->active = false;
+	KUNIT_ASSERT_PTR_EQ(test, conn_state->crtc, priv->crtc);
+
+	/* The hardware is now off */
+	priv->hw_active = false;
+
+	identical = drm_atomic_sro_readout_and_compare(commit);
+	KUNIT_EXPECT_TRUE(test, identical);
+
+	/*
+	 * A pipeline that should be driving a picture but isn't is
+	 * still reported as a mismatch.
+	 */
+	crtc_state->active = true;
+
+	identical = drm_atomic_sro_readout_and_compare(commit);
+	KUNIT_EXPECT_FALSE(test, identical);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+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_and_compare_blanked),
+	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,
+};
+
+/*
+ * Readout module parameter tests
+ */
+
+/*
+ * The drm.atomic_readout module parameter is global: save the mode
+ * the kernel was booted with before every test case, and restore it
+ * afterwards. The exit hook runs even when an assertion aborts the
+ * test, and KUnit executes the test cases of every suite one at a
+ * time, so no other suite can observe the change.
+ */
+static int drm_test_sro_readout_mode_init(struct kunit *test)
+{
+	test->priv = (void *)(unsigned long)atomic_readout;
+
+	return 0;
+}
+
+static void drm_test_sro_readout_mode_exit(struct kunit *test)
+{
+	atomic_readout = (unsigned long)test->priv;
+}
+
+/*
+ * Test that a device whose objects all implement the readout and
+ * comparison hooks can be read out, unless the readout is disabled
+ * entirely.
+ */
+static void drm_test_sro_readout_mode_disabled(struct kunit *test)
+{
+	struct sro_test_priv *priv;
+	int ret;
+
+	priv = mock_device_with_sro_create(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+	atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	atomic_readout = DRM_ATOMIC_READOUT_DISABLED;
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP);
+}
+
+/*
+ * Test that a device whose CRTC has no comparison hook can only be
+ * read out by the modes tolerating missing comparison
+ * implementations.
+ */
+static void drm_test_sro_readout_mode_skip_missing_compare(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_no_compare_funcs,
+				  &mock_connector_with_sro_funcs);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+	atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP);
+
+	atomic_readout = DRM_ATOMIC_READOUT_SKIP_MISSING_COMPARE;
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+/*
+ * Test that a device whose plane and CRTC have no readout hook can
+ * only be read out by the mode tolerating missing readout
+ * implementations, and that the objects without a hook then get a
+ * pristine state installed rather than none at all.
+ */
+static void drm_test_sro_readout_mode_skip_missing_readout(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,
+				  NULL, NULL,
+				  &mock_connector_with_sro_funcs);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+	atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP);
+	KUNIT_EXPECT_NULL(test, priv->plane->state);
+	KUNIT_EXPECT_NULL(test, priv->crtc->state);
+
+	atomic_readout = DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT;
+	ret = drm_atomic_helper_sro_readout_state(&priv->drm);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_NOT_NULL(test, priv->plane->state);
+	KUNIT_EXPECT_NOT_NULL(test, priv->crtc->state);
+}
+
+static struct kunit_case drm_atomic_sro_readout_mode_tests[] = {
+	KUNIT_CASE(drm_test_sro_readout_mode_disabled),
+	KUNIT_CASE(drm_test_sro_readout_mode_skip_missing_compare),
+	KUNIT_CASE(drm_test_sro_readout_mode_skip_missing_readout),
+	{ }
+};
+
+static struct kunit_suite drm_atomic_sro_readout_mode_test_suite = {
+	.name = "drm_atomic_sro_readout_mode",
+	.init = drm_test_sro_readout_mode_init,
+	.exit = drm_test_sro_readout_mode_exit,
+	.test_cases = drm_atomic_sro_readout_mode_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);
+
+	priv->plane->funcs->atomic_destroy_state(priv->plane, state1);
+	priv->plane->funcs->atomic_destroy_state(priv->plane, state2);
+}
+
+/*
+ * 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);
+
+	priv->plane->funcs->atomic_destroy_state(priv->plane, state1);
+	priv->plane->funcs->atomic_destroy_state(priv->plane, state2);
+}
+
+/*
+ * 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);
+
+	priv->crtc->funcs->atomic_destroy_state(priv->crtc, state1);
+	priv->crtc->funcs->atomic_destroy_state(priv->crtc, state2);
+}
+
+/*
+ * 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);
+
+	priv->crtc->funcs->atomic_destroy_state(priv->crtc, state1);
+	priv->crtc->funcs->atomic_destroy_state(priv->crtc, state2);
+}
+
+/*
+ * 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);
+
+	priv->crtc->funcs->atomic_destroy_state(priv->crtc, state1);
+	priv->crtc->funcs->atomic_destroy_state(priv->crtc, state2);
+}
+
+/*
+ * 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);
+
+	connector->funcs->atomic_destroy_state(connector, state1);
+	connector->funcs->atomic_destroy_state(connector, state2);
+}
+
+/*
+ * 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);
+
+	/*
+	 * A state bound to a CRTC holds a reference on its connector,
+	 * released by __drm_atomic_helper_connector_destroy_state():
+	 * take it since the crtc pointer is set by hand here.
+	 */
+	drm_connector_get(connector);
+	state2->crtc = priv->crtc;
+
+	identical =
+		drm_atomic_helper_connector_compare_state(connector, p,
+							  state1, state2);
+	KUNIT_EXPECT_FALSE(test, identical);
+
+	connector->funcs->atomic_destroy_state(connector, state1);
+	connector->funcs->atomic_destroy_state(connector, state2);
+}
+
+/*
+ * 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_err_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);
+
+	/* 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_sro_err_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);
+
+	/* 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_readout_mode_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] 60+ messages in thread

* [PATCH v8 29/39] drm/encoder: Create atomic_sro_get_current_crtc hook
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (27 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 28/39] drm/tests: Add tests for the atomic state readout Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 30/39] drm/bridge: display-connector: Implement readout support Maxime Ripard
                   ` (9 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 30/39] drm/bridge: display-connector: Implement readout support
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (28 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 29/39] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:50   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 31/39] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
                   ` (8 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 31/39] drm/bridge_connector: Implement hw readout for connector
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (29 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 30/39] drm/bridge: display-connector: Implement readout support Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 32/39] drm/tidss: crtc: Add atomic_print_state implementation Maxime Ripard
                   ` (7 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 65 +++++++++++++++++++++++++-
 include/drm/drm_bridge.h                       | 32 +++++++++++++
 2 files changed, 95 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 2202e6044444..c1c208abf75c 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)
@@ -278,4 +307,16 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = {
 };
 
+static const struct drm_connector_funcs drm_bridge_connector_sro_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,
+	.debugfs_init = drm_bridge_connector_debugfs_init,
+	.oob_hotplug_event = drm_bridge_connector_oob_hotplug_event,
+	.color_format = drm_bridge_connector_color_format,
+};
+
 /* -----------------------------------------------------------------------------
  * Bridge Connector Helper Functions
@@ -828,4 +869,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);
@@ -858,4 +900,5 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 						struct drm_encoder *encoder)
 {
+	const struct drm_connector_funcs *connector_funcs;
 	struct drm_bridge_connector *bridge_connector;
 	struct drm_connector *connector;
@@ -897,4 +940,11 @@ 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) {
+			if (!bridge->funcs->atomic_sro_connector_readout)
+				return ERR_PTR(-EINVAL);
+
+			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.
@@ -1029,4 +1079,15 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 		return ERR_PTR(-EINVAL);
 
+	/*
+	 * Only expose the hardware state readout hooks when a bridge in
+	 * the chain provides a connector readout implementation.
+	 * Otherwise, the readout would silently succeed with a
+	 * pristine, disconnected connector state.
+	 */
+	if (bridge_connector->bridge_connector_hw_readout)
+		connector_funcs = &drm_bridge_connector_sro_funcs;
+	else
+		connector_funcs = &drm_bridge_connector_funcs;
+
 	if (bridge_connector->bridge_hdmi) {
 		bridge_connector->hdmi_funcs = drm_bridge_connector_hdmi_funcs;
@@ -1075,5 +1136,5 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 
 		ret = drmm_connector_hdmi_init(drm, connector,
-					       &drm_bridge_connector_funcs,
+					       connector_funcs,
 					       &bridge_connector->hdmi_funcs,
 					       connector_type, ddc);
@@ -1082,5 +1143,5 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	} else {
 		ret = drmm_connector_init(drm, connector,
-					  &drm_bridge_connector_funcs,
+					  connector_funcs,
 					  connector_type, ddc);
 		if (ret)
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] 60+ messages in thread

* [PATCH v8 32/39] drm/tidss: crtc: Add atomic_print_state implementation
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (30 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 31/39] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 33/39] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
                   ` (6 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 ae9c66831b6c..d2664bda53a9 100644
--- a/drivers/gpu/drm/tidss/tidss_crtc.c
+++ b/drivers/gpu/drm/tidss/tidss_crtc.c
@@ -404,4 +404,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,
@@ -411,4 +420,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] 60+ messages in thread

* [PATCH v8 33/39] drm/tidss: dispc: Improve mode checking logs
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (31 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 32/39] drm/tidss: crtc: Add atomic_print_state implementation Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 34/39] drm/tidss: Implement hw_reset Maxime Ripard
                   ` (5 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 e516f493d6d9..b5fb296fddcc 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1318,4 +1318,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;
 
@@ -1332,6 +1334,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;
@@ -1342,4 +1347,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;
@@ -1347,12 +1354,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))
@@ -1360,6 +1373,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;
+	}
 
 	/*
@@ -1371,6 +1386,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;
@@ -1384,10 +1401,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) {
@@ -1399,6 +1424,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] 60+ messages in thread

* [PATCH v8 34/39] drm/tidss: Implement hw_reset
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (32 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 33/39] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:50   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 35/39] drm/tidss: Implement readout support Maxime Ripard
                   ` (4 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 48 +++++++++----------------------------
 drivers/gpu/drm/tidss/tidss_dispc.h |  1 +
 drivers/gpu/drm/tidss/tidss_drv.c   | 22 ++++++++++-------
 drivers/gpu/drm/tidss/tidss_kms.c   | 14 +++++++++++
 4 files changed, 40 insertions(+), 45 deletions(-)

diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
index b5fb296fddcc..8720403af9ed 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -2932,43 +2932,21 @@ 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;
+	struct tidss_device *tidss = dispc->tidss;
 	int ret;
 
-	ret = pm_runtime_set_active(dev);
-	if (ret) {
-		dev_err(dev, "Failed to set DSS PM to active\n");
-		return ret;
-	}
-
-	ret = clk_prepare_enable(dispc->fclk);
-	if (ret) {
-		dev_err(dev, "Failed to enable DSS fclk\n");
-		goto err_runtime_suspend;
-	}
+	/*
+	 * The reset runs from drm_mode_config_create_initial_state(), so
+	 * after the driver has enabled runtime PM. As
+	 * pm_runtime_set_active() is only allowed while runtime PM is
+	 * disabled, rely on the regular runtime PM references to power
+	 * the device up for the reset instead.
+	 */
+	tidss_runtime_get(tidss);
 
 	ret = dispc_softreset(dispc);
-	if (ret)
-		goto err_clk_disable;
 
-	clk_disable_unprepare(dispc->fclk);
-	ret = pm_runtime_set_suspended(dev);
-	if (ret) {
-		dev_err(dev, "Failed to set DSS PM to suspended\n");
-		return ret;
-	}
-
-	return 0;
-
-err_clk_disable:
-	clk_disable_unprepare(dispc->fclk);
-
-err_runtime_suspend:
-	ret = pm_runtime_set_suspended(dev);
-	if (ret) {
-		dev_err(dev, "Failed to set DSS PM to suspended\n");
-		return ret;
-	}
+	tidss_runtime_put(tidss);
 
 	return ret;
@@ -3082,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 df3daccf3f31..caf0f8c58739 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.h
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -149,4 +149,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_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c
index e258f656cf03..409d599bb2ff 100644
--- a/drivers/gpu/drm/tidss/tidss_drv.c
+++ b/drivers/gpu/drm/tidss/tidss_drv.c
@@ -152,4 +152,18 @@ static int tidss_probe(struct platform_device *pdev)
 	}
 
+	drm_kms_helper_poll_init(ddev);
+
+	/*
+	 * drm_mode_config_create_initial_state() resets the DISPC when
+	 * the state readout isn't possible, which also clears the
+	 * interrupt enable registers. Install the interrupts only once
+	 * that's been done, so that the reset can't wipe them.
+	 */
+	ret = drm_mode_config_create_initial_state(ddev);
+	if (ret) {
+		dev_err(dev, "failed to create initial state: %d\n", ret);
+		goto err_runtime_suspend;
+	}
+
 	irq = platform_get_irq(pdev, 0);
 	if (irq < 0) {
@@ -165,12 +179,4 @@ static int tidss_probe(struct platform_device *pdev)
 	}
 
-	drm_kms_helper_poll_init(ddev);
-
-	ret = drm_mode_config_create_initial_state(ddev);
-	if (ret) {
-		dev_err(dev, "failed to create initial state: %d\n", ret);
-		goto err_irq_uninstall;
-	}
-
 	ret = drm_dev_register(ddev, 0);
 	if (ret) {
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
index eefe2221bdde..a3bf94e56fcb 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] 60+ messages in thread

* [PATCH v8 35/39] drm/tidss: Implement readout support
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (33 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 34/39] drm/tidss: Implement hw_reset Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 14:06   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 36/39] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
                   ` (3 subsequent siblings)
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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    | 109 +++++++++
 drivers/gpu/drm/tidss/tidss_dispc.c   | 427 +++++++++++++++++++++++++++++++++-
 drivers/gpu/drm/tidss/tidss_dispc.h   |  20 ++
 drivers/gpu/drm/tidss/tidss_encoder.c |  42 ++++
 drivers/gpu/drm/tidss/tidss_kms.c     |   6 +-
 drivers/gpu/drm/tidss/tidss_plane.c   | 205 ++++++++++++++++
 6 files changed, 803 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
index d2664bda53a9..38d7a5dfe4f3 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>
@@ -373,4 +377,106 @@ 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;
+
+	/*
+	 * The video port wasn't enabled in hardware: nothing to take
+	 * over for it, and enabling the clock and the vblank reporting
+	 * would leak their reference for a CRTC that no disable will
+	 * ever release.
+	 */
+	if (!crtc_state->active)
+		return;
+
+	tidss_runtime_get(tidss);
+
+	ret = dispc_vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
+	if (ret != 0) {
+		tidss_runtime_put(tidss);
+		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);
+
+	/*
+	 * Both inactive: the bus configuration wasn't read out from the
+	 * disabled pipeline, and the committed one is stale anyway.
+	 */
+	if (!expected->active && !actual->active)
+		return ret;
+
+	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)
 {
@@ -418,4 +524,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 8720403af9ed..c3117745965b 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1217,4 +1217,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)
 {
@@ -1475,4 +1481,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,
@@ -1548,4 +1776,92 @@ void dispc_ovr_enable_layer(struct dispc_device *dispc,
 }
 
+/**
+ * dispc_ovr_get_plane - find the overlay layer a plane is routed to
+ * @dispc: the dispc
+ * @hw_plane: the plane to look for
+ * @hw_videoport: returned video port the plane is routed to
+ * @x: returned X position of the plane
+ * @y: returned Y position of the plane
+ * @layer: returned overlay layer index the plane is routed to
+ *
+ * Reads the overlay manager registers to find which video port, at
+ * which position and in which layer, a plane is currently routed to.
+ * The layer index matches the normalized zpos the plane was committed
+ * with, as that is what dispc_ovr_set_plane() uses as the layer index.
+ *
+ * The CHANNELIN field of a disabled layer keeps its stale value, so
+ * only the enabled layers are considered.
+ *
+ * RETURNS:
+ *
+ * 0 on success, -ENODEV if the plane is not routed to any enabled
+ * overlay layer.
+ */
+int dispc_ovr_get_plane(struct dispc_device *dispc, u32 hw_plane,
+			u32 *hw_videoport, int *x, int *y, u32 *layer)
+{
+	u32 vp, l;
+
+	switch (dispc->feat->subrev) {
+	case DISPC_K2G: {
+		u32 val;
+
+		/*
+		 * On K2G there is only one plane and no overlay
+		 * manager: the position is programmed in the VID
+		 * pipeline registers.
+		 */
+		val = dispc_vid_read(dispc, hw_plane, DISPC_VID_K2G_POSITION);
+		*hw_videoport = 0;
+		*layer = 0;
+		*x = val & 0xffff;
+		*y = val >> 16;
+		return 0;
+	}
+	case DISPC_AM625:
+	case DISPC_AM62A7:
+	case DISPC_AM62L:
+	case DISPC_AM65X:
+	case DISPC_J721E:
+		for (vp = 0; vp < dispc->feat->num_vps; vp++) {
+			for (l = 0; l < dispc->feat->num_vids; l++) {
+				u32 attrs = dispc_ovr_read(dispc, vp,
+							   DISPC_OVR_ATTRIBUTES(l));
+
+				if (!(attrs & DISPC_OVR_ATTRIBUTES_ENABLE_MASK))
+					continue;
+
+				if (FIELD_GET(DISPC_OVR_ATTRIBUTES_CHANNELIN_MASK,
+					      attrs) != hw_plane)
+					continue;
+
+				*hw_videoport = vp;
+				*layer = l;
+
+				if (dispc->feat->subrev == DISPC_J721E) {
+					u32 attrs2 = dispc_ovr_read(dispc, vp,
+								    DISPC_OVR_ATTRIBUTES2(l));
+
+					*x = FIELD_GET(DISPC_OVR_ATTRIBUTES2_POSX_MASK,
+						       attrs2);
+					*y = FIELD_GET(DISPC_OVR_ATTRIBUTES2_POSY_MASK,
+						       attrs2);
+				} else {
+					*x = FIELD_GET(DISPC_OVR_ATTRIBUTES_POSX_MASK,
+						       attrs);
+					*y = FIELD_GET(DISPC_OVR_ATTRIBUTES_POSY_MASK,
+						       attrs);
+				}
+
+				return 0;
+			}
+		}
+
+		return -ENODEV;
+	default:
+		return -ENODEV;
+	}
+}
+
 /* CSC */
 enum csc_ctm {
@@ -2120,4 +2436,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)
@@ -2206,5 +2533,4 @@ void dispc_plane_setup(struct dispc_device *dispc, u32 hw_plane,
 	u16 cpp = state->fb->format->cpp[0];
 	u32 fb_width = state->fb->pitches[0] / cpp;
-	dma_addr_t dma_addr = drm_fb_dma_get_gem_addr(state->fb, state, 0);
 	struct dispc_scaling_params scale;
 
@@ -2213,8 +2539,18 @@ void dispc_plane_setup(struct dispc_device *dispc, u32 hw_plane,
 	dispc_plane_set_pixel_format(dispc, hw_plane, fourcc);
 
-	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);
-	dispc_vid_write(dispc, hw_plane, DISPC_VID_BA_1, dma_addr & 0xffffffff);
-	dispc_vid_write(dispc, hw_plane, DISPC_VID_BA_EXT_1, (u64)dma_addr >> 32);
+	/*
+	 * A framebuffer reconstructed from a hardware state readout has
+	 * no GEM object to derive the DMA address from. The hardware is
+	 * already scanning out that buffer, so keep the address the
+	 * firmware programmed.
+	 */
+	if (state->fb->obj[0]) {
+		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);
+		dispc_vid_write(dispc, hw_plane, DISPC_VID_BA_1, dma_addr & 0xffffffff);
+		dispc_vid_write(dispc, hw_plane, DISPC_VID_BA_EXT_1, (u64)dma_addr >> 32);
+	}
 
 	dispc_vid_write(dispc, hw_plane, DISPC_VID_PICTURE_SIZE,
@@ -2285,4 +2621,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)
 {
@@ -2291,4 +2702,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 caf0f8c58739..159fe3b94b0a 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.h
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -116,8 +116,11 @@ void dispc_ovr_set_plane(struct dispc_device *dispc, u32 hw_plane,
 void dispc_ovr_enable_layer(struct dispc_device *dispc,
 			    u32 hw_videoport, u32 layer, bool enable);
+int dispc_ovr_get_plane(struct dispc_device *dispc, u32 hw_plane,
+			u32 *hw_videoport, int *x, int *y, u32 *layer);
 
 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);
@@ -136,4 +139,10 @@ void dispc_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
 		    const struct drm_crtc_state *state, bool newmodeset,
 		    bool dpi_output);
+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);
@@ -146,5 +155,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 a3bf94e56fcb..967cef33d191 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..94824cff77f9 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,204 @@ 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,
+						 int *crtc_x, int *crtc_y,
+						 u32 *normalized_zpos)
+{
+	struct drm_device *dev = plane->dev;
+	struct tidss_device *tidss = to_tidss(dev);
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+	struct drm_crtc *crtc;
+	u32 hw_videoport, layer;
+	int ret;
+
+	ret = dispc_ovr_get_plane(tidss->dispc, tplane->hw_plane_id,
+				  &hw_videoport, crtc_x, crtc_y, &layer);
+	if (ret)
+		return ERR_PTR(ret);
+
+	*normalized_zpos = layer;
+
+	drm_for_each_crtc(crtc, dev) {
+		if (to_tidss_crtc(crtc)->hw_videoport == hw_videoport)
+			return crtc;
+	}
+
+	return ERR_PTR(-ENODEV);
+}
+
+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 crtc_x, crtc_y;
+	u32 normalized_zpos;
+	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, &crtc_x, &crtc_y,
+					&normalized_zpos);
+	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);
+
+	plane_state->crtc_x = crtc_x;
+	plane_state->crtc_y = crtc_y;
+
+	/*
+	 * The overlay layer index a plane is routed to is its
+	 * normalized zpos, as that is what the atomic update uses as
+	 * the layer index. The raw zpos property userspace set
+	 * cannot be recovered from the hardware.
+	 */
+	plane_state->normalized_zpos = normalized_zpos;
+	plane_state->zpos = normalized_zpos;
+
+	// TODO: Handle src_x/src_y, see DISPC_VID_BA_0
+
+	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 = crtc_x;
+	plane_state->dst.x2 = crtc_x + plane_state->crtc_w;
+	plane_state->dst.y1 = crtc_y;
+	plane_state->dst.y2 = crtc_y + 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 +385,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] 60+ messages in thread

* [PATCH v8 36/39] drm/tidss: encoder: Implement atomic_sro_get_current_crtc
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (34 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 35/39] drm/tidss: Implement readout support Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 37/39] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
                   ` (2 subsequent siblings)
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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] 60+ messages in thread

* [PATCH v8 37/39] drm/bridge: sii902x: Implement hw state readout
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (35 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 36/39] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 38/39] drm/panel: Implement state readout support Maxime Ripard
  2026-11-06 13:14 ` [PATCH v8 39/39] fixup! drm/tidss: Implement " Maxime Ripard
  38 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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 | 120 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 119 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
index 219edd36f7be..3666de315c0a 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,116 @@ 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;
+
+	mutex_lock(&sii902x->mutex);
+
+	ret = regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN);
+	/* I2C error */
+	if (ret < 0)
+		goto err_mutex_unlock;
+	/* Device is powered down, nothing to read out */
+	if (ret)
+		goto out;
+
+	encoder = bridge->encoder;
+	if (!encoder->funcs || !encoder->funcs->atomic_sro_get_current_crtc) {
+		ret = -ENODEV;
+		goto err_mutex_unlock;
+	}
+
+	crtc = encoder->funcs->atomic_sro_get_current_crtc(encoder);
+	if (!crtc) {
+		ret = -ENODEV;
+		goto err_mutex_unlock;
+	}
+
+	crtc_state = drm_atomic_sro_get_crtc_state(state, crtc);
+	if (!crtc_state) {
+		ret = -ENODEV;
+		goto err_mutex_unlock;
+	}
+
+	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;
+
+out:
+	mutex_unlock(&sii902x->mutex);
+	return 0;
+
+err_mutex_unlock:
+	mutex_unlock(&sii902x->mutex);
+	return ret;
+}
+
+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;
+	int ret;
+
+	mutex_lock(&sii902x->mutex);
+
+	ret = regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN);
+	/* I2C error */
+	if (ret < 0)
+		goto err_mutex_unlock;
+	/* Device is powered down, nothing to read out */
+	if (ret)
+		goto out;
+
+	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:
+		ret = -EINVAL;
+		goto err_mutex_unlock;
+	}
+
+	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;
+
+out:
+	mutex_unlock(&sii902x->mutex);
+	return 0;
+
+err_mutex_unlock:
+	mutex_unlock(&sii902x->mutex);
+	return ret;
+}
+
 static const struct drm_bridge_funcs sii902x_bridge_funcs = {
 	.attach = sii902x_bridge_attach,
@@ -548,4 +662,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,
@@ -1142,5 +1259,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] 60+ messages in thread

* [PATCH v8 38/39] drm/panel: Implement state readout support
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (36 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 37/39] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:45   ` sashiko-bot
  2026-11-06 13:14 ` [PATCH v8 39/39] fixup! drm/tidss: Implement " Maxime Ripard
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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

A panel bridge has no state of its own to read out of the hardware:
neither the panel enable state nor the bus configuration is observable
through it.

The bus configuration the atomic helpers negotiate can however be
reconstructed: the panel bridge is always the last bridge of a chain, so
the negotiation picks the first connector display info format as its
output format, or MEDIA_BUS_FMT_FIXED when the display info has none,
passes it through to its input end, and propagates the display info
flags to both ends. The atomic_sro_readout_state hook reports that
configuration. The display info is only populated once the connector
modes have been read, after the initial state has been created: the
fixed format reported then matches what the negotiation would produce,
and is harmless as the initial state is never compared.

The atomic_sro_connector_readout hook reports the connector as bound to
the current CRTC of its encoder whenever that CRTC was read out as
active, the panel being assumed enabled whenever its pipeline is.  A
panel bridge only carries DRM_BRIDGE_OP_CONNECTOR_HW_READOUT when no
bridge attached earlier in its chain provides a connector readout:
drm_bridge_connector_init() picks the last bridge carrying that op, and
earlier bridges may know the actual link state.

Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/drm_panel.c | 123 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)

diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 560ea57591bd..bef0e6af73ba 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -10,8 +10,11 @@
 #include <linux/err.h>
 #include <linux/export.h>
+#include <linux/media-bus-format.h>
 #include <linux/module.h>
 #include <linux/of.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_connector.h>
@@ -84,6 +87,29 @@ static int panel_bridge_attach(struct drm_bridge *bridge,
 	struct drm_panel *panel = drm_bridge_to_panel(bridge);
 	struct drm_connector *connector = &panel->connector;
+	bool upstream_readout = false;
 	int ret;
 
+	/*
+	 * drm_bridge_connector_init() picks the connector readout
+	 * provider from the last bridge of the chain carrying
+	 * DRM_BRIDGE_OP_CONNECTOR_HW_READOUT. Only provide the
+	 * fallback readout of a panel bridge when no bridge earlier
+	 * in the chain provides one, as those know the actual link
+	 * state while a panel bridge can only assume the panel is
+	 * enabled.
+	 */
+	drm_for_each_bridge_in_chain(encoder, iter) {
+		if (iter == bridge)
+			break;
+
+		if (iter->ops & DRM_BRIDGE_OP_CONNECTOR_HW_READOUT) {
+			upstream_readout = true;
+			break;
+		}
+	}
+
+	if (!upstream_readout)
+		bridge->ops |= DRM_BRIDGE_OP_CONNECTOR_HW_READOUT;
+
 	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
 		return 0;
@@ -227,4 +253,98 @@ static void panel_bridge_debugfs_init(struct drm_bridge *bridge,
 }
 
+/*
+ * A panel bridge has no state of its own to read out of the hardware:
+ * reconstruct the bus configuration the atomic helpers negotiate
+ * instead.
+ *
+ * The panel bridge is always the last bridge of a chain, and the
+ * negotiation picks the first connector display info format as its
+ * output format (or MEDIA_BUS_FMT_FIXED when the display info has
+ * none), which drm_atomic_helper_bridge_propagate_bus_fmt() passes
+ * through to its input. drm_atomic_bridge_propagate_bus_flags()
+ * similarly propagates the display info flags to both its output and
+ * input. This is also what happens when the display info has no
+ * formats yet, like during the initial state creation at probe time
+ * before any mode has been read: the bus configuration then reports
+ * a fixed format, which is harmless as the initial state is never
+ * compared, and every commit negotiates the bus configuration anew.
+ *
+ * The format the negotiation selects can only differ from the first
+ * display info format when the connector reports several formats and
+ * the rest of the bridge chain rejects it, which panel connectors
+ * don't do.
+ */
+static int
+panel_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 drm_connector *connector = conn_state->connector;
+	struct drm_display_info *info = &connector->display_info;
+	u32 format;
+
+	if (info->num_bus_formats)
+		format = info->bus_formats[0];
+	else
+		format = MEDIA_BUS_FMT_FIXED;
+
+	bridge_state->input_bus_cfg.format = format;
+	bridge_state->output_bus_cfg.format = format;
+
+	bridge_state->input_bus_cfg.flags = info->bus_flags;
+	bridge_state->output_bus_cfg.flags = info->bus_flags;
+
+	return 0;
+}
+
+/*
+ * A panel bridge has no way to read the panel state out of the
+ * hardware: assume the panel is enabled whenever its pipeline is.
+ */
+static int
+panel_bridge_connector_readout(struct drm_bridge *bridge,
+			       struct drm_atomic_sro_state *state,
+			       struct drm_connector_state *conn_state)
+{
+	struct drm_connector *connector = conn_state->connector;
+	struct drm_crtc_state *crtc_state;
+	struct drm_encoder *encoder;
+	struct drm_crtc *crtc;
+
+	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;
+
+	/* The pipeline is off, there is nothing to read out. */
+	if (!crtc_state->active)
+		return 0;
+
+	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().
+	 * The 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 const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
 	.attach = panel_bridge_attach,
@@ -240,4 +360,7 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
 	.atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
 	.debugfs_init = panel_bridge_debugfs_init,
+	.atomic_sro_readout_state = panel_bridge_readout_state,
+	.atomic_sro_compare_state = drm_atomic_helper_bridge_compare_state,
+	.atomic_sro_connector_readout = panel_bridge_connector_readout,
 };
 

-- 
2.55.0


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

* [PATCH v8 39/39] fixup! drm/tidss: Implement readout support
  2026-11-06 13:14 [PATCH v8 00/39] drm: Implement state readout support Maxime Ripard
                   ` (37 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH v8 38/39] drm/panel: Implement state readout support Maxime Ripard
@ 2026-11-06 13:14 ` Maxime Ripard
  2026-11-06 13:57   ` sashiko-bot
  38 siblings, 1 reply; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:14 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

This has not been tested, but if it works should be squashed.

Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
 drivers/gpu/drm/tidss/tidss_dispc.c |   5 ++
 drivers/gpu/drm/tidss/tidss_dispc.h |   1 +
 drivers/gpu/drm/tidss/tidss_oldi.c  | 138 ++++++++++++++++++++++++++++++++++++
 3 files changed, 144 insertions(+)

diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
index c3117745965b..023dd6135cd8 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -561,4 +561,9 @@ void tidss_disable_oldi(struct tidss_device *tidss, u32 hw_videoport)
 }
 
+u32 tidss_get_oldi_config(struct tidss_device *tidss, u32 hw_videoport)
+{
+	return dispc_vp_read(tidss->dispc, hw_videoport, DISPC_VP_DSS_OLDI_CFG);
+}
+
 /*
  * TRM gives bitfields as start:end, where start is the higher bit
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
index 159fe3b94b0a..2322abd1c181 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.h
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -107,4 +107,5 @@ int tidss_configure_oldi(struct tidss_device *tidss, u32 hw_videoport,
 			 u32 oldi_cfg);
 void tidss_disable_oldi(struct tidss_device *tidss, u32 hw_videoport);
+u32 tidss_get_oldi_config(struct tidss_device *tidss, u32 hw_videoport);
 unsigned int dispc_pclk_diff(unsigned long rate, unsigned long real_rate);
 
diff --git a/drivers/gpu/drm/tidss/tidss_oldi.c b/drivers/gpu/drm/tidss/tidss_oldi.c
index 82b059f85244..7dbfbb380205 100644
--- a/drivers/gpu/drm/tidss/tidss_oldi.c
+++ b/drivers/gpu/drm/tidss/tidss_oldi.c
@@ -6,4 +6,5 @@
  */
 
+#include <linux/bitfield.h>
 #include <linux/clk.h>
 #include <linux/of.h>
@@ -14,6 +15,10 @@
 
 #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_connector.h>
 #include <drm/drm_of.h>
+#include <drm/drm_panel.h>
 
 #include "tidss_dispc.h"
@@ -352,4 +357,133 @@ tidss_oldi_mode_valid(struct drm_bridge *bridge,
 }
 
+static u32 tidss_oldi_read_config(struct tidss_oldi *oldi)
+{
+	u32 cfg;
+
+	tidss_runtime_get(oldi->tidss);
+	cfg = tidss_get_oldi_config(oldi->tidss, oldi->parent_vp);
+	tidss_runtime_put(oldi->tidss);
+
+	return cfg;
+}
+
+static int
+tidss_oldi_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 tidss_oldi *oldi = drm_bridge_to_tidss_oldi(bridge);
+	const struct oldi_bus_format *bus_fmt = NULL;
+	unsigned int i;
+	u32 cfg, map;
+
+	cfg = tidss_oldi_read_config(oldi);
+
+	/* The transmitter is off, there is nothing to read out. */
+	if (!(cfg & OLDI_ENABLE))
+		return 0;
+
+	/*
+	 * The most significant bit of the data mapping field carries
+	 * the dual-link flag rather than the LVDS mapping: mask it out
+	 * before looking for the bus format the mapping corresponds
+	 * to.
+	 */
+	map = cfg & (OLDI_MAP & ~BIT(3));
+
+	for (i = 0; i < ARRAY_SIZE(oldi_bus_formats); i++) {
+		if (oldi_bus_formats[i].oldi_mode_reg_val << 1 != map)
+			continue;
+
+		if ((cfg & OLDI_MSB) ?
+		    oldi_bus_formats[i].data_width != 24 :
+		    oldi_bus_formats[i].data_width != 18)
+			continue;
+
+		bus_fmt = &oldi_bus_formats[i];
+		break;
+	}
+
+	if (!bus_fmt) {
+		dev_err(oldi->dev,
+			"OLDI%u: unrecognised mapping in the config register\n",
+			oldi->oldi_instance);
+		return -EINVAL;
+	}
+
+	/*
+	 * Reconstruct the bus configuration the atomic helpers
+	 * negotiate: the OLDI transmitter takes the parallel RGB
+	 * output of the video port and serialises it with the LVDS
+	 * mapping found in the config register.
+	 */
+	bridge_state->input_bus_cfg.format = bus_fmt->input_bus_fmt;
+	bridge_state->output_bus_cfg.format = bus_fmt->bus_fmt;
+
+	/*
+	 * tidss_oldi_atomic_check() forces positive edge sampling on
+	 * the input side, and the DE polarity is programmed from the
+	 * negotiated flags: OLDI_DEPOL is set when the DE is active
+	 * low.
+	 */
+	bridge_state->input_bus_cfg.flags =
+		DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
+		DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE |
+		((cfg & OLDI_DEPOL) ? DRM_BUS_FLAG_DE_LOW :
+				      DRM_BUS_FLAG_DE_HIGH);
+
+	bridge_state->output_bus_cfg.flags =
+		(cfg & OLDI_DEPOL) ? DRM_BUS_FLAG_DE_LOW :
+				     DRM_BUS_FLAG_DE_HIGH;
+
+	return 0;
+}
+
+static int
+tidss_oldi_bridge_connector_readout(struct drm_bridge *bridge,
+				    struct drm_atomic_sro_state *state,
+				    struct drm_connector_state *conn_state)
+{
+	struct tidss_oldi *oldi = drm_bridge_to_tidss_oldi(bridge);
+	struct drm_connector *connector = conn_state->connector;
+	struct drm_crtc_state *crtc_state;
+	struct drm_encoder *encoder;
+	struct drm_crtc *crtc;
+
+	/* The transmitter is off, there is nothing to read out. */
+	if (!(tidss_oldi_read_config(oldi) & OLDI_ENABLE))
+		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().
+	 * The 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 const struct drm_bridge_funcs tidss_oldi_bridge_funcs = {
 	.attach = tidss_oldi_bridge_attach,
@@ -362,4 +496,7 @@ static const struct drm_bridge_funcs tidss_oldi_bridge_funcs = {
 	.atomic_create_state = drm_atomic_helper_bridge_create_state,
 	.mode_valid = tidss_oldi_mode_valid,
+	.atomic_sro_readout_state = tidss_oldi_bridge_readout_state,
+	.atomic_sro_compare_state = drm_atomic_helper_bridge_compare_state,
+	.atomic_sro_connector_readout = tidss_oldi_bridge_connector_readout,
 };
 
@@ -622,4 +759,5 @@ int tidss_oldi_init(struct tidss_device *tidss)
 		oldi->bridge.of_node = child;
 		oldi->bridge.driver_private = oldi;
+		oldi->bridge.ops |= DRM_BRIDGE_OP_CONNECTOR_HW_READOUT;
 
 		tidss->oldis[tidss->num_oldis++] = oldi;

-- 
2.55.0


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

* Re: [PATCH v8 06/39] drm/atomic: Fix private obj error handling
  2026-11-06 13:14 ` [PATCH v8 06/39] drm/atomic: Fix private obj error handling Maxime Ripard
@ 2026-11-06 13:29   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:29 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] drm/atomic: wrong Fixes commit in drm_atomic_private_obj_init()
  fix
--

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

> drm/atomic: Fix private obj error handling
>
> 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>

[Severity: Low]
In b962a12050a3 ("drm/atomic: integrate modeset lock with private objects"),
drm_atomic_private_obj_init() returned void, took a caller-allocated state
pointer, and had no error path.  The atomic_create_state() call and its
uncleaned error return were added by 47b5ac7daa46 ("drm/atomic: Add new
atomic_create_state callback to drm_private_obj").  Should the Fixes tag be:

  Fixes: 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback to drm_private_obj")

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=6

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

* Re: [PATCH v8 14/39] drm/atomic_sro: Create kernel parameter to force or disable readout
  2026-11-06 13:14 ` [PATCH v8 14/39] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
@ 2026-11-06 13:31   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:31 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] drm/atomic_sro: missing linux/types.h include in
  drm_atomic_state_readout.h
--

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

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

This commit adds the drm.atomic_readout module parameter and
drm_atomic_sro_device_can_readout() to control KMS atomic hardware state
readout.

> diff --git a/include/drm/drm_atomic_state_readout.h b/include/drm/drm_atomic_state_readout.h
> index 5a9333a057964..955e2de9d5b1d 100644
> --- a/include/drm/drm_atomic_state_readout.h
> +++ b/include/drm/drm_atomic_state_readout.h
> @@ -15,6 +15,15 @@ struct drm_printer;
>  struct drm_private_obj;
>  struct drm_private_state;
>  
> +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,
> +};
> +
> +bool drm_atomic_sro_device_can_readout(struct drm_device *dev);

[Severity: Low]
Does include/drm/drm_atomic_state_readout.h need to include
<linux/types.h> for bool?

Without it, building with CONFIG_DRM_HEADER_TEST=y fails when compiling
drm_atomic_state_readout.hdrtest standalone.  A later patch in the series,
commit "drm/atomic_helper: Compare actual and readout states once the
commit is done", adds #include <linux/types.h>, so moving that include
into this patch would keep bisection working across the series.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=14

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

* Re: [PATCH v8 10/39] drm/fb-dma-helper: Handle framebuffers without a GEM object
  2026-11-06 13:14 ` [PATCH v8 10/39] drm/fb-dma-helper: Handle framebuffers without a GEM object Maxime Ripard
@ 2026-11-06 13:32   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:32 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] drm/fb-dma-helper: drm_WARN_ON_ONCE() in
  drm_fb_dma_get_scanout_buffer()
--

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

drm/fb-dma-helper: Handle framebuffers without a GEM object

This commit updates drm_fb_dma_get_scanout_buffer() to return -ENODEV when
a framebuffer has no backing GEM object on its first plane, such as a
framebuffer reconstructed from hardware state readout.

> diff --git a/drivers/gpu/drm/drm_fb_dma_helper.c b/drivers/gpu/drm/drm_fb_dma_helper.c
> index fd71969d2fb15..3a37253ab9f83 100644
> --- a/drivers/gpu/drm/drm_fb_dma_helper.c
> +++ b/drivers/gpu/drm/drm_fb_dma_helper.c
> @@ -179,6 +179,14 @@ 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))
>  		return -ENODEV;

[Severity: Medium]
Does calling drm_fb_dma_get_gem_obj(fb, 0) here trigger a drm_WARN_ON_ONCE()
when fb->obj[0] is NULL?

drm_fb_dma_get_gem_obj() calls drm_gem_fb_get_obj(), which warns when a
plane has no backing GEM object:

drivers/gpu/drm/drm_gem_framebuffer_helper.c:drm_gem_fb_get_obj() {
	...
	if (drm_WARN_ON_ONCE(dev, plane >= ARRAY_SIZE(fb->obj)))
		return NULL;
	else if (drm_WARN_ON_ONCE(dev, !fb->obj[plane]))
		return NULL;
	...
}

When a panic occurs while a hardware readout framebuffer is still active,
drm_fb_dma_get_scanout_buffer() will hit that warning (or a nested panic if
panic_on_warn is set).

Note that the following patch in the series, commit "drm/gem-atomic-helper:
Handle framebuffers without a GEM object", checks state->fb->obj[i] directly
in drm_gem_plane_helper_prepare_fb() to avoid this same warning. Checking
!fb->obj[0] before calling drm_fb_dma_get_gem_obj(fb, 0) would avoid it here
as well.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=10

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

* Re: [PATCH v8 04/39] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test
  2026-11-06 13:14 ` [PATCH v8 04/39] drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test Maxime Ripard
@ 2026-11-06 13:32   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:32 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] drm/tests: commit message inaccurately claims semicolon removal
  in 420-only test
--

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

drm/tests: hdmi-state-helper: Handle EDEADLK in the color format test

This commit adds the modeset lock backoff and retry pattern to
drm_test_check_hdmi_color_format() when atomic state helpers return
-EDEADLK.

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

[Severity: Low]
This isn't a bug, but this commit only modifies
drm_test_check_hdmi_color_format().  The stray semicolon at the end of
drm_test_check_hdmi_color_format_420_only() was already removed in the
earlier commit "drm/tests: hdmi-state-helper: Handle EDEADLK in the
420-only test".

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=4

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

* Re: [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc()
  2026-11-06 13:14 ` [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc() Maxime Ripard
@ 2026-11-06 13:33   ` sashiko-bot
  2026-11-06 13:55   ` Luca Ceresoli
  1 sibling, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:33 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] drm/ingenic: use-after-free in ingenic_drm_bind() from
  devm/drmm mismatch
- [Low] drm/ingenic: missing devm_drm_bridge_add() in ingenic_drm_bind()
--

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

drm/ingenic: Convert to devm_drm_bridge_alloc()

This commit converts struct ingenic_drm_bridge allocation in
ingenic_drm_bind() from drmm_encoder_alloc() to devm_drm_bridge_alloc()
paired with drmm_encoder_init().

> diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> index 63198fd594984..8cad8dcfae609 100644
> --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> @@ -1303,20 +1303,27 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
>  			drm_panel_put(panel);
>  		}
>  
> -		ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
> -					NULL, DRM_MODE_ENCODER_DPI, NULL);
> +		ib = devm_drm_bridge_alloc(dev, struct ingenic_drm_bridge,
> +					   bridge, &ingenic_drm_bridge_funcs);
>  		if (IS_ERR(ib)) {
>  			ret = PTR_ERR(ib);
> -			dev_err(dev, "Failed to init encoder: %d\n", ret);
> +			dev_err(dev, "Failed to allocate bridge: %d\n", ret);
>  			goto err_drvdata;
>  		}
>  
>  		encoder = &ib->encoder;
> +
> +		ret = drmm_encoder_init(drm, encoder, NULL,
> +					DRM_MODE_ENCODER_DPI, NULL);
> +		if (ret) {
> +			dev_err(dev, "Failed to init encoder: %d\n", ret);
> +			goto err_drvdata;
> +		}

[Severity: High]
Can mixing devm_drm_bridge_alloc() and drmm_encoder_init() on the same
struct ingenic_drm_bridge cause a use-after-free of ib->encoder during
teardown?

ingenic_drm_bind() allocates drm via devm_drm_dev_alloc() before calling
devm_drm_bridge_alloc(), so devres runs drm_bridge_put_void(&ib->bridge)
before devm_drm_dev_init_release() runs drm_managed_release().

On unbind, drm_bridge_put_void() drops the devm reference on ib->bridge,
leaving only the reference taken by drm_bridge_attach().  When
drmm_encoder_alloc_release() then calls drm_encoder_cleanup(&ib->encoder),
detaching ib->bridge drops its last reference and __drm_bridge_free() frees
the entire struct ingenic_drm_bridge container while drm_encoder_cleanup()
is still accessing encoder:

drivers/gpu/drm/drm_encoder.c:drm_encoder_cleanup() {
	...
	list_for_each_entry_safe(bridge, next, &tmplist, chain_node)
		drm_bridge_detach(bridge);

	drm_mode_object_unregister(dev, &encoder->base);
	kfree(encoder->name);
	list_del(&encoder->head);
	dev->mode_config.num_encoder--;
	mutex_destroy(&encoder->bridge_chain_mutex);

	memset(encoder, 0, sizeof(*encoder));
}

If drm_bridge_attach() fails earlier during bind (for example with
-EPROBE_DEFER), drm_bridge_put_void() frees ib before drm_managed_release()
even enters drm_encoder_cleanup().

> +
>  		encoder->possible_crtcs = drm_crtc_mask(&priv->crtc);
>  
>  		drm_encoder_helper_add(encoder, &ingenic_drm_encoder_helper_funcs);
>  
> -		ib->bridge.funcs = &ingenic_drm_bridge_funcs;
>  		ib->next_bridge = bridge;
>  
>  		ret = drm_bridge_attach(encoder, &ib->bridge, NULL,

[Severity: Low]
Does attaching &ib->bridge without calling devm_drm_bridge_add() first
trigger a warning in drm_bridge_attach()?

Previously drmm_encoder_alloc() zero-initialized ib->bridge.list, so
list_empty(&bridge->list) was false.  __devm_drm_bridge_alloc() initializes
bridge->list with INIT_LIST_HEAD(), causing drm_bridge_attach() to hit
DRM_WARN("Missing drm_bridge_add() before attach\n") on every probe.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=1

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

* Re: [PATCH v8 18/39] drm/bridge: Handle bridges with hardware state readout
  2026-11-06 13:14 ` [PATCH v8 18/39] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
@ 2026-11-06 13:33   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:33 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] drm/bridge: missing bridge detach on init failure in
  drm_bridge_attach()
--

commit 0fb1d1b23ec990c1a040f81e5f14e9c95302ae67
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
providing a private state readout callback and a dedicated
drm_private_state_funcs table for bridges implementing
atomic_sro_readout_state.

> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> index b6cdbc172f689..c0224dcc8aa50 100644
> --- a/drivers/gpu/drm/drm_bridge.c
> +++ b/drivers/gpu/drm/drm_bridge.c

[ ... ]

> @@ -677,8 +760,17 @@ 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, bridge->name,
> -				    &drm_bridge_priv_state_funcs);
> +	if (bridge->funcs &&
> +	    bridge->funcs->atomic_sro_readout_state)
> +		ret = drm_atomic_private_obj_init(bridge->dev, &bridge->base,
> +						  bridge->name,
> +						  &drm_bridge_priv_state_funcs_with_sro);
> +	else
> +		ret = drm_atomic_private_obj_init(bridge->dev, &bridge->base,
> +						  bridge->name,
> +						  &drm_bridge_priv_state_funcs);
> +	if (ret)
> +		goto err_reset_bridge;
>  
>  	return 0;

[Severity: Medium]
Does jumping to err_reset_bridge when drm_atomic_private_obj_init() fails
leave bridge->funcs->attach() un-undone?

bridge->funcs->attach() has already succeeded at this point, and
err_reset_bridge unlinks bridge->chain_node and clears bridge->dev and
bridge->encoder without calling bridge->funcs->detach(bridge):

drivers/gpu/drm/drm_bridge.c:drm_bridge_attach() {
    ...
	if (bridge->funcs->attach) {
		ret = bridge->funcs->attach(bridge, encoder, flags);
		if (ret < 0)
			goto err_reset_bridge;
	}
    ...
err_reset_bridge:
	bridge->dev = NULL;
	bridge->encoder = NULL;
	mutex_lock(&encoder->bridge_chain_mutex);
	list_del(&bridge->chain_node);
	mutex_unlock(&encoder->bridge_chain_mutex);
    ...
}

Because the bridge is removed from encoder->bridge_chain, subsequent
drm_encoder_cleanup() will not detach it either, leaking or leaving
dangling whatever bridge->funcs->attach() registered (such as connectors,
DP AUX channels, or downstream bridges).

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=18

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

* Re: [PATCH v8 19/39] drm/mode-config: Read out hardware state in drm_mode_config_create_state
  2026-11-06 13:14 ` [PATCH v8 19/39] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
@ 2026-11-06 13:33   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:33 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] drm/mode-config: NULL deref of mode_config.funcs in
  drm_mode_config_create_initial_state()
- [Low] drm/mode-config: commit message function name mismatch and
  unused header
--

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

> drm/mode-config: Read out hardware state in drm_mode_config_create_state
>
> 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 c1e993a78702e..9f242e2654c82 100644
> --- a/drivers/gpu/drm/drm_mode_config.c
> +++ b/drivers/gpu/drm/drm_mode_config.c
> @@ -24,6 +24,7 @@
>  #include <linux/uaccess.h>
>  
>  #include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_state_readout.h>

[Severity: Low]
This isn't a bug, but the commit subject and message refer to
drm_mode_config_create_state(), whereas the patch renames the fallback
allocator to a static drm_mode_config_create_state() helper and calls
atomic_sro_readout_state under modeset locks in the exported
drm_mode_config_create_initial_state(). Also, nothing in this file uses
<drm/drm_atomic_state_readout.h>.

[ ... ]

> @@ -408,6 +396,58 @@ int drm_mode_config_create_initial_state(struct drm_device *dev)
>  
>  	return 0;
>  }
> +

[ ... ]

> +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;
> +	}

[Severity: High]
Can this cause a NULL pointer dereference on dev->mode_config.funcs?

Previously, drm_mode_config_create_initial_state() did not dereference
dev->mode_config.funcs, and some drivers call it before setting that
pointer:

drivers/gpu/drm/sun4i/sun4i_framebuffer.c:sun4i_framebuffer_init() {
	...
	drm_WARN(drm, drm_mode_config_create_initial_state(drm),
		 "Failed to create state\n");

	drm->mode_config.max_width = 8192;
	drm->mode_config.max_height = 8192;
	drm->mode_config.normalize_zpos = true;

	drm->mode_config.funcs = &sun4i_de_mode_config_funcs;
	...
}

When sun4i (or shmob_drm_modeset_init()) probes, dev->mode_config.funcs is
still NULL here, causing an immediate NULL pointer dereference.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=19

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

* Re: [PATCH v8 16/39] drm/atomic_sro: Add function to install state into drm objects
  2026-11-06 13:14 ` [PATCH v8 16/39] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
@ 2026-11-06 13:33   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:33 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] drm/atomic_sro: bad private_objs loop bound in
  drm_atomic_sro_install_state()
- [Medium] drm/atomic_sro: premature state destruction in
  drm_atomic_sro_install_state()
--

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

drm/atomic_sro: Add function to install state into drm objects

This commit adds drm_atomic_sro_install_state() to install readout states
into DRM objects and introduces the optional atomic_sro_install_state hook
for planes, CRTCs, connectors, and private objects.

> diff --git a/drivers/gpu/drm/drm_atomic_state_readout.c b/drivers/gpu/drm/drm_atomic_state_readout.c
> index 07ddc315b13d9..de0f27186bea9 100644
> --- a/drivers/gpu/drm/drm_atomic_state_readout.c
> +++ b/drivers/gpu/drm/drm_atomic_state_readout.c

[ ... ]

> @@ -581,3 +582,145 @@ void drm_atomic_sro_set_private_obj_state(struct drm_atomic_sro_state *state,
>  		       obj_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);
> +	}

[Severity: Medium]
Does destroying conn->state (and crtc->state, plane->state, and obj->state
in the loops below) before checking whether the new state pointer in the
SRO container is non-NULL leave the object with a NULL state pointer?

If state->connectors[i].state is NULL, the old state has already been
destroyed and nulled when the loop continues, leaving the KMS object
without a state and causing NULL pointer dereferences in later
atomic_duplicate_state() calls.  It also leaves the object's state pointer
NULL while atomic_sro_install_state() runs.

[ ... ]

> +	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;
> +}

[Severity: Medium]
Should this loop bound be state->num_private_objs instead of
count_private_obj(state->dev)?

drm_atomic_sro_state_init() allocates state->private_objs for the count at
allocation time, and drm_atomic_sro_set_private_obj_state() populates up to
state->num_private_objs entries.  Bounding the loop by
count_private_obj(state->dev) re-walks dev->mode_config.privobj_list on
every iteration, scans unpopulated slots, and reads past the end of
state->private_objs if a private object was added after
drm_atomic_sro_state_alloc().  In addition, zeroing state->num_private_objs
at the end prevents drm_atomic_sro_state_free() from freeing any
obj_state skipped by an early continue.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=16

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

* Re: [PATCH v8 07/39] drm/atomic: Add drm_private_obj name
  2026-11-06 13:14 ` [PATCH v8 07/39] drm/atomic: Add drm_private_obj name Maxime Ripard
@ 2026-11-06 13:35   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:35 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] drm/atomic: leak and uninitialized state if
  drm_atomic_private_obj_init() fails
--

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

drm/atomic: Add drm_private_obj name

This commit adds a human-readable name field to struct drm_private_obj,
initialized via drm_atomic_private_obj_init() and updated via
drm_atomic_private_obj_set_name(), and updates callers to provide a name.

[ ... ]

> diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c
> index d29ad4116e641..5f30f82b2ebb2 100644
> --- a/drivers/gpu/drm/display/drm_dp_tunnel.c
> +++ b/drivers/gpu/drm/display/drm_dp_tunnel.c
> @@ -389,6 +389,14 @@ lookup_or_alloc_group(struct drm_dp_tunnel_mgr *mgr, u8 drv_group_id)
>  		 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));
>  
> +	/*
> +	 * The private object was named after the group index when it
> +	 * was initialized, since the group identifier wasn't known
> +	 * yet. Refresh it now that we have one. The name is only used
> +	 * for debug messages, so ignore any allocation failure.
> +	 */
> +	drm_atomic_private_obj_set_name(&group->base, group->name);
> +
>  	return group;
>  }
>  
> @@ -1727,11 +1735,22 @@ EXPORT_SYMBOL(drm_dp_tunnel_atomic_get_new_state);
>  
>  static bool init_group(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_tunnel_group *group)
>  {
> +	char name[32];
> +
>  	group->mgr = mgr;
>  	group->available_bw = -1;
>  	INIT_LIST_HEAD(&group->tunnels);
>  
> -	drm_atomic_private_obj_init(mgr->dev, &group->base,
> +	/*
> +	 * The group identifier, and thus the group name, is only known
> +	 * once a tunnel has been added to the group. Name the private
> +	 * object after the group index until then,
> +	 * lookup_or_alloc_group() will refresh it.
> +	 */
> +	snprintf(name, sizeof(name), "dp-tunnel-group-%d",
> +		 (int)(group - mgr->groups));
> +
> +	drm_atomic_private_obj_init(mgr->dev, &group->base, name,
>  				    &tunnel_group_funcs);
>  
>  	return true;
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index a1f18fc96b4ca..63647dd3204f6 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -1048,6 +1048,7 @@ static void drm_atomic_plane_print_state(struct drm_printer *p,
>   * drm_atomic_private_obj_init - initialize private object
>   * @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
>   *
> @@ -1059,6 +1060,7 @@ 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)
>  {
>  	struct drm_private_state *state;
> @@ -1077,6 +1079,12 @@ 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);
>  
>  	state = obj->funcs->atomic_create_state(obj);
> @@ -1091,6 +1099,9 @@ 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;
>  }
> @@ -1118,6 +1129,7 @@ drm_atomic_private_obj_fini(struct drm_private_obj *obj)
>  
>  	list_del_init(&obj->head);
>  	obj->funcs->atomic_destroy_state(obj, obj->state);
> +	kfree(obj->name);
>  	drm_modeset_lock_fini(&obj->lock);
>  }
>  EXPORT_SYMBOL(drm_atomic_private_obj_fini);
> @@ -1151,6 +1163,37 @@ static int drm_atomic_commit_set_private_obj_state(struct drm_atomic_commit *com
>  	return 0;
>  }
>  
> +/**
> + * drm_atomic_private_obj_set_name - update the name of a private object
> + * @obj: private object
> + * @name: new human-readable name for debug messages
> + *
> + * Replaces the name of @obj previously set at
> + * drm_atomic_private_obj_init() time. This is meant for callers that
> + * only get a meaningful name well after the object has been
> + * initialized.
> + *
> + * RETURNS:
> + * Zero on success, a negative error code on allocation failure. The
> + * name is only used for debug messages, so callers can safely ignore
> + * the return value.
> + */
> +int drm_atomic_private_obj_set_name(struct drm_private_obj *obj,
> +				    const char *name)
> +{
> +	char *new_name;
> +
> +	new_name = kstrdup(name, GFP_KERNEL);
> +	if (!new_name)
> +		return -ENOMEM;
> +
> +	kfree(obj->name);
> +	obj->name = new_name;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_atomic_private_obj_set_name);

[Severity: Medium]
Can this leak obj->name or dereference a NULL obj->state when
drm_atomic_private_obj_init() fails in init_group()?

init_group() ignores the return value of drm_atomic_private_obj_init() and
returns true.  If kstrdup() or atomic_create_state() fails, group->base is
left with an empty obj->head and obj->state set to NULL.  When a tunnel is
later added to the group, lookup_or_alloc_group() calls
drm_atomic_private_obj_set_name(), which allocates a new obj->name even
though obj->head is empty, and cleanup_group() ->
drm_atomic_private_obj_fini() returns early on list_empty(&obj->head)
without freeing obj->name.  Duplicating the group's atomic state in
tunnel_group_duplicate_state() will also dereference the NULL obj->state.

Also, drm_atomic_private_obj_fini() does not reset obj->name to NULL after
kfree(obj->name), leaving a dangling pointer if the object is inspected or
renamed after finalization.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=7

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

* Re: [PATCH v8 02/39] drm/bridge-helper: Don't unlock the connection mutex after a backoff
  2026-11-06 13:14 ` [PATCH v8 02/39] drm/bridge-helper: Don't unlock the connection mutex after a backoff Maxime Ripard
@ 2026-11-06 13:39   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:39 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] drm/bridge-helper: connection_mutex dropped in
  drm_bridge_helper_reset_crtc()
- [Low] drm/bridge-helper: inaccurate backoff description in commit
  message
--

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

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

This commit stops drm_bridge_helper_reset_crtc() from unlocking
connection_mutex on exit, re-acquires connection_mutex after
drm_atomic_get_connector_for_encoder(), and validates the connector's
best_encoder and crtc before resetting the CRTC.

> 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.

[Severity: Low]
Is this description of how connection_mutex gets dropped accurate?

Neither drm_atomic_get_connector_for_encoder() nor
drm_atomic_helper_reset_crtc() calls drm_modeset_backoff() on -EDEADLK;
drm_modeset_lock() only records ctx->contended and returns -EDEADLK for
the caller to back off.  Instead, as the new code comment notes,
drm_atomic_get_connector_for_encoder() unconditionally calls
drm_modeset_unlock(&dev->mode_config.connection_mutex) on its normal exit
path, which is why connection_mutex was already unlocked at the old
goto out label and why it must be re-acquired before inspecting
connector->state.

> diff --git a/drivers/gpu/drm/drm_bridge_helper.c b/drivers/gpu/drm/drm_bridge_helper.c
> index 0a3c8fee66b32..2efa4e1bc4b71 100644
> --- a/drivers/gpu/drm/drm_bridge_helper.c
> +++ b/drivers/gpu/drm/drm_bridge_helper.c
[ ... ]
> @@ -33,26 +37,45 @@ 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() drops the connection
> +	 * mutex on its way out, whether or not it was held through @ctx,
> +	 * so we need to 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;
>  
> -	if (!connector->state) {
> -		ret = -EINVAL;
> -		goto out;
> -	}
> +	/*
> +	 * A concurrent atomic commit may have run while the connection
> +	 * mutex was released above, and re-routed the connector to
> +	 * another encoder or CRTC. Make sure the connector is still
> +	 * tied to our encoder before resetting anything.
> +	 */
> +	if (connector->state->best_encoder != encoder)
> +		return -EINVAL;

[Severity: Medium]
Why lock connection_mutex before drm_atomic_get_connector_for_encoder()
if that helper immediately unlocks it on exit?

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_modeset_unlock(&dev->mode_config.connection_mutex);

	return out_connector;
}

Because drm_atomic_get_connector_for_encoder() drops connection_mutex
unconditionally, the first drm_modeset_lock() in
drm_bridge_helper_reset_crtc() is immediately undone.  If connection_mutex
was already held in ctx on entry (for example, slow-locked via
drm_modeset_backoff() after the second drm_modeset_lock() returned
-EDEADLK), drm_atomic_get_connector_for_encoder() drops that slow-locked
mutex and removes it from ctx->locked, so the second drm_modeset_lock()
acquires it again on the normal path and can hit -EDEADLK again.

Dropping and re-acquiring connection_mutex also opens a window where a
concurrent atomic commit can move encoder to a different connector,
causing the connector->state->best_encoder != encoder check to fail with
-EINVAL even though encoder is still active on the new connector.  If
drm_atomic_get_connector_for_encoder() leaves connection_mutex locked in
ctx for the caller to drop via drm_modeset_drop_locks(), connection_mutex
stays held across the lookup without a second lock acquisition or race
window.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=2

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

* Re: [PATCH v8 28/39] drm/tests: Add tests for the atomic state readout
  2026-11-06 13:14 ` [PATCH v8 28/39] drm/tests: Add tests for the atomic state readout Maxime Ripard
@ 2026-11-06 13:44   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:44 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] drm/tests: missing -EDEADLK backoff in atomic readout KUnit
  tests
- [Low] drm/tests: unchecked init calls in mock_device_create()
--

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

drm/tests: Add tests for the atomic state readout

This commit adds KUnit test suites for the DRM atomic state readout
infrastructure, covering state containers, readout and state comparison
helpers, readout modes, 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..f2bc6d52c5b22
> --- /dev/null
> +++ b/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c

[ ... ]

> +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)
> +{

[ ... ]

> +	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;
> +}

[Severity: Low]
Should mock_device_create() assert the return values of
drm_atomic_private_obj_init() and kunit_add_action_or_reset() with
KUNIT_ASSERT_EQ(test, ret, 0)?  If either fails, priv->private_obj is left
off drm->mode_config.privobj_list (or torn down with a dangling state
pointer), so drm_atomic_sro_state_alloc() allocates state->private_objs as
ZERO_SIZE_PTR and drm_atomic_sro_set_private_obj_state() dereferences it.

[ ... ]

> +static void drm_test_sro_readout_no_modeset(struct kunit *test)
> +{

[ ... ]

> +	/* 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;
> +}
> +
> +/*
> + * Test that blanking a pipeline isn't reported as a mismatch. A DPMS
> + * off commit only clears drm_crtc_state.active and leaves the
> + * connector bound to its CRTC, while the hardware readout of a
> + * disabled pipeline reports an unbound connector: the two states
> + * differ, but there is nothing to compare for a pipeline that doesn't
> + * drive a picture anymore.
> + */
> +static void drm_test_sro_readout_and_compare_blanked(struct kunit *test)
> +{
> +	struct drm_modeset_acquire_ctx ctx;
> +	struct drm_connector_state *conn_state;
> +	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, with the pipeline enabled */
> +	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);
> +
> +	conn_state = drm_atomic_get_connector_state(commit, &priv->connector);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);

[Severity: Medium]
Can drm_atomic_get_connector_state() return ERR_PTR(-EDEADLK) here when
CONFIG_DEBUG_WW_MUTEX_SLOWPATH is enabled?

drm_atomic_get_crtc_state() acquires priv->crtc->mutex as the first lock in
ctx, and drm_atomic_get_connector_state() then acquires connection_mutex as
the second lock, where ww_mutex_deadlock_injection() can inject -EDEADLK.
Without a backoff retry loop, KUNIT_ASSERT_NOT_ERR_OR_NULL(test,
conn_state) aborts the test before drm_modeset_drop_locks() and
drm_modeset_acquire_fini() run, leaving priv->crtc->mutex locked during
device teardown.

In drm_test_sro_readout_no_modeset() above, after drm_modeset_backoff(&ctx)
locks the contended lock, drm_atomic_get_crtc_state() becomes the second
lock acquisition in ctx and can likewise return ERR_PTR(-EDEADLK), and the
retry label also reallocates commit and mode on each backoff iteration even
though backoff clears commit with drm_atomic_commit_clear().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=28

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

* Re: [PATCH v8 25/39] drm/atomic-state-helper: Provide atomic_compare_state helpers
  2026-11-06 13:14 ` [PATCH v8 25/39] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
@ 2026-11-06 13:44   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:44 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] drm/atomic: false plane mismatches in
  drm_atomic_helper_plane_compare_state()
- [Low] drm/atomic: missing crtc return in
  drm_atomic_helper_connector_compare_state()
--

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

drm/atomic-state-helper: Provide atomic_compare_state helpers

This commit adds default atomic_sro_compare_state helper implementations
for planes, CRTCs, connectors, and bridges.

> diff --git a/drivers/gpu/drm/drm_atomic_state_readout_helper.c b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
> index 4156f3a27f049..57376c9d4014b 100644
> --- a/drivers/gpu/drm/drm_atomic_state_readout_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
[ ... ]
> @@ -788,6 +789,311 @@ 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)
> +{
> +	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;
> +	}

[Severity: Medium]
Does drm_atomic_helper_plane_compare_state() report false crtc and fb
mismatches when a plane is clipped off-screen or its CRTC is set to DPMS
off?

During a DPMS off commit (enable = true, active = false) or when a plane
is clipped off-screen (visible = false), expected->crtc and expected->fb
remain non-NULL in the committed state, while the hardware plane is
disabled so hardware readout produces a pristine state with actual->crtc
and actual->fb set to NULL.  Unlike the connector, bridge, and CRTC paths,
neither drm_atomic_sro_readout_and_compare() nor
drm_atomic_helper_plane_compare_state() checks whether the plane is
visible or bound to an active CRTC before comparing crtc and fb, causing
drm_atomic_sro_readout_and_compare() to log false mismatches and 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);
> +
> +	/*
> +	 * Both unbound, the routing and the negotiated properties are
> +	 * meaningless: a disabled connector keeps the values it was
> +	 * last enabled with, while a state read out from a disabled
> +	 * pipeline is pristine.
> +	 */
> +	if (!expected->crtc && !actual->crtc)
> +		return ret;
> +
> +	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);

[Severity: Low]
Should drm_atomic_helper_connector_compare_state() return false right
after checking crtc when one of expected->crtc or actual->crtc is NULL?
Continuing when one state is unbound compares negotiated properties
against stale or pristine fields and logs spurious mismatches, unlike
drm_atomic_helper_plane_compare_state() and
drm_atomic_helper_crtc_compare_state().

Also, only tv.select_subconnector and tv.subconnector are compared here,
leaving the remaining struct drm_tv_connector_state fields (margins,
legacy_mode, mode, brightness, contrast, flicker_reduction, overscan,
saturation, and hue) unchecked.

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

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

* Re: [PATCH v8 20/39] drm/atomic_sro: Provide helpers to implement hardware state readout
  2026-11-06 13:14 ` [PATCH v8 20/39] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
@ 2026-11-06 13:44   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:44 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [High] drm/atomic_sro: missing drm_crtc_vblank_reset() in CRTC state
  readout
--

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

drm/atomic_sro: Provide helpers to implement hardware state readout

This commit adds helper functions to read out KMS object states from
hardware and install them as the initial atomic 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..1b6cf304702c2
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_atomic_state_readout_helper.c
[ ... ]
> +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)) {
> +		drm_warn(dev, "Couldn't read out the hardware state: %pe\n",
> +			 state);
> +		return PTR_ERR(state);
> +	}
> +
> +	/* TODO: Add colorop state readout support */
> +	drm_for_each_colorop(colorop, dev) {
> +		struct drm_colorop_state *colorop_state;
> +
> +		/*
> +		 * The colorop states are already initialized when the
> +		 * colorops are created. Only create a pristine state for
> +		 * the ones that don't have one, like
> +		 * drm_mode_config_create_state() does.
> +		 */
> +		if (colorop->state)
> +			continue;
> +
> +		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);

[Severity: High]
Does bypassing drm_mode_config_create_state() when readout succeeds leave
disabled CRTCs without a drm_crtc_vblank_reset() call?

On the non-readout path, drm_mode_config_crtc_create_state() resets each
CRTC's vblank state when drm_dev_has_vblank(crtc->dev) is true:

drivers/gpu/drm/drm_mode_config.c:drm_mode_config_crtc_create_state() {
    ...
	if (drm_dev_has_vblank(crtc->dev))
		drm_crtc_vblank_reset(crtc);
}

Because crtc_funcs->atomic_create_state() is side-effect-free and readout
skips drm_mode_config_create_state(), any CRTC that is read out as
inactive (or that falls back to a pristine state when drm.atomic_readout
skips missing readout hooks) is left with vblank->inmodeset == 0 and
vblank->refcount == 0 after drm_vblank_init().  If userspace issues
DRM_IOCTL_WAIT_VBLANK on that disabled CRTC before a modeset,
drm_vblank_get() transitions refcount from 0 to 1 and invokes
crtc->funcs->enable_vblank() on disabled hardware instead of returning
-EINVAL, and any call to drm_crtc_set_max_vblank_count() before
drm_crtc_vblank_on() will hit drm_WARN_ON(dev, !READ_ONCE(vblank->inmodeset)).

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=20

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

* Re: [PATCH v8 38/39] drm/panel: Implement state readout support
  2026-11-06 13:14 ` [PATCH v8 38/39] drm/panel: Implement state readout support Maxime Ripard
@ 2026-11-06 13:45   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:45 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] drm/panel: active readout in panel_bridge_connector_readout()
  desyncs drm_panel
- [Medium] drm/panel: panel_bridge_connector_readout() returns -ENODEV
  on inactive CRTC
--

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

drm/panel: Implement state readout support

This commit implements atomic state readout callbacks for the panel bridge,
reconstructing the negotiated bus configuration and binding the connector
to the encoder's active CRTC when no earlier bridge provides connector
readout.

> diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
> index 560ea57591bd7..bef0e6af73ba5 100644
> --- a/drivers/gpu/drm/drm_panel.c
> +++ b/drivers/gpu/drm/drm_panel.c

[ ... ]

> @@ -226,6 +252,100 @@ static void panel_bridge_debugfs_init(struct drm_bridge *bridge,
>  		panel->funcs->debugfs_init(panel, root);
>  }
>  

[ ... ]

> +/*
> + * A panel bridge has no way to read the panel state out of the
> + * hardware: assume the panel is enabled whenever its pipeline is.
> + */
> +static int
> +panel_bridge_connector_readout(struct drm_bridge *bridge,
> +			       struct drm_atomic_sro_state *state,
> +			       struct drm_connector_state *conn_state)
> +{
> +	struct drm_connector *connector = conn_state->connector;
> +	struct drm_crtc_state *crtc_state;
> +	struct drm_encoder *encoder;
> +	struct drm_crtc *crtc;
> +
> +	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;

[Severity: Medium]
Should panel_bridge_connector_readout() return 0 instead of -ENODEV when
crtc or crtc_state is NULL?

In include/drm/drm_encoder.h, atomic_sro_get_current_crtc() is documented
to return NULL when the encoder is disabled.  In addition, during a
commit-scoped readout in drm_atomic_sro_readout_and_compare() that touches
a disabled connector without including its CRTC in the commit,
drm_atomic_sro_get_crtc_state() returns NULL.  Returning -ENODEV in these
cases causes drm_atomic_helper_sro_build_state() to fail readout when the
pipeline is off.

> +
> +	/* The pipeline is off, there is nothing to read out. */
> +	if (!crtc_state->active)
> +		return 0;
> +
> +	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().
> +	 * The readout bypasses that path, so take the reference
> +	 * explicitly.
> +	 */
> +	drm_connector_get(connector);
> +	conn_state->crtc = crtc;
> +	conn_state->best_encoder = encoder;
> +
> +	return 0;
> +}

[Severity: High]
When a pipeline is read out as active, does binding conn_state to the CRTC
without preparing or enabling the underlying struct drm_panel leave
panel->prepared and panel->enabled out of sync with the atomic state?

Nothing in the state installation path calls drm_panel_prepare() or
drm_panel_enable().  On a subsequent fastboot commit where
drm_atomic_crtc_needs_modeset() is false, panel_bridge_atomic_pre_enable()
and panel_bridge_atomic_enable() are skipped, so the panel's regulators,
clocks, backlight and followers are never enabled in the kernel.  When a
later commit disables the pipeline, panel_bridge_atomic_disable() and
panel_bridge_atomic_post_disable() call drm_panel_disable() and
drm_panel_unprepare(), which hit the !panel->enabled and !panel->prepared
guards, log a warning and skip hardware teardown.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=38

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

* Re: [PATCH v8 34/39] drm/tidss: Implement hw_reset
  2026-11-06 13:14 ` [PATCH v8 34/39] drm/tidss: Implement hw_reset Maxime Ripard
@ 2026-11-06 13:50   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:50 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] drm/tidss: dispc_reset_hw() wipes initial config and skips
  irq_mask setup
- [Medium] drm/tidss: unchecked tidss_runtime_get() in dispc_reset_hw()
--

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

drm/tidss: Implement hw_reset

This commit moves the DISPC hardware soft reset out of dispc_init() into a
drm_mode_config_funcs.hw_reset callback and reorders tidss_probe() so that
drm_mode_config_create_initial_state() runs before tidss_irq_install().

> diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
> index b5fb296fddccc..8720403af9ed3 100644
> --- a/drivers/gpu/drm/tidss/tidss_dispc.c
> +++ b/drivers/gpu/drm/tidss/tidss_dispc.c
> @@ -2931,45 +2931,23 @@ 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;
> +	struct tidss_device *tidss = dispc->tidss;
>  	int ret;
>  
> -	ret = pm_runtime_set_active(dev);
> -	if (ret) {
> -		dev_err(dev, "Failed to set DSS PM to active\n");
> -		return ret;
> -	}
> -
> -	ret = clk_prepare_enable(dispc->fclk);
> -	if (ret) {
> -		dev_err(dev, "Failed to enable DSS fclk\n");
> -		goto err_runtime_suspend;
> -	}
> +	/*
> +	 * The reset runs from drm_mode_config_create_initial_state(), so
> +	 * after the driver has enabled runtime PM. As
> +	 * pm_runtime_set_active() is only allowed while runtime PM is
> +	 * disabled, rely on the regular runtime PM references to power
> +	 * the device up for the reset instead.
> +	 */
> +	tidss_runtime_get(tidss);
>  
>  	ret = dispc_softreset(dispc);
> -	if (ret)
> -		goto err_clk_disable;
>  
> -	clk_disable_unprepare(dispc->fclk);
> -	ret = pm_runtime_set_suspended(dev);
> -	if (ret) {
> -		dev_err(dev, "Failed to set DSS PM to suspended\n");
> -		return ret;
> -	}
> -
> -	return 0;
> -
> -err_clk_disable:
> -	clk_disable_unprepare(dispc->fclk);
> -
> -err_runtime_suspend:
> -	ret = pm_runtime_set_suspended(dev);
> -	if (ret) {
> -		dev_err(dev, "Failed to set DSS PM to suspended\n");
> -		return ret;
> -	}
> +	tidss_runtime_put(tidss);
>  
>  	return ret;
>  }

[Severity: Medium]
What happens if tidss_runtime_get() fails here?

tidss_runtime_get() wraps pm_runtime_resume_and_get(), which drops its usage
reference on error and leaves the power domain and dispc->fclk disabled.
Continuing on failure accesses unclocked DISPC registers in
dispc_softreset() and then underflows the runtime PM usage count in
tidss_runtime_put().

[ ... ]

> diff --git a/drivers/gpu/drm/tidss/tidss_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c
> index e258f656cf03d..409d599bb2ffd 100644
> --- a/drivers/gpu/drm/tidss/tidss_drv.c
> +++ b/drivers/gpu/drm/tidss/tidss_drv.c
> @@ -151,6 +151,20 @@ static int tidss_probe(struct platform_device *pdev)
>  		goto err_runtime_suspend;
>  	}
>  
> +	drm_kms_helper_poll_init(ddev);
> +
> +	/*
> +	 * drm_mode_config_create_initial_state() resets the DISPC when
> +	 * the state readout isn't possible, which also clears the
> +	 * interrupt enable registers. Install the interrupts only once
> +	 * that's been done, so that the reset can't wipe them.
> +	 */
> +	ret = drm_mode_config_create_initial_state(ddev);
> +	if (ret) {
> +		dev_err(dev, "failed to create initial state: %d\n", ret);
> +		goto err_runtime_suspend;
> +	}
> +
>  	irq = platform_get_irq(pdev, 0);
>  	if (irq < 0) {
>  		ret = irq;
> @@ -164,14 +178,6 @@ static int tidss_probe(struct platform_device *pdev)
>  		goto err_runtime_suspend;
>  	}
>  
> -	drm_kms_helper_poll_init(ddev);
> -
> -	ret = drm_mode_config_create_initial_state(ddev);
> -	if (ret) {
> -		dev_err(dev, "failed to create initial state: %d\n", ret);
> -		goto err_irq_uninstall;
> -	}
> -
>  	ret = drm_dev_register(ddev, 0);

[Severity: High]
Does running dispc_reset_hw() via runtime PM and before tidss_irq_install()
wipe the initial DISPC configuration and leave tidss->irq_mask unprogrammed
in hardware?

drivers/gpu/drm/tidss/tidss_dispc.c:dispc_runtime_resume() {
    ...
	dispc_initial_config(dispc);

	dispc->is_enabled = true;

	tidss_irq_resume(dispc->tidss);
    ...
}

Previously, dispc_init_hw() ran before pm_runtime_enable() using
pm_runtime_set_active() and clk_prepare_enable() so that the soft reset
completed before dispc_runtime_resume() ever ran (as noted in
bc288a927815 ("drm/tidss: Fix dss reset")).  Now, tidss_runtime_get() in
dispc_reset_hw() triggers dispc_runtime_resume() first, and
dispc_softreset() immediately resets all DISPC registers back to their
defaults, wiping the plane thresholds, preload values, gamma shadow bits,
and J721E DISPC_CONNECTIONS programmed by dispc_initial_config().

In addition, tidss_irq_install() only populates tidss->irq_mask in memory
and relies on tidss_irq_resume() during dispc_runtime_resume() to program
the hardware IRQ enable registers.  Because dispc_runtime_resume() now runs
during drm_mode_config_create_initial_state() while tidss->irq_mask is still
0, and tidss_runtime_put() uses a 1000 ms autosuspend delay (or never
suspends when CONFIG_PM is disabled), the device stays runtime-active across
tidss_irq_install() and the initial modeset in drm_client_setup(), leaving
the hardware unconfigured and frame-done/underflow interrupts disabled in
hardware.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=34

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

* Re: [PATCH v8 30/39] drm/bridge: display-connector: Implement readout support
  2026-11-06 13:14 ` [PATCH v8 30/39] drm/bridge: display-connector: Implement readout support Maxime Ripard
@ 2026-11-06 13:50   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:50 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] drm/bridge: unreachable fallback in
  display_connector_readout_state()
--

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

drm/bridge: display-connector: Implement readout support

This commit adds state readout support to the display-connector bridge by
implementing the atomic_sro_readout_state callback and wiring up
drm_atomic_helper_bridge_compare_state().

> diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
> index eb3412ec30a07..781684da1da4c 100644
> --- a/drivers/gpu/drm/bridge/display-connector.c
> +++ b/drivers/gpu/drm/bridge/display-connector.c
[ ... ]
> @@ -213,6 +216,70 @@ static u32 *display_connector_get_input_bus_fmts(struct drm_bridge *bridge,
[ ... ]
> +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;

[Severity: Medium]
Can this fallback ever be reached when prev_bridge is present but lacks
readout support?

When a bridge does not implement atomic_sro_readout_state,
drm_atomic_helper_sro_readout_bridge_state() still allocates a zeroed
pristine state and stores it in the SRO state container:

drivers/gpu/drm/drm_atomic_state_readout_helper.c:drm_atomic_helper_sro_readout_bridge_state() {
	if (!bridge_obj_funcs->atomic_sro_readout_state) {
		...
		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);

		drm_atomic_sro_set_private_obj_state(state, bridge_obj,
						     bridge_obj_state);
		return 0;
	}
}

Because prev_priv_state is non-NULL, display_connector_readout_state()
copies the zeroed output_bus_cfg.format and output_bus_cfg.flags from that
pristine state instead of falling back to MEDIA_BUS_FMT_FIXED and
di->bus_flags, causing drm_atomic_helper_bridge_compare_state() to report
false input bus mismatches when drm.atomic_readout skips missing readout
implementations.  Checking prev_bridge->funcs->atomic_sro_readout_state
before using prev_priv_state would avoid this.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=30

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

* Re: [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc()
  2026-11-06 13:14 ` [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc() Maxime Ripard
  2026-11-06 13:33   ` sashiko-bot
@ 2026-11-06 13:55   ` Luca Ceresoli
  2026-11-06 13:59     ` Maxime Ripard
  1 sibling, 1 reply; 60+ messages in thread
From: Luca Ceresoli @ 2026-11-06 13:55 UTC (permalink / raw)
  To: Maxime Ripard, 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, Paul Cercueil, Luca Ceresoli

Hi Maxime,

On Fri Nov 6, 2026 at 2:14 PM CET, Maxime Ripard wrote:
> The ingenic driver allocates a structure holding both the DPI encoder
> and the source bridge of its pipeline, and allocates it as a whole
> through drmm_encoder_alloc(). The bridge is thus not known to
> devm_drm_bridge_alloc(), which drm_bridge_attach() already warns
> about.
>
> The bridge private object also gets its name from the devm bridge
> allocation, and drm_bridge_attach() will soon report a failure of
> that initialization instead of ignoring it, so a bridge allocated any
> other way would prevent the driver from probing.
>
> Allocate the structure through devm_drm_bridge_alloc(), making the
> bridge the primary object of the allocation, and initialise the
> embedded encoder in place with drmm_encoder_init(). Both objects
> stay in the same structure, so the container_of() conversions keep
> working, and the bridge funcs assignment moves into the allocator
> call.
>
> Signed-off-by: Maxime Ripard <mripard@kernel.org>

Seems there are now 3 competing patches to fix the same issue! :)
This, [0] and [1].

[0] https://lore.kernel.org/all/4f30341735d77c77a9a4952abd91879007bc00fd.1787604223.git.hns@goldelico.com/
[1] https://lore.kernel.org/r/20261105203517.2293404-1-f.fainelli@gmail.com

However yours does not add a call to [devm_]drm_bridge_add(), so I'd rather
prefer applying [1]. Also [1] removes the encoder pointer as it is not
really necessary.

Luca

--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

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

* Re: [PATCH v8 39/39] fixup! drm/tidss: Implement readout support
  2026-11-06 13:14 ` [PATCH v8 39/39] fixup! drm/tidss: Implement " Maxime Ripard
@ 2026-11-06 13:57   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 13:57 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] drm/tidss: oldi: bus flags mismatch in
  tidss_oldi_bridge_readout_state()
- [Low] drm/tidss: oldi: unsquashed fixup commit and unused header
  includes
--

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

fixup! drm/tidss: Implement readout support

This commit adds atomic state and connector hardware readout callbacks for
the TI DSS OLDI bridge.

> diff --git a/drivers/gpu/drm/tidss/tidss_oldi.c b/drivers/gpu/drm/tidss/tidss_oldi.c
> index 82b059f852449..7dbfbb380205e 100644
> --- a/drivers/gpu/drm/tidss/tidss_oldi.c
> +++ b/drivers/gpu/drm/tidss/tidss_oldi.c
> @@ -5,6 +5,7 @@
>   * Aradhya Bhatia <a-bhatia1@ti.com>
>   */
>  
> +#include <linux/bitfield.h>
>  #include <linux/clk.h>
>  #include <linux/of.h>
>  #include <linux/of_graph.h>
> @@ -13,8 +14,12 @@
>  #include <linux/regmap.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_connector.h>
>  #include <drm/drm_of.h>
> +#include <drm/drm_panel.h>

[Severity: Low]
This isn't a bug, but <linux/bitfield.h> and <drm/drm_panel.h> are not used
in tidss_oldi.c.  Also, this commit is still formatted as an unsquashed
"fixup!" patch with a placeholder commit message instead of being squashed
into commit "drm/tidss: Implement readout support" or given a full
changelog.

[ ... ]

> @@ -351,6 +356,135 @@ tidss_oldi_mode_valid(struct drm_bridge *bridge,
>  	return 0;
>  }

[ ... ]

> +static int
> +tidss_oldi_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)
> +{

[ ... ]

> +	/*
> +	 * tidss_oldi_atomic_check() forces positive edge sampling on
> +	 * the input side, and the DE polarity is programmed from the
> +	 * negotiated flags: OLDI_DEPOL is set when the DE is active
> +	 * low.
> +	 */
> +	bridge_state->input_bus_cfg.flags =
> +		DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
> +		DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE |
> +		((cfg & OLDI_DEPOL) ? DRM_BUS_FLAG_DE_LOW :
> +				      DRM_BUS_FLAG_DE_HIGH);
> +
> +	bridge_state->output_bus_cfg.flags =
> +		(cfg & OLDI_DEPOL) ? DRM_BUS_FLAG_DE_LOW :
> +				     DRM_BUS_FLAG_DE_HIGH;
> +
> +	return 0;
> +}

[Severity: Medium]
Will input_bus_cfg.flags and output_bus_cfg.flags mismatch the committed
state during drm_atomic_helper_bridge_compare_state() when the downstream
panel or bridge sets other bus flags or omits DE polarity?

During atomic check, drm_atomic_bridge_propagate_bus_flags() copies the
downstream bridge's input_bus_cfg.flags (populated by panel_bridge from
connector->display_info.bus_flags) into both output_bus_cfg.flags and
input_bus_cfg.flags, and tidss_oldi_atomic_check() only adjusts the sampling
edge flags on input_bus_cfg.flags:

drivers/gpu/drm/tidss/tidss_oldi.c:tidss_oldi_atomic_check() {
	bridge_state->input_bus_cfg.flags &=
		~(DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
		  DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE);

	bridge_state->input_bus_cfg.flags |=
		DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
		DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE;
}

For LVDS panels driven by panel-lvds (which sets
DRM_BUS_FLAG_DATA_MSB_TO_LSB or DRM_BUS_FLAG_DATA_LSB_TO_MSB in
display_info.bus_flags) or panels that leave both DE polarity flags unset,
the committed bridge state retains those flags in input_bus_cfg.flags and
output_bus_cfg.flags, whereas tidss_oldi_bridge_readout_state() reconstructs
only DRM_BUS_FLAG_DE_LOW or DRM_BUS_FLAG_DE_HIGH from OLDI_DEPOL.  Every
blocking atomic modeset on those panels will therefore report a bridge state
readout mismatch.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=39

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

* Re: [PATCH v8 01/39] drm/ingenic: Convert to devm_drm_bridge_alloc()
  2026-11-06 13:55   ` Luca Ceresoli
@ 2026-11-06 13:59     ` Maxime Ripard
  0 siblings, 0 replies; 60+ messages in thread
From: Maxime Ripard @ 2026-11-06 13:59 UTC (permalink / raw)
  To: Luca Ceresoli
  Cc: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Jyri Sarha, Tomi Valkeinen,
	Devarsh Thakkar, dri-devel, Paul Cercueil

[-- Attachment #1: Type: text/plain, Size: 1714 bytes --]

On Fri, Nov 06, 2026 at 02:55:47PM +0100, Luca Ceresoli wrote:
> Hi Maxime,
> 
> On Fri Nov 6, 2026 at 2:14 PM CET, Maxime Ripard wrote:
> > The ingenic driver allocates a structure holding both the DPI encoder
> > and the source bridge of its pipeline, and allocates it as a whole
> > through drmm_encoder_alloc(). The bridge is thus not known to
> > devm_drm_bridge_alloc(), which drm_bridge_attach() already warns
> > about.
> >
> > The bridge private object also gets its name from the devm bridge
> > allocation, and drm_bridge_attach() will soon report a failure of
> > that initialization instead of ignoring it, so a bridge allocated any
> > other way would prevent the driver from probing.
> >
> > Allocate the structure through devm_drm_bridge_alloc(), making the
> > bridge the primary object of the allocation, and initialise the
> > embedded encoder in place with drmm_encoder_init(). Both objects
> > stay in the same structure, so the container_of() conversions keep
> > working, and the bridge funcs assignment moves into the allocator
> > call.
> >
> > Signed-off-by: Maxime Ripard <mripard@kernel.org>
> 
> Seems there are now 3 competing patches to fix the same issue! :)
> This, [0] and [1].
> 
> [0] https://lore.kernel.org/all/4f30341735d77c77a9a4952abd91879007bc00fd.1787604223.git.hns@goldelico.com/
> [1] https://lore.kernel.org/r/20261105203517.2293404-1-f.fainelli@gmail.com

ouch, I completely missed those ptaches somehow, sorry.

> However yours does not add a call to [devm_]drm_bridge_add(), so I'd rather
> prefer applying [1]. Also [1] removes the encoder pointer as it is not
> really necessary.

Yeah, that makes total sense to me

Maxime

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 273 bytes --]

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

* Re: [PATCH v8 35/39] drm/tidss: Implement readout support
  2026-11-06 13:14 ` [PATCH v8 35/39] drm/tidss: Implement readout support Maxime Ripard
@ 2026-11-06 14:06   ` sashiko-bot
  0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-11-06 14:06 UTC (permalink / raw)
  To: Maxime Ripard; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 8 potential
issues to consider:
- [High] drm/tidss: dispc_vp_init() clears DPIENABLE and dpi_output is
  unset on readout
- [High] drm/tidss: tidss_crtc_install_state() runs before
  tidss_irq_install() in probe
- [Medium] drm/tidss: missing drm_crtc_vblank_reset() in
  tidss_crtc_install_state()
- [Medium] drm/tidss: wrong channel ID comparison in
  dispc_ovr_get_plane()
- [Medium] drm/tidss: plane readout fails in
  tidss_plane_atomic_readout_state() when CRTC is disabled
- [Medium] drm/tidss: zpos and cropping mismatches in
  tidss_plane_atomic_readout_state()
- [Medium] drm/tidss: refcount imbalance and leak from
  tidss_crtc_install_state()
- [Medium] drm/tidss: bus_flags mismatch in dispc_vp_get_bus_flags()
  readout
--

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

drm/tidss: Implement readout support

This commit implements KMS hardware state readout callbacks for the tidss
CRTC, encoder bridge, plane, and DISPC layers.

> diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
> index d2664bda53a99..38d7a5dfe4f3d 100644
> --- a/drivers/gpu/drm/tidss/tidss_crtc.c
> +++ b/drivers/gpu/drm/tidss/tidss_crtc.c
[ ... ]
> @@ -372,6 +376,108 @@ 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;

[Severity: High]
Does the first tidss_runtime_get() during boot readout disable active DPI
outputs on SoCs with has_vp_control_dpienable set (AM62L)?

During tidss_probe(), the device starts runtime-suspended, so the first
tidss_runtime_get() calls dispc_runtime_resume() -> dispc_initial_config() ->
dispc_vp_init():

drivers/gpu/drm/tidss/tidss_dispc.c:dispc_vp_init() {
	for (i = 0; i < dispc->feat->num_vps; i++) {
		VP_REG_FLD_MOD(dispc, i, DISPC_VP_CONFIG, 1,
			       DISPC_VP_CONFIG_GAMMAENABLE_MASK);

		if (dispc->feat->has_vp_control_dpienable) {
			/* Disable DPIENABLE for all VPs */
			VP_REG_FLD_MOD(dispc, i, DISPC_VP_CONTROL, 0,
				       DISPC_VP_CONTROL_DPIENABLE_MASK);
		}
	}
}

Because DISPC_VP_CONTROL_ENABLE_MASK stays set, dispc_vp_is_enabled() still
returns true and state readout succeeds without a full modeset, leaving
DISPC_VP_CONTROL_DPIENABLE_MASK cleared and the DPI display blank.

In addition, dispc->vp_data[hw_videoport].dpi_output is only populated in
dispc_vp_setup() during tidss_crtc_atomic_enable() or
tidss_crtc_atomic_flush(), so tidss_crtc_install_state() leaves it false on a
read-out CRTC and a subsequent tidss_crtc_atomic_disable() skips clearing
DISPC_VP_CONTROL_DPIENABLE_MASK in dispc_vp_unprepare().

[ ... ]

> +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;
> +
> +	/*
> +	 * The video port wasn't enabled in hardware: nothing to take
> +	 * over for it, and enabling the clock and the vblank reporting
> +	 * would leak their reference for a CRTC that no disable will
> +	 * ever release.
> +	 */
> +	if (!crtc_state->active)
> +		return;

[Severity: Medium]
Should the inactive branch call drm_crtc_vblank_reset(crtc) before returning?

When drm_mode_config_create_initial_state() succeeds via state readout, it
skips the drm_mode_config_create_state() fallback that normally calls
drm_crtc_vblank_reset().  Returning early here leaves vblank->inmodeset at 0
after drm_vblank_init(), allowing userspace vblank ioctls such as
DRM_IOCTL_WAIT_VBLANK to enable vblank interrupts on a disabled video port
instead of failing with -EINVAL.

> +
> +	tidss_runtime_get(tidss);
> +
> +	ret = dispc_vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
> +	if (ret != 0) {
> +		tidss_runtime_put(tidss);
> +		return;
> +	}

[Severity: Medium]
If dispc_vp_enable_clk() fails here, or if tidss_probe() fails after
drm_mode_config_create_initial_state(), does this cause a clock and runtime
PM refcount imbalance?

When dispc_vp_enable_clk() fails, tidss_crtc_install_state() drops the
runtime PM reference and returns without enabling vp_clk, but crtc->state is
still installed with active = true, so a later tidss_crtc_atomic_disable()
unconditionally calls drm_crtc_vblank_off(), dispc_vp_disable_clk(), and
tidss_runtime_put(), underflowing both counts.  Conversely, if tidss_probe()
fails in platform_get_irq(), tidss_irq_install(), drm_dev_register(), or
aperture_remove_all_conflicting_devices(), the error path never unwinds the
vp_clk and runtime PM references acquired here for active CRTCs.

> +
> +	/* Turn vertical blanking interrupt reporting on. */\
> +	drm_crtc_vblank_on(crtc);
> +}

[Severity: High]
Does running tidss_crtc_install_state() before tidss_irq_install() in
tidss_probe() leave hardware interrupts unprogrammed while a read-out CRTC is
active?

In tidss_probe(), drm_mode_config_create_initial_state() runs before
tidss_irq_install():

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

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		ret = irq;
		goto err_runtime_suspend;
	}
	tidss->irq = irq;

	ret = tidss_irq_install(ddev, irq);
    ...
}

When readout finds an active CRTC, tidss_crtc_install_state() keeps the
device runtime-active and calls drm_crtc_vblank_on(crtc).  tidss_irq_install()
then resets tidss->irq_mask = 0 (wiping any vblank IRQ bits set when
drm_vblank_offdelay == 0) and populates DSS_IRQ_VP_FRAME_DONE,
DSS_IRQ_VP_SYNC_LOST, and DSS_IRQ_PLANE_FIFO_UNDERFLOW in tidss->irq_mask
without writing the mask to hardware.  Because the device is already runtime
active, dispc_runtime_resume() -> tidss_irq_resume() does not run again, so
FRAME_DONE remains disabled in hardware and a subsequent
tidss_crtc_atomic_disable() times out waiting for framedone_completion.

> diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
> index 8720403af9ed3..c3117745965b7 100644
> --- a/drivers/gpu/drm/tidss/tidss_dispc.c
> +++ b/drivers/gpu/drm/tidss/tidss_dispc.c
[ ... ]
> @@ -1474,6 +1480,228 @@ int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport,
[ ... ]
> +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;
> +}

[Severity: Medium]
Will dispc_vp_get_bus_flags() cause state comparison mismatches in
tidss_crtc_compare_state() and drm_atomic_helper_bridge_compare_state() when a
panel or bridge leaves some polarity flags unset?

In tidss_bridge_atomic_check(), tcrtc_state->bus_flags is copied directly
from the next bridge or connector display_info without normalizing unset
polarity bits, and dispc_vp_prepare() defaults unset bits in hardware to
POSEDGE / DE_HIGH / NEGEDGE (for example, rocktech_rk101ii01d_ct on AM654
OLDI only sets DRM_BUS_FLAG_DE_HIGH).  Because dispc_vp_get_bus_flags()
unconditionally sets all three polarity flags, the read-out bus_flags will
not match the committed bus_flags after a blocking commit.

> +
>  /* OVR */
>  static void dispc_k2g_ovr_set_plane(struct dispc_device *dispc,
>  				    u32 hw_plane, u32 hw_videoport,
> @@ -1547,6 +1775,94 @@ void dispc_ovr_enable_layer(struct dispc_device *dispc,
[ ... ]
> +int dispc_ovr_get_plane(struct dispc_device *dispc, u32 hw_plane,
> +			u32 *hw_videoport, int *x, int *y, u32 *layer)
> +{
> +	u32 vp, l;
> +
> +	switch (dispc->feat->subrev) {
[ ... ]
> +	case DISPC_AM625:
> +	case DISPC_AM62A7:
> +	case DISPC_AM62L:
> +	case DISPC_AM65X:
> +	case DISPC_J721E:
> +		for (vp = 0; vp < dispc->feat->num_vps; vp++) {
> +			for (l = 0; l < dispc->feat->num_vids; l++) {
> +				u32 attrs = dispc_ovr_read(dispc, vp,
> +							   DISPC_OVR_ATTRIBUTES(l));
> +
> +				if (!(attrs & DISPC_OVR_ATTRIBUTES_ENABLE_MASK))
> +					continue;
> +
> +				if (FIELD_GET(DISPC_OVR_ATTRIBUTES_CHANNELIN_MASK,
> +					      attrs) != hw_plane)
> +					continue;

[Severity: Medium]
Should DISPC_OVR_ATTRIBUTES_CHANNELIN_MASK be compared against
dispc->feat->vid_info[hw_plane].hw_id instead of hw_plane?

dispc_am65x_ovr_set_plane() and dispc_j721e_ovr_set_plane() program CHANNELIN
with dispc->feat->vid_info[hw_plane].hw_id.  On DISPC_AM62L, the single plane
has software index hw_plane = 0 but vid_info[0].hw_id = 1, so
dispc_ovr_get_plane() never matches the enabled layer and returns -ENODEV,
breaking both initial state readout and post-commit readout verification.

> diff --git a/drivers/gpu/drm/tidss/tidss_plane.c b/drivers/gpu/drm/tidss/tidss_plane.c
> index b0d5d6959b885..94824cff77f9d 100644
> --- a/drivers/gpu/drm/tidss/tidss_plane.c
> +++ b/drivers/gpu/drm/tidss/tidss_plane.c
[ ... ]
> @@ -176,11 +179,213 @@ static const struct drm_plane_helper_funcs tidss_primary_plane_helper_funcs = {
[ ... ]
> +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 crtc_x, crtc_y;
> +	u32 normalized_zpos;
> +	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, &crtc_x, &crtc_y,
> +					&normalized_zpos);
> +	if (IS_ERR(crtc)) {
> +		drm_framebuffer_put(fb);
> +		ret = PTR_ERR(crtc);
> +		goto err_runtime_pm;
> +	}

[Severity: Medium]
Does plane state readout fail or report stale active planes when a CRTC is
disabled?

When a CRTC is disabled, tidss_crtc_atomic_disable() disables the OVR layers
and video port, while tidss_atomic_commit_tail() passes
DRM_PLANE_COMMIT_ACTIVE_ONLY to drm_atomic_helper_commit_planes(), skipping
tidss_plane_atomic_disable() on the newly inactive CRTC and leaving
DISPC_VID_ATTRIBUTES_ENABLE_MASK set in hardware.  When post-commit readout
runs:
- on K3 SoCs, dispc_ovr_get_plane() finds no enabled OVR layer and returns
  -ENODEV, which tidss_plane_atomic_readout_state() treats as a fatal error
  instead of an unrouted/inactive plane;
- on K2G, dispc_ovr_get_plane() unconditionally returns videoport 0 even when
  the video port is disabled, and tidss_plane_atomic_readout_state() never
  checks crtc_state->active before marking the plane visible and attaching it
  to the disabled CRTC.

> +
> +	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);
> +
> +	plane_state->crtc_x = crtc_x;
> +	plane_state->crtc_y = crtc_y;
> +
> +	/*
> +	 * The overlay layer index a plane is routed to is its
> +	 * normalized zpos, as that is what the atomic update uses as
> +	 * the layer index. The raw zpos property userspace set
> +	 * cannot be recovered from the hardware.
> +	 */
> +	plane_state->normalized_zpos = normalized_zpos;
> +	plane_state->zpos = normalized_zpos;
> +
> +	// TODO: Handle src_x/src_y, see DISPC_VID_BA_0
> +
> +	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;

[Severity: Medium]
Will using drm_atomic_helper_plane_compare_state() trigger false mismatch
warnings whenever zpos != normalized_zpos or a plane is cropped?

tidss_plane_create() initializes each plane's default zpos to its global
plane index (0, 1, 2, ...), whereas drm_atomic_normalize_zpos() normalizes
normalized_zpos per CRTC starting from 0.  Whenever plane 1 (such as the
primary plane on a second CRTC) or an overlay plane is enabled without all
lower-numbered planes on the same CRTC, expected->zpos differs from
normalized_zpos and STATE_CHECK_U32() on zpos in
drm_atomic_helper_plane_compare_state() fails.

Similarly, when a plane uses source cropping or non-zero src_x/src_y offsets,
tidss_plane_readout_fb() sets fb->width and fb->height from
DISPC_VID_PICTURE_SIZE (the cropped source dimensions) and
tidss_plane_atomic_readout_state() leaves src_x and src_y at 0, causing
drm_atomic_helper_plane_compare_state() to warn on fb->width, fb->height,
src_x, src_y, and src.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106-drm-state-readout-v8-0-cbe78624e1bc@kernel.org?part=35

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

end of thread, other threads:[~2026-11-06 14:07 UTC | newest]

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