* [PATCH v2 04/28] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const
2026-04-23 10:06 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
@ 2026-04-23 10:06 ` Maxime Ripard
0 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10: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, linux-kernel, 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 b9e7281cfc97..537092d3ed16 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -1375,15 +1375,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_state *state,
struct drm_bridge *bridge);
struct drm_bridge_state *
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 00/28] drm: Implement state readout support
@ 2026-04-23 10:18 Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 01/28] drm/atomic: Fix unused but set warning in state iterator macros Maxime Ripard
` (27 more replies)
0 siblings, 28 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard,
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 commited.
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
It's now in a much better state than the v1 was, but there's still some
notable things broken:
- Bridges do not read their input and output bus formats and flags
- The tidss_crtc_state fields are not read properly at the moment
either.
- It looks like, when left unattended, we get some atomic_flush
WARN eventually, maybe because of something broken around
hotplugging and the monitor shutting itself down?
- If readout is disabled for any reason, the hardware isn't reset
anymore.
The main thing works though: 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 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 (28):
drm/atomic: Fix unused but set warning in state iterator macros
drm/atomic_helper: Skip over NULL private_obj pointers
drm/atomic_state_helper: Remove memset in __drm_atomic_helper_bridge_reset()
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/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/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: dispc: Improve mode checking logs
drm/tidss: Implement readout support
drm/tidss: encoder: Implement atomic_sro_get_current_crtc
drm/bridge: sii902x: Implement hw state readout
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 | 15 +
drivers/gpu/drm/bridge/sii902x.c | 56 +-
drivers/gpu/drm/display/drm_bridge_connector.c | 31 +
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 | 854 +++++++++++++++++++++
drivers/gpu/drm/drm_atomic_sro_helper.c | 677 ++++++++++++++++
drivers/gpu/drm/drm_atomic_state_helper.c | 3 +-
drivers/gpu/drm/drm_bridge.c | 133 +++-
drivers/gpu/drm/drm_internal.h | 12 +
drivers/gpu/drm/drm_mode_config.c | 5 +
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 | 93 +++
drivers/gpu/drm/tidss/tidss_dispc.c | 332 ++++++--
drivers/gpu/drm/tidss/tidss_dispc.h | 14 +
drivers/gpu/drm/tidss/tidss_encoder.c | 37 +-
drivers/gpu/drm/tidss/tidss_kms.c | 6 +-
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 | 275 +++++++
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 | 18 +
include/drm/drm_modeset_helper_vtables.h | 23 +
include/drm/drm_plane.h | 69 ++
39 files changed, 3255 insertions(+), 113 deletions(-)
---
base-commit: ea61048876a7137897da26dac49ee234fb38a35a
change-id: 20250730-drm-state-readout-108f089c1c30
prerequisite-change-id: 20260310-drm-mode-config-init-1e1f52b745d0:v2
prerequisite-patch-id: 8831e320ca55e930e6dd8db8abfd2a79179fc1a0
prerequisite-patch-id: 5d2ae478dfb0a8b1eed30431b52a63141f1a3edb
prerequisite-patch-id: be3ef2f85cc3bba3ce65cbe4c1bf73b26c59b46c
prerequisite-patch-id: ad4de3b4f7bf5861d8e74d15042fb1d17cf6f071
prerequisite-patch-id: 6af92ef1aef1541ba0c504c3618612a89f5433a4
prerequisite-patch-id: d7b9200d7155e7ae2a76121adb50ce974aa78ddc
prerequisite-patch-id: 811a936fa6b38549807256e718b4c1faf49e90dd
prerequisite-patch-id: 3816a4bdc28e3353dba2811cc82d6680cf9ada11
prerequisite-patch-id: e156096dae812cab909877d7d614f56374652c6a
prerequisite-patch-id: fa0187e85c3587e7b28e411f2198b7c070793fbb
prerequisite-patch-id: f97b4088a1357121e4b35c4822839e45d952cba9
prerequisite-patch-id: 2e942d5540fc208b0c94f9f9215f85cc59dd6c1c
prerequisite-patch-id: 45ce20ef439a538b6c71c7aedb251bd62c882a11
prerequisite-patch-id: 86674e487ba650461e29c284308ff9ba321a48e4
prerequisite-patch-id: 1d7744840555f74f310c1d48642bfc755ac645c9
prerequisite-patch-id: b01716030f5f9b3cedb4a6096b2d4769f625741a
prerequisite-patch-id: 0372103622aa6006a3fe645b4c2eb2387d7d3b5a
prerequisite-patch-id: 24a0b772f6fc53c41892a37b52cba95bbd261ca2
prerequisite-patch-id: 80ec7eea600ad3be7b94a182e274695b7c5cf17f
prerequisite-patch-id: 273773ea001ab348b4e85b47b20b45f48d4c1cbc
Best regards,
--
Maxime Ripard <mripard@kernel.org>
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH v2 01/28] drm/atomic: Fix unused but set warning in state iterator macros
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 02/28] drm/atomic_helper: Skip over NULL private_obj pointers Maxime Ripard
` (26 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard
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.
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 f03cd199aee7..b9e7281cfc97 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -946,13 +946,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_state pointer
* @connector: &struct drm_connector iteration cursor
@@ -968,12 +970,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_state pointer
* @connector: &struct drm_connector iteration cursor
@@ -989,13 +992,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_state pointer
* @crtc: &struct drm_crtc iteration cursor
@@ -1012,14 +1015,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_state pointer
* @crtc: &struct drm_crtc iteration cursor
@@ -1035,11 +1038,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_state pointer
* @crtc: &struct drm_crtc iteration cursor
@@ -1120,12 +1124,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_state pointer
@@ -1142,12 +1148,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_state pointer
@@ -1159,11 +1168,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_state pointer
* @plane: &struct drm_plane iteration cursor
@@ -1178,11 +1189,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_state pointer
* @plane: &struct drm_plane iteration cursor
* @new_plane_state: &struct drm_plane_state iteration cursor for the new state
@@ -1216,12 +1230,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_state pointer
@@ -1235,11 +1252,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_state pointer
@@ -1254,11 +1273,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 02/28] drm/atomic_helper: Skip over NULL private_obj pointers
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 01/28] drm/atomic: Fix unused but set warning in state iterator macros Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 03/28] drm/atomic_state_helper: Remove memset in __drm_atomic_helper_bridge_reset() Maxime Ripard
` (25 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard
Just like for connectors, drm_atomic_state 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_state_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_state_put() at this point, chances are
that drm_atomic_state_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: 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 3bd52602fe30..84bc91886b4c 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -334,10 +334,13 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 03/28] drm/atomic_state_helper: Remove memset in __drm_atomic_helper_bridge_reset()
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 01/28] drm/atomic: Fix unused but set warning in state iterator macros Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 02/28] drm/atomic_helper: Skip over NULL private_obj pointers Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 04/28] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
` (24 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard
The general consensus is that reset helpers shouldn't call memset
directly because the object it initializes is supposed to have been
zeroed out.
Remove the memset call from
__drm_atomic_helper_bridge_reset() and document the expectation.
It should not have any impact on the current code base since there's
only two callers: drm_atomic_helper_bridge_reset() and
cdns-mhdp8546-core's cdns_mhdp_bridge_atomic_reset(), both allocating
the object using kzalloc_obj().
Suggested-by: Thomas Zimmermann <tzimmermann@suse.de>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index c7eb3ce8c282..1a6f55329fee 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -952,15 +952,16 @@ EXPORT_SYMBOL(drm_atomic_helper_bridge_destroy_state);
* @state: bridge state to initialize
*
* Initializes the bridge state to default values. This is meant to be called
* by the bridge &drm_bridge_funcs.atomic_reset hook for bridges that subclass
* the bridge state.
+ *
+ * The object @state points to is assumed to have been initialized to zero.
*/
void __drm_atomic_helper_bridge_reset(struct drm_bridge *bridge,
struct drm_bridge_state *state)
{
- memset(state, 0, sizeof(*state));
__drm_atomic_helper_private_obj_create_state(&bridge->base, &state->base);
state->bridge = bridge;
}
EXPORT_SYMBOL(__drm_atomic_helper_bridge_reset);
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 04/28] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (2 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 03/28] drm/atomic_state_helper: Remove memset in __drm_atomic_helper_bridge_reset() Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 05/28] drm/atomic: Add drm_private_obj name Maxime Ripard
` (23 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 b9e7281cfc97..537092d3ed16 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -1375,15 +1375,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_state *state,
struct drm_bridge *bridge);
struct drm_bridge_state *
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 05/28] drm/atomic: Add drm_private_obj name
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (3 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 04/28] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 06/28] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
` (22 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 c2066319772b..d993f6e5f3e6 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -5020,10 +5020,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 8757972e8e24..7936bb7e0bf6 100644
--- a/drivers/gpu/drm/display/drm_dp_mst_topology.c
+++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c
@@ -5762,11 +5762,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 6519b4244728..ff32ac855780 100644
--- a/drivers/gpu/drm/display/drm_dp_tunnel.c
+++ b/drivers/gpu/drm/display/drm_dp_tunnel.c
@@ -1599,10 +1599,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 84bc91886b4c..0a9f5e9fc69b 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -985,10 +985,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.
@@ -996,17 +997,19 @@ 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)
{
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);
return 0;
}
@@ -1021,10 +1024,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 986e4c79a4e0..44d53906e4c5 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -379,10 +379,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);
@@ -586,11 +587,11 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
if (ret < 0)
goto err_reset_bridge;
}
if (drm_bridge_is_atomic(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 4068114adf8c..135d8b08f213 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 34545b9c8c33..17a0e5dc6df0 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 35f7af4743d7..f72b94a60e9d 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 2d26b07b06f5..1177f53a85fc 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
@@ -715,10 +715,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 ae678696fbac..da7722e9b538 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 10d993b8d043..94f9aa690f59 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 264b5e80c24d..f6ac2a20576e 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 537092d3ed16..4f58289d3a34 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -354,10 +354,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;
@@ -735,11 +740,11 @@ drm_atomic_get_new_colorop_state(struct drm_atomic_state *state,
struct drm_connector_state * __must_check
drm_atomic_get_connector_state(struct drm_atomic_state *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_state *state,
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index d6cd0f5af045..84b5517726c1 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1100,10 +1100,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 06/28] drm/bridge: Add drm_private_obj_is_bridge()
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (4 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 05/28] drm/atomic: Add drm_private_obj name Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 07/28] drm/bridge: Implement atomic_print_state Maxime Ripard
` (21 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 44d53906e4c5..5b8e171afbe5 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -512,10 +512,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);
+
static bool drm_bridge_is_atomic(struct drm_bridge *bridge)
{
return bridge->funcs->atomic_reset != NULL;
}
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 84b5517726c1..425f3ca03d95 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1285,10 +1285,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 07/28] drm/bridge: Implement atomic_print_state
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (5 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 06/28] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 08/28] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
` (20 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 5b8e171afbe5..fba440bddcb3 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -506,14 +506,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(p, "\tinput bus configuration:");
+ drm_printf(p, "\t\tcode: %04x", state->input_bus_cfg.format);
+ drm_printf(p, "\t\tflags: %08x", state->input_bus_cfg.flags);
+ drm_printf(p, "\toutput bus configuration:");
+ drm_printf(p, "\t\tcode: %04x", state->output_bus_cfg.format);
+ drm_printf(p, "\t\tflags: %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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 08/28] drm/atomic: Export drm_atomic_*_print_state
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (6 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 07/28] drm/bridge: Implement atomic_print_state Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 09/28] drm/atomic: Only call atomic_destroy_state on a !NULL pointer Maxime Ripard
` (19 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 0a9f5e9fc69b..c714a6e6e9ae 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -521,12 +521,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(p, "\tenable=%d\n", state->enable);
@@ -916,12 +916,12 @@ static void drm_atomic_colorop_print_state(struct drm_printer *p,
}
drm_printf(p, "\tnext=%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);
@@ -1402,12 +1402,12 @@ drm_atomic_get_connector_state(struct drm_atomic_state *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(p, "\tcrtc=%s\n", state->crtc ? state->crtc->name : "(null)");
@@ -2048,12 +2048,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 09/28] drm/atomic: Only call atomic_destroy_state on a !NULL pointer
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (7 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 08/28] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 10/28] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
` (18 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard,
Laurent Pinchart
The drm_atomic_state structure is freed through the
drm_atomic_state_put() function, that eventually calls
drm_atomic_state_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 c714a6e6e9ae..6449a4fd4ae0 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -274,12 +274,14 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *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);
@@ -289,12 +291,13 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *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;
@@ -309,12 +312,14 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *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;
}
@@ -337,12 +342,14 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 10/28] drm/atomic_sro: Create drm_atomic_sro_state container
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (8 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 09/28] drm/atomic: Only call atomic_destroy_state on a !NULL pointer Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 11/28] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
` (17 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard
The SRO infrastructure needs a state container to collect the readout
states for all KMS objects. Unlike drm_atomic_state 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 | 424 +++++++++++++++++++++++++++++++++++++++
include/drm/drm_atomic_sro.h | 51 +++++
3 files changed, 476 insertions(+)
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index e97faabcd783..64705ac96bd1 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..177b97d451f5
--- /dev/null
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -0,0 +1,424 @@
+// 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)
+{
+ 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)
+ return -ENOMEM;
+
+ state->connectors = kzalloc_objs(*state->connectors,
+ dev->mode_config.num_connector);
+ if (!state->connectors)
+ return -ENOMEM;
+
+ state->private_objs =
+ kzalloc_objs(*state->private_objs, count_private_obj(dev));
+ if (!state->private_objs)
+ return -ENOMEM;
+ state->num_private_objs = 0;
+
+ drm_dev_get(dev);
+ state->dev = dev;
+
+ return 0;
+}
+
+/**
+ * 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)
+ 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(). 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;
+
+ 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;
+
+ 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;
+
+ 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;
+
+ 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;
+
+ 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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 11/28] drm/atomic_sro: Create kernel parameter to force or disable readout
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (9 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 10/28] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 12/28] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
` (16 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard
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>
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 177b97d451f5..a46f06e75c4e 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, 0);
+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_core_check_feature(dev, DRIVER_ATOMIC))
+ 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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 12/28] drm/atomic_sro: Add atomic state readout infrastructure
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (10 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 11/28] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 13/28] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
` (15 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 59 ++++++++++++++++++++++++++++++++
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 | 23 +++++++++++++
include/drm/drm_plane.h | 26 ++++++++++++++
8 files changed, 234 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index a46f06e75c4e..3eb22c654973 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,62 @@ enum drm_atomic_readout_status {
static unsigned int atomic_readout = DRM_ATOMIC_READOUT_ENABLED;
module_param_unsafe(atomic_readout, uint, 0);
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);
+ 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 +90,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_core_check_feature(dev, DRIVER_ATOMIC))
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 4f58289d3a34..554dde45b799 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -293,10 +293,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 425f3ca03d95..e0f9b7e6353a 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -373,10 +373,41 @@ struct drm_bridge_funcs {
* The @atomic_post_disable callback is optional.
*/
void (*atomic_post_disable)(struct drm_bridge *bridge,
struct drm_atomic_state *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 a2c6c87065b6..db0d2bb80bd5 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;
@@ -1582,10 +1583,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 9e410191683c..b6d4a2341776 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;
@@ -649,10 +650,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 22c7e767a2e9..ce2b4115b417 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_state is deprecated in favour of using
* &drm_private_state and &drm_private_obj.
*/
void (*atomic_state_free)(struct drm_atomic_state *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 3e68213958dd..8c886c5ea9d3 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -1550,8 +1550,31 @@ struct drm_mode_config_helper_funcs {
* how one should implement this.
*
* This hook is optional.
*/
int (*atomic_commit_setup)(struct drm_atomic_state *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.
+ *
+ * The default implementation is
+ * drm_atomic_helper_sro_build_state().
+ *
+ * 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 4d4d511b681d..22a16bc63f3e 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;
@@ -399,10 +400,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 13/28] drm/atomic_sro: Add function to install state into drm objects
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (11 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 12/28] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 14/28] drm/atomic_sro: Create documentation Maxime Ripard
` (14 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 97 ++++++++++++++++++++++++++++++++++++++++
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, 171 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index 3eb22c654973..7ff67c140ff2 100644
--- a/drivers/gpu/drm/drm_atomic_sro.c
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -515,5 +515,102 @@ 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_atomic_helper_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++) {
+ struct drm_connector *conn = state->connectors[i].ptr;
+ const struct drm_connector_funcs *conn_funcs = conn->funcs;
+ struct drm_connector_state *conn_state =
+ state->connectors[i].state;
+
+ if (conn->state) {
+ conn_funcs->atomic_destroy_state(conn, conn->state);
+ conn->state = NULL;
+ }
+
+ 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++) {
+ struct drm_crtc *crtc = state->crtcs[i].ptr;
+ const struct drm_crtc_funcs *crtc_funcs = crtc->funcs;
+ struct drm_crtc_state *crtc_state = state->crtcs[i].state;
+
+ if (crtc->state) {
+ crtc_funcs->atomic_destroy_state(crtc, crtc->state);
+ crtc->state = NULL;
+ }
+
+ 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++) {
+ struct drm_plane *plane = state->planes[i].ptr;
+ const struct drm_plane_funcs *plane_funcs = plane->funcs;
+ struct drm_plane_state *plane_state = state->planes[i].state;
+
+ if (plane->state) {
+ plane_funcs->atomic_destroy_state(plane, plane->state);
+ plane->state = NULL;
+ }
+
+ 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++) {
+ struct drm_private_obj *obj = state->private_objs[i].ptr;
+ const struct drm_private_state_funcs *obj_funcs = obj->funcs;
+ struct drm_private_state *obj_state =
+ state->private_objs[i].state;
+
+ if (obj->state) {
+ obj_funcs->atomic_destroy_state(obj, obj->state);
+ obj->state = NULL;
+ }
+
+ 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 554dde45b799..81290c4a5ad3 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -318,10 +318,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 db0d2bb80bd5..3cd20198a5e7 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1608,10 +1608,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 b6d4a2341776..146da65448dc 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -675,10 +675,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 22a16bc63f3e..1e02728838e2 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -425,10 +425,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 14/28] drm/atomic_sro: Create documentation
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (12 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 13/28] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 15/28] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
` (13 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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>
---
drivers/gpu/drm/drm_atomic_sro.c | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_sro.c b/drivers/gpu/drm/drm_atomic_sro.c
index 7ff67c140ff2..0e62e9d22ecc 100644
--- a/drivers/gpu/drm/drm_atomic_sro.c
+++ b/drivers/gpu/drm/drm_atomic_sro.c
@@ -12,10 +12,44 @@
#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 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.
+ */
+
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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 15/28] drm/bridge: Handle bridges with hardware state readout
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (13 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 14/28] drm/atomic_sro: Create documentation Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 16/28] drm/mode_config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
` (12 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 81 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 77 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index fba440bddcb3..08226af6b82a 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>
@@ -506,10 +507,66 @@ 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)
+ 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);
@@ -533,10 +590,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:
@@ -544,11 +609,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);
static bool drm_bridge_is_atomic(struct drm_bridge *bridge)
{
@@ -622,13 +689,19 @@ 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;
}
- if (drm_bridge_is_atomic(bridge))
- drm_atomic_private_obj_init(bridge->dev, &bridge->base, bridge->name,
- &drm_bridge_priv_state_funcs);
+ if (drm_bridge_is_atomic(bridge)) {
+ 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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 16/28] drm/mode_config: Read out hardware state in drm_mode_config_create_state
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (14 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 15/28] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 17/28] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
` (11 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index 61c3ba5d3901..6bae788c30cc 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>
@@ -364,10 +365,14 @@ int drm_mode_config_create_state(struct drm_device *dev)
if (ret)
return ret;
}
drm_connector_list_iter_end(&conn_iter);
+ ret = dev->mode_config.funcs->atomic_sro_readout_state(dev);
+ if (ret)
+ return ret;
+
return 0;
}
EXPORT_SYMBOL(drm_mode_config_create_state);
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 17/28] drm/atomic_sro: Provide helpers to implement hardware state readout
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (15 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 16/28] drm/mode_config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 18/28] drm/atomic_helper: Pass nonblock to commit_tail Maxime Ripard
` (10 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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.
Also provide default implementations for the atomic_sro_compare_state
hooks: drm_atomic_helper_plane_compare_state(),
drm_atomic_helper_crtc_compare_state(),
drm_atomic_helper_connector_compare_state(), and
drm_atomic_helper_bridge_compare_state(). These compare the base
state fields and report mismatches through a drm_printer.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/drm_atomic_sro_helper.c | 407 ++++++++++++++++++++++++++++++++
include/drm/drm_atomic_sro_helper.h | 28 +++
3 files changed, 436 insertions(+)
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 64705ac96bd1..082721f1453c 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_helper.c b/drivers/gpu/drm/drm_atomic_sro_helper.c
new file mode 100644
index 000000000000..367d9bb7e442
--- /dev/null
+++ b/drivers/gpu/drm/drm_atomic_sro_helper.c
@@ -0,0 +1,407 @@
+// 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 0;
+
+ 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)))
+ return PTR_ERR(conn_state);
+
+ ret = conn_funcs->atomic_sro_readout_state(connector, state,
+ conn_state);
+ if (drm_WARN_ON(dev, ret)) {
+ conn_funcs->atomic_destroy_state(connector, conn_state);
+ return ret;
+ }
+
+ drm_atomic_sro_set_connector_state(state, connector, conn_state);
+ }
+ 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 ret;
+
+ 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, !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.
+ *
+ * If the device does not support readout, this is a no-op.
+ *
+ * RETURNS:
+ *
+ * 0 on success, 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 0;
+ }
+
+ 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_ */
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 18/28] drm/atomic_helper: Pass nonblock to commit_tail
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (16 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 17/28] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 19/28] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
` (9 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 26953ed6b53e..9deb3dad34a6 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -2029,11 +2029,11 @@ void drm_atomic_helper_commit_tail_rpm(struct drm_atomic_state *state)
drm_atomic_helper_cleanup_planes(dev, state);
}
EXPORT_SYMBOL(drm_atomic_helper_commit_tail_rpm);
-static void commit_tail(struct drm_atomic_state *state)
+static void commit_tail(struct drm_atomic_state *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;
@@ -2087,11 +2087,11 @@ static void commit_tail(struct drm_atomic_state *state)
static void commit_work(struct work_struct *work)
{
struct drm_atomic_state *state = container_of(work,
struct drm_atomic_state,
commit_work);
- commit_tail(state);
+ commit_tail(state, true);
}
/**
* drm_atomic_helper_async_check - check if state can be committed asynchronously
* @dev: DRM device
@@ -2307,11 +2307,11 @@ int drm_atomic_helper_commit(struct drm_device *dev,
drm_atomic_state_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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 19/28] drm/atomic_helper: Compare actual and readout states once the commit is done
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (17 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 18/28] drm/atomic_helper: Pass nonblock to commit_tail Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 20/28] drm/atomic_state_helper: Provide comparison macros Maxime Ripard
` (8 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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_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 | 206 +++++++++++++++++++++++++++++++++++-
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 | 25 +++++
include/drm/drm_connector.h | 25 +++++
include/drm/drm_crtc.h | 25 +++++
include/drm/drm_plane.h | 25 +++++
9 files changed, 374 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 9deb3dad34a6..e46727069ad0 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -29,10 +29,11 @@
#include <linux/dma-fence.h>
#include <linux/ktime.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>
@@ -2079,10 +2080,49 @@ static void commit_tail(struct drm_atomic_state *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_state_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 0e62e9d22ecc..64c93588d9fe 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,10 +37,19 @@
* 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.
*
+ * 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
@@ -110,10 +119,60 @@ 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);
+ 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
@@ -136,10 +195,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 {
@@ -646,5 +709,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
+ * @committed_state: the atomic state that was just committed
+ *
+ * Reads out a fresh &struct drm_atomic_sro_state from the hardware and
+ * compares it to @committed_state 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_state *committed_state)
+{
+ struct drm_device *dev = committed_state->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(committed_state, 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(committed_state, 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(committed_state, 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(committed_state, 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 08226af6b82a..6621d80f5252 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -691,11 +691,12 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
goto err_reset_bridge;
}
if (drm_bridge_is_atomic(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 81290c4a5ad3..428233d572fc 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -336,10 +336,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..ff4e39f65e6b 100644
--- a/include/drm/drm_atomic_sro.h
+++ b/include/drm/drm_atomic_sro.h
@@ -1,11 +1,14 @@
/* 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_sro_state;
+struct drm_atomic_state;
struct drm_connector;
struct drm_connector_state;
struct drm_crtc;
struct drm_crtc_state;
struct drm_device;
@@ -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_state *committed_state);
#endif /* DRM_ATOMIC_SRO_H_ */
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index e0f9b7e6353a..36d558a5cd4d 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -404,10 +404,35 @@ 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. 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:
+ *
+ * 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 3cd20198a5e7..6ab78c0eb80d 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1670,10 +1670,35 @@ 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. 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:
+ *
+ * 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 146da65448dc..77b7922bd288 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -737,10 +737,35 @@ 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. 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:
+ *
+ * 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 1e02728838e2..08d97d00e839 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -487,10 +487,35 @@ 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. 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:
+ *
+ * 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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 20/28] drm/atomic_state_helper: Provide comparison macros
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (18 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 19/28] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 21/28] drm/atomic_state_helper: Provide atomic_compare_state helpers Maxime Ripard
` (7 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard
In order to make the implementation of atomic_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 | 219 ++++++++++++++++++++++++++++++++
2 files changed, 249 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_sro_helper.c b/drivers/gpu/drm/drm_atomic_sro_helper.c
index 367d9bb7e442..b6411bfdba78 100644
--- a/drivers/gpu/drm/drm_atomic_sro_helper.c
+++ b/drivers/gpu/drm/drm_atomic_sro_helper.c
@@ -403,5 +403,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..db5d62ac0db9 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,222 @@ 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(name) " 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(name) " 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(name) " 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(name) " 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->format, \
+ &sb->f->format); \
+ 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(name) " is not a drm_property_blob pointer"); \
+ if (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(name) " is not an s32"); \
+ 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_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(name) " 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(name) " 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(name) " 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(name) " 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(name) " 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(name) " 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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 21/28] drm/atomic_state_helper: Provide atomic_compare_state helpers
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (19 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 20/28] drm/atomic_state_helper: Provide comparison macros Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 22/28] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
` (6 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 ++++
3 files changed, 284 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_sro_helper.c b/drivers/gpu/drm/drm_atomic_sro_helper.c
index b6411bfdba78..153c09a58430 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>
@@ -404,10 +405,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 6621d80f5252..e0f91e445dbb 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -563,10 +563,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);
@@ -591,10 +606,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 db5d62ac0db9..e7bc4816c743 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, ...);
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 22/28] drm/encoder: Create atomic_sro_get_current_crtc hook
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (20 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 21/28] drm/atomic_state_helper: Provide atomic_compare_state helpers Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 23/28] drm/bridge: display-connector: Implement readout support Maxime Ripard
` (5 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, Maxime Ripard
In order for drivers to implement drm_connectors atomic_state_readout
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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 23/28] drm/bridge: display-connector: Implement readout support
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (21 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 22/28] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 24/28] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
` (4 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
index 16c0631adeb1..94b645248368 100644
--- a/drivers/gpu/drm/bridge/display-connector.c
+++ b/drivers/gpu/drm/bridge/display-connector.c
@@ -12,10 +12,11 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <drm/drm_atomic_helper.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;
@@ -173,19 +174,33 @@ 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);
}
+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)
+{
+ // TODO: Actually implement it
+
+ return 0;
+}
+
static const struct drm_bridge_funcs display_connector_bridge_funcs = {
.attach = display_connector_attach,
.detect = display_connector_bridge_detect,
.edid_read = display_connector_edid_read,
.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_reset = drm_atomic_helper_bridge_reset,
+ .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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 24/28] drm/bridge_connector: Implement hw readout for connector
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (22 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 23/28] drm/bridge: display-connector: Implement readout support Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 25/28] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
` (3 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 31 ++++++++++++++++++++++++++
include/drm/drm_bridge.h | 27 ++++++++++++++++++++++
2 files changed, 58 insertions(+)
diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 4b310fe505b4..43f6de5c1295 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).
@@ -281,15 +290,33 @@ 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;
+
+ if (readout)
+ readout->funcs->atomic_sro_connector_readout(readout, state, conn_state);
+
+ return 0;
+}
+
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,
};
@@ -831,10 +858,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 36d558a5cd4d..17c863da5d6d 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1006,10 +1006,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.
*/
@@ -1146,10 +1166,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 25/28] drm/tidss: dispc: Improve mode checking logs
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (23 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 24/28] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 26/28] drm/tidss: Implement readout support Maxime Ripard
` (2 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 34bafe951924..c24c06cae10b 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1317,10 +1317,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.
@@ -1331,51 +1333,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;
@@ -1383,29 +1399,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 26/28] drm/tidss: Implement readout support
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (24 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 25/28] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 27/28] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 28/28] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 93 +++++++++++
drivers/gpu/drm/tidss/tidss_dispc.c | 287 ++++++++++++++++++++++++++++------
drivers/gpu/drm/tidss/tidss_dispc.h | 14 ++
drivers/gpu/drm/tidss/tidss_encoder.c | 13 ++
drivers/gpu/drm/tidss/tidss_kms.c | 6 +-
drivers/gpu/drm/tidss/tidss_plane.c | 154 ++++++++++++++++++
6 files changed, 518 insertions(+), 49 deletions(-)
diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
index 66e3d161c60b..baf90c0f02d1 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,96 @@ static struct drm_crtc_state *tidss_crtc_create_state(struct drm_crtc *crtc)
__drm_atomic_helper_crtc_create_state(crtc, &tstate->base);
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);
+
+ /*
+ * 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);
+ int 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))
@@ -404,10 +494,13 @@ static void tidss_crtc_destroy(struct drm_crtc *crtc)
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,
.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 c24c06cae10b..e6caf3c91a62 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1221,10 +1221,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);
}
@@ -1474,10 +1480,161 @@ 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_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;
+
+ if (FIELD_GET(DISPC_VP_POL_FREQ_IEO_MASK, val))
+ flags |= DRM_BUS_FLAG_DE_LOW;
+
+ if (FIELD_GET(DISPC_VP_POL_FREQ_RF_MASK, val))
+ flags |= DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE;
+
+ 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)
{
@@ -2119,10 +2276,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;
@@ -2316,16 +2484,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);
}
@@ -2913,51 +3152,10 @@ static void dispc_init_errata(struct dispc_device *dispc)
dispc->errata.i2000 = true;
dev_info(dispc->dev, "WA for erratum i2000: YUV formats disabled\n");
}
}
-/*
- * K2G display controller does not support soft reset, so we do a basic manual
- * reset here: make sure the IRQs are masked and VPs are disabled.
- */
-static void dispc_softreset_k2g(struct dispc_device *dispc)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&dispc->tidss->irq_lock, flags);
- dispc_set_irqenable(dispc, 0);
- dispc_read_and_clear_irqstatus(dispc);
- spin_unlock_irqrestore(&dispc->tidss->irq_lock, flags);
-
- for (unsigned int vp_idx = 0; vp_idx < dispc->feat->num_vps; ++vp_idx)
- VP_REG_FLD_MOD(dispc, vp_idx, DISPC_VP_CONTROL, 0,
- DISPC_VP_CONTROL_ENABLE_MASK);
-}
-
-static int dispc_softreset(struct dispc_device *dispc)
-{
- u32 val;
- int ret;
-
- if (dispc->feat->subrev == DISPC_K2G) {
- dispc_softreset_k2g(dispc);
- return 0;
- }
-
- /* Soft reset */
- REG_FLD_MOD(dispc, DSS_SYSCONFIG, 1, DSS_SYSCONFIG_SOFTRESET_MASK);
- /* Wait for reset to complete */
- ret = readl_poll_timeout(dispc->base_common + DSS_SYSSTATUS,
- val, val & 1, 100, 5000);
- if (ret) {
- dev_err(dispc->dev, "failed to reset dispc\n");
- return ret;
- }
-
- return 0;
-}
-
static int dispc_init_hw(struct dispc_device *dispc)
{
struct device *dev = dispc->dev;
int ret;
@@ -2971,26 +3169,19 @@ static int dispc_init_hw(struct dispc_device *dispc)
if (ret) {
dev_err(dev, "Failed to enable DSS fclk\n");
goto err_runtime_suspend;
}
- ret = dispc_softreset(dispc);
- if (ret)
- goto err_clk_disable;
-
clk_disable_unprepare(dispc->fclk);
ret = pm_runtime_set_suspended(dev);
if (ret) {
dev_err(dev, "Failed to set DSS PM to suspended\n");
return ret;
}
return 0;
-err_clk_disable:
- clk_disable_unprepare(dispc->fclk);
-
err_runtime_suspend:
ret = pm_runtime_set_suspended(dev);
if (ret) {
dev_err(dev, "Failed to set DSS PM to suspended\n");
return ret;
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
index 739d211d0018..b549b84c4b89 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,34 @@ 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_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,
const 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_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 db467bbcdb77..a20d7826e3cc 100644
--- a/drivers/gpu/drm/tidss/tidss_encoder.c
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -5,10 +5,11 @@
*/
#include <linux/export.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_crtc.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_panel.h>
@@ -71,16 +72,28 @@ 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)
+{
+ // TODO: Actually implement it
+ return 0;
+}
+
static const struct drm_bridge_funcs tidss_bridge_funcs = {
.attach = tidss_bridge_attach,
.atomic_check = tidss_bridge_atomic_check,
.atomic_reset = drm_atomic_helper_bridge_reset,
.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,
};
int tidss_encoder_create(struct tidss_device *tidss,
struct drm_bridge *next_bridge,
u32 encoder_type, u32 possible_crtcs)
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
index b4779c09a1bf..ce656c193777 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_vblank.h>
@@ -61,11 +63,12 @@ static void tidss_atomic_commit_tail(struct drm_atomic_state *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_state *state)
{
@@ -118,10 +121,11 @@ static int tidss_atomic_check(struct drm_device *ddev,
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,
};
static int tidss_dispc_modeset_init(struct tidss_device *tidss)
{
struct device *dev = tidss->dev;
diff --git a/drivers/gpu/drm/tidss/tidss_plane.c b/drivers/gpu/drm/tidss/tidss_plane.c
index 518498d45765..39939c43ff72 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) << 16;
+ 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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 27/28] drm/tidss: encoder: Implement atomic_sro_get_current_crtc
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (25 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 26/28] drm/tidss: Implement readout support Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 28/28] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c
index a20d7826e3cc..7883f5ec6d41 100644
--- a/drivers/gpu/drm/tidss/tidss_encoder.c
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -92,10 +92,29 @@ 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,
u32 encoder_type, u32 possible_crtcs)
{
struct tidss_encoder *t_enc;
@@ -106,12 +125,13 @@ int tidss_encoder_create(struct tidss_device *tidss,
t_enc = devm_drm_bridge_alloc(tidss->dev, struct tidss_encoder,
bridge, &tidss_bridge_funcs);
if (IS_ERR(t_enc))
return PTR_ERR(t_enc);
- ret = drm_simple_encoder_init(&tidss->ddev, &t_enc->encoder,
- encoder_type);
+ ret = drm_encoder_init(&tidss->ddev, &t_enc->encoder,
+ &tidss_encoder_funcs,
+ encoder_type, NULL);
if (ret)
return ret;
t_enc->tidss = tidss;
t_enc->next_bridge = next_bridge;
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 28/28] drm/bridge: sii902x: Implement hw state readout
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
` (26 preceding siblings ...)
2026-04-23 10:18 ` [PATCH v2 27/28] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
@ 2026-04-23 10:18 ` Maxime Ripard
27 siblings, 0 replies; 30+ messages in thread
From: Maxime Ripard @ 2026-04-23 10:18 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, linux-kernel, 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 | 56 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
index 12497f5ce4ff..4ad0fdfc1c38 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,17 +537,68 @@ 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);
+
+ 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);
+
+ if (regmap_test_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN))
+ return 0;
+
+ /* bridge_state is pretty trivial, we don't have anything to do here */
+
+ 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_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_reset = drm_atomic_helper_bridge_reset,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_get_input_bus_fmts = sii902x_bridge_atomic_get_input_bus_fmts,
.atomic_check = sii902x_bridge_atomic_check,
@@ -1131,11 +1184,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.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
end of thread, other threads:[~2026-04-23 10:19 UTC | newest]
Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-23 10:18 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 01/28] drm/atomic: Fix unused but set warning in state iterator macros Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 02/28] drm/atomic_helper: Skip over NULL private_obj pointers Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 03/28] drm/atomic_state_helper: Remove memset in __drm_atomic_helper_bridge_reset() Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 04/28] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 05/28] drm/atomic: Add drm_private_obj name Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 06/28] drm/bridge: Add drm_private_obj_is_bridge() Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 07/28] drm/bridge: Implement atomic_print_state Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 08/28] drm/atomic: Export drm_atomic_*_print_state Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 09/28] drm/atomic: Only call atomic_destroy_state on a !NULL pointer Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 10/28] drm/atomic_sro: Create drm_atomic_sro_state container Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 11/28] drm/atomic_sro: Create kernel parameter to force or disable readout Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 12/28] drm/atomic_sro: Add atomic state readout infrastructure Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 13/28] drm/atomic_sro: Add function to install state into drm objects Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 14/28] drm/atomic_sro: Create documentation Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 15/28] drm/bridge: Handle bridges with hardware state readout Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 16/28] drm/mode_config: Read out hardware state in drm_mode_config_create_state Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 17/28] drm/atomic_sro: Provide helpers to implement hardware state readout Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 18/28] drm/atomic_helper: Pass nonblock to commit_tail Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 19/28] drm/atomic_helper: Compare actual and readout states once the commit is done Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 20/28] drm/atomic_state_helper: Provide comparison macros Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 21/28] drm/atomic_state_helper: Provide atomic_compare_state helpers Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 22/28] drm/encoder: Create atomic_sro_get_current_crtc hook Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 23/28] drm/bridge: display-connector: Implement readout support Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 24/28] drm/bridge_connector: Implement hw readout for connector Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 25/28] drm/tidss: dispc: Improve mode checking logs Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 26/28] drm/tidss: Implement readout support Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 27/28] drm/tidss: encoder: Implement atomic_sro_get_current_crtc Maxime Ripard
2026-04-23 10:18 ` [PATCH v2 28/28] drm/bridge: sii902x: Implement hw state readout Maxime Ripard
-- strict thread matches above, loose matches on Subject: below --
2026-04-23 10:06 [PATCH v2 00/28] drm: Implement state readout support Maxime Ripard
2026-04-23 10:06 ` [PATCH v2 04/28] drm/atomic: Convert drm_priv_to_bridge_state to container_of_const Maxime Ripard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox