VIGS: fix DPMS deadlock 50/26350/1
authorStanislav Vorobiov <s.vorobiov@samsung.com>
Tue, 5 Aug 2014 10:16:32 +0000 (14:16 +0400)
committerStanislav Vorobiov <s.vorobiov@samsung.com>
Tue, 19 Aug 2014 12:17:30 +0000 (16:17 +0400)
fb call chain callback might issue FB_BLANK event
itself, this leads to DPMS call in DRM. If fb call
chain walk is initiated from DPMS then this leads to
deadlock

Change-Id: I00f81cd8f81ea783f740f11767f65e4c01097989
Signed-off-by: Stanislav Vorobiov <s.vorobiov@samsung.com>
drivers/gpu/drm/vigs/vigs_crtc.c
drivers/gpu/drm/vigs/vigs_crtc.h
drivers/gpu/drm/vigs/vigs_device.h
drivers/gpu/drm/vigs/vigs_fbdev.c

index da578a3..2a17907 100644 (file)
@@ -100,16 +100,15 @@ static void vigs_crtc_destroy(struct drm_crtc *crtc)
 
 static void vigs_crtc_dpms(struct drm_crtc *crtc, int mode)
 {
-    struct vigs_crtc *vigs_crtc = crtc_to_vigs_crtc(crtc);
     struct vigs_device *vigs_dev = crtc->dev->dev_private;
     int blank, i;
     struct fb_event event;
 
-    DRM_DEBUG_KMS("enter: fb_blank = %d, mode = %d\n",
-                  vigs_crtc->in_fb_blank,
+    DRM_DEBUG_KMS("enter: in_dpms = %d, mode = %d\n",
+                  vigs_dev->in_dpms,
                   mode);
 
-    if (vigs_crtc->in_fb_blank) {
+    if (vigs_dev->in_dpms) {
         return;
     }
 
@@ -153,7 +152,20 @@ static void vigs_crtc_dpms(struct drm_crtc *crtc, int mode)
      */
     for (i = 0; i < 5; ++i) {
         if (console_trylock()) {
+            /*
+             * We must set in_dpms to true while walking
+             * fb call chain because a callback inside the
+             * call chain might do FB_BLANK on its own, i.e.
+             * 'vigs_fbdev_dpms' might get called from here. To avoid
+             * this we set in_dpms to true and 'vigs_fbdev_dpms'
+             * checks this and returns.
+             */
+            vigs_dev->in_dpms = true;
+
             fb_notifier_call_chain(FB_EVENT_BLANK, &event);
+
+            vigs_dev->in_dpms = false;
+
             console_unlock();
             return;
         }
index 6f33feb..b6e6feb 100644 (file)
@@ -8,12 +8,6 @@ struct vigs_device;
 struct vigs_crtc
 {
     struct drm_crtc base;
-
-    /*
-     * A hack to tell if DPMS callback is called from inside
-     * 'fb_blank' or not.
-     */
-    bool in_fb_blank;
 };
 
 static inline struct vigs_crtc *crtc_to_vigs_crtc(struct drm_crtc *crtc)
index 1838981..d75f61c 100644 (file)
@@ -60,6 +60,12 @@ struct vigs_device
      * so no race will occur.
      */
     bool track_gem_access;
+
+    /*
+     * A hack to tell if DPMS callback is called from inside
+     * 'fb_blank' or vice-versa.
+     */
+    bool in_dpms;
 };
 
 int vigs_device_init(struct vigs_device *vigs_dev,
index ba6176e..6447667 100644 (file)
@@ -182,13 +182,14 @@ static int vigs_fbdev_set_par(struct fb_info *fbi)
 }
 
 /*
- * This is 'drm_fb_helper_dpms' modified to set 'fbdev'
- * flag inside 'mode_config.mutex'.
+ * This is 'drm_fb_helper_dpms' modified to set 'in_dpms'
+ * flag inside drm_modeset_lock_all.
  */
 static void vigs_fbdev_dpms(struct fb_info *fbi, int dpms_mode)
 {
     struct drm_fb_helper *fb_helper = fbi->par;
     struct drm_device *dev = fb_helper->dev;
+    struct vigs_device *vigs_dev = dev->dev_private;
     struct drm_crtc *crtc;
     struct vigs_crtc *vigs_crtc;
     struct drm_connector *connector;
@@ -203,6 +204,18 @@ static void vigs_fbdev_dpms(struct fb_info *fbi, int dpms_mode)
         return;
     }
 
+    if (vigs_dev->in_dpms) {
+        /*
+         * If this is called from 'vigs_crtc_dpms' then we just
+         * return in order to not deadlock. Note that it's
+         * correct to check this flag here without any locks
+         * being held since 'fb_blank' callback is already called with
+         * console lock being held and 'vigs_crtc_dpms' only sets in_dpms
+         * inside the console lock.
+         */
+        return;
+    }
+
     /*
      * For each CRTC in this fb, turn the connectors on/off.
      */
@@ -220,7 +233,7 @@ static void vigs_fbdev_dpms(struct fb_info *fbi, int dpms_mode)
             continue;
         }
 
-        vigs_crtc->in_fb_blank = true;
+        vigs_dev->in_dpms = true;
 
         /* Walk the connectors & encoders on this fb turning them on/off */
         for (j = 0; j < fb_helper->connector_count; j++) {
@@ -230,7 +243,7 @@ static void vigs_fbdev_dpms(struct fb_info *fbi, int dpms_mode)
                 dev->mode_config.dpms_property, dpms_mode);
         }
 
-        vigs_crtc->in_fb_blank = false;
+        vigs_dev->in_dpms = false;
     }
 
     drm_modeset_unlock_all(dev);