[PATCH 2/4] media: videobuf2: add opt-in DMA-BUF CPU access
From: Karl Mehltretter
Date: Mon Oct 19 2026 - 03:15:12 EST
Drivers must bracket CPU access to imported DMA-BUFs with
dma_buf_begin_cpu_access() and dma_buf_end_cpu_access().
Add a queue flag to do so from buf_prepare() through buf_finish(). Use
read access for output and write access for capture. Map imports
bidirectionally because CPU and device access directions are opposite.
Pass the mapping direction to attach_dmabuf() so USERPTR and MMAP keep
the queue's normal direction.
Keep this opt-in because some vb2-vmalloc drivers pass buffers to
hardware. End access in process context because DMA-BUF callbacks may
sleep.
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <kmehltretter@xxxxxxxxx>
---
.../media/common/videobuf2/videobuf2-core.c | 83 ++++++++++++++++++-
.../common/videobuf2/videobuf2-dma-contig.c | 5 +-
.../media/common/videobuf2/videobuf2-dma-sg.c | 5 +-
.../common/videobuf2/videobuf2-vmalloc.c | 5 +-
include/media/videobuf2-core.h | 12 ++-
5 files changed, 101 insertions(+), 9 deletions(-)
diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c
index 460eaf4e82f78..d2a78cd0a86bb 100644
--- a/drivers/media/common/videobuf2/videobuf2-core.c
+++ b/drivers/media/common/videobuf2/videobuf2-core.c
@@ -371,6 +371,65 @@ static void __vb2_buf_mem_finish(struct vb2_buffer *vb)
call_void_memop(vb, finish, vb->planes[plane].mem_priv);
}
+/* Output buffers are read by the driver; capture buffers are written. */
+static enum dma_data_direction vb2_cpu_access_dir(const struct vb2_queue *q)
+{
+ return q->is_output ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+}
+
+static bool __vb2_dmabuf_is_duplicate(const struct vb2_buffer *vb,
+ unsigned int plane)
+{
+ unsigned int i;
+
+ for (i = 0; i < plane; i++)
+ if (vb->planes[i].dbuf == vb->planes[plane].dbuf)
+ return true;
+
+ return false;
+}
+
+static void __vb2_buf_end_cpu_access(struct vb2_buffer *vb,
+ unsigned int num_planes)
+{
+ struct vb2_queue *q = vb->vb2_queue;
+ enum dma_data_direction dir = vb2_cpu_access_dir(q);
+ unsigned int plane;
+
+ for (plane = 0; plane < num_planes; plane++) {
+ int ret;
+
+ if (__vb2_dmabuf_is_duplicate(vb, plane))
+ continue;
+
+ ret = dma_buf_end_cpu_access(vb->planes[plane].dbuf, dir);
+ if (ret)
+ pr_warn_ratelimited("[%s] failed to end CPU access to dmabuf plane %u: %d\n",
+ q->name, plane, ret);
+ }
+}
+
+static int __vb2_buf_begin_cpu_access(struct vb2_buffer *vb)
+{
+ struct vb2_queue *q = vb->vb2_queue;
+ enum dma_data_direction dir = vb2_cpu_access_dir(q);
+ unsigned int plane;
+ int ret;
+
+ for (plane = 0; plane < vb->num_planes; plane++) {
+ if (__vb2_dmabuf_is_duplicate(vb, plane))
+ continue;
+
+ ret = dma_buf_begin_cpu_access(vb->planes[plane].dbuf, dir);
+ if (ret) {
+ __vb2_buf_end_cpu_access(vb, plane);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
/*
* __setup_offsets() - setup unique offsets ("cookies") for every plane in
* the buffer.
@@ -1423,6 +1482,9 @@ static int __prepare_dmabuf(struct vb2_buffer *vb)
}
if (reacquired) {
+ enum dma_data_direction attach_dir = q->uses_cpu_access ?
+ DMA_BIDIRECTIONAL : q->dma_dir;
+
if (vb->planes[0].mem_priv) {
vb->copied_timestamp = 0;
call_void_vb_qop(vb, buf_cleanup, vb);
@@ -1453,7 +1515,8 @@ static int __prepare_dmabuf(struct vb2_buffer *vb)
vb,
q->alloc_devs[plane] ? : q->dev,
planes[plane].dbuf,
- planes[plane].length);
+ planes[plane].length,
+ attach_dir);
if (IS_ERR(mem_priv)) {
dprintk(q, 1, "failed to attach dmabuf\n");
ret = PTR_ERR(mem_priv);
@@ -1504,9 +1567,21 @@ static int __prepare_dmabuf(struct vb2_buffer *vb)
}
}
+ if (q->uses_cpu_access) {
+ ret = __vb2_buf_begin_cpu_access(vb);
+ if (ret) {
+ dprintk(q, 1, "failed to begin CPU access to dmabuf: %d\n",
+ ret);
+ call_void_vb_qop(vb, buf_cleanup, vb);
+ goto err_put_vb2_buf;
+ }
+ }
+
ret = call_vb_qop(vb, buf_prepare, vb);
if (ret) {
dprintk(q, 1, "buffer preparation failed\n");
+ if (q->uses_cpu_access)
+ __vb2_buf_end_cpu_access(vb, vb->num_planes);
call_void_vb_qop(vb, buf_cleanup, vb);
goto err_put_vb2_buf;
}
@@ -1756,6 +1831,8 @@ int vb2_core_remove_bufs(struct vb2_queue *q, unsigned int start, unsigned int c
__vb2_buf_mem_finish(vb);
call_void_vb_qop(vb, buf_finish, vb);
+ if (q->memory == VB2_MEMORY_DMABUF && q->uses_cpu_access)
+ __vb2_buf_end_cpu_access(vb, vb->num_planes);
vb->prepared = 0;
}
@@ -2160,6 +2237,8 @@ int vb2_core_dqbuf(struct vb2_queue *q, unsigned int *pindex, void *pb,
}
call_void_vb_qop(vb, buf_finish, vb);
+ if (q->memory == VB2_MEMORY_DMABUF && q->uses_cpu_access)
+ __vb2_buf_end_cpu_access(vb, vb->num_planes);
vb->prepared = 0;
if (pindex)
@@ -2296,6 +2375,8 @@ static void __vb2_queue_cancel(struct vb2_queue *q)
if (vb->prepared) {
call_void_vb_qop(vb, buf_finish, vb);
+ if (q->memory == VB2_MEMORY_DMABUF && q->uses_cpu_access)
+ __vb2_buf_end_cpu_access(vb, vb->num_planes);
vb->prepared = 0;
}
__vb2_dqbuf(vb);
diff --git a/drivers/media/common/videobuf2/videobuf2-dma-contig.c b/drivers/media/common/videobuf2/videobuf2-dma-contig.c
index 9ce6284cd5f26..68c146c043fb1 100644
--- a/drivers/media/common/videobuf2/videobuf2-dma-contig.c
+++ b/drivers/media/common/videobuf2/videobuf2-dma-contig.c
@@ -768,7 +768,8 @@ static void vb2_dc_detach_dmabuf(void *mem_priv)
}
static void *vb2_dc_attach_dmabuf(struct vb2_buffer *vb, struct device *dev,
- struct dma_buf *dbuf, unsigned long size)
+ struct dma_buf *dbuf, unsigned long size,
+ enum dma_data_direction dma_dir)
{
struct vb2_dc_buf *buf;
struct dma_buf_attachment *dba;
@@ -794,7 +795,7 @@ static void *vb2_dc_attach_dmabuf(struct vb2_buffer *vb, struct device *dev,
return dba;
}
- buf->dma_dir = vb->vb2_queue->dma_dir;
+ buf->dma_dir = dma_dir;
buf->size = size;
buf->db_attach = dba;
diff --git a/drivers/media/common/videobuf2/videobuf2-dma-sg.c b/drivers/media/common/videobuf2/videobuf2-dma-sg.c
index b1d0695cda260..ab03f9d31391a 100644
--- a/drivers/media/common/videobuf2/videobuf2-dma-sg.c
+++ b/drivers/media/common/videobuf2/videobuf2-dma-sg.c
@@ -616,7 +616,8 @@ static void vb2_dma_sg_detach_dmabuf(void *mem_priv)
}
static void *vb2_dma_sg_attach_dmabuf(struct vb2_buffer *vb, struct device *dev,
- struct dma_buf *dbuf, unsigned long size)
+ struct dma_buf *dbuf, unsigned long size,
+ enum dma_data_direction dma_dir)
{
struct vb2_dma_sg_buf *buf;
struct dma_buf_attachment *dba;
@@ -640,7 +641,7 @@ static void *vb2_dma_sg_attach_dmabuf(struct vb2_buffer *vb, struct device *dev,
return dba;
}
- buf->dma_dir = vb->vb2_queue->dma_dir;
+ buf->dma_dir = dma_dir;
buf->size = size;
buf->db_attach = dba;
buf->vb = vb;
diff --git a/drivers/media/common/videobuf2/videobuf2-vmalloc.c b/drivers/media/common/videobuf2/videobuf2-vmalloc.c
index 63f023696f3ee..c38faa0188bc9 100644
--- a/drivers/media/common/videobuf2/videobuf2-vmalloc.c
+++ b/drivers/media/common/videobuf2/videobuf2-vmalloc.c
@@ -402,7 +402,8 @@ static void vb2_vmalloc_detach_dmabuf(void *mem_priv)
static void *vb2_vmalloc_attach_dmabuf(struct vb2_buffer *vb,
struct device *dev,
struct dma_buf *dbuf,
- unsigned long size)
+ unsigned long size,
+ enum dma_data_direction dma_dir)
{
struct vb2_vmalloc_buf *buf;
@@ -414,7 +415,7 @@ static void *vb2_vmalloc_attach_dmabuf(struct vb2_buffer *vb,
return ERR_PTR(-ENOMEM);
buf->dbuf = dbuf;
- buf->dma_dir = vb->vb2_queue->dma_dir;
+ buf->dma_dir = dma_dir;
buf->size = size;
return buf;
diff --git a/include/media/videobuf2-core.h b/include/media/videobuf2-core.h
index 4b4f4c15c53a7..8dfe22fa4423c 100644
--- a/include/media/videobuf2-core.h
+++ b/include/media/videobuf2-core.h
@@ -78,7 +78,8 @@ struct vb2_buffer;
* to the userspace, also optional.
* @attach_dmabuf: attach a shared &struct dma_buf for a hardware operation;
* used for DMABUF memory types; dev is the alloc device
- * dbuf is the shared dma_buf; returns ERR_PTR() on failure;
+ * dbuf is the shared dma_buf; dma_dir is the DMA mapping
+ * direction; returns ERR_PTR() on failure;
* allocator private per-buffer structure on success;
* this needs to be used for further accesses to the buffer.
* @detach_dmabuf: inform the exporter of the buffer that the current DMABUF
@@ -136,7 +137,8 @@ struct vb2_mem_ops {
void *(*attach_dmabuf)(struct vb2_buffer *vb,
struct device *dev,
struct dma_buf *dbuf,
- unsigned long size);
+ unsigned long size,
+ enum dma_data_direction dma_dir);
void (*detach_dmabuf)(void *buf_priv);
int (*map_dmabuf)(void *buf_priv);
void (*unmap_dmabuf)(void *buf_priv);
@@ -510,6 +512,11 @@ struct vb2_buf_ops {
* ->finish().
* @non_coherent_mem: when set queue will attempt to allocate buffers using
* non-coherent memory.
+ * @uses_cpu_access: the driver transfers buffer data with the CPU instead of
+ * DMA. For imported dma-bufs, videobuf2 brackets the interval from
+ * buf_prepare through buf_finish with dma_buf_begin_cpu_access()
+ * and dma_buf_end_cpu_access(). Imported attachments use
+ * bidirectional DMA mappings.
* @lock: pointer to a mutex that protects the &struct vb2_queue. The
* driver must set this to a mutex to let the v4l2 core serialize
* the queuing ioctls. This lock is used when waiting for a new
@@ -614,6 +621,7 @@ struct vb2_queue {
unsigned int uses_requests:1;
unsigned int allow_cache_hints:1;
unsigned int non_coherent_mem:1;
+ unsigned int uses_cpu_access:1;
struct mutex *lock;
void *owner;
--
2.47.3