diff mbox

[RFC,v2,1/8] drm/fb-helper: Add fb_deferred_io support

Message ID 1460135110-24121-2-git-send-email-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes April 8, 2016, 5:05 p.m. UTC
This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
Accumulated fbdev framebuffer changes are signaled using the callback
(struct drm_framebuffer_funcs *)->dirty()

The drm_fb_helper_sys_*() functions will accumulate changes and
schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
This worker is used by the deferred io mmap code to signal that it
has been collecting page faults. The page faults and/or other changes
are then merged into a drm_clip_rect and passed to the framebuffer
dirty() function.

The driver is responsible for setting up the fb_info.fbdefio structure
and calling fb_deferred_io_init() using the provided callback:
(struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_fb_helper.c | 189 +++++++++++++++++++++++++++++++++++++++-
 include/drm/drm_fb_helper.h     |  15 ++++
 2 files changed, 203 insertions(+), 1 deletion(-)

Comments

Daniel Vetter April 13, 2016, 10:57 a.m. UTC | #1
On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
> Accumulated fbdev framebuffer changes are signaled using the callback
> (struct drm_framebuffer_funcs *)->dirty()
> 
> The drm_fb_helper_sys_*() functions will accumulate changes and
> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
> This worker is used by the deferred io mmap code to signal that it
> has been collecting page faults. The page faults and/or other changes
> are then merged into a drm_clip_rect and passed to the framebuffer
> dirty() function.
> 
> The driver is responsible for setting up the fb_info.fbdefio structure
> and calling fb_deferred_io_init() using the provided callback:
> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  drivers/gpu/drm/drm_fb_helper.c | 189 +++++++++++++++++++++++++++++++++++++++-
>  include/drm/drm_fb_helper.h     |  15 ++++
>  2 files changed, 203 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index 1e103c4..30f3dfd 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -48,6 +48,100 @@ MODULE_PARM_DESC(fbdev_emulation,
>  
>  static LIST_HEAD(kernel_fb_helper_list);
>  
> +/*
> + * Where should I put these drm_clip_rect functions?
> + */

drm_rect.[hc] Some of them are there already but under different names
(e.g. intersect instead of sanitize).
-Daniel

> +
> +/**
> + * drm_clip_rect_reset - Reset clip
> + * @clip: clip rectangle
> + *
> + * Sets clip to {0,0,0,0}.
> + */
> +static inline void drm_clip_rect_reset(struct drm_clip_rect *clip)
> +{
> +	clip->x1 = 0;
> +	clip->x2 = 0;
> +	clip->y1 = 0;
> +	clip->y2 = 0;
> +}
> +
> +/**
> + * drm_clip_rect_is_empty - Is clip empty?
> + * @clip: clip rectangle
> + *
> + * Returns true if clip is {0,0,0,0}.
> + */
> +static inline bool drm_clip_rect_is_empty(struct drm_clip_rect *clip)
> +{
> +	return (!clip->x1 && !clip->x2 && !clip->y1 && !clip->y2);
> +}
> +
> +/**
> + * drm_clip_rect_sanetize - Make sure clip rectangle has sane values
> + * @clip: clip rectangle
> + * @width: maximum width of rectangle
> + * @height: maximum height of rectangle
> + *
> + * Makes sure the clip doesn't exceed the specified width and height and that
> + * x1 <= x2 and y1 <= y2.
> + */
> +void drm_clip_rect_sanetize(struct drm_clip_rect *clip, u32 width, u32 height)
> +{
> +	if (clip->x1 > clip->x2)
> +		swap(clip->x1, clip->x2);
> +	if (clip->y1 > clip->y2)
> +		swap(clip->y1, clip->y2);
> +
> +	clip->x1 = min_t(u32, clip->x1, width - 1);
> +	clip->x2 = min_t(u32, clip->x2, width - 1);
> +	clip->y1 = min_t(u32, clip->y1, height - 1);
> +	clip->y2 = min_t(u32, clip->y2, height - 1);
> +}
> +EXPORT_SYMBOL(drm_clip_rect_sanetize);
> +
> +/**
> + * drm_clip_rect_merge - Merge clip rectangles
> + * @dst: destination clip rectangle
> + * @src: source clip rectangle(s), can be NULL
> + * @num_clips: number of source clip rectangles
> + * @width: width of rectangle if @src is NULL
> + * @height: height of rectangle if @src is NULL
> + *
> + * The dirtyfb ioctl allows for a NULL clip to be passed in,
> + * so if @src is NULL, width and height is used to set a full clip.
> + * @dst takes part in the merge unless it is empty {0,0,0,0}.
> + */
> +void drm_clip_rect_merge(struct drm_clip_rect *dst,
> +			 struct drm_clip_rect *src, unsigned num_clips,
> +			 unsigned flags, u32 width, u32 height)
> +{
> +	int i;
> +
> +	if (!src || !num_clips) {
> +		dst->x1 = 0;
> +		dst->x2 = width - 1;
> +		dst->y1 = 0;
> +		dst->y2 = height - 1;
> +		return;
> +	}
> +
> +	if (drm_clip_rect_is_empty(dst)) {
> +		dst->x1 = ~0;
> +		dst->y1 = ~0;
> +	}
> +
> +	for (i = 0; i < num_clips; i++) {
> +		if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY)
> +			i++;
> +		dst->x1 = min(dst->x1, src[i].x1);
> +		dst->x2 = max(dst->x2, src[i].x2);
> +		dst->y1 = min(dst->y1, src[i].y1);
> +		dst->y2 = max(dst->y2, src[i].y2);
> +	}
> +}
> +EXPORT_SYMBOL(drm_clip_rect_merge);
> +
>  /**
>   * DOC: fbdev helpers
>   *
> @@ -410,6 +504,13 @@ static int restore_fbdev_mode(struct drm_fb_helper *fb_helper)
>  
>  	drm_warn_on_modeset_not_all_locked(dev);
>  
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	spin_lock(&fb_helper->dirty_lock);
> +	drm_clip_rect_merge(&fb_helper->dirty_clip, NULL, 0, 0,
> +			    fb_helper->fbdev->var.xres,
> +			    fb_helper->fbdev->var.yres);
> +	spin_unlock(&fb_helper->dirty_lock);
> +#endif
>  	if (fb_helper->atomic)
>  		return restore_fbdev_mode_atomic(fb_helper);
>  
> @@ -654,6 +755,9 @@ void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
>  			   const struct drm_fb_helper_funcs *funcs)
>  {
>  	INIT_LIST_HEAD(&helper->kernel_fb_list);
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	spin_lock_init(&helper->dirty_lock);
> +#endif
>  	helper->funcs = funcs;
>  	helper->dev = dev;
>  }
> @@ -838,6 +942,76 @@ void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  }
>  EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
>  
> +#ifdef CONFIG_FB_DEFERRED_IO
> +void drm_fb_helper_deferred_io(struct fb_info *info,
> +			       struct list_head *pagelist)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +	unsigned long start, end, min, max;
> +	struct drm_clip_rect clip;
> +	struct page *page;
> +
> +	if (!helper->fb->funcs->dirty)
> +		return;
> +
> +	spin_lock(&helper->dirty_lock);
> +	clip = helper->dirty_clip;
> +	drm_clip_rect_reset(&helper->dirty_clip);
> +	spin_unlock(&helper->dirty_lock);
> +
> +	min = ULONG_MAX;
> +	max = 0;
> +	list_for_each_entry(page, pagelist, lru) {
> +		start = page->index << PAGE_SHIFT;
> +		end = start + PAGE_SIZE - 1;
> +		min = min(min, start);
> +		max = max(max, end);
> +	}
> +
> +	if (min < max) {
> +		clip.x1 = 0;
> +		clip.x2 = info->var.xres - 1;
> +		clip.y1 = min / info->fix.line_length;
> +		clip.y2 = min_t(u32, max / info->fix.line_length,
> +				    info->var.yres - 1);
> +	} else if (drm_clip_rect_is_empty(&clip)) {
> +		clip.x1 = 0;
> +		clip.x2 = info->var.xres - 1;
> +		clip.y1 = 0;
> +		clip.y2 = info->var.yres - 1;
> +	}
> +
> +	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
> +}
> +EXPORT_SYMBOL(drm_fb_helper_deferred_io);
> +
> +static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
> +				       u32 width, u32 height)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +	struct drm_clip_rect clip;
> +
> +	if (!info->fbdefio)
> +		return;
> +
> +	clip.x1 = x;
> +	clip.x2 = x + width - 1;
> +	clip.y1 = y;
> +	clip.y2 = y + height - 1;
> +
> +	spin_lock(&helper->dirty_lock);
> +	drm_clip_rect_merge(&helper->dirty_clip, &clip, 1, 0, 0, 0);
> +	spin_unlock(&helper->dirty_lock);
> +
> +	schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
> +}
> +#else
> +static inline void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
> +					      u32 width, u32 height)
> +{
> +}
> +#endif
> +
>  /**
>   * drm_fb_helper_sys_read - wrapper around fb_sys_read
>   * @info: fb_info struct pointer
> @@ -866,7 +1040,14 @@ EXPORT_SYMBOL(drm_fb_helper_sys_read);
>  ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
>  				size_t count, loff_t *ppos)
>  {
> -	return fb_sys_write(info, buf, count, ppos);
> +	ssize_t ret;
> +
> +	ret = fb_sys_write(info, buf, count, ppos);
> +	if (ret > 0)
> +		drm_fb_helper_sys_deferred(info, 0, 0,
> +					   info->var.xres, info->var.yres);
> +
> +	return ret;
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_write);
>  
> @@ -881,6 +1062,8 @@ void drm_fb_helper_sys_fillrect(struct fb_info *info,
>  				const struct fb_fillrect *rect)
>  {
>  	sys_fillrect(info, rect);
> +	drm_fb_helper_sys_deferred(info, rect->dx, rect->dy,
> +				   rect->width, rect->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_fillrect);
>  
> @@ -895,6 +1078,8 @@ void drm_fb_helper_sys_copyarea(struct fb_info *info,
>  				const struct fb_copyarea *area)
>  {
>  	sys_copyarea(info, area);
> +	drm_fb_helper_sys_deferred(info, area->dx, area->dy,
> +				   area->width, area->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_copyarea);
>  
> @@ -909,6 +1094,8 @@ void drm_fb_helper_sys_imageblit(struct fb_info *info,
>  				 const struct fb_image *image)
>  {
>  	sys_imageblit(info, image);
> +	drm_fb_helper_sys_deferred(info, image->dx, image->dy,
> +				   image->width, image->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_imageblit);
>  
> diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
> index d8a40df..1daadc7 100644
> --- a/include/drm/drm_fb_helper.h
> +++ b/include/drm/drm_fb_helper.h
> @@ -172,6 +172,9 @@ struct drm_fb_helper_connector {
>   * @funcs: driver callbacks for fb helper
>   * @fbdev: emulated fbdev device info struct
>   * @pseudo_palette: fake palette of 16 colors
> + * @dirty_clip: clip rectangle used with deferred_io to accumulate damage to
> + *              the screen buffer
> + * @dirty_lock: spinlock protecting @dirty_clip
>   *
>   * This is the main structure used by the fbdev helpers. Drivers supporting
>   * fbdev emulation should embedded this into their overall driver structure.
> @@ -189,6 +192,10 @@ struct drm_fb_helper {
>  	const struct drm_fb_helper_funcs *funcs;
>  	struct fb_info *fbdev;
>  	u32 pseudo_palette[17];
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	struct drm_clip_rect dirty_clip;
> +	spinlock_t dirty_lock;
> +#endif
>  
>  	/**
>  	 * @kernel_fb_list:
> @@ -244,6 +251,9 @@ 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);
>  
> +void drm_fb_helper_deferred_io(struct fb_info *info,
> +			       struct list_head *pagelist);
> +
>  ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf,
>  			       size_t count, loff_t *ppos);
>  ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
> @@ -362,6 +372,11 @@ static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  {
>  }
>  
> +static inline void drm_fb_helper_deferred_io(struct fb_info *info,
> +					     struct list_head *pagelist)
> +{
> +}
> +
>  static inline ssize_t drm_fb_helper_sys_read(struct fb_info *info,
>  					     char __user *buf, size_t count,
>  					     loff_t *ppos)
> -- 
> 2.2.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Daniel Vetter April 13, 2016, 11:09 a.m. UTC | #2
On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
> Accumulated fbdev framebuffer changes are signaled using the callback
> (struct drm_framebuffer_funcs *)->dirty()
> 
> The drm_fb_helper_sys_*() functions will accumulate changes and
> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
> This worker is used by the deferred io mmap code to signal that it
> has been collecting page faults. The page faults and/or other changes
> are then merged into a drm_clip_rect and passed to the framebuffer
> dirty() function.
> 
> The driver is responsible for setting up the fb_info.fbdefio structure
> and calling fb_deferred_io_init() using the provided callback:
> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

For this one it'd be awesome to throw patches for qxl/udl on top to remove
their own hand-rolled implementations. Just to maximize the testing
coverage of this new code. Should be doable to set up a qxl virtual
machine quickly, but even without that we should be able to pull it in
(since it's mostly just about removing code from these two drivers).
-Daniel

> ---
>  drivers/gpu/drm/drm_fb_helper.c | 189 +++++++++++++++++++++++++++++++++++++++-
>  include/drm/drm_fb_helper.h     |  15 ++++
>  2 files changed, 203 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index 1e103c4..30f3dfd 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -48,6 +48,100 @@ MODULE_PARM_DESC(fbdev_emulation,
>  
>  static LIST_HEAD(kernel_fb_helper_list);
>  
> +/*
> + * Where should I put these drm_clip_rect functions?
> + */
> +
> +/**
> + * drm_clip_rect_reset - Reset clip
> + * @clip: clip rectangle
> + *
> + * Sets clip to {0,0,0,0}.
> + */
> +static inline void drm_clip_rect_reset(struct drm_clip_rect *clip)
> +{
> +	clip->x1 = 0;
> +	clip->x2 = 0;
> +	clip->y1 = 0;
> +	clip->y2 = 0;
> +}
> +
> +/**
> + * drm_clip_rect_is_empty - Is clip empty?
> + * @clip: clip rectangle
> + *
> + * Returns true if clip is {0,0,0,0}.
> + */
> +static inline bool drm_clip_rect_is_empty(struct drm_clip_rect *clip)
> +{
> +	return (!clip->x1 && !clip->x2 && !clip->y1 && !clip->y2);
> +}
> +
> +/**
> + * drm_clip_rect_sanetize - Make sure clip rectangle has sane values
> + * @clip: clip rectangle
> + * @width: maximum width of rectangle
> + * @height: maximum height of rectangle
> + *
> + * Makes sure the clip doesn't exceed the specified width and height and that
> + * x1 <= x2 and y1 <= y2.
> + */
> +void drm_clip_rect_sanetize(struct drm_clip_rect *clip, u32 width, u32 height)
> +{
> +	if (clip->x1 > clip->x2)
> +		swap(clip->x1, clip->x2);
> +	if (clip->y1 > clip->y2)
> +		swap(clip->y1, clip->y2);
> +
> +	clip->x1 = min_t(u32, clip->x1, width - 1);
> +	clip->x2 = min_t(u32, clip->x2, width - 1);
> +	clip->y1 = min_t(u32, clip->y1, height - 1);
> +	clip->y2 = min_t(u32, clip->y2, height - 1);
> +}
> +EXPORT_SYMBOL(drm_clip_rect_sanetize);
> +
> +/**
> + * drm_clip_rect_merge - Merge clip rectangles
> + * @dst: destination clip rectangle
> + * @src: source clip rectangle(s), can be NULL
> + * @num_clips: number of source clip rectangles
> + * @width: width of rectangle if @src is NULL
> + * @height: height of rectangle if @src is NULL
> + *
> + * The dirtyfb ioctl allows for a NULL clip to be passed in,
> + * so if @src is NULL, width and height is used to set a full clip.
> + * @dst takes part in the merge unless it is empty {0,0,0,0}.
> + */
> +void drm_clip_rect_merge(struct drm_clip_rect *dst,
> +			 struct drm_clip_rect *src, unsigned num_clips,
> +			 unsigned flags, u32 width, u32 height)
> +{
> +	int i;
> +
> +	if (!src || !num_clips) {
> +		dst->x1 = 0;
> +		dst->x2 = width - 1;
> +		dst->y1 = 0;
> +		dst->y2 = height - 1;
> +		return;
> +	}
> +
> +	if (drm_clip_rect_is_empty(dst)) {
> +		dst->x1 = ~0;
> +		dst->y1 = ~0;
> +	}
> +
> +	for (i = 0; i < num_clips; i++) {
> +		if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY)
> +			i++;
> +		dst->x1 = min(dst->x1, src[i].x1);
> +		dst->x2 = max(dst->x2, src[i].x2);
> +		dst->y1 = min(dst->y1, src[i].y1);
> +		dst->y2 = max(dst->y2, src[i].y2);
> +	}
> +}
> +EXPORT_SYMBOL(drm_clip_rect_merge);
> +
>  /**
>   * DOC: fbdev helpers
>   *
> @@ -410,6 +504,13 @@ static int restore_fbdev_mode(struct drm_fb_helper *fb_helper)
>  
>  	drm_warn_on_modeset_not_all_locked(dev);
>  
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	spin_lock(&fb_helper->dirty_lock);
> +	drm_clip_rect_merge(&fb_helper->dirty_clip, NULL, 0, 0,
> +			    fb_helper->fbdev->var.xres,
> +			    fb_helper->fbdev->var.yres);
> +	spin_unlock(&fb_helper->dirty_lock);
> +#endif
>  	if (fb_helper->atomic)
>  		return restore_fbdev_mode_atomic(fb_helper);
>  
> @@ -654,6 +755,9 @@ void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
>  			   const struct drm_fb_helper_funcs *funcs)
>  {
>  	INIT_LIST_HEAD(&helper->kernel_fb_list);
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	spin_lock_init(&helper->dirty_lock);
> +#endif
>  	helper->funcs = funcs;
>  	helper->dev = dev;
>  }
> @@ -838,6 +942,76 @@ void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  }
>  EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
>  
> +#ifdef CONFIG_FB_DEFERRED_IO
> +void drm_fb_helper_deferred_io(struct fb_info *info,
> +			       struct list_head *pagelist)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +	unsigned long start, end, min, max;
> +	struct drm_clip_rect clip;
> +	struct page *page;
> +
> +	if (!helper->fb->funcs->dirty)
> +		return;
> +
> +	spin_lock(&helper->dirty_lock);
> +	clip = helper->dirty_clip;
> +	drm_clip_rect_reset(&helper->dirty_clip);
> +	spin_unlock(&helper->dirty_lock);
> +
> +	min = ULONG_MAX;
> +	max = 0;
> +	list_for_each_entry(page, pagelist, lru) {
> +		start = page->index << PAGE_SHIFT;
> +		end = start + PAGE_SIZE - 1;
> +		min = min(min, start);
> +		max = max(max, end);
> +	}
> +
> +	if (min < max) {
> +		clip.x1 = 0;
> +		clip.x2 = info->var.xres - 1;
> +		clip.y1 = min / info->fix.line_length;
> +		clip.y2 = min_t(u32, max / info->fix.line_length,
> +				    info->var.yres - 1);
> +	} else if (drm_clip_rect_is_empty(&clip)) {
> +		clip.x1 = 0;
> +		clip.x2 = info->var.xres - 1;
> +		clip.y1 = 0;
> +		clip.y2 = info->var.yres - 1;
> +	}
> +
> +	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
> +}
> +EXPORT_SYMBOL(drm_fb_helper_deferred_io);
> +
> +static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
> +				       u32 width, u32 height)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +	struct drm_clip_rect clip;
> +
> +	if (!info->fbdefio)
> +		return;
> +
> +	clip.x1 = x;
> +	clip.x2 = x + width - 1;
> +	clip.y1 = y;
> +	clip.y2 = y + height - 1;
> +
> +	spin_lock(&helper->dirty_lock);
> +	drm_clip_rect_merge(&helper->dirty_clip, &clip, 1, 0, 0, 0);
> +	spin_unlock(&helper->dirty_lock);
> +
> +	schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
> +}
> +#else
> +static inline void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
> +					      u32 width, u32 height)
> +{
> +}
> +#endif
> +
>  /**
>   * drm_fb_helper_sys_read - wrapper around fb_sys_read
>   * @info: fb_info struct pointer
> @@ -866,7 +1040,14 @@ EXPORT_SYMBOL(drm_fb_helper_sys_read);
>  ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
>  				size_t count, loff_t *ppos)
>  {
> -	return fb_sys_write(info, buf, count, ppos);
> +	ssize_t ret;
> +
> +	ret = fb_sys_write(info, buf, count, ppos);
> +	if (ret > 0)
> +		drm_fb_helper_sys_deferred(info, 0, 0,
> +					   info->var.xres, info->var.yres);
> +
> +	return ret;
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_write);
>  
> @@ -881,6 +1062,8 @@ void drm_fb_helper_sys_fillrect(struct fb_info *info,
>  				const struct fb_fillrect *rect)
>  {
>  	sys_fillrect(info, rect);
> +	drm_fb_helper_sys_deferred(info, rect->dx, rect->dy,
> +				   rect->width, rect->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_fillrect);
>  
> @@ -895,6 +1078,8 @@ void drm_fb_helper_sys_copyarea(struct fb_info *info,
>  				const struct fb_copyarea *area)
>  {
>  	sys_copyarea(info, area);
> +	drm_fb_helper_sys_deferred(info, area->dx, area->dy,
> +				   area->width, area->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_copyarea);
>  
> @@ -909,6 +1094,8 @@ void drm_fb_helper_sys_imageblit(struct fb_info *info,
>  				 const struct fb_image *image)
>  {
>  	sys_imageblit(info, image);
> +	drm_fb_helper_sys_deferred(info, image->dx, image->dy,
> +				   image->width, image->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_imageblit);
>  
> diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
> index d8a40df..1daadc7 100644
> --- a/include/drm/drm_fb_helper.h
> +++ b/include/drm/drm_fb_helper.h
> @@ -172,6 +172,9 @@ struct drm_fb_helper_connector {
>   * @funcs: driver callbacks for fb helper
>   * @fbdev: emulated fbdev device info struct
>   * @pseudo_palette: fake palette of 16 colors
> + * @dirty_clip: clip rectangle used with deferred_io to accumulate damage to
> + *              the screen buffer
> + * @dirty_lock: spinlock protecting @dirty_clip
>   *
>   * This is the main structure used by the fbdev helpers. Drivers supporting
>   * fbdev emulation should embedded this into their overall driver structure.
> @@ -189,6 +192,10 @@ struct drm_fb_helper {
>  	const struct drm_fb_helper_funcs *funcs;
>  	struct fb_info *fbdev;
>  	u32 pseudo_palette[17];
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	struct drm_clip_rect dirty_clip;
> +	spinlock_t dirty_lock;
> +#endif
>  
>  	/**
>  	 * @kernel_fb_list:
> @@ -244,6 +251,9 @@ 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);
>  
> +void drm_fb_helper_deferred_io(struct fb_info *info,
> +			       struct list_head *pagelist);
> +
>  ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf,
>  			       size_t count, loff_t *ppos);
>  ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
> @@ -362,6 +372,11 @@ static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  {
>  }
>  
> +static inline void drm_fb_helper_deferred_io(struct fb_info *info,
> +					     struct list_head *pagelist)
> +{
> +}
> +
>  static inline ssize_t drm_fb_helper_sys_read(struct fb_info *info,
>  					     char __user *buf, size_t count,
>  					     loff_t *ppos)
> -- 
> 2.2.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Noralf Trønnes April 18, 2016, 3:15 p.m. UTC | #3
Den 13.04.2016 13:09, skrev Daniel Vetter:
> On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
>> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
>> Accumulated fbdev framebuffer changes are signaled using the callback
>> (struct drm_framebuffer_funcs *)->dirty()
>>
>> The drm_fb_helper_sys_*() functions will accumulate changes and
>> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
>> This worker is used by the deferred io mmap code to signal that it
>> has been collecting page faults. The page faults and/or other changes
>> are then merged into a drm_clip_rect and passed to the framebuffer
>> dirty() function.
>>
>> The driver is responsible for setting up the fb_info.fbdefio structure
>> and calling fb_deferred_io_init() using the provided callback:
>> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> For this one it'd be awesome to throw patches for qxl/udl on top to remove
> their own hand-rolled implementations. Just to maximize the testing
> coverage of this new code. Should be doable to set up a qxl virtual
> machine quickly, but even without that we should be able to pull it in
> (since it's mostly just about removing code from these two drivers).

There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and 
vmwgfx.

drivers/gpu/drm/vmwgfx
It doesn't use drm_fb_helper (uses the cfb_{fillrect,copyarea,imageblit}
functions directly). This made me think that I should probably add
fb_deferred_io support to drm_fb_helper_cfb_*() as well.

drivers/gpu/drm/udl
First of all it has had deferred io disabled by default since 2013
(commit 677d23b). Secondly it handles damage directly in it's
fb_{fillrect,copyarea,imageblit} functions, but defers to the next call if
it is in atomic context. My patch always defers those function to a worker.
The driver uses the drm_fb_helper_sys_* functions, so my patch would mess
it up if someone would enable deferred io (module_param: fb_defio).
So this driver isn't a good candidate for easy conversion also because it
has different code paths for fbdev mmap damage and the other damages
(although the code is similar). But it needs a patch to use the sys_*()
functions directly.

drivers/gpu/drm/qxl
This one uses a worker as a buffer between the mmap damage tracking and
fb_*() functions, and flushing of the changes. I'll give it a go.

Studying these in detail and looking at the git log was useful as it showed
me that the (struct fb_ops *)->fb_*() functions can be called in interrupt
context. This means that I need the irq version of spin_lock(). It also
validates my choice to defer these calls using the mmap defer damage worker
((struct fb_info).deferred_work), because it ensures that the dirty() call
will always run in process context.


Noralf.
Daniel Vetter April 20, 2016, 11:12 a.m. UTC | #4
On Mon, Apr 18, 2016 at 05:15:03PM +0200, Noralf Trønnes wrote:
> 
> Den 13.04.2016 13:09, skrev Daniel Vetter:
> >On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
> >>This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
> >>Accumulated fbdev framebuffer changes are signaled using the callback
> >>(struct drm_framebuffer_funcs *)->dirty()
> >>
> >>The drm_fb_helper_sys_*() functions will accumulate changes and
> >>schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
> >>This worker is used by the deferred io mmap code to signal that it
> >>has been collecting page faults. The page faults and/or other changes
> >>are then merged into a drm_clip_rect and passed to the framebuffer
> >>dirty() function.
> >>
> >>The driver is responsible for setting up the fb_info.fbdefio structure
> >>and calling fb_deferred_io_init() using the provided callback:
> >>(struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
> >>
> >>Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> >For this one it'd be awesome to throw patches for qxl/udl on top to remove
> >their own hand-rolled implementations. Just to maximize the testing
> >coverage of this new code. Should be doable to set up a qxl virtual
> >machine quickly, but even without that we should be able to pull it in
> >(since it's mostly just about removing code from these two drivers).
> 
> There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and
> vmwgfx.
> 
> drivers/gpu/drm/vmwgfx
> It doesn't use drm_fb_helper (uses the cfb_{fillrect,copyarea,imageblit}
> functions directly). This made me think that I should probably add
> fb_deferred_io support to drm_fb_helper_cfb_*() as well.

Yup, that one is special and can be ignored.

> drivers/gpu/drm/udl
> First of all it has had deferred io disabled by default since 2013
> (commit 677d23b). Secondly it handles damage directly in it's
> fb_{fillrect,copyarea,imageblit} functions, but defers to the next call if
> it is in atomic context. My patch always defers those function to a worker.
> The driver uses the drm_fb_helper_sys_* functions, so my patch would mess
> it up if someone would enable deferred io (module_param: fb_defio).
> So this driver isn't a good candidate for easy conversion also because it
> has different code paths for fbdev mmap damage and the other damages
> (although the code is similar). But it needs a patch to use the sys_*()
> functions directly.

Since it's disabled by default I'd just do a conversion. It should result
largely in deleting code and just using the fb helpers. What's special
with the mmap handling, and do we care?

> drivers/gpu/drm/qxl
> This one uses a worker as a buffer between the mmap damage tracking and
> fb_*() functions, and flushing of the changes. I'll give it a go.
> 
> Studying these in detail and looking at the git log was useful as it showed
> me that the (struct fb_ops *)->fb_*() functions can be called in interrupt
> context. This means that I need the irq version of spin_lock(). It also
> validates my choice to defer these calls using the mmap defer damage worker
> ((struct fb_info).deferred_work), because it ensures that the dirty() call
> will always run in process context.

Yeah, mostly I'd like to get qxl&udl converted so that all the lessons
learned in those implementations aren't lost. Great work digging out those
details ;-)

Thanks, Daniel
Noralf Trønnes April 20, 2016, 3:22 p.m. UTC | #5
Den 20.04.2016 13:12, skrev Daniel Vetter:
> On Mon, Apr 18, 2016 at 05:15:03PM +0200, Noralf Trønnes wrote:
>> Den 13.04.2016 13:09, skrev Daniel Vetter:
>>> On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
>>>> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
>>>> Accumulated fbdev framebuffer changes are signaled using the callback
>>>> (struct drm_framebuffer_funcs *)->dirty()
>>>>
>>>> The drm_fb_helper_sys_*() functions will accumulate changes and
>>>> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
>>>> This worker is used by the deferred io mmap code to signal that it
>>>> has been collecting page faults. The page faults and/or other changes
>>>> are then merged into a drm_clip_rect and passed to the framebuffer
>>>> dirty() function.
>>>>
>>>> The driver is responsible for setting up the fb_info.fbdefio structure
>>>> and calling fb_deferred_io_init() using the provided callback:
>>>> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>>>
>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>> For this one it'd be awesome to throw patches for qxl/udl on top to remove
>>> their own hand-rolled implementations. Just to maximize the testing
>>> coverage of this new code. Should be doable to set up a qxl virtual
>>> machine quickly, but even without that we should be able to pull it in
>>> (since it's mostly just about removing code from these two drivers).
>> There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and
>> vmwgfx.
>>
>> drivers/gpu/drm/vmwgfx
>> It doesn't use drm_fb_helper (uses the cfb_{fillrect,copyarea,imageblit}
>> functions directly). This made me think that I should probably add
>> fb_deferred_io support to drm_fb_helper_cfb_*() as well.
> Yup, that one is special and can be ignored.
>
>> drivers/gpu/drm/udl
>> First of all it has had deferred io disabled by default since 2013
>> (commit 677d23b). Secondly it handles damage directly in it's
>> fb_{fillrect,copyarea,imageblit} functions, but defers to the next call if
>> it is in atomic context. My patch always defers those function to a worker.
>> The driver uses the drm_fb_helper_sys_* functions, so my patch would mess
>> it up if someone would enable deferred io (module_param: fb_defio).
>> So this driver isn't a good candidate for easy conversion also because it
>> has different code paths for fbdev mmap damage and the other damages
>> (although the code is similar). But it needs a patch to use the sys_*()
>> functions directly.
> Since it's disabled by default I'd just do a conversion. It should result
> largely in deleting code and just using the fb helpers. What's special
> with the mmap handling, and do we care?

The git log mentions page list corruption, but I realised that I can
just disable the deferred mmap code and keep the rest.
Dave Airlie April 20, 2016, 10:29 p.m. UTC | #6
On 19 April 2016 at 01:15, Noralf Trønnes <noralf@tronnes.org> wrote:
>
> Den 13.04.2016 13:09, skrev Daniel Vetter:
>>
>> On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
>>>
>>> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
>>> Accumulated fbdev framebuffer changes are signaled using the callback
>>> (struct drm_framebuffer_funcs *)->dirty()
>>>
>>> The drm_fb_helper_sys_*() functions will accumulate changes and
>>> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
>>> This worker is used by the deferred io mmap code to signal that it
>>> has been collecting page faults. The page faults and/or other changes
>>> are then merged into a drm_clip_rect and passed to the framebuffer
>>> dirty() function.
>>>
>>> The driver is responsible for setting up the fb_info.fbdefio structure
>>> and calling fb_deferred_io_init() using the provided callback:
>>> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>>
>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>
>> For this one it'd be awesome to throw patches for qxl/udl on top to remove
>> their own hand-rolled implementations. Just to maximize the testing
>> coverage of this new code. Should be doable to set up a qxl virtual
>> machine quickly, but even without that we should be able to pull it in
>> (since it's mostly just about removing code from these two drivers).
>
>
> There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and
> vmwgfx.

So I'm a bit confused. For me fb defio is a thing which userspace users,
without an ioctl.

So we keep track in the kernel of dirty pages in the fb and then flush those
pages. Now the thing is that last time I tried this it interacted badly with
gem/ttm as defio wanted to do something to the same pages etc.

So I disabled it in udl for that reasons.

if we are talking about just having some damage tracking and not doing
the page level tracking then ignore me.

Dave.
Daniel Vetter April 21, 2016, 6:53 a.m. UTC | #7
On Thu, Apr 21, 2016 at 12:29 AM, Dave Airlie <airlied@gmail.com> wrote:
> On 19 April 2016 at 01:15, Noralf Trønnes <noralf@tronnes.org> wrote:
>>
>> Den 13.04.2016 13:09, skrev Daniel Vetter:
>>>
>>> On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
>>>>
>>>> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
>>>> Accumulated fbdev framebuffer changes are signaled using the callback
>>>> (struct drm_framebuffer_funcs *)->dirty()
>>>>
>>>> The drm_fb_helper_sys_*() functions will accumulate changes and
>>>> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
>>>> This worker is used by the deferred io mmap code to signal that it
>>>> has been collecting page faults. The page faults and/or other changes
>>>> are then merged into a drm_clip_rect and passed to the framebuffer
>>>> dirty() function.
>>>>
>>>> The driver is responsible for setting up the fb_info.fbdefio structure
>>>> and calling fb_deferred_io_init() using the provided callback:
>>>> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>>>
>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>>
>>> For this one it'd be awesome to throw patches for qxl/udl on top to remove
>>> their own hand-rolled implementations. Just to maximize the testing
>>> coverage of this new code. Should be doable to set up a qxl virtual
>>> machine quickly, but even without that we should be able to pull it in
>>> (since it's mostly just about removing code from these two drivers).
>>
>>
>> There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and
>> vmwgfx.
>
> So I'm a bit confused. For me fb defio is a thing which userspace users,
> without an ioctl.
>
> So we keep track in the kernel of dirty pages in the fb and then flush those
> pages. Now the thing is that last time I tried this it interacted badly with
> gem/ttm as defio wanted to do something to the same pages etc.
>
> So I disabled it in udl for that reasons.
>
> if we are talking about just having some damage tracking and not doing
> the page level tracking then ignore me.

Yeah I read through the code, and with shmem it'll die because shmem
and fb defio will fight over the page lru. But if you have your pages
backed by cma, or the fbdev mmap pointing at an mmio range I think it
should all work (and seems to at least for Noralf).

Either way it's all optional opt-in code (just shared) and drivers can
still opt to only do defio for in-kernel rendering and either have
broken fbdev mmap support (like udl today), or implement their own
defio mmap support compatible with shmem (not that hard either, but
meh). And Noralf's conversion patches should result in bug-for-bug
compatibility.

Cheers, Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index 1e103c4..30f3dfd 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -48,6 +48,100 @@  MODULE_PARM_DESC(fbdev_emulation,
 
 static LIST_HEAD(kernel_fb_helper_list);
 
+/*
+ * Where should I put these drm_clip_rect functions?
+ */
+
+/**
+ * drm_clip_rect_reset - Reset clip
+ * @clip: clip rectangle
+ *
+ * Sets clip to {0,0,0,0}.
+ */
+static inline void drm_clip_rect_reset(struct drm_clip_rect *clip)
+{
+	clip->x1 = 0;
+	clip->x2 = 0;
+	clip->y1 = 0;
+	clip->y2 = 0;
+}
+
+/**
+ * drm_clip_rect_is_empty - Is clip empty?
+ * @clip: clip rectangle
+ *
+ * Returns true if clip is {0,0,0,0}.
+ */
+static inline bool drm_clip_rect_is_empty(struct drm_clip_rect *clip)
+{
+	return (!clip->x1 && !clip->x2 && !clip->y1 && !clip->y2);
+}
+
+/**
+ * drm_clip_rect_sanetize - Make sure clip rectangle has sane values
+ * @clip: clip rectangle
+ * @width: maximum width of rectangle
+ * @height: maximum height of rectangle
+ *
+ * Makes sure the clip doesn't exceed the specified width and height and that
+ * x1 <= x2 and y1 <= y2.
+ */
+void drm_clip_rect_sanetize(struct drm_clip_rect *clip, u32 width, u32 height)
+{
+	if (clip->x1 > clip->x2)
+		swap(clip->x1, clip->x2);
+	if (clip->y1 > clip->y2)
+		swap(clip->y1, clip->y2);
+
+	clip->x1 = min_t(u32, clip->x1, width - 1);
+	clip->x2 = min_t(u32, clip->x2, width - 1);
+	clip->y1 = min_t(u32, clip->y1, height - 1);
+	clip->y2 = min_t(u32, clip->y2, height - 1);
+}
+EXPORT_SYMBOL(drm_clip_rect_sanetize);
+
+/**
+ * drm_clip_rect_merge - Merge clip rectangles
+ * @dst: destination clip rectangle
+ * @src: source clip rectangle(s), can be NULL
+ * @num_clips: number of source clip rectangles
+ * @width: width of rectangle if @src is NULL
+ * @height: height of rectangle if @src is NULL
+ *
+ * The dirtyfb ioctl allows for a NULL clip to be passed in,
+ * so if @src is NULL, width and height is used to set a full clip.
+ * @dst takes part in the merge unless it is empty {0,0,0,0}.
+ */
+void drm_clip_rect_merge(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *src, unsigned num_clips,
+			 unsigned flags, u32 width, u32 height)
+{
+	int i;
+
+	if (!src || !num_clips) {
+		dst->x1 = 0;
+		dst->x2 = width - 1;
+		dst->y1 = 0;
+		dst->y2 = height - 1;
+		return;
+	}
+
+	if (drm_clip_rect_is_empty(dst)) {
+		dst->x1 = ~0;
+		dst->y1 = ~0;
+	}
+
+	for (i = 0; i < num_clips; i++) {
+		if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY)
+			i++;
+		dst->x1 = min(dst->x1, src[i].x1);
+		dst->x2 = max(dst->x2, src[i].x2);
+		dst->y1 = min(dst->y1, src[i].y1);
+		dst->y2 = max(dst->y2, src[i].y2);
+	}
+}
+EXPORT_SYMBOL(drm_clip_rect_merge);
+
 /**
  * DOC: fbdev helpers
  *
@@ -410,6 +504,13 @@  static int restore_fbdev_mode(struct drm_fb_helper *fb_helper)
 
 	drm_warn_on_modeset_not_all_locked(dev);
 
+#ifdef CONFIG_FB_DEFERRED_IO
+	spin_lock(&fb_helper->dirty_lock);
+	drm_clip_rect_merge(&fb_helper->dirty_clip, NULL, 0, 0,
+			    fb_helper->fbdev->var.xres,
+			    fb_helper->fbdev->var.yres);
+	spin_unlock(&fb_helper->dirty_lock);
+#endif
 	if (fb_helper->atomic)
 		return restore_fbdev_mode_atomic(fb_helper);
 
@@ -654,6 +755,9 @@  void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
 			   const struct drm_fb_helper_funcs *funcs)
 {
 	INIT_LIST_HEAD(&helper->kernel_fb_list);
+#ifdef CONFIG_FB_DEFERRED_IO
+	spin_lock_init(&helper->dirty_lock);
+#endif
 	helper->funcs = funcs;
 	helper->dev = dev;
 }
@@ -838,6 +942,76 @@  void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
 }
 EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
 
+#ifdef CONFIG_FB_DEFERRED_IO
+void drm_fb_helper_deferred_io(struct fb_info *info,
+			       struct list_head *pagelist)
+{
+	struct drm_fb_helper *helper = info->par;
+	unsigned long start, end, min, max;
+	struct drm_clip_rect clip;
+	struct page *page;
+
+	if (!helper->fb->funcs->dirty)
+		return;
+
+	spin_lock(&helper->dirty_lock);
+	clip = helper->dirty_clip;
+	drm_clip_rect_reset(&helper->dirty_clip);
+	spin_unlock(&helper->dirty_lock);
+
+	min = ULONG_MAX;
+	max = 0;
+	list_for_each_entry(page, pagelist, lru) {
+		start = page->index << PAGE_SHIFT;
+		end = start + PAGE_SIZE - 1;
+		min = min(min, start);
+		max = max(max, end);
+	}
+
+	if (min < max) {
+		clip.x1 = 0;
+		clip.x2 = info->var.xres - 1;
+		clip.y1 = min / info->fix.line_length;
+		clip.y2 = min_t(u32, max / info->fix.line_length,
+				    info->var.yres - 1);
+	} else if (drm_clip_rect_is_empty(&clip)) {
+		clip.x1 = 0;
+		clip.x2 = info->var.xres - 1;
+		clip.y1 = 0;
+		clip.y2 = info->var.yres - 1;
+	}
+
+	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
+}
+EXPORT_SYMBOL(drm_fb_helper_deferred_io);
+
+static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
+				       u32 width, u32 height)
+{
+	struct drm_fb_helper *helper = info->par;
+	struct drm_clip_rect clip;
+
+	if (!info->fbdefio)
+		return;
+
+	clip.x1 = x;
+	clip.x2 = x + width - 1;
+	clip.y1 = y;
+	clip.y2 = y + height - 1;
+
+	spin_lock(&helper->dirty_lock);
+	drm_clip_rect_merge(&helper->dirty_clip, &clip, 1, 0, 0, 0);
+	spin_unlock(&helper->dirty_lock);
+
+	schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
+}
+#else
+static inline void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
+					      u32 width, u32 height)
+{
+}
+#endif
+
 /**
  * drm_fb_helper_sys_read - wrapper around fb_sys_read
  * @info: fb_info struct pointer
@@ -866,7 +1040,14 @@  EXPORT_SYMBOL(drm_fb_helper_sys_read);
 ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
 				size_t count, loff_t *ppos)
 {
-	return fb_sys_write(info, buf, count, ppos);
+	ssize_t ret;
+
+	ret = fb_sys_write(info, buf, count, ppos);
+	if (ret > 0)
+		drm_fb_helper_sys_deferred(info, 0, 0,
+					   info->var.xres, info->var.yres);
+
+	return ret;
 }
 EXPORT_SYMBOL(drm_fb_helper_sys_write);
 
@@ -881,6 +1062,8 @@  void drm_fb_helper_sys_fillrect(struct fb_info *info,
 				const struct fb_fillrect *rect)
 {
 	sys_fillrect(info, rect);
+	drm_fb_helper_sys_deferred(info, rect->dx, rect->dy,
+				   rect->width, rect->height);
 }
 EXPORT_SYMBOL(drm_fb_helper_sys_fillrect);
 
@@ -895,6 +1078,8 @@  void drm_fb_helper_sys_copyarea(struct fb_info *info,
 				const struct fb_copyarea *area)
 {
 	sys_copyarea(info, area);
+	drm_fb_helper_sys_deferred(info, area->dx, area->dy,
+				   area->width, area->height);
 }
 EXPORT_SYMBOL(drm_fb_helper_sys_copyarea);
 
@@ -909,6 +1094,8 @@  void drm_fb_helper_sys_imageblit(struct fb_info *info,
 				 const struct fb_image *image)
 {
 	sys_imageblit(info, image);
+	drm_fb_helper_sys_deferred(info, image->dx, image->dy,
+				   image->width, image->height);
 }
 EXPORT_SYMBOL(drm_fb_helper_sys_imageblit);
 
diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
index d8a40df..1daadc7 100644
--- a/include/drm/drm_fb_helper.h
+++ b/include/drm/drm_fb_helper.h
@@ -172,6 +172,9 @@  struct drm_fb_helper_connector {
  * @funcs: driver callbacks for fb helper
  * @fbdev: emulated fbdev device info struct
  * @pseudo_palette: fake palette of 16 colors
+ * @dirty_clip: clip rectangle used with deferred_io to accumulate damage to
+ *              the screen buffer
+ * @dirty_lock: spinlock protecting @dirty_clip
  *
  * This is the main structure used by the fbdev helpers. Drivers supporting
  * fbdev emulation should embedded this into their overall driver structure.
@@ -189,6 +192,10 @@  struct drm_fb_helper {
 	const struct drm_fb_helper_funcs *funcs;
 	struct fb_info *fbdev;
 	u32 pseudo_palette[17];
+#ifdef CONFIG_FB_DEFERRED_IO
+	struct drm_clip_rect dirty_clip;
+	spinlock_t dirty_lock;
+#endif
 
 	/**
 	 * @kernel_fb_list:
@@ -244,6 +251,9 @@  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);
 
+void drm_fb_helper_deferred_io(struct fb_info *info,
+			       struct list_head *pagelist);
+
 ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf,
 			       size_t count, loff_t *ppos);
 ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
@@ -362,6 +372,11 @@  static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
 {
 }
 
+static inline void drm_fb_helper_deferred_io(struct fb_info *info,
+					     struct list_head *pagelist)
+{
+}
+
 static inline ssize_t drm_fb_helper_sys_read(struct fb_info *info,
 					     char __user *buf, size_t count,
 					     loff_t *ppos)