drm: Fix ww mutex crtc locking
authorFrançois Tigeot <ftigeot@wolfpond.org>
Thu, 24 Dec 2015 08:56:57 +0000 (09:56 +0100)
committerFrançois Tigeot <ftigeot@wolfpond.org>
Thu, 24 Dec 2015 08:56:19 +0000 (09:56 +0100)
* The original code had many problems:
  - the ctx wasn't available to drm_modeset_unlock().
  - 'struct list_head' was used for both the list head AND the list entry
  It's like it was coded to create maximum confusion on top of maximum pain

* drm_modeset_acquire_ctx structure needs a second list which we add
  kmalloc()'d entries to to track the extra locks in the EALREADY case

* Replace the reference from the modeset_lock structure with a pointer
  to an intermediate (kmalloc'd) structure that references both and turn
  the list entry in the lock into a list head

Submitted-by: Matthew Dillon <dillon@apollo.backplane.com>
sys/dev/drm/drm_modeset_lock.c
sys/dev/drm/include/drm/drm_modeset_lock.h

index 0a71b8f..ddb9f8e 100644 (file)
@@ -271,12 +271,12 @@ void drm_modeset_drop_locks(struct drm_modeset_acquire_ctx *ctx)
 {
        WARN_ON(ctx->contended);
        while (!list_empty(&ctx->locked)) {
-               struct drm_modeset_lock *lock;
+               struct drm_modeset_lock_info *info;
 
-               lock = list_first_entry(&ctx->locked,
-                               struct drm_modeset_lock, head);
+               info = list_first_entry(&ctx->locked,
+                               struct drm_modeset_lock_info, ctx_entry);
 
-               drm_modeset_unlock(lock);
+               drm_modeset_unlock(info->lock);
        }
 }
 EXPORT_SYMBOL(drm_modeset_drop_locks);
@@ -299,10 +299,7 @@ static inline int modeset_lock(struct drm_modeset_lock *lock,
        } else {
                ret = ww_mutex_lock(&lock->mutex, &ctx->ww_ctx);
        }
-       if (!ret) {
-               WARN_ON(!list_empty(&lock->head));
-               list_add(&lock->head, &ctx->locked);
-       } else if (ret == -EALREADY) {
+       if (ret == -EALREADY) {
                /* we already hold the lock.. this is fine.  For atomic
                 * we will need to be able to drm_modeset_lock() things
                 * without having to keep track of what is already locked
@@ -312,6 +309,17 @@ static inline int modeset_lock(struct drm_modeset_lock *lock,
        } else if (ret == -EDEADLK) {
                ctx->contended = lock;
        }
+       if (ret == 0) {
+               struct drm_modeset_lock_info *info;
+
+               info = kzalloc(sizeof(*info), GFP_KERNEL);
+               INIT_LIST_HEAD(&info->ctx_entry);
+               INIT_LIST_HEAD(&info->lock_entry);
+               info->lock = lock;
+               info->ctx = ctx;
+               list_add(&info->ctx_entry, &ctx->locked);
+               list_add(&info->lock_entry, &lock->locked);
+       }
 
        return ret;
 }
@@ -402,7 +410,17 @@ EXPORT_SYMBOL(drm_modeset_lock_interruptible);
  */
 void drm_modeset_unlock(struct drm_modeset_lock *lock)
 {
-       list_del_init(&lock->head);
+       struct drm_modeset_lock_info *info;
+
+       /* undo in reverse order */
+       if (!list_empty(&lock->locked)) {
+               info = list_last_entry(&lock->locked,
+                               struct drm_modeset_lock_info, lock_entry);
+               list_del_init(&info->lock_entry);
+               if (info->ctx)
+                       list_del_init(&info->ctx_entry);
+               kfree(info);
+       }
        ww_mutex_unlock(&lock->mutex);
 }
 EXPORT_SYMBOL(drm_modeset_unlock);
index 1e88695..d41f7dc 100644 (file)
@@ -50,7 +50,7 @@ struct drm_modeset_acquire_ctx {
        struct drm_modeset_lock *contended;
 
        /**
-        * list of held locks (drm_modeset_lock)
+        * list of held locks (drm_modeset_lock_info)
         */
        struct list_head locked;
 
@@ -60,6 +60,16 @@ struct drm_modeset_acquire_ctx {
        bool trylock_only;
 };
 
+/*
+ * Keep track of extra locks.
+ */
+struct drm_modeset_lock_info {
+       struct list_head ctx_entry;
+       struct list_head lock_entry;
+       struct drm_modeset_lock *lock;
+       struct drm_modeset_acquire_ctx *ctx;
+};
+
 /**
  * struct drm_modeset_lock - used for locking modeset resources.
  * @mutex: resource locking
@@ -78,7 +88,7 @@ struct drm_modeset_lock {
         * Resources that are locked as part of an atomic update are added
         * to a list (so we know what to unlock at the end).
         */
-       struct list_head head;
+       struct list_head locked;
 };
 
 extern struct ww_class crtc_ww_class;
@@ -97,7 +107,7 @@ int drm_modeset_backoff_interruptible(struct drm_modeset_acquire_ctx *ctx);
 static inline void drm_modeset_lock_init(struct drm_modeset_lock *lock)
 {
        ww_mutex_init(&lock->mutex, &crtc_ww_class);
-       INIT_LIST_HEAD(&lock->head);
+       INIT_LIST_HEAD(&lock->locked);
 }
 
 /**
@@ -106,7 +116,7 @@ static inline void drm_modeset_lock_init(struct drm_modeset_lock *lock)
  */
 static inline void drm_modeset_lock_fini(struct drm_modeset_lock *lock)
 {
-       WARN_ON(!list_empty(&lock->head));
+       WARN_ON(!list_empty(&lock->locked));
 }
 
 /**