[Intel-gfx] [PATCH v4 06/11] drm/fb-helper: Make top-level lock more robust

Daniel Vetter daniel at ffwll.ch
Mon Apr 3 08:40:07 UTC 2017


On Wed, Mar 29, 2017 at 04:43:56PM +0200, Thierry Reding wrote:
> From: Thierry Reding <treding at nvidia.com>
> 
> The existing drm_fb_helper_hotplug_event() function needs to take the
> top-level fb-helper lock. However, the function can also be called from
> code that has already taken this lock. Introduce an unlocked variant of
> this function that can be used in the latter case.
> 
> This function calls drm_fb_helper_restore_fbdev_mode_unlocked(), via
> drm_fb_helper_set_par(), so we also need to introduce an unlocked copy
> of that to avoid recursive locking issues.
> 
> Similarly, the drm_fb_helper_initial_config() function ends up calling
> drm_fb_helper_set_par(), via register_framebuffer(), and needs an
> unlocked variant to avoid recursive locking.
> 
> Signed-off-by: Thierry Reding <treding at nvidia.com>
> ---
>  drivers/gpu/drm/drm_fb_helper.c | 167 +++++++++++++++++++++++++---------------
>  1 file changed, 104 insertions(+), 63 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index 860be51d92f6..21a90322531c 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -491,18 +491,10 @@ static int restore_fbdev_mode(struct drm_fb_helper *fb_helper)
>  	return 0;
>  }
>  
> -/**
> - * drm_fb_helper_restore_fbdev_mode_unlocked - restore fbdev configuration
> - * @fb_helper: fbcon to restore
> - *
> - * This should be called from driver's drm &drm_driver.lastclose callback
> - * when implementing an fbcon on top of kms using this helper. This ensures that
> - * the user isn't greeted with a black screen when e.g. X dies.
> - *
> - * RETURNS:
> - * Zero if everything went ok, negative error code otherwise.
> - */
> -int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
> +static int __drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper);
> +
> +static int
> +__drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
>  {
>  	struct drm_device *dev = fb_helper->dev;
>  	bool do_delayed;
> @@ -511,7 +503,8 @@ int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
>  	if (!drm_fbdev_emulation)
>  		return -ENODEV;
>  
> -	mutex_lock(&fb_helper->lock);
> +	WARN_ON(!mutex_is_locked(&fb_helper->lock));

lockdep_assert_held is the new cool.

> +
>  	drm_modeset_lock_all(dev);
>  
>  	ret = restore_fbdev_mode(fb_helper);
> @@ -521,10 +514,31 @@ int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
>  		fb_helper->delayed_hotplug = false;
>  
>  	drm_modeset_unlock_all(dev);
> -	mutex_unlock(&fb_helper->lock);
>  
>  	if (do_delayed)
> -		drm_fb_helper_hotplug_event(fb_helper);
> +		__drm_fb_helper_hotplug_event(fb_helper);
> +
> +	return ret;
> +}
> +
> +/**
> + * drm_fb_helper_restore_fbdev_mode_unlocked - restore fbdev configuration
> + * @fb_helper: fbcon to restore
> + *
> + * This should be called from driver's drm &drm_driver.lastclose callback
> + * when implementing an fbcon on top of kms using this helper. This ensures that
> + * the user isn't greeted with a black screen when e.g. X dies.
> + *
> + * RETURNS:
> + * Zero if everything went ok, negative error code otherwise.
> + */
> +int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
> +{
> +	int ret;
> +
> +	mutex_lock(&fb_helper->lock);
> +	ret = __drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
> +	mutex_unlock(&fb_helper->lock);
>  
>  	return ret;
>  }
> @@ -1486,7 +1500,7 @@ int drm_fb_helper_set_par(struct fb_info *info)
>  		return -EINVAL;
>  	}
>  
> -	drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
> +	__drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);

Nah, you need the locking still for when this is called from userspace
through fbdev ioctl.
>  
>  	return 0;
>  }
> @@ -2333,6 +2347,46 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper,
>  	kfree(enabled);
>  }
>  
> +static int __drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper,
> +					  int bpp_sel)
> +{
> +	struct drm_device *dev = fb_helper->dev;
> +	struct fb_info *info;
> +	int ret;
> +
> +	if (!drm_fbdev_emulation)
> +		return 0;
> +
> +	WARN_ON(!mutex_is_locked(&fb_helper->lock));
> +
> +	mutex_lock(&dev->mode_config.mutex);
> +	drm_setup_crtcs(fb_helper,
> +			dev->mode_config.max_width,
> +			dev->mode_config.max_height);
> +	ret = drm_fb_helper_single_fb_probe(fb_helper, bpp_sel);
> +	mutex_unlock(&dev->mode_config.mutex);
> +	if (ret)
> +		return ret;
> +
> +	info = fb_helper->fbdev;
> +	info->var.pixclock = 0;
> +	ret = register_framebuffer(info);
> +	if (ret < 0)
> +		return ret;
> +
> +	dev_info(dev->dev, "fb%d: %s frame buffer device\n",
> +		 info->node, info->fix.id);
> +
> +	mutex_lock(&kernel_fb_helper_lock);
> +	if (list_empty(&kernel_fb_helper_list))
> +		register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
> +
> +	list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
> +	mutex_unlock(&kernel_fb_helper_lock);
> +
> +	return 0;
> +}
> +
>  /**
>   * drm_fb_helper_initial_config - setup a sane initial connector configuration
>   * @fb_helper: fb_helper device struct
> @@ -2377,41 +2431,50 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper,
>   */
>  int drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper, int bpp_sel)
>  {
> -	struct drm_device *dev = fb_helper->dev;
> -	struct fb_info *info;
>  	int ret;
>  
> +	mutex_lock(&fb_helper->lock);
> +	ret = __drm_fb_helper_initial_config(fb_helper, bpp_sel);
> +	mutex_unlock(&fb_helper->lock);

Yeah this won't work because it'll deadlock with the register_framebuffer.
You need to push it down so that it only protects the same critical
section as mode_config.mutex currently ddoes.

> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_fb_helper_initial_config);
> +
> +static int __drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
> +{
> +	struct drm_device *dev = fb_helper->dev;
> +	unsigned int width, height;
> +
>  	if (!drm_fbdev_emulation)
>  		return 0;
>  
> +	WARN_ON(!mutex_is_locked(&fb_helper->lock));
> +
>  	mutex_lock(&dev->mode_config.mutex);
> -	drm_setup_crtcs(fb_helper,
> -			dev->mode_config.max_width,
> -			dev->mode_config.max_height);
> -	ret = drm_fb_helper_single_fb_probe(fb_helper, bpp_sel);
> -	mutex_unlock(&dev->mode_config.mutex);
> -	if (ret)
> -		return ret;
>  
> -	info = fb_helper->fbdev;
> -	info->var.pixclock = 0;
> -	ret = register_framebuffer(info);
> -	if (ret < 0)
> -		return ret;
> +	if (!fb_helper->fb || !drm_fb_helper_is_bound(fb_helper)) {
> +		fb_helper->delayed_hotplug = true;
> +		mutex_unlock(&dev->mode_config.mutex);
> +		return 0;
> +	}
>  
> -	dev_info(dev->dev, "fb%d: %s frame buffer device\n",
> -		 info->node, info->fix.id);
> +	DRM_DEBUG_KMS("\n");
>  
> -	mutex_lock(&kernel_fb_helper_lock);
> -	if (list_empty(&kernel_fb_helper_list))
> -		register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
> +	width = dev->mode_config.max_width;
> +	height = dev->mode_config.max_height;
>  
> -	list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
> -	mutex_unlock(&kernel_fb_helper_lock);
> +	if (drm_fb_helper_probe_connector_modes(fb_helper, width, height) == 0)
> +		DRM_DEBUG_KMS("No connectors reported connected with modes\n");
> +
> +	drm_setup_crtcs(fb_helper, fb_helper->fb->width, fb_helper->fb->height);
> +
> +	mutex_unlock(&dev->mode_config.mutex);
> +
> +	drm_fb_helper_set_par(fb_helper->fbdev);
>  
>  	return 0;
>  }
> -EXPORT_SYMBOL(drm_fb_helper_initial_config);
>  
>  /**
>   * drm_fb_helper_hotplug_event - respond to a hotplug notification by
> @@ -2436,35 +2499,13 @@ EXPORT_SYMBOL(drm_fb_helper_initial_config);
>   */
>  int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
>  {
> -	struct drm_device *dev = fb_helper->dev;
> -	int err = 0;
> -
> -	if (!drm_fbdev_emulation)
> -		return 0;
> +	int ret;
>  
>  	mutex_lock(&fb_helper->lock);
> -	mutex_lock(&dev->mode_config.mutex);
> -
> -	if (!fb_helper->fb || !drm_fb_helper_is_bound(fb_helper)) {
> -		fb_helper->delayed_hotplug = true;
> -		mutex_unlock(&dev->mode_config.mutex);
> -		goto unlock;
> -	}
> -
> -	DRM_DEBUG_KMS("\n");
> -
> -	drm_setup_crtcs(fb_helper, fb_helper->fb->width, fb_helper->fb->height);
> -
> -	mutex_unlock(&dev->mode_config.mutex);
> +	ret = __drm_fb_helper_hotplug_event(fb_helper);
>  	mutex_unlock(&fb_helper->lock);

Yeah you can't hold the lock through the entire hotplug_event process,
otherwise the potential register_framebuffer will go boom on a deadlock.
I'll reply to one of the other patches with what I think should be done.
-Daniel

>  
> -	drm_fb_helper_set_par(fb_helper->fbdev);
> -
> -	return 0;
> -
> -unlock:
> -	mutex_unlock(&fb_helper->lock);
> -	return err;
> +	return ret;
>  }
>  EXPORT_SYMBOL(drm_fb_helper_hotplug_event);
>  
> -- 
> 2.12.0
> 
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch


More information about the Intel-gfx mailing list