Re: [PATCH v3 09/16] accel/thames: Add IOCTLs for job creation and submission

From: Christian König

Date: Mon Oct 26 2026 - 06:17:54 EST


On 10/23/26 08:39, Tomeu Vizoso wrote:
> A Job is created once from a program, its parameters and its transfers,
> checked then, and submitted any number of times; the params buffer's
> contents may change between submissions. A submission pushes the Job
> onto a core's Ring and rings the doorbell; the firmware moves the Fence
> when the Job is done. Each core has a drm-gpu-scheduler instance, with
> one credit per Ring entry. A submit call takes an array of Jobs, the
> buffers they use for implicit synchronisation, and sync objects to
> signal; it waits on none.
>
> The job submission code was initially based on Panfrost.

I only skimmed over this, but what I saw looks sane to me from the dma_fence requirement side.

Regards,
Christian.

> Assisted-by: LLM
> Signed-off-by: Tomeu Vizoso <tomeu@xxxxxxxxxxxxxxx>
>
> ---
>
> v3:
> - Add Job create/destroy, for validating transfers.
> - Add sync objects to the submission IOCTL.
> - Allocate the done fence with the Job; run_job allocates nothing
> (Christian König).
> v2:
> - Add thames_accel.h UAPI header (Robert Nelson).
> ---
> drivers/accel/thames/Kconfig | 1 +
> drivers/accel/thames/Makefile | 3 +
> drivers/accel/thames/thames_core.c | 36 +-
> drivers/accel/thames/thames_core.h | 14 +
> drivers/accel/thames/thames_device.h | 6 +
> drivers/accel/thames/thames_drv.c | 43 ++
> drivers/accel/thames/thames_drv.h | 7 +
> drivers/accel/thames/thames_job.c | 869 +++++++++++++++++++++++++++++++++++
> drivers/accel/thames/thames_job.h | 73 +++
> drivers/accel/thames/thames_trace.h | 77 ++++
> include/uapi/drm/thames_accel.h | 259 ++++++++++-
> 11 files changed, 1381 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/accel/thames/Kconfig b/drivers/accel/thames/Kconfig
> index 47a2986fcabd..f2c32ce8d228 100644
> --- a/drivers/accel/thames/Kconfig
> +++ b/drivers/accel/thames/Kconfig
> @@ -6,6 +6,7 @@ config DRM_ACCEL_THAMES
> depends on TI_K3_DSP_REMOTEPROC
> depends on REMOTEPROC
> depends on MAILBOX
> + select DRM_SCHED
> select DRM_GEM_DMA_HELPER
> select DRM_GEM_SHMEM_HELPER
> select IOMMU_IO_PGTABLE_LPAE
> diff --git a/drivers/accel/thames/Makefile b/drivers/accel/thames/Makefile
> index 143a829f24cf..7bd652795cf5 100644
> --- a/drivers/accel/thames/Makefile
> +++ b/drivers/accel/thames/Makefile
> @@ -6,4 +6,7 @@ thames-y := \
> thames_core.o \
> thames_drv.o \
> thames_gem.o \
> + thames_job.o \
> thames_transfers.o
> +
> +CFLAGS_thames_job.o := -I$(src)
> diff --git a/drivers/accel/thames/thames_core.c b/drivers/accel/thames/thames_core.c
> index dff76abd8951..85e8f48aec60 100644
> --- a/drivers/accel/thames/thames_core.c
> +++ b/drivers/accel/thames/thames_core.c
> @@ -25,6 +25,7 @@
> #include "thames_core.h"
> #include "thames_device.h"
> #include "thames_gem.h"
> +#include "thames_job.h"
> #include "thames_transfers.h"
>
> #define THAMES_RESTART_TIMEOUT_MS 5000
> @@ -327,6 +328,7 @@ static void thames_mbox_rx(struct mbox_client *cl, void *mssg)
>
> dev_dbg(core->dev, "Doorbell on " THAMES_CORE_FMT ": 0x%x\n", THAMES_CORE_ARG(core),
> (u32)(uintptr_t)mssg);
> + thames_job_complete(core);
> }
>
> /* Embedded in struct thames_core: nothing to free. */
> @@ -515,10 +517,18 @@ int thames_core_init(struct thames_core *core)
> if (err)
> goto err_timers;
>
> - err = thames_core_request_doorbell(core);
> + /*
> + * Before the doorbell: requesting it delivers at once a word the core
> + * wrote while no driver was loaded.
> + */
> + err = thames_job_init(core);
> if (err)
> goto err_unmap;
>
> + err = thames_core_request_doorbell(core);
> + if (err)
> + goto err_job;
> +
> if (rproc->state == RPROC_OFFLINE) {
> err = rproc_boot(rproc);
> if (err)
> @@ -540,6 +550,8 @@ int thames_core_init(struct thames_core *core)
>
> err_doorbell:
> thames_core_free_doorbell(core);
> +err_job:
> + thames_job_fini(core);
> err_unmap:
> thames_core_unmap_ipc(core);
> err_timers:
> @@ -554,6 +566,7 @@ void thames_core_fini(struct thames_core *core)
> {
> if (core->mbox_chan)
> thames_core_free_doorbell(core);
> + thames_job_fini(core);
> /*
> * Drop only our reference and keep the boot count: the core runs on
> * for the next load to reattach to; only a reset or a suspend, which
> @@ -567,6 +580,21 @@ void thames_core_fini(struct thames_core *core)
> core->of_node = NULL;
> }
>
> +/* The system firmware accepts a stop only of a core in IDLE. */
> +bool thames_core_idles(struct thames_core *core)
> +{
> + u32 head = READ_ONCE(core->ipc->ring.head);
> + u32 slot = head % THAMES_RING_DEPTH;
> + struct thames_fault_record *record = &core->ipc->fault_records[slot];
> +
> + if (head == core->ring_tail)
> + return true;
> +
> + /* Read the fault record after the head that says it is there. */
> + rmb();
> + return READ_ONCE(record->kind) == DRM_THAMES_FAULT_FIRMWARE;
> +}
> +
> /*
> * rproc_shutdown() reaches the system firmware because the core has one power
> * count, remoteproc's auto-boot or this driver's boot: the probe defers while
> @@ -577,6 +605,10 @@ int thames_core_suspend(struct thames_core *core)
> struct rproc *rproc = core->rproc;
> int err;
>
> + err = thames_job_suspend(core);
> + if (err)
> + return err;
> +
> if (core->suspended)
> return 0;
>
> @@ -589,6 +621,7 @@ int thames_core_suspend(struct thames_core *core)
> err = -EBUSY;
> }
> if (err) {
> + thames_job_resume(core);
> return err;
> }
>
> @@ -640,5 +673,6 @@ int thames_core_resume(struct thames_core *core)
>
> offline:
> restart:
> + thames_job_resume(core);
> return err;
> }
> diff --git a/drivers/accel/thames/thames_core.h b/drivers/accel/thames/thames_core.h
> index 590c5716f7f5..a78414d18ad3 100644
> --- a/drivers/accel/thames/thames_core.h
> +++ b/drivers/accel/thames/thames_core.h
> @@ -10,6 +10,7 @@
> #include <linux/mutex_types.h>
> #include <linux/remoteproc.h>
>
> +#include <drm/gpu_scheduler.h>
>
> #include "thames_abi.h"
>
> @@ -44,13 +45,25 @@ struct thames_core {
> struct mbox_chan *mbox_chan;
>
> u32 ring_tail;
> + struct list_head submitted;
> + struct list_head held;
> +
> + struct mutex job_lock;
> +
> + spinlock_t fence_lock;
> +
> /*
> * @rproc_caller is the task inside the driver's own rproc_boot() or
> * rproc_shutdown(), whose subdevice callbacks then do nothing.
> */
> struct task_struct *rproc_caller;
>
> + struct drm_gpu_scheduler sched;
> + u64 fence_context;
> + u64 emit_seqno;
> +
> bool suspended;
> + unsigned long timeout_left;
>
> struct resource l2sram;
> struct resource l2aux;
> @@ -81,6 +94,7 @@ static inline const struct resource *thames_core_window(const struct thames_core
>
> int thames_core_init(struct thames_core *core);
> void thames_core_fini(struct thames_core *core);
> +bool thames_core_idles(struct thames_core *core);
> int thames_core_suspend(struct thames_core *core);
> int thames_core_resume(struct thames_core *core);
>
> diff --git a/drivers/accel/thames/thames_device.h b/drivers/accel/thames/thames_device.h
> index e183c78e2acf..0a34764885df 100644
> --- a/drivers/accel/thames/thames_device.h
> +++ b/drivers/accel/thames/thames_device.h
> @@ -36,7 +36,10 @@ struct thames_caps {
> struct thames_device {
> struct drm_device ddev;
>
> + struct mutex sched_lock;
> +
> struct thames_core cores[THAMES_MAX_CORES];
> + struct drm_gpu_scheduler *sched_list[THAMES_MAX_CORES];
> unsigned int num_cores;
>
> struct thames_caps caps;
> @@ -72,6 +75,9 @@ struct thames_device {
> */
> struct mutex bindings_lock;
>
> + /* Whoever takes a file off the list closes its Jobs. */
> + struct mutex files_lock;
> + struct list_head files;
> };
>
> #define to_thames_device(drm_dev) \
> diff --git a/drivers/accel/thames/thames_drv.c b/drivers/accel/thames/thames_drv.c
> index 030c60963a0a..fc3328834162 100644
> --- a/drivers/accel/thames/thames_drv.c
> +++ b/drivers/accel/thames/thames_drv.c
> @@ -23,6 +23,7 @@
> #include "thames_core.h"
> #include "thames_drv.h"
> #include "thames_gem.h"
> +#include "thames_job.h"
> #include "thames_transfers.h"
>
> static struct platform_device *drm_dev;
> @@ -54,14 +55,23 @@ static int thames_open(struct drm_device *dev, struct drm_file *file)
>
> file->driver_priv = thames_priv;
>
> + ret = thames_job_open(thames_priv);
> + if (ret)
> + goto err_free;
> +
> ret = thames_context_create(thames_priv);
> if (ret)
> goto err_close_job;
>
> + scoped_guard(mutex, &tdev->files_lock)
> + list_add_tail(&thames_priv->file_node, &tdev->files);
> +
> drm_dev_exit(idx);
> return 0;
>
> err_close_job:
> + thames_job_close(thames_priv);
> +err_free:
> kfree(thames_priv);
> err_exit:
> drm_dev_exit(idx);
> @@ -87,6 +97,14 @@ void thames_file_priv_put(struct thames_file_priv *priv)
> static void thames_postclose(struct drm_device *dev, struct drm_file *file)
> {
> struct thames_file_priv *thames_priv = file->driver_priv;
> + struct thames_device *tdev = thames_priv->tdev;
> +
> + scoped_guard(mutex, &tdev->files_lock) {
> + if (!list_empty(&thames_priv->file_node)) {
> + list_del_init(&thames_priv->file_node);
> + thames_job_close(thames_priv);
> + }
> + }
>
> dev_dbg(dev->dev, "Context %u: file closed, %u references left\n",
> thames_priv->context_id, kref_read(&thames_priv->refcount) - 1);
> @@ -208,6 +226,9 @@ static int thames_ioctl_##func##_entered(struct drm_device *dev, void *data, \
> THAMES_IOCTL_ENTERED(bo_create)
> THAMES_IOCTL_ENTERED(bo_mmap_offset)
> THAMES_IOCTL_ENTERED(dev_query)
> +THAMES_IOCTL_ENTERED(job_create)
> +THAMES_IOCTL_ENTERED(job_submit)
> +THAMES_IOCTL_ENTERED(job_destroy)
> THAMES_IOCTL_ENTERED(bo_bind)
>
> static const struct drm_ioctl_desc thames_drm_driver_ioctls[] = {
> @@ -215,6 +236,9 @@ static const struct drm_ioctl_desc thames_drm_driver_ioctls[] = {
> THAMES_IOCTL(BO_CREATE, bo_create_entered),
> THAMES_IOCTL(BO_MMAP_OFFSET, bo_mmap_offset_entered),
> THAMES_IOCTL(DEV_QUERY, dev_query_entered),
> + THAMES_IOCTL(JOB_CREATE, job_create_entered),
> + THAMES_IOCTL(JOB_SUBMIT, job_submit_entered),
> + THAMES_IOCTL(JOB_DESTROY, job_destroy_entered),
> THAMES_IOCTL(BO_BIND, bo_bind_entered),
> };
>
> @@ -272,6 +296,10 @@ static struct thames_device *thames_device_init(struct platform_device *pdev,
> if (err)
> return ERR_PTR(err);
>
> + err = drmm_mutex_init(&tdev->ddev, &tdev->sched_lock);
> + if (err)
> + return ERR_PTR(err);
> +
> err = drmm_mutex_init(&tdev->ddev, &tdev->transfer_table_pool_lock);
> if (err)
> return ERR_PTR(err);
> @@ -288,8 +316,13 @@ static struct thames_device *thames_device_init(struct platform_device *pdev,
> if (err)
> return ERR_PTR(err);
>
> + err = drmm_mutex_init(&tdev->ddev, &tdev->files_lock);
> + if (err)
> + return ERR_PTR(err);
> +
> INIT_LIST_HEAD(&tdev->transfer_tables);
> INIT_LIST_HEAD(&tdev->contexts);
> + INIT_LIST_HEAD(&tdev->files);
>
> tdev->asid_next = 1;
> /*
> @@ -347,6 +380,7 @@ static int thames_platform_probe(struct platform_device *pdev)
> continue;
> }
>
> + tdev->sched_list[core] = &tdev->cores[core].sched;
> tdev->num_cores++;
> core++;
> }
> @@ -391,6 +425,15 @@ static void thames_platform_remove(struct platform_device *pdev)
>
> drm_dev_unplug(&tdev->ddev);
>
> + scoped_guard(mutex, &tdev->files_lock) {
> + struct thames_file_priv *priv, *tmp;
> +
> + list_for_each_entry_safe(priv, tmp, &tdev->files, file_node) {
> + list_del_init(&priv->file_node);
> + thames_job_close(priv);
> + }
> + }
> +
> for (core = 0; core < tdev->num_cores; core++)
> thames_core_fini(&tdev->cores[core]);
> tdev->num_cores = 0;
> diff --git a/drivers/accel/thames/thames_drv.h b/drivers/accel/thames/thames_drv.h
> index 7f9031b63780..2115f7eae589 100644
> --- a/drivers/accel/thames/thames_drv.h
> +++ b/drivers/accel/thames/thames_drv.h
> @@ -11,6 +11,7 @@
> #include <linux/xarray.h>
>
> #include <drm/drm_mm.h>
> +#include <drm/gpu_scheduler.h>
>
> #include "thames_device.h"
>
> @@ -26,6 +27,12 @@ struct thames_file_priv {
> */
> struct kref refcount;
>
> + struct list_head file_node;
> +
> + struct drm_sched_entity sched_entity;
> +
> + struct xarray jobs;
> +
> struct io_pgtable_ops *pgtbl_ops[THAMES_MAX_CORES];
> u64 page_table_base[THAMES_MAX_CORES];
> u32 asid;
> diff --git a/drivers/accel/thames/thames_job.c b/drivers/accel/thames/thames_job.c
> new file mode 100644
> index 000000000000..6560106d47a8
> --- /dev/null
> +++ b/drivers/accel/thames/thames_job.c
> @@ -0,0 +1,869 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@xxxxxxxxxx> */
> +/* Copyright 2019 Collabora ltd. */
> +/* Copyright 2024-2025 Tomeu Vizoso <tomeu@xxxxxxxxxxxxxxx> */
> +/* Copyright 2026 Texas Instruments Incorporated - https://www.ti.com/ */
> +
> +#include <clocksource/arm_arch_timer.h>
> +#include <linux/dev_printk.h>
> +#include <linux/dma-fence-chain.h>
> +#include <linux/iopoll.h>
> +#include <linux/mailbox_client.h>
> +#include <linux/platform_device.h>
> +#include <linux/xarray.h>
> +
> +#include <drm/drm_drv.h>
> +#include <drm/drm_file.h>
> +#include <drm/drm_gem.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_syncobj.h>
> +#include <drm/thames_accel.h>
> +
> +#include "thames_core.h"
> +#include "thames_device.h"
> +#include "thames_drv.h"
> +#include "thames_gem.h"
> +#include "thames_job.h"
> +
> +#define CREATE_TRACE_POINTS
> +#include "thames_trace.h"
> +
> +static struct thames_sched_job *to_thames_sched_job(struct drm_sched_job *sched_job)
> +{
> + return container_of(sched_job, struct thames_sched_job, base);
> +}
> +
> +static void thames_sched_job_put(struct thames_sched_job *job);
> +static void thames_job_put(struct thames_job *job);
> +
> +#define THAMES_MAX_JOBS_PER_SUBMIT 256
> +#define THAMES_MAX_SUBMIT_BOS 4096
> +#define THAMES_MAX_SUBMIT_SYNCS 64
> +
> +/*
> + * The host's timeout is longer than the device's: a Job still on the Ring when
> + * it fires is on a stalled core.
> + */
> +#define THAMES_DEVICE_TIMEOUT_MS 500
> +
> +struct thames_submit_bos {
> + struct kref refcount;
> + u32 read_count;
> + u32 count;
> + struct drm_gem_object *bos[] __counted_by(count);
> +};
> +
> +static void thames_submit_bos_free(struct kref *ref)
> +{
> + struct thames_submit_bos *bos = container_of(ref, struct thames_submit_bos, refcount);
> + u32 i;
> +
> + for (i = 0; i < bos->count; i++) {
> + if (bos->bos[i])
> + drm_gem_object_put(bos->bos[i]);
> + }
> +
> + kvfree(bos);
> +}
> +
> +static void thames_submit_bos_put(struct thames_submit_bos *bos)
> +{
> + if (bos)
> + kref_put(&bos->refcount, thames_submit_bos_free);
> +}
> +
> +static const char *thames_fence_get_driver_name(struct dma_fence *fence)
> +{
> + return "thames";
> +}
> +
> +static const char *thames_fence_get_timeline_name(struct dma_fence *fence)
> +{
> + return "thames";
> +}
> +
> +static const struct dma_fence_ops thames_fence_ops = {
> + .get_driver_name = thames_fence_get_driver_name,
> + .get_timeline_name = thames_fence_get_timeline_name,
> +};
> +
> +static void thames_job_fail_done_fence(struct thames_sched_job *job, int error)
> +{
> + if (!dma_fence_was_initialized(job->done_fence) || dma_fence_is_signaled(job->done_fence))
> + return;
> +
> + dma_fence_set_error(job->done_fence, error);
> + dma_fence_signal(job->done_fence);
> +}
> +
> +static int thames_job_hw_submit(struct thames_core *core, struct thames_sched_job *sched_job)
> +{
> + struct thames_ring *ring = &core->ipc->ring;
> + struct thames_ring_host *host;
> + u32 tail = core->ring_tail;
> + u32 index = tail % THAMES_RING_DEPTH;
> + int ret;
> +
> + if (tail - READ_ONCE(ring->head) >= THAMES_RING_DEPTH) {
> + dev_err(core->dev, "Ring full on " THAMES_CORE_FMT "\n", THAMES_CORE_ARG(core));
> + return -ENOSPC;
> + }
> +
> + sched_job->stamps[0] = arch_timer_read_counter();
> + sched_job->ring_index = index;
> +
> + host = &ring->entries[index].host;
> + host->context_id = sched_job->file_priv->context_id;
> + host->transfer_count = sched_job->job->transfers.count;
> + host->transfer_group_count = sched_job->job->transfers.group_count;
> + host->transfer_table_offset = sched_job->job->transfers.offset;
> + host->seqno = sched_job->seqno;
> + host->program_dsp_va = sched_job->job->program_dsp_va;
> + host->params_dsp_va = sched_job->job->params_dsp_va;
> + host->context_serial = sched_job->file_priv->context_serial;
> + host->timeout_ms = THAMES_DEVICE_TIMEOUT_MS;
> +
> + scoped_guard(mutex, &core->job_lock) {
> + kref_get(&sched_job->refcount); /* Put by thames_job_complete(). */
> + list_add_tail(&sched_job->node, &core->submitted);
> +
> + /* Order the host half before the tail. */
> + wmb();
> + WRITE_ONCE(ring->tail, tail + 1);
> + core->ring_tail = tail + 1;
> + /* Order the tail before the doorbell. */
> + wmb();
> + }
> +
> + /*
> + * The Job is on the Ring whatever the send returns; it completes
> + * through the Fence or the timeout.
> + */
> + ret = mbox_send_message(core->mbox_chan, (void *)(uintptr_t)THAMES_DOORBELL);
> + if (ret < 0)
> + dev_warn_ratelimited(core->dev, "Doorbell not sent: %d\n", ret);
> +
> + return 0;
> +}
> +
> +void thames_job_complete(struct thames_core *core)
> +{
> + struct thames_sched_job *job, *tmp;
> + LIST_HEAD(done);
> + u64 completed, signalled;
> +
> + completed = READ_ONCE(core->ipc->fence.seqno);
> + /* Read the entries after the Fence that covers them. */
> + rmb();
> +
> + scoped_guard(mutex, &core->job_lock) {
> + list_for_each_entry_safe(job, tmp, &core->submitted, node) {
> + if (job->seqno > completed)
> + break;
> + list_move_tail(&job->node, &done);
> + }
> + }
> +
> + list_for_each_entry_safe(job, tmp, &done, node) {
> + struct thames_ring_fw *fw = &core->ipc->ring.entries[job->ring_index].fw;
> +
> + job->stamps[1] = fw->doorbell_seen_stamp;
> + job->stamps[2] = fw->run_start_stamp;
> + job->stamps[3] = fw->run_end_stamp;
> + job->stamps[4] = fw->kernel_entry_stamp;
> + job->stamps[5] = fw->kernel_exit_stamp;
> +
> + if (fw->fence_value == THAMES_FENCE_ERROR) {
> + dma_fence_set_error(job->done_fence, -EIO);
> + }
> +
> + list_del_init(&job->node);
> + signalled = arch_timer_read_counter();
> + dma_fence_signal(job->done_fence);
> + trace_thames_job_done(core, job, signalled);
> + thames_sched_job_put(job);
> + }
> +}
> +
> +static int thames_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 thames_attach_object_fences(struct drm_gem_object **bos, int bo_count,
> + struct dma_fence *fence, enum dma_resv_usage usage)
> +{
> + int i;
> +
> + for (i = 0; i < bo_count; i++)
> + dma_resv_add_fence(bos[i]->resv, fence, usage);
> +}
> +
> +/* Nothing here fails, so a submit call pushes every Job or none. */
> +static void thames_job_push(struct thames_sched_job *sched_job)
> +{
> + scoped_guard(mutex, &sched_job->tdev->sched_lock) {
> + drm_sched_job_arm(&sched_job->base);
> +
> + sched_job->inference_done_fence = dma_fence_get(&sched_job->base.s_fence->finished);
> +
> + kref_get(&sched_job->refcount); /* put by scheduler job completion */
> +
> + drm_sched_entity_push_job(&sched_job->base);
> + }
> +}
> +
> +static void thames_sched_job_cleanup(struct kref *ref)
> +{
> + struct thames_sched_job *sched_job = container_of(ref, struct thames_sched_job, refcount);
> +
> + if (dma_fence_was_initialized(sched_job->done_fence))
> + dma_fence_put(sched_job->done_fence);
> + else
> + dma_fence_free(sched_job->done_fence);
> + dma_fence_put(sched_job->inference_done_fence);
> +
> + thames_submit_bos_put(sched_job->bos);
> +
> + if (sched_job->file_priv)
> + thames_file_priv_put(sched_job->file_priv);
> +
> + if (sched_job->job)
> + thames_job_put(sched_job->job);
> +
> + kfree(sched_job);
> +}
> +
> +static void thames_sched_job_put(struct thames_sched_job *job)
> +{
> + kref_put(&job->refcount, thames_sched_job_cleanup);
> +}
> +
> +static void thames_sched_job_free(struct drm_sched_job *sched_job)
> +{
> + struct thames_sched_job *job = to_thames_sched_job(sched_job);
> +
> + drm_sched_job_cleanup(sched_job);
> +
> + thames_sched_job_put(job);
> +}
> +
> +static struct dma_fence *thames_job_run(struct drm_sched_job *sched_job)
> +{
> + struct thames_sched_job *job = to_thames_sched_job(sched_job);
> + struct thames_core *core = container_of(sched_job->sched, struct thames_core, sched);
> + int ret;
> +
> + if (unlikely(job->base.s_fence->finished.error))
> + return NULL;
> +
> + /* The done fence keeps the seqno of the Job's first push. */
> + job->seqno = ++core->emit_seqno;
> + if (!dma_fence_was_initialized(job->done_fence))
> + dma_fence_init(job->done_fence, &thames_fence_ops, &core->fence_lock,
> + core->fence_context, job->seqno);
> +
> + ret = thames_job_hw_submit(core, job);
> + if (ret) {
> + thames_job_fail_done_fence(job, ret);
> + return ERR_PTR(ret);
> + }
> +
> + return dma_fence_get(job->done_fence);
> +}
> +
> +/*
> + * A done fence not signalled here is a Job on a live core's Ring that may still
> + * run.
> + */
> +static void thames_job_cancel(struct drm_sched_job *sched_job)
> +{
> + struct thames_sched_job *job = to_thames_sched_job(sched_job);
> + struct thames_core *core = container_of(sched_job->sched, struct thames_core, sched);
> +
> + if (!dma_fence_was_initialized(job->done_fence) || dma_fence_is_signaled(job->done_fence))
> + return;
> +
> + scoped_guard(mutex, &core->job_lock)
> + list_move_tail(&job->node, &core->held);
> +
> + thames_job_fail_done_fence(job, -ECANCELED);
> +}
> +
> +static const struct drm_sched_backend_ops thames_sched_ops = {
> + .run_job = thames_job_run,
> + .free_job = thames_sched_job_free,
> + .cancel_job = thames_job_cancel,
> +};
> +
> +int thames_job_init(struct thames_core *core)
> +{
> + /* One credit per Ring entry, so the tail never overruns the head. */
> + struct drm_sched_init_args args = {
> + .ops = &thames_sched_ops,
> + .num_rqs = 1,
> + .credit_limit = THAMES_RING_DEPTH,
> + .timeout = MAX_SCHEDULE_TIMEOUT,
> + .name = dev_name(core->dev),
> + .dev = core->dev,
> + };
> + u32 head, tail, before;
> + u64 fence, before_seqno, last;
> + int ret;
> +
> + spin_lock_init(&core->fence_lock);
> + mutex_init(&core->job_lock);
> +
> + INIT_LIST_HEAD(&core->submitted);
> + INIT_LIST_HEAD(&core->held);
> +
> + /*
> + * Both indices run free, so only a tail not at the head is wrong: the
> + * previous instance's entries are still pending. The Fence may trail
> + * the head by one entry, the firmware moving the head first.
> + */
> + head = READ_ONCE(core->ipc->ring.head);
> + tail = READ_ONCE(core->ipc->ring.tail);
> + before = (head - 1) % THAMES_RING_DEPTH;
> +
> + if (tail != head) {
> + dev_err(core->dev, "Ring on " THAMES_CORE_FMT
> + " has %u pending entries (tail %u, head %u); not attaching\n",
> + THAMES_CORE_ARG(core), tail - head, tail, head);
> + return -EBUSY;
> + }
> +
> + /* Read the Fence and the entry after the head and the tail. */
> + rmb();
> + fence = READ_ONCE(core->ipc->fence.seqno);
> + before_seqno = core->ipc->ring.entries[before].host.seqno;
> + last = max(fence, before_seqno);
> +
> + core->ring_tail = head;
> + core->emit_seqno = last;
> +
> + core->fence_context = dma_fence_context_alloc(1);
> +
> + ret = drm_sched_init(&core->sched, &args);
> + if (ret) {
> + dev_err(core->dev, "Failed to create scheduler: %d.", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * These Jobs' core may still run their Kernels: the Jobs, Contexts and buffers
> + * stay allocated.
> + */
> +void thames_job_fini(struct thames_core *core)
> +{
> + struct thames_sched_job *job;
> + u64 first = U64_MAX, last = 0;
> + size_t kept;
> +
> + drm_sched_fini(&core->sched);
> +
> + guard(mutex)(&core->job_lock);
> +
> + kept = list_count_nodes(&core->held);
> + if (!kept)
> + return;
> +
> + list_for_each_entry(job, &core->held, node) {
> + first = min(first, job->seqno);
> + last = max(last, job->seqno);
> + }
> +
> + drm_dev_get(&core->tdev->ddev);
> + dev_warn(core->dev, THAMES_CORE_FMT
> + " may still run %zu Jobs (seqno %llu to %llu); keeping them and their buffers until reboot\n",
> + THAMES_CORE_ARG(core), kept, first, last);
> +}
> +
> +/*
> + * Above the device's timeout, so the firmware ends a runaway Kernel within the
> + * wait.
> + */
> +#define THAMES_SUSPEND_TIMEOUT_MS (THAMES_DEVICE_TIMEOUT_MS + 100)
> +
> +int thames_job_suspend(struct thames_core *core)
> +{
> + struct drm_gpu_scheduler *sched = &core->sched;
> + unsigned int wait_ms;
> + size_t pending;
> + bool idle;
> + int err;
> +
> + drm_sched_wqueue_stop(sched);
> + core->timeout_left = drm_sched_suspend_timeout(sched);
> + cancel_delayed_work_sync(&sched->work_tdr);
> + drm_sched_wqueue_stop(sched);
> +
> + scoped_guard(mutex, &core->job_lock)
> + pending = list_count_nodes(&core->submitted);
> + wait_ms = max_t(unsigned int, pending, 1) * THAMES_SUSPEND_TIMEOUT_MS;
> +
> + err = read_poll_timeout(thames_core_idles, idle, idle, USEC_PER_MSEC,
> + wait_ms * USEC_PER_MSEC, false, core);
> +
> + if (!err) {
> + thames_job_complete(core);
> + scoped_guard(mutex, &core->job_lock) {
> + if (!list_empty(&core->submitted))
> + err = -EBUSY;
> + }
> + }
> +
> + if (err) {
> + dev_err(core->dev, THAMES_CORE_FMT
> + " is not idle with its Jobs completed after %u ms; refusing the suspend\n",
> + THAMES_CORE_ARG(core), wait_ms);
> + thames_job_resume(core);
> + return -EBUSY;
> + }
> +
> + return 0;
> +}
> +
> +void thames_job_resume(struct thames_core *core)
> +{
> + drm_sched_resume_timeout(&core->sched, core->timeout_left);
> + drm_sched_wqueue_start(&core->sched);
> +}
> +
> +int thames_job_open(struct thames_file_priv *thames_priv)
> +{
> + struct thames_device *tdev = thames_priv->tdev;
> + int ret;
> +
> + ret = drm_sched_entity_init(&thames_priv->sched_entity, 0, tdev->sched_list,
> + tdev->num_cores, NULL);
> + if (WARN_ON(ret))
> + return ret;
> +
> + xa_init_flags(&thames_priv->jobs, XA_FLAGS_ALLOC1);
> +
> + return 0;
> +}
> +
> +void thames_job_close(struct thames_file_priv *thames_priv)
> +{
> + struct thames_job *job;
> + unsigned long handle;
> +
> + drm_sched_entity_destroy(&thames_priv->sched_entity);
> +
> + xa_for_each(&thames_priv->jobs, handle, job)
> + thames_job_put(job);
> + xa_destroy(&thames_priv->jobs);
> +}
> +
> +static void thames_job_free(struct kref *ref)
> +{
> + struct thames_job *job = container_of(ref, struct thames_job, refcount);
> +
> + if (job->program)
> + drm_gem_object_put(job->program);
> + if (job->params)
> + drm_gem_object_put(job->params);
> +
> + thames_transfers_free(job->tdev, &job->transfers);
> + mutex_destroy(&job->lock);
> + kfree(job);
> +}
> +
> +static void thames_job_put(struct thames_job *job)
> +{
> + kref_put(&job->refcount, thames_job_free);
> +}
> +
> +int thames_ioctl_job_create(struct drm_device *dev, void *data, struct drm_file *file)
> +{
> + struct drm_thames_job_create *args = data;
> + struct thames_file_priv *file_priv = file->driver_priv;
> + struct thames_device *tdev = to_thames_device(dev);
> + struct thames_job *job;
> + u32 handle;
> + int ret;
> +
> + if (args->flags || args->pad)
> + return -EINVAL;
> +
> + if (args->type != DRM_THAMES_JOB_DISPATCH)
> + return -EINVAL;
> +
> + if (!args->program || !args->params)
> + return -EINVAL;
> +
> + job = kzalloc_obj(*job, GFP_KERNEL);
> + if (!job)
> + return -ENOMEM;
> +
> + kref_init(&job->refcount);
> + mutex_init(&job->lock);
> + job->tdev = tdev;
> +
> + job->program = drm_gem_object_lookup(file, args->program);
> + if (!job->program) {
> + ret = -ENOENT;
> + goto err_put;
> + }
> +
> + if (!(to_thames_bo(job->program)->flags & DRM_THAMES_BO_EXECUTABLE)) {
> + ret = -EINVAL;
> + goto err_put;
> + }
> +
> + job->program_dsp_va = thames_bo_dsp_va(to_thames_bo(job->program), file_priv);
> + if (!job->program_dsp_va) {
> + ret = -EINVAL;
> + goto err_put;
> + }
> +
> + job->params = drm_gem_object_lookup(file, args->params);
> + if (!job->params) {
> + ret = -ENOENT;
> + goto err_put;
> + }
> +
> + job->params_dsp_va = thames_bo_dsp_va(to_thames_bo(job->params), file_priv);
> + if (!job->params_dsp_va) {
> + ret = -EINVAL;
> + goto err_put;
> + }
> +
> + ret = thames_transfers_build(file, &args->transfers, &job->transfers);
> + if (ret)
> + goto err_put;
> +
> + ret = xa_alloc(&file_priv->jobs, &handle, job, xa_limit_32b, GFP_KERNEL);
> + if (ret)
> + goto err_put;
> +
> + job->handle = handle;
> + args->handle = handle;
> +
> + return 0;
> +
> +err_put:
> + thames_job_put(job);
> + return ret;
> +}
> +
> +static struct thames_sched_job *thames_sched_job_new(struct drm_file *file, u32 handle,
> + struct thames_submit_bos *bos)
> +{
> + struct thames_file_priv *file_priv = file->driver_priv;
> + struct thames_job *job;
> + struct thames_sched_job *sched_job;
> + int ret;
> +
> + xa_lock(&file_priv->jobs);
> + job = xa_load(&file_priv->jobs, handle);
> + if (job)
> + kref_get(&job->refcount);
> + xa_unlock(&file_priv->jobs);
> +
> + if (!job)
> + return ERR_PTR(-ENOENT);
> +
> + sched_job = kzalloc_obj(*sched_job, GFP_KERNEL);
> + if (!sched_job) {
> + thames_job_put(job);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + kref_init(&sched_job->refcount);
> + INIT_LIST_HEAD(&sched_job->node);
> + sched_job->tdev = job->tdev;
> + sched_job->job = job; /* The lookup's reference. */
> + /* Put by the completion path once it has counted the Job for the ban. */
> + kref_get(&file_priv->refcount);
> + sched_job->file_priv = file_priv;
> + kref_get(&bos->refcount);
> + sched_job->bos = bos;
> +
> + sched_job->done_fence = kzalloc_obj(*sched_job->done_fence);
> + if (!sched_job->done_fence) {
> + thames_sched_job_put(sched_job);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + ret = drm_sched_job_init(&sched_job->base, &file_priv->sched_entity, 1, NULL,
> + file->client_id);
> + if (ret) {
> + thames_sched_job_put(sched_job);
> + return ERR_PTR(ret);
> + }
> +
> + return sched_job;
> +}
> +
> +static int thames_submit_bos_lookup(struct drm_file *file,
> + const struct drm_thames_job_submit *args,
> + struct thames_submit_bos **out)
> +{
> + const struct drm_thames_obj_array *lists[2] = { &args->read_handles,
> + &args->write_handles };
> + struct thames_submit_bos *bos;
> + u32 total = 0, n = 0, l, i;
> +
> + for (l = 0; l < ARRAY_SIZE(lists); l++) {
> + if (lists[l]->count > THAMES_MAX_SUBMIT_BOS - total)
> + return -EINVAL;
> + total += lists[l]->count;
> + }
> +
> + bos = kvzalloc(struct_size(bos, bos, total), GFP_KERNEL);
> + if (!bos)
> + return -ENOMEM;
> +
> + kref_init(&bos->refcount);
> + bos->count = total;
> + bos->read_count = args->read_handles.count;
> +
> + for (l = 0; l < ARRAY_SIZE(lists); l++) {
> + u32 *handles;
> +
> + if (!lists[l]->count)
> + continue;
> +
> + handles = thames_get_uobj_array(lists[l], sizeof(*handles), sizeof(*handles));
> + if (IS_ERR(handles)) {
> + thames_submit_bos_put(bos);
> + return PTR_ERR(handles);
> + }
> +
> + for (i = 0; i < lists[l]->count; i++, n++) {
> + bos->bos[n] = drm_gem_object_lookup(file, handles[i]);
> + if (!bos->bos[n]) {
> + kvfree(handles);
> + thames_submit_bos_put(bos);
> + return -ENOENT;
> + }
> + }
> +
> + kvfree(handles);
> + }
> +
> + *out = bos;
> + return 0;
> +}
> +
> +/*
> + * Allocated before the first push, so signalling cannot fail once the Jobs run.
> + */
> +struct thames_submit_syncs {
> + u32 count;
> + struct drm_thames_sync_op *ops;
> + struct thames_sync_signal {
> + struct drm_syncobj *syncobj;
> + struct dma_fence_chain *chain;
> + } *signals;
> +};
> +
> +static int thames_sync_op_check(const struct drm_thames_sync_op *op)
> +{
> + if (op->flags != (DRM_THAMES_SYNC_OP_HANDLE_TYPE_TIMELINE_SYNCOBJ |
> + DRM_THAMES_SYNC_OP_SIGNAL))
> + return -EINVAL;
> +
> + return op->timeline_value ? 0 : -EINVAL;
> +}
> +
> +static void thames_submit_syncs_free(struct thames_submit_syncs *syncs)
> +{
> + u32 i;
> +
> + for (i = 0; i < syncs->count; i++) {
> + if (syncs->signals[i].syncobj)
> + drm_syncobj_put(syncs->signals[i].syncobj);
> + dma_fence_chain_free(syncs->signals[i].chain);
> + }
> +
> + kvfree(syncs->signals);
> + kvfree(syncs->ops);
> +}
> +
> +static int thames_submit_syncs_get(struct drm_file *file, const struct drm_thames_obj_array *in,
> + struct thames_submit_syncs *syncs)
> +{
> + struct drm_thames_sync_op *ops;
> + u32 i;
> + int ret;
> +
> + if (!in->count)
> + return 0;
> +
> + if (in->count > THAMES_MAX_SUBMIT_SYNCS)
> + return -EINVAL;
> +
> + ops = thames_get_uobj_array(in, offsetofend(struct drm_thames_sync_op, timeline_value),
> + sizeof(*ops));
> + if (IS_ERR(ops))
> + return PTR_ERR(ops);
> + syncs->ops = ops;
> +
> + syncs->signals = kvcalloc(in->count, sizeof(*syncs->signals), GFP_KERNEL);
> + if (!syncs->signals)
> + return -ENOMEM;
> + syncs->count = in->count;
> +
> + for (i = 0; i < syncs->count; i++) {
> + struct thames_sync_signal *signal = &syncs->signals[i];
> +
> + ret = thames_sync_op_check(&ops[i]);
> + if (ret)
> + return ret;
> +
> + signal->syncobj = drm_syncobj_find(file, ops[i].handle);
> + if (!signal->syncobj)
> + return -ENOENT;
> +
> + signal->chain = dma_fence_chain_alloc();
> + if (!signal->chain)
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +static void thames_submit_syncs_signal(struct thames_submit_syncs *syncs, struct dma_fence *fence)
> +{
> + u32 i;
> +
> + for (i = 0; i < syncs->count; i++) {
> + struct thames_sync_signal *signal = &syncs->signals[i];
> +
> + drm_syncobj_add_point(signal->syncobj, signal->chain, fence,
> + syncs->ops[i].timeline_value);
> + signal->chain = NULL;
> + }
> +}
> +
> +int thames_ioctl_job_submit(struct drm_device *dev, void *data, struct drm_file *file)
> +{
> + struct drm_thames_job_submit *args = data;
> + struct thames_submit_syncs syncs = {};
> + struct thames_submit_bos *bos = NULL;
> + struct ww_acquire_ctx acquire_ctx;
> + struct thames_sched_job **subs = NULL;
> + struct dma_fence *last;
> + u32 count = args->jobs.count;
> + u32 pushed = 0;
> + u32 *handles;
> + unsigned int i;
> + int ret = 0;
> +
> + if (args->flags || args->pad)
> + return -EINVAL;
> +
> + if (!count || count > THAMES_MAX_JOBS_PER_SUBMIT)
> + return -EINVAL;
> +
> + handles = thames_get_uobj_array(&args->jobs, sizeof(u32), sizeof(u32));
> + if (IS_ERR(handles))
> + return PTR_ERR(handles);
> +
> + ret = thames_submit_syncs_get(file, &args->syncs, &syncs);
> + if (ret)
> + goto out_syncs;
> +
> + ret = thames_submit_bos_lookup(file, args, &bos);
> + if (ret)
> + goto out_syncs;
> +
> + subs = kvcalloc(count, sizeof(*subs), GFP_KERNEL);
> + if (!subs) {
> + ret = -ENOMEM;
> + goto out_put;
> + }
> +
> + for (i = 0; i < count; i++) {
> + subs[i] = thames_sched_job_new(file, handles[i], bos);
> + if (IS_ERR(subs[i])) {
> + ret = PTR_ERR(subs[i]);
> + subs[i] = NULL;
> + goto out_subs;
> + }
> + }
> +
> + ret = drm_gem_lock_reservations(bos->bos, bos->count, &acquire_ctx);
> + if (ret)
> + goto out_subs;
> +
> + /*
> + * Only the first Job waits: the entity runs the call's Jobs in order.
> + */
> + ret = thames_acquire_object_fences(bos->bos, bos->read_count, &subs[0]->base, false);
> + if (ret)
> + goto out_unlock;
> +
> + ret = thames_acquire_object_fences(&bos->bos[bos->read_count], bos->count - bos->read_count,
> + &subs[0]->base, true);
> + if (ret)
> + goto out_unlock;
> +
> + for (pushed = 0; pushed < count; pushed++)
> + thames_job_push(subs[pushed]);
> +
> + last = subs[count - 1]->inference_done_fence;
> + thames_attach_object_fences(bos->bos, bos->read_count, last, DMA_RESV_USAGE_READ);
> + thames_attach_object_fences(&bos->bos[bos->read_count], bos->count - bos->read_count, last,
> + DMA_RESV_USAGE_WRITE);
> + thames_submit_syncs_signal(&syncs, last);
> +
> +out_unlock:
> + drm_gem_unlock_reservations(bos->bos, bos->count, &acquire_ctx);
> +out_subs:
> + for (i = 0; i < count; i++) {
> + if (!subs[i])
> + continue;
> + if (i >= pushed)
> + drm_sched_job_cleanup(&subs[i]->base);
> + /* The creation reference; the scheduler holds its own. */
> + thames_sched_job_put(subs[i]);
> + }
> + kvfree(subs);
> +out_put:
> + thames_submit_bos_put(bos);
> +out_syncs:
> + thames_submit_syncs_free(&syncs);
> + kvfree(handles);
> + return ret;
> +}
> +
> +int thames_ioctl_job_destroy(struct drm_device *dev, void *data, struct drm_file *file)
> +{
> + struct drm_thames_job_destroy *args = data;
> + struct thames_file_priv *file_priv = file->driver_priv;
> + struct thames_job *job;
> +
> + if (args->pad)
> + return -EINVAL;
> +
> + job = xa_erase(&file_priv->jobs, args->handle);
> + if (!job)
> + return -ENOENT;
> +
> + /* A submission still running holds its own reference. */
> + thames_job_put(job);
> +
> + return 0;
> +}
> +
> diff --git a/drivers/accel/thames/thames_job.h b/drivers/accel/thames/thames_job.h
> new file mode 100644
> index 000000000000..a851febe8f98
> --- /dev/null
> +++ b/drivers/accel/thames/thames_job.h
> @@ -0,0 +1,73 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright 2024-2025 Tomeu Vizoso <tomeu@xxxxxxxxxxxxxxx> */
> +/* Copyright 2026 Texas Instruments Incorporated - https://www.ti.com/ */
> +
> +#ifndef __THAMES_JOB_H__
> +#define __THAMES_JOB_H__
> +
> +#include <drm/drm_drv.h>
> +#include <drm/gpu_scheduler.h>
> +#include <drm/thames_accel.h>
> +
> +#include "thames_core.h"
> +#include "thames_drv.h"
> +#include "thames_transfers.h"
> +
> +struct thames_submit_bos;
> +
> +struct thames_sched_job {
> + struct drm_sched_job base;
> +
> + struct thames_device *tdev;
> + struct thames_file_priv *file_priv;
> +
> + struct dma_fence *inference_done_fence;
> + struct dma_fence *done_fence;
> +
> + /* @seqno moves at every push; the done fence keeps the first push's. */
> + u64 seqno;
> + u32 ring_index;
> + u64 stamps[6];
> + struct list_head node;
> +
> + struct thames_job *job;
> +
> + struct thames_submit_bos *bos;
> +
> + struct kref refcount;
> +};
> +
> +struct thames_job {
> + struct kref refcount;
> + struct thames_device *tdev;
> + u32 handle;
> +
> + struct drm_gem_object *program;
> + struct drm_gem_object *params;
> +
> + /*
> + * Taken at create: after an unbind the Job faults in its Context rather
> + * than read a binding that is gone.
> + */
> + u64 program_dsp_va;
> + u64 params_dsp_va;
> +
> + struct thames_transfer_table transfers;
> +
> + struct mutex lock;
> +};
> +
> +int thames_ioctl_job_create(struct drm_device *dev, void *data, struct drm_file *file);
> +int thames_ioctl_job_submit(struct drm_device *dev, void *data, struct drm_file *file);
> +int thames_ioctl_job_destroy(struct drm_device *dev, void *data, struct drm_file *file);
> +
> +void thames_job_complete(struct thames_core *core);
> +
> +int thames_job_init(struct thames_core *core);
> +int thames_job_suspend(struct thames_core *core);
> +void thames_job_resume(struct thames_core *core);
> +void thames_job_fini(struct thames_core *core);
> +int thames_job_open(struct thames_file_priv *thames_priv);
> +void thames_job_close(struct thames_file_priv *thames_priv);
> +
> +#endif
> diff --git a/drivers/accel/thames/thames_trace.h b/drivers/accel/thames/thames_trace.h
> new file mode 100644
> index 000000000000..3d1e59708869
> --- /dev/null
> +++ b/drivers/accel/thames/thames_trace.h
> @@ -0,0 +1,77 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright 2026 Texas Instruments Incorporated - https://www.ti.com/ */
> +
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM thames
> +
> +#if !defined(__THAMES_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ)
> +#define __THAMES_TRACE_H__
> +
> +#include <linux/math64.h>
> +#include <linux/time64.h>
> +#include <linux/tracepoint.h>
> +#include <linux/types.h>
> +
> +#include "thames_core.h"
> +#include "thames_device.h"
> +#include "thames_drv.h"
> +#include "thames_job.h"
> +
> +/* Every stamp is the SoC's system counter in microseconds, so any two subtract. */
> +TRACE_EVENT(thames_job_done,
> + TP_PROTO(const struct thames_core *core, const struct thames_sched_job *job,
> + u64 signalled),
> +
> + TP_ARGS(core, job, signalled),
> +
> + TP_STRUCT__entry(__string(dev_name, dev_name(core->dev))
> + __field(u32, core)
> + __field(u32, context)
> + __field(u64, seqno)
> + __field(int, error)
> + __field(u64, submit_us)
> + __field(u64, doorbell_seen_us)
> + __field(u64, run_start_us)
> + __field(u64, kernel_entry_us)
> + __field(u64, kernel_exit_us)
> + __field(u64, run_end_us)
> + __field(u64, signalled_us)),
> +
> + TP_fast_assign(u32 hz = core->tdev->caps.timestamp_hz;
> +
> + __assign_str(dev_name);
> + __entry->core = core->index;
> + __entry->context = job->file_priv->context_id;
> + __entry->seqno = job->seqno;
> + __entry->error = job->done_fence->error;
> + __entry->submit_us =
> + mul_u64_u32_div(job->stamps[0], USEC_PER_SEC, hz);
> + __entry->doorbell_seen_us =
> + mul_u64_u32_div(job->stamps[1], USEC_PER_SEC, hz);
> + __entry->run_start_us =
> + mul_u64_u32_div(job->stamps[2], USEC_PER_SEC, hz);
> + __entry->run_end_us =
> + mul_u64_u32_div(job->stamps[3], USEC_PER_SEC, hz);
> + __entry->kernel_entry_us =
> + mul_u64_u32_div(job->stamps[4], USEC_PER_SEC, hz);
> + __entry->kernel_exit_us =
> + mul_u64_u32_div(job->stamps[5], USEC_PER_SEC, hz);
> + __entry->signalled_us =
> + mul_u64_u32_div(signalled, USEC_PER_SEC, hz);),
> +
> + TP_printk("%s: core=%u context=%u seqno=%llu error=%d submit_us=%llu doorbell_seen_us=%llu run_start_us=%llu kernel_entry_us=%llu kernel_exit_us=%llu run_end_us=%llu signalled_us=%llu",
> + __get_str(dev_name), __entry->core, __entry->context,
> + __entry->seqno, __entry->error, __entry->submit_us,
> + __entry->doorbell_seen_us, __entry->run_start_us,
> + __entry->kernel_entry_us, __entry->kernel_exit_us,
> + __entry->run_end_us, __entry->signalled_us)
> +);
> +
> +#endif /* __THAMES_TRACE_H__ */
> +
> +#undef TRACE_INCLUDE_PATH
> +#define TRACE_INCLUDE_PATH .
> +#undef TRACE_INCLUDE_FILE
> +#define TRACE_INCLUDE_FILE thames_trace
> +
> +#include <trace/define_trace.h>
> diff --git a/include/uapi/drm/thames_accel.h b/include/uapi/drm/thames_accel.h
> index 5671cffaf8f9..bd2b4271b896 100644
> --- a/include/uapi/drm/thames_accel.h
> +++ b/include/uapi/drm/thames_accel.h
> @@ -57,6 +57,15 @@ enum drm_thames_ioctl_id {
> /** @DRM_THAMES_DEV_QUERY: Query the device's capabilities. */
> DRM_THAMES_DEV_QUERY,
>
> + /** @DRM_THAMES_JOB_CREATE: Validate a Dispatch once into a Job. */
> + DRM_THAMES_JOB_CREATE,
> +
> + /** @DRM_THAMES_JOB_SUBMIT: Run created Jobs once, in order. */
> + DRM_THAMES_JOB_SUBMIT,
> +
> + /** @DRM_THAMES_JOB_DESTROY: Release a Job and the buffers it holds. */
> + DRM_THAMES_JOB_DESTROY,
> +
> /**
> * @DRM_THAMES_BO_BIND: Bind buffers into the file's Context at the
> * addresses the client chooses, or unbind them.
> @@ -244,6 +253,21 @@ enum drm_thames_dev_query_type {
>
> };
>
> +/**
> + * enum drm_thames_job_type - What a Job is
> + *
> + * A Job is one Dispatch, and a submission is an ordered array of Jobs. A Job
> + * whose transfers are not K3 DMA records would be a new type, not a new
> + * operation, and so are the copies, fills, timestamps and the list-carrying
> + * command buffer a Vulkan compute driver will add. The query grows a field
> + * naming the types beyond the Dispatch when the first one comes; an absent type
> + * is refused at Job creation.
> + */
> +enum drm_thames_job_type {
> + /** @DRM_THAMES_JOB_DISPATCH: Run a Kernel with its transfers. */
> + DRM_THAMES_JOB_DISPATCH = 0,
> +};
> +
> /**
> * enum drm_thames_hardware_id - &drm_thames_caps.hardware_id
> */
> @@ -255,8 +279,9 @@ enum drm_thames_hardware_id {
> /**
> * struct drm_thames_caps - The capability query
> *
> - * Each window sits at one address whatever core runs the Job and reaches that
> - * core's own memory.
> + * The submit's limits are stated on &struct drm_thames_job_submit. Each window
> + * sits at one address whatever core runs the Job and reaches that core's own
> + * memory.
> */
> struct drm_thames_caps {
> /** @l2_size: Size in bytes of each core's L2 SRAM. */
> @@ -328,6 +353,35 @@ struct drm_thames_caps {
> __u64 dru_capabilities;
> };
>
> +/**
> + * enum drm_thames_fault_kind - The kind of a Job's fault
> + *
> + * The kind is what the firmware knows without decoding a register: what stopped
> + * the Job and whose mode it stopped in.
> + */
> +enum drm_thames_fault_kind {
> + /**
> + * @DRM_THAMES_FAULT_KERNEL: A Kernel took an exception in user mode.
> + * The Context continues; the registers say why.
> + */
> + DRM_THAMES_FAULT_KERNEL = 1,
> +
> + /**
> + * @DRM_THAMES_FAULT_FIRMWARE: The firmware took an exception in
> + * supervisor mode while running this Job. The core was reset.
> + */
> + DRM_THAMES_FAULT_FIRMWARE,
> +
> + /**
> + * @DRM_THAMES_FAULT_TIMEOUT: The device's Job timeout fired: the Kernel
> + * ran longer than the driver lets a Kernel run and the device ended it,
> + * or the Kernel returned with transfers still running and they did not
> + * drain. The Context continues; NRP and NTSR say where the Kernel was,
> + * and are 0 for transfers that did not drain.
> + */
> + DRM_THAMES_FAULT_TIMEOUT,
> +};
> +
> /**
> * struct drm_thames_dev_query - Arguments passed to DRM_IOCTL_THAMES_DEV_QUERY
> */
> @@ -353,9 +407,9 @@ struct drm_thames_dev_query {
> * order: the first group starts at the first transfer, each later group at a
> * transfer carrying this flag. The firmware submits the first group before it
> * enters the Kernel, and each later group, whole, when the Kernel asks for it
> - * with system call 2. No group may hold more transfers on one channel than
> - * &drm_thames_caps.dru_queue_depth. Never on the first transfer. A Job
> - * without the flag is one group.
> + * with system call 2 (see &struct drm_thames_job_create). No group may hold
> + * more transfers on one channel than &drm_thames_caps.dru_queue_depth. Never
> + * on the first transfer. A Job without the flag is one group.
> */
> #define DRM_THAMES_TRANSFER_GROUP (1 << 0)
>
> @@ -393,7 +447,9 @@ struct drm_thames_dev_query {
> * auxiliary aperture, at &drm_thames_caps.l2_main_dsp_va and l2_aux, which the
> * kernel translates to the physical aperture of the core the Job runs on. The
> * Job holds a reference on each buffer a transfer resolves to until it is
> - * destroyed and its last submission has completed.
> + * destroyed and its last submission has completed. No list names these
> + * buffers: the read and write lists of &drm_thames_job_submit are for
> + * synchronisation alone.
> */
> struct drm_thames_transfer {
> /**
> @@ -409,6 +465,191 @@ struct drm_thames_transfer {
> __u32 words[16];
> };
>
> +/**
> + * struct drm_thames_job_create - Arguments passed to DRM_IOCTL_THAMES_JOB_CREATE
> + *
> + * Once per Dispatch: the transfers are copied, each checked and translated, a
> + * reference taken on the program, the params and every buffer a transfer
> + * resolves to so the translation holds for the Job's life, and a handle
> + * returned. The buffers the Kernel reads and writes are named at each
> + * submit, in &drm_thames_job_submit.
> + *
> + * The firmware enters the Kernel at the program's start as a C function
> + * taking one argument, the params' address, and returning a status word:
> + *
> + * int kernel(void *params);
> + *
> + * That is the whole of what the kernel and the firmware know. The Kernel finds
> + * everything else, its inputs, outputs, constants, grid and status buffer,
> + * through params, whose layout is private to the userspace driver that emitted
> + * the Kernel. The program must have been created DRM_THAMES_BO_EXECUTABLE.
> + * The program and the params must be bound in the Context.
> + * The Job keeps the two addresses they are bound at when it is created, and
> + * every submission runs the Kernel at them.
> + *
> + * A Kernel asks for its Job's next transfer group with system call 2 (the
> + * SYSCALL instruction with 2, no argument). The call returns to the
> + * instruction after it with every register kept but A0-A3 and RP. The firmware
> + * submits the group only if the Job has one left and every channel the group
> + * uses has no transfer queued or running; otherwise it fails the Job, writing
> + * none of the group's transfers. A Kernel waits for the tile event of its
> + * channels' DRU records with system call 3.
> + */
> +struct drm_thames_job_create {
> + /** @type: One of &enum drm_thames_job_type. */
> + __u32 type;
> +
> + /** @flags: MBZ. */
> + __u32 flags;
> +
> + /** @program: Handle of the buffer holding the Kernel. */
> + __u32 program;
> +
> + /** @params: Handle of the params buffer. */
> + __u32 params;
> +
> + /**
> + * @transfers: Array of &struct drm_thames_transfer, the DRU records
> + * the compiler scheduled for this Kernel; may be empty.
> + */
> + struct drm_thames_obj_array transfers;
> +
> + /** @handle: Returned handle of the Job. Job handles are nonzero. */
> + __u32 handle;
> +
> + /** @pad: MBZ. */
> + __u32 pad;
> +};
> +
> +/**
> + * DOC: Sync operation flags
> + *
> + * The low byte of &drm_thames_sync_op.flags is the handle type, bit 31 the
> + * direction; every other bit is MBZ.
> + *
> + * The one op accepted is a timeline sync object signalled,
> + * %DRM_THAMES_SYNC_OP_HANDLE_TYPE_TIMELINE_SYNCOBJ |
> + * %DRM_THAMES_SYNC_OP_SIGNAL. Handle type 0 and a clear bit 31, a binary sync
> + * object and a wait, are left for the client that will use them.
> + *
> + * - %DRM_THAMES_SYNC_OP_HANDLE_TYPE_MASK: The bits of the handle type.
> + * - %DRM_THAMES_SYNC_OP_HANDLE_TYPE_TIMELINE_SYNCOBJ: A timeline sync
> + * object, at the point &drm_thames_sync_op.timeline_value.
> + * - %DRM_THAMES_SYNC_OP_SIGNAL: The call's last Job's finished fence is
> + * added to the timeline at the point.
> + */
> +#define DRM_THAMES_SYNC_OP_HANDLE_TYPE_MASK 0xff
> +#define DRM_THAMES_SYNC_OP_HANDLE_TYPE_TIMELINE_SYNCOBJ 1
> +#define DRM_THAMES_SYNC_OP_SIGNAL (1u << 31)
> +
> +/**
> + * struct drm_thames_sync_op - One signal of a submit call
> + */
> +struct drm_thames_sync_op {
> + /**
> + * @flags: %DRM_THAMES_SYNC_OP_HANDLE_TYPE_TIMELINE_SYNCOBJ |
> + * %DRM_THAMES_SYNC_OP_SIGNAL.
> + */
> + __u32 flags;
> +
> + /** @handle: A timeline sync object of the client. */
> + __u32 handle;
> +
> + /**
> + * @timeline_value: The point on the timeline sync object, nonzero.
> + */
> + __u64 timeline_value;
> +};
> +
> +/**
> + * struct drm_thames_job_submit - Arguments passed to DRM_IOCTL_THAMES_JOB_SUBMIT
> + *
> + * A submission is an ordered array of Jobs, one Ring entry each, so a sequence
> + * of Dispatches is one call. Per run the kernel re-checks only the Context's
> + * current state, waits on the implicit fences of the buffers the call names and
> + * pushes the Ring entries. A submission of a Job while an earlier
> + * submission of the same Job is in flight waits for it, which the Ring's order
> + * gives on its own. One Ring completes its Jobs in submission order; there is
> + * no order between the two cores' Rings, but the Jobs of one call run in the
> + * order named.
> + *
> + * The buffers the call's Jobs read and write are named here, one pair of lists
> + * per call. They are for synchronisation alone: the first Job waits on the
> + * fences already on them, and the last Job's fence is set on them once the Jobs
> + * are pushed, for read usage on @read_handles and write usage on
> + * @write_handles, so DRM_IOCTL_THAMES_BO_PREP on a written buffer waits for the
> + * call's Jobs. No flag turns either side off. The call holds a reference on
> + * each buffer named until its last Job completes. A buffer may be named once
> + * in the two lists together.
> + *
> + * The sync objects the call signals are named in @syncs. Every op receives the
> + * call's last Job's finished fence, the same fence the listed buffers get; the
> + * call waits on no sync object. The call is all or nothing: every Job handle
> + * is looked up and every sync op resolved before the first Job is pushed, so a
> + * call that fails has submitted nothing and signals nothing. An opt-out of the
> + * implicit synchronisation would be a bit of @flags, appended under a minor
> + * bump.
> + *
> + * Every Job the call submits has its fault record cleared; a refused call
> + * clears none.
> + *
> + * A Job of the call that fails does not stop the ones after it: each later Job
> + * runs, and completes or fails on its own merits. Only a Context banned after
> + * its faults has its Jobs not yet run cancelled, their fences signalled with
> + * -ECANCELED, and its later submit calls refused with -ECANCELED.
> + */
> +struct drm_thames_job_submit {
> + /** @flags: MBZ. */
> + __u32 flags;
> +
> + /** @pad: MBZ. */
> + __u32 pad;
> +
> + /**
> + * @jobs: Array of __u32 Job handles, run in order.
> + *
> + * At least 1 and at most 256 Jobs.
> + */
> + struct drm_thames_obj_array jobs;
> +
> + /**
> + * @read_handles: Array of __u32 handles of the buffers the Jobs read;
> + * may be empty.
> + *
> + * At most 4096 handles in @read_handles and @write_handles together.
> + */
> + struct drm_thames_obj_array read_handles;
> +
> + /**
> + * @write_handles: Array of __u32 handles of the buffers the Jobs write;
> + * may be empty. Counted with @read_handles.
> + */
> + struct drm_thames_obj_array write_handles;
> +
> + /**
> + * @syncs: Array of &struct drm_thames_sync_op; may be empty.
> + *
> + * At most 64 ops.
> + */
> + struct drm_thames_obj_array syncs;
> +};
> +
> +/**
> + * struct drm_thames_job_destroy - Arguments passed to DRM_IOCTL_THAMES_JOB_DESTROY
> + *
> + * Releases the handle and returns at once; it does not wait for the Job's
> + * submissions. A submission still in flight keeps the Job, its validated copy
> + * and the references on its buffers until it completes, and its fence signals
> + * as it would have. The handle names nothing afterwards.
> + */
> +struct drm_thames_job_destroy {
> + /** @handle: The Job. */
> + __u32 handle;
> +
> + /** @pad: MBZ. */
> + __u32 pad;
> +};
> +
> /**
> * DRM_IOCTL_THAMES() - Build a thames IOCTL number
> * @__access: Access type. Must be R, W or RW.
> @@ -431,6 +672,12 @@ enum {
> DRM_IOCTL_THAMES(WR, BO_MMAP_OFFSET, bo_mmap_offset),
> DRM_IOCTL_THAMES_DEV_QUERY =
> DRM_IOCTL_THAMES(WR, DEV_QUERY, dev_query),
> + DRM_IOCTL_THAMES_JOB_CREATE =
> + DRM_IOCTL_THAMES(WR, JOB_CREATE, job_create),
> + DRM_IOCTL_THAMES_JOB_SUBMIT =
> + DRM_IOCTL_THAMES(WR, JOB_SUBMIT, job_submit),
> + DRM_IOCTL_THAMES_JOB_DESTROY =
> + DRM_IOCTL_THAMES(WR, JOB_DESTROY, job_destroy),
> DRM_IOCTL_THAMES_BO_BIND =
> DRM_IOCTL_THAMES(WR, BO_BIND, bo_bind),
> };
>
> --
> 2.54.0
>