diff mbox series

drm/fb-helper: generic: Fix setup error path

Message ID 20190105181846.26495-1-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show
Series drm/fb-helper: generic: Fix setup error path | expand

Commit Message

Noralf Trønnes Jan. 5, 2019, 6:18 p.m. UTC
If register_framebuffer() fails during fbdev setup we will leak the
framebuffer, the GEM buffer and the shadow buffer for defio. This is
because drm_fb_helper_fbdev_setup() just calls drm_fb_helper_fini() on
error not taking into account that register_framebuffer() can fail.

Since the generic emulation uses DRM client for its framebuffer and
backing buffer in addition to a shadow buffer, it's necessary to open code
drm_fb_helper_fbdev_setup() to properly handle the error path.

Error cleanup is removed from .fb_probe and is handled by one function for
all paths.

Fixes: 9060d7f49376 ("drm/fb-helper: Finish the generic fbdev emulation")
Reported-by: Peter Wu <peter@lekensteyn.nl>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_fb_helper.c | 98 +++++++++++++++++++--------------
 1 file changed, 58 insertions(+), 40 deletions(-)

Comments

Gerd Hoffmann Jan. 7, 2019, 12:39 p.m. UTC | #1
Hi,

> If register_framebuffer() fails during fbdev setup we will leak the
> framebuffer, the GEM buffer and the shadow buffer for defio. This is
> because drm_fb_helper_fbdev_setup() just calls drm_fb_helper_fini() on
> error not taking into account that register_framebuffer() can fail.
> 
> Since the generic emulation uses DRM client for its framebuffer and
> backing buffer in addition to a shadow buffer, it's necessary to open code
> drm_fb_helper_fbdev_setup() to properly handle the error path.
> 
> Error cleanup is removed from .fb_probe and is handled by one function for
> all paths.
> 
> Fixes: 9060d7f49376 ("drm/fb-helper: Finish the generic fbdev emulation")
> Reported-by: Peter Wu <peter@lekensteyn.nl>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Looks sane to me.

Acked-by: Gerd Hoffmann <kraxel@redhat.com>

> ---
>  drivers/gpu/drm/drm_fb_helper.c | 98 +++++++++++++++++++--------------
>  1 file changed, 58 insertions(+), 40 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index 5e9ca6f96379..80136a7a5af1 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -2961,18 +2961,16 @@ static int drm_fbdev_fb_release(struct fb_info *info, int user)
>  	return 0;
>  }
>  
> -/*
> - * fb_ops.fb_destroy is called by the last put_fb_info() call at the end of
> - * unregister_framebuffer() or fb_release().
> - */
> -static void drm_fbdev_fb_destroy(struct fb_info *info)
> +static void drm_fbdev_cleanup(struct drm_fb_helper *fb_helper)
>  {
> -	struct drm_fb_helper *fb_helper = info->par;
>  	struct fb_info *fbi = fb_helper->fbdev;
>  	struct fb_ops *fbops = NULL;
>  	void *shadow = NULL;
>  
> -	if (fbi->fbdefio) {
> +	if (!fb_helper->dev)
> +		return;
> +
> +	if (fbi && fbi->fbdefio) {
>  		fb_deferred_io_cleanup(fbi);
>  		shadow = fbi->screen_buffer;
>  		fbops = fbi->fbops;
> @@ -2986,6 +2984,12 @@ static void drm_fbdev_fb_destroy(struct fb_info *info)
>  	}
>  
>  	drm_client_framebuffer_delete(fb_helper->buffer);
> +}
> +
> +static void drm_fbdev_release(struct drm_fb_helper *fb_helper)
> +{
> +	drm_fbdev_cleanup(fb_helper);
> +
>  	/*
>  	 * FIXME:
>  	 * Remove conditional when all CMA drivers have been moved over to using
> @@ -2997,6 +3001,15 @@ static void drm_fbdev_fb_destroy(struct fb_info *info)
>  	}
>  }
>  
> +/*
> + * fb_ops.fb_destroy is called by the last put_fb_info() call at the end of
> + * unregister_framebuffer() or fb_release().
> + */
> +static void drm_fbdev_fb_destroy(struct fb_info *info)
> +{
> +	drm_fbdev_release(info->par);
> +}
> +
>  static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
>  {
>  	struct drm_fb_helper *fb_helper = info->par;
> @@ -3047,7 +3060,6 @@ int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
>  	struct drm_framebuffer *fb;
>  	struct fb_info *fbi;
>  	u32 format;
> -	int ret;
>  
>  	DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
>  		      sizes->surface_width, sizes->surface_height,
> @@ -3064,10 +3076,8 @@ int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
>  	fb = buffer->fb;
>  
>  	fbi = drm_fb_helper_alloc_fbi(fb_helper);
> -	if (IS_ERR(fbi)) {
> -		ret = PTR_ERR(fbi);
> -		goto err_free_buffer;
> -	}
> +	if (IS_ERR(fbi))
> +		return PTR_ERR(fbi);
>  
>  	fbi->par = fb_helper;
>  	fbi->fbops = &drm_fbdev_fb_ops;
> @@ -3098,8 +3108,7 @@ int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
>  		if (!fbops || !shadow) {
>  			kfree(fbops);
>  			vfree(shadow);
> -			ret = -ENOMEM;
> -			goto err_fb_info_destroy;
> +			return -ENOMEM;
>  		}
>  
>  		*fbops = *fbi->fbops;
> @@ -3111,13 +3120,6 @@ int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
>  	}
>  
>  	return 0;
> -
> -err_fb_info_destroy:
> -	drm_fb_helper_fini(fb_helper);
> -err_free_buffer:
> -	drm_client_framebuffer_delete(buffer);
> -
> -	return ret;
>  }
>  EXPORT_SYMBOL(drm_fb_helper_generic_probe);
>  
> @@ -3129,18 +3131,11 @@ static void drm_fbdev_client_unregister(struct drm_client_dev *client)
>  {
>  	struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
>  
> -	if (fb_helper->fbdev) {
> -		drm_fb_helper_unregister_fbi(fb_helper);
> +	if (fb_helper->fbdev)
>  		/* drm_fbdev_fb_destroy() takes care of cleanup */
> -		return;
> -	}
> -
> -	/* Did drm_fb_helper_fbdev_setup() run? */
> -	if (fb_helper->dev)
> -		drm_fb_helper_fini(fb_helper);
> -
> -	drm_client_release(client);
> -	kfree(fb_helper);
> +		drm_fb_helper_unregister_fbi(fb_helper);
> +	else
> +		drm_fbdev_release(fb_helper);
>  }
>  
>  static int drm_fbdev_client_restore(struct drm_client_dev *client)
> @@ -3158,7 +3153,7 @@ static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
>  	struct drm_device *dev = client->dev;
>  	int ret;
>  
> -	/* If drm_fb_helper_fbdev_setup() failed, we only try once */
> +	/* Setup is not retried if it has failed */
>  	if (!fb_helper->dev && fb_helper->funcs)
>  		return 0;
>  
> @@ -3170,15 +3165,34 @@ static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
>  		return 0;
>  	}
>  
> -	ret = drm_fb_helper_fbdev_setup(dev, fb_helper, &drm_fb_helper_generic_funcs,
> -					fb_helper->preferred_bpp, 0);
> -	if (ret) {
> -		fb_helper->dev = NULL;
> -		fb_helper->fbdev = NULL;
> -		return ret;
> -	}
> +	drm_fb_helper_prepare(dev, fb_helper, &drm_fb_helper_generic_funcs);
> +
> +	ret = drm_fb_helper_init(dev, fb_helper, dev->mode_config.num_connector);
> +	if (ret)
> +		goto err;
> +
> +	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
> +	if (ret)
> +		goto err_cleanup;
> +
> +	if (!drm_drv_uses_atomic_modeset(dev))
> +		drm_helper_disable_unused_functions(dev);
> +
> +	ret = drm_fb_helper_initial_config(fb_helper, fb_helper->preferred_bpp);
> +	if (ret)
> +		goto err_cleanup;
>  
>  	return 0;
> +
> +err_cleanup:
> +	drm_fbdev_cleanup(fb_helper);
> +err:
> +	fb_helper->dev = NULL;
> +	fb_helper->fbdev = NULL;
> +
> +	DRM_DEV_ERROR(dev->dev, "fbdev: Failed to setup generic emulation (ret=%d)\n", ret);
> +
> +	return ret;
>  }
>  
>  static const struct drm_client_funcs drm_fbdev_client_funcs = {
> @@ -3237,6 +3251,10 @@ int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
>  
>  	drm_client_add(&fb_helper->client);
>  
> +	if (!preferred_bpp)
> +		preferred_bpp = dev->mode_config.preferred_depth;
> +	if (!preferred_bpp)
> +		preferred_bpp = 32;
>  	fb_helper->preferred_bpp = preferred_bpp;
>  
>  	ret = drm_fbdev_client_hotplug(&fb_helper->client);
> -- 
> 2.20.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Noralf Trønnes Jan. 8, 2019, 5:54 p.m. UTC | #2
Den 07.01.2019 13.39, skrev Gerd Hoffmann:
>   Hi,
> 
>> If register_framebuffer() fails during fbdev setup we will leak the
>> framebuffer, the GEM buffer and the shadow buffer for defio. This is
>> because drm_fb_helper_fbdev_setup() just calls drm_fb_helper_fini() on
>> error not taking into account that register_framebuffer() can fail.
>>
>> Since the generic emulation uses DRM client for its framebuffer and
>> backing buffer in addition to a shadow buffer, it's necessary to open code
>> drm_fb_helper_fbdev_setup() to properly handle the error path.
>>
>> Error cleanup is removed from .fb_probe and is handled by one function for
>> all paths.
>>
>> Fixes: 9060d7f49376 ("drm/fb-helper: Finish the generic fbdev emulation")
>> Reported-by: Peter Wu <peter@lekensteyn.nl>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> 
> Looks sane to me.
> 
> Acked-by: Gerd Hoffmann <kraxel@redhat.com>

Thanks, applied to drm-misc-next.

Noralf.
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index 5e9ca6f96379..80136a7a5af1 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -2961,18 +2961,16 @@  static int drm_fbdev_fb_release(struct fb_info *info, int user)
 	return 0;
 }
 
-/*
- * fb_ops.fb_destroy is called by the last put_fb_info() call at the end of
- * unregister_framebuffer() or fb_release().
- */
-static void drm_fbdev_fb_destroy(struct fb_info *info)
+static void drm_fbdev_cleanup(struct drm_fb_helper *fb_helper)
 {
-	struct drm_fb_helper *fb_helper = info->par;
 	struct fb_info *fbi = fb_helper->fbdev;
 	struct fb_ops *fbops = NULL;
 	void *shadow = NULL;
 
-	if (fbi->fbdefio) {
+	if (!fb_helper->dev)
+		return;
+
+	if (fbi && fbi->fbdefio) {
 		fb_deferred_io_cleanup(fbi);
 		shadow = fbi->screen_buffer;
 		fbops = fbi->fbops;
@@ -2986,6 +2984,12 @@  static void drm_fbdev_fb_destroy(struct fb_info *info)
 	}
 
 	drm_client_framebuffer_delete(fb_helper->buffer);
+}
+
+static void drm_fbdev_release(struct drm_fb_helper *fb_helper)
+{
+	drm_fbdev_cleanup(fb_helper);
+
 	/*
 	 * FIXME:
 	 * Remove conditional when all CMA drivers have been moved over to using
@@ -2997,6 +3001,15 @@  static void drm_fbdev_fb_destroy(struct fb_info *info)
 	}
 }
 
+/*
+ * fb_ops.fb_destroy is called by the last put_fb_info() call at the end of
+ * unregister_framebuffer() or fb_release().
+ */
+static void drm_fbdev_fb_destroy(struct fb_info *info)
+{
+	drm_fbdev_release(info->par);
+}
+
 static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
 {
 	struct drm_fb_helper *fb_helper = info->par;
@@ -3047,7 +3060,6 @@  int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
 	struct drm_framebuffer *fb;
 	struct fb_info *fbi;
 	u32 format;
-	int ret;
 
 	DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
 		      sizes->surface_width, sizes->surface_height,
@@ -3064,10 +3076,8 @@  int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
 	fb = buffer->fb;
 
 	fbi = drm_fb_helper_alloc_fbi(fb_helper);
-	if (IS_ERR(fbi)) {
-		ret = PTR_ERR(fbi);
-		goto err_free_buffer;
-	}
+	if (IS_ERR(fbi))
+		return PTR_ERR(fbi);
 
 	fbi->par = fb_helper;
 	fbi->fbops = &drm_fbdev_fb_ops;
@@ -3098,8 +3108,7 @@  int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
 		if (!fbops || !shadow) {
 			kfree(fbops);
 			vfree(shadow);
-			ret = -ENOMEM;
-			goto err_fb_info_destroy;
+			return -ENOMEM;
 		}
 
 		*fbops = *fbi->fbops;
@@ -3111,13 +3120,6 @@  int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
 	}
 
 	return 0;
-
-err_fb_info_destroy:
-	drm_fb_helper_fini(fb_helper);
-err_free_buffer:
-	drm_client_framebuffer_delete(buffer);
-
-	return ret;
 }
 EXPORT_SYMBOL(drm_fb_helper_generic_probe);
 
@@ -3129,18 +3131,11 @@  static void drm_fbdev_client_unregister(struct drm_client_dev *client)
 {
 	struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
 
-	if (fb_helper->fbdev) {
-		drm_fb_helper_unregister_fbi(fb_helper);
+	if (fb_helper->fbdev)
 		/* drm_fbdev_fb_destroy() takes care of cleanup */
-		return;
-	}
-
-	/* Did drm_fb_helper_fbdev_setup() run? */
-	if (fb_helper->dev)
-		drm_fb_helper_fini(fb_helper);
-
-	drm_client_release(client);
-	kfree(fb_helper);
+		drm_fb_helper_unregister_fbi(fb_helper);
+	else
+		drm_fbdev_release(fb_helper);
 }
 
 static int drm_fbdev_client_restore(struct drm_client_dev *client)
@@ -3158,7 +3153,7 @@  static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
 	struct drm_device *dev = client->dev;
 	int ret;
 
-	/* If drm_fb_helper_fbdev_setup() failed, we only try once */
+	/* Setup is not retried if it has failed */
 	if (!fb_helper->dev && fb_helper->funcs)
 		return 0;
 
@@ -3170,15 +3165,34 @@  static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
 		return 0;
 	}
 
-	ret = drm_fb_helper_fbdev_setup(dev, fb_helper, &drm_fb_helper_generic_funcs,
-					fb_helper->preferred_bpp, 0);
-	if (ret) {
-		fb_helper->dev = NULL;
-		fb_helper->fbdev = NULL;
-		return ret;
-	}
+	drm_fb_helper_prepare(dev, fb_helper, &drm_fb_helper_generic_funcs);
+
+	ret = drm_fb_helper_init(dev, fb_helper, dev->mode_config.num_connector);
+	if (ret)
+		goto err;
+
+	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
+	if (ret)
+		goto err_cleanup;
+
+	if (!drm_drv_uses_atomic_modeset(dev))
+		drm_helper_disable_unused_functions(dev);
+
+	ret = drm_fb_helper_initial_config(fb_helper, fb_helper->preferred_bpp);
+	if (ret)
+		goto err_cleanup;
 
 	return 0;
+
+err_cleanup:
+	drm_fbdev_cleanup(fb_helper);
+err:
+	fb_helper->dev = NULL;
+	fb_helper->fbdev = NULL;
+
+	DRM_DEV_ERROR(dev->dev, "fbdev: Failed to setup generic emulation (ret=%d)\n", ret);
+
+	return ret;
 }
 
 static const struct drm_client_funcs drm_fbdev_client_funcs = {
@@ -3237,6 +3251,10 @@  int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
 
 	drm_client_add(&fb_helper->client);
 
+	if (!preferred_bpp)
+		preferred_bpp = dev->mode_config.preferred_depth;
+	if (!preferred_bpp)
+		preferred_bpp = 32;
 	fb_helper->preferred_bpp = preferred_bpp;
 
 	ret = drm_fbdev_client_hotplug(&fb_helper->client);