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: Thomas Hellstrom <thellstrom@vmware.com>
Reviewed-by: Deepak Rawat <drawat@vmware.com>
This commit is contained in:
Thomas Hellstrom 2019-03-28 11:36:25 +01:00
parent 86aeaa09f3
commit d58e3b087b
5 changed files with 177 additions and 11 deletions

View File

@ -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(struct vmw_resource *res);
extern struct vmw_resource * extern struct vmw_resource *
vmw_resource_reference_unless_doomed(struct vmw_resource *res); 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, extern int vmw_resource_reserve(struct vmw_resource *res, bool interruptible,
bool no_backup); bool no_backup);
extern bool vmw_resource_needs_backup(const struct vmw_resource *res); 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_mob_detach(struct vmw_resource *res);
void vmw_resource_dirty_update(struct vmw_resource *res, pgoff_t start, void vmw_resource_dirty_update(struct vmw_resource *res, pgoff_t start,
pgoff_t end); 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 * 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_transfer_to_res(struct vmw_resource *res);
void vmw_bo_dirty_clear_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_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_fault(struct vm_fault *vmf);
vm_fault_t vmw_bo_vm_mkwrite(struct vm_fault *vmf); vm_fault_t vmw_bo_vm_mkwrite(struct vm_fault *vmf);

View File

@ -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 * vmw_bo_dirty_scan - Scan for dirty pages and add them to the dirty
* tracking structure * 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_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 * vmw_bo_dirty_add - Add a dirty-tracking user to a buffer object
* @vbo: The buffer object * @vbo: The buffer object
@ -389,21 +433,40 @@ vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf)
if (ret) if (ret)
return 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 * If we don't track dirty using the MKWRITE method, make sure
* write-enable vmas. * 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) if (vbo->dirty && vbo->dirty->method == VMW_BO_DIRTY_MKWRITE)
prot = vma->vm_page_prot; prot = vma->vm_page_prot;
else else
prot = vm_get_page_prot(vma->vm_flags); 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); ret = ttm_bo_vm_fault_reserved(vmf, prot, num_prefault);
if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT)) if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
return ret; return ret;
out_unlock:
reservation_object_unlock(bo->resv); reservation_object_unlock(bo->resv);
return ret; return ret;
} }

View File

@ -395,7 +395,8 @@ static int vmw_resource_buf_alloc(struct vmw_resource *res,
* should be retried once resources have been freed up. * should be retried once resources have been freed up.
*/ */
static int vmw_resource_do_validate(struct vmw_resource *res, 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; int ret = 0;
const struct vmw_res_func *func = res->func; const struct vmw_res_func *func = res->func;
@ -437,6 +438,15 @@ static int vmw_resource_do_validate(struct vmw_resource *res,
* the resource. * the resource.
*/ */
if (res->dirty) { 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); vmw_bo_dirty_transfer_to_res(res);
return func->dirty_sync(res); return func->dirty_sync(res);
} }
@ -681,6 +691,7 @@ static int vmw_resource_do_evict(struct ww_acquire_ctx *ticket,
* to the device. * to the device.
* @res: The resource to make visible to the device. * @res: The resource to make visible to the device.
* @intr: Perform waits interruptible if possible. * @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 * On succesful return, any backup DMA buffer pointed to by @res->backup will
* be reserved and validated. * 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 * Return: Zero on success, -ERESTARTSYS if interrupted, negative error code
* on failure. * 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; int ret;
struct vmw_resource *evict_res; struct vmw_resource *evict_res;
@ -707,7 +719,7 @@ int vmw_resource_validate(struct vmw_resource *res, bool intr)
if (res->backup) if (res->backup)
val_buf.bo = &res->backup->base; val_buf.bo = &res->backup->base;
do { do {
ret = vmw_resource_do_validate(res, &val_buf); ret = vmw_resource_do_validate(res, &val_buf, dirtying);
if (likely(ret != -EBUSY)) if (likely(ret != -EBUSY))
break; 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? */ /* Do we really need to pin the MOB as well? */
vmw_bo_pin_reserved(vbo, true); vmw_bo_pin_reserved(vbo, true);
} }
ret = vmw_resource_validate(res, interruptible); ret = vmw_resource_validate(res, interruptible, true);
if (vbo) if (vbo)
ttm_bo_unreserve(&vbo->base); ttm_bo_unreserve(&vbo->base);
if (ret) 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, res->func->dirty_range_add(res, start << PAGE_SHIFT,
end << 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;
}

View File

@ -77,6 +77,7 @@ struct vmw_user_resource_conv {
* @dirty_sync: Upload the dirty mob contents to the resource. * @dirty_sync: Upload the dirty mob contents to the resource.
* @dirty_add_range: Add a sequential dirty range to the resource * @dirty_add_range: Add a sequential dirty range to the resource
* dirty tracker. * dirty tracker.
* @clean: Clean the resource.
*/ */
struct vmw_res_func { struct vmw_res_func {
enum vmw_res_type res_type; enum vmw_res_type res_type;
@ -101,6 +102,7 @@ struct vmw_res_func {
int (*dirty_sync)(struct vmw_resource *res); int (*dirty_sync)(struct vmw_resource *res);
void (*dirty_range_add)(struct vmw_resource *res, size_t start, void (*dirty_range_add)(struct vmw_resource *res, size_t start,
size_t end); size_t end);
int (*clean)(struct vmw_resource *res);
}; };
/** /**

View File

@ -641,7 +641,8 @@ int vmw_validation_res_validate(struct vmw_validation_context *ctx, bool intr)
struct vmw_resource *res = val->res; struct vmw_resource *res = val->res;
struct vmw_buffer_object *backup = res->backup; 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) {
if (ret != -ERESTARTSYS) if (ret != -ERESTARTSYS)
DRM_ERROR("Failed to validate resource.\n"); DRM_ERROR("Failed to validate resource.\n");