mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-28 04:45:43 +01:00
sched_to_core() walks rdev->cores[] up to rdev->num_cores, and
rocket_remove() decrements num_cores for every core it removes. Unbind a
core that is not the last one and the cores behind it fall outside the
search, so sched_to_core() returns NULL for a core that is still bound and
still running jobs. Neither caller checks the result:
rocket_job_run(): rocket_fence_create(core), core->dev
rocket_job_timedout(): dev_err(core->dev, "NPU job timed out")
Unbinding the middle core of the three on an RK3588 while three clients are
submitting to all of them faults twice, once from the surviving core's
job queue and once from its reset work:
KASAN: null-ptr-deref in range [0x0000000000000220-0x0000000000000227]
Workqueue: fdad0000.npu drm_sched_run_job_work [gpu_sched]
pc : rocket_job_run+0x234/0x838 [rocket]
Call trace:
rocket_job_run+0x234/0x838 [rocket]
drm_sched_run_job_work+0x2cc/0xad8 [gpu_sched]
process_one_work+0x640/0x14f0
KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
Workqueue: rocket-reset-2 drm_sched_job_timedout [gpu_sched]
pc : rocket_job_timedout+0xf0/0x1e0 [rocket]
Call trace:
rocket_job_timedout+0xf0/0x1e0 [rocket]
drm_sched_job_timedout+0x188/0x6a0 [gpu_sched]
Both are the third core: the workqueue names are its device and its
core->index, and it was left at slot 2 while num_cores had dropped to 2.
Search all the slots that were allocated, the way find_core_for_dev() now
does. A core that is still bound is then found, and the two callers get
the pointer they already assume they have.
This does not make unbinding one core out of several safe. An open client
keeps an entity pointing at the scheduler of the core that went away:
drm_sched reports it as not ready for every job that lands on it, and the
client waits in dma_fence_default_wait for a fence that will never signal.
Stopping the NULL dereference is what belongs in a fix; the rest wants
more thought.
Reported-by: Sidong Yang <[email protected]>
Closes: https://lore.kernel.org/dri-devel/apwUewaRnoTNXHCt@rock-5b-plus/
Fixes: 0810d5ad88 ("accel/rocket: Add job submission IOCTL")
Cc: [email protected]
Signed-off-by: Igor Paunovic <[email protected]>
Assisted-by: LLM sparse checkpatch
Signed-off-by: Heiko Stuebner <[email protected]>
Link: https://patch.msgid.link/[email protected]
651 lines
15 KiB
C
651 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright 2019 Linaro, Ltd, Rob Herring <[email protected]> */
|
|
/* Copyright 2019 Collabora ltd. */
|
|
/* Copyright 2024-2025 Tomeu Vizoso <[email protected]> */
|
|
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_file.h>
|
|
#include <drm/drm_gem.h>
|
|
#include <drm/rocket_accel.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/overflow.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include "rocket_core.h"
|
|
#include "rocket_device.h"
|
|
#include "rocket_drv.h"
|
|
#include "rocket_job.h"
|
|
#include "rocket_registers.h"
|
|
|
|
#define JOB_TIMEOUT_MS 500
|
|
|
|
static struct rocket_job *
|
|
to_rocket_job(struct drm_sched_job *sched_job)
|
|
{
|
|
return container_of(sched_job, struct rocket_job, base);
|
|
}
|
|
|
|
static const char *rocket_fence_get_driver_name(struct dma_fence *fence)
|
|
{
|
|
return "rocket";
|
|
}
|
|
|
|
static const char *rocket_fence_get_timeline_name(struct dma_fence *fence)
|
|
{
|
|
return "rockchip-npu";
|
|
}
|
|
|
|
static const struct dma_fence_ops rocket_fence_ops = {
|
|
.get_driver_name = rocket_fence_get_driver_name,
|
|
.get_timeline_name = rocket_fence_get_timeline_name,
|
|
};
|
|
|
|
static struct dma_fence *rocket_fence_create(struct rocket_core *core)
|
|
{
|
|
struct dma_fence *fence;
|
|
|
|
fence = kzalloc_obj(*fence);
|
|
if (!fence)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
dma_fence_init(fence, &rocket_fence_ops, &core->fence_lock,
|
|
core->fence_context, ++core->emit_seqno);
|
|
|
|
return fence;
|
|
}
|
|
|
|
static int
|
|
rocket_copy_tasks(struct drm_device *dev,
|
|
struct drm_file *file_priv,
|
|
struct drm_rocket_job *job,
|
|
struct rocket_job *rjob)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (job->task_struct_size < sizeof(struct drm_rocket_task))
|
|
return -EINVAL;
|
|
|
|
rjob->task_count = job->task_count;
|
|
|
|
if (!rjob->task_count)
|
|
return 0;
|
|
|
|
rjob->tasks = kvmalloc_objs(*rjob->tasks, job->task_count);
|
|
if (!rjob->tasks) {
|
|
drm_dbg(dev, "Failed to allocate task array\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (int i = 0; i < rjob->task_count; i++) {
|
|
struct drm_rocket_task task = {0};
|
|
|
|
if (copy_from_user(&task,
|
|
u64_to_user_ptr(job->tasks) + i * job->task_struct_size,
|
|
sizeof(task))) {
|
|
drm_dbg(dev, "Failed to copy incoming tasks\n");
|
|
ret = -EFAULT;
|
|
goto fail;
|
|
}
|
|
|
|
if (task.regcmd_count == 0) {
|
|
drm_dbg(dev, "regcmd_count field in drm_rocket_task should be > 0.\n");
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
rjob->tasks[i].regcmd = task.regcmd;
|
|
rjob->tasks[i].regcmd_count = task.regcmd_count;
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
kvfree(rjob->tasks);
|
|
rjob->tasks = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static void rocket_job_hw_submit(struct rocket_core *core, struct rocket_job *job)
|
|
{
|
|
struct rocket_task *task;
|
|
unsigned int extra_bit;
|
|
|
|
/* Don't queue the job if a reset is in progress */
|
|
if (atomic_read(&core->reset.pending))
|
|
return;
|
|
|
|
/* GO ! */
|
|
|
|
task = &job->tasks[job->next_task_idx];
|
|
job->next_task_idx++;
|
|
|
|
rocket_pc_writel(core, BASE_ADDRESS, 0x1);
|
|
|
|
/* From rknpu, in the TRM this bit is marked as reserved */
|
|
extra_bit = 0x10000000 * core->index;
|
|
rocket_cna_writel(core, S_POINTER, CNA_S_POINTER_POINTER_PP_EN(1) |
|
|
CNA_S_POINTER_EXECUTER_PP_EN(1) |
|
|
CNA_S_POINTER_POINTER_PP_MODE(1) |
|
|
extra_bit);
|
|
|
|
rocket_core_writel(core, S_POINTER, CORE_S_POINTER_POINTER_PP_EN(1) |
|
|
CORE_S_POINTER_EXECUTER_PP_EN(1) |
|
|
CORE_S_POINTER_POINTER_PP_MODE(1) |
|
|
extra_bit);
|
|
|
|
rocket_pc_writel(core, BASE_ADDRESS, task->regcmd);
|
|
rocket_pc_writel(core, REGISTER_AMOUNTS,
|
|
PC_REGISTER_AMOUNTS_PC_DATA_AMOUNT((task->regcmd_count + 1) / 2 - 1));
|
|
|
|
rocket_pc_writel(core, INTERRUPT_MASK, PC_INTERRUPT_MASK_DPU_0 | PC_INTERRUPT_MASK_DPU_1);
|
|
rocket_pc_writel(core, INTERRUPT_CLEAR, PC_INTERRUPT_CLEAR_DPU_0 | PC_INTERRUPT_CLEAR_DPU_1);
|
|
|
|
rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
|
|
PC_TASK_CON_TASK_COUNT_CLEAR(1) |
|
|
PC_TASK_CON_TASK_NUMBER(1) |
|
|
PC_TASK_CON_TASK_PP_EN(1));
|
|
|
|
rocket_pc_writel(core, TASK_DMA_BASE_ADDR, PC_TASK_DMA_BASE_ADDR_DMA_BASE_ADDR(0x0));
|
|
|
|
rocket_pc_writel(core, OPERATION_ENABLE, PC_OPERATION_ENABLE_OP_EN(1));
|
|
|
|
dev_dbg(core->dev, "Submitted regcmd at 0x%llx to core %d", task->regcmd, core->index);
|
|
}
|
|
|
|
static int rocket_acquire_object_fences(struct drm_gem_object **bos,
|
|
int bo_count,
|
|
struct drm_sched_job *job,
|
|
bool is_write)
|
|
{
|
|
int i, ret;
|
|
|
|
for (i = 0; i < bo_count; i++) {
|
|
ret = dma_resv_reserve_fences(bos[i]->resv, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = drm_sched_job_add_implicit_dependencies(job, bos[i],
|
|
is_write);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rocket_attach_object_fences(struct drm_gem_object **bos,
|
|
int bo_count,
|
|
struct dma_fence *fence)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < bo_count; i++)
|
|
dma_resv_add_fence(bos[i]->resv, fence, DMA_RESV_USAGE_WRITE);
|
|
}
|
|
|
|
static int rocket_job_push(struct rocket_job *job)
|
|
{
|
|
struct rocket_device *rdev = job->rdev;
|
|
struct drm_gem_object **bos;
|
|
struct ww_acquire_ctx acquire_ctx;
|
|
u32 bo_count;
|
|
int ret = 0;
|
|
|
|
if (check_add_overflow(job->in_bo_count, job->out_bo_count, &bo_count))
|
|
return -EINVAL;
|
|
|
|
bos = kvmalloc_objs(*bos, bo_count);
|
|
if (!bos)
|
|
return -ENOMEM;
|
|
memcpy(bos, job->in_bos, job->in_bo_count * sizeof(void *));
|
|
memcpy(&bos[job->in_bo_count], job->out_bos, job->out_bo_count * sizeof(void *));
|
|
|
|
ret = drm_gem_lock_reservations(bos, bo_count, &acquire_ctx);
|
|
if (ret)
|
|
goto err;
|
|
|
|
scoped_guard(mutex, &rdev->sched_lock) {
|
|
drm_sched_job_arm(&job->base);
|
|
|
|
job->inference_done_fence = dma_fence_get(&job->base.s_fence->finished);
|
|
|
|
ret = rocket_acquire_object_fences(job->in_bos, job->in_bo_count, &job->base, false);
|
|
if (ret)
|
|
goto err_unlock;
|
|
|
|
ret = rocket_acquire_object_fences(job->out_bos, job->out_bo_count, &job->base, true);
|
|
if (ret)
|
|
goto err_unlock;
|
|
|
|
kref_get(&job->refcount); /* put by scheduler job completion */
|
|
|
|
drm_sched_entity_push_job(&job->base);
|
|
}
|
|
|
|
rocket_attach_object_fences(job->out_bos, job->out_bo_count, job->inference_done_fence);
|
|
|
|
err_unlock:
|
|
drm_gem_unlock_reservations(bos, bo_count, &acquire_ctx);
|
|
err:
|
|
kvfree(bos);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void rocket_job_cleanup(struct kref *ref)
|
|
{
|
|
struct rocket_job *job = container_of(ref, struct rocket_job,
|
|
refcount);
|
|
unsigned int i;
|
|
|
|
rocket_iommu_domain_put(job->domain);
|
|
|
|
dma_fence_put(job->done_fence);
|
|
dma_fence_put(job->inference_done_fence);
|
|
|
|
if (job->in_bos) {
|
|
for (i = 0; i < job->in_bo_count; i++)
|
|
drm_gem_object_put(job->in_bos[i]);
|
|
|
|
kvfree(job->in_bos);
|
|
}
|
|
|
|
if (job->out_bos) {
|
|
for (i = 0; i < job->out_bo_count; i++)
|
|
drm_gem_object_put(job->out_bos[i]);
|
|
|
|
kvfree(job->out_bos);
|
|
}
|
|
|
|
kvfree(job->tasks);
|
|
|
|
kfree(job);
|
|
}
|
|
|
|
static void rocket_job_put(struct rocket_job *job)
|
|
{
|
|
kref_put(&job->refcount, rocket_job_cleanup);
|
|
}
|
|
|
|
static void rocket_job_free(struct drm_sched_job *sched_job)
|
|
{
|
|
struct rocket_job *job = to_rocket_job(sched_job);
|
|
|
|
drm_sched_job_cleanup(sched_job);
|
|
|
|
rocket_job_put(job);
|
|
}
|
|
|
|
static struct rocket_core *sched_to_core(struct rocket_device *rdev,
|
|
struct drm_gpu_scheduler *sched)
|
|
{
|
|
unsigned int core;
|
|
|
|
for (core = 0; core < rdev->max_cores; core++) {
|
|
if (&rdev->cores[core].sched == sched)
|
|
return &rdev->cores[core];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct dma_fence *rocket_job_run(struct drm_sched_job *sched_job)
|
|
{
|
|
struct rocket_job *job = to_rocket_job(sched_job);
|
|
struct rocket_device *rdev = job->rdev;
|
|
struct rocket_core *core = sched_to_core(rdev, sched_job->sched);
|
|
struct dma_fence *fence = NULL;
|
|
int ret;
|
|
|
|
if (unlikely(job->base.s_fence->finished.error))
|
|
return NULL;
|
|
|
|
/*
|
|
* Nothing to execute: can happen if the job has finished while
|
|
* we were resetting the NPU.
|
|
*/
|
|
if (job->next_task_idx == job->task_count)
|
|
return NULL;
|
|
|
|
fence = rocket_fence_create(core);
|
|
if (IS_ERR(fence))
|
|
return fence;
|
|
|
|
if (job->done_fence)
|
|
dma_fence_put(job->done_fence);
|
|
job->done_fence = dma_fence_get(fence);
|
|
|
|
ret = pm_runtime_resume_and_get(core->dev);
|
|
if (ret < 0)
|
|
goto err_put_fences;
|
|
|
|
ret = iommu_attach_group(job->domain->domain, core->iommu_group);
|
|
if (ret < 0)
|
|
goto err_put_pm;
|
|
|
|
scoped_guard(mutex, &core->job_lock) {
|
|
core->in_flight_job = job;
|
|
rocket_job_hw_submit(core, job);
|
|
}
|
|
|
|
return fence;
|
|
|
|
err_put_pm:
|
|
pm_runtime_put(core->dev);
|
|
err_put_fences:
|
|
dma_fence_put(job->done_fence);
|
|
job->done_fence = NULL;
|
|
dma_fence_put(fence);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static void rocket_job_handle_irq(struct rocket_core *core)
|
|
{
|
|
pm_runtime_mark_last_busy(core->dev);
|
|
|
|
rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
|
|
rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
|
|
|
|
scoped_guard(mutex, &core->job_lock)
|
|
if (core->in_flight_job) {
|
|
if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
|
|
rocket_job_hw_submit(core, core->in_flight_job);
|
|
return;
|
|
}
|
|
|
|
iommu_detach_group(NULL, iommu_group_get(core->dev));
|
|
dma_fence_signal(core->in_flight_job->done_fence);
|
|
pm_runtime_put_autosuspend(core->dev);
|
|
core->in_flight_job = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
|
|
{
|
|
if (!atomic_read(&core->reset.pending))
|
|
return;
|
|
|
|
drm_sched_stop(&core->sched, bad);
|
|
|
|
/*
|
|
* Remaining interrupts have been handled, but we might still have
|
|
* stuck jobs. Let's make sure the PM counters stay balanced by
|
|
* manually calling pm_runtime_put_noidle().
|
|
*/
|
|
scoped_guard(mutex, &core->job_lock) {
|
|
if (core->in_flight_job)
|
|
pm_runtime_put_noidle(core->dev);
|
|
|
|
iommu_detach_group(NULL, core->iommu_group);
|
|
|
|
core->in_flight_job = NULL;
|
|
}
|
|
|
|
/* Proceed with reset now. */
|
|
rocket_core_reset(core);
|
|
|
|
/* NPU has been reset, we can clear the reset pending bit. */
|
|
atomic_set(&core->reset.pending, 0);
|
|
|
|
/* Restart the scheduler */
|
|
drm_sched_start(&core->sched, 0);
|
|
}
|
|
|
|
static enum drm_gpu_sched_stat rocket_job_timedout(struct drm_sched_job *sched_job)
|
|
{
|
|
struct rocket_job *job = to_rocket_job(sched_job);
|
|
struct rocket_device *rdev = job->rdev;
|
|
struct rocket_core *core = sched_to_core(rdev, sched_job->sched);
|
|
|
|
dev_err(core->dev, "NPU job timed out");
|
|
|
|
atomic_set(&core->reset.pending, 1);
|
|
rocket_reset(core, sched_job);
|
|
|
|
return DRM_GPU_SCHED_STAT_RESET;
|
|
}
|
|
|
|
static void rocket_reset_work(struct work_struct *work)
|
|
{
|
|
struct rocket_core *core;
|
|
|
|
core = container_of(work, struct rocket_core, reset.work);
|
|
rocket_reset(core, NULL);
|
|
}
|
|
|
|
static const struct drm_sched_backend_ops rocket_sched_ops = {
|
|
.run_job = rocket_job_run,
|
|
.timedout_job = rocket_job_timedout,
|
|
.free_job = rocket_job_free
|
|
};
|
|
|
|
static irqreturn_t rocket_job_irq_handler_thread(int irq, void *data)
|
|
{
|
|
struct rocket_core *core = data;
|
|
|
|
rocket_job_handle_irq(core);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t rocket_job_irq_handler(int irq, void *data)
|
|
{
|
|
struct rocket_core *core = data;
|
|
u32 raw_status = rocket_pc_readl(core, INTERRUPT_RAW_STATUS);
|
|
|
|
WARN_ON(raw_status & PC_INTERRUPT_RAW_STATUS_DMA_READ_ERROR);
|
|
WARN_ON(raw_status & PC_INTERRUPT_RAW_STATUS_DMA_WRITE_ERROR);
|
|
|
|
if (!(raw_status & PC_INTERRUPT_RAW_STATUS_DPU_0 ||
|
|
raw_status & PC_INTERRUPT_RAW_STATUS_DPU_1))
|
|
return IRQ_NONE;
|
|
|
|
rocket_pc_writel(core, INTERRUPT_MASK, 0x0);
|
|
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
int rocket_job_init(struct rocket_core *core)
|
|
{
|
|
struct drm_sched_init_args args = {
|
|
.ops = &rocket_sched_ops,
|
|
.num_rqs = DRM_SCHED_PRIORITY_COUNT,
|
|
.credit_limit = 1,
|
|
.timeout = msecs_to_jiffies(JOB_TIMEOUT_MS),
|
|
.name = dev_name(core->dev),
|
|
.dev = core->dev,
|
|
};
|
|
int ret;
|
|
|
|
INIT_WORK(&core->reset.work, rocket_reset_work);
|
|
spin_lock_init(&core->fence_lock);
|
|
mutex_init(&core->job_lock);
|
|
|
|
core->irq = platform_get_irq(to_platform_device(core->dev), 0);
|
|
if (core->irq < 0)
|
|
return core->irq;
|
|
|
|
ret = devm_request_threaded_irq(core->dev, core->irq,
|
|
rocket_job_irq_handler,
|
|
rocket_job_irq_handler_thread,
|
|
IRQF_SHARED, dev_name(core->dev),
|
|
core);
|
|
if (ret) {
|
|
dev_err(core->dev, "failed to request job irq");
|
|
return ret;
|
|
}
|
|
|
|
core->reset.wq = alloc_ordered_workqueue("rocket-reset-%d", 0, core->index);
|
|
if (!core->reset.wq)
|
|
return -ENOMEM;
|
|
|
|
core->fence_context = dma_fence_context_alloc(1);
|
|
|
|
args.timeout_wq = core->reset.wq;
|
|
ret = drm_sched_init(&core->sched, &args);
|
|
if (ret) {
|
|
dev_err(core->dev, "Failed to create scheduler: %d.", ret);
|
|
goto err_sched;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_sched:
|
|
drm_sched_fini(&core->sched);
|
|
|
|
destroy_workqueue(core->reset.wq);
|
|
return ret;
|
|
}
|
|
|
|
void rocket_job_fini(struct rocket_core *core)
|
|
{
|
|
drm_sched_fini(&core->sched);
|
|
|
|
cancel_work_sync(&core->reset.work);
|
|
destroy_workqueue(core->reset.wq);
|
|
}
|
|
|
|
int rocket_job_open(struct rocket_file_priv *rocket_priv)
|
|
{
|
|
struct rocket_device *rdev = rocket_priv->rdev;
|
|
struct drm_gpu_scheduler **scheds = kmalloc_objs(*scheds,
|
|
rdev->num_cores);
|
|
unsigned int core;
|
|
int ret;
|
|
|
|
for (core = 0; core < rdev->num_cores; core++)
|
|
scheds[core] = &rdev->cores[core].sched;
|
|
|
|
ret = drm_sched_entity_init(&rocket_priv->sched_entity,
|
|
DRM_SCHED_PRIORITY_NORMAL,
|
|
scheds,
|
|
rdev->num_cores, NULL);
|
|
if (WARN_ON(ret))
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rocket_job_close(struct rocket_file_priv *rocket_priv)
|
|
{
|
|
struct drm_sched_entity *entity = &rocket_priv->sched_entity;
|
|
|
|
kfree(entity->sched_list);
|
|
drm_sched_entity_destroy(entity);
|
|
}
|
|
|
|
int rocket_job_is_idle(struct rocket_core *core)
|
|
{
|
|
/* If there are any jobs in this HW queue, we're not idle */
|
|
if (atomic_read(&core->sched.credit_count))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static int rocket_ioctl_submit_job(struct drm_device *dev, struct drm_file *file,
|
|
struct drm_rocket_job *job)
|
|
{
|
|
struct rocket_device *rdev = to_rocket_device(dev);
|
|
struct rocket_file_priv *file_priv = file->driver_priv;
|
|
struct rocket_job *rjob = NULL;
|
|
int ret = 0;
|
|
|
|
if (job->task_count == 0)
|
|
return -EINVAL;
|
|
|
|
rjob = kzalloc_obj(*rjob);
|
|
if (!rjob)
|
|
return -ENOMEM;
|
|
|
|
kref_init(&rjob->refcount);
|
|
|
|
rjob->rdev = rdev;
|
|
rjob->domain = rocket_iommu_domain_get(file_priv);
|
|
|
|
ret = drm_sched_job_init(&rjob->base,
|
|
&file_priv->sched_entity,
|
|
1, NULL, file->client_id);
|
|
if (ret)
|
|
goto out_put_job;
|
|
|
|
ret = rocket_copy_tasks(dev, file, job, rjob);
|
|
if (ret)
|
|
goto out_cleanup_job;
|
|
|
|
ret = drm_gem_objects_lookup(file, u64_to_user_ptr(job->in_bo_handles),
|
|
job->in_bo_handle_count, &rjob->in_bos);
|
|
if (ret)
|
|
goto out_cleanup_job;
|
|
|
|
rjob->in_bo_count = job->in_bo_handle_count;
|
|
|
|
ret = drm_gem_objects_lookup(file, u64_to_user_ptr(job->out_bo_handles),
|
|
job->out_bo_handle_count, &rjob->out_bos);
|
|
if (ret)
|
|
goto out_cleanup_job;
|
|
|
|
rjob->out_bo_count = job->out_bo_handle_count;
|
|
|
|
ret = rocket_job_push(rjob);
|
|
if (ret)
|
|
goto out_cleanup_job;
|
|
|
|
out_cleanup_job:
|
|
if (ret)
|
|
drm_sched_job_cleanup(&rjob->base);
|
|
out_put_job:
|
|
rocket_job_put(rjob);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int rocket_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *file)
|
|
{
|
|
struct drm_rocket_submit *args = data;
|
|
struct drm_rocket_job *jobs;
|
|
int ret = 0;
|
|
unsigned int i = 0;
|
|
|
|
if (args->job_count == 0)
|
|
return 0;
|
|
|
|
if (args->job_struct_size < sizeof(struct drm_rocket_job)) {
|
|
drm_dbg(dev, "job_struct_size field in drm_rocket_submit struct is too small.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (args->reserved != 0) {
|
|
drm_dbg(dev, "Reserved field in drm_rocket_submit struct should be 0.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
jobs = kvmalloc_objs(*jobs, args->job_count);
|
|
if (!jobs) {
|
|
drm_dbg(dev, "Failed to allocate incoming job array\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < args->job_count; i++) {
|
|
if (copy_from_user(&jobs[i],
|
|
u64_to_user_ptr(args->jobs) + i * args->job_struct_size,
|
|
sizeof(*jobs))) {
|
|
ret = -EFAULT;
|
|
drm_dbg(dev, "Failed to copy incoming job array\n");
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
|
|
for (i = 0; i < args->job_count; i++)
|
|
rocket_ioctl_submit_job(dev, file, &jobs[i]);
|
|
|
|
exit:
|
|
kvfree(jobs);
|
|
|
|
return ret;
|
|
}
|