summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/drm_encoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/drm_encoder.c')
-rw-r--r--drivers/gpu/drm/drm_encoder.c18
1 files changed, 16 insertions, 2 deletions
diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c
index 8f2bc6a28482..0d5dbed06db4 100644
--- a/drivers/gpu/drm/drm_encoder.c
+++ b/drivers/gpu/drm/drm_encoder.c
@@ -129,6 +129,7 @@ static int __drm_encoder_init(struct drm_device *dev,
}
INIT_LIST_HEAD(&encoder->bridge_chain);
+ mutex_init(&encoder->bridge_chain_mutex);
list_add_tail(&encoder->head, &dev->mode_config.encoder_list);
encoder->index = dev->mode_config.num_encoder++;
@@ -188,20 +189,33 @@ void drm_encoder_cleanup(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
struct drm_bridge *bridge, *next;
+ LIST_HEAD(tmplist);
/* Note that the encoder_list is considered to be static; should we
* remove the drm_encoder at runtime we would have to decrement all
* the indices on the drm_encoder after us in the encoder_list.
*/
- list_for_each_entry_safe(bridge, next, &encoder->bridge_chain,
- chain_node)
+ /*
+ * We need the bridge_chain_mutex to modify the chain, but
+ * drm_bridge_detach() will call DRM_MODESET_LOCK_ALL_BEGIN() (in
+ * drm_modeset_lock_fini()), resulting in a possible ABBA circular
+ * deadlock. Avoid it by first moving all the bridges to a
+ * temporary list holding the lock, and then calling
+ * drm_bridge_detach() without the lock.
+ */
+ mutex_lock(&encoder->bridge_chain_mutex);
+ list_cut_before(&tmplist, &encoder->bridge_chain, &encoder->bridge_chain);
+ mutex_unlock(&encoder->bridge_chain_mutex);
+
+ list_for_each_entry_safe(bridge, next, &tmplist, chain_node)
drm_bridge_detach(bridge);
drm_mode_object_unregister(dev, &encoder->base);
kfree(encoder->name);
list_del(&encoder->head);
dev->mode_config.num_encoder--;
+ mutex_destroy(&encoder->bridge_chain_mutex);
memset(encoder, 0, sizeof(*encoder));
}