Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d58e3b08 authored by Thomas Hellstrom's avatar Thomas Hellstrom
Browse files

drm/vmwgfx: Implement an infrastructure for read-coherent resources



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: default avatarThomas Hellstrom <thellstrom@vmware.com>
Reviewed-by: default avatarDeepak Rawat <drawat@vmware.com>
parent 86aeaa09
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -689,7 +689,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);
@@ -733,6 +734,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
@@ -1427,6 +1430,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);

+68 −5
Original line number Diff line number Diff line
@@ -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
@@ -389,21 +433,40 @@ 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.
	 * If we don't track dirty using the MKWRITE method, make sure
	 * sure the page protection is write-enabled so we don't get
	 * a lot of unnecessary write faults.
	 */
	if (vbo->dirty && vbo->dirty->method == VMW_BO_DIRTY_MKWRITE)
		prot = vma->vm_page_prot;
	else
		prot = vm_get_page_prot(vma->vm_flags);

	num_prefault = (vma->vm_flags & VM_RAND_READ) ? 0 :
		TTM_BO_VM_NUM_PREFAULT;
	ret = ttm_bo_vm_fault_reserved(vmf, prot, num_prefault);
	if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
		return ret;

out_unlock:
	reservation_object_unlock(bo->resv);
	return ret;
}
+99 −4
Original line number Diff line number Diff line
@@ -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);
	}
@@ -681,6 +691,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.
@@ -690,7 +701,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;
@@ -707,7 +719,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;

@@ -1007,7 +1019,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)
@@ -1082,3 +1094,86 @@ 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
 * @vbo: The mob buffer object
 * @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;
			/* Continue to look for resources with lower offsets */
		}
	}

	/*
	 * 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;
}
+2 −0
Original line number Diff line number Diff line
@@ -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);
};

/**
+2 −1
Original line number Diff line number Diff line
@@ -641,7 +641,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");