[PATCH 1/1] drm/bridge: Fix handling of bridges with pre_enable_prev_first flag

From: Vladimir Lypak
Date: Fri Jul 07 2023 - 15:09:12 EST


In function drm_atomic_bridge_chain_post_disable handling of
pre_enable_prev_first flag is broken because "next" variable will always
end up set to value of "bridge". This breaks loop which should disable
bridges in reverse:

next = list_next_entry(bridge, chain_node);

if (next->pre_enable_prev_first) {
/* next bridge had requested that prev
* was enabled first, so disabled last
*/
limit = next;

/* Find the next bridge that has NOT requested
* prev to be enabled first / disabled last
*/
list_for_each_entry_from(next, &encoder->bridge_chain,
chain_node) {
// Next condition is always true. It is likely meant to be inversed
// according to comment above. But doing this uncovers another problem:
// it won't work if there are few bridges with this flag set at the end.
if (next->pre_enable_prev_first) {
next = list_prev_entry(next, chain_node);
limit = next;
// Here we always set next = limit = branch at first iteration.
break;
}
}

/* Call these bridges in reverse order */
list_for_each_entry_from_reverse(next, &encoder->bridge_chain,
chain_node) {
// This loop never executes past this branch.
if (next == bridge)
break;

drm_atomic_bridge_call_post_disable(next, old_state);

In this patch logic for handling the flag is simplified. Temporary
"iter" variable is introduced instead of "next" which is used only
inside inner loops.

Fixes: 4fb912e5e190 ("drm/bridge: Introduce pre_enable_prev_first to alter bridge init order")
Signed-off-by: Vladimir Lypak <vladimir.lypak@xxxxxxxxx>
---
drivers/gpu/drm/drm_bridge.c | 46 ++++++++++++++----------------------
1 file changed, 18 insertions(+), 28 deletions(-)

diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index c3d69af02e79..7a5b39a46325 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -660,7 +660,7 @@ void drm_atomic_bridge_chain_post_disable(struct drm_bridge *bridge,
struct drm_atomic_state *old_state)
{
struct drm_encoder *encoder;
- struct drm_bridge *next, *limit;
+ struct drm_bridge *iter, *limit;

if (!bridge)
return;
@@ -670,36 +670,26 @@ void drm_atomic_bridge_chain_post_disable(struct drm_bridge *bridge,
list_for_each_entry_from(bridge, &encoder->bridge_chain, chain_node) {
limit = NULL;

- if (!list_is_last(&bridge->chain_node, &encoder->bridge_chain)) {
- next = list_next_entry(bridge, chain_node);
-
- if (next->pre_enable_prev_first) {
- /* next bridge had requested that prev
- * was enabled first, so disabled last
- */
- limit = next;
-
- /* Find the next bridge that has NOT requested
- * prev to be enabled first / disabled last
- */
- list_for_each_entry_from(next, &encoder->bridge_chain,
- chain_node) {
- if (next->pre_enable_prev_first) {
- next = list_prev_entry(next, chain_node);
- limit = next;
- break;
- }
- }
+ /* Find sequence of bridges (bridge, limit] which requested prev to
+ * be enabled first and disabled last
+ */
+ iter = list_next_entry(bridge, chain_node);
+ list_for_each_entry_from(iter, &encoder->bridge_chain, chain_node) {
+ if (!iter->pre_enable_prev_first)
+ break;
+
+ limit = iter;
+ }

- /* Call these bridges in reverse order */
- list_for_each_entry_from_reverse(next, &encoder->bridge_chain,
- chain_node) {
- if (next == bridge)
- break;
-
- drm_atomic_bridge_call_post_disable(next,
- old_state);
- }
+ if (limit) {
+ /* Call these bridges in reverse order */
+ iter = limit;
+ list_for_each_entry_from_reverse(iter,
+ &encoder->bridge_chain, chain_node) {
+ if (iter == bridge)
+ break;
+
+ drm_atomic_bridge_call_post_disable(iter, old_state);
}
}

--
2.41.0