* [PATCH v5 00/33] drm: Implement state readout support
@ 2026-09-07 13:23 Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 01/33] drm/atomic: Fix unused but set warning in state iterator macros Maxime Ripard
` (34 more replies)
0 siblings, 35 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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,
Laurent Pinchart
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 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 (33):
drm/atomic: Fix unused but set warning in state iterator macros
drm/atomic_helper: Skip over NULL private_obj pointers
drm/atomic: Convert drm_priv_to_bridge_state to container_of_const
drm/atomic: Add drm_private_obj name
drm/bridge: Add drm_private_obj_is_bridge()
drm/bridge: Implement atomic_print_state
drm/atomic: Export drm_atomic_*_print_state
drm/atomic: Only call atomic_destroy_state on a !NULL pointer
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: Pass nonblock to commit_tail
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/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: Switch to drm_mode_config_create_initial_state()
drm/tidss: Implement hw_reset
drm/tidss: Implement readout support
drm/tidss: encoder: Implement atomic_sro_get_current_crtc
drm/bridge: sii902x: Implement hw state readout
Documentation/gpu/drm-kms-helpers.rst | 26 +
drivers/gpu/drm/Makefile | 2 +
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 +
.../drm/arm/display/komeda/komeda_private_obj.c | 8 +
drivers/gpu/drm/bridge/display-connector.c | 64 ++
drivers/gpu/drm/bridge/sii902x.c | 82 +-
drivers/gpu/drm/display/drm_bridge_connector.c | 35 +
drivers/gpu/drm/display/drm_dp_mst_topology.c | 2 +-
drivers/gpu/drm/display/drm_dp_tunnel.c | 1 +
drivers/gpu/drm/drm_atomic.c | 46 +-
drivers/gpu/drm/drm_atomic_helper.c | 46 +-
drivers/gpu/drm/drm_atomic_sro.c | 932 +++++++++++++++++++++
drivers/gpu/drm/drm_atomic_sro_helper.c | 679 +++++++++++++++
drivers/gpu/drm/drm_atomic_state_helper.c | 20 +-
drivers/gpu/drm/drm_bridge.c | 132 ++-
drivers/gpu/drm/drm_internal.h | 12 +
drivers/gpu/drm/drm_mode_config.c | 55 +-
drivers/gpu/drm/drm_modeset_helper.c | 24 +
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/tidss/tidss_crtc.c | 104 +++
drivers/gpu/drm/tidss/tidss_dispc.c | 334 +++++++-
drivers/gpu/drm/tidss/tidss_dispc.h | 16 +
drivers/gpu/drm/tidss/tidss_drv.c | 6 +-
drivers/gpu/drm/tidss/tidss_encoder.c | 57 ++
drivers/gpu/drm/tidss/tidss_kms.c | 20 +-
drivers/gpu/drm/tidss/tidss_plane.c | 154 ++++
drivers/gpu/drm/vc4/vc4_kms.c | 3 +
include/drm/drm_atomic.h | 144 +++-
include/drm/drm_atomic_sro.h | 59 ++
include/drm/drm_atomic_sro_helper.h | 276 ++++++
include/drm/drm_bridge.h | 87 ++
include/drm/drm_connector.h | 69 ++
include/drm/drm_crtc.h | 69 ++
include/drm/drm_encoder.h | 18 +
include/drm/drm_mode_config.h | 50 ++
include/drm/drm_modeset_helper.h | 1 +
include/drm/drm_modeset_helper_vtables.h | 23 +
include/drm/drm_plane.h | 69 ++
43 files changed, 3644 insertions(+), 88 deletions(-)
---
base-commit: 17c6b88e85ffe5de5720004ae8c2176d52944aca
change-id: 20250730-drm-state-readout-108f089c1c30
prerequisite-change-id: 20260709-drm-no-more-crtc-reset-14a4d5bc8d41:v3
prerequisite-patch-id: 58c15cfdaac5894112d6b55152cee55444e59c2c
prerequisite-patch-id: f1d676df498396acf15592ca7ead5be61138f47a
prerequisite-patch-id: 0b4247d6f5e7d16522473260c92ad1b4a235e5d6
prerequisite-patch-id: 454462ebf2b4cc237d83aa8b3631252e158d16d0
prerequisite-patch-id: ffa6afc89be0c75a3b3565d204e998841fcd7c15
prerequisite-patch-id: c20225c4fc6139ca606ca94d44d973b22d94ad94
prerequisite-patch-id: a0b461fed6e896d3ea179caff28a00d912aee47a
prerequisite-patch-id: ae43b98d635078d5d2d76a9dee7dffba082c3dd4
prerequisite-patch-id: 91c7bc55447b4c4fc97000bc257a72d1694d8aa6
prerequisite-patch-id: 527b8258f3d80c3dafc1833732f66d7283e7bbb5
prerequisite-patch-id: 9ef306a7e4c68ac38a6ed26c0f18b02dd9e3beeb
prerequisite-patch-id: 576654f19b1d56754bdb115f673438140f242fda
prerequisite-patch-id: 166fb879fa7744d42ee56d6735cc344585a74b2a
Best regards,
--
Maxime Ripard <mripard@kernel.org>
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v5 01/33] drm/atomic: Fix unused but set warning in state iterator macros
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 02/33] drm/atomic_helper: Skip over NULL private_obj pointers Maxime Ripard
` (33 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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
A number of state iterator macros trigger a compiler warning if an
iterator parameter isn't used in the code block.
Add a similar workaround than in most other macros.
Acked-by: Simona Vetter <simona.vetter@ffwll.ch>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
include/drm/drm_atomic.h | 64 +++++++++++++++++++++++++++++++-----------------
1 file changed, 42 insertions(+), 22 deletions(-)
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 88087910ab1a..1d05d5d7162e 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -949,13 +949,15 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
for ((__i) = 0; \
(__i) < (__state)->num_connector; \
(__i)++) \
for_each_if ((__state)->connectors[__i].ptr && \
((connector) = (__state)->connectors[__i].ptr, \
- (void)(connector) /* Only to avoid unused-but-set-variable warning */, \
- (old_connector_state) = (__state)->connectors[__i].old_state, \
- (new_connector_state) = (__state)->connectors[__i].new_state, 1))
+ (void)(connector) /* Only to avoid unused-but-set-variable warning */, \
+ (old_connector_state) = (__state)->connectors[__i].old_state, \
+ (void)(old_connector_state) /* Only to avoid unused-but-set-variable warning */, \
+ (new_connector_state) = (__state)->connectors[__i].new_state, \
+ (void)(new_connector_state) /* Only to avoid unused-but-set-variable warning */, 1))
/**
* for_each_old_connector_in_state - iterate over all connectors in an atomic update
* @__state: &struct drm_atomic_commit pointer
* @connector: &struct drm_connector iteration cursor
@@ -971,12 +973,13 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
for ((__i) = 0; \
(__i) < (__state)->num_connector; \
(__i)++) \
for_each_if ((__state)->connectors[__i].ptr && \
((connector) = (__state)->connectors[__i].ptr, \
- (void)(connector) /* Only to avoid unused-but-set-variable warning */, \
- (old_connector_state) = (__state)->connectors[__i].old_state, 1))
+ (void)(connector) /* Only to avoid unused-but-set-variable warning */, \
+ (old_connector_state) = (__state)->connectors[__i].old_state, \
+ (void)(old_connector_state) /* Only to avoid unused-but-set-variable warning */, 1))
/**
* for_each_new_connector_in_state - iterate over all connectors in an atomic update
* @__state: &struct drm_atomic_commit pointer
* @connector: &struct drm_connector iteration cursor
@@ -992,13 +995,13 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
for ((__i) = 0; \
(__i) < (__state)->num_connector; \
(__i)++) \
for_each_if ((__state)->connectors[__i].ptr && \
((connector) = (__state)->connectors[__i].ptr, \
- (void)(connector) /* Only to avoid unused-but-set-variable warning */, \
- (new_connector_state) = (__state)->connectors[__i].new_state, \
- (void)(new_connector_state) /* Only to avoid unused-but-set-variable warning */, 1))
+ (void)(connector) /* Only to avoid unused-but-set-variable warning */, \
+ (new_connector_state) = (__state)->connectors[__i].new_state, \
+ (void)(new_connector_state) /* Only to avoid unused-but-set-variable warning */, 1))
/**
* for_each_oldnew_crtc_in_state - iterate over all CRTCs in an atomic update
* @__state: &struct drm_atomic_commit pointer
* @crtc: &struct drm_crtc iteration cursor
@@ -1015,14 +1018,14 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
(__i) < (__state)->dev->mode_config.num_crtc; \
(__i)++) \
for_each_if ((__state)->crtcs[__i].ptr && \
((crtc) = (__state)->crtcs[__i].ptr, \
(void)(crtc) /* Only to avoid unused-but-set-variable warning */, \
- (old_crtc_state) = (__state)->crtcs[__i].old_state, \
- (void)(old_crtc_state) /* Only to avoid unused-but-set-variable warning */, \
- (new_crtc_state) = (__state)->crtcs[__i].new_state, \
- (void)(new_crtc_state) /* Only to avoid unused-but-set-variable warning */, 1))
+ (old_crtc_state) = (__state)->crtcs[__i].old_state, \
+ (void)(old_crtc_state) /* Only to avoid unused-but-set-variable warning */, \
+ (new_crtc_state) = (__state)->crtcs[__i].new_state, \
+ (void)(new_crtc_state) /* Only to avoid unused-but-set-variable warning */, 1))
/**
* for_each_old_crtc_in_state - iterate over all CRTCs in an atomic update
* @__state: &struct drm_atomic_commit pointer
* @crtc: &struct drm_crtc iteration cursor
@@ -1038,11 +1041,12 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
(__i) < (__state)->dev->mode_config.num_crtc; \
(__i)++) \
for_each_if ((__state)->crtcs[__i].ptr && \
((crtc) = (__state)->crtcs[__i].ptr, \
(void)(crtc) /* Only to avoid unused-but-set-variable warning */, \
- (old_crtc_state) = (__state)->crtcs[__i].old_state, 1))
+ (old_crtc_state) = (__state)->crtcs[__i].old_state, \
+ (void)(old_crtc_state) /* Only to avoid unused-but-set-variable warning */, 1))
/**
* for_each_new_crtc_in_state - iterate over all CRTCs in an atomic update
* @__state: &struct drm_atomic_commit pointer
* @crtc: &struct drm_crtc iteration cursor
@@ -1124,12 +1128,14 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
(__i) < (__state)->dev->mode_config.num_total_plane; \
(__i)++) \
for_each_if ((__state)->planes[__i].ptr && \
((plane) = (__state)->planes[__i].ptr, \
(void)(plane) /* Only to avoid unused-but-set-variable warning */, \
- (old_plane_state) = (__state)->planes[__i].old_state,\
- (new_plane_state) = (__state)->planes[__i].new_state, 1))
+ (old_plane_state) = (__state)->planes[__i].old_state, \
+ (void)(old_plane_state) /* Only to avoid unused-but-set-variable warning */, \
+ (new_plane_state) = (__state)->planes[__i].new_state, \
+ (void)(new_plane_state) /* Only to avoid unused-but-set-variable warning */, 1))
/**
* for_each_oldnew_plane_in_state_reverse - iterate over all planes in an atomic
* update in reverse order
* @__state: &struct drm_atomic_commit pointer
@@ -1146,12 +1152,15 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
for ((__i) = ((__state)->dev->mode_config.num_total_plane - 1); \
(__i) >= 0; \
(__i)--) \
for_each_if ((__state)->planes[__i].ptr && \
((plane) = (__state)->planes[__i].ptr, \
- (old_plane_state) = (__state)->planes[__i].old_state,\
- (new_plane_state) = (__state)->planes[__i].new_state, 1))
+ (void)(plane) /* Only to avoid unused-but-set-variable warning */, \
+ (old_plane_state) = (__state)->planes[__i].old_state, \
+ (void)(old_plane_state) /* Only to avoid unused-but-set-variable warning */, \
+ (new_plane_state) = (__state)->planes[__i].new_state, \
+ (void)(new_plane_state) /* Only to avoid unused-but-set-variable warning */, 1))
/**
* for_each_new_plane_in_state_reverse - other than only tracking new state,
* it's the same as for_each_oldnew_plane_in_state_reverse
* @__state: &struct drm_atomic_commit pointer
@@ -1163,11 +1172,13 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
for ((__i) = ((__state)->dev->mode_config.num_total_plane - 1); \
(__i) >= 0; \
(__i)--) \
for_each_if ((__state)->planes[__i].ptr && \
((plane) = (__state)->planes[__i].ptr, \
- (new_plane_state) = (__state)->planes[__i].new_state, 1))
+ (void)(plane) /* Only to avoid unused-but-set-variable warning */, \
+ (new_plane_state) = (__state)->planes[__i].new_state, \
+ (void)(new_plane_state) /* Only to avoid unused-but-set-variable warning */, 1))
/**
* for_each_old_plane_in_state - iterate over all planes in an atomic update
* @__state: &struct drm_atomic_commit pointer
* @plane: &struct drm_plane iteration cursor
@@ -1182,11 +1193,14 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
for ((__i) = 0; \
(__i) < (__state)->dev->mode_config.num_total_plane; \
(__i)++) \
for_each_if ((__state)->planes[__i].ptr && \
((plane) = (__state)->planes[__i].ptr, \
- (old_plane_state) = (__state)->planes[__i].old_state, 1))
+ (void)(plane) /* Only to avoid unused-but-set-variable warning */, \
+ (old_plane_state) = (__state)->planes[__i].old_state, \
+ (void)(old_plane_state) /* Only to avoid unused-but-set-variable warning */, 1))
+
/**
* for_each_new_plane_in_state - iterate over all planes in an atomic update
* @__state: &struct drm_atomic_commit pointer
* @plane: &struct drm_plane iteration cursor
* @new_plane_state: &struct drm_plane_state iteration cursor for the new state
@@ -1220,12 +1234,15 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/
#define for_each_oldnew_private_obj_in_state(__state, obj, old_obj_state, new_obj_state, __i) \
for ((__i) = 0; \
(__i) < (__state)->num_private_objs && \
((obj) = (__state)->private_objs[__i].ptr, \
+ (void)(obj) /* Only to avoid unused-but-set-variable warning */, \
(old_obj_state) = (__state)->private_objs[__i].old_state, \
- (new_obj_state) = (__state)->private_objs[__i].new_state, 1); \
+ (void)(old_obj_state) /* Only to avoid unused-but-set-variable warning */, \
+ (new_obj_state) = (__state)->private_objs[__i].new_state, \
+ (void)(new_obj_state) /* Only to avoid unused-but-set-variable warning */, 1); \
(__i)++)
/**
* for_each_old_private_obj_in_state - iterate over all private objects in an atomic update
* @__state: &struct drm_atomic_commit pointer
@@ -1239,11 +1256,13 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/
#define for_each_old_private_obj_in_state(__state, obj, old_obj_state, __i) \
for ((__i) = 0; \
(__i) < (__state)->num_private_objs && \
((obj) = (__state)->private_objs[__i].ptr, \
- (old_obj_state) = (__state)->private_objs[__i].old_state, 1); \
+ (void)(obj) /* Only to avoid unused-but-set-variable warning */, \
+ (old_obj_state) = (__state)->private_objs[__i].old_state, \
+ (void)(old_obj_state) /* Only to avoid unused-but-set-variable warning */, 1); \
(__i)++)
/**
* for_each_new_private_obj_in_state - iterate over all private objects in an atomic update
* @__state: &struct drm_atomic_commit pointer
@@ -1258,11 +1277,12 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
#define for_each_new_private_obj_in_state(__state, obj, new_obj_state, __i) \
for ((__i) = 0; \
(__i) < (__state)->num_private_objs && \
((obj) = (__state)->private_objs[__i].ptr, \
(void)(obj) /* Only to avoid unused-but-set-variable warning */, \
- (new_obj_state) = (__state)->private_objs[__i].new_state, 1); \
+ (new_obj_state) = (__state)->private_objs[__i].new_state, \
+ (void)(new_obj_state) /* Only to avoid unused-but-set-variable warning */, 1); \
(__i)++)
/**
* drm_atomic_crtc_needs_modeset - compute combined modeset need
* @state: &drm_crtc_state for the CRTC
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 02/33] drm/atomic_helper: Skip over NULL private_obj pointers
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 01/33] drm/atomic: Fix unused but set warning in state iterator macros Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
` (32 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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
Just like for all other objects, drm_atomic_commit contains an array of
drm_private_state, with the number of states found in num_private_objs.
If we are to clean up a state by hand for some reason before calling
drm_atomic_commit_put(), chances are that the pointer to the affected
drm_private_obj and drm_private_states would have been cleared to avoid
any use-after-free.
However, since it's just an array, as we progress and free the items, we
can't update num_private_objs as we go since we would reduce the array
size, preventing us to remove the final elements.
And if the caller was to forget to update num_private_objs after it
iterated over the whole array, we're left with a (valid) array with a
non-zero number of NULL elements.
If we were to call drm_atomic_commit_put() at this point, chances are
that drm_atomic_commit_default_clear() would be called and it would
iterate over all those empty NULL items.
However, unlike what is found for connectors, crtcs and planes, we don't
test that our pointers are non-NULL before dereferencing them, leading
to a NULL pointer dereference.
Such callers should obviously be fixed, but there's no reason to not do
such a simple check, if only to be consistent.
Reviewed-by: Simona Vetter <simona.vetter@ffwll.ch>
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 45df6376f408..596b0de44f00 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -351,10 +351,13 @@ void drm_atomic_commit_default_clear(struct drm_atomic_commit *state)
}
for (i = 0; i < state->num_private_objs; i++) {
struct drm_private_obj *obj = state->private_objs[i].ptr;
+ if (!obj)
+ continue;
+
obj->funcs->atomic_destroy_state(obj,
state->private_objs[i].state_to_destroy);
state->private_objs[i].ptr = NULL;
state->private_objs[i].state_to_destroy = NULL;
state->private_objs[i].old_state = NULL;
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 01/33] drm/atomic: Fix unused but set warning in state iterator macros Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 02/33] drm/atomic_helper: Skip over NULL private_obj pointers Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-15 9:29 ` Javier Martinez Canillas
2026-09-15 14:14 ` (subset) " Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 04/33] drm/atomic: Add drm_private_obj name Maxime Ripard
` (31 subsequent siblings)
34 siblings, 2 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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
container_of_const is more flexible than container_of when
it comes to mixing pointers constness. Switch to it.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
include/drm/drm_atomic.h | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 1d05d5d7162e..def18b60a106 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -1379,15 +1379,12 @@ struct drm_bridge_state {
* @output_bus_cfg: output bus configuration
*/
struct drm_bus_cfg output_bus_cfg;
};
-static inline struct drm_bridge_state *
-drm_priv_to_bridge_state(struct drm_private_state *priv)
-{
- return container_of(priv, struct drm_bridge_state, base);
-}
+#define drm_priv_to_bridge_state(priv) \
+ container_of_const(priv, struct drm_bridge_state, base)
struct drm_bridge_state *
drm_atomic_get_bridge_state(struct drm_atomic_commit *state,
struct drm_bridge *bridge);
struct drm_bridge_state *
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 04/33] drm/atomic: Add drm_private_obj name
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (2 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-15 14:13 ` Laurent Pinchart
2026-09-07 13:23 ` [PATCH v5 05/33] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
` (30 subsequent siblings)
34 siblings, 1 reply; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 +
drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c | 8 ++++++++
drivers/gpu/drm/display/drm_dp_mst_topology.c | 2 +-
drivers/gpu/drm/display/drm_dp_tunnel.c | 1 +
drivers/gpu/drm/drm_atomic.c | 4 ++++
drivers/gpu/drm/drm_bridge.c | 3 ++-
drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 1 +
drivers/gpu/drm/ingenic/ingenic-ipu.c | 1 +
drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 1 +
drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c | 1 +
drivers/gpu/drm/omapdrm/omap_drv.c | 1 +
drivers/gpu/drm/tegra/hub.c | 1 +
drivers/gpu/drm/vc4/vc4_kms.c | 3 +++
include/drm/drm_atomic.h | 7 ++++++-
include/drm/drm_bridge.h | 2 ++
15 files changed, 34 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 a9d3ce8b173a..1d481c9437d7 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -2249,10 +2249,11 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev)
/* indicates support for immediate flip */
adev_to_drm(adev)->mode_config.async_page_flip = true;
drm_atomic_private_obj_init(adev_to_drm(adev),
&adev->dm.atomic_obj,
+ "amdgpu_dc_state",
&dm_atomic_state_funcs);
r = amdgpu_display_modeset_create_props(adev);
if (r)
return r;
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
@@ -64,10 +64,11 @@ static const struct drm_private_state_funcs komeda_layer_obj_funcs = {
static int komeda_layer_obj_add(struct komeda_kms_dev *kms,
struct komeda_layer *layer)
{
drm_atomic_private_obj_init(&kms->base, &layer->base.obj,
+ "komeda_layer",
&komeda_layer_obj_funcs);
return 0;
}
static struct drm_private_state *
@@ -117,10 +118,11 @@ static const struct drm_private_state_funcs komeda_scaler_obj_funcs = {
static int komeda_scaler_obj_add(struct komeda_kms_dev *kms,
struct komeda_scaler *scaler)
{
drm_atomic_private_obj_init(&kms->base,
&scaler->base.obj,
+ "komeda_scaler",
&komeda_scaler_obj_funcs);
return 0;
}
static struct drm_private_state *
@@ -169,10 +171,11 @@ static const struct drm_private_state_funcs komeda_compiz_obj_funcs = {
static int komeda_compiz_obj_add(struct komeda_kms_dev *kms,
struct komeda_compiz *compiz)
{
drm_atomic_private_obj_init(&kms->base, &compiz->base.obj,
+ "komeda_compiz",
&komeda_compiz_obj_funcs);
return 0;
}
@@ -223,10 +226,11 @@ static const struct drm_private_state_funcs komeda_splitter_obj_funcs = {
static int komeda_splitter_obj_add(struct komeda_kms_dev *kms,
struct komeda_splitter *splitter)
{
drm_atomic_private_obj_init(&kms->base,
&splitter->base.obj,
+ "komeda_splitter",
&komeda_splitter_obj_funcs);
return 0;
}
@@ -276,10 +280,11 @@ static const struct drm_private_state_funcs komeda_merger_obj_funcs = {
static int komeda_merger_obj_add(struct komeda_kms_dev *kms,
struct komeda_merger *merger)
{
drm_atomic_private_obj_init(&kms->base,
&merger->base.obj,
+ "komeda_merger",
&komeda_merger_obj_funcs);
return 0;
}
@@ -329,10 +334,11 @@ static const struct drm_private_state_funcs komeda_improc_obj_funcs = {
static int komeda_improc_obj_add(struct komeda_kms_dev *kms,
struct komeda_improc *improc)
{
drm_atomic_private_obj_init(&kms->base, &improc->base.obj,
+ "komeda_improc",
&komeda_improc_obj_funcs);
return 0;
}
@@ -382,10 +388,11 @@ static const struct drm_private_state_funcs komeda_timing_ctrlr_obj_funcs = {
static int komeda_timing_ctrlr_obj_add(struct komeda_kms_dev *kms,
struct komeda_timing_ctrlr *ctrlr)
{
drm_atomic_private_obj_init(&kms->base, &ctrlr->base.obj,
+ "komeda_timing_ctrlr",
&komeda_timing_ctrlr_obj_funcs);
return 0;
}
@@ -436,10 +443,11 @@ static const struct drm_private_state_funcs komeda_pipeline_obj_funcs = {
static int komeda_pipeline_obj_add(struct komeda_kms_dev *kms,
struct komeda_pipeline *pipe)
{
drm_atomic_private_obj_init(&kms->base, &pipe->obj,
+ "komeda_pipeline",
&komeda_pipeline_obj_funcs);
return 0;
}
diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c
index 7ce9e212770a..d3ed80d9fce3 100644
--- a/drivers/gpu/drm/display/drm_dp_mst_topology.c
+++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c
@@ -5766,11 +5766,11 @@ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr,
mgr->aux = aux;
mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes;
mgr->max_payloads = max_payloads;
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);
return 0;
}
EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init);
diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c
index d29ad4116e64..391d643793eb 100644
--- a/drivers/gpu/drm/display/drm_dp_tunnel.c
+++ b/drivers/gpu/drm/display/drm_dp_tunnel.c
@@ -1730,10 +1730,11 @@ static bool init_group(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_tunnel_group
group->mgr = mgr;
group->available_bw = -1;
INIT_LIST_HEAD(&group->tunnels);
drm_atomic_private_obj_init(mgr->dev, &group->base,
+ group->name,
&tunnel_group_funcs);
return true;
}
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 596b0de44f00..04da72b4aa70 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1006,10 +1006,11 @@ 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
*
* Initialize the private object, which can be embedded into any
* driver private object that needs its own atomic state.
@@ -1017,18 +1018,20 @@ static void drm_atomic_plane_print_state(struct drm_printer *p,
* RETURNS:
* Zero on success, error code on failure
*/
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;
memset(obj, 0, sizeof(*obj));
drm_modeset_lock_init(&obj->lock);
obj->dev = dev;
+ obj->name = kstrdup(name, GFP_KERNEL);
obj->funcs = funcs;
list_add_tail(&obj->head, &dev->mode_config.privobj_list);
state = obj->funcs->atomic_create_state(obj);
if (IS_ERR(state))
@@ -1049,10 +1052,11 @@ EXPORT_SYMBOL(drm_atomic_private_obj_init);
void
drm_atomic_private_obj_fini(struct drm_private_obj *obj)
{
list_del(&obj->head);
obj->funcs->atomic_destroy_state(obj, obj->state);
+ kfree(obj->name);
drm_modeset_lock_fini(&obj->lock);
}
EXPORT_SYMBOL(drm_atomic_private_obj_fini);
/**
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 83f1809a5d37..27bb9963c234 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -419,10 +419,11 @@ void *__devm_drm_bridge_alloc(struct device *dev, size_t size, size_t offset,
bridge = container + offset;
INIT_LIST_HEAD(&bridge->list);
bridge->container = container;
bridge->funcs = funcs;
+ bridge->name = devm_kasprintf(dev, GFP_KERNEL, "bridge-%s", dev_name(dev));
kref_init(&bridge->refcount);
err = devm_add_action_or_reset(dev, drm_bridge_put_void, bridge);
if (err)
return ERR_PTR(err);
@@ -622,11 +623,11 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
ret = bridge->funcs->attach(bridge, encoder, flags);
if (ret < 0)
goto err_reset_bridge;
}
- drm_atomic_private_obj_init(bridge->dev, &bridge->base,
+ drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
&drm_bridge_priv_state_funcs);
return 0;
err_reset_bridge:
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
index 625ec76cf04d..05ee0ccb121d 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
@@ -1400,10 +1400,11 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
dev_err(dev, "Unable to register clock notifier\n");
goto err_devclk_disable;
}
drm_atomic_private_obj_init(drm, &priv->private_obj,
+ "ingenic_drm",
&ingenic_drm_private_state_funcs);
ret = drmm_add_action_or_reset(drm, ingenic_drm_atomic_private_obj_fini,
&priv->private_obj);
if (ret)
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
@@ -900,10 +900,11 @@ static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
dev_err(dev, "Unable to prepare clock\n");
return err;
}
drm_atomic_private_obj_init(drm, &ipu->private_obj,
+ "ingenic_ipu_state",
&ingenic_ipu_private_state_funcs);
return 0;
}
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
@@ -1153,10 +1153,11 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
dev->mode_config.cursor_width = 512;
dev->mode_config.cursor_height = 512;
drm_atomic_private_obj_init(dpu_kms->dev, &dpu_kms->global_state,
+ "dpu_kms_global_state",
&dpu_kms_global_state_funcs);
atomic_set(&dpu_kms->bandwidth_ref, 0);
rc = pm_runtime_resume_and_get(&dpu_kms->pdev->dev);
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
@@ -710,10 +710,11 @@ static int mdp5_init(struct platform_device *pdev, struct drm_device *dev)
int ret;
mdp5_kms->dev = dev;
drm_atomic_private_obj_init(mdp5_kms->dev, &mdp5_kms->glob_state,
+ "mdp5_global_state",
&mdp5_global_state_funcs);
/* we need to set a default rate before enabling. Set a safe
* rate first, then figure out hw revision, and then set a
* more optimal rate:
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index 92d6a1f9c0a1..9738acbf6fae 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -298,10 +298,11 @@ static const struct drm_private_state_funcs omap_global_state_funcs = {
static int omap_global_obj_init(struct drm_device *dev)
{
struct omap_drm_private *priv = dev->dev_private;
drm_atomic_private_obj_init(dev, &priv->glob_obj,
+ "omap_global",
&omap_global_state_funcs);
return 0;
}
static void omap_global_obj_fini(struct omap_drm_private *priv)
diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c
index bd442bfd4540..b38fd353a648 100644
--- a/drivers/gpu/drm/tegra/hub.c
+++ b/drivers/gpu/drm/tegra/hub.c
@@ -956,10 +956,11 @@ static int tegra_display_hub_init(struct host1x_client *client)
struct tegra_display_hub *hub = to_tegra_display_hub(client);
struct drm_device *drm = dev_get_drvdata(client->host);
struct tegra_drm *tegra = drm->dev_private;
drm_atomic_private_obj_init(drm, &hub->base,
+ "tegra_display_hub",
&tegra_display_hub_state_funcs);
tegra->hub = hub;
return 0;
diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c
index b17e73bce384..5f64cf1fcc9e 100644
--- a/drivers/gpu/drm/vc4/vc4_kms.c
+++ b/drivers/gpu/drm/vc4/vc4_kms.c
@@ -115,10 +115,11 @@ static void vc4_ctm_obj_fini(struct drm_device *dev, void *unused)
static int vc4_ctm_obj_init(struct vc4_dev *vc4)
{
drm_modeset_lock_init(&vc4->ctm_state_lock);
drm_atomic_private_obj_init(&vc4->base, &vc4->ctm_manager,
+ "vc4_ctm",
&vc4_ctm_state_funcs);
return drmm_add_action_or_reset(&vc4->base, vc4_ctm_obj_fini, NULL);
}
@@ -755,10 +756,11 @@ static void vc4_load_tracker_obj_fini(struct drm_device *dev, void *unused)
}
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);
return drmm_add_action_or_reset(&vc4->base, vc4_load_tracker_obj_fini, NULL);
}
@@ -846,10 +848,11 @@ static void vc4_hvs_channels_obj_fini(struct drm_device *dev, void *unused)
}
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);
return drmm_add_action_or_reset(&vc4->base, vc4_hvs_channels_obj_fini, NULL);
}
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index def18b60a106..47b2fe2b039e 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -357,10 +357,15 @@ struct drm_private_obj {
/**
* @dev: parent DRM device
*/
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
* (queued to &drm_mode_config.privobj_list).
*/
struct list_head head;
@@ -738,11 +743,11 @@ drm_atomic_get_new_colorop_state(struct drm_atomic_commit *state,
struct drm_connector_state * __must_check
drm_atomic_get_connector_state(struct drm_atomic_commit *state,
struct drm_connector *connector);
int drm_atomic_private_obj_init(struct drm_device *dev,
- struct drm_private_obj *obj,
+ struct drm_private_obj *obj, const char *name,
const struct drm_private_state_funcs *funcs);
void drm_atomic_private_obj_fini(struct drm_private_obj *obj);
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 58fff047f43b..389dcab3c3f3 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -994,10 +994,12 @@ enum drm_bridge_ops {
struct drm_bridge {
/** @base: inherit from &drm_private_object */
struct drm_private_obj base;
/** @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;
/** @chain_node: used to form a bridge chain */
struct list_head chain_node;
/** @of_node: device node pointer to the bridge */
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 05/33] drm/bridge: Add drm_private_obj_is_bridge()
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (3 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 04/33] drm/atomic: Add drm_private_obj name Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 06/33] drm/bridge: Implement atomic_print_state Maxime Ripard
` (29 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
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 27bb9963c234..186a4c14faef 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -554,10 +554,25 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
.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,
};
+/**
+ * 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->funcs && 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
*
* @encoder: DRM encoder
* @bridge: bridge to attach
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 389dcab3c3f3..a6b0ad25eea8 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1183,10 +1183,12 @@ static inline struct drm_bridge *
drm_priv_to_bridge(struct drm_private_obj *priv)
{
return container_of(priv, struct drm_bridge, base);
}
+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);
void drm_bridge_unplug(struct drm_bridge *bridge);
struct drm_bridge *drm_bridge_get(struct drm_bridge *bridge);
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 06/33] drm/bridge: Implement atomic_print_state
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (4 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 05/33] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-15 9:26 ` Geert Uytterhoeven
2026-09-07 13:23 ` [PATCH v5 07/33] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
` (28 subsequent siblings)
34 siblings, 1 reply; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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
Bridges have some fields in their state worth printing, but we don't
provide an atomic_print_state implementation to show those fields.
Provide one.
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_bridge.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 186a4c14faef..177f018dd3ff 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -548,14 +548,35 @@ drm_bridge_atomic_create_priv_state(struct drm_private_obj *obj)
return ERR_CAST(state);
return &state->base;
}
+static void
+drm_bridge_atomic_print_priv_state(struct drm_printer *p,
+ const struct drm_private_state *s)
+{
+ const struct drm_bridge_state *state = drm_priv_to_bridge_state(s);
+ struct drm_bridge *bridge = drm_priv_to_bridge(s->obj);
+
+ if (bridge->of_node)
+ drm_printf(p, "bridge: %ps (%pOFfc)\n", bridge->funcs, bridge->of_node);
+ else
+ drm_printf(p, "bridge: %ps\n", bridge->funcs);
+
+ drm_printf_indent(p, 1, "input bus configuration:");
+ drm_printf_indent(p, 2, "code: %04x", state->input_bus_cfg.format);
+ drm_printf_indent(p, 2, "flags: %08x", state->input_bus_cfg.flags);
+ drm_printf_indent(p, 1, "output bus configuration:");
+ drm_printf_indent(p, 2, "code: %04x", state->output_bus_cfg.format);
+ drm_printf_indent(p, 2, "flags: %08x", state->output_bus_cfg.flags);
+}
+
static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
.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
* @obj: private object to check
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 07/33] drm/atomic: Export drm_atomic_*_print_state
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (5 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 06/33] drm/bridge: Implement atomic_print_state Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 08/33] drm/atomic: Only call atomic_destroy_state on a !NULL pointer Maxime Ripard
` (27 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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_sro.c to
print the readout state for debugging purposes. Make them available
by dropping the static qualifier and declaring them in drm_internal.h.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic.c | 16 ++++++++--------
drivers/gpu/drm/drm_internal.h | 12 ++++++++++++
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 04da72b4aa70..c497d550c6c4 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -538,12 +538,12 @@ static int drm_atomic_crtc_check(const struct drm_crtc_state *old_crtc_state,
}
return 0;
}
-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;
drm_printf(p, "crtc[%u]: %s\n", crtc->base.id, crtc->name);
drm_printf_indent(p, 1, "enable=%d\n", state->enable);
@@ -937,12 +937,12 @@ static void drm_atomic_colorop_print_state(struct drm_printer *p,
}
drm_printf_indent(p, 1, "next=%d\n", colorop->next ? colorop->next->base.id : 0);
}
-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;
struct drm_rect src = drm_plane_state_src(state);
struct drm_rect dest = drm_plane_state_dest(state);
@@ -1430,12 +1430,12 @@ drm_atomic_get_connector_state(struct drm_atomic_commit *state,
return connector_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;
drm_printf(p, "connector[%u]: %s\n", connector->base.id, connector->name);
drm_printf_indent(p, 1, "crtc=%s\n", state->crtc ? state->crtc->name : "(null)");
@@ -2076,12 +2076,12 @@ int __drm_atomic_helper_set_config(struct drm_mode_set *set,
return 0;
}
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;
if (obj->funcs->atomic_print_state)
obj->funcs->atomic_print_state(p, state);
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
index f893b1e3a596..91ce23f69ec4 100644
--- a/drivers/gpu/drm/drm_internal.h
+++ b/drivers/gpu/drm/drm_internal.h
@@ -38,18 +38,30 @@
struct dentry;
struct dma_buf;
struct iosys_map;
struct drm_connector;
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)
void drm_client_debugfs_init(struct drm_device *dev);
#else
static inline void drm_client_debugfs_init(struct drm_device *dev)
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 08/33] drm/atomic: Only call atomic_destroy_state on a !NULL pointer
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (6 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 07/33] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 09/33] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
` (26 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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
The drm_atomic_commit structure is freed through the
drm_atomic_commit_put() function, that eventually calls
drm_atomic_commit_default_clear() by default when there's no active
users of that state.
It then iterates over all objects with a state, and will call the
atomic_destroy_state callback on the state pointer. The state pointer is
mostly used these days to point to which of the old or new state needs
to be freed, depending on whether the state was committed or not.
So it all makes sense.
However, with the hardware state readout support approaching, we might
have a state, with multiple objects in it, but no state to free because
we want them to persist. In such a case, the state pointer is going to
be NULL, and thus we'll end up with NULL pointer dereference.
Test if the state pointer is non-NULL before calling
atomic_destroy_state on it.
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic.c | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index c497d550c6c4..223f2d617179 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -291,12 +291,14 @@ void drm_atomic_commit_default_clear(struct drm_atomic_commit *state)
struct drm_connector *connector = state->connectors[i].ptr;
if (!connector)
continue;
- connector->funcs->atomic_destroy_state(connector,
- state->connectors[i].state_to_destroy);
+ if (state->connectors[i].state_to_destroy)
+ connector->funcs->atomic_destroy_state(connector,
+ state->connectors[i].state_to_destroy);
+
state->connectors[i].ptr = NULL;
state->connectors[i].state_to_destroy = NULL;
state->connectors[i].old_state = NULL;
state->connectors[i].new_state = NULL;
drm_connector_put(connector);
@@ -306,12 +308,13 @@ void drm_atomic_commit_default_clear(struct drm_atomic_commit *state)
struct drm_crtc *crtc = state->crtcs[i].ptr;
if (!crtc)
continue;
- crtc->funcs->atomic_destroy_state(crtc,
- state->crtcs[i].state_to_destroy);
+ if (state->crtcs[i].state_to_destroy)
+ crtc->funcs->atomic_destroy_state(crtc,
+ state->crtcs[i].state_to_destroy);
state->crtcs[i].ptr = NULL;
state->crtcs[i].state_to_destroy = NULL;
state->crtcs[i].old_state = NULL;
state->crtcs[i].new_state = NULL;
@@ -326,12 +329,14 @@ void drm_atomic_commit_default_clear(struct drm_atomic_commit *state)
struct drm_plane *plane = state->planes[i].ptr;
if (!plane)
continue;
- plane->funcs->atomic_destroy_state(plane,
- state->planes[i].state_to_destroy);
+ if (state->planes[i].state_to_destroy)
+ plane->funcs->atomic_destroy_state(plane,
+ state->planes[i].state_to_destroy);
+
state->planes[i].ptr = NULL;
state->planes[i].state_to_destroy = NULL;
state->planes[i].old_state = NULL;
state->planes[i].new_state = NULL;
}
@@ -354,12 +359,14 @@ void drm_atomic_commit_default_clear(struct drm_atomic_commit *state)
struct drm_private_obj *obj = state->private_objs[i].ptr;
if (!obj)
continue;
- obj->funcs->atomic_destroy_state(obj,
- state->private_objs[i].state_to_destroy);
+ if (state->private_objs[i].state_to_destroy)
+ obj->funcs->atomic_destroy_state(obj,
+ state->private_objs[i].state_to_destroy);
+
state->private_objs[i].ptr = NULL;
state->private_objs[i].state_to_destroy = NULL;
state->private_objs[i].old_state = NULL;
state->private_objs[i].new_state = NULL;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 09/33] drm/atomic_sro: Create drm_atomic_sro_state container
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (7 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 08/33] drm/atomic: Only call atomic_destroy_state on a !NULL pointer Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 10/33] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
` (25 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/drm_atomic_sro.c | 454 +++++++++++++++++++++++++++++++++++++++
include/drm/drm_atomic_sro.h | 51 +++++
3 files changed, 506 insertions(+)
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index e635fcffd379..e59a3d9af3e4 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -32,10 +32,11 @@ endif
# Enable -Werror in CI and development
subdir-ccflags-$(CONFIG_DRM_WERROR) += -Werror
drm-y := \
drm_atomic.o \
+ drm_atomic_sro.o \
drm_atomic_uapi.o \
drm_auth.o \
drm_blend.o \
drm_bridge.o \
drm_cache.o \
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
new file mode 100644
index 000000000000..096fdb708819
--- /dev/null
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_sro.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 "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;
+ struct __drm_atomic_sro_private_obj *private_objs;
+ unsigned int num_private_objs;
+};
+
+static unsigned int count_private_obj(struct drm_device *dev)
+{
+ struct drm_mode_config *config = &dev->mode_config;
+ struct drm_private_obj *obj;
+ unsigned int count = 0;
+
+ list_for_each_entry(obj, &config->privobj_list, head)
+ count++;
+
+ return count;
+}
+
+/**
+ * drm_atomic_sro_state_get_device - get the DRM device from an SRO state
+ * @state: SRO state
+ *
+ * RETURNS:
+ *
+ * The &struct drm_device associated with the SRO state.
+ */
+struct drm_device *
+drm_atomic_sro_state_get_device(struct drm_atomic_sro_state *state)
+{
+ return state->dev;
+}
+
+static int drm_atomic_sro_state_init(struct drm_device *dev,
+ struct drm_atomic_sro_state *state)
+{
+ int err;
+
+ state->planes =
+ kzalloc_objs(*state->planes, dev->mode_config.num_total_plane);
+ if (!state->planes)
+ return -ENOMEM;
+
+ state->crtcs = kzalloc_objs(*state->crtcs, dev->mode_config.num_crtc);
+ if (!state->crtcs) {
+ err = -ENOMEM;
+ goto err_free_planes;
+ }
+
+ state->connectors = kzalloc_objs(*state->connectors,
+ dev->mode_config.num_connector);
+ 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;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return ERR_PTR(-EINVAL);
+
+ 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->dev->mode_config.num_connector; i++) {
+ struct drm_connector *conn = state->connectors[i].ptr;
+ struct drm_connector_state *conn_state =
+ state->connectors[i].state;
+
+ if (!conn || !conn_state)
+ continue;
+
+ 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 < config->num_connector; i++) {
+ struct drm_connector_state *conn_state = state->connectors[i].state;
+
+ if (!conn_state)
+ continue;
+
+ drm_atomic_connector_print_state(p, conn_state);
+ }
+
+ for (i = 0; i < state->num_private_objs; i++) {
+ struct drm_private_state *obj_state = state->private_objs[i].state;
+
+ if (!obj_state)
+ continue;
+
+ drm_atomic_private_obj_print_state(p, obj_state);
+ }
+}
+EXPORT_SYMBOL(drm_atomic_sro_state_print);
+
+/**
+ * drm_atomic_sro_get_plane_state - get the plane state from an SRO state
+ * @state: SRO state container
+ * @plane: plane to get the state for
+ *
+ * RETURNS:
+ *
+ * The &struct drm_plane_state for @plane, or NULL if not yet read out.
+ */
+struct drm_plane_state *
+drm_atomic_sro_get_plane_state(struct drm_atomic_sro_state *state,
+ struct drm_plane *plane)
+{
+ unsigned int index = drm_plane_index(plane);
+
+ return state->planes[index].state;
+};
+EXPORT_SYMBOL(drm_atomic_sro_get_plane_state);
+
+/**
+ * drm_atomic_sro_set_plane_state - store a plane state into an SRO state
+ * @state: SRO state container
+ * @plane: plane the state belongs to
+ * @plane_state: plane state to store
+ */
+void drm_atomic_sro_set_plane_state(struct drm_atomic_sro_state *state,
+ struct drm_plane *plane,
+ struct drm_plane_state *plane_state)
+{
+ unsigned int index = drm_plane_index(plane);
+
+ state->planes[index].ptr = plane;
+ state->planes[index].state = plane_state;
+
+ drm_dbg_atomic(plane->dev,
+ "Added [PLANE:%d:%s] %p state to readout state %p\n",
+ plane->base.id, plane->name, plane_state, state);
+}
+EXPORT_SYMBOL(drm_atomic_sro_set_plane_state);
+
+/**
+ * drm_atomic_sro_get_crtc_state - get the CRTC state from an SRO state
+ * @state: SRO state container
+ * @crtc: CRTC to get the state for
+ *
+ * RETURNS:
+ *
+ * The &struct drm_crtc_state for @crtc, or NULL if not yet read out.
+ */
+struct drm_crtc_state *
+drm_atomic_sro_get_crtc_state(struct drm_atomic_sro_state *state,
+ struct drm_crtc *crtc)
+{
+ unsigned int index = drm_crtc_index(crtc);
+
+ return state->crtcs[index].state;
+};
+EXPORT_SYMBOL(drm_atomic_sro_get_crtc_state);
+
+/**
+ * drm_atomic_sro_set_crtc_state - store a CRTC state into an SRO state
+ * @state: SRO state container
+ * @crtc: CRTC the state belongs to
+ * @crtc_state: CRTC state to store
+ */
+void drm_atomic_sro_set_crtc_state(struct drm_atomic_sro_state *state,
+ struct drm_crtc *crtc,
+ struct drm_crtc_state *crtc_state)
+{
+ unsigned int index = drm_crtc_index(crtc);
+
+ state->crtcs[index].ptr = crtc;
+ state->crtcs[index].state = crtc_state;
+
+ drm_dbg_atomic(state->dev,
+ "Added [CRTC:%d:%s] %p state to readout state %p\n",
+ crtc->base.id, crtc->name, crtc_state, state);
+}
+EXPORT_SYMBOL(drm_atomic_sro_set_crtc_state);
+
+/**
+ * drm_atomic_sro_get_connector_state - get the connector state from an SRO state
+ * @state: SRO state container
+ * @connector: connector to get the state for
+ *
+ * RETURNS:
+ *
+ * The &struct drm_connector_state for @connector, or NULL if not yet
+ * read out.
+ */
+struct drm_connector_state *
+drm_atomic_sro_get_connector_state(struct drm_atomic_sro_state *state,
+ struct drm_connector *connector)
+{
+ unsigned int index = drm_connector_index(connector);
+
+ return state->connectors[index].state;
+};
+EXPORT_SYMBOL(drm_atomic_sro_get_connector_state);
+
+/**
+ * drm_atomic_sro_set_connector_state - store a connector state into an SRO state
+ * @state: SRO state container
+ * @conn: connector the state belongs to
+ * @conn_state: connector state to store
+ *
+ * Takes a reference on @conn which is released when the state is
+ * installed or freed.
+ */
+void drm_atomic_sro_set_connector_state(struct drm_atomic_sro_state *state,
+ struct drm_connector *conn,
+ struct drm_connector_state *conn_state)
+{
+ unsigned int index = drm_connector_index(conn);
+
+ drm_connector_get(conn);
+ state->connectors[index].ptr = conn;
+ state->connectors[index].state = conn_state;
+
+ drm_dbg_atomic(conn->dev,
+ "Added [CONNECTOR:%d:%s] %p state to readout state %p\n",
+ conn->base.id, conn->name, conn_state, state);
+}
+EXPORT_SYMBOL(drm_atomic_sro_set_connector_state);
+
+/**
+ * drm_atomic_sro_get_private_obj_state - get a private object state from an SRO state
+ * @state: SRO state container
+ * @obj: private object to get the state for
+ *
+ * RETURNS:
+ *
+ * The &struct drm_private_state for @obj, or NULL if not yet read out.
+ */
+struct drm_private_state *
+drm_atomic_sro_get_private_obj_state(struct drm_atomic_sro_state *state,
+ struct drm_private_obj *obj)
+{
+ unsigned int i;
+
+ for (i = 0; i < state->num_private_objs; i++)
+ if (obj == state->private_objs[i].ptr)
+ return state->private_objs[i].state;
+
+ return NULL;
+}
+EXPORT_SYMBOL(drm_atomic_sro_get_private_obj_state);
+
+/**
+ * drm_atomic_sro_set_private_obj_state - store a private object state into an SRO state
+ * @state: SRO state container
+ * @obj: private object the state belongs to
+ * @obj_state: private object state to store
+ */
+void drm_atomic_sro_set_private_obj_state(struct drm_atomic_sro_state *state,
+ struct drm_private_obj *obj,
+ struct drm_private_state *obj_state)
+{
+ unsigned int index = state->num_private_objs;
+
+ state->private_objs[index].ptr = obj;
+ state->private_objs[index].state = obj_state;
+ state->num_private_objs += 1;
+
+ drm_dbg_atomic(state->dev,
+ "Added new private object %p state %p to readout state %p\n", obj,
+ obj_state, state);
+}
+EXPORT_SYMBOL(drm_atomic_sro_set_private_obj_state);
diff --git a/include/drm/drm_atomic_sro.h b/include/drm/drm_atomic_sro.h
new file mode 100644
index 000000000000..5a9333a05796
--- /dev/null
+++ b/include/drm/drm_atomic_sro.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] 41+ messages in thread
* [PATCH v5 10/33] drm/atomic_sro: Create kernel parameter to force or disable readout
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (8 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 09/33] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 11/33] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
` (24 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_sro.c | 36 ++++++++++++++++++++++++++++++++++++
include/drm/drm_atomic_sro.h | 2 ++
2 files changed, 38 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index 096fdb708819..1551f6de6c89 100644
--- a/drivers/gpu/drm/drm_atomic_sro.c
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -11,10 +11,46 @@
#include <linux/module.h>
#include "drm_internal.h"
#include "drm_crtc_internal.h"
+enum drm_atomic_readout_status {
+ DRM_ATOMIC_READOUT_DISABLED = 0,
+ DRM_ATOMIC_READOUT_ENABLED,
+ DRM_ATOMIC_READOUT_SKIP_MISSING_COMPARE,
+ DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT,
+};
+
+static unsigned int atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
+module_param_unsafe(atomic_readout, uint, 0400);
+MODULE_PARM_DESC(atomic_readout,
+ "Enable Hardware State Readout (0 = disabled, 1 = enabled, 2 = ignore missing compares, 3 = ignore missing readouts and compares, default = 1)");
+
+/**
+ * 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;
struct drm_plane_state *state;
};
diff --git a/include/drm/drm_atomic_sro.h b/include/drm/drm_atomic_sro.h
index 5a9333a05796..6e5262384c71 100644
--- a/include/drm/drm_atomic_sro.h
+++ b/include/drm/drm_atomic_sro.h
@@ -13,10 +13,12 @@ struct drm_plane;
struct drm_plane_state;
struct drm_printer;
struct drm_private_obj;
struct drm_private_state;
+bool drm_atomic_sro_device_can_readout(struct drm_device *dev);
+
struct drm_atomic_sro_state *drm_atomic_sro_state_alloc(struct drm_device *dev);
void drm_atomic_sro_state_free(struct drm_atomic_sro_state *state);
void drm_atomic_sro_state_print(const struct drm_atomic_sro_state *state,
struct drm_printer *p);
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 11/33] drm/atomic_sro: Add atomic state readout infrastructure
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (9 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 10/33] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 12/33] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
` (23 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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().
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_sro.c | 60 ++++++++++++++++++++++++++++++++
include/drm/drm_atomic.h | 25 +++++++++++++
include/drm/drm_bridge.h | 31 +++++++++++++++++
include/drm/drm_connector.h | 26 ++++++++++++++
include/drm/drm_crtc.h | 26 ++++++++++++++
include/drm/drm_mode_config.h | 18 ++++++++++
include/drm/drm_modeset_helper_vtables.h | 20 +++++++++++
include/drm/drm_plane.h | 26 ++++++++++++++
8 files changed, 232 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index 1551f6de6c89..6921af73eb69 100644
--- a/drivers/gpu/drm/drm_atomic_sro.c
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -4,10 +4,11 @@
#include <drm/drm_atomic_sro.h>
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#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>
#include <linux/module.h>
#include "drm_internal.h"
@@ -23,10 +24,63 @@ enum drm_atomic_readout_status {
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)");
+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 (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
* @dev: DRM device to check
*
* Verifies that the device is an atomic driver, that readout is
@@ -37,16 +91,22 @@ MODULE_PARM_DESC(atomic_readout,
* True if the device supports full hardware state readout, false
* otherwise.
*/
bool drm_atomic_sro_device_can_readout(struct drm_device *dev)
{
+ bool ret;
+
if (!drm_drv_uses_atomic_modeset(dev))
return false;
if (atomic_readout == DRM_ATOMIC_READOUT_DISABLED)
return false;
+ ret = drm_atomic_sro_can_readout(dev);
+ if (!ret)
+ return false;
+
return true;
}
EXPORT_SYMBOL(drm_atomic_sro_device_can_readout);
struct __drm_atomic_sro_plane {
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 47b2fe2b039e..657f5f4c223f 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -296,10 +296,35 @@ struct drm_private_state_funcs {
* Frees the private object state created with @atomic_duplicate_state.
*/
void (*atomic_destroy_state)(struct drm_private_obj *obj,
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.
+ *
+ * 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:
*
* If driver subclasses &struct drm_private_state, it should implement
* this optional hook for printing additional driver specific state.
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index a6b0ad25eea8..9e3f9b82afc2 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -278,10 +278,41 @@ struct drm_bridge_funcs {
* The @atomic_post_disable callback is optional.
*/
void (*atomic_post_disable)(struct drm_bridge *bridge,
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.
+ *
+ * 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:
*
* Duplicate the current bridge state object (which is guaranteed to be
* non-NULL).
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index c61345e6fb08..730a2e41f2c5 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -32,10 +32,11 @@
#include <drm/drm_util.h>
#include <drm/drm_property.h>
#include <uapi/drm/drm_mode.h>
+struct drm_atomic_sro_state;
struct drm_connector_helper_funcs;
struct drm_modeset_acquire_ctx;
struct drm_device;
struct drm_crtc;
struct drm_display_mode;
@@ -1733,10 +1734,35 @@ struct drm_connector_funcs {
* A new, pristine, connector state instance or an error pointer
* on failure.
*/
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.
+ *
+ * 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:
*
* Duplicate the current atomic state for this connector and return it.
* The core and helpers guarantee that any atomic state duplicated with
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index be5dca538d87..62366caf2a84 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -33,10 +33,11 @@
#include <drm/drm_device.h>
#include <drm/drm_plane.h>
#include <drm/drm_debugfs_crc.h>
#include <drm/drm_mode_config.h>
+struct drm_atomic_sro_state;
struct drm_connector;
struct drm_device;
struct drm_framebuffer;
struct drm_mode_set;
struct drm_file;
@@ -640,10 +641,35 @@ struct drm_crtc_funcs {
* A new, pristine, CRTC state instance or an error pointer
* on failure.
*/
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.
+ *
+ * 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:
*
* Duplicate the current atomic state for this CRTC and return it.
* The core and helpers guarantee that any atomic state duplicated with
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index d8f5b7e9673e..14eab7b126ea 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -319,10 +319,28 @@ struct drm_mode_config_funcs {
*
* Subclassing of &drm_atomic_commit is deprecated in favour of using
* &drm_private_state and &drm_private_obj.
*/
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).
+ *
+ * RETURNS:
+ *
+ * 0 on success, a negative error code otherwise.
+ */
+ int (*atomic_sro_readout_state)(struct drm_device *dev);
};
/**
* struct drm_mode_config - Mode configuration control structure
* @min_width: minimum fb pixel width on this device
diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
index 4965356a6314..807e9ac8539f 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -1564,8 +1564,28 @@ struct drm_mode_config_helper_funcs {
* how one should implement this.
*
* This hook is optional.
*/
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 every KMS object.
+ *
+ * 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.
+ *
+ * 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);
};
#endif
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index 2c5a5a70a71b..d841fee3a9cd 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -30,10 +30,11 @@
#include <drm/drm_color_mgmt.h>
#include <drm/drm_rect.h>
#include <drm/drm_modeset_lock.h>
#include <drm/drm_util.h>
+struct drm_atomic_sro_state;
struct drm_crtc;
struct drm_plane_size_hint;
struct drm_printer;
struct drm_modeset_acquire_ctx;
@@ -402,10 +403,35 @@ struct drm_plane_funcs {
* A new, pristine, plane state instance or an error pointer
* on failure.
*/
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.
+ *
+ * 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:
*
* Duplicate the current atomic state for this plane and return it.
* The core and helpers guarantee that any atomic state duplicated with
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 12/33] drm/atomic_sro: Add function to install state into drm objects
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (10 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 11/33] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 13/33] drm/atomic_sro: Create documentation Maxime Ripard
` (22 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_sro.c | 145 ++++++++++++++++++++++++++++++++++++++-
include/drm/drm_atomic.h | 18 +++++
include/drm/drm_atomic_sro.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_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index 6921af73eb69..14fddd760165 100644
--- a/drivers/gpu/drm/drm_atomic_sro.c
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -246,11 +246,12 @@ 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().
+ * 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)
{
struct drm_device *dev = state->dev;
unsigned int i;
@@ -546,5 +547,147 @@ void drm_atomic_sro_set_private_obj_state(struct drm_atomic_sro_state *state,
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);
+
+/**
+ * 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->dev->mode_config.num_connector; 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 657f5f4c223f..0e5c87f258ef 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -321,10 +321,28 @@ struct drm_private_state_funcs {
*/
int (*atomic_sro_readout_state)(struct drm_private_obj *obj,
struct drm_atomic_sro_state *state,
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:
*
* If driver subclasses &struct drm_private_state, it should implement
* this optional hook for printing additional driver specific state.
diff --git a/include/drm/drm_atomic_sro.h b/include/drm/drm_atomic_sro.h
index 6e5262384c71..195154850ab4 100644
--- a/include/drm/drm_atomic_sro.h
+++ b/include/drm/drm_atomic_sro.h
@@ -48,6 +48,8 @@ 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);
+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 730a2e41f2c5..50e9e94c2532 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1759,10 +1759,28 @@ struct drm_connector_funcs {
*/
int (*atomic_sro_readout_state)(struct drm_connector *connector,
struct drm_atomic_sro_state *state,
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:
*
* Duplicate the current atomic state for this connector and return it.
* The core and helpers guarantee that any atomic state duplicated with
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 62366caf2a84..a23364fa7e3e 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -666,10 +666,28 @@ struct drm_crtc_funcs {
*/
int (*atomic_sro_readout_state)(struct drm_crtc *crtc,
struct drm_atomic_sro_state *state,
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:
*
* Duplicate the current atomic state for this CRTC and return it.
* The core and helpers guarantee that any atomic state duplicated with
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index d841fee3a9cd..358c4f4062b4 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -428,10 +428,28 @@ struct drm_plane_funcs {
*/
int (*atomic_sro_readout_state)(struct drm_plane *plane,
struct drm_atomic_sro_state *state,
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:
*
* Duplicate the current atomic state for this plane and return it.
* The core and helpers guarantee that any atomic state duplicated with
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 13/33] drm/atomic_sro: Create documentation
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (11 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 12/33] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 14/33] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
` (21 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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_sro.c describing the various
SRO phases and how drivers wire up the callbacks.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
Documentation/gpu/drm-kms-helpers.rst | 17 +++++++++++++++++
drivers/gpu/drm/drm_atomic_sro.c | 31 +++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index 94cfc26acecc..418637887fc8 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -80,10 +80,27 @@ Atomic State Helper Reference
-----------------------------
.. kernel-doc:: drivers/gpu/drm/drm_atomic_state_helper.c
:export:
+.. _drm_atomic_sro:
+
+Atomic State Read-Out
+---------------------
+
+.. kernel-doc:: drivers/gpu/drm/drm_atomic_sro.c
+ :doc: overview
+
+Atomic State Read-Out Reference
+--------------------------------
+
+.. kernel-doc:: include/drm/drm_atomic_sro.h
+ :internal:
+
+.. kernel-doc:: drivers/gpu/drm/drm_atomic_sro.c
+ :export:
+
GEM Atomic Helper Reference
---------------------------
.. kernel-doc:: drivers/gpu/drm/drm_gem_atomic_helper.c
:doc: overview
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index 14fddd760165..6cd002e857d9 100644
--- a/drivers/gpu/drm/drm_atomic_sro.c
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -12,10 +12,41 @@
#include <linux/module.h>
#include "drm_internal.h"
#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 second phase is the installation. Once all states have been read
+ * out, drm_atomic_sro_install_state() walks through the
+ * &struct drm_atomic_sro_state and assigns each readout state as the
+ * object's current state. Before doing so, it calls the optional
+ * atomic_sro_install_state hook on each object. This gives drivers a
+ * chance to acquire the resources needed to keep the hardware state
+ * active, such as power domains, clocks, or interrupts. This hook
+ * cannot fail.
+ *
+ * Drivers integrate with SRO by implementing the readout hooks in their
+ * object funcs vtables and setting the
+ * &drm_mode_config_funcs.atomic_sro_readout_state and
+ * &drm_mode_config_helper_funcs.atomic_sro_build_state callbacks.
+ */
+
enum drm_atomic_readout_status {
DRM_ATOMIC_READOUT_DISABLED = 0,
DRM_ATOMIC_READOUT_ENABLED,
DRM_ATOMIC_READOUT_SKIP_MISSING_COMPARE,
DRM_ATOMIC_READOUT_SKIP_MISSING_READOUT,
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 14/33] drm/bridge: Handle bridges with hardware state readout
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (12 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 13/33] drm/atomic_sro: Create documentation Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 15/33] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
` (20 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_bridge.c | 80 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 77 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 177f018dd3ff..17fef062d7e5 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -27,10 +27,11 @@
#include <linux/media-bus-format.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/srcu.h>
+#include <drm/drm_atomic_sro.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder.h>
@@ -548,10 +549,68 @@ drm_bridge_atomic_create_priv_state(struct drm_private_obj *obj)
return ERR_CAST(state);
return &state->base;
}
+static struct drm_connector_state *
+find_connector_state_for_encoder(struct drm_atomic_sro_state *state,
+ struct drm_encoder *encoder)
+{
+ struct drm_connector_list_iter conn_iter;
+ struct drm_connector *connector;
+
+ drm_connector_list_iter_begin(drm_atomic_sro_state_get_device(state),
+ &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ struct drm_connector_state *conn_state =
+ drm_atomic_sro_get_connector_state(state, connector);
+
+ if (WARN_ON(!conn_state))
+ continue;
+
+ 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;
+
+ conn_state = find_connector_state_for_encoder(state, encoder);
+ if (!conn_state)
+ return -EINVAL;
+
+ crtc_state = drm_atomic_sro_get_crtc_state(state, conn_state->crtc);
+ if (!crtc_state)
+ return -EINVAL;
+
+ bridge_state = drm_priv_to_bridge_state(priv_state);
+ if (bridge_funcs->atomic_sro_readout_state) {
+ ret = bridge_funcs->atomic_sro_readout_state(
+ bridge, state, bridge_state, crtc_state, conn_state);
+ if (WARN_ON(ret))
+ return ret;
+ }
+
+ return 0;
+}
+
static void
drm_bridge_atomic_print_priv_state(struct drm_printer *p,
const struct drm_private_state *s)
{
const struct drm_bridge_state *state = drm_priv_to_bridge_state(s);
@@ -575,10 +634,18 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
.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,
};
+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
* @obj: private object to check
*
* RETURNS:
@@ -586,11 +653,13 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
* 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->funcs && obj->funcs == &drm_bridge_priv_state_funcs;
+ return obj->funcs &&
+ (obj->funcs == &drm_bridge_priv_state_funcs ||
+ obj->funcs == &drm_bridge_priv_state_funcs_with_sro);
}
EXPORT_SYMBOL(drm_private_obj_is_bridge);
/**
* drm_bridge_attach - attach the bridge to an encoder's chain
@@ -659,12 +728,17 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
ret = bridge->funcs->attach(bridge, encoder, flags);
if (ret < 0)
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)
+ drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
+ &drm_bridge_priv_state_funcs_with_sro);
+ else
+ drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
+ &drm_bridge_priv_state_funcs);
return 0;
err_reset_bridge:
bridge->dev = NULL;
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 15/33] drm/mode-config: Read out hardware state in drm_mode_config_create_state
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (13 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 14/33] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 16/33] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
` (19 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_mode_config.c | 49 +++++++++++++++++++++++++++------------
1 file changed, 34 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index c7c8e38b192d..deb0590b01e2 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -22,10 +22,11 @@
#include <linux/export.h>
#include <linux/uaccess.h>
#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_sro.h>
#include <drm/drm_drv.h>
#include <drm/drm_encoder.h>
#include <drm/drm_file.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_managed.h>
@@ -324,25 +325,11 @@ void drm_mode_config_reset(struct drm_device *dev)
}
drm_connector_list_iter_end(&conn_iter);
}
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;
struct drm_colorop *colorop;
struct drm_plane *plane;
struct drm_connector *connector;
@@ -410,10 +397,42 @@ int drm_mode_config_create_initial_state(struct drm_device *dev)
}
drm_connector_list_iter_end(&conn_iter);
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.
+ *
+ * Returns: 0 on success, negative error value on failure.
+ */
+int drm_mode_config_create_initial_state(struct drm_device *dev)
+{
+ int ret;
+
+ if (dev->mode_config.funcs->atomic_sro_readout_state) {
+ ret = dev->mode_config.funcs->atomic_sro_readout_state(dev);
+ if (!ret)
+ return 0;
+ }
+
+ return drm_mode_config_create_state(dev);
+}
EXPORT_SYMBOL(drm_mode_config_create_initial_state);
/*
* Global properties
*/
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 16/33] drm/atomic_sro: Provide helpers to implement hardware state readout
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (14 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 15/33] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 17/33] drm/mode-config: Introduce hw_reset hook Maxime Ripard
` (18 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
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_sro.c | 5 +-
drivers/gpu/drm/drm_atomic_sro_helper.c | 409 +++++++++++++++++++++++++++++++
include/drm/drm_atomic_sro_helper.h | 28 +++
include/drm/drm_modeset_helper_vtables.h | 3 +
6 files changed, 454 insertions(+), 1 deletion(-)
diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index 418637887fc8..46e6dd04768f 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -97,10 +97,19 @@ Atomic State Read-Out Reference
:internal:
.. kernel-doc:: drivers/gpu/drm/drm_atomic_sro.c
:export:
+Atomic State Read-Out Helpers Reference
+----------------------------------------
+
+.. kernel-doc:: include/drm/drm_atomic_sro_helper.h
+ :internal:
+
+.. kernel-doc:: drivers/gpu/drm/drm_atomic_sro_helper.c
+ :export:
+
GEM Atomic Helper Reference
---------------------------
.. kernel-doc:: drivers/gpu/drm/drm_gem_atomic_helper.c
:doc: overview
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index e59a3d9af3e4..c7f5867a6dbe 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -140,10 +140,11 @@ obj-$(CONFIG_DRM_TTM_HELPER) += drm_ttm_helper.o
# Modesetting helpers
#
drm_kms_helper-y := \
drm_atomic_helper.o \
+ drm_atomic_sro_helper.o \
drm_atomic_state_helper.o \
drm_bridge_helper.o \
drm_crtc_helper.o \
drm_damage_helper.o \
drm_flip_work.o \
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index 6cd002e857d9..15ecf88714fa 100644
--- a/drivers/gpu/drm/drm_atomic_sro.c
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -40,11 +40,14 @@
* 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.
+ * &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.
*/
enum drm_atomic_readout_status {
DRM_ATOMIC_READOUT_DISABLED = 0,
DRM_ATOMIC_READOUT_ENABLED,
diff --git a/drivers/gpu/drm/drm_atomic_sro_helper.c b/drivers/gpu/drm/drm_atomic_sro_helper.c
new file mode 100644
index 000000000000..50d1af53bfc2
--- /dev/null
+++ b/drivers/gpu/drm/drm_atomic_sro_helper.c
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_sro.h>
+#include <drm/drm_atomic_sro_helper.h>
+#include <drm/drm_bridge.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>
+
+/**
+ * drm_atomic_helper_sro_readout_planes_state - read out all plane states from hardware
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ *
+ * Iterates over all planes, allocates a state for each 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_plane *plane;
+
+ might_alloc(GFP_KERNEL);
+
+ drm_for_each_plane(plane, dev) {
+ const struct drm_plane_funcs *plane_funcs = plane->funcs;
+ struct drm_plane_state *plane_state;
+ int ret;
+
+ if (!plane_funcs->atomic_sro_readout_state) {
+ drm_warn(dev, "No plane readout implementation.");
+ continue;
+ }
+
+ drm_dbg_kms(dev, "Initializing Plane %s state.\n", plane->name);
+
+ plane_state = plane_funcs->atomic_create_state(plane);
+ if (drm_WARN_ON(dev, IS_ERR(plane_state)))
+ return PTR_ERR(plane_state);
+
+ ret = plane_funcs->atomic_sro_readout_state(plane, state,
+ plane_state);
+ if (drm_WARN_ON(dev, ret)) {
+ plane_funcs->atomic_destroy_state(plane, plane_state);
+ return ret;
+ }
+
+ drm_atomic_sro_set_plane_state(state, plane, plane_state);
+ }
+
+ return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_readout_crtcs_state - read out all CRTC states from hardware
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ *
+ * Iterates over all CRTCs, allocates a state for each 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_crtc *crtc;
+
+ might_alloc(GFP_KERNEL);
+
+ drm_for_each_crtc(crtc, dev) {
+ const struct drm_crtc_funcs *crtc_funcs = crtc->funcs;
+ struct drm_crtc_state *crtc_state;
+ int ret;
+
+ if (!crtc_funcs->atomic_sro_readout_state) {
+ drm_warn(dev, "No CRTC readout implementation.");
+ continue;
+ }
+
+ drm_dbg_kms(dev, "Initializing CRTC %s state.\n", crtc->name);
+
+ crtc_state = crtc_funcs->atomic_create_state(crtc);
+ if (drm_WARN_ON(dev, IS_ERR(crtc_state)))
+ return PTR_ERR(crtc_state);
+
+ ret = crtc_funcs->atomic_sro_readout_state(crtc, state,
+ crtc_state);
+ if (drm_WARN_ON(dev, ret)) {
+ crtc_funcs->atomic_destroy_state(crtc, crtc_state);
+ return ret;
+ }
+
+ drm_atomic_sro_set_crtc_state(state, crtc, crtc_state);
+ }
+
+ return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_readout_connectors_state - read out all connector states from hardware
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ *
+ * Iterates over all connectors, allocates a state for each 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_connector_list_iter conn_iter;
+ struct drm_connector *connector;
+
+ might_alloc(GFP_KERNEL);
+
+ drm_connector_list_iter_begin(dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ const struct drm_connector_funcs *conn_funcs = connector->funcs;
+ struct drm_connector_state *conn_state;
+ int ret;
+
+ if (!conn_funcs->atomic_sro_readout_state) {
+ drm_warn(dev, "No Connector readout implementation.");
+ continue;
+ }
+
+ 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))) {
+ drm_connector_list_iter_end(&conn_iter);
+ return PTR_ERR(conn_state);
+ }
+
+ ret = conn_funcs->atomic_sro_readout_state(connector, state,
+ conn_state);
+ if (drm_WARN_ON(dev, ret)) {
+ conn_funcs->atomic_destroy_state(connector, conn_state);
+ drm_connector_list_iter_end(&conn_iter);
+ return ret;
+ }
+
+ drm_atomic_sro_set_connector_state(state, connector, conn_state);
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+
+ 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
+ *
+ * Iterates over all encoders and their bridge chains, allocates a state
+ * for each bridge 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_encoder *encoder;
+
+ 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.
+ */
+ drm_for_each_encoder(encoder, dev) {
+ struct drm_bridge *bridge;
+ int ret;
+
+ list_for_each_entry(bridge, &encoder->bridge_chain, chain_node) {
+ 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;
+
+ if (!bridge_obj_funcs->atomic_sro_readout_state) {
+ drm_warn(dev,
+ "No bridge %s readout implementation.",
+ bridge->name);
+ continue;
+ }
+
+ drm_dbg_kms(dev, "Initializing Bridge %s", bridge->name);
+
+ bridge_obj_state =
+ bridge_obj_funcs->atomic_create_state(
+ bridge_obj);
+ if (drm_WARN_ON(dev, IS_ERR(bridge_obj_state)))
+ return PTR_ERR(bridge_obj_state);
+
+ ret = bridge_obj_funcs->atomic_sro_readout_state(
+ bridge_obj, state, bridge_obj_state);
+ if (drm_WARN_ON(dev, ret)) {
+ bridge_obj_funcs->atomic_destroy_state(
+ bridge_obj, bridge_obj_state);
+ return ret;
+ }
+
+ drm_atomic_sro_set_private_obj_state(state, bridge_obj,
+ bridge_obj_state);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_readout_private_objs_state - read out non-bridge private object states
+ * @dev: DRM device
+ * @state: SRO state container to fill
+ *
+ * Iterates over all private objects that are not bridges (bridges are
+ * handled by drm_atomic_helper_sro_readout_bridges_state()), allocates
+ * a state for each 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_private_obj *privobj;
+
+ might_alloc(GFP_KERNEL);
+
+ drm_for_each_privobj(privobj, dev) {
+ const struct drm_private_state_funcs *priv_funcs =
+ privobj->funcs;
+ struct drm_private_state *priv_state;
+ int ret;
+
+ /*
+ * We already accounted readout the bridge state earlier, we only
+ * have to deal with !bridges drm_private_obj now.
+ */
+ if (drm_private_obj_is_bridge(privobj))
+ continue;
+
+ if (!priv_funcs->atomic_sro_readout_state) {
+ drm_warn(dev,
+ "No private object %s readout implementation.",
+ privobj->name);
+ continue;
+ }
+
+ drm_dbg_kms(dev, "Initializing Private Object %s",
+ privobj->name);
+
+ priv_state = priv_funcs->atomic_create_state(privobj);
+ if (drm_WARN_ON(dev, IS_ERR(priv_state)))
+ return PTR_ERR(priv_state);
+
+ ret = priv_funcs->atomic_sro_readout_state(privobj, state,
+ priv_state);
+ if (drm_WARN_ON(dev, ret)) {
+ priv_funcs->atomic_destroy_state(privobj, priv_state);
+ return ret;
+ }
+
+ drm_atomic_sro_set_private_obj_state(state, privobj,
+ priv_state);
+ }
+
+ return 0;
+}
+
+/**
+ * drm_atomic_helper_sro_build_state - build an SRO state from hardware
+ * @dev: DRM device to build the state for
+ *
+ * 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.
+ *
+ * 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_sro_state *state;
+ struct drm_printer p = drm_dbg_printer(dev, DRM_UT_ATOMIC, NULL);
+ int ret;
+
+ 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 ERR_PTR(-ENOMEM);
+
+ ret = drm_atomic_helper_sro_readout_crtcs_state(dev, state);
+ if (ret)
+ goto err_state_free;
+
+ ret = drm_atomic_helper_sro_readout_planes_state(dev, state);
+ if (ret)
+ goto err_state_free;
+
+ ret = drm_atomic_helper_sro_readout_connectors_state(dev, state);
+ if (ret)
+ goto err_state_free;
+
+ ret = drm_atomic_helper_sro_readout_bridges_state(dev, state);
+ if (ret)
+ goto err_state_free;
+
+ ret = drm_atomic_helper_sro_readout_private_objs_state(dev, state);
+ 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.
+ *
+ * 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;
+
+ 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);
+ if (IS_ERR(state))
+ return PTR_ERR(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_sro_helper.h b/include/drm/drm_atomic_sro_helper.h
new file mode 100644
index 000000000000..a42ed8136a49
--- /dev/null
+++ b/include/drm/drm_atomic_sro_helper.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef DRM_ATOMIC_SRO_HELPER_H_
+#define DRM_ATOMIC_SRO_HELPER_H_
+
+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);
+int
+drm_atomic_helper_sro_readout_crtcs_state(struct drm_device *dev,
+ struct drm_atomic_sro_state *state);
+int
+drm_atomic_helper_sro_readout_connectors_state(struct drm_device *dev,
+ struct drm_atomic_sro_state *state);
+int
+drm_atomic_helper_sro_readout_bridges_state(struct drm_device *dev,
+ struct drm_atomic_sro_state *state);
+int
+drm_atomic_helper_sro_readout_private_objs_state(struct drm_device *dev,
+ struct drm_atomic_sro_state *state);
+struct drm_atomic_sro_state *
+drm_atomic_helper_sro_build_state(struct drm_device *dev);
+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 807e9ac8539f..38d8f15594c2 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -1577,10 +1577,13 @@ struct drm_mode_config_helper_funcs {
* 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.
*
+ * The default implementation is
+ * drm_atomic_helper_sro_build_state().
+ *
* RETURNS:
*
* A &struct drm_atomic_sro_state on success, an error pointer
* otherwise.
*/
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 17/33] drm/mode-config: Introduce hw_reset hook
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (15 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 16/33] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 18/33] drm/modeset-helper: Add drm_mode_config_helper_hw_reset() Maxime Ripard
` (17 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_mode_config.c | 14 ++++++++++----
include/drm/drm_mode_config.h | 32 ++++++++++++++++++++++++++++++++
2 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index deb0590b01e2..c85946eef37e 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -406,17 +406,18 @@ static int drm_mode_config_create_state(struct drm_device *dev)
*
* 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
+ * read out the initial state from the hardware for all objects if state
+ * readout is enabled and the driver supports it. Then, if state
+ * readout 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.
*
* For historical reasons, drm_mode_config_reset() is similar, but
- * doesn't support state read-out, and will always reset the hardware.
+ * doesn't support state readout, and will always reset the hardware.
* It will also ignore all &drm_private_obj.
*
* Returns: 0 on success, negative error value on failure.
*/
int drm_mode_config_create_initial_state(struct drm_device *dev)
@@ -427,10 +428,15 @@ int drm_mode_config_create_initial_state(struct drm_device *dev)
ret = dev->mode_config.funcs->atomic_sro_readout_state(dev);
if (!ret)
return 0;
}
+ /* Reset the hardware first ... */
+ if (dev->mode_config.funcs->hw_reset)
+ dev->mode_config.funcs->hw_reset(dev);
+
+ /* ... then allocate a new pristine state */
return drm_mode_config_create_state(dev);
}
EXPORT_SYMBOL(drm_mode_config_create_initial_state);
/*
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 14eab7b126ea..5b3c194c72ca 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -107,10 +107,42 @@ struct drm_mode_config_funcs {
* should be checked in the .mode_valid() hook for each specific object.
*/
enum drm_mode_status (*mode_valid)(struct drm_device *dev,
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:
*
* This is the only hook to validate an atomic modeset update. This
* function must reject any modeset and state changes which the hardware
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 18/33] drm/modeset-helper: Add drm_mode_config_helper_hw_reset()
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (16 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 17/33] drm/mode-config: Introduce hw_reset hook Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 19/33] drm/atomic_helper: Pass nonblock to commit_tail Maxime Ripard
` (16 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_modeset_helper.c | 24 ++++++++++++++++++++++++
include/drm/drm_modeset_helper.h | 1 +
2 files changed, 25 insertions(+)
diff --git a/drivers/gpu/drm/drm_modeset_helper.c b/drivers/gpu/drm/drm_modeset_helper.c
index e54584da4c3d..efff6dc9b88f 100644
--- a/drivers/gpu/drm/drm_modeset_helper.c
+++ b/drivers/gpu/drm/drm_modeset_helper.c
@@ -258,5 +258,29 @@ int drm_mode_config_helper_resume(struct drm_device *dev)
drm_kms_helper_poll_enable(dev);
return ret;
}
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);
+ }
+}
+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
@@ -40,7 +40,8 @@ void drm_helper_mode_fill_fb_struct(struct drm_device *dev,
int drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
const struct drm_crtc_funcs *funcs);
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] 41+ messages in thread
* [PATCH v5 19/33] drm/atomic_helper: Pass nonblock to commit_tail
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (17 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 18/33] drm/modeset-helper: Add drm_mode_config_helper_hw_reset() Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 20/33] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
` (15 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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 state comparison infrastructure will need to know if a
commit is blocking or non-blocking in commit_tail. Pass the nonblock
flag along.
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_helper.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 285aac3554df..6405afc5037d 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -2035,11 +2035,11 @@ void drm_atomic_helper_commit_tail_rpm(struct drm_atomic_commit *state)
drm_atomic_helper_cleanup_planes(dev, state);
}
EXPORT_SYMBOL(drm_atomic_helper_commit_tail_rpm);
-static void commit_tail(struct drm_atomic_commit *state)
+static void commit_tail(struct drm_atomic_commit *state, bool nonblock)
{
struct drm_device *dev = state->dev;
const struct drm_mode_config_helper_funcs *funcs;
struct drm_crtc_state *new_crtc_state;
struct drm_crtc *crtc;
@@ -2093,11 +2093,11 @@ static void commit_tail(struct drm_atomic_commit *state)
static void commit_work(struct work_struct *work)
{
struct drm_atomic_commit *state = container_of(work,
struct drm_atomic_commit,
commit_work);
- commit_tail(state);
+ commit_tail(state, true);
}
/**
* drm_atomic_helper_async_check - check if state can be committed asynchronously
* @dev: DRM device
@@ -2313,11 +2313,11 @@ int drm_atomic_helper_commit(struct drm_device *dev,
drm_atomic_commit_get(state);
if (nonblock)
queue_work(system_dfl_wq, &state->commit_work);
else
- commit_tail(state);
+ commit_tail(state, false);
return 0;
err:
drm_atomic_helper_unprepare_planes(dev, state);
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 20/33] drm/atomic_helper: Compare actual and readout states once the commit is done
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (18 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 19/33] drm/atomic_helper: Pass nonblock to commit_tail Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 21/33] drm/atomic-state-helper: Provide comparison macros Maxime Ripard
` (14 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_helper.c | 40 +++++++
drivers/gpu/drm/drm_atomic_sro.c | 211 +++++++++++++++++++++++++++++++++++-
drivers/gpu/drm/drm_bridge.c | 3 +-
include/drm/drm_atomic.h | 23 ++++
include/drm/drm_atomic_sro.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, 365 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 6405afc5037d..3de6f42ea54b 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -30,10 +30,11 @@
#include <linux/ktime.h>
#include <linux/media-bus-format.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_sro.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_blend.h>
#include <drm/drm_bridge.h>
#include <drm/drm_colorop.h>
#include <drm/drm_damage_helper.h>
@@ -2085,10 +2086,49 @@ static void commit_tail(struct drm_atomic_commit *state, bool nonblock)
(unsigned long)commit_time_ms,
new_self_refresh_mask);
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 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);
}
static void commit_work(struct work_struct *work)
{
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index 15ecf88714fa..cd7015f8c333 100644
--- a/drivers/gpu/drm/drm_atomic_sro.c
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -22,11 +22,11 @@
* 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
+ * 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
* atomic_sro_readout_state hook called to populate a
* &struct drm_atomic_sro_state from hardware registers.
*
@@ -37,12 +37,21 @@
* 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
+ * 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
* default helpers drm_atomic_helper_sro_readout_state() and
* drm_atomic_helper_sro_build_state() handle the standard readout
* sequence.
@@ -111,10 +120,61 @@ static bool drm_atomic_sro_can_readout(struct drm_device *dev)
drm_connector_list_iter_end(&conn_iter);
return true;
}
+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
* @dev: DRM device to check
*
* Verifies that the device is an atomic driver, that readout is
@@ -137,10 +197,14 @@ bool drm_atomic_sro_device_can_readout(struct drm_device *dev)
ret = drm_atomic_sro_can_readout(dev);
if (!ret)
return false;
+ ret = drm_atomic_sro_can_compare(dev);
+ if (!ret)
+ return false;
+
return true;
}
EXPORT_SYMBOL(drm_atomic_sro_device_can_readout);
struct __drm_atomic_sro_plane {
@@ -723,5 +787,146 @@ void drm_atomic_sro_install_state(struct drm_atomic_sro_state *state)
state->private_objs[i].ptr = NULL;
}
state->num_private_objs = 0;
}
EXPORT_SYMBOL(drm_atomic_sro_install_state);
+
+/**
+ * drm_atomic_sro_readout_and_compare - verify committed state against hardware
+ * @commit: the atomic commit we just performed
+ *
+ * Reads out a fresh &struct drm_atomic_sro_state from the hardware 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.
+ *
+ * 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;
+
+ readout_state = funcs->atomic_sro_build_state(dev);
+ if (WARN_ON(IS_ERR(readout_state)))
+ return false;
+
+ for_each_new_plane_in_state(commit, plane, new_plane_state, i) {
+ const struct drm_plane_funcs *plane_funcs =
+ plane->funcs;
+ struct drm_plane_state *readout_plane_state;
+
+ readout_plane_state = drm_atomic_sro_get_plane_state(readout_state, plane);
+ if (!readout_plane_state) {
+ identical = false;
+ continue;
+ }
+
+ if (!plane_funcs->atomic_sro_compare_state)
+ continue;
+
+ if (!plane_funcs->atomic_sro_compare_state(plane,
+ &p,
+ new_plane_state,
+ readout_plane_state)) {
+ drm_warn(dev, "[PLANE:%d:%s] Committed and Readout PLANE state don't match\n",
+ plane->base.id, plane->name);
+ identical = false;
+ continue;
+ }
+ }
+
+ for_each_new_crtc_in_state(commit, crtc, new_crtc_state, i) {
+ const struct drm_crtc_funcs *crtc_funcs = crtc->funcs;
+ struct drm_crtc_state *readout_crtc_state;
+
+ readout_crtc_state = drm_atomic_sro_get_crtc_state(readout_state, crtc);
+ if (!readout_crtc_state) {
+ identical = false;
+ continue;
+ }
+
+ if (!crtc_funcs->atomic_sro_compare_state)
+ continue;
+
+ if (!crtc_funcs->atomic_sro_compare_state(crtc,
+ &p,
+ new_crtc_state,
+ readout_crtc_state)) {
+ drm_warn(dev, "[CRTC:%d:%s] Committed and Readout CRTC state don't match\n",
+ crtc->base.id, crtc->name);
+ identical = false;
+ continue;
+ }
+ }
+
+ for_each_new_connector_in_state(commit, conn, new_conn_state, i) {
+ const struct drm_connector_funcs *conn_funcs =
+ conn->funcs;
+ struct drm_connector_state *readout_conn_state;
+
+ readout_conn_state = drm_atomic_sro_get_connector_state(readout_state, conn);
+ if (!readout_conn_state) {
+ identical = false;
+ continue;
+ }
+
+ if (!conn_funcs->atomic_sro_compare_state)
+ continue;
+
+ if (!conn_funcs->atomic_sro_compare_state(conn,
+ &p,
+ new_conn_state,
+ readout_conn_state)) {
+ drm_warn(dev, "[CONNECTOR:%d:%s] Committed and Readout connector state don't match\n",
+ conn->base.id, conn->name);
+ identical = false;
+ continue;
+ }
+ }
+
+ for_each_new_private_obj_in_state(commit, obj, new_obj_state, i) {
+ const struct drm_private_state_funcs *obj_funcs = obj->funcs;
+ struct drm_private_state *readout_obj_state;
+
+ readout_obj_state = drm_atomic_sro_get_private_obj_state(readout_state, obj);
+ if (!readout_obj_state) {
+ identical = false;
+ continue;
+ }
+
+ if (!obj_funcs->atomic_sro_compare_state)
+ continue;
+
+ if (!obj_funcs->atomic_sro_compare_state(obj,
+ &p,
+ new_obj_state,
+ readout_obj_state)) {
+ drm_warn(dev, "Committed and Readout private object state don't match\n");
+ identical = false;
+ continue;
+ }
+ }
+
+ drm_atomic_sro_state_free(readout_state);
+
+ return identical;
+}
+EXPORT_SYMBOL(drm_atomic_sro_readout_and_compare);
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 17fef062d7e5..691b688aea58 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -729,11 +729,12 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
if (ret < 0)
goto err_reset_bridge;
}
if (bridge->funcs &&
- bridge->funcs->atomic_sro_readout_state)
+ bridge->funcs->atomic_sro_readout_state &&
+ bridge->funcs->atomic_sro_compare_state)
drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
&drm_bridge_priv_state_funcs_with_sro);
else
drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
&drm_bridge_priv_state_funcs);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 0e5c87f258ef..6e06d0615f62 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -339,10 +339,33 @@ struct drm_private_state_funcs {
* hardware state readout initialization sequence.
*/
void (*atomic_sro_install_state)(struct drm_private_obj *obj,
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:
*
* If driver subclasses &struct drm_private_state, it should implement
* this optional hook for printing additional driver specific state.
diff --git a/include/drm/drm_atomic_sro.h b/include/drm/drm_atomic_sro.h
index 195154850ab4..6529774d9ca8 100644
--- a/include/drm/drm_atomic_sro.h
+++ b/include/drm/drm_atomic_sro.h
@@ -1,10 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef DRM_ATOMIC_SRO_H_
#define DRM_ATOMIC_SRO_H_
+#include <linux/types.h>
+
+struct drm_atomic_commit;
struct drm_atomic_sro_state;
struct drm_connector;
struct drm_connector_state;
struct drm_crtc;
struct drm_crtc_state;
@@ -49,7 +52,8 @@ drm_atomic_sro_get_private_obj_state(struct drm_atomic_sro_state *state,
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);
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 9e3f9b82afc2..5cd648a9e75b 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -309,10 +309,32 @@ struct drm_bridge_funcs {
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_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:
*
* Duplicate the current bridge state object (which is guaranteed to be
* non-NULL).
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 50e9e94c2532..0703e969bc2c 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1821,10 +1821,32 @@ struct drm_connector_funcs {
* This callback is mandatory for atomic drivers.
*/
void (*atomic_destroy_state)(struct drm_connector *connector,
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:
*
* Decode a driver-private property value and store the decoded value
* into the passed-in state structure. Since the atomic core decodes all
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index a23364fa7e3e..bf5198c46ef3 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -728,10 +728,32 @@ struct drm_crtc_funcs {
* This callback is mandatory for atomic drivers.
*/
void (*atomic_destroy_state)(struct drm_crtc *crtc,
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:
*
* Decode a driver-private property value and store the decoded value
* into the passed-in state structure. Since the atomic core decodes all
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index 358c4f4062b4..66e245e2fb5b 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -490,10 +490,32 @@ struct drm_plane_funcs {
* This callback is mandatory for atomic drivers.
*/
void (*atomic_destroy_state)(struct drm_plane *plane,
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:
*
* Decode a driver-private property value and store the decoded value
* into the passed-in state structure. Since the atomic core decodes all
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 21/33] drm/atomic-state-helper: Provide comparison macros
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (19 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 20/33] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 22/33] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
` (13 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_sro_helper.c | 30 +++++
include/drm/drm_atomic_sro_helper.h | 220 ++++++++++++++++++++++++++++++++
2 files changed, 250 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_sro_helper.c b/drivers/gpu/drm/drm_atomic_sro_helper.c
index 50d1af53bfc2..692a5a24d762 100644
--- a/drivers/gpu/drm/drm_atomic_sro_helper.c
+++ b/drivers/gpu/drm/drm_atomic_sro_helper.c
@@ -405,5 +405,35 @@ int drm_atomic_helper_sro_readout_state(struct drm_device *dev)
drm_atomic_sro_state_free(state);
return 0;
}
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_sro_helper.h b/include/drm/drm_atomic_sro_helper.h
index a42ed8136a49..71340978ce7c 100644
--- a/include/drm/drm_atomic_sro_helper.h
+++ b/include/drm/drm_atomic_sro_helper.h
@@ -1,12 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef DRM_ATOMIC_SRO_HELPER_H_
#define DRM_ATOMIC_SRO_HELPER_H_
+#include <linux/string_choices.h>
+
struct drm_atomic_sro_state;
struct drm_device;
+struct drm_printer;
int
drm_atomic_helper_sro_readout_planes_state(struct drm_device *dev,
struct drm_atomic_sro_state *state);
int
@@ -23,6 +26,223 @@ drm_atomic_helper_sro_readout_private_objs_state(struct drm_device *dev,
struct drm_atomic_sro_state *state);
struct drm_atomic_sro_state *
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(n) " 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(n) " 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(n) " 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(n) " 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(n) " 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(n) " 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(n) " 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(n) " 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(n) " 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(n) " 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(n) " 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(n) " 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] 41+ messages in thread
* [PATCH v5 22/33] drm/atomic-state-helper: Provide atomic_compare_state helpers
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (20 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 21/33] drm/atomic-state-helper: Provide comparison macros Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 23/33] drm/atomic-helper: Document State Read-Out as a state initialization approach Maxime Ripard
` (12 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_sro_helper.c | 240 ++++++++++++++++++++++++++++++++
drivers/gpu/drm/drm_bridge.c | 16 +++
include/drm/drm_atomic_sro_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, 300 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_sro_helper.c b/drivers/gpu/drm/drm_atomic_sro_helper.c
index 692a5a24d762..46a733e4dcce 100644
--- a/drivers/gpu/drm/drm_atomic_sro_helper.c
+++ b/drivers/gpu/drm/drm_atomic_sro_helper.c
@@ -5,10 +5,11 @@
#include <drm/drm_atomic_sro_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#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>
#include <drm/drm_print.h>
#include <linux/gfp.h>
@@ -406,10 +407,249 @@ int drm_atomic_helper_sro_readout_state(struct drm_device *dev)
return 0;
}
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);
+
+ if (expected->fb && actual->fb) {
+ if (!drm_atomic_helper_fb_compare(p, expected->fb, actual->fb))
+ ret = false;
+ } else 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");
+ 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);
+
+ // TODO: damage clips
+
+ STATE_CHECK_BOOL(ret, p, plane->name, expected, actual, ignore_damage_clips);
+ STATE_CHECK_S32(ret, p, plane->name, expected, actual, src.x1);
+ STATE_CHECK_S32(ret, p, plane->name, expected, actual, src.x2);
+ STATE_CHECK_S32(ret, p, plane->name, expected, actual, src.y1);
+ STATE_CHECK_S32(ret, p, plane->name, expected, actual, src.y2);
+ STATE_CHECK_S32(ret, p, plane->name, expected, actual, dst.x1);
+ STATE_CHECK_S32(ret, p, plane->name, expected, actual, dst.x2);
+ STATE_CHECK_S32(ret, p, plane->name, expected, actual, dst.y1);
+ STATE_CHECK_S32(ret, p, plane->name, expected, actual, dst.y2);
+ STATE_CHECK_BOOL(ret, p, plane->name, expected, actual, visible);
+ STATE_CHECK_U32(ret, p, plane->name, expected, actual, scaling_filter);
+
+ return ret;
+}
+EXPORT_SYMBOL(drm_atomic_helper_plane_compare_state);
+
+/**
+ * drm_atomic_helper_crtc_compare_state - default &drm_crtc_funcs.atomic_sro_compare_state hook
+ * @crtc: the CRTC being compared
+ * @p: printer for reporting mismatches
+ * @expected: the committed &struct drm_crtc_state
+ * @actual: the &struct drm_crtc_state read out from hardware
+ *
+ * Compares the base &struct drm_crtc_state fields of @actual to
+ * @expected and reports any mismatches through @p. Drivers subclassing
+ * the CRTC state should call this first, then compare their own fields.
+ *
+ * RETURNS:
+ *
+ * True if the states are identical, false otherwise.
+ */
+bool drm_atomic_helper_crtc_compare_state(struct drm_crtc *crtc,
+ struct drm_printer *p,
+ struct drm_crtc_state *expected,
+ struct drm_crtc_state *actual)
+{
+ bool ret = true;
+
+ STATE_CHECK_PTR(ret, p, crtc->name, expected, actual, crtc);
+ STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, enable);
+ STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, active);
+ STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, no_vblank);
+ STATE_CHECK_U32(ret, p, crtc->name, expected, actual, plane_mask);
+ STATE_CHECK_U32(ret, p, crtc->name, expected, actual, connector_mask);
+ STATE_CHECK_U32(ret, p, crtc->name, expected, actual, encoder_mask);
+
+ STATE_CHECK_DISPLAY_MODE(ret, p, crtc->name, expected, actual, mode);
+ STATE_CHECK_DISPLAY_MODE(ret, p, crtc->name, expected, actual, adjusted_mode);
+ STATE_CHECK_PROPERTY_BLOB(ret, p, crtc->name, expected, actual, mode_blob);
+ STATE_CHECK_PROPERTY_BLOB(ret, p, crtc->name, expected, actual, degamma_lut);
+ STATE_CHECK_PROPERTY_BLOB(ret, p, crtc->name, expected, actual, ctm);
+ STATE_CHECK_PROPERTY_BLOB(ret, p, crtc->name, expected, actual, gamma_lut);
+ STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, vrr_enabled);
+ STATE_CHECK_BOOL(ret, p, crtc->name, expected, actual, self_refresh_active);
+ STATE_CHECK_U32(ret, p, crtc->name, expected, actual, scaling_filter);
+
+ return ret;
+}
+EXPORT_SYMBOL(drm_atomic_helper_crtc_compare_state);
+
+/**
+ * drm_atomic_helper_connector_compare_state - default &drm_connector_funcs.atomic_sro_compare_state hook
+ * @conn: the connector being compared
+ * @p: printer for reporting mismatches
+ * @expected: the committed &struct drm_connector_state
+ * @actual: the &struct drm_connector_state read out from hardware
+ *
+ * Compares the base &struct drm_connector_state fields of @actual to
+ * @expected and reports any mismatches through @p. Drivers subclassing
+ * the connector state should call this first, then compare their own
+ * fields.
+ *
+ * RETURNS:
+ *
+ * True if the states are identical, false otherwise.
+ */
+bool drm_atomic_helper_connector_compare_state(struct drm_connector *conn,
+ struct drm_printer *p,
+ struct drm_connector_state *expected,
+ struct drm_connector_state *actual)
+{
+ bool ret = true;
+
+ STATE_CHECK_PTR(ret, p, conn->name, expected, actual, connector);
+ STATE_CHECK_PTR(ret, p, conn->name, expected, actual, crtc);
+ STATE_CHECK_PTR(ret, p, conn->name, expected, actual, best_encoder);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, link_status);
+
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, tv.select_subconnector);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, tv.subconnector);
+
+ STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, self_refresh_aware);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, picture_aspect_ratio);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, content_type);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdcp_content_type);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, scaling_mode);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, content_protection);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, colorspace);
+
+ /*
+ * NOTE: We can't check max_bpc and max_requested_bpc because it
+ * will typically come from userspace and we can't read it out
+ * from the hardware.
+ */
+
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, privacy_screen_sw_state);
+ STATE_CHECK_PROPERTY_BLOB(ret, p, conn->name, expected, actual, hdr_output_metadata);
+
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdmi.broadcast_rgb);
+ STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.avi.set);
+ STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.avi.data);
+ STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.hdr_drm.set);
+ STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.hdr_drm.data);
+ STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.spd.set);
+ STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.spd.data);
+ STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.infoframes.hdmi.set);
+ STATE_CHECK_INFOFRAME(ret, p, conn->name, expected, actual, hdmi.infoframes.hdmi.data);
+ STATE_CHECK_BOOL(ret, p, conn->name, expected, actual, hdmi.is_limited_range);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdmi.output_bpc);
+ STATE_CHECK_U32(ret, p, conn->name, expected, actual, hdmi.output_format);
+ STATE_CHECK_U64(ret, p, conn->name, expected, actual, hdmi.tmds_char_rate);
+
+ 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
* @p: printer to report through
* @name: human-readable name of the object (e.g. plane or CRTC name)
* @field: name of the mismatching field
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 691b688aea58..d00886c78145 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -607,10 +607,25 @@ drm_bridge_atomic_readout_priv_state(struct drm_private_obj *obj,
}
return 0;
}
+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,
const struct drm_private_state *s)
{
const struct drm_bridge_state *state = drm_priv_to_bridge_state(s);
@@ -635,10 +650,11 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
.atomic_destroy_state = drm_bridge_atomic_destroy_priv_state,
.atomic_print_state = drm_bridge_atomic_print_priv_state,
};
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,
.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,
diff --git a/include/drm/drm_atomic_sro_helper.h b/include/drm/drm_atomic_sro_helper.h
index 71340978ce7c..d7c996e729ea 100644
--- a/include/drm/drm_atomic_sro_helper.h
+++ b/include/drm/drm_atomic_sro_helper.h
@@ -4,11 +4,19 @@
#define DRM_ATOMIC_SRO_HELPER_H_
#include <linux/string_choices.h>
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;
int
drm_atomic_helper_sro_readout_planes_state(struct drm_device *dev,
struct drm_atomic_sro_state *state);
@@ -26,10 +34,30 @@ drm_atomic_helper_sro_readout_private_objs_state(struct drm_device *dev,
struct drm_atomic_sro_state *state);
struct drm_atomic_sro_state *
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,
const char *name,
const char *field,
const char *format, ...);
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 5cd648a9e75b..22aa658c0d74 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -317,11 +317,14 @@ struct drm_bridge_funcs {
* 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.
+ * 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
* be left unassigned otherwise.
*
* RETURNS:
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 0703e969bc2c..f4d971457cb4 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1829,11 +1829,14 @@ struct drm_connector_funcs {
* 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.
+ * 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
* be left unassigned otherwise.
*
* RETURNS:
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index bf5198c46ef3..f4656dd8de72 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -736,11 +736,14 @@ struct drm_crtc_funcs {
* 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.
+ * 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
* be left unassigned otherwise.
*
* RETURNS:
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index 66e245e2fb5b..15cde65188a4 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -498,11 +498,14 @@ struct drm_plane_funcs {
* 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.
+ * 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
* be left unassigned otherwise.
*
* RETURNS:
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 23/33] drm/atomic-helper: Document State Read-Out as a state initialization approach
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (21 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 22/33] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
@ 2026-09-07 13:23 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 24/33] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
` (11 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:23 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.
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 354fa9c6aca8..bd61c56ceca6 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -45,17 +45,25 @@
* DOC: atomic state reset and initialization
*
* Both the drm core and the atomic helpers assume that there is always the full
* 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
* and resume a lot. For drivers using drm_mode_config_reset() a complete recipe
* is implemented in drm_atomic_helper_suspend() and drm_atomic_helper_resume().
* For other drivers the building blocks are split out, see the documentation
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 24/33] drm/encoder: Create atomic_sro_get_current_crtc hook
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (22 preceding siblings ...)
2026-09-07 13:23 ` [PATCH v5 23/33] drm/atomic-helper: Document State Read-Out as a state initialization approach Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 25/33] drm/bridge: display-connector: Implement readout support Maxime Ripard
` (10 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
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
@@ -87,10 +87,28 @@ struct drm_encoder_funcs {
* @debugfs_init:
*
* Allows encoders to create encoder-specific debugfs files.
*/
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);
};
/**
* struct drm_encoder - central DRM encoder structure
* @dev: parent DRM device
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 25/33] drm/bridge: display-connector: Implement readout support
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (23 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 24/33] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 26/33] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
` (9 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/bridge/display-connector.c | 64 ++++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)
diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
index eb3412ec30a0..7f285b7c1e84 100644
--- a/drivers/gpu/drm/bridge/display-connector.c
+++ b/drivers/gpu/drm/bridge/display-connector.c
@@ -12,11 +12,14 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>
+#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_sro.h>
+#include <drm/drm_atomic_sro_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_edid.h>
struct display_connector {
struct drm_bridge bridge;
@@ -211,10 +214,69 @@ static u32 *display_connector_get_input_bus_fmts(struct drm_bridge *bridge,
return prev_bridge->funcs->atomic_get_input_bus_fmts(prev_bridge, prev_bridge_state,
crtc_state, conn_state, output_fmt,
num_input_fmts);
}
+/*
+ * 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);
+ struct drm_bridge_state *prev_bridge_state;
+
+ if (!prev_priv_state)
+ return -ENODEV;
+
+ 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;
+ } else {
+ bridge_state->input_bus_cfg.format = MEDIA_BUS_FMT_FIXED;
+ bridge_state->input_bus_cfg.flags = di->bus_flags;
+ }
+
+ 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,
.destroy = display_connector_destroy,
.detect = display_connector_bridge_detect,
.hpd_enable = display_connector_hpd_enable,
@@ -223,10 +285,12 @@ static const struct drm_bridge_funcs display_connector_bridge_funcs = {
.atomic_get_output_bus_fmts = display_connector_get_output_bus_fmts,
.atomic_get_input_bus_fmts = display_connector_get_input_bus_fmts,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.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,
};
static irqreturn_t display_connector_hpd_irq(int irq, void *arg)
{
struct display_connector *conn = arg;
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 26/33] drm/bridge_connector: Implement hw readout for connector
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (24 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 25/33] drm/bridge: display-connector: Implement readout support Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 27/33] drm/tidss: crtc: Add atomic_print_state implementation Maxime Ripard
` (8 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/display/drm_bridge_connector.c | 35 ++++++++++++++++++++++++++
include/drm/drm_bridge.h | 27 ++++++++++++++++++++
2 files changed, 62 insertions(+)
diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 00feb73e63d7..9a51f75df5a7 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -8,10 +8,11 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/property.h>
#include <linux/slab.h>
+#include <drm/drm_atomic_sro_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_connector.h>
#include <drm/drm_device.h>
@@ -64,10 +65,18 @@ struct drm_bridge_connector {
* @encoder:
*
* The encoder at the start of the bridges chain.
*/
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:
*
* The last bridge in the chain (closest to the connector) that provides
* EDID read support, if any (see &DRM_BRIDGE_OP_EDID).
@@ -284,10 +293,30 @@ drm_bridge_connector_create_state(struct drm_connector *connector)
conn_state);
return conn_state;
}
+static int
+drm_bridge_connector_readout_state(struct drm_connector *connector,
+ struct drm_atomic_sro_state *state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_bridge_connector *bridge_connector =
+ to_drm_bridge_connector(connector);
+ struct drm_bridge *readout =
+ bridge_connector->bridge_connector_hw_readout;
+ int ret;
+
+ if (readout) {
+ ret = readout->funcs->atomic_sro_connector_readout(readout, state, conn_state);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static enum drm_connector_color_format
drm_bridge_connector_color_format(const struct drm_connector_state *conn_state)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(conn_state->connector);
@@ -312,10 +341,12 @@ drm_bridge_connector_color_format(const struct drm_connector_state *conn_state)
static const struct drm_connector_funcs drm_bridge_connector_funcs = {
.detect = drm_bridge_connector_detect,
.force = drm_bridge_connector_force,
.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,
@@ -861,10 +892,14 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
if (!bridge->interlace_allowed)
connector->interlace_allowed = false;
if (!bridge->ycbcr_420_allowed)
connector->ycbcr_420_allowed = false;
+ if (bridge->ops & DRM_BRIDGE_OP_CONNECTOR_HW_READOUT) {
+ drm_bridge_put(bridge_connector->bridge_connector_hw_readout);
+ bridge_connector->bridge_connector_hw_readout = drm_bridge_get(bridge);
+ }
/*
* Ensure the last bridge declares OP_EDID or OP_MODES or both.
*/
if (bridge->ops & DRM_BRIDGE_OP_EDID || bridge->ops & DRM_BRIDGE_OP_MODES) {
drm_bridge_put(bridge_connector->bridge_edid);
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 22aa658c0d74..00da1ba1dd7b 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -900,10 +900,30 @@ struct drm_bridge_funcs {
*/
int (*dp_audio_mute_stream)(struct drm_bridge *bridge,
struct drm_connector *connector,
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.
+ *
+ * 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:
*
* Allows bridges to create bridge-specific debugfs files.
*/
@@ -1040,10 +1060,17 @@ enum drm_bridge_ops {
* @DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME: The bridge supports
* &drm_bridge_funcs->hdmi_write_spd_infoframe and
* &drm_bridge_funcs->hdmi_clear_spd_infoframe callbacks.
*/
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),
};
/**
* struct drm_bridge - central DRM bridge control structure
*/
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 27/33] drm/tidss: crtc: Add atomic_print_state implementation
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (25 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 26/33] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 28/33] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
` (7 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/tidss/tidss_crtc.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
index 400329aa2200..3da49f77fa08 100644
--- a/drivers/gpu/drm/tidss/tidss_crtc.c
+++ b/drivers/gpu/drm/tidss/tidss_crtc.c
@@ -399,17 +399,27 @@ static void tidss_crtc_destroy(struct drm_crtc *crtc)
drm_crtc_cleanup(crtc);
kfree(tcrtc);
}
+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,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_create_state = tidss_crtc_create_state,
.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,
};
struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss,
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 28/33] drm/tidss: dispc: Improve mode checking logs
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (26 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 27/33] drm/tidss: crtc: Add atomic_print_state implementation Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 29/33] drm/tidss: Switch to drm_mode_config_create_initial_state() Maxime Ripard
` (6 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/tidss/tidss_dispc.c | 45 +++++++++++++++++++++++++++++--------
1 file changed, 36 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
index 2a0ec11a6c90..a19b74b0ca30 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1302,10 +1302,12 @@ unsigned int dispc_pclk_diff(unsigned long rate, unsigned long real_rate)
}
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;
/*
* For VP's with external clocking, clock operations must be
* delegated to respective driver, so we skip the check here.
@@ -1316,51 +1318,65 @@ static int check_pixel_clock(struct dispc_device *dispc, u32 hw_videoport,
round_clock = clk_round_rate(dispc->vp_clk[hw_videoport], clock);
/*
* To keep the check consistent with dispc_vp_set_clk_rate(), we
* 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, "Mode pixel clock below hardware minimum pixel clock.");
return -EINVAL;
+ }
return 0;
}
enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
u32 hw_videoport,
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;
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))
return MODE_CLOCK_RANGE;
/* 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;
+ }
/*
* Enforce the output width is divisible by 2. Actually this
* is only needed in following cases:
* - YUV output selected (BT656, BT1120)
* - Dithering enabled
* - TDM with TDMCycleFormat == 3
* 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;
hsw = mode->hsync_end - mode->hsync_start;
hbp = mode->htotal - mode->hsync_end;
@@ -1368,29 +1384,40 @@ enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
vsw = mode->vsync_end - mode->vsync_start;
vbp = mode->vtotal - mode->vsync_end;
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) {
const unsigned int bpp = 4;
u64 bandwidth;
bandwidth = 1000 * mode->clock;
bandwidth = bandwidth * mode->hdisplay * mode->vdisplay * bpp;
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;
+ }
}
+ drm_dbg(dev, "Mode is valid.");
return MODE_OK;
}
int dispc_vp_enable_clk(struct dispc_device *dispc, u32 hw_videoport)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 29/33] drm/tidss: Switch to drm_mode_config_create_initial_state()
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (27 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 28/33] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 30/33] drm/tidss: Implement hw_reset Maxime Ripard
` (5 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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 drm_mode_config_create_initial_state() exists to create the
initial state, use it instead of drm_mode_config_reset() during
driver probe.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/tidss/tidss_drv.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/tidss/tidss_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c
index 1c8cc18bc53c..f5099d5d6e32 100644
--- a/drivers/gpu/drm/tidss/tidss_drv.c
+++ b/drivers/gpu/drm/tidss/tidss_drv.c
@@ -169,11 +169,15 @@ static int tidss_probe(struct platform_device *pdev)
goto err_runtime_suspend;
}
drm_kms_helper_poll_init(ddev);
- drm_mode_config_reset(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) {
dev_err(dev, "failed to register DRM device\n");
goto err_irq_uninstall;
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 30/33] drm/tidss: Implement hw_reset
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (28 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 29/33] drm/tidss: Switch to drm_mode_config_create_initial_state() Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 31/33] drm/tidss: Implement readout support Maxime Ripard
` (4 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/tidss/tidss_dispc.c | 6 +-----
drivers/gpu/drm/tidss/tidss_dispc.h | 1 +
drivers/gpu/drm/tidss/tidss_kms.c | 14 ++++++++++++++
3 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
index a19b74b0ca30..fdd3b166c118 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -2907,11 +2907,11 @@ static int dispc_softreset(struct dispc_device *dispc)
}
return 0;
}
-static int dispc_init_hw(struct dispc_device *dispc)
+int dispc_reset_hw(struct dispc_device *dispc)
{
struct device *dev = dispc->dev;
int ret;
ret = pm_runtime_set_active(dev);
@@ -3057,13 +3057,9 @@ int dispc_init(struct tidss_device *tidss)
dev_dbg(dev, "DSS fclk %lu Hz\n", clk_get_rate(dispc->fclk));
of_property_read_u32(dispc->dev->of_node, "max-memory-bandwidth",
&dispc->memory_bandwidth_limit);
- r = dispc_init_hw(dispc);
- if (r)
- return r;
-
tidss->dispc = dispc;
return 0;
}
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
index 2183254f1ab7..e51a2088b40c 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.h
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -143,9 +143,10 @@ void dispc_plane_setup(struct dispc_device *dispc, u32 hw_plane,
struct drm_plane_state *state,
u32 hw_videoport);
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);
#endif
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
index 70c14c3be10d..3b97c08c4f71 100644
--- a/drivers/gpu/drm/tidss/tidss_kms.c
+++ b/drivers/gpu/drm/tidss/tidss_kms.c
@@ -8,10 +8,11 @@
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include "tidss_crtc.h"
#include "tidss_dispc.h"
#include "tidss_drv.h"
@@ -114,14 +115,27 @@ static int tidss_atomic_check(struct drm_device *ddev,
}
return 0;
}
+static void tidss_hw_reset(struct drm_device *dev)
+{
+ struct tidss_device *tidss = to_tidss(dev);
+ int ret;
+
+ ret = dispc_reset_hw(tidss->dispc);
+ if (ret)
+ drm_warn(dev, "Couldn't reset dispc");
+
+ drm_mode_config_helper_hw_reset(dev);
+}
+
static const struct drm_mode_config_funcs mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.atomic_check = tidss_atomic_check,
.atomic_commit = drm_atomic_helper_commit,
+ .hw_reset = tidss_hw_reset,
};
static int tidss_dispc_modeset_init(struct tidss_device *tidss)
{
struct device *dev = tidss->dev;
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 31/33] drm/tidss: Implement readout support
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (29 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 30/33] drm/tidss: Implement hw_reset Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 32/33] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
` (3 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/tidss/tidss_crtc.c | 94 +++++++++++
drivers/gpu/drm/tidss/tidss_dispc.c | 283 ++++++++++++++++++++++++++++++++++
drivers/gpu/drm/tidss/tidss_dispc.h | 15 ++
drivers/gpu/drm/tidss/tidss_encoder.c | 42 +++++
drivers/gpu/drm/tidss/tidss_kms.c | 6 +-
drivers/gpu/drm/tidss/tidss_plane.c | 154 ++++++++++++++++++
6 files changed, 593 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
index 3da49f77fa08..666b950278ee 100644
--- a/drivers/gpu/drm/tidss/tidss_crtc.c
+++ b/drivers/gpu/drm/tidss/tidss_crtc.c
@@ -2,12 +2,16 @@
/*
* Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
+#include <linux/clk.h>
+
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_sro_helper.h>
+#include <drm/drm_atomic_uapi.h>
#include <drm/drm_crtc.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
@@ -368,10 +372,97 @@ static struct drm_crtc_state *tidss_crtc_create_state(struct drm_crtc *crtc)
__drm_atomic_helper_crtc_state_init(&tstate->base, 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;
+
+ /*
+ * The display is active, we need to enable our clock to have
+ * proper reference count.
+ */
+ WARN_ON(dispc_vp_enable_clk(tidss->dispc, tcrtc->hw_videoport));
+
+ 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);
+ tstate->bus_format = dispc_vp_get_bus_format(dispc, tcrtc->hw_videoport);
+
+ /*
+ * The active connectors and planes will be filled by their
+ * respective readout callbacks.
+ */
+
+out:
+ tidss_runtime_put(tidss);
+ return 0;
+
+err_runtime_put:
+ tidss_runtime_put(tidss);
+ return ret;
+}
+
+static void tidss_crtc_install_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *crtc_state)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = to_tidss(ddev);
+ int ret;
+
+ tidss_runtime_get(tidss);
+
+ ret = dispc_vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
+ if (ret != 0)
+ return;
+
+ /* Turn vertical blanking interrupt reporting on. */
+ drm_crtc_vblank_on(crtc);
+}
+
+static bool tidss_crtc_compare_state(struct drm_crtc *crtc,
+ struct drm_printer *p,
+ struct drm_crtc_state *expected,
+ struct drm_crtc_state *actual)
+{
+ struct tidss_crtc_state *t_expected = to_tidss_crtc_state(expected);
+ struct tidss_crtc_state *t_actual = to_tidss_crtc_state(actual);
+ bool ret = drm_atomic_helper_crtc_compare_state(crtc, p, expected, actual);
+
+ STATE_CHECK_U32_X(ret, p, crtc->name, t_expected, t_actual, bus_format);
+ STATE_CHECK_U32_X(ret, p, crtc->name, t_expected, t_actual, bus_flags);
+
+ return ret;
+}
+
static struct drm_crtc_state *tidss_crtc_duplicate_state(struct drm_crtc *crtc)
{
struct tidss_crtc_state *state, *current_state;
if (WARN_ON(!crtc->state))
@@ -413,10 +504,13 @@ static void tidss_crtc_atomic_print_state(struct drm_printer *p,
static const struct drm_crtc_funcs tidss_crtc_funcs = {
.destroy = tidss_crtc_destroy,
.set_config = drm_atomic_helper_set_config,
.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,
.atomic_print_state = tidss_crtc_atomic_print_state,
.enable_vblank = tidss_crtc_enable_vblank,
.disable_vblank = tidss_crtc_disable_vblank,
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
index fdd3b166c118..50e6d9fb9208 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1206,10 +1206,16 @@ void dispc_vp_enable(struct dispc_device *dispc, u32 hw_videoport)
{
VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1,
DISPC_VP_CONTROL_ENABLE_MASK);
}
+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)
{
VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 0,
DISPC_VP_CONTROL_ENABLE_MASK);
}
@@ -1459,10 +1465,205 @@ int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport,
hw_videoport, clk_get_rate(dispc->vp_clk[hw_videoport]), rate);
return 0;
}
+static unsigned long calc_pixel_clock_hz(unsigned int htotal,
+ unsigned int vtotal,
+ unsigned int refresh,
+ unsigned int freq_div)
+{
+ unsigned long rate = (unsigned long)htotal * vtotal * refresh;
+
+ return (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;
+ }
+ }
+ }
+
+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;
+}
+
+u32 dispc_vp_get_bus_format(struct dispc_device *dispc, u32 hw_videoport)
+{
+ static const u32 datalines_to_width[] = { 12, 16, 18, 24, 30, 36 };
+ 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
+ return 0;
+
+ 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)
+ return dispc_bus_formats[i].bus_fmt;
+ }
+
+ return 0;
+ }
+
+ 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)
+ return dispc_bus_formats[i].bus_fmt;
+ }
+
+ return 0;
+}
+
+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,
u32 hw_plane, u32 hw_videoport,
u32 x, u32 y, u32 layer)
{
@@ -2104,10 +2305,21 @@ static const struct {
{ DRM_FORMAT_UYVY, 0x3f, },
{ DRM_FORMAT_NV12, 0x3d, },
};
+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)
{
unsigned int i;
@@ -2269,16 +2481,87 @@ void dispc_plane_setup(struct dispc_device *dispc, u32 hw_plane,
else
VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, 0,
DISPC_VID_ATTRIBUTES_PREMULTIPLYALPHA_MASK);
}
+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));
+}
+
+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)
{
VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable,
DISPC_VID_ATTRIBUTES_ENABLE_MASK);
}
+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)
{
return VID_REG_GET(dispc, hw_plane, DISPC_VID_BUF_SIZE_STATUS,
DISPC_VID_BUF_SIZE_STATUS_BUFSIZE_MASK);
}
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
index e51a2088b40c..e469705c5ecc 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.h
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -115,10 +115,11 @@ void dispc_ovr_enable_layer(struct dispc_device *dispc,
u32 hw_videoport, u32 layer, bool enable);
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);
bool dispc_vp_go_busy(struct dispc_device *dispc, u32 hw_videoport);
void dispc_vp_go(struct dispc_device *dispc, u32 hw_videoport);
int dispc_vp_bus_check(struct dispc_device *dispc, u32 hw_videoport,
@@ -130,21 +131,35 @@ int dispc_vp_enable_clk(struct dispc_device *dispc, u32 hw_videoport);
void dispc_vp_disable_clk(struct dispc_device *dispc, u32 hw_videoport);
int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport,
unsigned long rate);
void dispc_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
const struct drm_crtc_state *state, bool newmodeset);
+int dispc_vp_readout_mode(struct dispc_device *dispc,
+ u32 hw_videoport,
+ struct drm_display_mode *mode);
+u32 dispc_vp_get_bus_format(struct dispc_device *dispc, u32 hw_videoport);
+u32 dispc_vp_get_bus_flags(struct dispc_device *dispc, u32 hw_videoport);
int dispc_runtime_suspend(struct dispc_device *dispc);
int dispc_runtime_resume(struct dispc_device *dispc);
int dispc_plane_check(struct dispc_device *dispc, u32 hw_plane,
const struct drm_plane_state *state,
u32 hw_videoport);
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);
+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);
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_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c
index 10dbcc6cdf6a..cb4b8feb90d2 100644
--- a/drivers/gpu/drm/tidss/tidss_encoder.c
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -3,12 +3,14 @@
* Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <linux/export.h>
+#include <linux/media-bus-format.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_sro_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_encoder.h>
#include <drm/drm_crtc.h>
#include <drm/drm_modeset_helper_vtables.h>
@@ -71,16 +73,56 @@ static int tidss_bridge_atomic_check(struct drm_bridge *bridge,
}
return 0;
}
+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,
.atomic_check = tidss_bridge_atomic_check,
.atomic_create_state = drm_atomic_helper_bridge_create_state,
.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,
};
static const struct drm_encoder_funcs tidss_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
index 3b97c08c4f71..6fa1a8c082dc 100644
--- a/drivers/gpu/drm/tidss/tidss_kms.c
+++ b/drivers/gpu/drm/tidss/tidss_kms.c
@@ -4,10 +4,12 @@
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_sro.h>
+#include <drm/drm_atomic_sro_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
@@ -62,11 +64,12 @@ static void tidss_atomic_commit_tail(struct drm_atomic_commit *old_state)
tidss_runtime_put(tidss);
}
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,
};
static int tidss_atomic_check(struct drm_device *ddev,
struct drm_atomic_commit *state)
{
@@ -131,10 +134,11 @@ static void tidss_hw_reset(struct drm_device *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,
+ .atomic_sro_readout_state = drm_atomic_helper_sro_readout_state,
.hw_reset = tidss_hw_reset,
};
static int tidss_dispc_modeset_init(struct tidss_device *tidss)
{
diff --git a/drivers/gpu/drm/tidss/tidss_plane.c b/drivers/gpu/drm/tidss/tidss_plane.c
index 6d82976c2db1..eddbe33bbab7 100644
--- a/drivers/gpu/drm/tidss/tidss_plane.c
+++ b/drivers/gpu/drm/tidss/tidss_plane.c
@@ -4,16 +4,19 @@
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_sro.h>
+#include <drm/drm_atomic_sro_helper.h>
#include <drm/drm_blend.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
#include "tidss_crtc.h"
#include "tidss_dispc.h"
#include "tidss_drv.h"
#include "tidss_plane.h"
@@ -173,17 +176,168 @@ static const struct drm_plane_helper_funcs tidss_primary_plane_helper_funcs = {
.atomic_enable = tidss_plane_atomic_enable,
.atomic_disable = tidss_plane_atomic_disable,
.get_scanout_buffer = drm_fb_dma_get_scanout_buffer,
};
+static const struct drm_framebuffer_funcs tidss_plane_readout_fb_funcs = {
+ .destroy = drm_gem_fb_destroy,
+};
+
+static struct drm_framebuffer *tidss_plane_readout_fb(struct drm_plane *plane)
+{
+ struct drm_device *ddev = plane->dev;
+ struct tidss_device *tidss = to_tidss(ddev);
+ struct dispc_device *dispc = tidss->dispc;
+ struct tidss_plane *tplane = to_tidss_plane(plane);
+ const struct drm_format_info *info;
+ struct drm_framebuffer *fb;
+ int ret;
+
+ fb = kzalloc_obj(*fb);
+ if (!fb)
+ return ERR_PTR(-ENOMEM);
+
+ fb->dev = plane->dev;
+
+ info = dispc_plane_get_current_format(dispc, tplane->hw_plane_id);
+ if (IS_ERR(info)) {
+ ret = PTR_ERR(info);
+ goto err_free_fb;
+ }
+
+ // TODO: Figure out YUV and multiplanar formats
+ 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);
+
+ /*
+ * TODO: Figure out what the row increment is about exactly and if we should
+ * take it into account?
+ */
+ fb->pitches[0] = fb->width * (drm_format_info_bpp(info, 0) / 8);
+
+ // TODO: Figure out the offsets
+ fb->offsets[0] = 0;
+
+ ret = drm_framebuffer_init(plane->dev, fb, &tidss_plane_readout_fb_funcs);
+ if (ret) {
+ kfree(fb);
+ return ERR_PTR(ret);
+ }
+
+ return fb;
+
+err_free_fb:
+ kfree(fb);
+ return ERR_PTR(ret);
+}
+
+static struct drm_crtc *tidss_plane_readout_crtc(struct drm_plane *plane)
+{
+ struct drm_device *dev = plane->dev;
+
+ if (dev->num_crtcs != 1)
+ return ERR_PTR(-EINVAL);
+
+ return list_first_entry(&dev->mode_config.crtc_list, struct drm_crtc, head);
+}
+
+static int tidss_plane_atomic_readout_state(struct drm_plane *plane,
+ struct drm_atomic_sro_state *state,
+ struct drm_plane_state *plane_state)
+{
+ struct drm_device *ddev = plane->dev;
+ struct tidss_device *tidss = to_tidss(ddev);
+ struct dispc_device *dispc = tidss->dispc;
+ struct tidss_plane *tplane = to_tidss_plane(plane);
+ struct drm_crtc_state *crtc_state;
+ struct drm_framebuffer *fb;
+ struct drm_crtc *crtc;
+ unsigned int in_w, in_h;
+ int ret;
+
+ tidss_runtime_get(tidss);
+
+ if (!dispc_plane_is_enabled(dispc, tplane->hw_plane_id))
+ goto out;
+
+ fb = tidss_plane_readout_fb(plane);
+ if (IS_ERR(fb)) {
+ ret = PTR_ERR(fb);
+ goto err_runtime_pm;
+ }
+
+ crtc = tidss_plane_readout_crtc(plane);
+ if (IS_ERR(crtc)) {
+ ret = PTR_ERR(crtc);
+ goto err_runtime_pm;
+ }
+
+ plane_state->fb = fb;
+ plane_state->crtc = crtc;
+ plane_state->visible = true;
+
+ dispc_plane_get_picture_size(dispc, tplane->hw_plane_id, &in_w, &in_h);
+ plane_state->src_w = in_w << 16;
+ plane_state->src_h = in_h << 16;
+
+ dispc_plane_get_size(dispc, tplane->hw_plane_id, &plane_state->crtc_w,
+ &plane_state->crtc_h);
+
+ // TODO: Handle crtc_x/crtc_x/src_x/src_y
+ // crtc_x/crtc_y are handled by DISPC_OVR_ATTRIBUTES / OVR1_DSS_ATTRIBUTES
+
+ // TODO: Handle zpos, see DISPC_OVR_ATTRIBUTES / OVR1_DSS_ATTRIBUTES
+
+ plane_state->src.x1 = 0;
+ plane_state->src.x2 = plane_state->src_w;
+ plane_state->src.y1 = 0;
+ plane_state->src.y2 = plane_state->src_h;
+ plane_state->dst.x1 = 0;
+ plane_state->dst.x2 = plane_state->crtc_w;
+ plane_state->dst.y1 = 0;
+ plane_state->dst.y2 = plane_state->crtc_h;
+
+ plane_state->alpha =
+ dispc_plane_get_global_alpha(dispc, tplane->hw_plane_id) << 8 | 0xff;
+ plane_state->pixel_blend_mode =
+ dispc_plane_get_blend_mode(dispc, tplane->hw_plane_id);
+
+ // TODO: If YUV, handle color encoding and range
+
+ 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,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_destroy,
.atomic_create_state = drm_atomic_helper_plane_create_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,
+ .atomic_sro_readout_state = tidss_plane_atomic_readout_state,
};
struct tidss_plane *tidss_plane_create(struct tidss_device *tidss,
u32 hw_plane_id, u32 plane_type,
u32 crtc_mask, const u32 *formats,
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 32/33] drm/tidss: encoder: Implement atomic_sro_get_current_crtc
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (30 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 31/33] drm/tidss: Implement readout support Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 33/33] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
` (2 subsequent siblings)
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
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 cb4b8feb90d2..ec995335953b 100644
--- a/drivers/gpu/drm/tidss/tidss_encoder.c
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -121,11 +121,26 @@ static const struct drm_bridge_funcs tidss_bridge_funcs = {
.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,
};
+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,
};
int tidss_encoder_create(struct tidss_device *tidss,
struct drm_bridge *next_bridge,
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v5 33/33] drm/bridge: sii902x: Implement hw state readout
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (31 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 32/33] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
@ 2026-09-07 13:24 ` Maxime Ripard
2026-09-15 8:06 ` [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
2026-09-15 12:34 ` Tomi Valkeinen
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-07 13:24 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.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/bridge/sii902x.c | 82 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 81 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
index 9aac44ed5b67..332945f3c9f7 100644
--- a/drivers/gpu/drm/bridge/sii902x.c
+++ b/drivers/gpu/drm/bridge/sii902x.c
@@ -20,10 +20,12 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/clk.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_sro.h>
+#include <drm/drm_atomic_sro_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
@@ -535,20 +537,97 @@ sii902x_bridge_mode_valid(struct drm_bridge *bridge,
return MODE_CLOCK_HIGH;
return MODE_OK;
}
+static int
+sii902x_bridge_connector_readout(struct drm_bridge *bridge,
+ struct drm_atomic_sro_state *state,
+ struct drm_connector_state *conn_state)
+{
+ struct sii902x *sii902x = bridge_to_sii902x(bridge);
+ struct drm_connector *connector = conn_state->connector;
+ struct drm_crtc_state *crtc_state;
+ struct drm_encoder *encoder;
+ struct drm_crtc *crtc;
+
+ if (regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN))
+ return 0;
+
+ encoder = bridge->encoder;
+ crtc = encoder->funcs->atomic_sro_get_current_crtc(encoder);
+ if (!crtc)
+ return -ENODEV;
+
+ crtc_state = drm_atomic_sro_get_crtc_state(state, crtc);
+ if (!crtc_state)
+ return -ENODEV;
+
+ crtc_state->encoder_mask |= drm_encoder_mask(encoder);
+ crtc_state->connector_mask |= drm_connector_mask(connector);
+
+ /*
+ * drm_atomic_set_crtc_for_connector() takes a connector reference
+ * when setting the crtc pointer, which is later released by
+ * __drm_atomic_helper_connector_destroy_state(). SRO readout
+ * bypasses that path, so take the reference explicitly.
+ */
+ drm_connector_get(connector);
+ conn_state->crtc = crtc;
+ conn_state->best_encoder = encoder;
+
+ return 0;
+}
+
+static int sii902x_bridge_readout_state(struct drm_bridge *bridge,
+ struct drm_atomic_sro_state *state,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct sii902x *sii902x = bridge_to_sii902x(bridge);
+ u32 input_format;
+
+ if (regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN))
+ return 0;
+
+ switch (sii902x->bus_width) {
+ case 16:
+ input_format = MEDIA_BUS_FMT_RGB565_1X16;
+ break;
+ case 18:
+ input_format = MEDIA_BUS_FMT_RGB666_1X18;
+ break;
+ case 24:
+ input_format = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ bridge_state->input_bus_cfg.format = input_format;
+ bridge_state->input_bus_cfg.flags = bridge->timings->input_bus_flags;
+
+ /* The sii902x is a passthrough transmitter, output format equals input. */
+ bridge_state->output_bus_cfg.format = input_format;
+
+ return 0;
+}
+
static const struct drm_bridge_funcs sii902x_bridge_funcs = {
.attach = sii902x_bridge_attach,
.mode_set = sii902x_bridge_mode_set,
.atomic_disable = sii902x_bridge_atomic_disable,
.atomic_enable = sii902x_bridge_atomic_enable,
.detect = sii902x_bridge_detect,
.edid_read = sii902x_bridge_edid_read,
.atomic_create_state = drm_atomic_helper_bridge_create_state,
.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,
.mode_valid = sii902x_bridge_mode_valid,
};
@@ -1131,11 +1210,12 @@ static int sii902x_init(struct sii902x *sii902x)
if (ret)
goto err_unreg_audio;
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;
if (sii902x->i2c->irq > 0)
sii902x->bridge.ops |= DRM_BRIDGE_OP_HPD;
--
2.55.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* Re: [PATCH v5 00/33] drm: Implement state readout support
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (32 preceding siblings ...)
2026-09-07 13:24 ` [PATCH v5 33/33] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
@ 2026-09-15 8:06 ` Maxime Ripard
2026-09-15 12:34 ` Tomi Valkeinen
34 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-15 8:06 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, Simona Vetter, Laurent Pinchart
[-- Attachment #1: Type: text/plain, Size: 1449 bytes --]
On Mon, Sep 07, 2026 at 03:23:36PM +0200, Maxime Ripard wrote:
> 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
Merged patch 1, 2, 6, 8 and 19 to drm-misc-next
Maxime
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 273 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v5 06/33] drm/bridge: Implement atomic_print_state
2026-09-07 13:23 ` [PATCH v5 06/33] drm/bridge: Implement atomic_print_state Maxime Ripard
@ 2026-09-15 9:26 ` Geert Uytterhoeven
2026-09-15 10:08 ` Maxime Ripard
0 siblings, 1 reply; 41+ messages in thread
From: Geert Uytterhoeven @ 2026-09-15 9:26 UTC (permalink / raw)
To: mripard
Cc: Laurent.pinchart, airlied, andrzej.hajda, devarsht, dri-devel,
jernej.skrabec, jonas, jyri.sarha, laurent.pinchart+renesas,
maarten.lankhorst, neil.armstrong, rfoss, simona, tomi.valkeinen,
tzimmermann
Hi Maxime,
> Bridges have some fields in their state worth printing, but we don't
> provide an atomic_print_state implementation to show those fields.
>
> Provide one.
>
> Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
> Signed-off-by: Maxime Ripard <mripard@kernel.org>
Thanks for your patch, which is now commit 4a7a146f1cc6791a
("drm/bridge: Implement atomic_print_state") in drm-misc/for-linux-next.
> --- a/drivers/gpu/drm/drm_bridge.c
> +++ b/drivers/gpu/drm/drm_bridge.c
> @@ -548,14 +548,35 @@ drm_bridge_atomic_create_priv_state(struct drm_private_obj *obj)
> return ERR_CAST(state);
>
> return &state->base;
> }
>
> +static void
> +drm_bridge_atomic_print_priv_state(struct drm_printer *p,
> + const struct drm_private_state *s)
> +{
> + const struct drm_bridge_state *state = drm_priv_to_bridge_state(s);
warning: passing argument 1 of ‘drm_priv_to_bridge_state’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
Apparently you lost "[PATCH v2 4/28] drm/atomic: Convert
drm_priv_to_bridge_state to container_of_const"[1] along the way?
> + struct drm_bridge *bridge = drm_priv_to_bridge(s->obj);
> +
> + if (bridge->of_node)
> + drm_printf(p, "bridge: %ps (%pOFfc)\n", bridge->funcs, bridge->of_node);
> + else
> + drm_printf(p, "bridge: %ps\n", bridge->funcs);
> +
> + drm_printf_indent(p, 1, "input bus configuration:");
> + drm_printf_indent(p, 2, "code: %04x", state->input_bus_cfg.format);
> + drm_printf_indent(p, 2, "flags: %08x", state->input_bus_cfg.flags);
> + drm_printf_indent(p, 1, "output bus configuration:");
> + drm_printf_indent(p, 2, "code: %04x", state->output_bus_cfg.format);
> + drm_printf_indent(p, 2, "flags: %08x", state->output_bus_cfg.flags);
> +}
> +
[1] https://lore.kernel.org/20260423-drm-state-readout-v2-4-8549f87cb978@kernel.org
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v5 03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const
2026-09-07 13:23 ` [PATCH v5 03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
@ 2026-09-15 9:29 ` Javier Martinez Canillas
2026-09-15 14:14 ` (subset) " Maxime Ripard
1 sibling, 0 replies; 41+ messages in thread
From: Javier Martinez Canillas @ 2026-09-15 9:29 UTC (permalink / raw)
To: Maxime Ripard
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
Hello Maxime,
On Mon, Sep 7, 2026 at 3:24 PM Maxime Ripard <mripard@kernel.org> wrote:
>
> container_of_const is more flexible than container_of when
> it comes to mixing pointers constness. Switch to it.
>
> Signed-off-by: Maxime Ripard <mripard@kernel.org>
> ---
Reviewed-by: Javier Martinez Canillas <javierm@redhat.com>
--
Best regards,
Javier Martinez Canillas
Core Platforms
Red Hat
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v5 06/33] drm/bridge: Implement atomic_print_state
2026-09-15 9:26 ` Geert Uytterhoeven
@ 2026-09-15 10:08 ` Maxime Ripard
0 siblings, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-15 10:08 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Laurent.pinchart, airlied, andrzej.hajda, devarsht, dri-devel,
jernej.skrabec, jonas, jyri.sarha, laurent.pinchart+renesas,
maarten.lankhorst, neil.armstrong, rfoss, simona, tomi.valkeinen,
tzimmermann
[-- Attachment #1: Type: text/plain, Size: 1349 bytes --]
On Tue, Sep 15, 2026 at 11:26:30AM +0200, Geert Uytterhoeven wrote:
> Hi Maxime,
>
> > Bridges have some fields in their state worth printing, but we don't
> > provide an atomic_print_state implementation to show those fields.
> >
> > Provide one.
> >
> > Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
> > Signed-off-by: Maxime Ripard <mripard@kernel.org>
>
> Thanks for your patch, which is now commit 4a7a146f1cc6791a
> ("drm/bridge: Implement atomic_print_state") in drm-misc/for-linux-next.
>
> > --- a/drivers/gpu/drm/drm_bridge.c
> > +++ b/drivers/gpu/drm/drm_bridge.c
> > @@ -548,14 +548,35 @@ drm_bridge_atomic_create_priv_state(struct drm_private_obj *obj)
> > return ERR_CAST(state);
> >
> > return &state->base;
> > }
> >
> > +static void
> > +drm_bridge_atomic_print_priv_state(struct drm_printer *p,
> > + const struct drm_private_state *s)
> > +{
> > + const struct drm_bridge_state *state = drm_priv_to_bridge_state(s);
>
> warning: passing argument 1 of ‘drm_priv_to_bridge_state’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
>
> Apparently you lost "[PATCH v2 4/28] drm/atomic: Convert
> drm_priv_to_bridge_state to container_of_const"[1] along the way?
Yes, indeed. It should be fixed now, sorry
Maxime
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 273 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v5 00/33] drm: Implement state readout support
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
` (33 preceding siblings ...)
2026-09-15 8:06 ` [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
@ 2026-09-15 12:34 ` Tomi Valkeinen
34 siblings, 0 replies; 41+ messages in thread
From: Tomi Valkeinen @ 2026-09-15 12:34 UTC (permalink / raw)
To: Maxime Ripard
Cc: Devarsh Thakkar, dri-devel, Simona Vetter, Laurent Pinchart,
Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Jyri Sarha
Hi Maxime,
On 07/09/2026 16:23, Maxime Ripard wrote:
> 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,
Thanks for working on this. I tested the earlier versions sometime in
the beginning of the year, and finally found time to look at this again.
I'm testing on AM62-SK, with HDMI and OLDI (separately and together,
u-boot enabled and not enabled), and I've found multiple issues.
Some of the issues are quite obvious, like OLDI bridge and panel_bridge
missing the SRO support. Some are related to using tidss with multiple
displays (or just one, but the "wrong" one enabled in the u-boot), but
the current code expects just the HDMI to be in use. And I think there
are a few on the framework side too.
So far I've been just hacking through the issues, so I don't have
anything to share. I just wanted to tell I'm working on this, and
hopefully late this week or next week I can share my findings, or
perhaps even proper patches.
But I can already see that it's (almost) working, so good work =).
Tomi
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v5 04/33] drm/atomic: Add drm_private_obj name
2026-09-07 13:23 ` [PATCH v5 04/33] drm/atomic: Add drm_private_obj name Maxime Ripard
@ 2026-09-15 14:13 ` Laurent Pinchart
0 siblings, 0 replies; 41+ messages in thread
From: Laurent Pinchart @ 2026-09-15 14:13 UTC (permalink / raw)
To: Maxime Ripard
Cc: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Andrzej Hajda, Neil Armstrong, Robert Foss, Jonas Karlman,
Jernej Skrabec, Jyri Sarha, Tomi Valkeinen, Devarsh Thakkar,
dri-devel
Hi Maxime,
Thank you for the patch.
On Mon, Sep 07, 2026 at 03:23:40PM +0200, Maxime Ripard wrote:
> 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.
>
> Signed-off-by: Maxime Ripard <mripard@kernel.org>
> ---
> drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 +
> drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c | 8 ++++++++
> drivers/gpu/drm/display/drm_dp_mst_topology.c | 2 +-
> drivers/gpu/drm/display/drm_dp_tunnel.c | 1 +
> drivers/gpu/drm/drm_atomic.c | 4 ++++
> drivers/gpu/drm/drm_bridge.c | 3 ++-
> drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 1 +
> drivers/gpu/drm/ingenic/ingenic-ipu.c | 1 +
> drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 1 +
> drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c | 1 +
> drivers/gpu/drm/omapdrm/omap_drv.c | 1 +
> drivers/gpu/drm/tegra/hub.c | 1 +
> drivers/gpu/drm/vc4/vc4_kms.c | 3 +++
> include/drm/drm_atomic.h | 7 ++++++-
> include/drm/drm_bridge.h | 2 ++
> 15 files changed, 34 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 a9d3ce8b173a..1d481c9437d7 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> @@ -2249,10 +2249,11 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev)
> /* indicates support for immediate flip */
> adev_to_drm(adev)->mode_config.async_page_flip = true;
>
> drm_atomic_private_obj_init(adev_to_drm(adev),
> &adev->dm.atomic_obj,
> + "amdgpu_dc_state",
I'll let individual driver maintainers comment no the names.
> &dm_atomic_state_funcs);
>
> r = amdgpu_display_modeset_create_props(adev);
> if (r)
> return r;
> 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
> @@ -64,10 +64,11 @@ static const struct drm_private_state_funcs komeda_layer_obj_funcs = {
>
> static int komeda_layer_obj_add(struct komeda_kms_dev *kms,
> struct komeda_layer *layer)
> {
> drm_atomic_private_obj_init(&kms->base, &layer->base.obj,
> + "komeda_layer",
> &komeda_layer_obj_funcs);
> return 0;
> }
>
> static struct drm_private_state *
> @@ -117,10 +118,11 @@ static const struct drm_private_state_funcs komeda_scaler_obj_funcs = {
> static int komeda_scaler_obj_add(struct komeda_kms_dev *kms,
> struct komeda_scaler *scaler)
> {
> drm_atomic_private_obj_init(&kms->base,
> &scaler->base.obj,
> + "komeda_scaler",
> &komeda_scaler_obj_funcs);
> return 0;
> }
>
> static struct drm_private_state *
> @@ -169,10 +171,11 @@ static const struct drm_private_state_funcs komeda_compiz_obj_funcs = {
>
> static int komeda_compiz_obj_add(struct komeda_kms_dev *kms,
> struct komeda_compiz *compiz)
> {
> drm_atomic_private_obj_init(&kms->base, &compiz->base.obj,
> + "komeda_compiz",
> &komeda_compiz_obj_funcs);
>
> return 0;
> }
>
> @@ -223,10 +226,11 @@ static const struct drm_private_state_funcs komeda_splitter_obj_funcs = {
> static int komeda_splitter_obj_add(struct komeda_kms_dev *kms,
> struct komeda_splitter *splitter)
> {
> drm_atomic_private_obj_init(&kms->base,
> &splitter->base.obj,
> + "komeda_splitter",
> &komeda_splitter_obj_funcs);
>
> return 0;
> }
>
> @@ -276,10 +280,11 @@ static const struct drm_private_state_funcs komeda_merger_obj_funcs = {
> static int komeda_merger_obj_add(struct komeda_kms_dev *kms,
> struct komeda_merger *merger)
> {
> drm_atomic_private_obj_init(&kms->base,
> &merger->base.obj,
> + "komeda_merger",
> &komeda_merger_obj_funcs);
>
> return 0;
> }
>
> @@ -329,10 +334,11 @@ static const struct drm_private_state_funcs komeda_improc_obj_funcs = {
>
> static int komeda_improc_obj_add(struct komeda_kms_dev *kms,
> struct komeda_improc *improc)
> {
> drm_atomic_private_obj_init(&kms->base, &improc->base.obj,
> + "komeda_improc",
> &komeda_improc_obj_funcs);
>
> return 0;
> }
>
> @@ -382,10 +388,11 @@ static const struct drm_private_state_funcs komeda_timing_ctrlr_obj_funcs = {
>
> static int komeda_timing_ctrlr_obj_add(struct komeda_kms_dev *kms,
> struct komeda_timing_ctrlr *ctrlr)
> {
> drm_atomic_private_obj_init(&kms->base, &ctrlr->base.obj,
> + "komeda_timing_ctrlr",
> &komeda_timing_ctrlr_obj_funcs);
>
> return 0;
> }
>
> @@ -436,10 +443,11 @@ static const struct drm_private_state_funcs komeda_pipeline_obj_funcs = {
>
> static int komeda_pipeline_obj_add(struct komeda_kms_dev *kms,
> struct komeda_pipeline *pipe)
> {
> drm_atomic_private_obj_init(&kms->base, &pipe->obj,
> + "komeda_pipeline",
> &komeda_pipeline_obj_funcs);
>
> return 0;
> }
>
> diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c
> index 7ce9e212770a..d3ed80d9fce3 100644
> --- a/drivers/gpu/drm/display/drm_dp_mst_topology.c
> +++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c
> @@ -5766,11 +5766,11 @@ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr,
> mgr->aux = aux;
> mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes;
> mgr->max_payloads = max_payloads;
> 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);
>
> return 0;
> }
> EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init);
> diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c
> index d29ad4116e64..391d643793eb 100644
> --- a/drivers/gpu/drm/display/drm_dp_tunnel.c
> +++ b/drivers/gpu/drm/display/drm_dp_tunnel.c
> @@ -1730,10 +1730,11 @@ static bool init_group(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_tunnel_group
> group->mgr = mgr;
> group->available_bw = -1;
> INIT_LIST_HEAD(&group->tunnels);
>
> drm_atomic_private_obj_init(mgr->dev, &group->base,
> + group->name,
> &tunnel_group_funcs);
>
> return true;
> }
>
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 596b0de44f00..04da72b4aa70 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -1006,10 +1006,11 @@ 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
> *
> * Initialize the private object, which can be embedded into any
> * driver private object that needs its own atomic state.
> @@ -1017,18 +1018,20 @@ static void drm_atomic_plane_print_state(struct drm_printer *p,
> * RETURNS:
> * Zero on success, error code on failure
> */
> 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;
> memset(obj, 0, sizeof(*obj));
>
> drm_modeset_lock_init(&obj->lock);
>
> obj->dev = dev;
> + obj->name = kstrdup(name, GFP_KERNEL);
> obj->funcs = funcs;
> list_add_tail(&obj->head, &dev->mode_config.privobj_list);
>
> state = obj->funcs->atomic_create_state(obj);
> if (IS_ERR(state))
You seem to be leaking obj->name in the error path. Actually, unless
callers of drm_atomic_private_obj_init() are required to call
drm_atomic_private_obj_fini() on failure (which wouldn't be a great
API), the function is already failing to remove the object from the
privobj_list, and leaks the object lock.
A quick grep shows that the komeda driver seems to call
drm_atomic_private_obj_fini() on private objects that failed to
initialized, because it iterates over the privobj_list. Other drivers
may implement different patterns. It may be safer to make
drm_atomic_private_obj_fini() idempotent.
> @@ -1049,10 +1052,11 @@ EXPORT_SYMBOL(drm_atomic_private_obj_init);
> void
> drm_atomic_private_obj_fini(struct drm_private_obj *obj)
> {
> list_del(&obj->head);
> obj->funcs->atomic_destroy_state(obj, obj->state);
> + kfree(obj->name);
> drm_modeset_lock_fini(&obj->lock);
> }
> EXPORT_SYMBOL(drm_atomic_private_obj_fini);
>
> /**
> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> index 83f1809a5d37..27bb9963c234 100644
> --- a/drivers/gpu/drm/drm_bridge.c
> +++ b/drivers/gpu/drm/drm_bridge.c
> @@ -419,10 +419,11 @@ void *__devm_drm_bridge_alloc(struct device *dev, size_t size, size_t offset,
>
> bridge = container + offset;
> INIT_LIST_HEAD(&bridge->list);
> bridge->container = container;
> bridge->funcs = funcs;
> + bridge->name = devm_kasprintf(dev, GFP_KERNEL, "bridge-%s", dev_name(dev));
> kref_init(&bridge->refcount);
>
> err = devm_add_action_or_reset(dev, drm_bridge_put_void, bridge);
> if (err)
> return ERR_PTR(err);
> @@ -622,11 +623,11 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
> ret = bridge->funcs->attach(bridge, encoder, flags);
> if (ret < 0)
> goto err_reset_bridge;
> }
>
> - drm_atomic_private_obj_init(bridge->dev, &bridge->base,
> + drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
As far as I can see, bridge->name is used here only. We could avoid the
permanent allocation by constructing the name string on-demand here, but
I suppose the runtime overhead would be worse than the small saving in
permanent memory consumption.
> &drm_bridge_priv_state_funcs);
>
> return 0;
>
> err_reset_bridge:
> diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> index 625ec76cf04d..05ee0ccb121d 100644
> --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> @@ -1400,10 +1400,11 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
> dev_err(dev, "Unable to register clock notifier\n");
> goto err_devclk_disable;
> }
>
> drm_atomic_private_obj_init(drm, &priv->private_obj,
> + "ingenic_drm",
> &ingenic_drm_private_state_funcs);
>
> ret = drmm_add_action_or_reset(drm, ingenic_drm_atomic_private_obj_fini,
> &priv->private_obj);
> if (ret)
> 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
> @@ -900,10 +900,11 @@ static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
> dev_err(dev, "Unable to prepare clock\n");
> return err;
> }
>
> drm_atomic_private_obj_init(drm, &ipu->private_obj,
> + "ingenic_ipu_state",
> &ingenic_ipu_private_state_funcs);
>
> return 0;
> }
>
> 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
> @@ -1153,10 +1153,11 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
>
> dev->mode_config.cursor_width = 512;
> dev->mode_config.cursor_height = 512;
>
> drm_atomic_private_obj_init(dpu_kms->dev, &dpu_kms->global_state,
> + "dpu_kms_global_state",
> &dpu_kms_global_state_funcs);
>
> atomic_set(&dpu_kms->bandwidth_ref, 0);
>
> rc = pm_runtime_resume_and_get(&dpu_kms->pdev->dev);
> 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
> @@ -710,10 +710,11 @@ static int mdp5_init(struct platform_device *pdev, struct drm_device *dev)
> int ret;
>
> mdp5_kms->dev = dev;
>
> drm_atomic_private_obj_init(mdp5_kms->dev, &mdp5_kms->glob_state,
> + "mdp5_global_state",
> &mdp5_global_state_funcs);
>
> /* we need to set a default rate before enabling. Set a safe
> * rate first, then figure out hw revision, and then set a
> * more optimal rate:
> diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
> index 92d6a1f9c0a1..9738acbf6fae 100644
> --- a/drivers/gpu/drm/omapdrm/omap_drv.c
> +++ b/drivers/gpu/drm/omapdrm/omap_drv.c
> @@ -298,10 +298,11 @@ static const struct drm_private_state_funcs omap_global_state_funcs = {
> static int omap_global_obj_init(struct drm_device *dev)
> {
> struct omap_drm_private *priv = dev->dev_private;
>
> drm_atomic_private_obj_init(dev, &priv->glob_obj,
> + "omap_global",
> &omap_global_state_funcs);
> return 0;
> }
>
> static void omap_global_obj_fini(struct omap_drm_private *priv)
> diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c
> index bd442bfd4540..b38fd353a648 100644
> --- a/drivers/gpu/drm/tegra/hub.c
> +++ b/drivers/gpu/drm/tegra/hub.c
> @@ -956,10 +956,11 @@ static int tegra_display_hub_init(struct host1x_client *client)
> struct tegra_display_hub *hub = to_tegra_display_hub(client);
> struct drm_device *drm = dev_get_drvdata(client->host);
> struct tegra_drm *tegra = drm->dev_private;
>
> drm_atomic_private_obj_init(drm, &hub->base,
> + "tegra_display_hub",
> &tegra_display_hub_state_funcs);
>
> tegra->hub = hub;
>
> return 0;
> diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c
> index b17e73bce384..5f64cf1fcc9e 100644
> --- a/drivers/gpu/drm/vc4/vc4_kms.c
> +++ b/drivers/gpu/drm/vc4/vc4_kms.c
> @@ -115,10 +115,11 @@ static void vc4_ctm_obj_fini(struct drm_device *dev, void *unused)
> static int vc4_ctm_obj_init(struct vc4_dev *vc4)
> {
> drm_modeset_lock_init(&vc4->ctm_state_lock);
>
> drm_atomic_private_obj_init(&vc4->base, &vc4->ctm_manager,
> + "vc4_ctm",
> &vc4_ctm_state_funcs);
>
> return drmm_add_action_or_reset(&vc4->base, vc4_ctm_obj_fini, NULL);
> }
>
> @@ -755,10 +756,11 @@ static void vc4_load_tracker_obj_fini(struct drm_device *dev, void *unused)
> }
>
> 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);
>
> return drmm_add_action_or_reset(&vc4->base, vc4_load_tracker_obj_fini, NULL);
> }
>
> @@ -846,10 +848,11 @@ static void vc4_hvs_channels_obj_fini(struct drm_device *dev, void *unused)
> }
>
> 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);
>
> return drmm_add_action_or_reset(&vc4->base, vc4_hvs_channels_obj_fini, NULL);
> }
>
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index def18b60a106..47b2fe2b039e 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -357,10 +357,15 @@ struct drm_private_obj {
> /**
> * @dev: parent DRM device
> */
> 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
> * (queued to &drm_mode_config.privobj_list).
> */
> struct list_head head;
> @@ -738,11 +743,11 @@ drm_atomic_get_new_colorop_state(struct drm_atomic_commit *state,
> struct drm_connector_state * __must_check
> drm_atomic_get_connector_state(struct drm_atomic_commit *state,
> struct drm_connector *connector);
>
> int drm_atomic_private_obj_init(struct drm_device *dev,
> - struct drm_private_obj *obj,
> + struct drm_private_obj *obj, const char *name,
> const struct drm_private_state_funcs *funcs);
> void drm_atomic_private_obj_fini(struct drm_private_obj *obj);
>
> 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 58fff047f43b..389dcab3c3f3 100644
> --- a/include/drm/drm_bridge.h
> +++ b/include/drm/drm_bridge.h
> @@ -994,10 +994,12 @@ enum drm_bridge_ops {
> struct drm_bridge {
> /** @base: inherit from &drm_private_object */
> struct drm_private_obj base;
> /** @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;
> /** @chain_node: used to form a bridge chain */
> struct list_head chain_node;
> /** @of_node: device node pointer to the bridge */
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: (subset) [PATCH v5 03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const
2026-09-07 13:23 ` [PATCH v5 03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
2026-09-15 9:29 ` Javier Martinez Canillas
@ 2026-09-15 14:14 ` Maxime Ripard
1 sibling, 0 replies; 41+ messages in thread
From: Maxime Ripard @ 2026-09-15 14: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,
Maxime Ripard
Cc: Devarsh Thakkar, dri-devel
On Mon, 07 Sep 2026 15:23:39 +0200, Maxime Ripard wrote:
> container_of_const is more flexible than container_of when
> it comes to mixing pointers constness. Switch to it.
>
>
Applied to misc/kernel.git (drm-misc-next)
[03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const
commit: 7a4e435602c949c4a64e8cadfe816450953c2f6d
Thanks!
Maxime
^ permalink raw reply [flat|nested] 41+ messages in thread
end of thread, other threads:[~2026-09-15 14:14 UTC | newest]
Thread overview: 41+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-07 13:23 [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 01/33] drm/atomic: Fix unused but set warning in state iterator macros Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 02/33] drm/atomic_helper: Skip over NULL private_obj pointers Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 03/33] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
2026-09-15 9:29 ` Javier Martinez Canillas
2026-09-15 14:14 ` (subset) " Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 04/33] drm/atomic: Add drm_private_obj name Maxime Ripard
2026-09-15 14:13 ` Laurent Pinchart
2026-09-07 13:23 ` [PATCH v5 05/33] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 06/33] drm/bridge: Implement atomic_print_state Maxime Ripard
2026-09-15 9:26 ` Geert Uytterhoeven
2026-09-15 10:08 ` Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 07/33] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 08/33] drm/atomic: Only call atomic_destroy_state on a !NULL pointer Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 09/33] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 10/33] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 11/33] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 12/33] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 13/33] drm/atomic_sro: Create documentation Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 14/33] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 15/33] drm/mode-config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 16/33] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 17/33] drm/mode-config: Introduce hw_reset hook Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 18/33] drm/modeset-helper: Add drm_mode_config_helper_hw_reset() Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 19/33] drm/atomic_helper: Pass nonblock to commit_tail Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 20/33] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 21/33] drm/atomic-state-helper: Provide comparison macros Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 22/33] drm/atomic-state-helper: Provide atomic_compare_state helpers Maxime Ripard
2026-09-07 13:23 ` [PATCH v5 23/33] drm/atomic-helper: Document State Read-Out as a state initialization approach Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 24/33] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 25/33] drm/bridge: display-connector: Implement readout support Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 26/33] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 27/33] drm/tidss: crtc: Add atomic_print_state implementation Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 28/33] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 29/33] drm/tidss: Switch to drm_mode_config_create_initial_state() Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 30/33] drm/tidss: Implement hw_reset Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 31/33] drm/tidss: Implement readout support Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 32/33] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
2026-09-07 13:24 ` [PATCH v5 33/33] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
2026-09-15 8:06 ` [PATCH v5 00/33] drm: Implement state readout support Maxime Ripard
2026-09-15 12:34 ` Tomi Valkeinen
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.