[PATCH 6/6] drm/tegra: Wait for syncpoint syncobj_in fences in hardware
From: Mikko Perttunen
Date: Thu Jul 30 2026 - 00:47:32 EST
Currently all syncobj_in fences are waited synchronously on the CPU.
However, since Host1x channels are capable of waiting for syncpoint
fences on the hardware, let's use that capability.
This filters the any non-syncpoint (or different host1x instance) fences
in syncobj_in, waits only those on the CPU, and adds the rest as waits
to the channel before the actual job contents.
Also make minor clarifications to the doc comments in the UAPI header.
Signed-off-by: Mikko Perttunen <mperttunen@xxxxxxxxxx>
---
drivers/gpu/drm/tegra/submit.c | 111 +++++++++++++++++++++++++++++++++--------
include/uapi/drm/tegra_drm.h | 4 +-
2 files changed, 93 insertions(+), 22 deletions(-)
diff --git a/drivers/gpu/drm/tegra/submit.c b/drivers/gpu/drm/tegra/submit.c
index 1e5332eb6e26..54312922f7b2 100644
--- a/drivers/gpu/drm/tegra/submit.c
+++ b/drivers/gpu/drm/tegra/submit.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020 NVIDIA Corporation */
+#include <linux/dma-fence-unwrap.h>
#include <linux/dma-mapping.h>
#include <linux/file.h>
#include <linux/host1x.h>
@@ -344,6 +345,71 @@ static int submit_get_syncpt(struct tegra_drm_context *context, struct host1x_jo
return 0;
}
+static int submit_handle_in_fence(struct tegra_drm_context *context, struct host1x *host1x,
+ struct dma_fence *fence, u32 *num_waits)
+{
+ struct dma_fence_unwrap iter;
+ struct host1x *fence_host1x;
+ struct dma_fence *f;
+ int err = 0;
+
+ *num_waits = 0;
+
+ dma_fence_unwrap_for_each(f, &iter, fence) {
+ long wait_err;
+
+ if (err)
+ continue;
+
+ if (dma_fence_is_signaled(f))
+ continue;
+
+ err = host1x_fence_extract(f, &fence_host1x, NULL, NULL);
+ if (!err && fence_host1x == host1x) {
+ (*num_waits)++;
+ continue;
+ }
+
+ wait_err = dma_fence_wait_timeout(f, true, msecs_to_jiffies(10000));
+ if (wait_err == 0) {
+ SUBMIT_ERR(context, "wait for syncobj_in timed out");
+ err = -ETIMEDOUT;
+ continue;
+ } else if (wait_err < 0) {
+ /* In practice, -ERESTARTSYS */
+ err = wait_err;
+ continue;
+ } else {
+ err = 0;
+ }
+ }
+
+ return err;
+}
+
+static void submit_job_add_prefence(struct host1x_job *job, struct dma_fence *fence, u32 class)
+{
+ struct dma_fence_unwrap iter;
+ struct dma_fence *f;
+
+ dma_fence_unwrap_for_each(f, &iter, fence) {
+ u32 id, threshold;
+
+ if (dma_fence_is_signaled(f))
+ continue;
+
+ if (host1x_fence_extract(f, NULL, &id, &threshold)) {
+ /*
+ * The fence may have signalled since the check above, and
+ * the dma_fence framework then detached its ops.
+ */
+ continue;
+ }
+
+ host1x_job_add_wait(job, id, threshold, false, class);
+ }
+}
+
static int submit_job_add_gather(struct host1x_job *job, struct tegra_drm_context *context,
struct drm_tegra_submit_cmd_gather_uptr *cmd,
struct gather_bo *bo, u32 *offset,
@@ -389,7 +455,7 @@ static int submit_job_add_gather(struct host1x_job *job, struct tegra_drm_contex
static struct host1x_job *
submit_create_job(struct tegra_drm_context *context, struct gather_bo *bo,
struct drm_tegra_channel_submit *args, struct tegra_drm_submit_data *job_data,
- struct xarray *syncpoints)
+ struct xarray *syncpoints, struct dma_fence *in_fence, u32 num_prefence_waits)
{
struct drm_tegra_submit_cmd *cmds;
u32 i, gather_offset = 0, class;
@@ -406,7 +472,7 @@ submit_create_job(struct tegra_drm_context *context, struct gather_bo *bo,
return ERR_CAST(cmds);
}
- job = host1x_job_alloc(context->channel, args->num_cmds, 0, true);
+ job = host1x_job_alloc(context->channel, args->num_cmds + num_prefence_waits, 0, true);
if (!job) {
SUBMIT_ERR(context, "failed to allocate memory for job");
job = ERR_PTR(-ENOMEM);
@@ -421,6 +487,9 @@ submit_create_job(struct tegra_drm_context *context, struct gather_bo *bo,
job->class = context->client->base.class;
job->serialize = true;
+ if (in_fence)
+ submit_job_add_prefence(job, in_fence, class);
+
for (i = 0; i < args->num_cmds; i++) {
struct drm_tegra_submit_cmd *cmd = &cmds[i];
@@ -506,11 +575,14 @@ static void release_job(struct host1x_job *job)
int tegra_drm_ioctl_channel_submit(struct drm_device *drm, void *data,
struct drm_file *file)
{
+ struct host1x *host1x = dev_get_drvdata(drm->dev->parent);
struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_channel_submit *args = data;
struct tegra_drm_submit_data *job_data;
struct drm_syncobj *syncobj = NULL;
+ struct dma_fence *in_fence = NULL;
struct tegra_drm_context *context;
+ u32 num_prefence_waits = 0;
struct host1x_job *job;
struct gather_bo *bo;
u32 i;
@@ -527,26 +599,15 @@ int tegra_drm_ioctl_channel_submit(struct drm_device *drm, void *data,
}
if (args->syncobj_in) {
- struct dma_fence *fence;
- long wait_err;
-
- err = drm_syncobj_find_fence(file, args->syncobj_in, 0, 0, &fence);
+ err = drm_syncobj_find_fence(file, args->syncobj_in, 0, 0, &in_fence);
if (err) {
SUBMIT_ERR(context, "invalid syncobj_in '%#x'", args->syncobj_in);
goto unlock;
}
- wait_err = dma_fence_wait_timeout(fence, true, msecs_to_jiffies(10000));
- dma_fence_put(fence);
- if (wait_err == 0) {
- SUBMIT_ERR(context, "wait for syncobj_in timed out");
- err = -ETIMEDOUT;
- goto unlock;
- } else if (wait_err < 0) {
- /* In practice, -ERESTARTSYS */
- err = wait_err;
- goto unlock;
- }
+ err = submit_handle_in_fence(context, host1x, in_fence, &num_prefence_waits);
+ if (err)
+ goto put_in_fence;
}
if (args->syncobj_out) {
@@ -554,14 +615,14 @@ int tegra_drm_ioctl_channel_submit(struct drm_device *drm, void *data,
if (!syncobj) {
SUBMIT_ERR(context, "invalid syncobj_out '%#x'", args->syncobj_out);
err = -ENOENT;
- goto unlock;
+ goto put_in_fence;
}
}
/* Allocate gather BO and copy gather words in. */
err = submit_copy_gather_data(&bo, drm->dev, context, args);
if (err)
- goto unlock;
+ goto put_in_fence;
job_data = kzalloc_obj(*job_data);
if (!job_data) {
@@ -576,12 +637,20 @@ int tegra_drm_ioctl_channel_submit(struct drm_device *drm, void *data,
goto free_job_data;
/* Allocate host1x_job and add gathers and waits to it. */
- job = submit_create_job(context, bo, args, job_data, &fpriv->syncpoints);
+ job = submit_create_job(context, bo, args, job_data, &fpriv->syncpoints, in_fence,
+ num_prefence_waits);
if (IS_ERR(job)) {
err = PTR_ERR(job);
goto free_job_data;
}
+ /*
+ * The prefence has been recorded as plain syncpoint waits, so the fence
+ * itself is not needed anymore.
+ */
+ dma_fence_put(in_fence);
+ in_fence = NULL;
+
/* Map gather data for Host1x. */
err = host1x_job_pin(job, context->client->base.dev);
if (err) {
@@ -680,6 +749,8 @@ int tegra_drm_ioctl_channel_submit(struct drm_device *drm, void *data,
kfree(job_data);
put_bo:
gather_bo_put(&bo->base);
+put_in_fence:
+ dma_fence_put(in_fence);
unlock:
if (syncobj)
drm_syncobj_put(syncobj);
diff --git a/include/uapi/drm/tegra_drm.h b/include/uapi/drm/tegra_drm.h
index 8f21f3a44832..b17472103e91 100644
--- a/include/uapi/drm/tegra_drm.h
+++ b/include/uapi/drm/tegra_drm.h
@@ -986,8 +986,8 @@ struct drm_tegra_channel_submit {
/**
* @syncobj_in: [in]
*
- * Handle for DRM syncobj that will be waited before submission.
- * Ignored if zero.
+ * Handle for DRM syncobj that will be waited for before the job
+ * executes. Ignored if zero.
*/
__u32 syncobj_in;
--
2.53.0