| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. |
| * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved. |
| */ |
| |
| /* |
| * 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/slab.h> |
| #include <linux/dma-fence-array.h> |
| |
| #include "adreno_drawctxt.h" |
| #include "kgsl_compat.h" |
| #include "kgsl_device.h" |
| #include "kgsl_drawobj.h" |
| #include "kgsl_eventlog.h" |
| #include "kgsl_sync.h" |
| #include "kgsl_timeline.h" |
| #include "kgsl_trace.h" |
| |
| /* |
| * Define an kmem cache for the memobj structures since we |
| * allocate and free them so frequently |
| */ |
| static struct kmem_cache *memobjs_cache; |
| |
| static void syncobj_destroy_object(struct kgsl_drawobj *drawobj) |
| { |
| struct kgsl_drawobj_sync *syncobj = SYNCOBJ(drawobj); |
| int i; |
| |
| for (i = 0; i < syncobj->numsyncs; i++) { |
| struct kgsl_drawobj_sync_event *event = &syncobj->synclist[i]; |
| |
| if (event->type == KGSL_CMD_SYNCPOINT_TYPE_FENCE) { |
| struct event_fence_info *priv = event->priv; |
| |
| if (priv) { |
| kfree(priv->fences); |
| kfree(priv); |
| } |
| |
| if (event->handle) { |
| struct kgsl_sync_fence_cb *kcb = event->handle; |
| |
| dma_fence_put(kcb->fence); |
| kfree(kcb); |
| } |
| |
| } else if (event->type == KGSL_CMD_SYNCPOINT_TYPE_TIMELINE) { |
| kfree(event->priv); |
| } |
| } |
| |
| kfree(syncobj->synclist); |
| kfree(syncobj); |
| } |
| |
| static void cmdobj_destroy_object(struct kgsl_drawobj *drawobj) |
| { |
| kfree(CMDOBJ(drawobj)); |
| } |
| |
| static void bindobj_destroy_object(struct kgsl_drawobj *drawobj) |
| { |
| kfree(BINDOBJ(drawobj)); |
| } |
| |
| static void timelineobj_destroy_object(struct kgsl_drawobj *drawobj) |
| { |
| kfree(TIMELINEOBJ(drawobj)); |
| } |
| |
| void kgsl_drawobj_destroy_object(struct kref *kref) |
| { |
| struct kgsl_drawobj *drawobj = container_of(kref, |
| struct kgsl_drawobj, refcount); |
| |
| kgsl_context_put(drawobj->context); |
| drawobj->destroy_object(drawobj); |
| } |
| |
| void kgsl_dump_syncpoints(struct kgsl_device *device, |
| struct kgsl_drawobj_sync *syncobj) |
| { |
| struct kgsl_drawobj_sync_event *event; |
| unsigned int i; |
| |
| 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 %u timestamp %u (retired %u)\n", |
| event->context->id, event->timestamp, |
| retired); |
| break; |
| } |
| case KGSL_CMD_SYNCPOINT_TYPE_FENCE: { |
| int j; |
| struct event_fence_info *info = event->priv; |
| |
| for (j = 0; info && j < info->num_fences; j++) |
| dev_err(device->dev, "[%d] fence: %s\n", |
| i, info->fences[j].name); |
| break; |
| } |
| case KGSL_CMD_SYNCPOINT_TYPE_TIMELINE: { |
| int j; |
| struct event_timeline_info *info = event->priv; |
| |
| for (j = 0; info && info[j].timeline; j++) |
| dev_err(device->dev, "[%d] timeline: %d seqno %lld\n", |
| i, info[j].timeline, info[j].seqno); |
| break; |
| } |
| } |
| } |
| } |
| |
| static void syncobj_timer(struct timer_list *t) |
| { |
| struct kgsl_device *device; |
| struct kgsl_drawobj_sync *syncobj = from_timer(syncobj, t, timer); |
| struct kgsl_drawobj *drawobj; |
| struct kgsl_drawobj_sync_event *event; |
| unsigned int i; |
| |
| if (syncobj == NULL) |
| return; |
| |
| drawobj = DRAWOBJ(syncobj); |
| |
| if (!kref_get_unless_zero(&drawobj->refcount)) |
| return; |
| |
| if (drawobj->context == NULL) { |
| kgsl_drawobj_put(drawobj); |
| return; |
| } |
| |
| device = drawobj->context->device; |
| |
| dev_err(device->dev, |
| "kgsl: possible gpu syncpoint deadlock for context %u timestamp %u\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, " [%u] TIMESTAMP %u:%u\n", |
| i, event->context->id, event->timestamp); |
| break; |
| case KGSL_CMD_SYNCPOINT_TYPE_FENCE: { |
| int j; |
| struct event_fence_info *info = event->priv; |
| |
| for (j = 0; info && j < info->num_fences; j++) |
| dev_err(device->dev, " [%u] FENCE %s\n", |
| i, info->fences[j].name); |
| break; |
| } |
| case KGSL_CMD_SYNCPOINT_TYPE_TIMELINE: { |
| int j; |
| struct event_timeline_info *info = event->priv; |
| struct dma_fence *fence = event->fence; |
| bool retired = false; |
| bool signaled = test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, |
| &fence->flags); |
| const char *str = NULL; |
| |
| if (fence->ops->signaled && fence->ops->signaled(fence)) |
| retired = true; |
| |
| if (!retired) |
| str = "not retired"; |
| else if (retired && signaled) |
| str = "signaled"; |
| else if (retired && !signaled) |
| str = "retired but not signaled"; |
| dev_err(device->dev, " [%u] FENCE %s\n", |
| i, str); |
| for (j = 0; info && info[j].timeline; j++) |
| dev_err(device->dev, " TIMELINE %d SEQNO %lld\n", |
| info[j].timeline, info[j].seqno); |
| break; |
| } |
| } |
| } |
| |
| kgsl_drawobj_put(drawobj); |
| 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(&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); |
| |
| kgsl_drawobj_put(&event->syncobj->base); |
| } |
| |
| static void drawobj_sync_timeline_fence_work(struct work_struct *work) |
| { |
| struct kgsl_drawobj_sync_event *event = container_of(work, |
| struct kgsl_drawobj_sync_event, work); |
| |
| dma_fence_put(event->fence); |
| kgsl_drawobj_put(&event->syncobj->base); |
| } |
| |
| static void trace_syncpoint_timeline_fence(struct kgsl_drawobj_sync *syncobj, |
| struct dma_fence *f, bool expire) |
| { |
| struct dma_fence_array *array = to_dma_fence_array(f); |
| struct dma_fence **fences = &f; |
| u32 num_fences = 1; |
| int i; |
| |
| if (array) { |
| num_fences = array->num_fences; |
| fences = array->fences; |
| } |
| |
| for (i = 0; i < num_fences; i++) { |
| char fence_name[KGSL_FENCE_NAME_LEN]; |
| |
| snprintf(fence_name, sizeof(fence_name), "%s:%llu", |
| fences[i]->ops->get_timeline_name(fences[i]), |
| fences[i]->seqno); |
| if (expire) { |
| trace_syncpoint_fence_expire(syncobj, fence_name); |
| log_kgsl_syncpoint_fence_expire_event( |
| syncobj->base.context->id, fence_name); |
| } else { |
| trace_syncpoint_fence(syncobj, fence_name); |
| log_kgsl_syncpoint_fence_event( |
| syncobj->base.context->id, fence_name); |
| } |
| } |
| } |
| |
| static void drawobj_sync_timeline_fence_callback(struct dma_fence *f, |
| struct dma_fence_cb *cb) |
| { |
| struct kgsl_drawobj_sync_event *event = container_of(cb, |
| struct kgsl_drawobj_sync_event, cb); |
| |
| trace_syncpoint_timeline_fence(event->syncobj, f, true); |
| |
| /* |
| * Mark the event as synced and then fire off a worker to handle |
| * removing the fence |
| */ |
| if (drawobj_sync_expire(event->device, event)) |
| queue_work(kgsl_driver.lockless_workqueue, &event->work); |
| } |
| |
| static void syncobj_destroy(struct kgsl_drawobj *drawobj) |
| { |
| struct kgsl_drawobj_sync *syncobj = SYNCOBJ(drawobj); |
| 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: |
| kgsl_sync_fence_async_cancel(event->handle); |
| kgsl_drawobj_put(drawobj); |
| break; |
| case KGSL_CMD_SYNCPOINT_TYPE_TIMELINE: |
| dma_fence_remove_callback(event->fence, &event->cb); |
| dma_fence_put(event->fence); |
| kgsl_drawobj_put(drawobj); |
| 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_timelineobj_retire(struct kref *kref) |
| { |
| int i; |
| struct kgsl_drawobj_timeline *timelineobj = container_of(kref, |
| struct kgsl_drawobj_timeline, sig_refcount); |
| |
| for (i = 0; i < timelineobj->count; i++) { |
| kgsl_timeline_signal(timelineobj->timelines[i].timeline, |
| timelineobj->timelines[i].seqno); |
| |
| kgsl_timeline_put(timelineobj->timelines[i].timeline); |
| kgsl_context_put(timelineobj->timelines[i].context); |
| } |
| |
| kvfree(timelineobj->timelines); |
| timelineobj->timelines = NULL; |
| timelineobj->count = 0; |
| } |
| |
| static void kgsl_timelineobj_signal(struct kgsl_drawobj_timeline *timelineobj) |
| { |
| kref_put(&timelineobj->sig_refcount, _drawobj_timelineobj_retire); |
| } |
| |
| static void timelineobj_destroy(struct kgsl_drawobj *drawobj) |
| { |
| struct kgsl_drawobj_timeline *timelineobj = TIMELINEOBJ(drawobj); |
| int i; |
| |
| /* |
| * At this point any syncobjs blocking this timelinobj have been |
| * signaled. The timelineobj now only needs all preceding timestamps to |
| * retire before signaling the timelines. Notify timelines to keep them |
| * in sync with the timestamps as they retire. |
| */ |
| for (i = 0; i < timelineobj->count; i++) |
| kgsl_timeline_add_signal(&timelineobj->timelines[i]); |
| |
| /* |
| * The scheduler is done with the timelineobj. Put the initial |
| * sig_refcount to continue with the signaling process. |
| */ |
| kgsl_timelineobj_signal(timelineobj); |
| } |
| |
| static void bindobj_destroy(struct kgsl_drawobj *drawobj) |
| { |
| struct kgsl_drawobj_bind *bindobj = BINDOBJ(drawobj); |
| |
| kgsl_sharedmem_put_bind_op(bindobj->bind); |
| } |
| |
| static void cmdobj_destroy(struct kgsl_drawobj *drawobj) |
| { |
| struct kgsl_drawobj_cmd *cmdobj = CMDOBJ(drawobj); |
| struct kgsl_memobj_node *mem, *tmpmem; |
| |
| /* |
| * 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 command list */ |
| list_for_each_entry_safe(mem, tmpmem, &cmdobj->cmdlist, node) { |
| list_del_init(&mem->node); |
| kmem_cache_free(memobjs_cache, mem); |
| } |
| |
| /* Destroy the memory list */ |
| list_for_each_entry_safe(mem, tmpmem, &cmdobj->memlist, node) { |
| list_del_init(&mem->node); |
| kmem_cache_free(memobjs_cache, mem); |
| } |
| |
| if (drawobj->type & CMDOBJ_TYPE) { |
| atomic_dec(&drawobj->context->proc_priv->cmd_count); |
| atomic_dec(&drawobj->context->proc_priv->period->active_cmds); |
| } |
| } |
| |
| /** |
| * 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 (IS_ERR_OR_NULL(drawobj)) |
| return; |
| |
| drawobj->destroy(drawobj); |
| |
| kgsl_drawobj_put(drawobj); |
| } |
| |
| static bool drawobj_sync_fence_func(void *priv) |
| { |
| struct kgsl_drawobj_sync_event *event = priv; |
| struct event_fence_info *info = event->priv; |
| int i; |
| |
| for (i = 0; info && i < info->num_fences; i++) { |
| trace_syncpoint_fence_expire(event->syncobj, |
| info->fences[i].name); |
| log_kgsl_syncpoint_fence_expire_event( |
| event->syncobj->base.context->id, info->fences[i].name); |
| } |
| |
| /* |
| * Only call kgsl_drawobj_put() if it's not marked for cancellation |
| * in another thread. |
| */ |
| if (drawobj_sync_expire(event->device, event)) { |
| kgsl_drawobj_put(&event->syncobj->base); |
| return true; |
| } |
| return false; |
| } |
| |
| static struct event_timeline_info * |
| drawobj_get_sync_timeline_priv(void __user *uptr, u64 usize, u32 count) |
| { |
| int i; |
| struct event_timeline_info *priv; |
| |
| /* Make sure we don't accidently overflow count */ |
| if (count == UINT_MAX) |
| return NULL; |
| |
| priv = kcalloc(count + 1, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return NULL; |
| |
| for (i = 0; i < count; i++, uptr += usize) { |
| struct kgsl_timeline_val val; |
| |
| if (copy_struct_from_user(&val, sizeof(val), uptr, usize)) |
| continue; |
| |
| priv[i].timeline = val.timeline; |
| priv[i].seqno = val.seqno; |
| } |
| |
| priv[i].timeline = 0; |
| return priv; |
| } |
| |
| static int drawobj_add_sync_timeline(struct kgsl_device *device, |
| |
| struct kgsl_drawobj_sync *syncobj, void __user *uptr, |
| u64 usize) |
| { |
| struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj); |
| struct kgsl_cmd_syncpoint_timeline sync; |
| struct kgsl_drawobj_sync_event *event; |
| struct dma_fence *fence; |
| unsigned int id; |
| int ret; |
| |
| if (copy_struct_from_user(&sync, sizeof(sync), uptr, usize)) |
| return -EFAULT; |
| |
| fence = kgsl_timelines_to_fence_array(device, sync.timelines, |
| sync.count, sync.timelines_size, false); |
| if (IS_ERR(fence)) |
| return PTR_ERR(fence); |
| |
| kref_get(&drawobj->refcount); |
| |
| id = syncobj->numsyncs++; |
| |
| event = &syncobj->synclist[id]; |
| |
| event->id = id; |
| event->type = KGSL_CMD_SYNCPOINT_TYPE_TIMELINE; |
| event->syncobj = syncobj; |
| event->device = device; |
| event->context = NULL; |
| event->fence = fence; |
| INIT_WORK(&event->work, drawobj_sync_timeline_fence_work); |
| |
| INIT_LIST_HEAD(&event->cb.node); |
| |
| event->priv = |
| drawobj_get_sync_timeline_priv(u64_to_user_ptr(sync.timelines), |
| sync.timelines_size, sync.count); |
| |
| /* Set pending flag before adding callback to avoid race */ |
| set_bit(event->id, &syncobj->pending); |
| |
| /* Get a dma_fence refcount to hand over to the callback */ |
| dma_fence_get(event->fence); |
| ret = dma_fence_add_callback(event->fence, |
| &event->cb, drawobj_sync_timeline_fence_callback); |
| |
| if (ret) { |
| clear_bit(event->id, &syncobj->pending); |
| |
| if (dma_fence_is_signaled(event->fence)) { |
| trace_syncpoint_fence_expire(syncobj, "signaled"); |
| log_kgsl_syncpoint_fence_expire_event( |
| syncobj->base.context->id, "signaled"); |
| dma_fence_put(event->fence); |
| ret = 0; |
| } |
| |
| /* Put the refcount from fence creation */ |
| dma_fence_put(event->fence); |
| kgsl_drawobj_put(drawobj); |
| return ret; |
| } |
| |
| trace_syncpoint_timeline_fence(event->syncobj, event->fence, false); |
| |
| /* Put the refcount from fence creation */ |
| dma_fence_put(event->fence); |
| return 0; |
| } |
| |
| static int drawobj_add_sync_fence(struct kgsl_device *device, |
| struct kgsl_drawobj_sync *syncobj, void __user *data, |
| u64 datasize) |
| { |
| struct kgsl_cmd_syncpoint_fence sync; |
| struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj); |
| struct kgsl_drawobj_sync_event *event; |
| struct event_fence_info *priv; |
| unsigned int id, i; |
| |
| if (copy_struct_from_user(&sync, sizeof(sync), data, datasize)) |
| return -EFAULT; |
| |
| 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; |
| |
| priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
| |
| set_bit(event->id, &syncobj->pending); |
| |
| event->handle = kgsl_sync_fence_async_wait(sync.fd, drawobj_sync_fence_func, event); |
| |
| event->priv = priv; |
| |
| if (IS_ERR_OR_NULL(event->handle)) { |
| int ret = PTR_ERR(event->handle); |
| |
| clear_bit(event->id, &syncobj->pending); |
| event->handle = NULL; |
| |
| kgsl_drawobj_put(drawobj); |
| |
| /* |
| * If ret == 0 the fence was already signaled - print a trace |
| * message so we can track that |
| */ |
| if (ret == 0) { |
| trace_syncpoint_fence_expire(syncobj, "signaled"); |
| log_kgsl_syncpoint_fence_expire_event( |
| syncobj->base.context->id, "signaled"); |
| } |
| |
| return ret; |
| } |
| |
| kgsl_get_fence_info(event); |
| |
| for (i = 0; priv && i < priv->num_fences; i++) { |
| trace_syncpoint_fence(syncobj, priv->fences[i].name); |
| log_kgsl_syncpoint_fence_event(syncobj->base.context->id, |
| priv->fences[i].name); |
| } |
| |
| return 0; |
| } |
| |
| /* 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, |
| struct kgsl_cmd_syncpoint_timestamp *timestamp) |
| |
| { |
| struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj); |
| struct kgsl_context *context = kgsl_context_get(device, |
| timestamp->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(timestamp->timestamp, queued) > 0) { |
| dev_err(device->dev, |
| "Cannot create syncpoint for future timestamp %d (current %d)\n", |
| timestamp->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 = timestamp->timestamp; |
| event->device = device; |
| |
| set_bit(event->id, &syncobj->pending); |
| |
| ret = kgsl_add_event(device, &context->events, timestamp->timestamp, |
| drawobj_sync_func, event); |
| |
| if (ret) { |
| clear_bit(event->id, &syncobj->pending); |
| kgsl_drawobj_put(drawobj); |
| } else { |
| trace_syncpoint_timestamp(syncobj, context, |
| timestamp->timestamp); |
| } |
| |
| done: |
| if (ret) |
| kgsl_context_put(context); |
| |
| return ret; |
| } |
| |
| static int drawobj_add_sync_timestamp_from_user(struct kgsl_device *device, |
| struct kgsl_drawobj_sync *syncobj, void __user *data, |
| u64 datasize) |
| { |
| struct kgsl_cmd_syncpoint_timestamp timestamp; |
| |
| if (copy_struct_from_user(×tamp, sizeof(timestamp), |
| data, datasize)) |
| return -EFAULT; |
| |
| return drawobj_add_sync_timestamp(device, syncobj, ×tamp); |
| } |
| |
| /** |
| * 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) |
| { |
| struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj); |
| |
| if (sync->type != KGSL_CMD_SYNCPOINT_TYPE_FENCE) |
| syncobj->flags |= KGSL_SYNCOBJ_SW; |
| |
| if (sync->type == KGSL_CMD_SYNCPOINT_TYPE_TIMESTAMP) |
| return drawobj_add_sync_timestamp_from_user(device, |
| syncobj, sync->priv, sync->size); |
| else if (sync->type == KGSL_CMD_SYNCPOINT_TYPE_FENCE) |
| return drawobj_add_sync_fence(device, |
| syncobj, sync->priv, sync->size); |
| else if (sync->type == KGSL_CMD_SYNCPOINT_TYPE_TIMELINE) |
| return drawobj_add_sync_timeline(device, |
| syncobj, sync->priv, sync->size); |
| |
| dev_err(device->dev, "bad syncpoint type %d for ctxt %u\n", |
| sync->type, drawobj->context->id); |
| |
| return -EINVAL; |
| } |
| |
| 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); |
| u64 start; |
| |
| 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) { |
| start = id ? (entry->memdesc.gpuaddr + offset) : gpuaddr; |
| /* |
| * Make sure there is enough room in the object to store the |
| * entire profiling buffer object |
| */ |
| if (!kgsl_gpuaddr_in_memdesc(&entry->memdesc, gpuaddr, size) || |
| !kgsl_gpuaddr_in_memdesc(&entry->memdesc, start, |
| sizeof(struct kgsl_drawobj_profiling_buffer))) { |
| kgsl_mem_entry_put(entry); |
| entry = NULL; |
| } |
| } |
| |
| if (entry == NULL) { |
| dev_err(device->dev, |
| "ignore bad profile buffer ctxt %u id %d offset %lld gpuaddr %llx size %lld\n", |
| drawobj->context->id, id, offset, gpuaddr, size); |
| return; |
| } |
| |
| cmdobj->profiling_buffer_gpuaddr = start; |
| cmdobj->profiling_buf_entry = entry; |
| } |
| |
| /** |
| * 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 int drawobj_init(struct kgsl_device *device, |
| struct kgsl_context *context, struct kgsl_drawobj *drawobj, |
| int type) |
| { |
| /* |
| * Increase the reference count on the context so it doesn't disappear |
| * during the lifetime of this object |
| */ |
| if (!_kgsl_context_get(context)) |
| return -ENOENT; |
| |
| kref_init(&drawobj->refcount); |
| |
| drawobj->device = device; |
| drawobj->context = context; |
| drawobj->type = type; |
| |
| return 0; |
| } |
| |
| static int get_aux_command(void __user *ptr, u64 generic_size, |
| int type, void *auxcmd, size_t auxcmd_size) |
| { |
| struct kgsl_gpu_aux_command_generic generic; |
| u64 size; |
| |
| if (copy_struct_from_user(&generic, sizeof(generic), ptr, generic_size)) |
| return -EFAULT; |
| |
| if (generic.type != type) |
| return -EINVAL; |
| |
| size = min_t(u64, auxcmd_size, generic.size); |
| if (copy_from_user(auxcmd, u64_to_user_ptr(generic.priv), size)) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| struct kgsl_drawobj_timeline * |
| kgsl_drawobj_timeline_create(struct kgsl_device *device, |
| struct kgsl_context *context) |
| { |
| int ret; |
| struct kgsl_drawobj_timeline *timelineobj = |
| kzalloc(sizeof(*timelineobj), GFP_KERNEL); |
| |
| if (!timelineobj) |
| return ERR_PTR(-ENOMEM); |
| |
| ret = drawobj_init(device, context, &timelineobj->base, |
| TIMELINEOBJ_TYPE); |
| if (ret) { |
| kfree(timelineobj); |
| return ERR_PTR(ret); |
| } |
| |
| /* |
| * Initialize the sig_refcount that triggers the timeline signal. |
| * This refcount goes to 0 when: |
| * 1) This timelineobj is popped off the context queue. This implies |
| * any syncobj blocking this timelineobj was already signaled, or |
| * the context queue is cleaned up at detach time. |
| * 2) The cmdobjs queued on this context before this timeline object |
| * are retired. |
| */ |
| kref_init(&timelineobj->sig_refcount); |
| |
| timelineobj->base.destroy = timelineobj_destroy; |
| timelineobj->base.destroy_object = timelineobj_destroy_object; |
| |
| return timelineobj; |
| } |
| |
| static void _timeline_signaled(struct kgsl_device *device, |
| struct kgsl_event_group *group, void *priv, int ret) |
| { |
| struct kgsl_drawobj_timeline *timelineobj = priv; |
| struct kgsl_drawobj *drawobj = DRAWOBJ(timelineobj); |
| |
| /* Put the sig_refcount we took when registering this event */ |
| kgsl_timelineobj_signal(timelineobj); |
| |
| /* Put the drawobj refcount we took when registering this event */ |
| kgsl_drawobj_put(drawobj); |
| } |
| |
| int kgsl_drawobj_add_timeline(struct kgsl_device_private *dev_priv, |
| struct kgsl_drawobj_timeline *timelineobj, |
| void __user *src, u64 cmdsize) |
| { |
| struct kgsl_device *device = dev_priv->device; |
| struct kgsl_gpu_aux_command_timeline cmd; |
| struct kgsl_drawobj *drawobj = DRAWOBJ(timelineobj); |
| struct kgsl_context *context = drawobj->context; |
| int i, ret; |
| u32 queued; |
| |
| memset(&cmd, 0, sizeof(cmd)); |
| |
| ret = get_aux_command(src, cmdsize, |
| KGSL_GPU_AUX_COMMAND_TIMELINE, &cmd, sizeof(cmd)); |
| if (ret) |
| return ret; |
| |
| if (!cmd.count) |
| return -EINVAL; |
| |
| timelineobj->timelines = kvcalloc(cmd.count, |
| sizeof(*timelineobj->timelines), |
| GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN); |
| if (!timelineobj->timelines) |
| return -ENOMEM; |
| |
| src = u64_to_user_ptr(cmd.timelines); |
| |
| /* Get the last queued timestamp on the drawobj context */ |
| ret = kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_QUEUED, &queued); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < cmd.count; i++) { |
| struct kgsl_timeline_val val; |
| |
| if (copy_struct_from_user(&val, sizeof(val), src, |
| cmd.timelines_size)) { |
| ret = -EFAULT; |
| goto err; |
| } |
| |
| if (val.padding) { |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| timelineobj->timelines[i].timeline = |
| kgsl_timeline_by_id(dev_priv->device, |
| val.timeline); |
| |
| if (!timelineobj->timelines[i].timeline) { |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| /* Get a context refcount so we can use the context pointer */ |
| if (!_kgsl_context_get(context)) { |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| trace_kgsl_drawobj_timeline(val.timeline, val.seqno); |
| timelineobj->timelines[i].seqno = val.seqno; |
| timelineobj->timelines[i].context = context; |
| timelineobj->timelines[i].timestamp = queued; |
| |
| src += cmd.timelines_size; |
| } |
| |
| timelineobj->count = cmd.count; |
| |
| /* |
| * Register a kgsl_event to notify us when the last queued timestamp |
| * retires. Take a refcount on the drawobj to keep it valid for the |
| * callback, and take the sig_refcount to synchronize with the |
| * timelineobj retire. Both these refcounts are put in the callback. |
| */ |
| kref_get(&drawobj->refcount); |
| kref_get(&timelineobj->sig_refcount); |
| ret = kgsl_add_event(device, &context->events, queued, |
| _timeline_signaled, timelineobj); |
| |
| if (ret) |
| goto event_err; |
| |
| return 0; |
| |
| event_err: |
| /* |
| * If there was an error, put back sig_refcount and drawobj refcounts. |
| * The caller still holds initial refcounts on both and puts them in |
| * kgsl_drawobj_destroy(). Clean up the timelinelines array since we |
| * do not want to signal anything now. |
| */ |
| kgsl_timelineobj_signal(timelineobj); |
| kgsl_drawobj_put(drawobj); |
| err: |
| for (i = 0; i < cmd.count; i++) { |
| kgsl_timeline_put(timelineobj->timelines[i].timeline); |
| kgsl_context_put(timelineobj->timelines[i].context); |
| } |
| |
| kvfree(timelineobj->timelines); |
| timelineobj->timelines = NULL; |
| return ret; |
| } |
| |
| static void kgsl_drawobj_bind_callback(struct kgsl_sharedmem_bind_op *op) |
| { |
| struct kgsl_drawobj_bind *bindobj = op->data; |
| struct kgsl_drawobj *drawobj = DRAWOBJ(bindobj); |
| struct kgsl_device *device = drawobj->device; |
| |
| set_bit(KGSL_BINDOBJ_STATE_DONE, &bindobj->state); |
| |
| /* Re-schedule the context */ |
| if (device->ftbl->drawctxt_sched) |
| device->ftbl->drawctxt_sched(device, |
| drawobj->context); |
| |
| /* Put back the reference we took when we started the operation */ |
| kgsl_context_put(drawobj->context); |
| kgsl_drawobj_put(drawobj); |
| } |
| |
| int kgsl_drawobj_add_bind(struct kgsl_device_private *dev_priv, |
| struct kgsl_drawobj_bind *bindobj, |
| void __user *src, u64 cmdsize) |
| { |
| struct kgsl_gpu_aux_command_bind cmd; |
| struct kgsl_process_private *private = dev_priv->process_priv; |
| struct kgsl_sharedmem_bind_op *op; |
| int ret; |
| |
| ret = get_aux_command(src, cmdsize, |
| KGSL_GPU_AUX_COMMAND_BIND, &cmd, sizeof(cmd)); |
| if (ret) |
| return ret; |
| |
| op = kgsl_sharedmem_create_bind_op(private, cmd.target, |
| u64_to_user_ptr(cmd.rangeslist), cmd.numranges, |
| cmd.rangesize); |
| |
| if (IS_ERR(op)) |
| return PTR_ERR(op); |
| |
| op->callback = kgsl_drawobj_bind_callback; |
| op->data = bindobj; |
| |
| bindobj->bind = op; |
| return 0; |
| } |
| |
| struct kgsl_drawobj_bind *kgsl_drawobj_bind_create(struct kgsl_device *device, |
| struct kgsl_context *context) |
| { |
| int ret; |
| struct kgsl_drawobj_bind *bindobj = |
| kzalloc(sizeof(*bindobj), GFP_KERNEL); |
| |
| if (!bindobj) |
| return ERR_PTR(-ENOMEM); |
| |
| ret = drawobj_init(device, context, &bindobj->base, BINDOBJ_TYPE); |
| if (ret) { |
| kfree(bindobj); |
| return ERR_PTR(ret); |
| } |
| |
| bindobj->base.destroy = bindobj_destroy; |
| bindobj->base.destroy_object = bindobj_destroy_object; |
| |
| return bindobj; |
| } |
| |
| /** |
| * 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 = |
| kzalloc(sizeof(*syncobj), GFP_KERNEL); |
| int ret; |
| |
| if (!syncobj) |
| return ERR_PTR(-ENOMEM); |
| |
| ret = drawobj_init(device, context, &syncobj->base, SYNCOBJ_TYPE); |
| if (ret) { |
| kfree(syncobj); |
| return ERR_PTR(ret); |
| } |
| |
| syncobj->base.destroy = syncobj_destroy; |
| syncobj->base.destroy_object = syncobj_destroy_object; |
| |
| timer_setup(&syncobj->timer, syncobj_timer, 0); |
| |
| 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 = kzalloc(sizeof(*cmdobj), GFP_KERNEL); |
| int ret; |
| |
| if (!cmdobj) |
| return ERR_PTR(-ENOMEM); |
| |
| ret = drawobj_init(device, context, &cmdobj->base, |
| (type & (CMDOBJ_TYPE | MARKEROBJ_TYPE))); |
| if (ret) { |
| kfree(cmdobj); |
| return ERR_PTR(ret); |
| } |
| |
| cmdobj->base.destroy = cmdobj_destroy; |
| cmdobj->base.destroy_object = cmdobj_destroy_object; |
| |
| /* sanitize our flags for drawobjs */ |
| 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 |
| | KGSL_DRAWOBJ_START_RECURRING |
| | KGSL_DRAWOBJ_STOP_RECURRING); |
| |
| INIT_LIST_HEAD(&cmdobj->cmdlist); |
| INIT_LIST_HEAD(&cmdobj->memlist); |
| cmdobj->requeue_cnt = 0; |
| |
| if (!(type & CMDOBJ_TYPE)) |
| return cmdobj; |
| |
| atomic_inc(&context->proc_priv->cmd_count); |
| atomic_inc(&context->proc_priv->period->active_cmds); |
| spin_lock(&device->work_period_lock); |
| if (!__test_and_set_bit(KGSL_WORK_PERIOD, &device->flags)) { |
| mod_timer(&device->work_period_timer, |
| jiffies + msecs_to_jiffies(KGSL_WORK_PERIOD_MS)); |
| device->gpu_period.begin = ktime_get_ns(); |
| } |
| |
| /* Take a refcount here and put it back in kgsl_work_period_timer() */ |
| if (!__test_and_set_bit(KGSL_WORK_PERIOD, &context->proc_priv->period->flags)) |
| kref_get(&context->proc_priv->period->refcount); |
| |
| spin_unlock(&device->work_period_lock); |
| |
| 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; |
| } |
| |
| #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++) { |
| if (copy_struct_from_user(&obj, sizeof(obj), ptr, size)) |
| return -EFAULT; |
| |
| /* Sanity check the flags */ |
| if (!(obj.flags & CMDLIST_FLAGS)) { |
| dev_err(device->dev, |
| "invalid cmdobj ctxt %u flags %d id %d offset %llu addr %llx size %llu\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++) { |
| if (copy_struct_from_user(&obj, sizeof(obj), ptr, size)) |
| return -EFAULT; |
| |
| if (!(obj.flags & KGSL_OBJLIST_MEMOBJ)) { |
| dev_err(device->dev, |
| "invalid memobj ctxt %u 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; |
| } |
| |
| struct kgsl_drawobj_sync * |
| kgsl_drawobj_create_timestamp_syncobj(struct kgsl_device *device, |
| struct kgsl_context *context, unsigned int timestamp) |
| { |
| struct kgsl_drawobj_sync *syncobj; |
| struct kgsl_cmd_syncpoint_timestamp priv; |
| int ret; |
| |
| syncobj = kgsl_drawobj_sync_create(device, context); |
| if (IS_ERR(syncobj)) |
| return syncobj; |
| |
| syncobj->synclist = kzalloc(sizeof(*syncobj->synclist), GFP_KERNEL); |
| if (!syncobj->synclist) { |
| kgsl_drawobj_destroy(DRAWOBJ(syncobj)); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| priv.timestamp = timestamp; |
| priv.context_id = context->id; |
| |
| ret = drawobj_add_sync_timestamp(device, syncobj, &priv); |
| if (ret) { |
| kgsl_drawobj_destroy(DRAWOBJ(syncobj)); |
| return ERR_PTR(ret); |
| } |
| |
| return syncobj; |
| } |
| |
| 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++) { |
| if (copy_struct_from_user(&syncpoint, sizeof(syncpoint), ptr, size)) |
| return -EFAULT; |
| |
| sync.type = syncpoint.type; |
| sync.priv = u64_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); |
| } |
| |
| int kgsl_drawobjs_cache_init(void) |
| { |
| memobjs_cache = KMEM_CACHE(kgsl_memobj_node, 0); |
| |
| if (!memobjs_cache) |
| return -ENOMEM; |
| |
| return 0; |
| } |