linux/drivers/gpu/drm/v3d/v3d_submit.c
Harshit Mogalapalli dce94061f0 drm/v3d: Fix missing error code in v3d_submit_cpu_ioctl()
Smatch warns:
	drivers/gpu/drm/v3d/v3d_submit.c:1222 v3d_submit_cpu_ioctl()
	warn: missing error code 'ret'

When there is no job type or job is submitted with wrong number of BOs
it is an error path, ret is zero at this point which is incorrect
return.

Fix this by changing it to -EINVAL.

Fixes: aafc1a2bea67 ("drm/v3d: Add a CPU job submission")
Signed-off-by: Harshit Mogalapalli <harshit.m.mogalapalli@oracle.com>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Signed-off-by: Melissa Wen <melissa.srw@gmail.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20231204122102.181298-1-harshit.m.mogalapalli@oracle.com
2023-12-04 21:30:33 -01:00

1321 lines
32 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2014-2018 Broadcom
* Copyright (C) 2023 Raspberry Pi
*/
#include <drm/drm_syncobj.h>
#include "v3d_drv.h"
#include "v3d_regs.h"
#include "v3d_trace.h"
/* Takes the reservation lock on all the BOs being referenced, so that
* at queue submit time we can update the reservations.
*
* We don't lock the RCL the tile alloc/state BOs, or overflow memory
* (all of which are on exec->unref_list). They're entirely private
* to v3d, so we don't attach dma-buf fences to them.
*/
static int
v3d_lock_bo_reservations(struct v3d_job *job,
struct ww_acquire_ctx *acquire_ctx)
{
int i, ret;
ret = drm_gem_lock_reservations(job->bo, job->bo_count, acquire_ctx);
if (ret)
return ret;
for (i = 0; i < job->bo_count; i++) {
ret = dma_resv_reserve_fences(job->bo[i]->resv, 1);
if (ret)
goto fail;
ret = drm_sched_job_add_implicit_dependencies(&job->base,
job->bo[i], true);
if (ret)
goto fail;
}
return 0;
fail:
drm_gem_unlock_reservations(job->bo, job->bo_count, acquire_ctx);
return ret;
}
/**
* v3d_lookup_bos() - Sets up job->bo[] with the GEM objects
* referenced by the job.
* @dev: DRM device
* @file_priv: DRM file for this fd
* @job: V3D job being set up
* @bo_handles: GEM handles
* @bo_count: Number of GEM handles passed in
*
* The command validator needs to reference BOs by their index within
* the submitted job's BO list. This does the validation of the job's
* BO list and reference counting for the lifetime of the job.
*
* Note that this function doesn't need to unreference the BOs on
* failure, because that will happen at v3d_exec_cleanup() time.
*/
static int
v3d_lookup_bos(struct drm_device *dev,
struct drm_file *file_priv,
struct v3d_job *job,
u64 bo_handles,
u32 bo_count)
{
job->bo_count = bo_count;
if (!job->bo_count) {
/* See comment on bo_index for why we have to check
* this.
*/
DRM_DEBUG("Rendering requires BOs\n");
return -EINVAL;
}
return drm_gem_objects_lookup(file_priv,
(void __user *)(uintptr_t)bo_handles,
job->bo_count, &job->bo);
}
static void
v3d_job_free(struct kref *ref)
{
struct v3d_job *job = container_of(ref, struct v3d_job, refcount);
int i;
if (job->bo) {
for (i = 0; i < job->bo_count; i++)
drm_gem_object_put(job->bo[i]);
kvfree(job->bo);
}
dma_fence_put(job->irq_fence);
dma_fence_put(job->done_fence);
if (job->perfmon)
v3d_perfmon_put(job->perfmon);
kfree(job);
}
static void
v3d_render_job_free(struct kref *ref)
{
struct v3d_render_job *job = container_of(ref, struct v3d_render_job,
base.refcount);
struct v3d_bo *bo, *save;
list_for_each_entry_safe(bo, save, &job->unref_list, unref_head) {
drm_gem_object_put(&bo->base.base);
}
v3d_job_free(ref);
}
void v3d_job_cleanup(struct v3d_job *job)
{
if (!job)
return;
drm_sched_job_cleanup(&job->base);
v3d_job_put(job);
}
void v3d_job_put(struct v3d_job *job)
{
if (!job)
return;
kref_put(&job->refcount, job->free);
}
static int
v3d_job_allocate(void **container, size_t size)
{
*container = kcalloc(1, size, GFP_KERNEL);
if (!*container) {
DRM_ERROR("Cannot allocate memory for V3D job.\n");
return -ENOMEM;
}
return 0;
}
static int
v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
struct v3d_job *job, void (*free)(struct kref *ref),
u32 in_sync, struct v3d_submit_ext *se, enum v3d_queue queue)
{
struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
bool has_multisync = se && (se->flags & DRM_V3D_EXT_ID_MULTI_SYNC);
int ret, i;
job->v3d = v3d;
job->free = free;
job->file = file_priv;
ret = drm_sched_job_init(&job->base, &v3d_priv->sched_entity[queue],
1, v3d_priv);
if (ret)
return ret;
if (has_multisync) {
if (se->in_sync_count && se->wait_stage == queue) {
struct drm_v3d_sem __user *handle = u64_to_user_ptr(se->in_syncs);
for (i = 0; i < se->in_sync_count; i++) {
struct drm_v3d_sem in;
if (copy_from_user(&in, handle++, sizeof(in))) {
ret = -EFAULT;
DRM_DEBUG("Failed to copy wait dep handle.\n");
goto fail_deps;
}
ret = drm_sched_job_add_syncobj_dependency(&job->base, file_priv, in.handle, 0);
// TODO: Investigate why this was filtered out for the IOCTL.
if (ret && ret != -ENOENT)
goto fail_deps;
}
}
} else {
ret = drm_sched_job_add_syncobj_dependency(&job->base, file_priv, in_sync, 0);
// TODO: Investigate why this was filtered out for the IOCTL.
if (ret && ret != -ENOENT)
goto fail_deps;
}
kref_init(&job->refcount);
return 0;
fail_deps:
drm_sched_job_cleanup(&job->base);
return ret;
}
static void
v3d_push_job(struct v3d_job *job)
{
drm_sched_job_arm(&job->base);
job->done_fence = dma_fence_get(&job->base.s_fence->finished);
/* put by scheduler job completion */
kref_get(&job->refcount);
drm_sched_entity_push_job(&job->base);
}
static void
v3d_attach_fences_and_unlock_reservation(struct drm_file *file_priv,
struct v3d_job *job,
struct ww_acquire_ctx *acquire_ctx,
u32 out_sync,
struct v3d_submit_ext *se,
struct dma_fence *done_fence)
{
struct drm_syncobj *sync_out;
bool has_multisync = se && (se->flags & DRM_V3D_EXT_ID_MULTI_SYNC);
int i;
for (i = 0; i < job->bo_count; i++) {
/* XXX: Use shared fences for read-only objects. */
dma_resv_add_fence(job->bo[i]->resv, job->done_fence,
DMA_RESV_USAGE_WRITE);
}
drm_gem_unlock_reservations(job->bo, job->bo_count, acquire_ctx);
/* Update the return sync object for the job */
/* If it only supports a single signal semaphore*/
if (!has_multisync) {
sync_out = drm_syncobj_find(file_priv, out_sync);
if (sync_out) {
drm_syncobj_replace_fence(sync_out, done_fence);
drm_syncobj_put(sync_out);
}
return;
}
/* If multiple semaphores extension is supported */
if (se->out_sync_count) {
for (i = 0; i < se->out_sync_count; i++) {
drm_syncobj_replace_fence(se->out_syncs[i].syncobj,
done_fence);
drm_syncobj_put(se->out_syncs[i].syncobj);
}
kvfree(se->out_syncs);
}
}
static int
v3d_setup_csd_jobs_and_bos(struct drm_file *file_priv,
struct v3d_dev *v3d,
struct drm_v3d_submit_csd *args,
struct v3d_csd_job **job,
struct v3d_job **clean_job,
struct v3d_submit_ext *se,
struct ww_acquire_ctx *acquire_ctx)
{
int ret;
ret = v3d_job_allocate((void *)job, sizeof(**job));
if (ret)
return ret;
ret = v3d_job_init(v3d, file_priv, &(*job)->base,
v3d_job_free, args->in_sync, se, V3D_CSD);
if (ret)
return ret;
ret = v3d_job_allocate((void *)clean_job, sizeof(**clean_job));
if (ret)
return ret;
ret = v3d_job_init(v3d, file_priv, *clean_job,
v3d_job_free, 0, NULL, V3D_CACHE_CLEAN);
if (ret)
return ret;
(*job)->args = *args;
ret = v3d_lookup_bos(&v3d->drm, file_priv, *clean_job,
args->bo_handles, args->bo_handle_count);
if (ret)
return ret;
return v3d_lock_bo_reservations(*clean_job, acquire_ctx);
}
static void
v3d_put_multisync_post_deps(struct v3d_submit_ext *se)
{
unsigned int i;
if (!(se && se->out_sync_count))
return;
for (i = 0; i < se->out_sync_count; i++)
drm_syncobj_put(se->out_syncs[i].syncobj);
kvfree(se->out_syncs);
}
static int
v3d_get_multisync_post_deps(struct drm_file *file_priv,
struct v3d_submit_ext *se,
u32 count, u64 handles)
{
struct drm_v3d_sem __user *post_deps;
int i, ret;
if (!count)
return 0;
se->out_syncs = (struct v3d_submit_outsync *)
kvmalloc_array(count,
sizeof(struct v3d_submit_outsync),
GFP_KERNEL);
if (!se->out_syncs)
return -ENOMEM;
post_deps = u64_to_user_ptr(handles);
for (i = 0; i < count; i++) {
struct drm_v3d_sem out;
if (copy_from_user(&out, post_deps++, sizeof(out))) {
ret = -EFAULT;
DRM_DEBUG("Failed to copy post dep handles\n");
goto fail;
}
se->out_syncs[i].syncobj = drm_syncobj_find(file_priv,
out.handle);
if (!se->out_syncs[i].syncobj) {
ret = -EINVAL;
goto fail;
}
}
se->out_sync_count = count;
return 0;
fail:
for (i--; i >= 0; i--)
drm_syncobj_put(se->out_syncs[i].syncobj);
kvfree(se->out_syncs);
return ret;
}
/* Get data for multiple binary semaphores synchronization. Parse syncobj
* to be signaled when job completes (out_sync).
*/
static int
v3d_get_multisync_submit_deps(struct drm_file *file_priv,
struct drm_v3d_extension __user *ext,
struct v3d_submit_ext *se)
{
struct drm_v3d_multi_sync multisync;
int ret;
if (se->in_sync_count || se->out_sync_count) {
DRM_DEBUG("Two multisync extensions were added to the same job.");
return -EINVAL;
}
if (copy_from_user(&multisync, ext, sizeof(multisync)))
return -EFAULT;
if (multisync.pad)
return -EINVAL;
ret = v3d_get_multisync_post_deps(file_priv, se, multisync.out_sync_count,
multisync.out_syncs);
if (ret)
return ret;
se->in_sync_count = multisync.in_sync_count;
se->in_syncs = multisync.in_syncs;
se->flags |= DRM_V3D_EXT_ID_MULTI_SYNC;
se->wait_stage = multisync.wait_stage;
return 0;
}
/* Get data for the indirect CSD job submission. */
static int
v3d_get_cpu_indirect_csd_params(struct drm_file *file_priv,
struct drm_v3d_extension __user *ext,
struct v3d_cpu_job *job)
{
struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
struct v3d_dev *v3d = v3d_priv->v3d;
struct drm_v3d_indirect_csd indirect_csd;
struct v3d_indirect_csd_info *info = &job->indirect_csd;
if (!job) {
DRM_DEBUG("CPU job extension was attached to a GPU job.\n");
return -EINVAL;
}
if (job->job_type) {
DRM_DEBUG("Two CPU job extensions were added to the same CPU job.\n");
return -EINVAL;
}
if (copy_from_user(&indirect_csd, ext, sizeof(indirect_csd)))
return -EFAULT;
if (!v3d_has_csd(v3d)) {
DRM_DEBUG("Attempting CSD submit on non-CSD hardware.\n");
return -EINVAL;
}
job->job_type = V3D_CPU_JOB_TYPE_INDIRECT_CSD;
info->offset = indirect_csd.offset;
info->wg_size = indirect_csd.wg_size;
memcpy(&info->wg_uniform_offsets, &indirect_csd.wg_uniform_offsets,
sizeof(indirect_csd.wg_uniform_offsets));
info->indirect = drm_gem_object_lookup(file_priv, indirect_csd.indirect);
return v3d_setup_csd_jobs_and_bos(file_priv, v3d, &indirect_csd.submit,
&info->job, &info->clean_job,
NULL, &info->acquire_ctx);
}
/* Get data for the query timestamp job submission. */
static int
v3d_get_cpu_timestamp_query_params(struct drm_file *file_priv,
struct drm_v3d_extension __user *ext,
struct v3d_cpu_job *job)
{
u32 __user *offsets, *syncs;
struct drm_v3d_timestamp_query timestamp;
if (!job) {
DRM_DEBUG("CPU job extension was attached to a GPU job.\n");
return -EINVAL;
}
if (job->job_type) {
DRM_DEBUG("Two CPU job extensions were added to the same CPU job.\n");
return -EINVAL;
}
if (copy_from_user(&timestamp, ext, sizeof(timestamp)))
return -EFAULT;
if (timestamp.pad)
return -EINVAL;
job->job_type = V3D_CPU_JOB_TYPE_TIMESTAMP_QUERY;
job->timestamp_query.queries = kvmalloc_array(timestamp.count,
sizeof(struct v3d_timestamp_query),
GFP_KERNEL);
if (!job->timestamp_query.queries)
return -ENOMEM;
offsets = u64_to_user_ptr(timestamp.offsets);
syncs = u64_to_user_ptr(timestamp.syncs);
for (int i = 0; i < timestamp.count; i++) {
u32 offset, sync;
if (copy_from_user(&offset, offsets++, sizeof(offset))) {
kvfree(job->timestamp_query.queries);
return -EFAULT;
}
job->timestamp_query.queries[i].offset = offset;
if (copy_from_user(&sync, syncs++, sizeof(sync))) {
kvfree(job->timestamp_query.queries);
return -EFAULT;
}
job->timestamp_query.queries[i].syncobj = drm_syncobj_find(file_priv, sync);
}
job->timestamp_query.count = timestamp.count;
return 0;
}
static int
v3d_get_cpu_reset_timestamp_params(struct drm_file *file_priv,
struct drm_v3d_extension __user *ext,
struct v3d_cpu_job *job)
{
u32 __user *syncs;
struct drm_v3d_reset_timestamp_query reset;
if (!job) {
DRM_DEBUG("CPU job extension was attached to a GPU job.\n");
return -EINVAL;
}
if (job->job_type) {
DRM_DEBUG("Two CPU job extensions were added to the same CPU job.\n");
return -EINVAL;
}
if (copy_from_user(&reset, ext, sizeof(reset)))
return -EFAULT;
job->job_type = V3D_CPU_JOB_TYPE_RESET_TIMESTAMP_QUERY;
job->timestamp_query.queries = kvmalloc_array(reset.count,
sizeof(struct v3d_timestamp_query),
GFP_KERNEL);
if (!job->timestamp_query.queries)
return -ENOMEM;
syncs = u64_to_user_ptr(reset.syncs);
for (int i = 0; i < reset.count; i++) {
u32 sync;
job->timestamp_query.queries[i].offset = reset.offset + 8 * i;
if (copy_from_user(&sync, syncs++, sizeof(sync))) {
kvfree(job->timestamp_query.queries);
return -EFAULT;
}
job->timestamp_query.queries[i].syncobj = drm_syncobj_find(file_priv, sync);
}
job->timestamp_query.count = reset.count;
return 0;
}
/* Get data for the copy timestamp query results job submission. */
static int
v3d_get_cpu_copy_query_results_params(struct drm_file *file_priv,
struct drm_v3d_extension __user *ext,
struct v3d_cpu_job *job)
{
u32 __user *offsets, *syncs;
struct drm_v3d_copy_timestamp_query copy;
int i;
if (!job) {
DRM_DEBUG("CPU job extension was attached to a GPU job.\n");
return -EINVAL;
}
if (job->job_type) {
DRM_DEBUG("Two CPU job extensions were added to the same CPU job.\n");
return -EINVAL;
}
if (copy_from_user(&copy, ext, sizeof(copy)))
return -EFAULT;
if (copy.pad)
return -EINVAL;
job->job_type = V3D_CPU_JOB_TYPE_COPY_TIMESTAMP_QUERY;
job->timestamp_query.queries = kvmalloc_array(copy.count,
sizeof(struct v3d_timestamp_query),
GFP_KERNEL);
if (!job->timestamp_query.queries)
return -ENOMEM;
offsets = u64_to_user_ptr(copy.offsets);
syncs = u64_to_user_ptr(copy.syncs);
for (i = 0; i < copy.count; i++) {
u32 offset, sync;
if (copy_from_user(&offset, offsets++, sizeof(offset))) {
kvfree(job->timestamp_query.queries);
return -EFAULT;
}
job->timestamp_query.queries[i].offset = offset;
if (copy_from_user(&sync, syncs++, sizeof(sync))) {
kvfree(job->timestamp_query.queries);
return -EFAULT;
}
job->timestamp_query.queries[i].syncobj = drm_syncobj_find(file_priv, sync);
}
job->timestamp_query.count = copy.count;
job->copy.do_64bit = copy.do_64bit;
job->copy.do_partial = copy.do_partial;
job->copy.availability_bit = copy.availability_bit;
job->copy.offset = copy.offset;
job->copy.stride = copy.stride;
return 0;
}
static int
v3d_get_cpu_reset_performance_params(struct drm_file *file_priv,
struct drm_v3d_extension __user *ext,
struct v3d_cpu_job *job)
{
u32 __user *syncs;
u64 __user *kperfmon_ids;
struct drm_v3d_reset_performance_query reset;
if (!job) {
DRM_DEBUG("CPU job extension was attached to a GPU job.\n");
return -EINVAL;
}
if (job->job_type) {
DRM_DEBUG("Two CPU job extensions were added to the same CPU job.\n");
return -EINVAL;
}
if (copy_from_user(&reset, ext, sizeof(reset)))
return -EFAULT;
job->job_type = V3D_CPU_JOB_TYPE_RESET_PERFORMANCE_QUERY;
job->performance_query.queries = kvmalloc_array(reset.count,
sizeof(struct v3d_performance_query),
GFP_KERNEL);
if (!job->performance_query.queries)
return -ENOMEM;
syncs = u64_to_user_ptr(reset.syncs);
kperfmon_ids = u64_to_user_ptr(reset.kperfmon_ids);
for (int i = 0; i < reset.count; i++) {
u32 sync;
u64 ids;
u32 __user *ids_pointer;
u32 id;
if (copy_from_user(&sync, syncs++, sizeof(sync))) {
kvfree(job->performance_query.queries);
return -EFAULT;
}
job->performance_query.queries[i].syncobj = drm_syncobj_find(file_priv, sync);
if (copy_from_user(&ids, kperfmon_ids++, sizeof(ids))) {
kvfree(job->performance_query.queries);
return -EFAULT;
}
ids_pointer = u64_to_user_ptr(ids);
for (int j = 0; j < reset.nperfmons; j++) {
if (copy_from_user(&id, ids_pointer++, sizeof(id))) {
kvfree(job->performance_query.queries);
return -EFAULT;
}
job->performance_query.queries[i].kperfmon_ids[j] = id;
}
}
job->performance_query.count = reset.count;
job->performance_query.nperfmons = reset.nperfmons;
return 0;
}
static int
v3d_get_cpu_copy_performance_query_params(struct drm_file *file_priv,
struct drm_v3d_extension __user *ext,
struct v3d_cpu_job *job)
{
u32 __user *syncs;
u64 __user *kperfmon_ids;
struct drm_v3d_copy_performance_query copy;
if (!job) {
DRM_DEBUG("CPU job extension was attached to a GPU job.\n");
return -EINVAL;
}
if (job->job_type) {
DRM_DEBUG("Two CPU job extensions were added to the same CPU job.\n");
return -EINVAL;
}
if (copy_from_user(&copy, ext, sizeof(copy)))
return -EFAULT;
if (copy.pad)
return -EINVAL;
job->job_type = V3D_CPU_JOB_TYPE_COPY_PERFORMANCE_QUERY;
job->performance_query.queries = kvmalloc_array(copy.count,
sizeof(struct v3d_performance_query),
GFP_KERNEL);
if (!job->performance_query.queries)
return -ENOMEM;
syncs = u64_to_user_ptr(copy.syncs);
kperfmon_ids = u64_to_user_ptr(copy.kperfmon_ids);
for (int i = 0; i < copy.count; i++) {
u32 sync;
u64 ids;
u32 __user *ids_pointer;
u32 id;
if (copy_from_user(&sync, syncs++, sizeof(sync))) {
kvfree(job->performance_query.queries);
return -EFAULT;
}
job->performance_query.queries[i].syncobj = drm_syncobj_find(file_priv, sync);
if (copy_from_user(&ids, kperfmon_ids++, sizeof(ids))) {
kvfree(job->performance_query.queries);
return -EFAULT;
}
ids_pointer = u64_to_user_ptr(ids);
for (int j = 0; j < copy.nperfmons; j++) {
if (copy_from_user(&id, ids_pointer++, sizeof(id))) {
kvfree(job->performance_query.queries);
return -EFAULT;
}
job->performance_query.queries[i].kperfmon_ids[j] = id;
}
}
job->performance_query.count = copy.count;
job->performance_query.nperfmons = copy.nperfmons;
job->performance_query.ncounters = copy.ncounters;
job->copy.do_64bit = copy.do_64bit;
job->copy.do_partial = copy.do_partial;
job->copy.availability_bit = copy.availability_bit;
job->copy.offset = copy.offset;
job->copy.stride = copy.stride;
return 0;
}
/* Whenever userspace sets ioctl extensions, v3d_get_extensions parses data
* according to the extension id (name).
*/
static int
v3d_get_extensions(struct drm_file *file_priv,
u64 ext_handles,
struct v3d_submit_ext *se,
struct v3d_cpu_job *job)
{
struct drm_v3d_extension __user *user_ext;
int ret;
user_ext = u64_to_user_ptr(ext_handles);
while (user_ext) {
struct drm_v3d_extension ext;
if (copy_from_user(&ext, user_ext, sizeof(ext))) {
DRM_DEBUG("Failed to copy submit extension\n");
return -EFAULT;
}
switch (ext.id) {
case DRM_V3D_EXT_ID_MULTI_SYNC:
ret = v3d_get_multisync_submit_deps(file_priv, user_ext, se);
break;
case DRM_V3D_EXT_ID_CPU_INDIRECT_CSD:
ret = v3d_get_cpu_indirect_csd_params(file_priv, user_ext, job);
break;
case DRM_V3D_EXT_ID_CPU_TIMESTAMP_QUERY:
ret = v3d_get_cpu_timestamp_query_params(file_priv, user_ext, job);
break;
case DRM_V3D_EXT_ID_CPU_RESET_TIMESTAMP_QUERY:
ret = v3d_get_cpu_reset_timestamp_params(file_priv, user_ext, job);
break;
case DRM_V3D_EXT_ID_CPU_COPY_TIMESTAMP_QUERY:
ret = v3d_get_cpu_copy_query_results_params(file_priv, user_ext, job);
break;
case DRM_V3D_EXT_ID_CPU_RESET_PERFORMANCE_QUERY:
ret = v3d_get_cpu_reset_performance_params(file_priv, user_ext, job);
break;
case DRM_V3D_EXT_ID_CPU_COPY_PERFORMANCE_QUERY:
ret = v3d_get_cpu_copy_performance_query_params(file_priv, user_ext, job);
break;
default:
DRM_DEBUG_DRIVER("Unknown extension id: %d\n", ext.id);
return -EINVAL;
}
if (ret)
return ret;
user_ext = u64_to_user_ptr(ext.next);
}
return 0;
}
/**
* v3d_submit_cl_ioctl() - Submits a job (frame) to the V3D.
* @dev: DRM device
* @data: ioctl argument
* @file_priv: DRM file for this fd
*
* This is the main entrypoint for userspace to submit a 3D frame to
* the GPU. Userspace provides the binner command list (if
* applicable), and the kernel sets up the render command list to draw
* to the framebuffer described in the ioctl, using the command lists
* that the 3D engine's binner will produce.
*/
int
v3d_submit_cl_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct v3d_dev *v3d = to_v3d_dev(dev);
struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
struct drm_v3d_submit_cl *args = data;
struct v3d_submit_ext se = {0};
struct v3d_bin_job *bin = NULL;
struct v3d_render_job *render = NULL;
struct v3d_job *clean_job = NULL;
struct v3d_job *last_job;
struct ww_acquire_ctx acquire_ctx;
int ret = 0;
trace_v3d_submit_cl_ioctl(&v3d->drm, args->rcl_start, args->rcl_end);
if (args->pad)
return -EINVAL;
if (args->flags &&
args->flags & ~(DRM_V3D_SUBMIT_CL_FLUSH_CACHE |
DRM_V3D_SUBMIT_EXTENSION)) {
DRM_INFO("invalid flags: %d\n", args->flags);
return -EINVAL;
}
if (args->flags & DRM_V3D_SUBMIT_EXTENSION) {
ret = v3d_get_extensions(file_priv, args->extensions, &se, NULL);
if (ret) {
DRM_DEBUG("Failed to get extensions.\n");
return ret;
}
}
ret = v3d_job_allocate((void *)&render, sizeof(*render));
if (ret)
return ret;
ret = v3d_job_init(v3d, file_priv, &render->base,
v3d_render_job_free, args->in_sync_rcl, &se, V3D_RENDER);
if (ret)
goto fail;
render->start = args->rcl_start;
render->end = args->rcl_end;
INIT_LIST_HEAD(&render->unref_list);
if (args->bcl_start != args->bcl_end) {
ret = v3d_job_allocate((void *)&bin, sizeof(*bin));
if (ret)
goto fail;
ret = v3d_job_init(v3d, file_priv, &bin->base,
v3d_job_free, args->in_sync_bcl, &se, V3D_BIN);
if (ret)
goto fail;
bin->start = args->bcl_start;
bin->end = args->bcl_end;
bin->qma = args->qma;
bin->qms = args->qms;
bin->qts = args->qts;
bin->render = render;
}
if (args->flags & DRM_V3D_SUBMIT_CL_FLUSH_CACHE) {
ret = v3d_job_allocate((void *)&clean_job, sizeof(*clean_job));
if (ret)
goto fail;
ret = v3d_job_init(v3d, file_priv, clean_job,
v3d_job_free, 0, NULL, V3D_CACHE_CLEAN);
if (ret)
goto fail;
last_job = clean_job;
} else {
last_job = &render->base;
}
ret = v3d_lookup_bos(dev, file_priv, last_job,
args->bo_handles, args->bo_handle_count);
if (ret)
goto fail;
ret = v3d_lock_bo_reservations(last_job, &acquire_ctx);
if (ret)
goto fail;
if (args->perfmon_id) {
render->base.perfmon = v3d_perfmon_find(v3d_priv,
args->perfmon_id);
if (!render->base.perfmon) {
ret = -ENOENT;
goto fail_perfmon;
}
}
mutex_lock(&v3d->sched_lock);
if (bin) {
bin->base.perfmon = render->base.perfmon;
v3d_perfmon_get(bin->base.perfmon);
v3d_push_job(&bin->base);
ret = drm_sched_job_add_dependency(&render->base.base,
dma_fence_get(bin->base.done_fence));
if (ret)
goto fail_unreserve;
}
v3d_push_job(&render->base);
if (clean_job) {
struct dma_fence *render_fence =
dma_fence_get(render->base.done_fence);
ret = drm_sched_job_add_dependency(&clean_job->base,
render_fence);
if (ret)
goto fail_unreserve;
clean_job->perfmon = render->base.perfmon;
v3d_perfmon_get(clean_job->perfmon);
v3d_push_job(clean_job);
}
mutex_unlock(&v3d->sched_lock);
v3d_attach_fences_and_unlock_reservation(file_priv,
last_job,
&acquire_ctx,
args->out_sync,
&se,
last_job->done_fence);
v3d_job_put(&bin->base);
v3d_job_put(&render->base);
v3d_job_put(clean_job);
return 0;
fail_unreserve:
mutex_unlock(&v3d->sched_lock);
fail_perfmon:
drm_gem_unlock_reservations(last_job->bo,
last_job->bo_count, &acquire_ctx);
fail:
v3d_job_cleanup((void *)bin);
v3d_job_cleanup((void *)render);
v3d_job_cleanup(clean_job);
v3d_put_multisync_post_deps(&se);
return ret;
}
/**
* v3d_submit_tfu_ioctl() - Submits a TFU (texture formatting) job to the V3D.
* @dev: DRM device
* @data: ioctl argument
* @file_priv: DRM file for this fd
*
* Userspace provides the register setup for the TFU, which we don't
* need to validate since the TFU is behind the MMU.
*/
int
v3d_submit_tfu_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct v3d_dev *v3d = to_v3d_dev(dev);
struct drm_v3d_submit_tfu *args = data;
struct v3d_submit_ext se = {0};
struct v3d_tfu_job *job = NULL;
struct ww_acquire_ctx acquire_ctx;
int ret = 0;
trace_v3d_submit_tfu_ioctl(&v3d->drm, args->iia);
if (args->flags && !(args->flags & DRM_V3D_SUBMIT_EXTENSION)) {
DRM_DEBUG("invalid flags: %d\n", args->flags);
return -EINVAL;
}
if (args->flags & DRM_V3D_SUBMIT_EXTENSION) {
ret = v3d_get_extensions(file_priv, args->extensions, &se, NULL);
if (ret) {
DRM_DEBUG("Failed to get extensions.\n");
return ret;
}
}
ret = v3d_job_allocate((void *)&job, sizeof(*job));
if (ret)
return ret;
ret = v3d_job_init(v3d, file_priv, &job->base,
v3d_job_free, args->in_sync, &se, V3D_TFU);
if (ret)
goto fail;
job->base.bo = kcalloc(ARRAY_SIZE(args->bo_handles),
sizeof(*job->base.bo), GFP_KERNEL);
if (!job->base.bo) {
ret = -ENOMEM;
goto fail;
}
job->args = *args;
for (job->base.bo_count = 0;
job->base.bo_count < ARRAY_SIZE(args->bo_handles);
job->base.bo_count++) {
struct drm_gem_object *bo;
if (!args->bo_handles[job->base.bo_count])
break;
bo = drm_gem_object_lookup(file_priv, args->bo_handles[job->base.bo_count]);
if (!bo) {
DRM_DEBUG("Failed to look up GEM BO %d: %d\n",
job->base.bo_count,
args->bo_handles[job->base.bo_count]);
ret = -ENOENT;
goto fail;
}
job->base.bo[job->base.bo_count] = bo;
}
ret = v3d_lock_bo_reservations(&job->base, &acquire_ctx);
if (ret)
goto fail;
mutex_lock(&v3d->sched_lock);
v3d_push_job(&job->base);
mutex_unlock(&v3d->sched_lock);
v3d_attach_fences_and_unlock_reservation(file_priv,
&job->base, &acquire_ctx,
args->out_sync,
&se,
job->base.done_fence);
v3d_job_put(&job->base);
return 0;
fail:
v3d_job_cleanup((void *)job);
v3d_put_multisync_post_deps(&se);
return ret;
}
/**
* v3d_submit_csd_ioctl() - Submits a CSD (compute shader) job to the V3D.
* @dev: DRM device
* @data: ioctl argument
* @file_priv: DRM file for this fd
*
* Userspace provides the register setup for the CSD, which we don't
* need to validate since the CSD is behind the MMU.
*/
int
v3d_submit_csd_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct v3d_dev *v3d = to_v3d_dev(dev);
struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
struct drm_v3d_submit_csd *args = data;
struct v3d_submit_ext se = {0};
struct v3d_csd_job *job = NULL;
struct v3d_job *clean_job = NULL;
struct ww_acquire_ctx acquire_ctx;
int ret;
trace_v3d_submit_csd_ioctl(&v3d->drm, args->cfg[5], args->cfg[6]);
if (args->pad)
return -EINVAL;
if (!v3d_has_csd(v3d)) {
DRM_DEBUG("Attempting CSD submit on non-CSD hardware\n");
return -EINVAL;
}
if (args->flags && !(args->flags & DRM_V3D_SUBMIT_EXTENSION)) {
DRM_INFO("invalid flags: %d\n", args->flags);
return -EINVAL;
}
if (args->flags & DRM_V3D_SUBMIT_EXTENSION) {
ret = v3d_get_extensions(file_priv, args->extensions, &se, NULL);
if (ret) {
DRM_DEBUG("Failed to get extensions.\n");
return ret;
}
}
ret = v3d_setup_csd_jobs_and_bos(file_priv, v3d, args,
&job, &clean_job, &se,
&acquire_ctx);
if (ret)
goto fail;
if (args->perfmon_id) {
job->base.perfmon = v3d_perfmon_find(v3d_priv,
args->perfmon_id);
if (!job->base.perfmon) {
ret = -ENOENT;
goto fail_perfmon;
}
}
mutex_lock(&v3d->sched_lock);
v3d_push_job(&job->base);
ret = drm_sched_job_add_dependency(&clean_job->base,
dma_fence_get(job->base.done_fence));
if (ret)
goto fail_unreserve;
v3d_push_job(clean_job);
mutex_unlock(&v3d->sched_lock);
v3d_attach_fences_and_unlock_reservation(file_priv,
clean_job,
&acquire_ctx,
args->out_sync,
&se,
clean_job->done_fence);
v3d_job_put(&job->base);
v3d_job_put(clean_job);
return 0;
fail_unreserve:
mutex_unlock(&v3d->sched_lock);
fail_perfmon:
drm_gem_unlock_reservations(clean_job->bo, clean_job->bo_count,
&acquire_ctx);
fail:
v3d_job_cleanup((void *)job);
v3d_job_cleanup(clean_job);
v3d_put_multisync_post_deps(&se);
return ret;
}
static const unsigned int cpu_job_bo_handle_count[] = {
[V3D_CPU_JOB_TYPE_INDIRECT_CSD] = 1,
[V3D_CPU_JOB_TYPE_TIMESTAMP_QUERY] = 1,
[V3D_CPU_JOB_TYPE_RESET_TIMESTAMP_QUERY] = 1,
[V3D_CPU_JOB_TYPE_COPY_TIMESTAMP_QUERY] = 2,
[V3D_CPU_JOB_TYPE_RESET_PERFORMANCE_QUERY] = 0,
[V3D_CPU_JOB_TYPE_COPY_PERFORMANCE_QUERY] = 1,
};
/**
* v3d_submit_cpu_ioctl() - Submits a CPU job to the V3D.
* @dev: DRM device
* @data: ioctl argument
* @file_priv: DRM file for this fd
*
* Userspace specifies the CPU job type and data required to perform its
* operations through the drm_v3d_extension struct.
*/
int
v3d_submit_cpu_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct v3d_dev *v3d = to_v3d_dev(dev);
struct drm_v3d_submit_cpu *args = data;
struct v3d_submit_ext se = {0};
struct v3d_submit_ext *out_se = NULL;
struct v3d_cpu_job *cpu_job = NULL;
struct v3d_csd_job *csd_job = NULL;
struct v3d_job *clean_job = NULL;
struct ww_acquire_ctx acquire_ctx;
int ret;
if (args->flags && !(args->flags & DRM_V3D_SUBMIT_EXTENSION)) {
DRM_INFO("Invalid flags: %d\n", args->flags);
return -EINVAL;
}
ret = v3d_job_allocate((void *)&cpu_job, sizeof(*cpu_job));
if (ret)
return ret;
if (args->flags & DRM_V3D_SUBMIT_EXTENSION) {
ret = v3d_get_extensions(file_priv, args->extensions, &se, cpu_job);
if (ret) {
DRM_DEBUG("Failed to get extensions.\n");
goto fail;
}
}
/* Every CPU job must have a CPU job user extension */
if (!cpu_job->job_type) {
DRM_DEBUG("CPU job must have a CPU job user extension.\n");
ret = -EINVAL;
goto fail;
}
if (args->bo_handle_count != cpu_job_bo_handle_count[cpu_job->job_type]) {
DRM_DEBUG("This CPU job was not submitted with the proper number of BOs.\n");
ret = -EINVAL;
goto fail;
}
trace_v3d_submit_cpu_ioctl(&v3d->drm, cpu_job->job_type);
ret = v3d_job_init(v3d, file_priv, &cpu_job->base,
v3d_job_free, 0, &se, V3D_CPU);
if (ret)
goto fail;
clean_job = cpu_job->indirect_csd.clean_job;
csd_job = cpu_job->indirect_csd.job;
if (args->bo_handle_count) {
ret = v3d_lookup_bos(dev, file_priv, &cpu_job->base,
args->bo_handles, args->bo_handle_count);
if (ret)
goto fail;
ret = v3d_lock_bo_reservations(&cpu_job->base, &acquire_ctx);
if (ret)
goto fail;
}
mutex_lock(&v3d->sched_lock);
v3d_push_job(&cpu_job->base);
switch (cpu_job->job_type) {
case V3D_CPU_JOB_TYPE_INDIRECT_CSD:
ret = drm_sched_job_add_dependency(&csd_job->base.base,
dma_fence_get(cpu_job->base.done_fence));
if (ret)
goto fail_unreserve;
v3d_push_job(&csd_job->base);
ret = drm_sched_job_add_dependency(&clean_job->base,
dma_fence_get(csd_job->base.done_fence));
if (ret)
goto fail_unreserve;
v3d_push_job(clean_job);
break;
default:
break;
}
mutex_unlock(&v3d->sched_lock);
out_se = (cpu_job->job_type == V3D_CPU_JOB_TYPE_INDIRECT_CSD) ? NULL : &se;
v3d_attach_fences_and_unlock_reservation(file_priv,
&cpu_job->base,
&acquire_ctx, 0,
out_se, cpu_job->base.done_fence);
switch (cpu_job->job_type) {
case V3D_CPU_JOB_TYPE_INDIRECT_CSD:
v3d_attach_fences_and_unlock_reservation(file_priv,
clean_job,
&cpu_job->indirect_csd.acquire_ctx,
0, &se, clean_job->done_fence);
break;
default:
break;
}
v3d_job_put(&cpu_job->base);
v3d_job_put(&csd_job->base);
v3d_job_put(clean_job);
return 0;
fail_unreserve:
mutex_unlock(&v3d->sched_lock);
drm_gem_unlock_reservations(cpu_job->base.bo, cpu_job->base.bo_count,
&acquire_ctx);
drm_gem_unlock_reservations(clean_job->bo, clean_job->bo_count,
&cpu_job->indirect_csd.acquire_ctx);
fail:
v3d_job_cleanup((void *)cpu_job);
v3d_job_cleanup((void *)csd_job);
v3d_job_cleanup(clean_job);
v3d_put_multisync_post_deps(&se);
kvfree(cpu_job->timestamp_query.queries);
kvfree(cpu_job->performance_query.queries);
return ret;
}