mirror of https://lore.kernel.org/lkml/
 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: 21+ 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-23  6:39 ` [PATCH v3 03/16] dt-bindings: remoteproc: k3-dsp: Allow referencing the timers' power domains Tomeu Vizoso
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  6:39 ` [PATCH v3 06/16] accel/thames: Create a context per client on device open Tomeu Vizoso
2026-10-23  6:39 ` [PATCH v3 07/16] accel/thames: Add IOCTLs for BO creation and mapping Tomeu Vizoso
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  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:39 ` [PATCH v3 12/16] accel/thames: Install memory mappings populated and dirty Tomeu Vizoso
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

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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®