[8/9] drm/vmwgfx: Implement an infrastructure for read-coherent resources
diff mbox series

Message ID 20190412160338.64994-9-thellstrom@vmware.com
State New
Headers show
Series
  • Emulated coherent graphics memory
Related show

Commit Message

Thomas Hellstrom April 12, 2019, 4:04 p.m. UTC
Similar to write-coherent resources, make sure that from the user-space
point of view, GPU rendered contents is automatically available for
reading by the CPU.

Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
---
 drivers/gpu/drm/ttm/ttm_bo_vm.c               |   1 +
 drivers/gpu/drm/vmwgfx/vmwgfx_drv.h           |   8 +-
 drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c    |  69 +++++++++++-
 drivers/gpu/drm/vmwgfx/vmwgfx_resource.c      | 102 +++++++++++++++++-
 drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h |   2 +
 drivers/gpu/drm/vmwgfx/vmwgfx_validation.c    |   3 +-
 6 files changed, 176 insertions(+), 9 deletions(-)

Comments

Deepak Singh Rawat April 22, 2019, 8:12 p.m. UTC | #1
Minor nits below, otherwise

Reviewed-by: Deepak Rawat <drawat@vmware.com>

On Fri, 2019-04-12 at 09:04 -0700, Thomas Hellstrom wrote:
> Similar to write-coherent resources, make sure that from the user-
> space
> point of view, GPU rendered contents is automatically available for
> reading by the CPU.
> 
> Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
> ---
>  drivers/gpu/drm/ttm/ttm_bo_vm.c               |   1 +
>  drivers/gpu/drm/vmwgfx/vmwgfx_drv.h           |   8 +-
>  drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c    |  69 +++++++++++-
>  drivers/gpu/drm/vmwgfx/vmwgfx_resource.c      | 102
> +++++++++++++++++-
>  drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h |   2 +
>  drivers/gpu/drm/vmwgfx/vmwgfx_validation.c    |   3 +-
>  6 files changed, 176 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c
> b/drivers/gpu/drm/ttm/ttm_bo_vm.c
> index 3bd28fb97124..0065b138f450 100644
> --- a/drivers/gpu/drm/ttm/ttm_bo_vm.c
> +++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c
> @@ -42,6 +42,7 @@
>  #include <linux/uaccess.h>
>  #include <linux/mem_encrypt.h>
>  
> +
>  static vm_fault_t ttm_bo_vm_fault_idle(struct ttm_buffer_object *bo,
>  				struct vm_fault *vmf)
>  {
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
> b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
> index 81ebcd668038..00794415335e 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
> @@ -96,6 +96,7 @@ struct vmw_fpriv {
>   * @map: Kmap object for semi-persistent mappings
>   * @res_prios: Eviction priority counts for attached resources
>   * @dirty: structure for user-space dirty-tracking
> + * @cleaning: Current validation sequence is cleaning.
>   */
>  struct vmw_buffer_object {
>  	struct ttm_buffer_object base;
> @@ -690,7 +691,8 @@ extern void vmw_resource_unreference(struct
> vmw_resource **p_res);
>  extern struct vmw_resource *vmw_resource_reference(struct
> vmw_resource *res);
>  extern struct vmw_resource *
>  vmw_resource_reference_unless_doomed(struct vmw_resource *res);
> -extern int vmw_resource_validate(struct vmw_resource *res, bool
> intr);
> +extern int vmw_resource_validate(struct vmw_resource *res, bool
> intr,
> +				 bool dirtying);
>  extern int vmw_resource_reserve(struct vmw_resource *res, bool
> interruptible,
>  				bool no_backup);
>  extern bool vmw_resource_needs_backup(const struct vmw_resource
> *res);
> @@ -734,6 +736,8 @@ void vmw_resource_mob_attach(struct vmw_resource
> *res);
>  void vmw_resource_mob_detach(struct vmw_resource *res);
>  void vmw_resource_dirty_update(struct vmw_resource *res, pgoff_t
> start,
>  			       pgoff_t end);
> +int vmw_resources_clean(struct vmw_buffer_object *vbo, pgoff_t
> start,
> +			pgoff_t end, pgoff_t *num_prefault);
>  
>  /**
>   * vmw_resource_mob_attached - Whether a resource currently has a
> mob attached
> @@ -1428,6 +1432,8 @@ int vmw_bo_dirty_add(struct vmw_buffer_object
> *vbo);
>  void vmw_bo_dirty_transfer_to_res(struct vmw_resource *res);
>  void vmw_bo_dirty_clear_res(struct vmw_resource *res);
>  void vmw_bo_dirty_release(struct vmw_buffer_object *vbo);
> +void vmw_bo_dirty_unmap(struct vmw_buffer_object *vbo,
> +			pgoff_t start, pgoff_t end);
>  vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf);
>  vm_fault_t vmw_bo_vm_mkwrite(struct vm_fault *vmf);
>  
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> index 87e4a73b1175..773ff30a4b60 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> @@ -153,7 +153,6 @@ static void vmw_bo_dirty_scan_mkwrite(struct
> vmw_buffer_object *vbo)
>  	}
>  }
>  
> -
>  /**
>   * vmw_bo_dirty_scan - Scan for dirty pages and add them to the
> dirty
>   * tracking structure
> @@ -171,6 +170,51 @@ void vmw_bo_dirty_scan(struct vmw_buffer_object
> *vbo)
>  		vmw_bo_dirty_scan_mkwrite(vbo);
>  }
>  
> +/**
> + * vmw_bo_dirty_pre_unmap - write-protect and pick up dirty pages
> before
> + * an unmap_mapping_range operation.
> + * @vbo: The buffer object,
> + * @start: First page of the range within the buffer object.
> + * @end: Last page of the range within the buffer object + 1.
> + *
> + * If we're using the _PAGETABLE scan method, we may leak dirty
> pages
> + * when calling unmap_mapping_range(). This function makes sure we
> pick
> + * up all dirty pages.
> + */
> +static void vmw_bo_dirty_pre_unmap(struct vmw_buffer_object *vbo,
> +				   pgoff_t start, pgoff_t end)
> +{
> +	struct vmw_bo_dirty *dirty = vbo->dirty;
> +	unsigned long offset = drm_vma_node_start(&vbo->base.vma_node);
> +	struct address_space *mapping = vbo->base.bdev->dev_mapping;
> +
> +	if (dirty->method != VMW_BO_DIRTY_PAGETABLE || start >= end)
> +		return;
> +
> +	apply_as_wrprotect(mapping, start + offset, end - start);
> +	apply_as_clean(mapping, start + offset, end - start, offset,
> +		       &dirty->bitmap[0], &dirty->start, &dirty->end);
> +}
> +
> +/**
> + * vmw_bo_dirty_unmap - Clear all ptes pointing to a range within a
> bo
> + * @vbo: The buffer object,
> + * @start: First page of the range within the buffer object.
> + * @end: Last page of the range within the buffer object + 1.
> + *
> + * This is similar to ttm_bo_unmap_virtual_locked() except it takes
> a subrange.
> + */
> +void vmw_bo_dirty_unmap(struct vmw_buffer_object *vbo,
> +			pgoff_t start, pgoff_t end)
> +{
> +	unsigned long offset = drm_vma_node_start(&vbo->base.vma_node);
> +	struct address_space *mapping = vbo->base.bdev->dev_mapping;
> +
> +	vmw_bo_dirty_pre_unmap(vbo, start, end);
> +	unmap_shared_mapping_range(mapping, (offset + start) <<
> PAGE_SHIFT,
> +				   (loff_t) (end - start) <<
> PAGE_SHIFT);
> +}
> +
>  /**
>   * vmw_bo_dirty_add - Add a dirty-tracking user to a buffer object
>   * @vbo: The buffer object
> @@ -392,6 +436,26 @@ vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf)
>  	if (ret)
>  		return ret;
>  
> +	num_prefault = (vma->vm_flags & VM_RAND_READ) ? 1 :
> +		TTM_BO_VM_NUM_PREFAULT;
> +
> +	if (vbo->dirty) {
> +		pgoff_t allowed_prefault;
> +		unsigned long page_offset;
> +
> +		page_offset = vmf->pgoff - drm_vma_node_start(&bo-
> >vma_node);
> +		if (page_offset >= bo->num_pages ||
> +		    vmw_resources_clean(vbo, page_offset,
> +					page_offset + PAGE_SIZE,
> +					&allowed_prefault)) {
> +			ret = VM_FAULT_SIGBUS;
> +			goto out_unlock;
> +		}
> +
> +		num_prefault = min(num_prefault, allowed_prefault);
> +	}
> +
> +

Extra space

>  	/*
>  	 * This will cause mkwrite() to be called for each pte on
>  	 * write-enable vmas.
> @@ -399,12 +463,11 @@ vm_fault_t vmw_bo_vm_fault(struct vm_fault
> *vmf)
>  	if (vbo->dirty && vbo->dirty->method == VMW_BO_DIRTY_MKWRITE)
>  		cvma.vm_flags &= ~VM_WRITE;
>  
> -	num_prefault = (vma->vm_flags & VM_RAND_READ) ? 0 :
> -		TTM_BO_VM_NUM_PREFAULT;
>  	ret = ttm_bo_vm_fault_reserved(vmf, &cvma, num_prefault);
>  	if (ret == VM_FAULT_RETRY && !(vmf->flags &
> FAULT_FLAG_RETRY_NOWAIT))
>  		return ret;
>  
> +out_unlock:
>  	reservation_object_unlock(bo->resv);
>  	return ret;
>  }
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
> b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
> index ff9fe5650468..30367cb06143 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
> @@ -395,7 +395,8 @@ static int vmw_resource_buf_alloc(struct
> vmw_resource *res,
>   * should be retried once resources have been freed up.
>   */
>  static int vmw_resource_do_validate(struct vmw_resource *res,
> -				    struct ttm_validate_buffer
> *val_buf)
> +				    struct ttm_validate_buffer
> *val_buf,
> +				    bool dirtying)
>  {
>  	int ret = 0;
>  	const struct vmw_res_func *func = res->func;
> @@ -437,6 +438,15 @@ static int vmw_resource_do_validate(struct
> vmw_resource *res,
>  	 * the resource.
>  	 */
>  	if (res->dirty) {
> +		if (dirtying && !res->res_dirty) {
> +			pgoff_t start = res->backup_offset >>
> PAGE_SHIFT;
> +			pgoff_t end = __KERNEL_DIV_ROUND_UP
> +				(res->backup_offset + res->backup_size,
> +				 PAGE_SIZE);
> +
> +			vmw_bo_dirty_unmap(res->backup, start, end);
> +		}
> +
>  		vmw_bo_dirty_transfer_to_res(res);
>  		return func->dirty_sync(res);
>  	}
> @@ -680,6 +690,7 @@ static int vmw_resource_do_evict(struct
> ww_acquire_ctx *ticket,
>   *                         to the device.
>   * @res: The resource to make visible to the device.
>   * @intr: Perform waits interruptible if possible.
> + * @dirtying: Pending GPU operation will dirty the resource
>   *
>   * On succesful return, any backup DMA buffer pointed to by @res-
> >backup will
>   * be reserved and validated.
> @@ -689,7 +700,8 @@ static int vmw_resource_do_evict(struct
> ww_acquire_ctx *ticket,
>   * Return: Zero on success, -ERESTARTSYS if interrupted, negative
> error code
>   * on failure.
>   */
> -int vmw_resource_validate(struct vmw_resource *res, bool intr)
> +int vmw_resource_validate(struct vmw_resource *res, bool intr,
> +			  bool dirtying)
>  {
>  	int ret;
>  	struct vmw_resource *evict_res;
> @@ -706,7 +718,7 @@ int vmw_resource_validate(struct vmw_resource
> *res, bool intr)
>  	if (res->backup)
>  		val_buf.bo = &res->backup->base;
>  	do {
> -		ret = vmw_resource_do_validate(res, &val_buf);
> +		ret = vmw_resource_do_validate(res, &val_buf,
> dirtying);
>  		if (likely(ret != -EBUSY))
>  			break;
>  
> @@ -1006,7 +1018,7 @@ int vmw_resource_pin(struct vmw_resource *res,
> bool interruptible)
>  			/* Do we really need to pin the MOB as well? */
>  			vmw_bo_pin_reserved(vbo, true);
>  		}
> -		ret = vmw_resource_validate(res, interruptible);
> +		ret = vmw_resource_validate(res, interruptible, true);
>  		if (vbo)
>  			ttm_bo_unreserve(&vbo->base);
>  		if (ret)
> @@ -1081,3 +1093,85 @@ void vmw_resource_dirty_update(struct
> vmw_resource *res, pgoff_t start,
>  		res->func->dirty_range_add(res, start << PAGE_SHIFT,
>  					   end << PAGE_SHIFT);
>  }
> +
> +/**
> + * vmw_resources_clean - Clean resources intersecting a mob range
> + * @res_tree: Tree of resources attached to the mob

This doesn't match function signature

> + * @start: The mob page offset starting the range
> + * @end: The mob page offset ending the range
> + * @num_prefault: Returns how many pages including the first have
> been
> + * cleaned and are ok to prefault
> + */
> +int vmw_resources_clean(struct vmw_buffer_object *vbo, pgoff_t
> start,
> +			pgoff_t end, pgoff_t *num_prefault)
> +{
> +	struct rb_node *cur = vbo->res_tree.rb_node;
> +	struct vmw_resource *found = NULL;
> +	unsigned long res_start = start << PAGE_SHIFT;
> +	unsigned long res_end = end << PAGE_SHIFT;
> +	unsigned long last_cleaned = 0;
> +
> +	/*
> +	 * Find the resource with lowest backup_offset that intersects
> the
> +	 * range.
> +	 */
> +	while (cur) {
> +		struct vmw_resource *cur_res =
> +			container_of(cur, struct vmw_resource,
> mob_node);
> +
> +		if (cur_res->backup_offset >= res_end) {
> +			cur = cur->rb_left;
> +		} else if (cur_res->backup_offset + cur_res-
> >backup_size <=
> +			   res_start) {
> +			cur = cur->rb_right;
> +		} else {
> +			found = cur_res;

I didn't looked into how RB tree works but do you need to break the
loop when resource is found?

> +			cur = cur->rb_left;
> +		}
> +	}
> +
> +	/*
> +	 * In order of increasing backup_offset, clean dirty resorces
> +	 * intersecting the range.
> +	 */
> +	while (found) {
> +		if (found->res_dirty) {
> +			int ret;
> +
> +			if (!found->func->clean)
> +				return -EINVAL;
> +
> +			ret = found->func->clean(found);
> +			if (ret)
> +				return ret;
> +
> +			found->res_dirty = false;
> +		}
> +		last_cleaned = found->backup_offset + found-
> >backup_size;
> +		cur = rb_next(&found->mob_node);
> +		if (!cur)
> +			break;
> +
> +		found = container_of(cur, struct vmw_resource,
> mob_node);
> +		if (found->backup_offset >= res_end)
> +			break;
> +	}
> +
> +	/*
> +	 * Set number of pages allowed prefaulting and fence the buffer
> object
> +	 */
> +	*num_prefault = 1;
> +	if (last_cleaned > res_start) {
> +		struct ttm_buffer_object *bo = &vbo->base;
> +
> +		*num_prefault = __KERNEL_DIV_ROUND_UP(last_cleaned -
> res_start,
> +						      PAGE_SIZE);
> +		vmw_bo_fence_single(bo, NULL);
> +		if (bo->moving)
> +			dma_fence_put(bo->moving);
> +		bo->moving = dma_fence_get
> +			(reservation_object_get_excl(bo->resv));
> +	}
> +
> +	return 0;
> +}
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h
> b/drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h
> index c85144286cfe..3b7438b2d289 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h
> @@ -77,6 +77,7 @@ struct vmw_user_resource_conv {
>   * @dirty_sync:        Upload the dirty mob contents to the
> resource.
>   * @dirty_add_range:   Add a sequential dirty range to the resource
>   *                     dirty tracker.
> + * @clean:             Clean the resource.
>   */
>  struct vmw_res_func {
>  	enum vmw_res_type res_type;
> @@ -101,6 +102,7 @@ struct vmw_res_func {
>  	int (*dirty_sync)(struct vmw_resource *res);
>  	void (*dirty_range_add)(struct vmw_resource *res, size_t start,
>  				 size_t end);
> +	int (*clean)(struct vmw_resource *res);
>  };
>  
>  /**
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
> b/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
> index 5b0c928bb5ba..81d9d7adc055 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
> @@ -644,7 +644,8 @@ int vmw_validation_res_validate(struct
> vmw_validation_context *ctx, bool intr)
>  		struct vmw_resource *res = val->res;
>  		struct vmw_buffer_object *backup = res->backup;
>  
> -		ret = vmw_resource_validate(res, intr);
> +		ret = vmw_resource_validate(res, intr, val->dirty_set
> &&
> +					    val->dirty);
>  		if (ret) {
>  			if (ret != -ERESTARTSYS)
>  				DRM_ERROR("Failed to validate
> resource.\n");
> -- 
> 2.20.1
>
Thomas Hellstrom April 24, 2019, 9:26 a.m. UTC | #2
On Mon, 2019-04-22 at 20:12 +0000, Deepak Singh Rawat wrote:
> Minor nits below, otherwise
> 
> Reviewed-by: Deepak Rawat <drawat@vmware.com>
> 
> On Fri, 2019-04-12 at 09:04 -0700, Thomas Hellstrom wrote:
> > Similar to write-coherent resources, make sure that from the user-
> > space
> > point of view, GPU rendered contents is automatically available for
> > reading by the CPU.
> > 
> > Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
> > ---
> > 
> > +	while (cur) {
> > +		struct vmw_resource *cur_res =
> > +			container_of(cur, struct vmw_resource,
> > mob_node);
> > +
> > +		if (cur_res->backup_offset >= res_end) {
> > +			cur = cur->rb_left;
> > +		} else if (cur_res->backup_offset + cur_res-
> > > backup_size <=
> > +			   res_start) {
> > +			cur = cur->rb_right;
> > +		} else {
> > +			found = cur_res;
> 
> I didn't looked into how RB tree works but do you need to break the
> loop when resource is found?


No, here we will continue looking for a resource with even lower
starting offset. I'll add a comment about that.

Thanks,
Thomas

Patch
diff mbox series

diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c b/drivers/gpu/drm/ttm/ttm_bo_vm.c
index 3bd28fb97124..0065b138f450 100644
--- a/drivers/gpu/drm/ttm/ttm_bo_vm.c
+++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c
@@ -42,6 +42,7 @@ 
 #include <linux/uaccess.h>
 #include <linux/mem_encrypt.h>
 
+
 static vm_fault_t ttm_bo_vm_fault_idle(struct ttm_buffer_object *bo,
 				struct vm_fault *vmf)
 {
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
index 81ebcd668038..00794415335e 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
@@ -96,6 +96,7 @@  struct vmw_fpriv {
  * @map: Kmap object for semi-persistent mappings
  * @res_prios: Eviction priority counts for attached resources
  * @dirty: structure for user-space dirty-tracking
+ * @cleaning: Current validation sequence is cleaning.
  */
 struct vmw_buffer_object {
 	struct ttm_buffer_object base;
@@ -690,7 +691,8 @@  extern void vmw_resource_unreference(struct vmw_resource **p_res);
 extern struct vmw_resource *vmw_resource_reference(struct vmw_resource *res);
 extern struct vmw_resource *
 vmw_resource_reference_unless_doomed(struct vmw_resource *res);
-extern int vmw_resource_validate(struct vmw_resource *res, bool intr);
+extern int vmw_resource_validate(struct vmw_resource *res, bool intr,
+				 bool dirtying);
 extern int vmw_resource_reserve(struct vmw_resource *res, bool interruptible,
 				bool no_backup);
 extern bool vmw_resource_needs_backup(const struct vmw_resource *res);
@@ -734,6 +736,8 @@  void vmw_resource_mob_attach(struct vmw_resource *res);
 void vmw_resource_mob_detach(struct vmw_resource *res);
 void vmw_resource_dirty_update(struct vmw_resource *res, pgoff_t start,
 			       pgoff_t end);
+int vmw_resources_clean(struct vmw_buffer_object *vbo, pgoff_t start,
+			pgoff_t end, pgoff_t *num_prefault);
 
 /**
  * vmw_resource_mob_attached - Whether a resource currently has a mob attached
@@ -1428,6 +1432,8 @@  int vmw_bo_dirty_add(struct vmw_buffer_object *vbo);
 void vmw_bo_dirty_transfer_to_res(struct vmw_resource *res);
 void vmw_bo_dirty_clear_res(struct vmw_resource *res);
 void vmw_bo_dirty_release(struct vmw_buffer_object *vbo);
+void vmw_bo_dirty_unmap(struct vmw_buffer_object *vbo,
+			pgoff_t start, pgoff_t end);
 vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf);
 vm_fault_t vmw_bo_vm_mkwrite(struct vm_fault *vmf);
 
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
index 87e4a73b1175..773ff30a4b60 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
@@ -153,7 +153,6 @@  static void vmw_bo_dirty_scan_mkwrite(struct vmw_buffer_object *vbo)
 	}
 }
 
-
 /**
  * vmw_bo_dirty_scan - Scan for dirty pages and add them to the dirty
  * tracking structure
@@ -171,6 +170,51 @@  void vmw_bo_dirty_scan(struct vmw_buffer_object *vbo)
 		vmw_bo_dirty_scan_mkwrite(vbo);
 }
 
+/**
+ * vmw_bo_dirty_pre_unmap - write-protect and pick up dirty pages before
+ * an unmap_mapping_range operation.
+ * @vbo: The buffer object,
+ * @start: First page of the range within the buffer object.
+ * @end: Last page of the range within the buffer object + 1.
+ *
+ * If we're using the _PAGETABLE scan method, we may leak dirty pages
+ * when calling unmap_mapping_range(). This function makes sure we pick
+ * up all dirty pages.
+ */
+static void vmw_bo_dirty_pre_unmap(struct vmw_buffer_object *vbo,
+				   pgoff_t start, pgoff_t end)
+{
+	struct vmw_bo_dirty *dirty = vbo->dirty;
+	unsigned long offset = drm_vma_node_start(&vbo->base.vma_node);
+	struct address_space *mapping = vbo->base.bdev->dev_mapping;
+
+	if (dirty->method != VMW_BO_DIRTY_PAGETABLE || start >= end)
+		return;
+
+	apply_as_wrprotect(mapping, start + offset, end - start);
+	apply_as_clean(mapping, start + offset, end - start, offset,
+		       &dirty->bitmap[0], &dirty->start, &dirty->end);
+}
+
+/**
+ * vmw_bo_dirty_unmap - Clear all ptes pointing to a range within a bo
+ * @vbo: The buffer object,
+ * @start: First page of the range within the buffer object.
+ * @end: Last page of the range within the buffer object + 1.
+ *
+ * This is similar to ttm_bo_unmap_virtual_locked() except it takes a subrange.
+ */
+void vmw_bo_dirty_unmap(struct vmw_buffer_object *vbo,
+			pgoff_t start, pgoff_t end)
+{
+	unsigned long offset = drm_vma_node_start(&vbo->base.vma_node);
+	struct address_space *mapping = vbo->base.bdev->dev_mapping;
+
+	vmw_bo_dirty_pre_unmap(vbo, start, end);
+	unmap_shared_mapping_range(mapping, (offset + start) << PAGE_SHIFT,
+				   (loff_t) (end - start) << PAGE_SHIFT);
+}
+
 /**
  * vmw_bo_dirty_add - Add a dirty-tracking user to a buffer object
  * @vbo: The buffer object
@@ -392,6 +436,26 @@  vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf)
 	if (ret)
 		return ret;
 
+	num_prefault = (vma->vm_flags & VM_RAND_READ) ? 1 :
+		TTM_BO_VM_NUM_PREFAULT;
+
+	if (vbo->dirty) {
+		pgoff_t allowed_prefault;
+		unsigned long page_offset;
+
+		page_offset = vmf->pgoff - drm_vma_node_start(&bo->vma_node);
+		if (page_offset >= bo->num_pages ||
+		    vmw_resources_clean(vbo, page_offset,
+					page_offset + PAGE_SIZE,
+					&allowed_prefault)) {
+			ret = VM_FAULT_SIGBUS;
+			goto out_unlock;
+		}
+
+		num_prefault = min(num_prefault, allowed_prefault);
+	}
+
+
 	/*
 	 * This will cause mkwrite() to be called for each pte on
 	 * write-enable vmas.
@@ -399,12 +463,11 @@  vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf)
 	if (vbo->dirty && vbo->dirty->method == VMW_BO_DIRTY_MKWRITE)
 		cvma.vm_flags &= ~VM_WRITE;
 
-	num_prefault = (vma->vm_flags & VM_RAND_READ) ? 0 :
-		TTM_BO_VM_NUM_PREFAULT;
 	ret = ttm_bo_vm_fault_reserved(vmf, &cvma, num_prefault);
 	if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
 		return ret;
 
+out_unlock:
 	reservation_object_unlock(bo->resv);
 	return ret;
 }
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
index ff9fe5650468..30367cb06143 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
@@ -395,7 +395,8 @@  static int vmw_resource_buf_alloc(struct vmw_resource *res,
  * should be retried once resources have been freed up.
  */
 static int vmw_resource_do_validate(struct vmw_resource *res,
-				    struct ttm_validate_buffer *val_buf)
+				    struct ttm_validate_buffer *val_buf,
+				    bool dirtying)
 {
 	int ret = 0;
 	const struct vmw_res_func *func = res->func;
@@ -437,6 +438,15 @@  static int vmw_resource_do_validate(struct vmw_resource *res,
 	 * the resource.
 	 */
 	if (res->dirty) {
+		if (dirtying && !res->res_dirty) {
+			pgoff_t start = res->backup_offset >> PAGE_SHIFT;
+			pgoff_t end = __KERNEL_DIV_ROUND_UP
+				(res->backup_offset + res->backup_size,
+				 PAGE_SIZE);
+
+			vmw_bo_dirty_unmap(res->backup, start, end);
+		}
+
 		vmw_bo_dirty_transfer_to_res(res);
 		return func->dirty_sync(res);
 	}
@@ -680,6 +690,7 @@  static int vmw_resource_do_evict(struct ww_acquire_ctx *ticket,
  *                         to the device.
  * @res: The resource to make visible to the device.
  * @intr: Perform waits interruptible if possible.
+ * @dirtying: Pending GPU operation will dirty the resource
  *
  * On succesful return, any backup DMA buffer pointed to by @res->backup will
  * be reserved and validated.
@@ -689,7 +700,8 @@  static int vmw_resource_do_evict(struct ww_acquire_ctx *ticket,
  * Return: Zero on success, -ERESTARTSYS if interrupted, negative error code
  * on failure.
  */
-int vmw_resource_validate(struct vmw_resource *res, bool intr)
+int vmw_resource_validate(struct vmw_resource *res, bool intr,
+			  bool dirtying)
 {
 	int ret;
 	struct vmw_resource *evict_res;
@@ -706,7 +718,7 @@  int vmw_resource_validate(struct vmw_resource *res, bool intr)
 	if (res->backup)
 		val_buf.bo = &res->backup->base;
 	do {
-		ret = vmw_resource_do_validate(res, &val_buf);
+		ret = vmw_resource_do_validate(res, &val_buf, dirtying);
 		if (likely(ret != -EBUSY))
 			break;
 
@@ -1006,7 +1018,7 @@  int vmw_resource_pin(struct vmw_resource *res, bool interruptible)
 			/* Do we really need to pin the MOB as well? */
 			vmw_bo_pin_reserved(vbo, true);
 		}
-		ret = vmw_resource_validate(res, interruptible);
+		ret = vmw_resource_validate(res, interruptible, true);
 		if (vbo)
 			ttm_bo_unreserve(&vbo->base);
 		if (ret)
@@ -1081,3 +1093,85 @@  void vmw_resource_dirty_update(struct vmw_resource *res, pgoff_t start,
 		res->func->dirty_range_add(res, start << PAGE_SHIFT,
 					   end << PAGE_SHIFT);
 }
+
+/**
+ * vmw_resources_clean - Clean resources intersecting a mob range
+ * @res_tree: Tree of resources attached to the mob
+ * @start: The mob page offset starting the range
+ * @end: The mob page offset ending the range
+ * @num_prefault: Returns how many pages including the first have been
+ * cleaned and are ok to prefault
+ */
+int vmw_resources_clean(struct vmw_buffer_object *vbo, pgoff_t start,
+			pgoff_t end, pgoff_t *num_prefault)
+{
+	struct rb_node *cur = vbo->res_tree.rb_node;
+	struct vmw_resource *found = NULL;
+	unsigned long res_start = start << PAGE_SHIFT;
+	unsigned long res_end = end << PAGE_SHIFT;
+	unsigned long last_cleaned = 0;
+
+	/*
+	 * Find the resource with lowest backup_offset that intersects the
+	 * range.
+	 */
+	while (cur) {
+		struct vmw_resource *cur_res =
+			container_of(cur, struct vmw_resource, mob_node);
+
+		if (cur_res->backup_offset >= res_end) {
+			cur = cur->rb_left;
+		} else if (cur_res->backup_offset + cur_res->backup_size <=
+			   res_start) {
+			cur = cur->rb_right;
+		} else {
+			found = cur_res;
+			cur = cur->rb_left;
+		}
+	}
+
+	/*
+	 * In order of increasing backup_offset, clean dirty resorces
+	 * intersecting the range.
+	 */
+	while (found) {
+		if (found->res_dirty) {
+			int ret;
+
+			if (!found->func->clean)
+				return -EINVAL;
+
+			ret = found->func->clean(found);
+			if (ret)
+				return ret;
+
+			found->res_dirty = false;
+		}
+		last_cleaned = found->backup_offset + found->backup_size;
+		cur = rb_next(&found->mob_node);
+		if (!cur)
+			break;
+
+		found = container_of(cur, struct vmw_resource, mob_node);
+		if (found->backup_offset >= res_end)
+			break;
+	}
+
+	/*
+	 * Set number of pages allowed prefaulting and fence the buffer object
+	 */
+	*num_prefault = 1;
+	if (last_cleaned > res_start) {
+		struct ttm_buffer_object *bo = &vbo->base;
+
+		*num_prefault = __KERNEL_DIV_ROUND_UP(last_cleaned - res_start,
+						      PAGE_SIZE);
+		vmw_bo_fence_single(bo, NULL);
+		if (bo->moving)
+			dma_fence_put(bo->moving);
+		bo->moving = dma_fence_get
+			(reservation_object_get_excl(bo->resv));
+	}
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h
index c85144286cfe..3b7438b2d289 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource_priv.h
@@ -77,6 +77,7 @@  struct vmw_user_resource_conv {
  * @dirty_sync:        Upload the dirty mob contents to the resource.
  * @dirty_add_range:   Add a sequential dirty range to the resource
  *                     dirty tracker.
+ * @clean:             Clean the resource.
  */
 struct vmw_res_func {
 	enum vmw_res_type res_type;
@@ -101,6 +102,7 @@  struct vmw_res_func {
 	int (*dirty_sync)(struct vmw_resource *res);
 	void (*dirty_range_add)(struct vmw_resource *res, size_t start,
 				 size_t end);
+	int (*clean)(struct vmw_resource *res);
 };
 
 /**
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c b/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
index 5b0c928bb5ba..81d9d7adc055 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c
@@ -644,7 +644,8 @@  int vmw_validation_res_validate(struct vmw_validation_context *ctx, bool intr)
 		struct vmw_resource *res = val->res;
 		struct vmw_buffer_object *backup = res->backup;
 
-		ret = vmw_resource_validate(res, intr);
+		ret = vmw_resource_validate(res, intr, val->dirty_set &&
+					    val->dirty);
 		if (ret) {
 			if (ret != -ERESTARTSYS)
 				DRM_ERROR("Failed to validate resource.\n");