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. Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Matthew Wilcox <willy@infradead.org> Cc: Will Deacon <will.deacon@arm.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Rik van Riel <riel@surriel.com> Cc: Minchan Kim <minchan@kernel.org> Cc: Michal Hocko <mhocko@suse.com> Cc: Huang Ying <ying.huang@intel.com> Cc: Jérôme Glisse <jglisse@redhat.com> Cc: Kirill A. Shutemov <kirill@shutemov.name> Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com> Reviewed-by: Deepak Rawat <drawat@vmware.com>
This commit is contained in:
parent
61335d7a5a
commit
fb80edb0d7
|
@ -684,7 +684,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);
|
||||
|
@ -728,6 +729,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
|
||||
|
@ -1421,6 +1424,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);
|
||||
|
||||
|
|
|
@ -155,7 +155,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
|
||||
|
@ -173,6 +172,53 @@ 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.base.vma_node);
|
||||
struct address_space *mapping = vbo->base.bdev->dev_mapping;
|
||||
|
||||
if (dirty->method != VMW_BO_DIRTY_PAGETABLE || start >= end)
|
||||
return;
|
||||
|
||||
wp_shared_mapping_range(mapping, start + offset, end - start);
|
||||
clean_record_shared_mapping_range(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.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
|
||||
|
@ -401,21 +447,42 @@ 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->base.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:
|
||||
dma_resv_unlock(bo->base.resv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -393,7 +393,8 @@ out_no_bo:
|
|||
* 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;
|
||||
|
@ -435,6 +436,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);
|
||||
}
|
||||
|
@ -678,6 +688,7 @@ out_no_unbind:
|
|||
* 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.
|
||||
|
@ -687,7 +698,8 @@ out_no_unbind:
|
|||
* 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;
|
||||
|
@ -704,7 +716,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;
|
||||
|
||||
|
@ -1004,7 +1016,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)
|
||||
|
@ -1079,3 +1091,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
|
||||
(dma_resv_get_excl(bo->base.resv));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue