diff mbox

[2/6] drm/fb-helper: Support device unplug

Message ID 1503940668-25883-3-git-send-email-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes Aug. 28, 2017, 5:17 p.m. UTC
Support device unplug by introducing a new initialization function:
drm_fb_helper_simple_init() which together with
drm_fb_helper_dev_unplug() and drm_fb_helper_fb_destroy() handles open
fbdev fd's after device unplug. There's also a
drm_fb_helper_simple_fini() for drivers who's device won't be removed.

It turns out that fbdev has the necessary ref counting, so
unregister_framebuffer() together with fb_ops->destroy handles unplug
with open fd's. The ref counting doesn't apply to the fb_info structure
itself, but to use of the fbdev framebuffer.

Analysis of entry points after unregister_framebuffer():
- fbcon has been unbound (through notifier)
- sysfs files removed

First we look at possible operations on open fbdev file handles:

static const struct file_operations fb_fops = {
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
	.compat_ioctl = fb_compat_ioctl,
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
	.get_unmapped_area = get_fb_unmapped_area,
	.fsync =	fb_deferred_io_fsync,
};

Not possible after unregister:
fb_open() -> fb_ops.fb_open

Protected by file_fb_info() (-ENODEV):
fb_read() -> fb_ops.fb_read : drm_fb_helper_sys_read()
fb_write() -> fb_ops.fb_write : drm_fb_helper_sys_write()
fb_ioctl() -> fb_ops.fb_ioctl : drm_fb_helper_ioctl()
fb_compat_ioctl() -> fb_ops.fb_compat_ioctl
fb_mmap() -> fb_ops.fb_mmap

Safe:
fb_release() -> fb_ops.fb_release
get_fb_unmapped_area() : info->screen_base
fb_deferred_io_fsync() : if (info->fbdefio) &info->deferred_work

Active mmap's will need the backing buffer to be present.
If deferred IO is used, mmap writes will via a worker generate calls to
drm_fb_helper_deferred_io() which in turn via a worker calls into
drm_fb_helper_dirty_work().

Secondly we look at the remaining struct fb_ops operations:

Called by fbcon:
- fb_ops.fb_fillrect : drm_fb_helper_{sys,cfb}_fillrect()
- fb_ops.fb_copyarea : drm_fb_helper_{sys,cfb}_copyarea()
- fb_ops.fb_imageblit : drm_fb_helper_{sys,cfb}_imageblit()

Called in fb_set_var() which is called from ioctl, sysfs and fbcon:
- fb_ops.fb_check_var : drm_fb_helper_check_var()
- fb_ops.fb_set_par : drm_fb_helper_set_par()
drm_fb_helper_set_par() is also called from drm_fb_helper_hotplug_event().

Called in fb_set_cmap() which is called from fb_set_var(), ioctl
and fbcon:
- fb_ops.fb_setcolreg
- fb_ops.fb_setcmap : drm_fb_helper_setcmap()

Called in fb_blank() which is called from ioctl and fbcon:
- fb_ops.fb_blank : drm_fb_helper_blank()

Called in fb_pan_display() which is called from fb_set_var(), ioctl,
sysfs and fbcon:
- fb_ops.fb_pan_display : drm_fb_helper_pan_display()

Called by fbcon:
- fb_ops.fb_cursor

Called in fb_read(), fb_write(), and fb_get_buffer_offset() which is
called by fbcon:
- fb_ops.fb_sync

Called by fb_set_var():
- fb_ops.fb_get_caps

Called by fbcon:
- fb_ops.fb_debug_enter : drm_fb_helper_debug_enter()
- fb_ops.fb_debug_leave : drm_fb_helper_debug_leave()

Destroy is safe
- fb_ops.fb_destroy

Finally we look at other call paths:

drm_fb_helper_set_suspend{_unlocked}() and
drm_fb_helper_resume_worker():
Calls into fb_set_suspend(), but that's fine since it just uses the
fbdev notifier.

drm_fb_helper_restore_fbdev_mode_unlocked():
Called from drm_driver.last_close, possibly after drm_fb_helper_fini().

drm_fb_helper_force_kernel_mode():
Triggered by sysrq, possibly after unplug, but before
drm_fb_helper_fini().

drm_fb_helper_hotplug_event():
Called by drm_kms_helper_hotplug_event(). I don't know if this can be
called after drm_dev_unregister(), so add a check to be safe.

Based on this analysis the following functions get a
drm_dev_is_unplugged() check:
- drm_fb_helper_restore_fbdev_mode_unlocked()
- drm_fb_helper_force_kernel_mode()
- drm_fb_helper_hotplug_event()
- drm_fb_helper_dirty_work()

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_fb_helper.c | 204 +++++++++++++++++++++++++++++++++++++++-
 include/drm/drm_fb_helper.h     |  35 +++++++
 2 files changed, 237 insertions(+), 2 deletions(-)

Comments

Daniel Vetter Aug. 28, 2017, 9:41 p.m. UTC | #1
On Mon, Aug 28, 2017 at 07:17:44PM +0200, Noralf Trønnes wrote:
> Support device unplug by introducing a new initialization function:
> drm_fb_helper_simple_init() which together with
> drm_fb_helper_dev_unplug() and drm_fb_helper_fb_destroy() handles open
> fbdev fd's after device unplug. There's also a
> drm_fb_helper_simple_fini() for drivers who's device won't be removed.
> 
> It turns out that fbdev has the necessary ref counting, so
> unregister_framebuffer() together with fb_ops->destroy handles unplug
> with open fd's. The ref counting doesn't apply to the fb_info structure
> itself, but to use of the fbdev framebuffer.
> 
> Analysis of entry points after unregister_framebuffer():
> - fbcon has been unbound (through notifier)
> - sysfs files removed
> 
> First we look at possible operations on open fbdev file handles:
> 
> static const struct file_operations fb_fops = {
> 	.read =		fb_read,
> 	.write =	fb_write,
> 	.unlocked_ioctl = fb_ioctl,
> 	.compat_ioctl = fb_compat_ioctl,
> 	.mmap =		fb_mmap,
> 	.open =		fb_open,
> 	.release =	fb_release,
> 	.get_unmapped_area = get_fb_unmapped_area,
> 	.fsync =	fb_deferred_io_fsync,
> };
> 
> Not possible after unregister:
> fb_open() -> fb_ops.fb_open
> 
> Protected by file_fb_info() (-ENODEV):
> fb_read() -> fb_ops.fb_read : drm_fb_helper_sys_read()
> fb_write() -> fb_ops.fb_write : drm_fb_helper_sys_write()
> fb_ioctl() -> fb_ops.fb_ioctl : drm_fb_helper_ioctl()
> fb_compat_ioctl() -> fb_ops.fb_compat_ioctl
> fb_mmap() -> fb_ops.fb_mmap
> 
> Safe:
> fb_release() -> fb_ops.fb_release
> get_fb_unmapped_area() : info->screen_base
> fb_deferred_io_fsync() : if (info->fbdefio) &info->deferred_work
> 
> Active mmap's will need the backing buffer to be present.
> If deferred IO is used, mmap writes will via a worker generate calls to
> drm_fb_helper_deferred_io() which in turn via a worker calls into
> drm_fb_helper_dirty_work().
> 
> Secondly we look at the remaining struct fb_ops operations:
> 
> Called by fbcon:
> - fb_ops.fb_fillrect : drm_fb_helper_{sys,cfb}_fillrect()
> - fb_ops.fb_copyarea : drm_fb_helper_{sys,cfb}_copyarea()
> - fb_ops.fb_imageblit : drm_fb_helper_{sys,cfb}_imageblit()
> 
> Called in fb_set_var() which is called from ioctl, sysfs and fbcon:
> - fb_ops.fb_check_var : drm_fb_helper_check_var()
> - fb_ops.fb_set_par : drm_fb_helper_set_par()
> drm_fb_helper_set_par() is also called from drm_fb_helper_hotplug_event().
> 
> Called in fb_set_cmap() which is called from fb_set_var(), ioctl
> and fbcon:
> - fb_ops.fb_setcolreg
> - fb_ops.fb_setcmap : drm_fb_helper_setcmap()
> 
> Called in fb_blank() which is called from ioctl and fbcon:
> - fb_ops.fb_blank : drm_fb_helper_blank()
> 
> Called in fb_pan_display() which is called from fb_set_var(), ioctl,
> sysfs and fbcon:
> - fb_ops.fb_pan_display : drm_fb_helper_pan_display()
> 
> Called by fbcon:
> - fb_ops.fb_cursor
> 
> Called in fb_read(), fb_write(), and fb_get_buffer_offset() which is
> called by fbcon:
> - fb_ops.fb_sync
> 
> Called by fb_set_var():
> - fb_ops.fb_get_caps
> 
> Called by fbcon:
> - fb_ops.fb_debug_enter : drm_fb_helper_debug_enter()
> - fb_ops.fb_debug_leave : drm_fb_helper_debug_leave()
> 
> Destroy is safe
> - fb_ops.fb_destroy
> 
> Finally we look at other call paths:
> 
> drm_fb_helper_set_suspend{_unlocked}() and
> drm_fb_helper_resume_worker():
> Calls into fb_set_suspend(), but that's fine since it just uses the
> fbdev notifier.
> 
> drm_fb_helper_restore_fbdev_mode_unlocked():
> Called from drm_driver.last_close, possibly after drm_fb_helper_fini().
> 
> drm_fb_helper_force_kernel_mode():
> Triggered by sysrq, possibly after unplug, but before
> drm_fb_helper_fini().
> 
> drm_fb_helper_hotplug_event():
> Called by drm_kms_helper_hotplug_event(). I don't know if this can be
> called after drm_dev_unregister(), so add a check to be safe.
> 
> Based on this analysis the following functions get a
> drm_dev_is_unplugged() check:
> - drm_fb_helper_restore_fbdev_mode_unlocked()
> - drm_fb_helper_force_kernel_mode()
> - drm_fb_helper_hotplug_event()
> - drm_fb_helper_dirty_work()
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

You're mixing in the new simple fbdev helper helpers as a bonus, that's
two things in one patch and makes it harder to review what's really going
on.

My gut feeling is that when we need a helper for the helper the original
helper needs to be improved, but I think that's much easier to decide when
it's a separate patch. Splitting things out would definitely make this
patch much smaller and easier to understand and review.

The only reason for the simple helpers that I could find is the
drm_dev_ref/unref, we should be able to do that in one of the existing
callbacks.

> ---
>  drivers/gpu/drm/drm_fb_helper.c | 204 +++++++++++++++++++++++++++++++++++++++-
>  include/drm/drm_fb_helper.h     |  35 +++++++
>  2 files changed, 237 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index 2e33467..fc898db 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -105,6 +105,158 @@ static DEFINE_MUTEX(kernel_fb_helper_lock);
>   * mmap page writes.
>   */
>  
> +/**
> + * drm_fb_helper_simple_init - Simple fbdev emulation initialization
> + * @dev: drm device
> + * @fb_helper: driver-allocated fbdev helper structure to initialize
> + * @bpp_sel: bpp value to use for the framebuffer configuration
> + * @max_conn_count: max connector count
> + * @funcs: pointer to structure of functions associate with this helper
> + *
> + * Simple fbdev emulation initialization which calls the following functions:
> + * drm_fb_helper_prepare(), drm_fb_helper_init(),
> + * drm_fb_helper_single_add_all_connectors() and
> + * drm_fb_helper_initial_config().
> + *
> + * This function takes a ref on &drm_device and must be used together with
> + * drm_fb_helper_simple_fini() or drm_fb_helper_dev_unplug().
> + *
> + * fbdev deferred I/O users must use drm_fb_helper_defio_init().
> + *
> + * Returns:
> + * 0 on success or a negative error code on failure.
> + */
> +int drm_fb_helper_simple_init(struct drm_device *dev,
> +			      struct drm_fb_helper *fb_helper, int bpp_sel,
> +			      int max_conn_count,
> +			      const struct drm_fb_helper_funcs *funcs)
> +{
> +	int ret;
> +
> +	drm_fb_helper_prepare(dev, fb_helper, funcs);
> +
> +	ret = drm_fb_helper_init(dev, fb_helper, max_conn_count);
> +	if (ret < 0) {
> +		DRM_DEV_ERROR(dev->dev, "Failed to initialize fb helper.\n");
> +		return ret;
> +	}
> +
> +	drm_dev_ref(dev);
> +
> +	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
> +	if (ret < 0) {
> +		DRM_DEV_ERROR(dev->dev, "Failed to add connectors.\n");
> +		goto err_drm_fb_helper_fini;
> +
> +	}
> +
> +	ret = drm_fb_helper_initial_config(fb_helper, bpp_sel);
> +	if (ret < 0) {
> +		DRM_DEV_ERROR(dev->dev, "Failed to set initial hw config.\n");
> +		goto err_drm_fb_helper_fini;
> +	}
> +
> +	return 0;
> +
> +err_drm_fb_helper_fini:
> +	drm_fb_helper_fini(fb_helper);
> +	drm_dev_unref(dev);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(drm_fb_helper_simple_init);
> +
> +static void drm_fb_helper_simple_fini_cleanup(struct drm_fb_helper *fb_helper)
> +{
> +	struct fb_info *info = fb_helper->fbdev;
> +	struct fb_ops *fbops = NULL;
> +
> +	if (info && info->fbdefio) {
> +		fb_deferred_io_cleanup(info);
> +		kfree(info->fbdefio);
> +		info->fbdefio = NULL;
> +		fbops = info->fbops;
> +	}
> +
> +	drm_fb_helper_fini(fb_helper);
> +	kfree(fbops);
> +	if (fb_helper->fb)
> +		drm_framebuffer_remove(fb_helper->fb);
> +	drm_dev_unref(fb_helper->dev);
> +}
> +
> +/**
> + * drm_fb_helper_simple_fini - Simple fbdev cleanup
> + * @fb_helper: fbdev emulation structure, can be NULL
> + *
> + * Simple fbdev emulation cleanup. This function unregisters fbdev, cleans up
> + * deferred I/O if necessary, finalises @fb_helper and removes the framebuffer.
> + * The driver if responsible for freeing the @fb_helper structure.
> + *
> + * Don't use this function if you use drm_fb_helper_dev_unplug().
> + */
> +void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper)
> +{
> +	struct fb_info *info;
> +
> +	if (!fb_helper)
> +		return;
> +
> +	info = fb_helper->fbdev;
> +
> +	/* Has drm_fb_helper_dev_unplug() been used? */
> +	if (info && info->dev)
> +		drm_fb_helper_unregister_fbi(fb_helper);
> +
> +	if (!(info && info->fbops && info->fbops->fb_destroy))
> +		drm_fb_helper_simple_fini_cleanup(fb_helper);
> +}
> +EXPORT_SYMBOL_GPL(drm_fb_helper_simple_fini);
> +
> +/**
> + * drm_fb_helper_dev_unplug - unplug an fbdev device
> + * @fb_helper: driver-allocated fbdev helper, can be NULL
> + *
> + * This unplugs the fbdev emulation for a hotpluggable DRM device, which makes
> + * fbdev inaccessible to userspace operations. This essentially unregisters
> + * fbdev and can be called while there are still open users of @fb_helper.
> + * Entry-points from fbdev into drm core/helpers are protected by the fbdev
> + * &fb_info ref count and drm_dev_is_unplugged(). This means that the driver
> + * also has to call drm_dev_unplug() to complete the unplugging.
> + *
> + * Drivers must use drm_fb_helper_fb_destroy() as their &fb_ops.fb_destroy
> + * callback and call drm_mode_config_cleanup() and free @fb_helper in their
> + * &drm_driver->release callback.
> + *
> + * @fb_helper is finalized by this function unless there are open fbdev fd's
> + * in case this is postponed to the closing of the last fd. Finalizing includes
> + * dropping the ref taken on &drm_device in drm_fb_helper_simple_init().
> + */
> +void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper)
> +{
> +	drm_fb_helper_unregister_fbi(fb_helper);

I don't see the value of this wrapper. If you want to explain how to best
unplug the fbdev emulation I thinkt that's better done in some dedicated
DOC section (referenced by drm_dev_unplug() maybe), than by providing a
wrapper that only gives you a different name.

_unplug is also a bit misleading, since _unplug is about yanking the
backing device. This just unregisters the the fbdev interface.

> +}
> +EXPORT_SYMBOL(drm_fb_helper_dev_unplug);
> +
> +/**
> + * drm_fb_helper_fb_destroy - implementation for &fb_ops.fb_destroy
> + * @info: fbdev registered by the helper
> + *
> + * This function does the same as drm_fb_helper_simple_fini() except
> + * unregistering fbdev which is already done.
> + *
> + * &fb_ops.fb_destroy is called during unregister_framebuffer() or the last
> + * fb_release() which ever comes last.
> + */
> +void drm_fb_helper_fb_destroy(struct fb_info *info)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +
> +	DRM_DEBUG("\n");
> +	drm_fb_helper_simple_fini_cleanup(helper);
> +}
> +EXPORT_SYMBOL(drm_fb_helper_fb_destroy);
> +
>  #define drm_fb_helper_for_each_connector(fbh, i__) \
>  	for (({ lockdep_assert_held(&(fbh)->lock); }), \
>  	     i__ = 0; i__ < (fbh)->connector_count; i__++)
> @@ -498,7 +650,7 @@ int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
>  	bool do_delayed;
>  	int ret;
>  
> -	if (!drm_fbdev_emulation)
> +	if (!drm_fbdev_emulation || drm_dev_is_unplugged(fb_helper->dev))
>  		return -ENODEV;
>  
>  	if (READ_ONCE(fb_helper->deferred_setup))
> @@ -563,6 +715,9 @@ static bool drm_fb_helper_force_kernel_mode(void)
>  	list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
>  		struct drm_device *dev = helper->dev;
>  
> +		if (drm_dev_is_unplugged(dev))
> +			continue;
> +
>  		if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
>  			continue;
>  
> @@ -735,6 +890,9 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
>  	struct drm_clip_rect clip_copy;
>  	unsigned long flags;
>  
> +	if (drm_dev_is_unplugged(helper->dev))
> +		return;
> +
>  	spin_lock_irqsave(&helper->dirty_lock, flags);
>  	clip_copy = *clip;
>  	clip->x1 = clip->y1 = ~0;
> @@ -949,6 +1107,48 @@ void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  }
>  EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
>  
> +/**
> + * drm_fb_helper_defio_init - fbdev deferred I/O initialization
> + * @fb_helper: driver-allocated fbdev helper
> + *
> + * This function allocates &fb_deferred_io, sets callback to
> + * drm_fb_helper_deferred_io(), delay to 50ms and calls fb_deferred_io_init().
> + *
> + * NOTE: A copy of &fb_ops is made and assigned to &info->fbops. This is done
> + * because fb_deferred_io_cleanup() clears &fbops->fb_mmap and would thereby
> + * affect other instances of that &fb_ops. This copy is freed by the helper
> + * during cleanup.
> + *
> + * Returns:
> + * 0 on success or a negative error code on failure.
> + */
> +int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper)
> +{
> +	struct fb_info *info = fb_helper->fbdev;
> +	struct fb_deferred_io *fbdefio;
> +	struct fb_ops *fbops;
> +
> +	fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
> +	fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
> +	if (!fbdefio || !fbops) {
> +		kfree(fbdefio);
> +		kfree(fbops);
> +		return -ENOMEM;
> +	}
> +
> +	info->fbdefio = fbdefio;
> +	fbdefio->delay = msecs_to_jiffies(50);
> +	fbdefio->deferred_io = drm_fb_helper_deferred_io;
> +
> +	*fbops = *info->fbops;
> +	info->fbops = fbops;
> +
> +	fb_deferred_io_init(info);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_fb_helper_defio_init);
> +
>  static void drm_fb_helper_dirty(struct fb_info *info, u32 x, u32 y,
>  				u32 width, u32 height)
>  {
> @@ -2591,7 +2791,7 @@ int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
>  {
>  	int err = 0;
>  
> -	if (!drm_fbdev_emulation)
> +	if (!drm_fbdev_emulation || drm_dev_is_unplugged(fb_helper->dev))
>  		return 0;
>  
>  	mutex_lock(&fb_helper->lock);
> diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
> index 33fe959..1c5a648 100644
> --- a/include/drm/drm_fb_helper.h
> +++ b/include/drm/drm_fb_helper.h
> @@ -242,6 +242,14 @@ struct drm_fb_helper {
>  	.fb_ioctl	= drm_fb_helper_ioctl
>  
>  #ifdef CONFIG_DRM_FBDEV_EMULATION
> +int drm_fb_helper_simple_init(struct drm_device *dev,
> +			      struct drm_fb_helper *fb_helper, int bpp_sel,
> +			      int max_conn_count,
> +			      const struct drm_fb_helper_funcs *funcs);
> +void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper);
> +void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper);
> +void drm_fb_helper_fb_destroy(struct fb_info *info);
> +
>  void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
>  			   const struct drm_fb_helper_funcs *funcs);
>  int drm_fb_helper_init(struct drm_device *dev,
> @@ -265,6 +273,7 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
>  
>  void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper);
>  
> +int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper);
>  void drm_fb_helper_deferred_io(struct fb_info *info,
>  			       struct list_head *pagelist);
>  
> @@ -311,6 +320,27 @@ int drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, struct drm_
>  int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper,
>  				       struct drm_connector *connector);
>  #else
> +static inline int
> +drm_fb_helper_simple_init(struct drm_device *dev,
> +			  struct drm_fb_helper *fb_helper, int bpp_sel,
> +			  int max_conn_count,
> +			  const struct drm_fb_helper_funcs *funcs)
> +{
> +	return 0;
> +}
> +
> +static inline void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper)
> +{
> +}
> +
> +static inline void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper)
> +{
> +}
> +
> +static inline void drm_fb_helper_fb_destroy(struct fb_info *info)
> +{
> +}
> +
>  static inline void drm_fb_helper_prepare(struct drm_device *dev,
>  					struct drm_fb_helper *helper,
>  					const struct drm_fb_helper_funcs *funcs)
> @@ -393,6 +423,11 @@ static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  {
>  }
>  
> +static inline int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper)
> +{
> +	return 0;
> +}
> +
>  static inline void drm_fb_helper_deferred_io(struct fb_info *info,
>  					     struct list_head *pagelist)
>  {
> -- 
> 2.7.4
>
Noralf Trønnes Aug. 29, 2017, 4:17 p.m. UTC | #2
Den 28.08.2017 23.41, skrev Daniel Vetter:
> On Mon, Aug 28, 2017 at 07:17:44PM +0200, Noralf Trønnes wrote:
>> Support device unplug by introducing a new initialization function:
>> drm_fb_helper_simple_init() which together with
>> drm_fb_helper_dev_unplug() and drm_fb_helper_fb_destroy() handles open
>> fbdev fd's after device unplug. There's also a
>> drm_fb_helper_simple_fini() for drivers who's device won't be removed.
>>
>> It turns out that fbdev has the necessary ref counting, so
>> unregister_framebuffer() together with fb_ops->destroy handles unplug
>> with open fd's. The ref counting doesn't apply to the fb_info structure
>> itself, but to use of the fbdev framebuffer.
>>
>> Analysis of entry points after unregister_framebuffer():
>> - fbcon has been unbound (through notifier)
>> - sysfs files removed
>>
>> First we look at possible operations on open fbdev file handles:
>>
>> static const struct file_operations fb_fops = {
>> 	.read =		fb_read,
>> 	.write =	fb_write,
>> 	.unlocked_ioctl = fb_ioctl,
>> 	.compat_ioctl = fb_compat_ioctl,
>> 	.mmap =		fb_mmap,
>> 	.open =		fb_open,
>> 	.release =	fb_release,
>> 	.get_unmapped_area = get_fb_unmapped_area,
>> 	.fsync =	fb_deferred_io_fsync,
>> };
>>
>> Not possible after unregister:
>> fb_open() -> fb_ops.fb_open
>>
>> Protected by file_fb_info() (-ENODEV):
>> fb_read() -> fb_ops.fb_read : drm_fb_helper_sys_read()
>> fb_write() -> fb_ops.fb_write : drm_fb_helper_sys_write()
>> fb_ioctl() -> fb_ops.fb_ioctl : drm_fb_helper_ioctl()
>> fb_compat_ioctl() -> fb_ops.fb_compat_ioctl
>> fb_mmap() -> fb_ops.fb_mmap
>>
>> Safe:
>> fb_release() -> fb_ops.fb_release
>> get_fb_unmapped_area() : info->screen_base
>> fb_deferred_io_fsync() : if (info->fbdefio) &info->deferred_work
>>
>> Active mmap's will need the backing buffer to be present.
>> If deferred IO is used, mmap writes will via a worker generate calls to
>> drm_fb_helper_deferred_io() which in turn via a worker calls into
>> drm_fb_helper_dirty_work().
>>
>> Secondly we look at the remaining struct fb_ops operations:
>>
>> Called by fbcon:
>> - fb_ops.fb_fillrect : drm_fb_helper_{sys,cfb}_fillrect()
>> - fb_ops.fb_copyarea : drm_fb_helper_{sys,cfb}_copyarea()
>> - fb_ops.fb_imageblit : drm_fb_helper_{sys,cfb}_imageblit()
>>
>> Called in fb_set_var() which is called from ioctl, sysfs and fbcon:
>> - fb_ops.fb_check_var : drm_fb_helper_check_var()
>> - fb_ops.fb_set_par : drm_fb_helper_set_par()
>> drm_fb_helper_set_par() is also called from drm_fb_helper_hotplug_event().
>>
>> Called in fb_set_cmap() which is called from fb_set_var(), ioctl
>> and fbcon:
>> - fb_ops.fb_setcolreg
>> - fb_ops.fb_setcmap : drm_fb_helper_setcmap()
>>
>> Called in fb_blank() which is called from ioctl and fbcon:
>> - fb_ops.fb_blank : drm_fb_helper_blank()
>>
>> Called in fb_pan_display() which is called from fb_set_var(), ioctl,
>> sysfs and fbcon:
>> - fb_ops.fb_pan_display : drm_fb_helper_pan_display()
>>
>> Called by fbcon:
>> - fb_ops.fb_cursor
>>
>> Called in fb_read(), fb_write(), and fb_get_buffer_offset() which is
>> called by fbcon:
>> - fb_ops.fb_sync
>>
>> Called by fb_set_var():
>> - fb_ops.fb_get_caps
>>
>> Called by fbcon:
>> - fb_ops.fb_debug_enter : drm_fb_helper_debug_enter()
>> - fb_ops.fb_debug_leave : drm_fb_helper_debug_leave()
>>
>> Destroy is safe
>> - fb_ops.fb_destroy
>>
>> Finally we look at other call paths:
>>
>> drm_fb_helper_set_suspend{_unlocked}() and
>> drm_fb_helper_resume_worker():
>> Calls into fb_set_suspend(), but that's fine since it just uses the
>> fbdev notifier.
>>
>> drm_fb_helper_restore_fbdev_mode_unlocked():
>> Called from drm_driver.last_close, possibly after drm_fb_helper_fini().
>>
>> drm_fb_helper_force_kernel_mode():
>> Triggered by sysrq, possibly after unplug, but before
>> drm_fb_helper_fini().
>>
>> drm_fb_helper_hotplug_event():
>> Called by drm_kms_helper_hotplug_event(). I don't know if this can be
>> called after drm_dev_unregister(), so add a check to be safe.
>>
>> Based on this analysis the following functions get a
>> drm_dev_is_unplugged() check:
>> - drm_fb_helper_restore_fbdev_mode_unlocked()
>> - drm_fb_helper_force_kernel_mode()
>> - drm_fb_helper_hotplug_event()
>> - drm_fb_helper_dirty_work()
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> You're mixing in the new simple fbdev helper helpers as a bonus, that's
> two things in one patch and makes it harder to review what's really going
> on.
>
> My gut feeling is that when we need a helper for the helper the original
> helper needs to be improved, but I think that's much easier to decide when
> it's a separate patch. Splitting things out would definitely make this
> patch much smaller and easier to understand and review.
>
> The only reason for the simple helpers that I could find is the
> drm_dev_ref/unref, we should be able to do that in one of the existing
> callbacks.

The reason I did that is because I couldn't put it in the existing
helpers since the framebuffer is removed after drm_fb_helper_fini() and
I can't drop the ref on drm_device before the framebuffer is gone.

I can split the patch in two:
1. Add drm_dev_is_unplugged() checks
2. Add drm_fb_helper_simple_init/fini() and drm_fb_helper_fb_destroy()

Maybe there's a way to remove the framebuffer in drm_fb_helper_fini()
so we can drop the ref there, I don't know.

Here's how drivers destroy fbdev:


static int amdgpu_fbdev_destroy(struct drm_device *dev, struct 
amdgpu_fbdev *rfbdev)
{
     struct amdgpu_framebuffer *rfb = &rfbdev->rfb;

     drm_fb_helper_unregister_fbi(&rfbdev->helper);

     if (rfb->obj) {
         amdgpufb_destroy_pinned_object(rfb->obj);
         rfb->obj = NULL;
     }
     drm_fb_helper_fini(&rfbdev->helper);
     drm_framebuffer_unregister_private(&rfb->base);
     drm_framebuffer_cleanup(&rfb->base);

     return 0;
}

void armada_fbdev_fini(struct drm_device *dev)
{
     struct armada_private *priv = dev->dev_private;
     struct drm_fb_helper *fbh = priv->fbdev;

     if (fbh) {
         drm_fb_helper_unregister_fbi(fbh);

         drm_fb_helper_fini(fbh);

         if (fbh->fb)
             fbh->fb->funcs->destroy(fbh->fb);

         priv->fbdev = NULL;
     }
}

static void ast_fbdev_destroy(struct drm_device *dev,
                   struct ast_fbdev *afbdev)
{
     struct ast_framebuffer *afb = &afbdev->afb;

     drm_fb_helper_unregister_fbi(&afbdev->helper);

     if (afb->obj) {
         drm_gem_object_unreference_unlocked(afb->obj);
         afb->obj = NULL;
     }
     drm_fb_helper_fini(&afbdev->helper);

     vfree(afbdev->sysram);
     drm_framebuffer_unregister_private(&afb->base);
     drm_framebuffer_cleanup(&afb->base);
}

static int bochs_fbdev_destroy(struct bochs_device *bochs)
{
     struct bochs_framebuffer *gfb = &bochs->fb.gfb;

     DRM_DEBUG_DRIVER("\n");

     drm_fb_helper_unregister_fbi(&bochs->fb.helper);

     if (gfb->obj) {
         drm_gem_object_unreference_unlocked(gfb->obj);
         gfb->obj = NULL;
     }

     drm_framebuffer_unregister_private(&gfb->base);
     drm_framebuffer_cleanup(&gfb->base);

     return 0;
}

static int cirrus_fbdev_destroy(struct drm_device *dev,
                 struct cirrus_fbdev *gfbdev)
{
     struct cirrus_framebuffer *gfb = &gfbdev->gfb;

     drm_fb_helper_unregister_fbi(&gfbdev->helper);

     if (gfb->obj) {
         drm_gem_object_unreference_unlocked(gfb->obj);
         gfb->obj = NULL;
     }

     vfree(gfbdev->sysram);
     drm_fb_helper_fini(&gfbdev->helper);
     drm_framebuffer_unregister_private(&gfb->base);
     drm_framebuffer_cleanup(&gfb->base);

     return 0;
}

void drm_fbdev_cma_fini(struct drm_fbdev_cma *fbdev_cma)
{
     drm_fb_helper_unregister_fbi(&fbdev_cma->fb_helper);
     if (fbdev_cma->fb_helper.fbdev)
         drm_fbdev_cma_defio_fini(fbdev_cma->fb_helper.fbdev);

     if (fbdev_cma->fb)
         drm_framebuffer_remove(&fbdev_cma->fb->fb);

     drm_fb_helper_fini(&fbdev_cma->fb_helper);
     kfree(fbdev_cma);
}

static void exynos_drm_fbdev_destroy(struct drm_device *dev,
                       struct drm_fb_helper *fb_helper)
{
     struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(fb_helper);
     struct exynos_drm_gem *exynos_gem = exynos_fbd->exynos_gem;
     struct drm_framebuffer *fb;

     vunmap(exynos_gem->kvaddr);

     /* release drm framebuffer and real buffer */
     if (fb_helper->fb && fb_helper->fb->funcs) {
         fb = fb_helper->fb;
         if (fb)
             drm_framebuffer_remove(fb);
     }

     drm_fb_helper_unregister_fbi(fb_helper);

     drm_fb_helper_fini(fb_helper);
}

static int psb_fbdev_destroy(struct drm_device *dev, struct psb_fbdev 
*fbdev)
{
     struct psb_framebuffer *psbfb = &fbdev->pfb;

     drm_fb_helper_unregister_fbi(&fbdev->psb_fb_helper);

     drm_fb_helper_fini(&fbdev->psb_fb_helper);
     drm_framebuffer_unregister_private(&psbfb->base);
     drm_framebuffer_cleanup(&psbfb->base);

     if (psbfb->gtt)
drm_gem_object_unreference_unlocked(&psbfb->gtt->gem);
     return 0;
}

static void hibmc_fbdev_destroy(struct hibmc_fbdev *fbdev)
{
     struct hibmc_framebuffer *gfb = fbdev->fb;
     struct drm_fb_helper *fbh = &fbdev->helper;

     drm_fb_helper_unregister_fbi(fbh);

     drm_fb_helper_fini(fbh);

     if (gfb)
         drm_framebuffer_unreference(&gfb->fb);
}

static void intel_fbdev_destroy(struct intel_fbdev *ifbdev)
{
     /* We rely on the object-free to release the VMA pinning for
      * the info->screen_base mmaping. Leaking the VMA is simpler than
      * trying to rectify all the possible error paths leading here.
      */

     drm_fb_helper_unregister_fbi(&ifbdev->helper);

     drm_fb_helper_fini(&ifbdev->helper);

     if (ifbdev->vma) {
         mutex_lock(&ifbdev->helper.dev->struct_mutex);
         intel_unpin_fb_vma(ifbdev->vma);
         mutex_unlock(&ifbdev->helper.dev->struct_mutex);
     }

     if (ifbdev->fb)
         drm_framebuffer_remove(&ifbdev->fb->base);

     kfree(ifbdev);
}

static int mga_fbdev_destroy(struct drm_device *dev,
                 struct mga_fbdev *mfbdev)
{
     struct mga_framebuffer *mfb = &mfbdev->mfb;

     drm_fb_helper_unregister_fbi(&mfbdev->helper);

     if (mfb->obj) {
         drm_gem_object_unreference_unlocked(mfb->obj);
         mfb->obj = NULL;
     }
     drm_fb_helper_fini(&mfbdev->helper);
     vfree(mfbdev->sysram);
     drm_framebuffer_unregister_private(&mfb->base);
     drm_framebuffer_cleanup(&mfb->base);

     return 0;
}

void msm_fbdev_free(struct drm_device *dev)
{
     struct msm_drm_private *priv = dev->dev_private;
     struct drm_fb_helper *helper = priv->fbdev;
     struct msm_fbdev *fbdev;

     DBG();

     drm_fb_helper_unregister_fbi(helper);

     drm_fb_helper_fini(helper);

     fbdev = to_msm_fbdev(priv->fbdev);

     /* this will free the backing object */
     if (fbdev->fb) {
         msm_gem_put_vaddr(fbdev->bo);
         drm_framebuffer_remove(fbdev->fb);
     }

     kfree(fbdev);

     priv->fbdev = NULL;
}

static int
nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
{
     struct nouveau_framebuffer *nouveau_fb = 
nouveau_framebuffer(fbcon->helper.fb);

     drm_fb_helper_unregister_fbi(&fbcon->helper);
     drm_fb_helper_fini(&fbcon->helper);

     if (nouveau_fb->nvbo) {
         nouveau_bo_vma_del(nouveau_fb->nvbo, &nouveau_fb->vma);
         nouveau_bo_unmap(nouveau_fb->nvbo);
         nouveau_bo_unpin(nouveau_fb->nvbo);
         drm_framebuffer_unreference(&nouveau_fb->base);
     }

     return 0;
}

void omap_fbdev_free(struct drm_device *dev)
{
     struct omap_drm_private *priv = dev->dev_private;
     struct drm_fb_helper *helper = priv->fbdev;
     struct omap_fbdev *fbdev;

     DBG();

     drm_fb_helper_unregister_fbi(helper);

     drm_fb_helper_fini(helper);

     fbdev = to_omap_fbdev(priv->fbdev);

     /* release the ref taken in omap_fbdev_create() */
     omap_gem_put_paddr(fbdev->bo);

     /* this will free the backing object */
     if (fbdev->fb)
         drm_framebuffer_remove(fbdev->fb);

     kfree(fbdev);

     priv->fbdev = NULL;
}

static int qxl_fbdev_destroy(struct drm_device *dev, struct qxl_fbdev 
*qfbdev)
{
     struct qxl_framebuffer *qfb = &qfbdev->qfb;

     drm_fb_helper_unregister_fbi(&qfbdev->helper);

     if (qfb->obj) {
         qxlfb_destroy_pinned_object(qfb->obj);
         qfb->obj = NULL;
     }
     drm_fb_helper_fini(&qfbdev->helper);
     vfree(qfbdev->shadow);
     drm_framebuffer_cleanup(&qfb->base);

     return 0;
}

static int radeon_fbdev_destroy(struct drm_device *dev, struct 
radeon_fbdev *rfbdev)
{
     struct radeon_framebuffer *rfb = &rfbdev->rfb;

     drm_fb_helper_unregister_fbi(&rfbdev->helper);

     if (rfb->obj) {
         radeonfb_destroy_pinned_object(rfb->obj);
         rfb->obj = NULL;
     }
     drm_fb_helper_fini(&rfbdev->helper);
     drm_framebuffer_unregister_private(&rfb->base);
     drm_framebuffer_cleanup(&rfb->base);

     return 0;
}

void rockchip_drm_fbdev_fini(struct drm_device *dev)
{
     struct rockchip_drm_private *private = dev->dev_private;
     struct drm_fb_helper *helper;

     helper = &private->fbdev_helper;

     drm_fb_helper_unregister_fbi(helper);

     if (helper->fb)
         drm_framebuffer_unreference(helper->fb);

     drm_fb_helper_fini(helper);
}

static void tegra_fbdev_exit(struct tegra_fbdev *fbdev)
{
     drm_fb_helper_unregister_fbi(&fbdev->base);

     if (fbdev->fb)
         drm_framebuffer_remove(&fbdev->fb->base);

     drm_fb_helper_fini(&fbdev->base);
     tegra_fbdev_free(fbdev);
}

static void udl_fbdev_destroy(struct drm_device *dev,
                   struct udl_fbdev *ufbdev)
{
     drm_fb_helper_unregister_fbi(&ufbdev->helper);
     drm_fb_helper_fini(&ufbdev->helper);
     drm_framebuffer_unregister_private(&ufbdev->ufb.base);
     drm_framebuffer_cleanup(&ufbdev->ufb.base);
drm_gem_object_unreference_unlocked(&ufbdev->ufb.obj->base);
}

static int virtio_gpu_fbdev_destroy(struct drm_device *dev,
                     struct virtio_gpu_fbdev *vgfbdev)
{
     struct virtio_gpu_framebuffer *vgfb = &vgfbdev->vgfb;

     drm_fb_helper_unregister_fbi(&vgfbdev->helper);

     if (vgfb->obj)
         vgfb->obj = NULL;
     drm_fb_helper_fini(&vgfbdev->helper);
     drm_framebuffer_cleanup(&vgfb->base);

     return 0;
}


Noralf.

>> ---
>>   drivers/gpu/drm/drm_fb_helper.c | 204 +++++++++++++++++++++++++++++++++++++++-
>>   include/drm/drm_fb_helper.h     |  35 +++++++
>>   2 files changed, 237 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
>> index 2e33467..fc898db 100644
>> --- a/drivers/gpu/drm/drm_fb_helper.c
>> +++ b/drivers/gpu/drm/drm_fb_helper.c
>> @@ -105,6 +105,158 @@ static DEFINE_MUTEX(kernel_fb_helper_lock);
>>    * mmap page writes.
>>    */
>>   
>> +/**
>> + * drm_fb_helper_simple_init - Simple fbdev emulation initialization
>> + * @dev: drm device
>> + * @fb_helper: driver-allocated fbdev helper structure to initialize
>> + * @bpp_sel: bpp value to use for the framebuffer configuration
>> + * @max_conn_count: max connector count
>> + * @funcs: pointer to structure of functions associate with this helper
>> + *
>> + * Simple fbdev emulation initialization which calls the following functions:
>> + * drm_fb_helper_prepare(), drm_fb_helper_init(),
>> + * drm_fb_helper_single_add_all_connectors() and
>> + * drm_fb_helper_initial_config().
>> + *
>> + * This function takes a ref on &drm_device and must be used together with
>> + * drm_fb_helper_simple_fini() or drm_fb_helper_dev_unplug().
>> + *
>> + * fbdev deferred I/O users must use drm_fb_helper_defio_init().
>> + *
>> + * Returns:
>> + * 0 on success or a negative error code on failure.
>> + */
>> +int drm_fb_helper_simple_init(struct drm_device *dev,
>> +			      struct drm_fb_helper *fb_helper, int bpp_sel,
>> +			      int max_conn_count,
>> +			      const struct drm_fb_helper_funcs *funcs)
>> +{
>> +	int ret;
>> +
>> +	drm_fb_helper_prepare(dev, fb_helper, funcs);
>> +
>> +	ret = drm_fb_helper_init(dev, fb_helper, max_conn_count);
>> +	if (ret < 0) {
>> +		DRM_DEV_ERROR(dev->dev, "Failed to initialize fb helper.\n");
>> +		return ret;
>> +	}
>> +
>> +	drm_dev_ref(dev);
>> +
>> +	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
>> +	if (ret < 0) {
>> +		DRM_DEV_ERROR(dev->dev, "Failed to add connectors.\n");
>> +		goto err_drm_fb_helper_fini;
>> +
>> +	}
>> +
>> +	ret = drm_fb_helper_initial_config(fb_helper, bpp_sel);
>> +	if (ret < 0) {
>> +		DRM_DEV_ERROR(dev->dev, "Failed to set initial hw config.\n");
>> +		goto err_drm_fb_helper_fini;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_drm_fb_helper_fini:
>> +	drm_fb_helper_fini(fb_helper);
>> +	drm_dev_unref(dev);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(drm_fb_helper_simple_init);
>> +
>> +static void drm_fb_helper_simple_fini_cleanup(struct drm_fb_helper *fb_helper)
>> +{
>> +	struct fb_info *info = fb_helper->fbdev;
>> +	struct fb_ops *fbops = NULL;
>> +
>> +	if (info && info->fbdefio) {
>> +		fb_deferred_io_cleanup(info);
>> +		kfree(info->fbdefio);
>> +		info->fbdefio = NULL;
>> +		fbops = info->fbops;
>> +	}
>> +
>> +	drm_fb_helper_fini(fb_helper);
>> +	kfree(fbops);
>> +	if (fb_helper->fb)
>> +		drm_framebuffer_remove(fb_helper->fb);
>> +	drm_dev_unref(fb_helper->dev);
>> +}
>> +
>> +/**
>> + * drm_fb_helper_simple_fini - Simple fbdev cleanup
>> + * @fb_helper: fbdev emulation structure, can be NULL
>> + *
>> + * Simple fbdev emulation cleanup. This function unregisters fbdev, cleans up
>> + * deferred I/O if necessary, finalises @fb_helper and removes the framebuffer.
>> + * The driver if responsible for freeing the @fb_helper structure.
>> + *
>> + * Don't use this function if you use drm_fb_helper_dev_unplug().
>> + */
>> +void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper)
>> +{
>> +	struct fb_info *info;
>> +
>> +	if (!fb_helper)
>> +		return;
>> +
>> +	info = fb_helper->fbdev;
>> +
>> +	/* Has drm_fb_helper_dev_unplug() been used? */
>> +	if (info && info->dev)
>> +		drm_fb_helper_unregister_fbi(fb_helper);
>> +
>> +	if (!(info && info->fbops && info->fbops->fb_destroy))
>> +		drm_fb_helper_simple_fini_cleanup(fb_helper);
>> +}
>> +EXPORT_SYMBOL_GPL(drm_fb_helper_simple_fini);
>> +
>> +/**
>> + * drm_fb_helper_dev_unplug - unplug an fbdev device
>> + * @fb_helper: driver-allocated fbdev helper, can be NULL
>> + *
>> + * This unplugs the fbdev emulation for a hotpluggable DRM device, which makes
>> + * fbdev inaccessible to userspace operations. This essentially unregisters
>> + * fbdev and can be called while there are still open users of @fb_helper.
>> + * Entry-points from fbdev into drm core/helpers are protected by the fbdev
>> + * &fb_info ref count and drm_dev_is_unplugged(). This means that the driver
>> + * also has to call drm_dev_unplug() to complete the unplugging.
>> + *
>> + * Drivers must use drm_fb_helper_fb_destroy() as their &fb_ops.fb_destroy
>> + * callback and call drm_mode_config_cleanup() and free @fb_helper in their
>> + * &drm_driver->release callback.
>> + *
>> + * @fb_helper is finalized by this function unless there are open fbdev fd's
>> + * in case this is postponed to the closing of the last fd. Finalizing includes
>> + * dropping the ref taken on &drm_device in drm_fb_helper_simple_init().
>> + */
>> +void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper)
>> +{
>> +	drm_fb_helper_unregister_fbi(fb_helper);
> I don't see the value of this wrapper. If you want to explain how to best
> unplug the fbdev emulation I thinkt that's better done in some dedicated
> DOC section (referenced by drm_dev_unplug() maybe), than by providing a
> wrapper that only gives you a different name.
>
> _unplug is also a bit misleading, since _unplug is about yanking the
> backing device. This just unregisters the the fbdev interface.
>
>> +}
>> +EXPORT_SYMBOL(drm_fb_helper_dev_unplug);
>> +
>> +/**
>> + * drm_fb_helper_fb_destroy - implementation for &fb_ops.fb_destroy
>> + * @info: fbdev registered by the helper
>> + *
>> + * This function does the same as drm_fb_helper_simple_fini() except
>> + * unregistering fbdev which is already done.
>> + *
>> + * &fb_ops.fb_destroy is called during unregister_framebuffer() or the last
>> + * fb_release() which ever comes last.
>> + */
>> +void drm_fb_helper_fb_destroy(struct fb_info *info)
>> +{
>> +	struct drm_fb_helper *helper = info->par;
>> +
>> +	DRM_DEBUG("\n");
>> +	drm_fb_helper_simple_fini_cleanup(helper);
>> +}
>> +EXPORT_SYMBOL(drm_fb_helper_fb_destroy);
>> +
>>   #define drm_fb_helper_for_each_connector(fbh, i__) \
>>   	for (({ lockdep_assert_held(&(fbh)->lock); }), \
>>   	     i__ = 0; i__ < (fbh)->connector_count; i__++)
>> @@ -498,7 +650,7 @@ int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
>>   	bool do_delayed;
>>   	int ret;
>>   
>> -	if (!drm_fbdev_emulation)
>> +	if (!drm_fbdev_emulation || drm_dev_is_unplugged(fb_helper->dev))
>>   		return -ENODEV;
>>   
>>   	if (READ_ONCE(fb_helper->deferred_setup))
>> @@ -563,6 +715,9 @@ static bool drm_fb_helper_force_kernel_mode(void)
>>   	list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
>>   		struct drm_device *dev = helper->dev;
>>   
>> +		if (drm_dev_is_unplugged(dev))
>> +			continue;
>> +
>>   		if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
>>   			continue;
>>   
>> @@ -735,6 +890,9 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
>>   	struct drm_clip_rect clip_copy;
>>   	unsigned long flags;
>>   
>> +	if (drm_dev_is_unplugged(helper->dev))
>> +		return;
>> +
>>   	spin_lock_irqsave(&helper->dirty_lock, flags);
>>   	clip_copy = *clip;
>>   	clip->x1 = clip->y1 = ~0;
>> @@ -949,6 +1107,48 @@ void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>>   }
>>   EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
>>   
>> +/**
>> + * drm_fb_helper_defio_init - fbdev deferred I/O initialization
>> + * @fb_helper: driver-allocated fbdev helper
>> + *
>> + * This function allocates &fb_deferred_io, sets callback to
>> + * drm_fb_helper_deferred_io(), delay to 50ms and calls fb_deferred_io_init().
>> + *
>> + * NOTE: A copy of &fb_ops is made and assigned to &info->fbops. This is done
>> + * because fb_deferred_io_cleanup() clears &fbops->fb_mmap and would thereby
>> + * affect other instances of that &fb_ops. This copy is freed by the helper
>> + * during cleanup.
>> + *
>> + * Returns:
>> + * 0 on success or a negative error code on failure.
>> + */
>> +int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper)
>> +{
>> +	struct fb_info *info = fb_helper->fbdev;
>> +	struct fb_deferred_io *fbdefio;
>> +	struct fb_ops *fbops;
>> +
>> +	fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
>> +	fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
>> +	if (!fbdefio || !fbops) {
>> +		kfree(fbdefio);
>> +		kfree(fbops);
>> +		return -ENOMEM;
>> +	}
>> +
>> +	info->fbdefio = fbdefio;
>> +	fbdefio->delay = msecs_to_jiffies(50);
>> +	fbdefio->deferred_io = drm_fb_helper_deferred_io;
>> +
>> +	*fbops = *info->fbops;
>> +	info->fbops = fbops;
>> +
>> +	fb_deferred_io_init(info);
>> +
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL(drm_fb_helper_defio_init);
>> +
>>   static void drm_fb_helper_dirty(struct fb_info *info, u32 x, u32 y,
>>   				u32 width, u32 height)
>>   {
>> @@ -2591,7 +2791,7 @@ int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
>>   {
>>   	int err = 0;
>>   
>> -	if (!drm_fbdev_emulation)
>> +	if (!drm_fbdev_emulation || drm_dev_is_unplugged(fb_helper->dev))
>>   		return 0;
>>   
>>   	mutex_lock(&fb_helper->lock);
>> diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
>> index 33fe959..1c5a648 100644
>> --- a/include/drm/drm_fb_helper.h
>> +++ b/include/drm/drm_fb_helper.h
>> @@ -242,6 +242,14 @@ struct drm_fb_helper {
>>   	.fb_ioctl	= drm_fb_helper_ioctl
>>   
>>   #ifdef CONFIG_DRM_FBDEV_EMULATION
>> +int drm_fb_helper_simple_init(struct drm_device *dev,
>> +			      struct drm_fb_helper *fb_helper, int bpp_sel,
>> +			      int max_conn_count,
>> +			      const struct drm_fb_helper_funcs *funcs);
>> +void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper);
>> +void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper);
>> +void drm_fb_helper_fb_destroy(struct fb_info *info);
>> +
>>   void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
>>   			   const struct drm_fb_helper_funcs *funcs);
>>   int drm_fb_helper_init(struct drm_device *dev,
>> @@ -265,6 +273,7 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
>>   
>>   void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper);
>>   
>> +int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper);
>>   void drm_fb_helper_deferred_io(struct fb_info *info,
>>   			       struct list_head *pagelist);
>>   
>> @@ -311,6 +320,27 @@ int drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, struct drm_
>>   int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper,
>>   				       struct drm_connector *connector);
>>   #else
>> +static inline int
>> +drm_fb_helper_simple_init(struct drm_device *dev,
>> +			  struct drm_fb_helper *fb_helper, int bpp_sel,
>> +			  int max_conn_count,
>> +			  const struct drm_fb_helper_funcs *funcs)
>> +{
>> +	return 0;
>> +}
>> +
>> +static inline void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper)
>> +{
>> +}
>> +
>> +static inline void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper)
>> +{
>> +}
>> +
>> +static inline void drm_fb_helper_fb_destroy(struct fb_info *info)
>> +{
>> +}
>> +
>>   static inline void drm_fb_helper_prepare(struct drm_device *dev,
>>   					struct drm_fb_helper *helper,
>>   					const struct drm_fb_helper_funcs *funcs)
>> @@ -393,6 +423,11 @@ static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>>   {
>>   }
>>   
>> +static inline int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper)
>> +{
>> +	return 0;
>> +}
>> +
>>   static inline void drm_fb_helper_deferred_io(struct fb_info *info,
>>   					     struct list_head *pagelist)
>>   {
>> -- 
>> 2.7.4
>>
Daniel Vetter Aug. 30, 2017, 7:29 a.m. UTC | #3
On Tue, Aug 29, 2017 at 06:17:25PM +0200, Noralf Trønnes wrote:
> 
> Den 28.08.2017 23.41, skrev Daniel Vetter:
> > On Mon, Aug 28, 2017 at 07:17:44PM +0200, Noralf Trønnes wrote:
> > > Support device unplug by introducing a new initialization function:
> > > drm_fb_helper_simple_init() which together with
> > > drm_fb_helper_dev_unplug() and drm_fb_helper_fb_destroy() handles open
> > > fbdev fd's after device unplug. There's also a
> > > drm_fb_helper_simple_fini() for drivers who's device won't be removed.
> > > 
> > > It turns out that fbdev has the necessary ref counting, so
> > > unregister_framebuffer() together with fb_ops->destroy handles unplug
> > > with open fd's. The ref counting doesn't apply to the fb_info structure
> > > itself, but to use of the fbdev framebuffer.
> > > 
> > > Analysis of entry points after unregister_framebuffer():
> > > - fbcon has been unbound (through notifier)
> > > - sysfs files removed
> > > 
> > > First we look at possible operations on open fbdev file handles:
> > > 
> > > static const struct file_operations fb_fops = {
> > > 	.read =		fb_read,
> > > 	.write =	fb_write,
> > > 	.unlocked_ioctl = fb_ioctl,
> > > 	.compat_ioctl = fb_compat_ioctl,
> > > 	.mmap =		fb_mmap,
> > > 	.open =		fb_open,
> > > 	.release =	fb_release,
> > > 	.get_unmapped_area = get_fb_unmapped_area,
> > > 	.fsync =	fb_deferred_io_fsync,
> > > };
> > > 
> > > Not possible after unregister:
> > > fb_open() -> fb_ops.fb_open
> > > 
> > > Protected by file_fb_info() (-ENODEV):
> > > fb_read() -> fb_ops.fb_read : drm_fb_helper_sys_read()
> > > fb_write() -> fb_ops.fb_write : drm_fb_helper_sys_write()
> > > fb_ioctl() -> fb_ops.fb_ioctl : drm_fb_helper_ioctl()
> > > fb_compat_ioctl() -> fb_ops.fb_compat_ioctl
> > > fb_mmap() -> fb_ops.fb_mmap
> > > 
> > > Safe:
> > > fb_release() -> fb_ops.fb_release
> > > get_fb_unmapped_area() : info->screen_base
> > > fb_deferred_io_fsync() : if (info->fbdefio) &info->deferred_work
> > > 
> > > Active mmap's will need the backing buffer to be present.
> > > If deferred IO is used, mmap writes will via a worker generate calls to
> > > drm_fb_helper_deferred_io() which in turn via a worker calls into
> > > drm_fb_helper_dirty_work().
> > > 
> > > Secondly we look at the remaining struct fb_ops operations:
> > > 
> > > Called by fbcon:
> > > - fb_ops.fb_fillrect : drm_fb_helper_{sys,cfb}_fillrect()
> > > - fb_ops.fb_copyarea : drm_fb_helper_{sys,cfb}_copyarea()
> > > - fb_ops.fb_imageblit : drm_fb_helper_{sys,cfb}_imageblit()
> > > 
> > > Called in fb_set_var() which is called from ioctl, sysfs and fbcon:
> > > - fb_ops.fb_check_var : drm_fb_helper_check_var()
> > > - fb_ops.fb_set_par : drm_fb_helper_set_par()
> > > drm_fb_helper_set_par() is also called from drm_fb_helper_hotplug_event().
> > > 
> > > Called in fb_set_cmap() which is called from fb_set_var(), ioctl
> > > and fbcon:
> > > - fb_ops.fb_setcolreg
> > > - fb_ops.fb_setcmap : drm_fb_helper_setcmap()
> > > 
> > > Called in fb_blank() which is called from ioctl and fbcon:
> > > - fb_ops.fb_blank : drm_fb_helper_blank()
> > > 
> > > Called in fb_pan_display() which is called from fb_set_var(), ioctl,
> > > sysfs and fbcon:
> > > - fb_ops.fb_pan_display : drm_fb_helper_pan_display()
> > > 
> > > Called by fbcon:
> > > - fb_ops.fb_cursor
> > > 
> > > Called in fb_read(), fb_write(), and fb_get_buffer_offset() which is
> > > called by fbcon:
> > > - fb_ops.fb_sync
> > > 
> > > Called by fb_set_var():
> > > - fb_ops.fb_get_caps
> > > 
> > > Called by fbcon:
> > > - fb_ops.fb_debug_enter : drm_fb_helper_debug_enter()
> > > - fb_ops.fb_debug_leave : drm_fb_helper_debug_leave()
> > > 
> > > Destroy is safe
> > > - fb_ops.fb_destroy
> > > 
> > > Finally we look at other call paths:
> > > 
> > > drm_fb_helper_set_suspend{_unlocked}() and
> > > drm_fb_helper_resume_worker():
> > > Calls into fb_set_suspend(), but that's fine since it just uses the
> > > fbdev notifier.
> > > 
> > > drm_fb_helper_restore_fbdev_mode_unlocked():
> > > Called from drm_driver.last_close, possibly after drm_fb_helper_fini().
> > > 
> > > drm_fb_helper_force_kernel_mode():
> > > Triggered by sysrq, possibly after unplug, but before
> > > drm_fb_helper_fini().
> > > 
> > > drm_fb_helper_hotplug_event():
> > > Called by drm_kms_helper_hotplug_event(). I don't know if this can be
> > > called after drm_dev_unregister(), so add a check to be safe.
> > > 
> > > Based on this analysis the following functions get a
> > > drm_dev_is_unplugged() check:
> > > - drm_fb_helper_restore_fbdev_mode_unlocked()
> > > - drm_fb_helper_force_kernel_mode()
> > > - drm_fb_helper_hotplug_event()
> > > - drm_fb_helper_dirty_work()
> > > 
> > > Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> > You're mixing in the new simple fbdev helper helpers as a bonus, that's
> > two things in one patch and makes it harder to review what's really going
> > on.
> > 
> > My gut feeling is that when we need a helper for the helper the original
> > helper needs to be improved, but I think that's much easier to decide when
> > it's a separate patch. Splitting things out would definitely make this
> > patch much smaller and easier to understand and review.
> > 
> > The only reason for the simple helpers that I could find is the
> > drm_dev_ref/unref, we should be able to do that in one of the existing
> > callbacks.
> 
> The reason I did that is because I couldn't put it in the existing
> helpers since the framebuffer is removed after drm_fb_helper_fini() and
> I can't drop the ref on drm_device before the framebuffer is gone.

Why is that impossible? I'd have assumed that we'd first drop all the
drm_device refcounts before we do any of the cleanup steps.

> I can split the patch in two:
> 1. Add drm_dev_is_unplugged() checks
> 2. Add drm_fb_helper_simple_init/fini() and drm_fb_helper_fb_destroy()
> 
> Maybe there's a way to remove the framebuffer in drm_fb_helper_fini()
> so we can drop the ref there, I don't know.

No, we can't do that since the references that get dropped in the driver
code is the driver code's private reference. The problem is that a bunch
of drivers still embed the framebuffer for the fbdev emulation into a
larger structure, which means the reference counting is obviously busted.

But all reasonable modern drivers should do that, and if we have to
convert some to do proper unplug behaviour - well that's expected anyway
because the current ->unload hook doesn't work for unplugging.
-Daniel

> Here's how drivers destroy fbdev:
> 
> 
> static int amdgpu_fbdev_destroy(struct drm_device *dev, struct amdgpu_fbdev
> *rfbdev)
> {
>     struct amdgpu_framebuffer *rfb = &rfbdev->rfb;
> 
>     drm_fb_helper_unregister_fbi(&rfbdev->helper);
> 
>     if (rfb->obj) {
>         amdgpufb_destroy_pinned_object(rfb->obj);
>         rfb->obj = NULL;
>     }
>     drm_fb_helper_fini(&rfbdev->helper);
>     drm_framebuffer_unregister_private(&rfb->base);
>     drm_framebuffer_cleanup(&rfb->base);
> 
>     return 0;
> }
> 
> void armada_fbdev_fini(struct drm_device *dev)
> {
>     struct armada_private *priv = dev->dev_private;
>     struct drm_fb_helper *fbh = priv->fbdev;
> 
>     if (fbh) {
>         drm_fb_helper_unregister_fbi(fbh);
> 
>         drm_fb_helper_fini(fbh);
> 
>         if (fbh->fb)
>             fbh->fb->funcs->destroy(fbh->fb);
> 
>         priv->fbdev = NULL;
>     }
> }
> 
> static void ast_fbdev_destroy(struct drm_device *dev,
>                   struct ast_fbdev *afbdev)
> {
>     struct ast_framebuffer *afb = &afbdev->afb;
> 
>     drm_fb_helper_unregister_fbi(&afbdev->helper);
> 
>     if (afb->obj) {
>         drm_gem_object_unreference_unlocked(afb->obj);
>         afb->obj = NULL;
>     }
>     drm_fb_helper_fini(&afbdev->helper);
> 
>     vfree(afbdev->sysram);
>     drm_framebuffer_unregister_private(&afb->base);
>     drm_framebuffer_cleanup(&afb->base);
> }
> 
> static int bochs_fbdev_destroy(struct bochs_device *bochs)
> {
>     struct bochs_framebuffer *gfb = &bochs->fb.gfb;
> 
>     DRM_DEBUG_DRIVER("\n");
> 
>     drm_fb_helper_unregister_fbi(&bochs->fb.helper);
> 
>     if (gfb->obj) {
>         drm_gem_object_unreference_unlocked(gfb->obj);
>         gfb->obj = NULL;
>     }
> 
>     drm_framebuffer_unregister_private(&gfb->base);
>     drm_framebuffer_cleanup(&gfb->base);
> 
>     return 0;
> }
> 
> static int cirrus_fbdev_destroy(struct drm_device *dev,
>                 struct cirrus_fbdev *gfbdev)
> {
>     struct cirrus_framebuffer *gfb = &gfbdev->gfb;
> 
>     drm_fb_helper_unregister_fbi(&gfbdev->helper);
> 
>     if (gfb->obj) {
>         drm_gem_object_unreference_unlocked(gfb->obj);
>         gfb->obj = NULL;
>     }
> 
>     vfree(gfbdev->sysram);
>     drm_fb_helper_fini(&gfbdev->helper);
>     drm_framebuffer_unregister_private(&gfb->base);
>     drm_framebuffer_cleanup(&gfb->base);
> 
>     return 0;
> }
> 
> void drm_fbdev_cma_fini(struct drm_fbdev_cma *fbdev_cma)
> {
>     drm_fb_helper_unregister_fbi(&fbdev_cma->fb_helper);
>     if (fbdev_cma->fb_helper.fbdev)
>         drm_fbdev_cma_defio_fini(fbdev_cma->fb_helper.fbdev);
> 
>     if (fbdev_cma->fb)
>         drm_framebuffer_remove(&fbdev_cma->fb->fb);
> 
>     drm_fb_helper_fini(&fbdev_cma->fb_helper);
>     kfree(fbdev_cma);
> }
> 
> static void exynos_drm_fbdev_destroy(struct drm_device *dev,
>                       struct drm_fb_helper *fb_helper)
> {
>     struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(fb_helper);
>     struct exynos_drm_gem *exynos_gem = exynos_fbd->exynos_gem;
>     struct drm_framebuffer *fb;
> 
>     vunmap(exynos_gem->kvaddr);
> 
>     /* release drm framebuffer and real buffer */
>     if (fb_helper->fb && fb_helper->fb->funcs) {
>         fb = fb_helper->fb;
>         if (fb)
>             drm_framebuffer_remove(fb);
>     }
> 
>     drm_fb_helper_unregister_fbi(fb_helper);
> 
>     drm_fb_helper_fini(fb_helper);
> }
> 
> static int psb_fbdev_destroy(struct drm_device *dev, struct psb_fbdev
> *fbdev)
> {
>     struct psb_framebuffer *psbfb = &fbdev->pfb;
> 
>     drm_fb_helper_unregister_fbi(&fbdev->psb_fb_helper);
> 
>     drm_fb_helper_fini(&fbdev->psb_fb_helper);
>     drm_framebuffer_unregister_private(&psbfb->base);
>     drm_framebuffer_cleanup(&psbfb->base);
> 
>     if (psbfb->gtt)
> drm_gem_object_unreference_unlocked(&psbfb->gtt->gem);
>     return 0;
> }
> 
> static void hibmc_fbdev_destroy(struct hibmc_fbdev *fbdev)
> {
>     struct hibmc_framebuffer *gfb = fbdev->fb;
>     struct drm_fb_helper *fbh = &fbdev->helper;
> 
>     drm_fb_helper_unregister_fbi(fbh);
> 
>     drm_fb_helper_fini(fbh);
> 
>     if (gfb)
>         drm_framebuffer_unreference(&gfb->fb);
> }
> 
> static void intel_fbdev_destroy(struct intel_fbdev *ifbdev)
> {
>     /* We rely on the object-free to release the VMA pinning for
>      * the info->screen_base mmaping. Leaking the VMA is simpler than
>      * trying to rectify all the possible error paths leading here.
>      */
> 
>     drm_fb_helper_unregister_fbi(&ifbdev->helper);
> 
>     drm_fb_helper_fini(&ifbdev->helper);
> 
>     if (ifbdev->vma) {
>         mutex_lock(&ifbdev->helper.dev->struct_mutex);
>         intel_unpin_fb_vma(ifbdev->vma);
>         mutex_unlock(&ifbdev->helper.dev->struct_mutex);
>     }
> 
>     if (ifbdev->fb)
>         drm_framebuffer_remove(&ifbdev->fb->base);
> 
>     kfree(ifbdev);
> }
> 
> static int mga_fbdev_destroy(struct drm_device *dev,
>                 struct mga_fbdev *mfbdev)
> {
>     struct mga_framebuffer *mfb = &mfbdev->mfb;
> 
>     drm_fb_helper_unregister_fbi(&mfbdev->helper);
> 
>     if (mfb->obj) {
>         drm_gem_object_unreference_unlocked(mfb->obj);
>         mfb->obj = NULL;
>     }
>     drm_fb_helper_fini(&mfbdev->helper);
>     vfree(mfbdev->sysram);
>     drm_framebuffer_unregister_private(&mfb->base);
>     drm_framebuffer_cleanup(&mfb->base);
> 
>     return 0;
> }
> 
> void msm_fbdev_free(struct drm_device *dev)
> {
>     struct msm_drm_private *priv = dev->dev_private;
>     struct drm_fb_helper *helper = priv->fbdev;
>     struct msm_fbdev *fbdev;
> 
>     DBG();
> 
>     drm_fb_helper_unregister_fbi(helper);
> 
>     drm_fb_helper_fini(helper);
> 
>     fbdev = to_msm_fbdev(priv->fbdev);
> 
>     /* this will free the backing object */
>     if (fbdev->fb) {
>         msm_gem_put_vaddr(fbdev->bo);
>         drm_framebuffer_remove(fbdev->fb);
>     }
> 
>     kfree(fbdev);
> 
>     priv->fbdev = NULL;
> }
> 
> static int
> nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
> {
>     struct nouveau_framebuffer *nouveau_fb =
> nouveau_framebuffer(fbcon->helper.fb);
> 
>     drm_fb_helper_unregister_fbi(&fbcon->helper);
>     drm_fb_helper_fini(&fbcon->helper);
> 
>     if (nouveau_fb->nvbo) {
>         nouveau_bo_vma_del(nouveau_fb->nvbo, &nouveau_fb->vma);
>         nouveau_bo_unmap(nouveau_fb->nvbo);
>         nouveau_bo_unpin(nouveau_fb->nvbo);
>         drm_framebuffer_unreference(&nouveau_fb->base);
>     }
> 
>     return 0;
> }
> 
> void omap_fbdev_free(struct drm_device *dev)
> {
>     struct omap_drm_private *priv = dev->dev_private;
>     struct drm_fb_helper *helper = priv->fbdev;
>     struct omap_fbdev *fbdev;
> 
>     DBG();
> 
>     drm_fb_helper_unregister_fbi(helper);
> 
>     drm_fb_helper_fini(helper);
> 
>     fbdev = to_omap_fbdev(priv->fbdev);
> 
>     /* release the ref taken in omap_fbdev_create() */
>     omap_gem_put_paddr(fbdev->bo);
> 
>     /* this will free the backing object */
>     if (fbdev->fb)
>         drm_framebuffer_remove(fbdev->fb);
> 
>     kfree(fbdev);
> 
>     priv->fbdev = NULL;
> }
> 
> static int qxl_fbdev_destroy(struct drm_device *dev, struct qxl_fbdev
> *qfbdev)
> {
>     struct qxl_framebuffer *qfb = &qfbdev->qfb;
> 
>     drm_fb_helper_unregister_fbi(&qfbdev->helper);
> 
>     if (qfb->obj) {
>         qxlfb_destroy_pinned_object(qfb->obj);
>         qfb->obj = NULL;
>     }
>     drm_fb_helper_fini(&qfbdev->helper);
>     vfree(qfbdev->shadow);
>     drm_framebuffer_cleanup(&qfb->base);
> 
>     return 0;
> }
> 
> static int radeon_fbdev_destroy(struct drm_device *dev, struct radeon_fbdev
> *rfbdev)
> {
>     struct radeon_framebuffer *rfb = &rfbdev->rfb;
> 
>     drm_fb_helper_unregister_fbi(&rfbdev->helper);
> 
>     if (rfb->obj) {
>         radeonfb_destroy_pinned_object(rfb->obj);
>         rfb->obj = NULL;
>     }
>     drm_fb_helper_fini(&rfbdev->helper);
>     drm_framebuffer_unregister_private(&rfb->base);
>     drm_framebuffer_cleanup(&rfb->base);
> 
>     return 0;
> }
> 
> void rockchip_drm_fbdev_fini(struct drm_device *dev)
> {
>     struct rockchip_drm_private *private = dev->dev_private;
>     struct drm_fb_helper *helper;
> 
>     helper = &private->fbdev_helper;
> 
>     drm_fb_helper_unregister_fbi(helper);
> 
>     if (helper->fb)
>         drm_framebuffer_unreference(helper->fb);
> 
>     drm_fb_helper_fini(helper);
> }
> 
> static void tegra_fbdev_exit(struct tegra_fbdev *fbdev)
> {
>     drm_fb_helper_unregister_fbi(&fbdev->base);
> 
>     if (fbdev->fb)
>         drm_framebuffer_remove(&fbdev->fb->base);
> 
>     drm_fb_helper_fini(&fbdev->base);
>     tegra_fbdev_free(fbdev);
> }
> 
> static void udl_fbdev_destroy(struct drm_device *dev,
>                   struct udl_fbdev *ufbdev)
> {
>     drm_fb_helper_unregister_fbi(&ufbdev->helper);
>     drm_fb_helper_fini(&ufbdev->helper);
>     drm_framebuffer_unregister_private(&ufbdev->ufb.base);
>     drm_framebuffer_cleanup(&ufbdev->ufb.base);
> drm_gem_object_unreference_unlocked(&ufbdev->ufb.obj->base);
> }
> 
> static int virtio_gpu_fbdev_destroy(struct drm_device *dev,
>                     struct virtio_gpu_fbdev *vgfbdev)
> {
>     struct virtio_gpu_framebuffer *vgfb = &vgfbdev->vgfb;
> 
>     drm_fb_helper_unregister_fbi(&vgfbdev->helper);
> 
>     if (vgfb->obj)
>         vgfb->obj = NULL;
>     drm_fb_helper_fini(&vgfbdev->helper);
>     drm_framebuffer_cleanup(&vgfb->base);
> 
>     return 0;
> }
> 
> 
> Noralf.
> 
> > > ---
> > >   drivers/gpu/drm/drm_fb_helper.c | 204 +++++++++++++++++++++++++++++++++++++++-
> > >   include/drm/drm_fb_helper.h     |  35 +++++++
> > >   2 files changed, 237 insertions(+), 2 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> > > index 2e33467..fc898db 100644
> > > --- a/drivers/gpu/drm/drm_fb_helper.c
> > > +++ b/drivers/gpu/drm/drm_fb_helper.c
> > > @@ -105,6 +105,158 @@ static DEFINE_MUTEX(kernel_fb_helper_lock);
> > >    * mmap page writes.
> > >    */
> > > +/**
> > > + * drm_fb_helper_simple_init - Simple fbdev emulation initialization
> > > + * @dev: drm device
> > > + * @fb_helper: driver-allocated fbdev helper structure to initialize
> > > + * @bpp_sel: bpp value to use for the framebuffer configuration
> > > + * @max_conn_count: max connector count
> > > + * @funcs: pointer to structure of functions associate with this helper
> > > + *
> > > + * Simple fbdev emulation initialization which calls the following functions:
> > > + * drm_fb_helper_prepare(), drm_fb_helper_init(),
> > > + * drm_fb_helper_single_add_all_connectors() and
> > > + * drm_fb_helper_initial_config().
> > > + *
> > > + * This function takes a ref on &drm_device and must be used together with
> > > + * drm_fb_helper_simple_fini() or drm_fb_helper_dev_unplug().
> > > + *
> > > + * fbdev deferred I/O users must use drm_fb_helper_defio_init().
> > > + *
> > > + * Returns:
> > > + * 0 on success or a negative error code on failure.
> > > + */
> > > +int drm_fb_helper_simple_init(struct drm_device *dev,
> > > +			      struct drm_fb_helper *fb_helper, int bpp_sel,
> > > +			      int max_conn_count,
> > > +			      const struct drm_fb_helper_funcs *funcs)
> > > +{
> > > +	int ret;
> > > +
> > > +	drm_fb_helper_prepare(dev, fb_helper, funcs);
> > > +
> > > +	ret = drm_fb_helper_init(dev, fb_helper, max_conn_count);
> > > +	if (ret < 0) {
> > > +		DRM_DEV_ERROR(dev->dev, "Failed to initialize fb helper.\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	drm_dev_ref(dev);
> > > +
> > > +	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
> > > +	if (ret < 0) {
> > > +		DRM_DEV_ERROR(dev->dev, "Failed to add connectors.\n");
> > > +		goto err_drm_fb_helper_fini;
> > > +
> > > +	}
> > > +
> > > +	ret = drm_fb_helper_initial_config(fb_helper, bpp_sel);
> > > +	if (ret < 0) {
> > > +		DRM_DEV_ERROR(dev->dev, "Failed to set initial hw config.\n");
> > > +		goto err_drm_fb_helper_fini;
> > > +	}
> > > +
> > > +	return 0;
> > > +
> > > +err_drm_fb_helper_fini:
> > > +	drm_fb_helper_fini(fb_helper);
> > > +	drm_dev_unref(dev);
> > > +
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_fb_helper_simple_init);
> > > +
> > > +static void drm_fb_helper_simple_fini_cleanup(struct drm_fb_helper *fb_helper)
> > > +{
> > > +	struct fb_info *info = fb_helper->fbdev;
> > > +	struct fb_ops *fbops = NULL;
> > > +
> > > +	if (info && info->fbdefio) {
> > > +		fb_deferred_io_cleanup(info);
> > > +		kfree(info->fbdefio);
> > > +		info->fbdefio = NULL;
> > > +		fbops = info->fbops;
> > > +	}
> > > +
> > > +	drm_fb_helper_fini(fb_helper);
> > > +	kfree(fbops);
> > > +	if (fb_helper->fb)
> > > +		drm_framebuffer_remove(fb_helper->fb);
> > > +	drm_dev_unref(fb_helper->dev);
> > > +}
> > > +
> > > +/**
> > > + * drm_fb_helper_simple_fini - Simple fbdev cleanup
> > > + * @fb_helper: fbdev emulation structure, can be NULL
> > > + *
> > > + * Simple fbdev emulation cleanup. This function unregisters fbdev, cleans up
> > > + * deferred I/O if necessary, finalises @fb_helper and removes the framebuffer.
> > > + * The driver if responsible for freeing the @fb_helper structure.
> > > + *
> > > + * Don't use this function if you use drm_fb_helper_dev_unplug().
> > > + */
> > > +void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper)
> > > +{
> > > +	struct fb_info *info;
> > > +
> > > +	if (!fb_helper)
> > > +		return;
> > > +
> > > +	info = fb_helper->fbdev;
> > > +
> > > +	/* Has drm_fb_helper_dev_unplug() been used? */
> > > +	if (info && info->dev)
> > > +		drm_fb_helper_unregister_fbi(fb_helper);
> > > +
> > > +	if (!(info && info->fbops && info->fbops->fb_destroy))
> > > +		drm_fb_helper_simple_fini_cleanup(fb_helper);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_fb_helper_simple_fini);
> > > +
> > > +/**
> > > + * drm_fb_helper_dev_unplug - unplug an fbdev device
> > > + * @fb_helper: driver-allocated fbdev helper, can be NULL
> > > + *
> > > + * This unplugs the fbdev emulation for a hotpluggable DRM device, which makes
> > > + * fbdev inaccessible to userspace operations. This essentially unregisters
> > > + * fbdev and can be called while there are still open users of @fb_helper.
> > > + * Entry-points from fbdev into drm core/helpers are protected by the fbdev
> > > + * &fb_info ref count and drm_dev_is_unplugged(). This means that the driver
> > > + * also has to call drm_dev_unplug() to complete the unplugging.
> > > + *
> > > + * Drivers must use drm_fb_helper_fb_destroy() as their &fb_ops.fb_destroy
> > > + * callback and call drm_mode_config_cleanup() and free @fb_helper in their
> > > + * &drm_driver->release callback.
> > > + *
> > > + * @fb_helper is finalized by this function unless there are open fbdev fd's
> > > + * in case this is postponed to the closing of the last fd. Finalizing includes
> > > + * dropping the ref taken on &drm_device in drm_fb_helper_simple_init().
> > > + */
> > > +void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper)
> > > +{
> > > +	drm_fb_helper_unregister_fbi(fb_helper);
> > I don't see the value of this wrapper. If you want to explain how to best
> > unplug the fbdev emulation I thinkt that's better done in some dedicated
> > DOC section (referenced by drm_dev_unplug() maybe), than by providing a
> > wrapper that only gives you a different name.
> > 
> > _unplug is also a bit misleading, since _unplug is about yanking the
> > backing device. This just unregisters the the fbdev interface.
> > 
> > > +}
> > > +EXPORT_SYMBOL(drm_fb_helper_dev_unplug);
> > > +
> > > +/**
> > > + * drm_fb_helper_fb_destroy - implementation for &fb_ops.fb_destroy
> > > + * @info: fbdev registered by the helper
> > > + *
> > > + * This function does the same as drm_fb_helper_simple_fini() except
> > > + * unregistering fbdev which is already done.
> > > + *
> > > + * &fb_ops.fb_destroy is called during unregister_framebuffer() or the last
> > > + * fb_release() which ever comes last.
> > > + */
> > > +void drm_fb_helper_fb_destroy(struct fb_info *info)
> > > +{
> > > +	struct drm_fb_helper *helper = info->par;
> > > +
> > > +	DRM_DEBUG("\n");
> > > +	drm_fb_helper_simple_fini_cleanup(helper);
> > > +}
> > > +EXPORT_SYMBOL(drm_fb_helper_fb_destroy);
> > > +
> > >   #define drm_fb_helper_for_each_connector(fbh, i__) \
> > >   	for (({ lockdep_assert_held(&(fbh)->lock); }), \
> > >   	     i__ = 0; i__ < (fbh)->connector_count; i__++)
> > > @@ -498,7 +650,7 @@ int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
> > >   	bool do_delayed;
> > >   	int ret;
> > > -	if (!drm_fbdev_emulation)
> > > +	if (!drm_fbdev_emulation || drm_dev_is_unplugged(fb_helper->dev))
> > >   		return -ENODEV;
> > >   	if (READ_ONCE(fb_helper->deferred_setup))
> > > @@ -563,6 +715,9 @@ static bool drm_fb_helper_force_kernel_mode(void)
> > >   	list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
> > >   		struct drm_device *dev = helper->dev;
> > > +		if (drm_dev_is_unplugged(dev))
> > > +			continue;
> > > +
> > >   		if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
> > >   			continue;
> > > @@ -735,6 +890,9 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
> > >   	struct drm_clip_rect clip_copy;
> > >   	unsigned long flags;
> > > +	if (drm_dev_is_unplugged(helper->dev))
> > > +		return;
> > > +
> > >   	spin_lock_irqsave(&helper->dirty_lock, flags);
> > >   	clip_copy = *clip;
> > >   	clip->x1 = clip->y1 = ~0;
> > > @@ -949,6 +1107,48 @@ void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
> > >   }
> > >   EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
> > > +/**
> > > + * drm_fb_helper_defio_init - fbdev deferred I/O initialization
> > > + * @fb_helper: driver-allocated fbdev helper
> > > + *
> > > + * This function allocates &fb_deferred_io, sets callback to
> > > + * drm_fb_helper_deferred_io(), delay to 50ms and calls fb_deferred_io_init().
> > > + *
> > > + * NOTE: A copy of &fb_ops is made and assigned to &info->fbops. This is done
> > > + * because fb_deferred_io_cleanup() clears &fbops->fb_mmap and would thereby
> > > + * affect other instances of that &fb_ops. This copy is freed by the helper
> > > + * during cleanup.
> > > + *
> > > + * Returns:
> > > + * 0 on success or a negative error code on failure.
> > > + */
> > > +int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper)
> > > +{
> > > +	struct fb_info *info = fb_helper->fbdev;
> > > +	struct fb_deferred_io *fbdefio;
> > > +	struct fb_ops *fbops;
> > > +
> > > +	fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
> > > +	fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
> > > +	if (!fbdefio || !fbops) {
> > > +		kfree(fbdefio);
> > > +		kfree(fbops);
> > > +		return -ENOMEM;
> > > +	}
> > > +
> > > +	info->fbdefio = fbdefio;
> > > +	fbdefio->delay = msecs_to_jiffies(50);
> > > +	fbdefio->deferred_io = drm_fb_helper_deferred_io;
> > > +
> > > +	*fbops = *info->fbops;
> > > +	info->fbops = fbops;
> > > +
> > > +	fb_deferred_io_init(info);
> > > +
> > > +	return 0;
> > > +}
> > > +EXPORT_SYMBOL(drm_fb_helper_defio_init);
> > > +
> > >   static void drm_fb_helper_dirty(struct fb_info *info, u32 x, u32 y,
> > >   				u32 width, u32 height)
> > >   {
> > > @@ -2591,7 +2791,7 @@ int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
> > >   {
> > >   	int err = 0;
> > > -	if (!drm_fbdev_emulation)
> > > +	if (!drm_fbdev_emulation || drm_dev_is_unplugged(fb_helper->dev))
> > >   		return 0;
> > >   	mutex_lock(&fb_helper->lock);
> > > diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
> > > index 33fe959..1c5a648 100644
> > > --- a/include/drm/drm_fb_helper.h
> > > +++ b/include/drm/drm_fb_helper.h
> > > @@ -242,6 +242,14 @@ struct drm_fb_helper {
> > >   	.fb_ioctl	= drm_fb_helper_ioctl
> > >   #ifdef CONFIG_DRM_FBDEV_EMULATION
> > > +int drm_fb_helper_simple_init(struct drm_device *dev,
> > > +			      struct drm_fb_helper *fb_helper, int bpp_sel,
> > > +			      int max_conn_count,
> > > +			      const struct drm_fb_helper_funcs *funcs);
> > > +void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper);
> > > +void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper);
> > > +void drm_fb_helper_fb_destroy(struct fb_info *info);
> > > +
> > >   void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
> > >   			   const struct drm_fb_helper_funcs *funcs);
> > >   int drm_fb_helper_init(struct drm_device *dev,
> > > @@ -265,6 +273,7 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
> > >   void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper);
> > > +int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper);
> > >   void drm_fb_helper_deferred_io(struct fb_info *info,
> > >   			       struct list_head *pagelist);
> > > @@ -311,6 +320,27 @@ int drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, struct drm_
> > >   int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper,
> > >   				       struct drm_connector *connector);
> > >   #else
> > > +static inline int
> > > +drm_fb_helper_simple_init(struct drm_device *dev,
> > > +			  struct drm_fb_helper *fb_helper, int bpp_sel,
> > > +			  int max_conn_count,
> > > +			  const struct drm_fb_helper_funcs *funcs)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > > +static inline void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper)
> > > +{
> > > +}
> > > +
> > > +static inline void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper)
> > > +{
> > > +}
> > > +
> > > +static inline void drm_fb_helper_fb_destroy(struct fb_info *info)
> > > +{
> > > +}
> > > +
> > >   static inline void drm_fb_helper_prepare(struct drm_device *dev,
> > >   					struct drm_fb_helper *helper,
> > >   					const struct drm_fb_helper_funcs *funcs)
> > > @@ -393,6 +423,11 @@ static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
> > >   {
> > >   }
> > > +static inline int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > >   static inline void drm_fb_helper_deferred_io(struct fb_info *info,
> > >   					     struct list_head *pagelist)
> > >   {
> > > -- 
> > > 2.7.4
> > > 
>
Noralf Trønnes Aug. 30, 2017, 1:45 p.m. UTC | #4
Den 30.08.2017 09.29, skrev Daniel Vetter:
> On Tue, Aug 29, 2017 at 06:17:25PM +0200, Noralf Trønnes wrote:
>> Den 28.08.2017 23.41, skrev Daniel Vetter:
>>> On Mon, Aug 28, 2017 at 07:17:44PM +0200, Noralf Trønnes wrote:
>>>> Support device unplug by introducing a new initialization function:
>>>> drm_fb_helper_simple_init() which together with
>>>> drm_fb_helper_dev_unplug() and drm_fb_helper_fb_destroy() handles open
>>>> fbdev fd's after device unplug. There's also a
>>>> drm_fb_helper_simple_fini() for drivers who's device won't be removed.
>>>>
>>>> It turns out that fbdev has the necessary ref counting, so
>>>> unregister_framebuffer() together with fb_ops->destroy handles unplug
>>>> with open fd's. The ref counting doesn't apply to the fb_info structure
>>>> itself, but to use of the fbdev framebuffer.
>>>>
>>>> Analysis of entry points after unregister_framebuffer():
>>>> - fbcon has been unbound (through notifier)
>>>> - sysfs files removed
>>>>
>>>> First we look at possible operations on open fbdev file handles:
>>>>
>>>> static const struct file_operations fb_fops = {
>>>> 	.read =		fb_read,
>>>> 	.write =	fb_write,
>>>> 	.unlocked_ioctl = fb_ioctl,
>>>> 	.compat_ioctl = fb_compat_ioctl,
>>>> 	.mmap =		fb_mmap,
>>>> 	.open =		fb_open,
>>>> 	.release =	fb_release,
>>>> 	.get_unmapped_area = get_fb_unmapped_area,
>>>> 	.fsync =	fb_deferred_io_fsync,
>>>> };
>>>>
>>>> Not possible after unregister:
>>>> fb_open() -> fb_ops.fb_open
>>>>
>>>> Protected by file_fb_info() (-ENODEV):
>>>> fb_read() -> fb_ops.fb_read : drm_fb_helper_sys_read()
>>>> fb_write() -> fb_ops.fb_write : drm_fb_helper_sys_write()
>>>> fb_ioctl() -> fb_ops.fb_ioctl : drm_fb_helper_ioctl()
>>>> fb_compat_ioctl() -> fb_ops.fb_compat_ioctl
>>>> fb_mmap() -> fb_ops.fb_mmap
>>>>
>>>> Safe:
>>>> fb_release() -> fb_ops.fb_release
>>>> get_fb_unmapped_area() : info->screen_base
>>>> fb_deferred_io_fsync() : if (info->fbdefio) &info->deferred_work
>>>>
>>>> Active mmap's will need the backing buffer to be present.
>>>> If deferred IO is used, mmap writes will via a worker generate calls to
>>>> drm_fb_helper_deferred_io() which in turn via a worker calls into
>>>> drm_fb_helper_dirty_work().
>>>>
>>>> Secondly we look at the remaining struct fb_ops operations:
>>>>
>>>> Called by fbcon:
>>>> - fb_ops.fb_fillrect : drm_fb_helper_{sys,cfb}_fillrect()
>>>> - fb_ops.fb_copyarea : drm_fb_helper_{sys,cfb}_copyarea()
>>>> - fb_ops.fb_imageblit : drm_fb_helper_{sys,cfb}_imageblit()
>>>>
>>>> Called in fb_set_var() which is called from ioctl, sysfs and fbcon:
>>>> - fb_ops.fb_check_var : drm_fb_helper_check_var()
>>>> - fb_ops.fb_set_par : drm_fb_helper_set_par()
>>>> drm_fb_helper_set_par() is also called from drm_fb_helper_hotplug_event().
>>>>
>>>> Called in fb_set_cmap() which is called from fb_set_var(), ioctl
>>>> and fbcon:
>>>> - fb_ops.fb_setcolreg
>>>> - fb_ops.fb_setcmap : drm_fb_helper_setcmap()
>>>>
>>>> Called in fb_blank() which is called from ioctl and fbcon:
>>>> - fb_ops.fb_blank : drm_fb_helper_blank()
>>>>
>>>> Called in fb_pan_display() which is called from fb_set_var(), ioctl,
>>>> sysfs and fbcon:
>>>> - fb_ops.fb_pan_display : drm_fb_helper_pan_display()
>>>>
>>>> Called by fbcon:
>>>> - fb_ops.fb_cursor
>>>>
>>>> Called in fb_read(), fb_write(), and fb_get_buffer_offset() which is
>>>> called by fbcon:
>>>> - fb_ops.fb_sync
>>>>
>>>> Called by fb_set_var():
>>>> - fb_ops.fb_get_caps
>>>>
>>>> Called by fbcon:
>>>> - fb_ops.fb_debug_enter : drm_fb_helper_debug_enter()
>>>> - fb_ops.fb_debug_leave : drm_fb_helper_debug_leave()
>>>>
>>>> Destroy is safe
>>>> - fb_ops.fb_destroy
>>>>
>>>> Finally we look at other call paths:
>>>>
>>>> drm_fb_helper_set_suspend{_unlocked}() and
>>>> drm_fb_helper_resume_worker():
>>>> Calls into fb_set_suspend(), but that's fine since it just uses the
>>>> fbdev notifier.
>>>>
>>>> drm_fb_helper_restore_fbdev_mode_unlocked():
>>>> Called from drm_driver.last_close, possibly after drm_fb_helper_fini().
>>>>
>>>> drm_fb_helper_force_kernel_mode():
>>>> Triggered by sysrq, possibly after unplug, but before
>>>> drm_fb_helper_fini().
>>>>
>>>> drm_fb_helper_hotplug_event():
>>>> Called by drm_kms_helper_hotplug_event(). I don't know if this can be
>>>> called after drm_dev_unregister(), so add a check to be safe.
>>>>
>>>> Based on this analysis the following functions get a
>>>> drm_dev_is_unplugged() check:
>>>> - drm_fb_helper_restore_fbdev_mode_unlocked()
>>>> - drm_fb_helper_force_kernel_mode()
>>>> - drm_fb_helper_hotplug_event()
>>>> - drm_fb_helper_dirty_work()
>>>>
>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>> You're mixing in the new simple fbdev helper helpers as a bonus, that's
>>> two things in one patch and makes it harder to review what's really going
>>> on.
>>>
>>> My gut feeling is that when we need a helper for the helper the original
>>> helper needs to be improved, but I think that's much easier to decide when
>>> it's a separate patch. Splitting things out would definitely make this
>>> patch much smaller and easier to understand and review.
>>>
>>> The only reason for the simple helpers that I could find is the
>>> drm_dev_ref/unref, we should be able to do that in one of the existing
>>> callbacks.
>> The reason I did that is because I couldn't put it in the existing
>> helpers since the framebuffer is removed after drm_fb_helper_fini() and
>> I can't drop the ref on drm_device before the framebuffer is gone.
> Why is that impossible? I'd have assumed that we'd first drop all the
> drm_device refcounts before we do any of the cleanup steps.

Ah, yes ofc that makes sense :$
I'm cleaning up fbdev in fb_ops.fb_destroy, but I can just drop the ref
there...

I've been several rounds with this and it feels like I have been
stepping in every pothole there is. The upside is that in the end,
the solution turns out to be very simple :-)

I'll respin and drop drm_fb_helper_simple_init/fini() and
drm_fb_helper_dev_unplug().


Noralf.
diff mbox

Patch

diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index 2e33467..fc898db 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -105,6 +105,158 @@  static DEFINE_MUTEX(kernel_fb_helper_lock);
  * mmap page writes.
  */
 
+/**
+ * drm_fb_helper_simple_init - Simple fbdev emulation initialization
+ * @dev: drm device
+ * @fb_helper: driver-allocated fbdev helper structure to initialize
+ * @bpp_sel: bpp value to use for the framebuffer configuration
+ * @max_conn_count: max connector count
+ * @funcs: pointer to structure of functions associate with this helper
+ *
+ * Simple fbdev emulation initialization which calls the following functions:
+ * drm_fb_helper_prepare(), drm_fb_helper_init(),
+ * drm_fb_helper_single_add_all_connectors() and
+ * drm_fb_helper_initial_config().
+ *
+ * This function takes a ref on &drm_device and must be used together with
+ * drm_fb_helper_simple_fini() or drm_fb_helper_dev_unplug().
+ *
+ * fbdev deferred I/O users must use drm_fb_helper_defio_init().
+ *
+ * Returns:
+ * 0 on success or a negative error code on failure.
+ */
+int drm_fb_helper_simple_init(struct drm_device *dev,
+			      struct drm_fb_helper *fb_helper, int bpp_sel,
+			      int max_conn_count,
+			      const struct drm_fb_helper_funcs *funcs)
+{
+	int ret;
+
+	drm_fb_helper_prepare(dev, fb_helper, funcs);
+
+	ret = drm_fb_helper_init(dev, fb_helper, max_conn_count);
+	if (ret < 0) {
+		DRM_DEV_ERROR(dev->dev, "Failed to initialize fb helper.\n");
+		return ret;
+	}
+
+	drm_dev_ref(dev);
+
+	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
+	if (ret < 0) {
+		DRM_DEV_ERROR(dev->dev, "Failed to add connectors.\n");
+		goto err_drm_fb_helper_fini;
+
+	}
+
+	ret = drm_fb_helper_initial_config(fb_helper, bpp_sel);
+	if (ret < 0) {
+		DRM_DEV_ERROR(dev->dev, "Failed to set initial hw config.\n");
+		goto err_drm_fb_helper_fini;
+	}
+
+	return 0;
+
+err_drm_fb_helper_fini:
+	drm_fb_helper_fini(fb_helper);
+	drm_dev_unref(dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drm_fb_helper_simple_init);
+
+static void drm_fb_helper_simple_fini_cleanup(struct drm_fb_helper *fb_helper)
+{
+	struct fb_info *info = fb_helper->fbdev;
+	struct fb_ops *fbops = NULL;
+
+	if (info && info->fbdefio) {
+		fb_deferred_io_cleanup(info);
+		kfree(info->fbdefio);
+		info->fbdefio = NULL;
+		fbops = info->fbops;
+	}
+
+	drm_fb_helper_fini(fb_helper);
+	kfree(fbops);
+	if (fb_helper->fb)
+		drm_framebuffer_remove(fb_helper->fb);
+	drm_dev_unref(fb_helper->dev);
+}
+
+/**
+ * drm_fb_helper_simple_fini - Simple fbdev cleanup
+ * @fb_helper: fbdev emulation structure, can be NULL
+ *
+ * Simple fbdev emulation cleanup. This function unregisters fbdev, cleans up
+ * deferred I/O if necessary, finalises @fb_helper and removes the framebuffer.
+ * The driver if responsible for freeing the @fb_helper structure.
+ *
+ * Don't use this function if you use drm_fb_helper_dev_unplug().
+ */
+void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper)
+{
+	struct fb_info *info;
+
+	if (!fb_helper)
+		return;
+
+	info = fb_helper->fbdev;
+
+	/* Has drm_fb_helper_dev_unplug() been used? */
+	if (info && info->dev)
+		drm_fb_helper_unregister_fbi(fb_helper);
+
+	if (!(info && info->fbops && info->fbops->fb_destroy))
+		drm_fb_helper_simple_fini_cleanup(fb_helper);
+}
+EXPORT_SYMBOL_GPL(drm_fb_helper_simple_fini);
+
+/**
+ * drm_fb_helper_dev_unplug - unplug an fbdev device
+ * @fb_helper: driver-allocated fbdev helper, can be NULL
+ *
+ * This unplugs the fbdev emulation for a hotpluggable DRM device, which makes
+ * fbdev inaccessible to userspace operations. This essentially unregisters
+ * fbdev and can be called while there are still open users of @fb_helper.
+ * Entry-points from fbdev into drm core/helpers are protected by the fbdev
+ * &fb_info ref count and drm_dev_is_unplugged(). This means that the driver
+ * also has to call drm_dev_unplug() to complete the unplugging.
+ *
+ * Drivers must use drm_fb_helper_fb_destroy() as their &fb_ops.fb_destroy
+ * callback and call drm_mode_config_cleanup() and free @fb_helper in their
+ * &drm_driver->release callback.
+ *
+ * @fb_helper is finalized by this function unless there are open fbdev fd's
+ * in case this is postponed to the closing of the last fd. Finalizing includes
+ * dropping the ref taken on &drm_device in drm_fb_helper_simple_init().
+ */
+void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper)
+{
+	drm_fb_helper_unregister_fbi(fb_helper);
+}
+EXPORT_SYMBOL(drm_fb_helper_dev_unplug);
+
+/**
+ * drm_fb_helper_fb_destroy - implementation for &fb_ops.fb_destroy
+ * @info: fbdev registered by the helper
+ *
+ * This function does the same as drm_fb_helper_simple_fini() except
+ * unregistering fbdev which is already done.
+ *
+ * &fb_ops.fb_destroy is called during unregister_framebuffer() or the last
+ * fb_release() which ever comes last.
+ */
+void drm_fb_helper_fb_destroy(struct fb_info *info)
+{
+	struct drm_fb_helper *helper = info->par;
+
+	DRM_DEBUG("\n");
+	drm_fb_helper_simple_fini_cleanup(helper);
+}
+EXPORT_SYMBOL(drm_fb_helper_fb_destroy);
+
 #define drm_fb_helper_for_each_connector(fbh, i__) \
 	for (({ lockdep_assert_held(&(fbh)->lock); }), \
 	     i__ = 0; i__ < (fbh)->connector_count; i__++)
@@ -498,7 +650,7 @@  int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
 	bool do_delayed;
 	int ret;
 
-	if (!drm_fbdev_emulation)
+	if (!drm_fbdev_emulation || drm_dev_is_unplugged(fb_helper->dev))
 		return -ENODEV;
 
 	if (READ_ONCE(fb_helper->deferred_setup))
@@ -563,6 +715,9 @@  static bool drm_fb_helper_force_kernel_mode(void)
 	list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
 		struct drm_device *dev = helper->dev;
 
+		if (drm_dev_is_unplugged(dev))
+			continue;
+
 		if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
 			continue;
 
@@ -735,6 +890,9 @@  static void drm_fb_helper_dirty_work(struct work_struct *work)
 	struct drm_clip_rect clip_copy;
 	unsigned long flags;
 
+	if (drm_dev_is_unplugged(helper->dev))
+		return;
+
 	spin_lock_irqsave(&helper->dirty_lock, flags);
 	clip_copy = *clip;
 	clip->x1 = clip->y1 = ~0;
@@ -949,6 +1107,48 @@  void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
 }
 EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
 
+/**
+ * drm_fb_helper_defio_init - fbdev deferred I/O initialization
+ * @fb_helper: driver-allocated fbdev helper
+ *
+ * This function allocates &fb_deferred_io, sets callback to
+ * drm_fb_helper_deferred_io(), delay to 50ms and calls fb_deferred_io_init().
+ *
+ * NOTE: A copy of &fb_ops is made and assigned to &info->fbops. This is done
+ * because fb_deferred_io_cleanup() clears &fbops->fb_mmap and would thereby
+ * affect other instances of that &fb_ops. This copy is freed by the helper
+ * during cleanup.
+ *
+ * Returns:
+ * 0 on success or a negative error code on failure.
+ */
+int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper)
+{
+	struct fb_info *info = fb_helper->fbdev;
+	struct fb_deferred_io *fbdefio;
+	struct fb_ops *fbops;
+
+	fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
+	fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
+	if (!fbdefio || !fbops) {
+		kfree(fbdefio);
+		kfree(fbops);
+		return -ENOMEM;
+	}
+
+	info->fbdefio = fbdefio;
+	fbdefio->delay = msecs_to_jiffies(50);
+	fbdefio->deferred_io = drm_fb_helper_deferred_io;
+
+	*fbops = *info->fbops;
+	info->fbops = fbops;
+
+	fb_deferred_io_init(info);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_fb_helper_defio_init);
+
 static void drm_fb_helper_dirty(struct fb_info *info, u32 x, u32 y,
 				u32 width, u32 height)
 {
@@ -2591,7 +2791,7 @@  int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
 {
 	int err = 0;
 
-	if (!drm_fbdev_emulation)
+	if (!drm_fbdev_emulation || drm_dev_is_unplugged(fb_helper->dev))
 		return 0;
 
 	mutex_lock(&fb_helper->lock);
diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
index 33fe959..1c5a648 100644
--- a/include/drm/drm_fb_helper.h
+++ b/include/drm/drm_fb_helper.h
@@ -242,6 +242,14 @@  struct drm_fb_helper {
 	.fb_ioctl	= drm_fb_helper_ioctl
 
 #ifdef CONFIG_DRM_FBDEV_EMULATION
+int drm_fb_helper_simple_init(struct drm_device *dev,
+			      struct drm_fb_helper *fb_helper, int bpp_sel,
+			      int max_conn_count,
+			      const struct drm_fb_helper_funcs *funcs);
+void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper);
+void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper);
+void drm_fb_helper_fb_destroy(struct fb_info *info);
+
 void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
 			   const struct drm_fb_helper_funcs *funcs);
 int drm_fb_helper_init(struct drm_device *dev,
@@ -265,6 +273,7 @@  void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
 
 void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper);
 
+int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper);
 void drm_fb_helper_deferred_io(struct fb_info *info,
 			       struct list_head *pagelist);
 
@@ -311,6 +320,27 @@  int drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, struct drm_
 int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper,
 				       struct drm_connector *connector);
 #else
+static inline int
+drm_fb_helper_simple_init(struct drm_device *dev,
+			  struct drm_fb_helper *fb_helper, int bpp_sel,
+			  int max_conn_count,
+			  const struct drm_fb_helper_funcs *funcs)
+{
+	return 0;
+}
+
+static inline void drm_fb_helper_simple_fini(struct drm_fb_helper *fb_helper)
+{
+}
+
+static inline void drm_fb_helper_dev_unplug(struct drm_fb_helper *fb_helper)
+{
+}
+
+static inline void drm_fb_helper_fb_destroy(struct fb_info *info)
+{
+}
+
 static inline void drm_fb_helper_prepare(struct drm_device *dev,
 					struct drm_fb_helper *helper,
 					const struct drm_fb_helper_funcs *funcs)
@@ -393,6 +423,11 @@  static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
 {
 }
 
+static inline int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper)
+{
+	return 0;
+}
+
 static inline void drm_fb_helper_deferred_io(struct fb_info *info,
 					     struct list_head *pagelist)
 {