drawobj_destroy_sync() tries to cancel all pending sync events by taking local copy of pending list. In case of sync point timestamp event, it goes ahead and accesses context's events list assuming that event's context would be alive. But at the same time, if the other context, which is of interest for these sync point events, can be destroyed by cancelling all events in its group. This leads to use-after-free in drawobj_destroy_sync() path. Fix is to give the responsibility of putting the context's ref count to the thread which clears the pending mask. Change-Id: I8d08ef6ddb38ca917f75088071c04727bced11d2 Signed-off-by: Rajesh Kemisetti <rajeshk@codeaurora.org>
1180 lines
29 KiB
C
1180 lines
29 KiB
C
/* Copyright (c) 2016-2017,2019, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
/*
|
|
* KGSL drawobj management
|
|
* A drawobj is a single submission from userland. The drawobj
|
|
* encapsulates everything about the submission : command buffers, flags and
|
|
* sync points.
|
|
*
|
|
* Sync points are events that need to expire before the
|
|
* drawobj can be queued to the hardware. All synpoints are contained in an
|
|
* array of kgsl_drawobj_sync_event structs in the drawobj. There can be
|
|
* multiple types of events both internal ones (GPU events) and external
|
|
* triggers. As the events expire bits are cleared in a pending bitmap stored
|
|
* in the drawobj. The GPU will submit the command as soon as the bitmap
|
|
* goes to zero indicating no more pending events.
|
|
*/
|
|
|
|
#include <linux/uaccess.h>
|
|
#include <linux/list.h>
|
|
#include <linux/compat.h>
|
|
|
|
#include "kgsl.h"
|
|
#include "kgsl_device.h"
|
|
#include "kgsl_drawobj.h"
|
|
#include "kgsl_sync.h"
|
|
#include "kgsl_trace.h"
|
|
#include "kgsl_compat.h"
|
|
|
|
/*
|
|
* Define an kmem cache for the memobj & sparseobj structures since we
|
|
* allocate and free them so frequently
|
|
*/
|
|
static struct kmem_cache *memobjs_cache;
|
|
static struct kmem_cache *sparseobjs_cache;
|
|
|
|
|
|
static void drawobj_destroy_object(struct kref *kref)
|
|
{
|
|
struct kgsl_drawobj *drawobj = container_of(kref,
|
|
struct kgsl_drawobj, refcount);
|
|
struct kgsl_drawobj_sync *syncobj;
|
|
|
|
kgsl_context_put(drawobj->context);
|
|
|
|
switch (drawobj->type) {
|
|
case SYNCOBJ_TYPE:
|
|
syncobj = SYNCOBJ(drawobj);
|
|
kfree(syncobj->synclist);
|
|
kfree(syncobj);
|
|
break;
|
|
case CMDOBJ_TYPE:
|
|
case MARKEROBJ_TYPE:
|
|
kfree(CMDOBJ(drawobj));
|
|
break;
|
|
case SPARSEOBJ_TYPE:
|
|
kfree(SPARSEOBJ(drawobj));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline void drawobj_put(struct kgsl_drawobj *drawobj)
|
|
{
|
|
if (drawobj)
|
|
kref_put(&drawobj->refcount, drawobj_destroy_object);
|
|
}
|
|
|
|
void kgsl_dump_syncpoints(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj)
|
|
{
|
|
struct kgsl_drawobj_sync_event *event;
|
|
unsigned int i;
|
|
unsigned long flags;
|
|
|
|
for (i = 0; i < syncobj->numsyncs; i++) {
|
|
event = &syncobj->synclist[i];
|
|
|
|
if (!kgsl_drawobj_event_pending(syncobj, i))
|
|
continue;
|
|
|
|
switch (event->type) {
|
|
case KGSL_CMD_SYNCPOINT_TYPE_TIMESTAMP: {
|
|
unsigned int retired;
|
|
|
|
kgsl_readtimestamp(event->device,
|
|
event->context, KGSL_TIMESTAMP_RETIRED,
|
|
&retired);
|
|
|
|
dev_err(device->dev,
|
|
" [timestamp] context %d timestamp %d (retired %d)\n",
|
|
event->context->id, event->timestamp,
|
|
retired);
|
|
break;
|
|
}
|
|
case KGSL_CMD_SYNCPOINT_TYPE_FENCE:
|
|
spin_lock_irqsave(&event->handle_lock, flags);
|
|
|
|
if (event->handle)
|
|
dev_err(device->dev, " fence: [%pK] %s\n",
|
|
event->handle->fence,
|
|
event->handle->name);
|
|
else
|
|
dev_err(device->dev, " fence: invalid\n");
|
|
|
|
spin_unlock_irqrestore(&event->handle_lock, flags);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void syncobj_timer(unsigned long data)
|
|
{
|
|
struct kgsl_device *device;
|
|
struct kgsl_drawobj_sync *syncobj = (struct kgsl_drawobj_sync *) data;
|
|
struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj);
|
|
struct kgsl_drawobj_sync_event *event;
|
|
unsigned int i;
|
|
unsigned long flags;
|
|
|
|
if (syncobj == NULL || drawobj->context == NULL)
|
|
return;
|
|
|
|
device = drawobj->context->device;
|
|
|
|
dev_err(device->dev,
|
|
"kgsl: possible gpu syncpoint deadlock for context %d timestamp %d\n",
|
|
drawobj->context->id, drawobj->timestamp);
|
|
|
|
set_bit(ADRENO_CONTEXT_FENCE_LOG, &drawobj->context->priv);
|
|
kgsl_context_dump(drawobj->context);
|
|
clear_bit(ADRENO_CONTEXT_FENCE_LOG, &drawobj->context->priv);
|
|
|
|
dev_err(device->dev, " pending events:\n");
|
|
|
|
for (i = 0; i < syncobj->numsyncs; i++) {
|
|
event = &syncobj->synclist[i];
|
|
|
|
if (!kgsl_drawobj_event_pending(syncobj, i))
|
|
continue;
|
|
|
|
switch (event->type) {
|
|
case KGSL_CMD_SYNCPOINT_TYPE_TIMESTAMP:
|
|
dev_err(device->dev, " [%d] TIMESTAMP %d:%d\n",
|
|
i, event->context->id, event->timestamp);
|
|
break;
|
|
case KGSL_CMD_SYNCPOINT_TYPE_FENCE:
|
|
spin_lock_irqsave(&event->handle_lock, flags);
|
|
|
|
if (event->handle != NULL) {
|
|
dev_err(device->dev, " [%d] FENCE %s\n",
|
|
i, event->handle->fence ?
|
|
event->handle->fence->name : "NULL");
|
|
kgsl_sync_fence_log(event->handle->fence);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&event->handle_lock, flags);
|
|
break;
|
|
}
|
|
}
|
|
|
|
dev_err(device->dev, "--gpu syncpoint deadlock print end--\n");
|
|
}
|
|
|
|
/*
|
|
* a generic function to retire a pending sync event and (possibly)
|
|
* kick the dispatcher
|
|
* Returns false if the event was already marked for cancellation in another
|
|
* thread. This function should return true if this thread is responsible for
|
|
* freeing up the memory, and the event will not be cancelled.
|
|
*/
|
|
static bool drawobj_sync_expire(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync_event *event)
|
|
{
|
|
struct kgsl_drawobj_sync *syncobj = event->syncobj;
|
|
/*
|
|
* Clear the event from the pending mask - if it is already clear, then
|
|
* leave without doing anything useful
|
|
*/
|
|
if (!test_and_clear_bit(event->id, &syncobj->pending))
|
|
return false;
|
|
|
|
/*
|
|
* If no more pending events, delete the timer and schedule the command
|
|
* for dispatch
|
|
*/
|
|
if (!kgsl_drawobj_events_pending(event->syncobj)) {
|
|
del_timer_sync(&syncobj->timer);
|
|
|
|
if (device->ftbl->drawctxt_sched)
|
|
device->ftbl->drawctxt_sched(device,
|
|
event->syncobj->base.context);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* This function is called by the GPU event when the sync event timestamp
|
|
* expires
|
|
*/
|
|
static void drawobj_sync_func(struct kgsl_device *device,
|
|
struct kgsl_event_group *group, void *priv, int result)
|
|
{
|
|
struct kgsl_drawobj_sync_event *event = priv;
|
|
|
|
trace_syncpoint_timestamp_expire(event->syncobj,
|
|
event->context, event->timestamp);
|
|
|
|
/*
|
|
* Put down the context ref count only if
|
|
* this thread successfully clears the pending bit mask.
|
|
*/
|
|
if (drawobj_sync_expire(device, event))
|
|
kgsl_context_put(event->context);
|
|
|
|
drawobj_put(&event->syncobj->base);
|
|
}
|
|
|
|
static inline void memobj_list_free(struct list_head *list)
|
|
{
|
|
struct kgsl_memobj_node *mem, *tmpmem;
|
|
|
|
/* Free the cmd mem here */
|
|
list_for_each_entry_safe(mem, tmpmem, list, node) {
|
|
list_del_init(&mem->node);
|
|
kmem_cache_free(memobjs_cache, mem);
|
|
}
|
|
}
|
|
|
|
static void drawobj_destroy_sparse(struct kgsl_drawobj *drawobj)
|
|
{
|
|
struct kgsl_sparseobj_node *mem, *tmpmem;
|
|
struct list_head *list = &SPARSEOBJ(drawobj)->sparselist;
|
|
|
|
/* Free the sparse mem here */
|
|
list_for_each_entry_safe(mem, tmpmem, list, node) {
|
|
list_del_init(&mem->node);
|
|
kmem_cache_free(sparseobjs_cache, mem);
|
|
}
|
|
}
|
|
|
|
static void drawobj_destroy_sync(struct kgsl_drawobj *drawobj)
|
|
{
|
|
struct kgsl_drawobj_sync *syncobj = SYNCOBJ(drawobj);
|
|
unsigned long flags;
|
|
unsigned int i;
|
|
|
|
/* Zap the canary timer */
|
|
del_timer_sync(&syncobj->timer);
|
|
|
|
/*
|
|
* Clear all pending events - this will render any subsequent async
|
|
* callbacks harmless
|
|
*/
|
|
for (i = 0; i < syncobj->numsyncs; i++) {
|
|
struct kgsl_drawobj_sync_event *event = &syncobj->synclist[i];
|
|
|
|
/*
|
|
* Don't do anything if the event has already expired.
|
|
* If this thread clears the pending bit mask then it is
|
|
* responsible for doing context put.
|
|
*/
|
|
if (!test_and_clear_bit(i, &syncobj->pending))
|
|
continue;
|
|
|
|
switch (event->type) {
|
|
case KGSL_CMD_SYNCPOINT_TYPE_TIMESTAMP:
|
|
kgsl_cancel_event(drawobj->device,
|
|
&event->context->events, event->timestamp,
|
|
drawobj_sync_func, event);
|
|
/*
|
|
* Do context put here to make sure the context is alive
|
|
* till this thread cancels kgsl event.
|
|
*/
|
|
kgsl_context_put(event->context);
|
|
break;
|
|
case KGSL_CMD_SYNCPOINT_TYPE_FENCE:
|
|
spin_lock_irqsave(&event->handle_lock, flags);
|
|
|
|
if (kgsl_sync_fence_async_cancel(event->handle)) {
|
|
event->handle = NULL;
|
|
spin_unlock_irqrestore(
|
|
&event->handle_lock, flags);
|
|
drawobj_put(drawobj);
|
|
} else {
|
|
spin_unlock_irqrestore(
|
|
&event->handle_lock, flags);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we cancelled an event, there's a good chance that the context is
|
|
* on a dispatcher queue, so schedule to get it removed.
|
|
*/
|
|
if (!bitmap_empty(&syncobj->pending, KGSL_MAX_SYNCPOINTS) &&
|
|
drawobj->device->ftbl->drawctxt_sched)
|
|
drawobj->device->ftbl->drawctxt_sched(drawobj->device,
|
|
drawobj->context);
|
|
|
|
}
|
|
|
|
static void drawobj_destroy_cmd(struct kgsl_drawobj *drawobj)
|
|
{
|
|
struct kgsl_drawobj_cmd *cmdobj = CMDOBJ(drawobj);
|
|
|
|
/*
|
|
* Release the refcount on the mem entry associated with the
|
|
* ib profiling buffer
|
|
*/
|
|
if (cmdobj->base.flags & KGSL_DRAWOBJ_PROFILING)
|
|
kgsl_mem_entry_put(cmdobj->profiling_buf_entry);
|
|
|
|
/* Destroy the cmdlist we created */
|
|
memobj_list_free(&cmdobj->cmdlist);
|
|
|
|
/* Destroy the memlist we created */
|
|
memobj_list_free(&cmdobj->memlist);
|
|
}
|
|
|
|
/**
|
|
* kgsl_drawobj_destroy() - Destroy a kgsl object structure
|
|
* @obj: Pointer to the kgsl object to destroy
|
|
*
|
|
* Start the process of destroying a command batch. Cancel any pending events
|
|
* and decrement the refcount. Asynchronous events can still signal after
|
|
* kgsl_drawobj_destroy has returned.
|
|
*/
|
|
void kgsl_drawobj_destroy(struct kgsl_drawobj *drawobj)
|
|
{
|
|
if (!drawobj)
|
|
return;
|
|
|
|
if (drawobj->type & SYNCOBJ_TYPE)
|
|
drawobj_destroy_sync(drawobj);
|
|
else if (drawobj->type & (CMDOBJ_TYPE | MARKEROBJ_TYPE))
|
|
drawobj_destroy_cmd(drawobj);
|
|
else if (drawobj->type == SPARSEOBJ_TYPE)
|
|
drawobj_destroy_sparse(drawobj);
|
|
else
|
|
return;
|
|
|
|
drawobj_put(drawobj);
|
|
}
|
|
EXPORT_SYMBOL(kgsl_drawobj_destroy);
|
|
|
|
static void drawobj_sync_fence_func(void *priv)
|
|
{
|
|
unsigned long flags;
|
|
struct kgsl_drawobj_sync_event *event = priv;
|
|
|
|
drawobj_sync_expire(event->device, event);
|
|
|
|
trace_syncpoint_fence_expire(event->syncobj,
|
|
event->handle ? event->handle->name : "unknown");
|
|
|
|
spin_lock_irqsave(&event->handle_lock, flags);
|
|
|
|
/*
|
|
* Setting the event->handle to NULL here make sure that
|
|
* other function does not dereference a invalid pointer.
|
|
*/
|
|
event->handle = NULL;
|
|
|
|
spin_unlock_irqrestore(&event->handle_lock, flags);
|
|
|
|
drawobj_put(&event->syncobj->base);
|
|
}
|
|
|
|
/* drawobj_add_sync_fence() - Add a new sync fence syncpoint
|
|
* @device: KGSL device
|
|
* @syncobj: KGSL sync obj to add the sync point to
|
|
* @priv: Private structure passed by the user
|
|
*
|
|
* Add a new fence sync syncpoint to the sync obj.
|
|
*/
|
|
static int drawobj_add_sync_fence(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj, void *priv)
|
|
{
|
|
struct kgsl_cmd_syncpoint_fence *sync = priv;
|
|
struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj);
|
|
struct kgsl_drawobj_sync_event *event;
|
|
struct sync_fence *fence = NULL;
|
|
unsigned int id;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
fence = sync_fence_fdget(sync->fd);
|
|
if (fence == NULL)
|
|
return -EINVAL;
|
|
|
|
kref_get(&drawobj->refcount);
|
|
|
|
id = syncobj->numsyncs++;
|
|
|
|
event = &syncobj->synclist[id];
|
|
|
|
event->id = id;
|
|
event->type = KGSL_CMD_SYNCPOINT_TYPE_FENCE;
|
|
event->syncobj = syncobj;
|
|
event->device = device;
|
|
event->context = NULL;
|
|
|
|
spin_lock_init(&event->handle_lock);
|
|
set_bit(event->id, &syncobj->pending);
|
|
|
|
trace_syncpoint_fence(syncobj, fence->name);
|
|
|
|
spin_lock_irqsave(&event->handle_lock, flags);
|
|
|
|
event->handle = kgsl_sync_fence_async_wait(sync->fd,
|
|
drawobj_sync_fence_func, event);
|
|
|
|
if (IS_ERR_OR_NULL(event->handle)) {
|
|
ret = PTR_ERR(event->handle);
|
|
|
|
event->handle = NULL;
|
|
spin_unlock_irqrestore(&event->handle_lock, flags);
|
|
|
|
clear_bit(event->id, &syncobj->pending);
|
|
|
|
drawobj_put(drawobj);
|
|
|
|
/*
|
|
* Print a syncpoint_fence_expire trace if
|
|
* the fence is already signaled or there is
|
|
* a failure in registering the fence waiter.
|
|
*/
|
|
trace_syncpoint_fence_expire(syncobj, (ret < 0) ?
|
|
"error" : fence->name);
|
|
} else {
|
|
spin_unlock_irqrestore(&event->handle_lock, flags);
|
|
}
|
|
|
|
sync_fence_put(fence);
|
|
return ret;
|
|
}
|
|
|
|
/* drawobj_add_sync_timestamp() - Add a new sync point for a sync obj
|
|
* @device: KGSL device
|
|
* @syncobj: KGSL sync obj to add the sync point to
|
|
* @priv: Private structure passed by the user
|
|
*
|
|
* Add a new sync point timestamp event to the sync obj.
|
|
*/
|
|
static int drawobj_add_sync_timestamp(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj, void *priv)
|
|
{
|
|
struct kgsl_cmd_syncpoint_timestamp *sync = priv;
|
|
struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj);
|
|
struct kgsl_context *context = kgsl_context_get(device,
|
|
sync->context_id);
|
|
struct kgsl_drawobj_sync_event *event;
|
|
int ret = -EINVAL;
|
|
unsigned int id;
|
|
|
|
if (context == NULL)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* We allow somebody to create a sync point on their own context.
|
|
* This has the effect of delaying a command from submitting until the
|
|
* dependent command has cleared. That said we obviously can't let them
|
|
* create a sync point on a future timestamp.
|
|
*/
|
|
|
|
if (context == drawobj->context) {
|
|
unsigned int queued;
|
|
|
|
kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_QUEUED,
|
|
&queued);
|
|
|
|
if (timestamp_cmp(sync->timestamp, queued) > 0) {
|
|
KGSL_DRV_ERR(device,
|
|
"Cannot create syncpoint for future timestamp %d (current %d)\n",
|
|
sync->timestamp, queued);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
kref_get(&drawobj->refcount);
|
|
|
|
id = syncobj->numsyncs++;
|
|
|
|
event = &syncobj->synclist[id];
|
|
event->id = id;
|
|
|
|
event->type = KGSL_CMD_SYNCPOINT_TYPE_TIMESTAMP;
|
|
event->syncobj = syncobj;
|
|
event->context = context;
|
|
event->timestamp = sync->timestamp;
|
|
event->device = device;
|
|
|
|
set_bit(event->id, &syncobj->pending);
|
|
|
|
ret = kgsl_add_event(device, &context->events, sync->timestamp,
|
|
drawobj_sync_func, event);
|
|
|
|
if (ret) {
|
|
clear_bit(event->id, &syncobj->pending);
|
|
drawobj_put(drawobj);
|
|
} else {
|
|
trace_syncpoint_timestamp(syncobj, context, sync->timestamp);
|
|
}
|
|
|
|
done:
|
|
if (ret)
|
|
kgsl_context_put(context);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* kgsl_drawobj_sync_add_sync() - Add a sync point to a command
|
|
* batch
|
|
* @device: Pointer to the KGSL device struct for the GPU
|
|
* @syncobj: Pointer to the sync obj
|
|
* @sync: Pointer to the user-specified struct defining the syncpoint
|
|
*
|
|
* Create a new sync point in the sync obj based on the
|
|
* user specified parameters
|
|
*/
|
|
int kgsl_drawobj_sync_add_sync(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj,
|
|
struct kgsl_cmd_syncpoint *sync)
|
|
{
|
|
void *priv;
|
|
int ret, psize;
|
|
struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj);
|
|
int (*func)(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj,
|
|
void *priv);
|
|
|
|
switch (sync->type) {
|
|
case KGSL_CMD_SYNCPOINT_TYPE_TIMESTAMP:
|
|
psize = sizeof(struct kgsl_cmd_syncpoint_timestamp);
|
|
func = drawobj_add_sync_timestamp;
|
|
break;
|
|
case KGSL_CMD_SYNCPOINT_TYPE_FENCE:
|
|
psize = sizeof(struct kgsl_cmd_syncpoint_fence);
|
|
func = drawobj_add_sync_fence;
|
|
break;
|
|
default:
|
|
KGSL_DRV_ERR(device,
|
|
"bad syncpoint type ctxt %d type 0x%x size %zu\n",
|
|
drawobj->context->id, sync->type, sync->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sync->size != psize) {
|
|
KGSL_DRV_ERR(device,
|
|
"bad syncpoint size ctxt %d type 0x%x size %zu\n",
|
|
drawobj->context->id, sync->type, sync->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv = kzalloc(sync->size, GFP_KERNEL);
|
|
if (priv == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(priv, sync->priv, sync->size)) {
|
|
kfree(priv);
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = func(device, syncobj, priv);
|
|
kfree(priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void add_profiling_buffer(struct kgsl_device *device,
|
|
struct kgsl_drawobj_cmd *cmdobj,
|
|
uint64_t gpuaddr, uint64_t size,
|
|
unsigned int id, uint64_t offset)
|
|
{
|
|
struct kgsl_mem_entry *entry;
|
|
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
|
|
|
|
if (!(drawobj->flags & KGSL_DRAWOBJ_PROFILING))
|
|
return;
|
|
|
|
/* Only the first buffer entry counts - ignore the rest */
|
|
if (cmdobj->profiling_buf_entry != NULL)
|
|
return;
|
|
|
|
if (id != 0)
|
|
entry = kgsl_sharedmem_find_id(drawobj->context->proc_priv,
|
|
id);
|
|
else
|
|
entry = kgsl_sharedmem_find(drawobj->context->proc_priv,
|
|
gpuaddr);
|
|
|
|
if (entry != NULL) {
|
|
if (!kgsl_gpuaddr_in_memdesc(&entry->memdesc, gpuaddr, size)) {
|
|
kgsl_mem_entry_put(entry);
|
|
entry = NULL;
|
|
}
|
|
}
|
|
|
|
if (entry == NULL) {
|
|
KGSL_DRV_ERR(device,
|
|
"ignore bad profile buffer ctxt %d id %d offset %lld gpuaddr %llx size %lld\n",
|
|
drawobj->context->id, id, offset, gpuaddr, size);
|
|
return;
|
|
}
|
|
|
|
cmdobj->profiling_buf_entry = entry;
|
|
|
|
if (id != 0)
|
|
cmdobj->profiling_buffer_gpuaddr =
|
|
entry->memdesc.gpuaddr + offset;
|
|
else
|
|
cmdobj->profiling_buffer_gpuaddr = gpuaddr;
|
|
}
|
|
|
|
/**
|
|
* kgsl_drawobj_cmd_add_ibdesc() - Add a legacy ibdesc to a command
|
|
* batch
|
|
* @cmdobj: Pointer to the ib
|
|
* @ibdesc: Pointer to the user-specified struct defining the memory or IB
|
|
*
|
|
* Create a new memory entry in the ib based on the
|
|
* user specified parameters
|
|
*/
|
|
int kgsl_drawobj_cmd_add_ibdesc(struct kgsl_device *device,
|
|
struct kgsl_drawobj_cmd *cmdobj, struct kgsl_ibdesc *ibdesc)
|
|
{
|
|
uint64_t gpuaddr = (uint64_t) ibdesc->gpuaddr;
|
|
uint64_t size = (uint64_t) ibdesc->sizedwords << 2;
|
|
struct kgsl_memobj_node *mem;
|
|
struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj);
|
|
|
|
/* sanitize the ibdesc ctrl flags */
|
|
ibdesc->ctrl &= KGSL_IBDESC_MEMLIST | KGSL_IBDESC_PROFILING_BUFFER;
|
|
|
|
if (drawobj->flags & KGSL_DRAWOBJ_MEMLIST &&
|
|
ibdesc->ctrl & KGSL_IBDESC_MEMLIST) {
|
|
if (ibdesc->ctrl & KGSL_IBDESC_PROFILING_BUFFER) {
|
|
add_profiling_buffer(device, cmdobj,
|
|
gpuaddr, size, 0, 0);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Ignore if SYNC or MARKER is specified */
|
|
if (drawobj->type & (SYNCOBJ_TYPE | MARKEROBJ_TYPE))
|
|
return 0;
|
|
|
|
mem = kmem_cache_alloc(memobjs_cache, GFP_KERNEL);
|
|
if (mem == NULL)
|
|
return -ENOMEM;
|
|
|
|
mem->gpuaddr = gpuaddr;
|
|
mem->size = size;
|
|
mem->priv = 0;
|
|
mem->id = 0;
|
|
mem->offset = 0;
|
|
mem->flags = 0;
|
|
|
|
if (drawobj->flags & KGSL_DRAWOBJ_MEMLIST &&
|
|
ibdesc->ctrl & KGSL_IBDESC_MEMLIST)
|
|
/* add to the memlist */
|
|
list_add_tail(&mem->node, &cmdobj->memlist);
|
|
else {
|
|
/* set the preamble flag if directed to */
|
|
if (drawobj->context->flags & KGSL_CONTEXT_PREAMBLE &&
|
|
list_empty(&cmdobj->cmdlist))
|
|
mem->flags = KGSL_CMDLIST_CTXTSWITCH_PREAMBLE;
|
|
|
|
/* add to the cmd list */
|
|
list_add_tail(&mem->node, &cmdobj->cmdlist);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *_drawobj_create(struct kgsl_device *device,
|
|
struct kgsl_context *context, unsigned int size,
|
|
unsigned int type)
|
|
{
|
|
void *obj = kzalloc(size, GFP_KERNEL);
|
|
struct kgsl_drawobj *drawobj;
|
|
|
|
if (obj == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/*
|
|
* Increase the reference count on the context so it doesn't disappear
|
|
* during the lifetime of this object
|
|
*/
|
|
if (!_kgsl_context_get(context)) {
|
|
kfree(obj);
|
|
return ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
drawobj = obj;
|
|
|
|
kref_init(&drawobj->refcount);
|
|
|
|
drawobj->device = device;
|
|
drawobj->context = context;
|
|
drawobj->type = type;
|
|
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* kgsl_drawobj_sparse_create() - Create a new sparse obj structure
|
|
* @device: Pointer to a KGSL device struct
|
|
* @context: Pointer to a KGSL context struct
|
|
* @flags: Flags for the sparse obj
|
|
*
|
|
* Allocate an new kgsl_drawobj_sparse structure
|
|
*/
|
|
struct kgsl_drawobj_sparse *kgsl_drawobj_sparse_create(
|
|
struct kgsl_device *device,
|
|
struct kgsl_context *context, unsigned int flags)
|
|
{
|
|
struct kgsl_drawobj_sparse *sparseobj = _drawobj_create(device,
|
|
context, sizeof(*sparseobj), SPARSEOBJ_TYPE);
|
|
|
|
if (!IS_ERR(sparseobj))
|
|
INIT_LIST_HEAD(&sparseobj->sparselist);
|
|
|
|
return sparseobj;
|
|
}
|
|
|
|
/**
|
|
* kgsl_drawobj_sync_create() - Create a new sync obj
|
|
* structure
|
|
* @device: Pointer to a KGSL device struct
|
|
* @context: Pointer to a KGSL context struct
|
|
*
|
|
* Allocate an new kgsl_drawobj_sync structure
|
|
*/
|
|
struct kgsl_drawobj_sync *kgsl_drawobj_sync_create(struct kgsl_device *device,
|
|
struct kgsl_context *context)
|
|
{
|
|
struct kgsl_drawobj_sync *syncobj = _drawobj_create(device,
|
|
context, sizeof(*syncobj), SYNCOBJ_TYPE);
|
|
|
|
/* Add a timer to help debug sync deadlocks */
|
|
if (!IS_ERR(syncobj))
|
|
setup_timer(&syncobj->timer, syncobj_timer,
|
|
(unsigned long) syncobj);
|
|
|
|
return syncobj;
|
|
}
|
|
|
|
/**
|
|
* kgsl_drawobj_cmd_create() - Create a new command obj
|
|
* structure
|
|
* @device: Pointer to a KGSL device struct
|
|
* @context: Pointer to a KGSL context struct
|
|
* @flags: Flags for the command obj
|
|
* @type: type of cmdobj MARKER/CMD
|
|
*
|
|
* Allocate a new kgsl_drawobj_cmd structure
|
|
*/
|
|
struct kgsl_drawobj_cmd *kgsl_drawobj_cmd_create(struct kgsl_device *device,
|
|
struct kgsl_context *context, unsigned int flags,
|
|
unsigned int type)
|
|
{
|
|
struct kgsl_drawobj_cmd *cmdobj = _drawobj_create(device,
|
|
context, sizeof(*cmdobj),
|
|
(type & (CMDOBJ_TYPE | MARKEROBJ_TYPE)));
|
|
|
|
if (!IS_ERR(cmdobj)) {
|
|
/* sanitize our flags for drawobj's */
|
|
cmdobj->base.flags = flags & (KGSL_DRAWOBJ_CTX_SWITCH
|
|
| KGSL_DRAWOBJ_MARKER
|
|
| KGSL_DRAWOBJ_END_OF_FRAME
|
|
| KGSL_DRAWOBJ_PWR_CONSTRAINT
|
|
| KGSL_DRAWOBJ_MEMLIST
|
|
| KGSL_DRAWOBJ_PROFILING
|
|
| KGSL_DRAWOBJ_PROFILING_KTIME);
|
|
|
|
INIT_LIST_HEAD(&cmdobj->cmdlist);
|
|
INIT_LIST_HEAD(&cmdobj->memlist);
|
|
}
|
|
|
|
return cmdobj;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static int add_ibdesc_list_compat(struct kgsl_device *device,
|
|
struct kgsl_drawobj_cmd *cmdobj, void __user *ptr, int count)
|
|
{
|
|
int i, ret = 0;
|
|
struct kgsl_ibdesc_compat ibdesc32;
|
|
struct kgsl_ibdesc ibdesc;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
memset(&ibdesc32, 0, sizeof(ibdesc32));
|
|
|
|
if (copy_from_user(&ibdesc32, ptr, sizeof(ibdesc32))) {
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
ibdesc.gpuaddr = (unsigned long) ibdesc32.gpuaddr;
|
|
ibdesc.sizedwords = (size_t) ibdesc32.sizedwords;
|
|
ibdesc.ctrl = (unsigned int) ibdesc32.ctrl;
|
|
|
|
ret = kgsl_drawobj_cmd_add_ibdesc(device, cmdobj, &ibdesc);
|
|
if (ret)
|
|
break;
|
|
|
|
ptr += sizeof(ibdesc32);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int add_syncpoints_compat(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj, void __user *ptr, int count)
|
|
{
|
|
struct kgsl_cmd_syncpoint_compat sync32;
|
|
struct kgsl_cmd_syncpoint sync;
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
memset(&sync32, 0, sizeof(sync32));
|
|
|
|
if (copy_from_user(&sync32, ptr, sizeof(sync32))) {
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
sync.type = sync32.type;
|
|
sync.priv = compat_ptr(sync32.priv);
|
|
sync.size = (size_t) sync32.size;
|
|
|
|
ret = kgsl_drawobj_sync_add_sync(device, syncobj, &sync);
|
|
if (ret)
|
|
break;
|
|
|
|
ptr += sizeof(sync32);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
static int add_ibdesc_list_compat(struct kgsl_device *device,
|
|
struct kgsl_drawobj_cmd *cmdobj, void __user *ptr, int count)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int add_syncpoints_compat(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj, void __user *ptr, int count)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
/* Returns:
|
|
* -EINVAL: Bad data
|
|
* 0: All data fields are empty (nothing to do)
|
|
* 1: All list information is valid
|
|
*/
|
|
static int _verify_input_list(unsigned int count, void __user *ptr,
|
|
unsigned int size)
|
|
{
|
|
/* Return early if nothing going on */
|
|
if (count == 0 && ptr == NULL && size == 0)
|
|
return 0;
|
|
|
|
/* Sanity check inputs */
|
|
if (count == 0 || ptr == NULL || size == 0)
|
|
return -EINVAL;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int kgsl_drawobj_cmd_add_ibdesc_list(struct kgsl_device *device,
|
|
struct kgsl_drawobj_cmd *cmdobj, void __user *ptr, int count)
|
|
{
|
|
struct kgsl_ibdesc ibdesc;
|
|
struct kgsl_drawobj *baseobj = DRAWOBJ(cmdobj);
|
|
int i, ret;
|
|
|
|
/* Ignore everything if this is a MARKER */
|
|
if (baseobj->type & MARKEROBJ_TYPE)
|
|
return 0;
|
|
|
|
ret = _verify_input_list(count, ptr, sizeof(ibdesc));
|
|
if (ret <= 0)
|
|
return -EINVAL;
|
|
|
|
if (is_compat_task())
|
|
return add_ibdesc_list_compat(device, cmdobj, ptr, count);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
memset(&ibdesc, 0, sizeof(ibdesc));
|
|
|
|
if (copy_from_user(&ibdesc, ptr, sizeof(ibdesc)))
|
|
return -EFAULT;
|
|
|
|
ret = kgsl_drawobj_cmd_add_ibdesc(device, cmdobj, &ibdesc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ptr += sizeof(ibdesc);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kgsl_drawobj_sync_add_syncpoints(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj, void __user *ptr, int count)
|
|
{
|
|
struct kgsl_cmd_syncpoint sync;
|
|
int i, ret;
|
|
|
|
if (count == 0)
|
|
return 0;
|
|
|
|
syncobj->synclist = kcalloc(count,
|
|
sizeof(struct kgsl_drawobj_sync_event), GFP_KERNEL);
|
|
|
|
if (syncobj->synclist == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (is_compat_task())
|
|
return add_syncpoints_compat(device, syncobj, ptr, count);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
memset(&sync, 0, sizeof(sync));
|
|
|
|
if (copy_from_user(&sync, ptr, sizeof(sync)))
|
|
return -EFAULT;
|
|
|
|
ret = kgsl_drawobj_sync_add_sync(device, syncobj, &sync);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ptr += sizeof(sync);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kgsl_drawobj_add_memobject(struct list_head *head,
|
|
struct kgsl_command_object *obj)
|
|
{
|
|
struct kgsl_memobj_node *mem;
|
|
|
|
mem = kmem_cache_alloc(memobjs_cache, GFP_KERNEL);
|
|
if (mem == NULL)
|
|
return -ENOMEM;
|
|
|
|
mem->gpuaddr = obj->gpuaddr;
|
|
mem->size = obj->size;
|
|
mem->id = obj->id;
|
|
mem->offset = obj->offset;
|
|
mem->flags = obj->flags;
|
|
mem->priv = 0;
|
|
|
|
list_add_tail(&mem->node, head);
|
|
return 0;
|
|
}
|
|
|
|
static int kgsl_drawobj_add_sparseobject(struct list_head *head,
|
|
struct kgsl_sparse_binding_object *obj, unsigned int virt_id)
|
|
{
|
|
struct kgsl_sparseobj_node *mem;
|
|
|
|
mem = kmem_cache_alloc(sparseobjs_cache, GFP_KERNEL);
|
|
if (mem == NULL)
|
|
return -ENOMEM;
|
|
|
|
mem->virt_id = virt_id;
|
|
mem->obj.id = obj->id;
|
|
mem->obj.virtoffset = obj->virtoffset;
|
|
mem->obj.physoffset = obj->physoffset;
|
|
mem->obj.size = obj->size;
|
|
mem->obj.flags = obj->flags;
|
|
|
|
list_add_tail(&mem->node, head);
|
|
return 0;
|
|
}
|
|
|
|
int kgsl_drawobj_sparse_add_sparselist(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sparse *sparseobj, unsigned int id,
|
|
void __user *ptr, unsigned int size, unsigned int count)
|
|
{
|
|
struct kgsl_sparse_binding_object obj;
|
|
int i, ret = 0;
|
|
|
|
ret = _verify_input_list(count, ptr, size);
|
|
if (ret <= 0)
|
|
return ret;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
memset(&obj, 0, sizeof(obj));
|
|
|
|
ret = _copy_from_user(&obj, ptr, sizeof(obj), size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!(obj.flags & (KGSL_SPARSE_BIND | KGSL_SPARSE_UNBIND)))
|
|
return -EINVAL;
|
|
|
|
ret = kgsl_drawobj_add_sparseobject(&sparseobj->sparselist,
|
|
&obj, id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ptr += sizeof(obj);
|
|
}
|
|
|
|
sparseobj->size = size;
|
|
sparseobj->count = count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define CMDLIST_FLAGS \
|
|
(KGSL_CMDLIST_IB | \
|
|
KGSL_CMDLIST_CTXTSWITCH_PREAMBLE | \
|
|
KGSL_CMDLIST_IB_PREAMBLE)
|
|
|
|
/* This can only accept MARKEROBJ_TYPE and CMDOBJ_TYPE */
|
|
int kgsl_drawobj_cmd_add_cmdlist(struct kgsl_device *device,
|
|
struct kgsl_drawobj_cmd *cmdobj, void __user *ptr,
|
|
unsigned int size, unsigned int count)
|
|
{
|
|
struct kgsl_command_object obj;
|
|
struct kgsl_drawobj *baseobj = DRAWOBJ(cmdobj);
|
|
int i, ret;
|
|
|
|
/* Ignore everything if this is a MARKER */
|
|
if (baseobj->type & MARKEROBJ_TYPE)
|
|
return 0;
|
|
|
|
ret = _verify_input_list(count, ptr, size);
|
|
if (ret <= 0)
|
|
return ret;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
memset(&obj, 0, sizeof(obj));
|
|
|
|
ret = _copy_from_user(&obj, ptr, sizeof(obj), size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Sanity check the flags */
|
|
if (!(obj.flags & CMDLIST_FLAGS)) {
|
|
KGSL_DRV_ERR(device,
|
|
"invalid cmdobj ctxt %d flags %d id %d offset %lld addr %lld size %lld\n",
|
|
baseobj->context->id, obj.flags, obj.id,
|
|
obj.offset, obj.gpuaddr, obj.size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = kgsl_drawobj_add_memobject(&cmdobj->cmdlist, &obj);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ptr += sizeof(obj);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kgsl_drawobj_cmd_add_memlist(struct kgsl_device *device,
|
|
struct kgsl_drawobj_cmd *cmdobj, void __user *ptr,
|
|
unsigned int size, unsigned int count)
|
|
{
|
|
struct kgsl_command_object obj;
|
|
struct kgsl_drawobj *baseobj = DRAWOBJ(cmdobj);
|
|
int i, ret;
|
|
|
|
/* Ignore everything if this is a MARKER */
|
|
if (baseobj->type & MARKEROBJ_TYPE)
|
|
return 0;
|
|
|
|
ret = _verify_input_list(count, ptr, size);
|
|
if (ret <= 0)
|
|
return ret;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
memset(&obj, 0, sizeof(obj));
|
|
|
|
ret = _copy_from_user(&obj, ptr, sizeof(obj), size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!(obj.flags & KGSL_OBJLIST_MEMOBJ)) {
|
|
KGSL_DRV_ERR(device,
|
|
"invalid memobj ctxt %d flags %d id %d offset %lld addr %lld size %lld\n",
|
|
DRAWOBJ(cmdobj)->context->id, obj.flags,
|
|
obj.id, obj.offset, obj.gpuaddr, obj.size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (obj.flags & KGSL_OBJLIST_PROFILE)
|
|
add_profiling_buffer(device, cmdobj, obj.gpuaddr,
|
|
obj.size, obj.id, obj.offset);
|
|
else {
|
|
ret = kgsl_drawobj_add_memobject(&cmdobj->memlist,
|
|
&obj);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
ptr += sizeof(obj);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kgsl_drawobj_sync_add_synclist(struct kgsl_device *device,
|
|
struct kgsl_drawobj_sync *syncobj, void __user *ptr,
|
|
unsigned int size, unsigned int count)
|
|
{
|
|
struct kgsl_command_syncpoint syncpoint;
|
|
struct kgsl_cmd_syncpoint sync;
|
|
int i, ret;
|
|
|
|
/* If creating a sync and the data is not there or wrong then error */
|
|
ret = _verify_input_list(count, ptr, size);
|
|
if (ret <= 0)
|
|
return -EINVAL;
|
|
|
|
syncobj->synclist = kcalloc(count,
|
|
sizeof(struct kgsl_drawobj_sync_event), GFP_KERNEL);
|
|
|
|
if (syncobj->synclist == NULL)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
memset(&syncpoint, 0, sizeof(syncpoint));
|
|
|
|
ret = _copy_from_user(&syncpoint, ptr, sizeof(syncpoint), size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
sync.type = syncpoint.type;
|
|
sync.priv = to_user_ptr(syncpoint.priv);
|
|
sync.size = syncpoint.size;
|
|
|
|
ret = kgsl_drawobj_sync_add_sync(device, syncobj, &sync);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ptr += sizeof(syncpoint);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void kgsl_drawobjs_cache_exit(void)
|
|
{
|
|
kmem_cache_destroy(memobjs_cache);
|
|
kmem_cache_destroy(sparseobjs_cache);
|
|
}
|
|
|
|
int kgsl_drawobjs_cache_init(void)
|
|
{
|
|
memobjs_cache = KMEM_CACHE(kgsl_memobj_node, 0);
|
|
sparseobjs_cache = KMEM_CACHE(kgsl_sparseobj_node, 0);
|
|
|
|
if (!memobjs_cache || !sparseobjs_cache)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|