From b1aad16d0cd3495854f4193ad4c27f889d178a76 Mon Sep 17 00:00:00 2001 From: Stanislav Vorobiov Date: Tue, 5 Aug 2014 14:16:32 +0400 Subject: [PATCH] VIGS: fix DPMS deadlock 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 --- drivers/gpu/drm/vigs/vigs_crtc.c | 20 ++++++++++++++++---- drivers/gpu/drm/vigs/vigs_crtc.h | 6 ------ drivers/gpu/drm/vigs/vigs_device.h | 6 ++++++ drivers/gpu/drm/vigs/vigs_fbdev.c | 21 +++++++++++++++++---- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/vigs/vigs_crtc.c b/drivers/gpu/drm/vigs/vigs_crtc.c index da578a3a02b3..2a17907690c9 100644 --- a/drivers/gpu/drm/vigs/vigs_crtc.c +++ b/drivers/gpu/drm/vigs/vigs_crtc.c @@ -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; } diff --git a/drivers/gpu/drm/vigs/vigs_crtc.h b/drivers/gpu/drm/vigs/vigs_crtc.h index 6f33feb0e813..b6e6febf661d 100644 --- a/drivers/gpu/drm/vigs/vigs_crtc.h +++ b/drivers/gpu/drm/vigs/vigs_crtc.h @@ -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) diff --git a/drivers/gpu/drm/vigs/vigs_device.h b/drivers/gpu/drm/vigs/vigs_device.h index 183898137881..d75f61c6464e 100644 --- a/drivers/gpu/drm/vigs/vigs_device.h +++ b/drivers/gpu/drm/vigs/vigs_device.h @@ -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, diff --git a/drivers/gpu/drm/vigs/vigs_fbdev.c b/drivers/gpu/drm/vigs/vigs_fbdev.c index ba6176ecca68..6447667108ed 100644 --- a/drivers/gpu/drm/vigs/vigs_fbdev.c +++ b/drivers/gpu/drm/vigs/vigs_fbdev.c @@ -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); -- 2.34.1