[PATCH v3 11/16] accel/thames: Add IOCTL for memory synchronization
From: Tomeu Vizoso
Date: Fri Oct 23 2026 - 02:45:33 EST
These DSP cores have their own access to the memory bus, and it isn't
cache coherent with the CPUs.
Add IOCTLs so userspace can mark when the caches need to be flushed, and
wait for every Job still using a buffer, readers included, before the
CPU accesses it.
Initially based on the same IOCTLs from the Etnaviv driver.
Assisted-by: LLM
Signed-off-by: Tomeu Vizoso <tomeu@xxxxxxxxxxxxxxx>
---
v2:
- Add thames_accel.h UAPI header (Robert Nelson).
---
drivers/accel/thames/thames_drv.c | 3 ++
drivers/accel/thames/thames_gem.c | 63 +++++++++++++++++++++++++++++++++++++++
drivers/accel/thames/thames_gem.h | 4 +++
include/uapi/drm/thames_accel.h | 56 ++++++++++++++++++++++++++++++++++
4 files changed, 126 insertions(+)
diff --git a/drivers/accel/thames/thames_drv.c b/drivers/accel/thames/thames_drv.c
index 5f704cbfe1d0..798b4743f336 100644
--- a/drivers/accel/thames/thames_drv.c
+++ b/drivers/accel/thames/thames_drv.c
@@ -252,6 +252,7 @@ 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(bo_fini)
THAMES_IOCTL_ENTERED(dev_query)
THAMES_IOCTL_ENTERED(job_create)
THAMES_IOCTL_ENTERED(job_submit)
@@ -262,6 +263,8 @@ static const struct drm_ioctl_desc thames_drm_driver_ioctls[] = {
#define THAMES_IOCTL(n, func) DRM_IOCTL_DEF_DRV(THAMES_##n, thames_ioctl_##func, 0)
THAMES_IOCTL(BO_CREATE, bo_create_entered),
THAMES_IOCTL(BO_MMAP_OFFSET, bo_mmap_offset_entered),
+ THAMES_IOCTL(BO_PREP, bo_prep),
+ THAMES_IOCTL(BO_FINI, bo_fini_entered),
THAMES_IOCTL(DEV_QUERY, dev_query_entered),
THAMES_IOCTL(JOB_CREATE, job_create_entered),
THAMES_IOCTL(JOB_SUBMIT, job_submit_entered),
diff --git a/drivers/accel/thames/thames_gem.c b/drivers/accel/thames/thames_gem.c
index c9fd08c7aaf4..5a4dc497768f 100644
--- a/drivers/accel/thames/thames_gem.c
+++ b/drivers/accel/thames/thames_gem.c
@@ -942,3 +942,66 @@ int thames_ioctl_bo_mmap_offset(struct drm_device *ddev, void *data, struct drm_
return 0;
}
+int thames_ioctl_bo_prep(struct drm_device *ddev, void *data, struct drm_file *file)
+{
+ struct drm_thames_bo_prep *args = data;
+ struct thames_gem_object *bo;
+ struct drm_gem_object *gem_obj;
+ unsigned long timeout = drm_timeout_abs_to_jiffies(args->timeout_ns);
+ long ret = 0;
+
+ if (args->reserved != 0)
+ return -EINVAL;
+
+ gem_obj = drm_gem_object_lookup(file, args->handle);
+ if (!gem_obj)
+ return -ENOENT;
+
+ ret = dma_resv_wait_timeout(gem_obj->resv, DMA_RESV_USAGE_READ, true, timeout);
+ if (ret == 0)
+ ret = timeout ? -ETIMEDOUT : -EBUSY;
+ else if (ret > 0)
+ ret = 0;
+
+ bo = to_thames_bo(gem_obj);
+ if (bo->flags & DRM_THAMES_BO_CONTIGUOUS)
+ dma_sync_single_for_cpu(ddev->dev, bo->base.dma_addr, gem_obj->size,
+ DMA_BIDIRECTIONAL);
+ else
+ dma_sync_sgtable_for_cpu(ddev->dev, bo->shmem.sgt, DMA_BIDIRECTIONAL);
+ /* Order the cache maintenance before the CPU reads. */
+ mb();
+
+ drm_gem_object_put(gem_obj);
+
+ return ret;
+}
+
+int thames_ioctl_bo_fini(struct drm_device *ddev, void *data, struct drm_file *file)
+{
+ struct drm_thames_bo_fini *args = data;
+ struct thames_gem_object *bo;
+ struct drm_gem_object *gem_obj;
+
+ if (args->reserved != 0)
+ return -EINVAL;
+
+ gem_obj = drm_gem_object_lookup(file, args->handle);
+ if (!gem_obj)
+ return -ENOENT;
+
+ bo = to_thames_bo(gem_obj);
+ if (bo->flags & DRM_THAMES_BO_CONTIGUOUS) {
+ dma_sync_single_for_device(ddev->dev, bo->base.dma_addr, gem_obj->size,
+ DMA_BIDIRECTIONAL);
+ } else {
+ dma_sync_sgtable_for_device(ddev->dev, bo->shmem.sgt, DMA_BIDIRECTIONAL);
+ }
+
+ /* Order the cache maintenance before the return. */
+ mb();
+
+ drm_gem_object_put(gem_obj);
+
+ return 0;
+}
diff --git a/drivers/accel/thames/thames_gem.h b/drivers/accel/thames/thames_gem.h
index c9d0e170dad3..7c9d375d1ebd 100644
--- a/drivers/accel/thames/thames_gem.h
+++ b/drivers/accel/thames/thames_gem.h
@@ -51,6 +51,10 @@ int thames_ioctl_bo_create(struct drm_device *ddev, void *data, struct drm_file
int thames_ioctl_bo_mmap_offset(struct drm_device *ddev, void *data, struct drm_file *file);
+int thames_ioctl_bo_prep(struct drm_device *ddev, void *data, struct drm_file *file);
+
+int thames_ioctl_bo_fini(struct drm_device *ddev, void *data, struct drm_file *file);
+
int thames_ioctl_bo_bind(struct drm_device *ddev, void *data, struct drm_file *file);
int thames_context_create(struct thames_file_priv *priv);
diff --git a/include/uapi/drm/thames_accel.h b/include/uapi/drm/thames_accel.h
index d193967485cb..ae43885bdcb5 100644
--- a/include/uapi/drm/thames_accel.h
+++ b/include/uapi/drm/thames_accel.h
@@ -54,6 +54,12 @@ enum drm_thames_ioctl_id {
*/
DRM_THAMES_BO_MMAP_OFFSET,
+ /** @DRM_THAMES_BO_PREP: Prepare a BO for CPU access after DSP writes. */
+ DRM_THAMES_BO_PREP,
+
+ /** @DRM_THAMES_BO_FINI: Finish CPU access and prepare BO for DSP access. */
+ DRM_THAMES_BO_FINI,
+
/** @DRM_THAMES_DEV_QUERY: Query the device's capabilities. */
DRM_THAMES_DEV_QUERY,
@@ -143,6 +149,52 @@ struct drm_thames_bo_mmap_offset {
__u64 offset;
};
+/**
+ * struct drm_thames_bo_prep - ioctl argument for preparing a BO for CPU access.
+ *
+ * Waits for the DSP to be done with the buffer and invalidates the CPU caches,
+ * so that what the DSP wrote is visible to the CPU. The DSP is not coherent
+ * with the CPU, and the wait and the cache invalidation are deliberately the
+ * same operation: userspace has no way of skipping the wait and keeping the
+ * invalidation, so a mapping is never handed out with stale cache lines.
+ *
+ * Returns 0 once the buffer is ready for CPU access, -ETIMEDOUT if
+ * &drm_thames_bo_prep.timeout_ns passed before the DSP was done with it, or
+ * -EBUSY if that timestamp was not in the future at the call, 0 included, and
+ * the buffer is still in use. It must never return -EAGAIN: userspace reaches
+ * this ioctl through drmIoctl(), which restarts on both -EINTR and -EAGAIN and
+ * would spin forever.
+ */
+struct drm_thames_bo_prep {
+ /** @handle: Handle of the buffer to prepare for CPU access. */
+ __u32 handle;
+
+ /** @reserved: MBZ. */
+ __u32 reserved;
+
+ /**
+ * @timeout_ns: Absolute CLOCK_MONOTONIC timestamp at which the wait
+ * gives up. A timestamp in the past, 0 included, makes the ioctl
+ * return without blocking.
+ */
+ __s64 timeout_ns;
+};
+
+/**
+ * struct drm_thames_bo_fini - ioctl argument for finishing CPU access to a BO.
+ *
+ * Flushes the CPU caches, so that what the CPU wrote is visible to the DSP.
+ * Userspace has to call this after writing to a mapping and before the buffer
+ * is used by a job, as the DSP is not coherent with the CPU.
+ */
+struct drm_thames_bo_fini {
+ /** @handle: Handle of the buffer the CPU is done writing to. */
+ __u32 handle;
+
+ /** @reserved: MBZ. */
+ __u32 reserved;
+};
+
/**
* struct drm_thames_obj_array - An array of versioned objects passed by pointer.
*
@@ -768,6 +820,10 @@ enum {
DRM_IOCTL_THAMES(WR, BO_CREATE, bo_create),
DRM_IOCTL_THAMES_BO_MMAP_OFFSET =
DRM_IOCTL_THAMES(WR, BO_MMAP_OFFSET, bo_mmap_offset),
+ DRM_IOCTL_THAMES_BO_PREP =
+ DRM_IOCTL_THAMES(WR, BO_PREP, bo_prep),
+ DRM_IOCTL_THAMES_BO_FINI =
+ DRM_IOCTL_THAMES(WR, BO_FINI, bo_fini),
DRM_IOCTL_THAMES_DEV_QUERY =
DRM_IOCTL_THAMES(WR, DEV_QUERY, dev_query),
DRM_IOCTL_THAMES_JOB_CREATE =
--
2.54.0