All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Christian König" <christian.koenig@amd.com>
To: Tomeu Vizoso <tomeu.vizoso@gmail.com>, Nishanth Menon <nm@ti.com>,
	"Andrew F. Davis" <afd@ti.com>, Randolph Sapp <rs@ti.com>,
	Jonathan Humphreys <j-humphreys@ti.com>,
	Andrei Aldea <a-aldea@ti.com>,
	Chirag Shilwant <c-shilwant@ti.com>,
	Vignesh Raghavendra <vigneshr@ti.com>,
	Beleswar Padhi <b-padhi@ti.com>, Tero Kristo <kristo@kernel.org>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Oded Gabbay <ogabbay@kernel.org>,
	Jonathan Corbet <corbet@lwn.net>,
	Sumit Semwal <sumit.semwal@linaro.org>,
	Robert Nelson <robertcnelson@gmail.com>,
	David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
	Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	Maxime Ripard <mripard@kernel.org>,
	Thomas Zimmermann <tzimmermann@suse.de>,
	Jani Nikula <jani.nikula@linux.intel.com>,
	Tvrtko Ursulin <tursulin@ursulin.net>,
	Markus Elfring <Markus.Elfring@web.de>,
	Bjorn Andersson <andersson@kernel.org>,
	Mathieu Poirier <mathieu.poirier@linaro.org>,
	Suman Anna <s-anna@ti.com>, Kees Cook <kees@kernel.org>,
	"Gustavo A. R. Silva" <gustavoars@kernel.org>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>
Cc: linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org,
	linux-doc@vger.kernel.org, linux-media@vger.kernel.org,
	linaro-mm-sig@lists.linaro.org,
	Tomeu Vizoso <tomeu@tomeuvizoso.net>,
	linux-remoteproc@vger.kernel.org,
	linux-hardening@vger.kernel.org
Subject: Re: [PATCH v3 09/16] accel/thames: Add IOCTLs for job creation and submission
Date: Mon, 26 Oct 2026 11:10:25 +0100	[thread overview]
Message-ID: <bd97b230-b513-4864-b4dc-882ae224c2ac@amd.com> (raw)
In-Reply-To: <20261023-thames-v3-9-3ab40aa17157@tomeuvizoso.net>

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@tomeuvizoso.net>
> 
> ---
> 
> 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@kernel.org> */
> +/* Copyright 2019 Collabora ltd. */
> +/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
> +/* 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@tomeuvizoso.net> */
> +/* 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
> 


  reply	other threads:[~2026-10-26 10:10 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-23  6:39 [PATCH v3 00/16] New DRM accel driver for Texas Instruments' C7x DSPs Tomeu Vizoso
2026-10-23  6:39 ` [PATCH v3 01/16] dt-bindings: remoteproc: k3-dsp: Add the L2 auxiliary and DRU regions Tomeu Vizoso
2026-10-23  6:39 ` [PATCH v3 02/16] dt-bindings: remoteproc: k3-dsp: Allow a second sub-mailbox Tomeu Vizoso
2026-10-26  7:49   ` Krzysztof Kozlowski
2026-10-26  7:49     ` Krzysztof Kozlowski
2026-10-23  6:39 ` [PATCH v3 03/16] dt-bindings: remoteproc: k3-dsp: Allow referencing the timers' power domains Tomeu Vizoso
2026-10-23  6:48   ` sashiko-bot
2026-10-26  7:51   ` Krzysztof Kozlowski
2026-10-26  7:51     ` Krzysztof Kozlowski
2026-10-23  6:39 ` [PATCH v3 04/16] accel/thames: Add header with IPC definitions Tomeu Vizoso
2026-10-23  6:39 ` [PATCH v3 05/16] accel/thames: Add driver for the C7x DSPs in TI SoCs Tomeu Vizoso
2026-10-23  7:37   ` sashiko-bot
2026-10-23  6:39 ` [PATCH v3 06/16] accel/thames: Create a context per client on device open Tomeu Vizoso
2026-10-23  7:27   ` sashiko-bot
2026-10-23  6:39 ` [PATCH v3 07/16] accel/thames: Add IOCTLs for BO creation and mapping Tomeu Vizoso
2026-10-23  7:40   ` sashiko-bot
2026-10-26 10:07   ` Christian König
2026-10-23  6:39 ` [PATCH v3 08/16] accel/thames: Add support for submitting DMA operations Tomeu Vizoso
2026-10-23  7:16   ` sashiko-bot
2026-10-23  6:39 ` [PATCH v3 09/16] accel/thames: Add IOCTLs for job creation and submission Tomeu Vizoso
2026-10-26 10:10   ` Christian König [this message]
2026-10-23  6:39 ` [PATCH v3 10/16] accel/thames: Recover from faults and timeouts Tomeu Vizoso
2026-10-23  6:39 ` [PATCH v3 11/16] accel/thames: Add IOCTL for memory synchronization Tomeu Vizoso
2026-10-23  6:53   ` sashiko-bot
2026-10-23  6:39 ` [PATCH v3 12/16] accel/thames: Install memory mappings populated and dirty Tomeu Vizoso
2026-10-23  7:34   ` sashiko-bot
2026-10-23  6:39 ` [PATCH v3 13/16] Documentation: accel/thames: Add initial docs Tomeu Vizoso
2026-10-23  6:39 ` [PATCH v3 14/16] arm64: dts: ti: k3-j722s: Add the AUX L2 SRAM and DRU regions Tomeu Vizoso
2026-10-23  6:39 ` [PATCH v3 15/16] arm64: dts: ti: k3-j722s: Add second submailbox to the C7x cores Tomeu Vizoso
2026-10-23  6:39 ` [PATCH v3 16/16] arm64: dts: ti: k3-j722s: Reference the power domains of the C7x timers Tomeu Vizoso
2026-10-23  7:17   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=bd97b230-b513-4864-b4dc-882ae224c2ac@amd.com \
    --to=christian.koenig@amd.com \
    --cc=Markus.Elfring@web.de \
    --cc=a-aldea@ti.com \
    --cc=afd@ti.com \
    --cc=airlied@gmail.com \
    --cc=andersson@kernel.org \
    --cc=b-padhi@ti.com \
    --cc=c-shilwant@ti.com \
    --cc=conor+dt@kernel.org \
    --cc=corbet@lwn.net \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=gustavoars@kernel.org \
    --cc=j-humphreys@ti.com \
    --cc=jani.nikula@linux.intel.com \
    --cc=kees@kernel.org \
    --cc=kristo@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linaro-mm-sig@lists.linaro.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=linux-remoteproc@vger.kernel.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mathieu.poirier@linaro.org \
    --cc=mripard@kernel.org \
    --cc=nm@ti.com \
    --cc=ogabbay@kernel.org \
    --cc=rdunlap@infradead.org \
    --cc=robertcnelson@gmail.com \
    --cc=robh@kernel.org \
    --cc=rs@ti.com \
    --cc=s-anna@ti.com \
    --cc=simona@ffwll.ch \
    --cc=skhan@linuxfoundation.org \
    --cc=sumit.semwal@linaro.org \
    --cc=tomeu.vizoso@gmail.com \
    --cc=tomeu@tomeuvizoso.net \
    --cc=tursulin@ursulin.net \
    --cc=tzimmermann@suse.de \
    --cc=vigneshr@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.