git-subtree-dir: qcom/opensource/graphics-kernel git-subtree-mainline:992813d9c1
git-subtree-split:b4fdc4c042
Change-Id: repo: https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/graphics-kernel tag: GRAPHICS.LA.14.0.r1-07700-lanai.0
733 lines
18 KiB
C
733 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/file.h>
|
|
#include <linux/interval_tree.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/sync_file.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "kgsl_device.h"
|
|
#include "kgsl_mmu.h"
|
|
#include "kgsl_reclaim.h"
|
|
#include "kgsl_sharedmem.h"
|
|
#include "kgsl_trace.h"
|
|
|
|
struct kgsl_memdesc_bind_range {
|
|
struct kgsl_mem_entry *entry;
|
|
struct interval_tree_node range;
|
|
};
|
|
|
|
static struct kgsl_memdesc_bind_range *bind_to_range(struct interval_tree_node *node)
|
|
{
|
|
return container_of(node, struct kgsl_memdesc_bind_range, range);
|
|
}
|
|
|
|
static struct kgsl_memdesc_bind_range *bind_range_create(u64 start, u64 last,
|
|
struct kgsl_mem_entry *entry)
|
|
{
|
|
struct kgsl_memdesc_bind_range *range =
|
|
kzalloc(sizeof(*range), GFP_KERNEL);
|
|
|
|
if (!range)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
range->range.start = start;
|
|
range->range.last = last;
|
|
range->entry = kgsl_mem_entry_get(entry);
|
|
|
|
if (!range->entry) {
|
|
kfree(range);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
atomic_inc(&entry->vbo_count);
|
|
return range;
|
|
}
|
|
|
|
static void bind_range_destroy(struct kgsl_memdesc_bind_range *range)
|
|
{
|
|
struct kgsl_mem_entry *entry = range->entry;
|
|
|
|
atomic_dec(&entry->vbo_count);
|
|
kgsl_mem_entry_put(entry);
|
|
kfree(range);
|
|
}
|
|
|
|
static u64 bind_range_len(struct kgsl_memdesc_bind_range *range)
|
|
{
|
|
return (range->range.last - range->range.start) + 1;
|
|
}
|
|
|
|
void kgsl_memdesc_print_vbo_ranges(struct kgsl_mem_entry *entry,
|
|
struct seq_file *s)
|
|
{
|
|
struct interval_tree_node *next;
|
|
struct kgsl_memdesc *memdesc = &entry->memdesc;
|
|
|
|
if (!(memdesc->flags & KGSL_MEMFLAGS_VBO))
|
|
return;
|
|
|
|
/*
|
|
* We are called in an atomic context so try to get the mutex but if we
|
|
* don't then skip this item
|
|
*/
|
|
if (!mutex_trylock(&memdesc->ranges_lock))
|
|
return;
|
|
|
|
next = interval_tree_iter_first(&memdesc->ranges, 0, ~0UL);
|
|
while (next) {
|
|
struct kgsl_memdesc_bind_range *range = bind_to_range(next);
|
|
|
|
seq_printf(s, "%5d %5d 0x%16.16lx-0x%16.16lx\n",
|
|
entry->id, range->entry->id, range->range.start,
|
|
range->range.last);
|
|
|
|
next = interval_tree_iter_next(next, 0, ~0UL);
|
|
}
|
|
|
|
mutex_unlock(&memdesc->ranges_lock);
|
|
}
|
|
|
|
static void kgsl_memdesc_remove_range(struct kgsl_mem_entry *target,
|
|
u64 start, u64 last, struct kgsl_mem_entry *entry)
|
|
{
|
|
struct interval_tree_node *node, *next;
|
|
struct kgsl_memdesc_bind_range *range;
|
|
struct kgsl_memdesc *memdesc = &target->memdesc;
|
|
|
|
mutex_lock(&memdesc->ranges_lock);
|
|
|
|
next = interval_tree_iter_first(&memdesc->ranges, start, last);
|
|
while (next) {
|
|
node = next;
|
|
range = bind_to_range(node);
|
|
next = interval_tree_iter_next(node, start, last);
|
|
|
|
/*
|
|
* If entry is null, consider it as a special request. Unbind
|
|
* the entire range between start and last in this case.
|
|
*/
|
|
if (!entry || range->entry->id == entry->id) {
|
|
if (kgsl_mmu_unmap_range(memdesc->pagetable,
|
|
memdesc, range->range.start, bind_range_len(range)))
|
|
continue;
|
|
|
|
interval_tree_remove(node, &memdesc->ranges);
|
|
trace_kgsl_mem_remove_bind_range(target,
|
|
range->range.start, range->entry,
|
|
bind_range_len(range));
|
|
|
|
if (!(memdesc->flags & KGSL_MEMFLAGS_VBO_NO_MAP_ZERO))
|
|
kgsl_mmu_map_zero_page_to_range(memdesc->pagetable,
|
|
memdesc, range->range.start, bind_range_len(range));
|
|
|
|
bind_range_destroy(range);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&memdesc->ranges_lock);
|
|
}
|
|
|
|
static int kgsl_memdesc_add_range(struct kgsl_mem_entry *target,
|
|
u64 start, u64 last, struct kgsl_mem_entry *entry, u64 offset)
|
|
{
|
|
struct interval_tree_node *node, *next;
|
|
struct kgsl_memdesc *memdesc = &target->memdesc;
|
|
struct kgsl_memdesc_bind_range *range =
|
|
bind_range_create(start, last, entry);
|
|
int ret = 0;
|
|
|
|
if (IS_ERR(range))
|
|
return PTR_ERR(range);
|
|
|
|
mutex_lock(&memdesc->ranges_lock);
|
|
|
|
/*
|
|
* If the VBO maps the zero page, then we can unmap the requested range
|
|
* in one call. Otherwise we have to figure out what ranges to unmap
|
|
* while walking the interval tree.
|
|
*/
|
|
if (!(memdesc->flags & KGSL_MEMFLAGS_VBO_NO_MAP_ZERO)) {
|
|
ret = kgsl_mmu_unmap_range(memdesc->pagetable, memdesc, start,
|
|
last - start + 1);
|
|
if (ret)
|
|
goto error;
|
|
}
|
|
|
|
next = interval_tree_iter_first(&memdesc->ranges, start, last);
|
|
|
|
while (next) {
|
|
struct kgsl_memdesc_bind_range *cur;
|
|
|
|
node = next;
|
|
cur = bind_to_range(node);
|
|
next = interval_tree_iter_next(node, start, last);
|
|
|
|
trace_kgsl_mem_remove_bind_range(target, cur->range.start,
|
|
cur->entry, bind_range_len(cur));
|
|
|
|
interval_tree_remove(node, &memdesc->ranges);
|
|
|
|
if (start <= cur->range.start) {
|
|
if (last >= cur->range.last) {
|
|
/* Unmap the entire cur range */
|
|
if (memdesc->flags & KGSL_MEMFLAGS_VBO_NO_MAP_ZERO) {
|
|
ret = kgsl_mmu_unmap_range(memdesc->pagetable, memdesc,
|
|
cur->range.start,
|
|
cur->range.last - cur->range.start + 1);
|
|
if (ret) {
|
|
interval_tree_insert(node, &memdesc->ranges);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
bind_range_destroy(cur);
|
|
continue;
|
|
}
|
|
|
|
/* Unmap the range overlapping cur */
|
|
if (memdesc->flags & KGSL_MEMFLAGS_VBO_NO_MAP_ZERO) {
|
|
ret = kgsl_mmu_unmap_range(memdesc->pagetable, memdesc,
|
|
cur->range.start,
|
|
last - cur->range.start + 1);
|
|
if (ret) {
|
|
interval_tree_insert(node, &memdesc->ranges);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Adjust the start of the mapping */
|
|
cur->range.start = last + 1;
|
|
/* And put it back into the tree */
|
|
interval_tree_insert(node, &memdesc->ranges);
|
|
|
|
trace_kgsl_mem_add_bind_range(target,
|
|
cur->range.start, cur->entry, bind_range_len(cur));
|
|
} else {
|
|
if (last < cur->range.last) {
|
|
struct kgsl_memdesc_bind_range *temp;
|
|
|
|
/*
|
|
* The range is split into two so make a new
|
|
* entry for the far side
|
|
*/
|
|
temp = bind_range_create(last + 1, cur->range.last,
|
|
cur->entry);
|
|
/* FIXME: Uhoh, this would be bad */
|
|
BUG_ON(IS_ERR(temp));
|
|
|
|
interval_tree_insert(&temp->range,
|
|
&memdesc->ranges);
|
|
|
|
trace_kgsl_mem_add_bind_range(target,
|
|
temp->range.start,
|
|
temp->entry, bind_range_len(temp));
|
|
}
|
|
|
|
/* Unmap the range overlapping cur */
|
|
if (memdesc->flags & KGSL_MEMFLAGS_VBO_NO_MAP_ZERO) {
|
|
ret = kgsl_mmu_unmap_range(memdesc->pagetable, memdesc,
|
|
start,
|
|
min_t(u64, cur->range.last, last) - start + 1);
|
|
if (ret) {
|
|
interval_tree_insert(node, &memdesc->ranges);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
cur->range.last = start - 1;
|
|
interval_tree_insert(node, &memdesc->ranges);
|
|
|
|
trace_kgsl_mem_add_bind_range(target, cur->range.start,
|
|
cur->entry, bind_range_len(cur));
|
|
}
|
|
}
|
|
|
|
ret = kgsl_mmu_map_child(memdesc->pagetable, memdesc, start,
|
|
&entry->memdesc, offset, last - start + 1);
|
|
if (ret)
|
|
goto error;
|
|
|
|
/* Add the new range */
|
|
interval_tree_insert(&range->range, &memdesc->ranges);
|
|
|
|
trace_kgsl_mem_add_bind_range(target, range->range.start,
|
|
range->entry, bind_range_len(range));
|
|
mutex_unlock(&memdesc->ranges_lock);
|
|
|
|
return ret;
|
|
|
|
error:
|
|
bind_range_destroy(range);
|
|
mutex_unlock(&memdesc->ranges_lock);
|
|
return ret;
|
|
}
|
|
|
|
static void kgsl_sharedmem_vbo_put_gpuaddr(struct kgsl_memdesc *memdesc)
|
|
{
|
|
struct interval_tree_node *node, *next;
|
|
struct kgsl_memdesc_bind_range *range;
|
|
int ret = 0;
|
|
bool unmap_fail;
|
|
|
|
/*
|
|
* If the VBO maps the zero range then we can unmap the entire
|
|
* pagetable region in one call.
|
|
*/
|
|
if (!(memdesc->flags & KGSL_MEMFLAGS_VBO_NO_MAP_ZERO))
|
|
ret = kgsl_mmu_unmap_range(memdesc->pagetable, memdesc,
|
|
0, memdesc->size);
|
|
|
|
unmap_fail = ret;
|
|
|
|
/*
|
|
* FIXME: do we have a use after free potential here? We might need to
|
|
* lock this and set a "do not update" bit
|
|
*/
|
|
|
|
/* Now delete each range and release the mem entries */
|
|
next = interval_tree_iter_first(&memdesc->ranges, 0, ~0UL);
|
|
|
|
while (next) {
|
|
node = next;
|
|
range = bind_to_range(node);
|
|
next = interval_tree_iter_next(node, 0, ~0UL);
|
|
|
|
interval_tree_remove(node, &memdesc->ranges);
|
|
|
|
/* Unmap this range */
|
|
if (memdesc->flags & KGSL_MEMFLAGS_VBO_NO_MAP_ZERO)
|
|
ret = kgsl_mmu_unmap_range(memdesc->pagetable, memdesc,
|
|
range->range.start,
|
|
range->range.last - range->range.start + 1);
|
|
|
|
/* Put the child's refcount if unmap succeeds */
|
|
if (!ret)
|
|
bind_range_destroy(range);
|
|
else
|
|
kfree(range);
|
|
|
|
unmap_fail = unmap_fail || ret;
|
|
}
|
|
|
|
if (unmap_fail)
|
|
return;
|
|
|
|
/* Put back the GPU address */
|
|
kgsl_mmu_put_gpuaddr(memdesc->pagetable, memdesc);
|
|
|
|
memdesc->gpuaddr = 0;
|
|
memdesc->pagetable = NULL;
|
|
}
|
|
|
|
static struct kgsl_memdesc_ops kgsl_vbo_ops = {
|
|
.put_gpuaddr = kgsl_sharedmem_vbo_put_gpuaddr,
|
|
};
|
|
|
|
int kgsl_sharedmem_allocate_vbo(struct kgsl_device *device,
|
|
struct kgsl_memdesc *memdesc, u64 size, u64 flags)
|
|
{
|
|
size = PAGE_ALIGN(size);
|
|
|
|
/* Make sure that VBOs are supported by the MMU */
|
|
if (WARN_ON_ONCE(!kgsl_mmu_has_feature(device,
|
|
KGSL_MMU_SUPPORT_VBO)))
|
|
return -EOPNOTSUPP;
|
|
|
|
kgsl_memdesc_init(device, memdesc, flags);
|
|
memdesc->priv = 0;
|
|
|
|
memdesc->ops = &kgsl_vbo_ops;
|
|
memdesc->size = size;
|
|
|
|
/* Set up the interval tree and lock */
|
|
memdesc->ranges = RB_ROOT_CACHED;
|
|
mutex_init(&memdesc->ranges_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool kgsl_memdesc_check_range(struct kgsl_memdesc *memdesc,
|
|
u64 offset, u64 length)
|
|
{
|
|
return ((offset < memdesc->size) &&
|
|
(offset + length > offset) &&
|
|
(offset + length) <= memdesc->size);
|
|
}
|
|
|
|
static void kgsl_sharedmem_free_bind_op(struct kgsl_sharedmem_bind_op *op)
|
|
{
|
|
int i;
|
|
|
|
if (IS_ERR_OR_NULL(op))
|
|
return;
|
|
|
|
for (i = 0; i < op->nr_ops; i++) {
|
|
/* Decrement the vbo_count we added when creating the bind_op */
|
|
if (op->ops[i].entry)
|
|
atomic_dec(&op->ops[i].entry->vbo_count);
|
|
|
|
/* Release the reference on the child entry */
|
|
kgsl_mem_entry_put_deferred(op->ops[i].entry);
|
|
}
|
|
|
|
/* Release the reference on the target entry */
|
|
kgsl_mem_entry_put_deferred(op->target);
|
|
|
|
kvfree(op->ops);
|
|
kfree(op);
|
|
}
|
|
|
|
struct kgsl_sharedmem_bind_op *
|
|
kgsl_sharedmem_create_bind_op(struct kgsl_process_private *private,
|
|
u32 target_id, void __user *ranges, u32 ranges_nents,
|
|
u64 ranges_size)
|
|
{
|
|
struct kgsl_sharedmem_bind_op *op;
|
|
struct kgsl_mem_entry *target;
|
|
int ret, i;
|
|
|
|
/* There must be at least one defined operation */
|
|
if (!ranges_nents)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* Find the target memory entry */
|
|
target = kgsl_sharedmem_find_id(private, target_id);
|
|
if (!target)
|
|
return ERR_PTR(-ENOENT);
|
|
|
|
if (!(target->memdesc.flags & KGSL_MEMFLAGS_VBO)) {
|
|
kgsl_mem_entry_put(target);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
/* Make a container for the bind operations */
|
|
op = kzalloc(sizeof(*op), GFP_KERNEL);
|
|
if (!op) {
|
|
kgsl_mem_entry_put(target);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
/*
|
|
* Make an array for the individual operations. Use __GFP_NOWARN and
|
|
* __GFP_NORETRY to make sure a very large request quietly fails
|
|
*/
|
|
op->ops = kvcalloc(ranges_nents, sizeof(*op->ops),
|
|
GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY);
|
|
if (!op->ops) {
|
|
kfree(op);
|
|
kgsl_mem_entry_put(target);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
op->nr_ops = ranges_nents;
|
|
op->target = target;
|
|
|
|
/* Make sure process is pinned in memory before proceeding */
|
|
atomic_inc(&private->cmd_count);
|
|
ret = kgsl_reclaim_to_pinned_state(private);
|
|
if (ret)
|
|
goto err;
|
|
|
|
for (i = 0; i < ranges_nents; i++) {
|
|
struct kgsl_gpumem_bind_range range;
|
|
struct kgsl_mem_entry *entry;
|
|
u32 size;
|
|
|
|
size = min_t(u32, sizeof(range), ranges_size);
|
|
|
|
ret = -EINVAL;
|
|
|
|
if (copy_from_user(&range, ranges, size)) {
|
|
ret = -EFAULT;
|
|
goto err;
|
|
}
|
|
|
|
/* The offset must be page aligned */
|
|
if (!PAGE_ALIGNED(range.target_offset))
|
|
goto err;
|
|
|
|
/* The length of the operation must be aligned and non zero */
|
|
if (!range.length || !PAGE_ALIGNED(range.length))
|
|
goto err;
|
|
|
|
/* Make sure the range fits in the target */
|
|
if (!kgsl_memdesc_check_range(&target->memdesc,
|
|
range.target_offset, range.length))
|
|
goto err;
|
|
|
|
/*
|
|
* Special case: Consider child id 0 as a special request incase of
|
|
* unbind. This helps to unbind the specified range (could span multiple
|
|
* child buffers) without supplying backing physical buffer information.
|
|
*/
|
|
if (range.child_id == 0 && range.op == KGSL_GPUMEM_RANGE_OP_UNBIND) {
|
|
op->ops[i].entry = NULL;
|
|
op->ops[i].start = range.target_offset;
|
|
op->ops[i].last = range.target_offset + range.length - 1;
|
|
/* Child offset doesn't matter for unbind. set it to 0 */
|
|
op->ops[i].child_offset = 0;
|
|
op->ops[i].op = range.op;
|
|
|
|
ranges += ranges_size;
|
|
continue;
|
|
}
|
|
|
|
/* Get the child object */
|
|
op->ops[i].entry = kgsl_sharedmem_find_id(private,
|
|
range.child_id);
|
|
entry = op->ops[i].entry;
|
|
if (!entry) {
|
|
ret = -ENOENT;
|
|
goto err;
|
|
}
|
|
|
|
/* Keep the child pinned in memory */
|
|
atomic_inc(&entry->vbo_count);
|
|
|
|
/* Make sure the child is not a VBO */
|
|
if ((entry->memdesc.flags & KGSL_MEMFLAGS_VBO)) {
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Make sure that only secure children are mapped in secure VBOs
|
|
* and vice versa
|
|
*/
|
|
if ((target->memdesc.flags & KGSL_MEMFLAGS_SECURE) !=
|
|
(entry->memdesc.flags & KGSL_MEMFLAGS_SECURE)) {
|
|
ret = -EPERM;
|
|
goto err;
|
|
}
|
|
|
|
/* Make sure the range operation is valid */
|
|
if (range.op != KGSL_GPUMEM_RANGE_OP_BIND &&
|
|
range.op != KGSL_GPUMEM_RANGE_OP_UNBIND)
|
|
goto err;
|
|
|
|
if (range.op == KGSL_GPUMEM_RANGE_OP_BIND) {
|
|
if (!PAGE_ALIGNED(range.child_offset))
|
|
goto err;
|
|
|
|
/* Make sure the range fits in the child */
|
|
if (!kgsl_memdesc_check_range(&entry->memdesc,
|
|
range.child_offset, range.length))
|
|
goto err;
|
|
} else {
|
|
/* For unop operations the child offset must be 0 */
|
|
if (range.child_offset)
|
|
goto err;
|
|
}
|
|
|
|
op->ops[i].entry = entry;
|
|
op->ops[i].start = range.target_offset;
|
|
op->ops[i].last = range.target_offset + range.length - 1;
|
|
op->ops[i].child_offset = range.child_offset;
|
|
op->ops[i].op = range.op;
|
|
|
|
ranges += ranges_size;
|
|
}
|
|
|
|
atomic_dec(&private->cmd_count);
|
|
init_completion(&op->comp);
|
|
kref_init(&op->ref);
|
|
|
|
return op;
|
|
|
|
err:
|
|
atomic_dec(&private->cmd_count);
|
|
kgsl_sharedmem_free_bind_op(op);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
void kgsl_sharedmem_bind_range_destroy(struct kref *kref)
|
|
{
|
|
struct kgsl_sharedmem_bind_op *op = container_of(kref,
|
|
struct kgsl_sharedmem_bind_op, ref);
|
|
|
|
kgsl_sharedmem_free_bind_op(op);
|
|
}
|
|
|
|
static void kgsl_sharedmem_bind_worker(struct work_struct *work)
|
|
{
|
|
struct kgsl_sharedmem_bind_op *op = container_of(work,
|
|
struct kgsl_sharedmem_bind_op, work);
|
|
int i;
|
|
|
|
for (i = 0; i < op->nr_ops; i++) {
|
|
if (op->ops[i].op == KGSL_GPUMEM_RANGE_OP_BIND)
|
|
kgsl_memdesc_add_range(op->target,
|
|
op->ops[i].start,
|
|
op->ops[i].last,
|
|
op->ops[i].entry,
|
|
op->ops[i].child_offset);
|
|
else
|
|
kgsl_memdesc_remove_range(op->target,
|
|
op->ops[i].start,
|
|
op->ops[i].last,
|
|
op->ops[i].entry);
|
|
}
|
|
|
|
/* Wake up any threads waiting for the bind operation */
|
|
complete_all(&op->comp);
|
|
|
|
if (op->callback)
|
|
op->callback(op);
|
|
|
|
/* Put the refcount we took when scheduling the worker */
|
|
kgsl_sharedmem_put_bind_op(op);
|
|
}
|
|
|
|
void kgsl_sharedmem_bind_ranges(struct kgsl_sharedmem_bind_op *op)
|
|
{
|
|
/* Take a reference to the operation while it is scheduled */
|
|
kref_get(&op->ref);
|
|
|
|
INIT_WORK(&op->work, kgsl_sharedmem_bind_worker);
|
|
schedule_work(&op->work);
|
|
}
|
|
|
|
struct kgsl_sharedmem_bind_fence {
|
|
struct dma_fence base;
|
|
spinlock_t lock;
|
|
int fd;
|
|
struct kgsl_sharedmem_bind_op *op;
|
|
};
|
|
|
|
static const char *bind_fence_get_driver_name(struct dma_fence *fence)
|
|
{
|
|
return "kgsl_sharedmem_bind";
|
|
}
|
|
|
|
static const char *bind_fence_get_timeline_name(struct dma_fence *fence)
|
|
{
|
|
return "(unbound)";
|
|
}
|
|
|
|
static void bind_fence_release(struct dma_fence *fence)
|
|
{
|
|
struct kgsl_sharedmem_bind_fence *bind_fence = container_of(fence,
|
|
struct kgsl_sharedmem_bind_fence, base);
|
|
|
|
kgsl_sharedmem_put_bind_op(bind_fence->op);
|
|
kfree(bind_fence);
|
|
}
|
|
|
|
static void
|
|
kgsl_sharedmem_bind_fence_callback(struct kgsl_sharedmem_bind_op *op)
|
|
{
|
|
struct kgsl_sharedmem_bind_fence *bind_fence = op->data;
|
|
|
|
dma_fence_signal(&bind_fence->base);
|
|
dma_fence_put(&bind_fence->base);
|
|
}
|
|
|
|
static const struct dma_fence_ops kgsl_sharedmem_bind_fence_ops = {
|
|
.get_driver_name = bind_fence_get_driver_name,
|
|
.get_timeline_name = bind_fence_get_timeline_name,
|
|
.release = bind_fence_release,
|
|
};
|
|
|
|
static struct kgsl_sharedmem_bind_fence *
|
|
kgsl_sharedmem_bind_fence(struct kgsl_sharedmem_bind_op *op)
|
|
{
|
|
struct kgsl_sharedmem_bind_fence *fence;
|
|
struct sync_file *sync_file;
|
|
int fd;
|
|
|
|
fence = kzalloc(sizeof(*fence), GFP_KERNEL);
|
|
if (!fence)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
spin_lock_init(&fence->lock);
|
|
|
|
dma_fence_init(&fence->base, &kgsl_sharedmem_bind_fence_ops,
|
|
&fence->lock, dma_fence_context_alloc(1), 0);
|
|
|
|
fd = get_unused_fd_flags(O_CLOEXEC);
|
|
if (fd < 0) {
|
|
kfree(fence);
|
|
return ERR_PTR(fd);
|
|
}
|
|
|
|
sync_file = sync_file_create(&fence->base);
|
|
if (!sync_file) {
|
|
put_unused_fd(fd);
|
|
kfree(fence);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
fd_install(fd, sync_file->file);
|
|
|
|
fence->fd = fd;
|
|
fence->op = op;
|
|
|
|
return fence;
|
|
}
|
|
|
|
long kgsl_ioctl_gpumem_bind_ranges(struct kgsl_device_private *dev_priv,
|
|
unsigned int cmd, void *data)
|
|
{
|
|
struct kgsl_process_private *private = dev_priv->process_priv;
|
|
struct kgsl_gpumem_bind_ranges *param = data;
|
|
struct kgsl_sharedmem_bind_op *op;
|
|
int ret;
|
|
|
|
/* If ranges_size isn't set, return the expected size to the user */
|
|
if (!param->ranges_size) {
|
|
param->ranges_size = sizeof(struct kgsl_gpumem_bind_range);
|
|
return 0;
|
|
}
|
|
|
|
/* FENCE_OUT only makes sense with ASYNC */
|
|
if ((param->flags & KGSL_GPUMEM_BIND_FENCE_OUT) &&
|
|
!(param->flags & KGSL_GPUMEM_BIND_ASYNC))
|
|
return -EINVAL;
|
|
|
|
op = kgsl_sharedmem_create_bind_op(private, param->id,
|
|
u64_to_user_ptr(param->ranges), param->ranges_nents,
|
|
param->ranges_size);
|
|
if (IS_ERR(op))
|
|
return PTR_ERR(op);
|
|
|
|
if (param->flags & KGSL_GPUMEM_BIND_ASYNC) {
|
|
struct kgsl_sharedmem_bind_fence *fence;
|
|
|
|
if (param->flags & KGSL_GPUMEM_BIND_FENCE_OUT) {
|
|
fence = kgsl_sharedmem_bind_fence(op);
|
|
|
|
if (IS_ERR(fence)) {
|
|
kgsl_sharedmem_put_bind_op(op);
|
|
return PTR_ERR(fence);
|
|
}
|
|
|
|
op->data = fence;
|
|
op->callback = kgsl_sharedmem_bind_fence_callback;
|
|
param->fence_id = fence->fd;
|
|
}
|
|
|
|
kgsl_sharedmem_bind_ranges(op);
|
|
|
|
if (!(param->flags & KGSL_GPUMEM_BIND_FENCE_OUT))
|
|
kgsl_sharedmem_put_bind_op(op);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Schedule the work. All the resources will be released after
|
|
* the bind operation is done
|
|
*/
|
|
kgsl_sharedmem_bind_ranges(op);
|
|
|
|
ret = wait_for_completion_interruptible(&op->comp);
|
|
kgsl_sharedmem_put_bind_op(op);
|
|
|
|
return ret;
|
|
}
|