[PATCH 70/74] media: qcom: camss: video: Support metadata output nodes

From: bod

Date: Mon Oct 05 2026 - 13:39:04 EST


From: Bryan O'Donoghue <bryan.odonoghue@xxxxxxxxxx>

Parameters need metadata output nodes. Register a node of video->type
V4L2_BUF_TYPE_META_OUTPUT alongside the metadata capture node:

- a source pad, VFL_DIR_TX and the META_OUTPUT format ioctls, returning
the fixed format the producer sets before registration
- each buffer has a kernel copy, buf->meta: the payload is copied into
it when the buffer is prepared, and the optional prepare_buffer()
video op validates the copy, so what is validated is what the
producer consumes
- like the capture node, it neither starts nor stops the line, and
stopping it returns its buffers

Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@xxxxxxxxxx>
---
drivers/media/platform/qcom/camss/camss-video.c | 101 ++++++++++++++++++++++--
drivers/media/platform/qcom/camss/camss-video.h | 8 ++
2 files changed, 101 insertions(+), 8 deletions(-)

diff --git a/drivers/media/platform/qcom/camss/camss-video.c b/drivers/media/platform/qcom/camss/camss-video.c
index b8501c895d393..69cbe78b80622 100644
--- a/drivers/media/platform/qcom/camss/camss-video.c
+++ b/drivers/media/platform/qcom/camss/camss-video.c
@@ -414,6 +414,65 @@ static const struct vb2_ops msm_video_meta_vb2_q_ops = {
.unprepare_streaming = video_unprepare_streaming,
};

+/*
+ * A metadata output buffer is copied to the kernel when it is prepared, so
+ * what is validated is what the producer later consumes.
+ */
+static int video_meta_out_buf_init(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer,
+ vb);
+
+ buffer->meta = kvmalloc(video->active_fmt.fmt.meta.buffersize,
+ GFP_KERNEL);
+
+ return buffer->meta ? 0 : -ENOMEM;
+}
+
+static void video_meta_out_buf_cleanup(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer,
+ vb);
+
+ kvfree(buffer->meta);
+ buffer->meta = NULL;
+}
+
+static int video_meta_out_buf_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer,
+ vb);
+ size_t payload = vb2_get_plane_payload(vb, 0);
+
+ if (payload > video->active_fmt.fmt.meta.buffersize)
+ return -EINVAL;
+
+ memcpy(buffer->meta, vb2_plane_vaddr(vb, 0), payload);
+ vbuf->field = V4L2_FIELD_NONE;
+
+ if (video->ops->prepare_buffer)
+ return video->ops->prepare_buffer(video, buffer);
+
+ return 0;
+}
+
+static const struct vb2_ops msm_video_meta_out_vb2_q_ops = {
+ .queue_setup = video_meta_queue_setup,
+ .buf_init = video_meta_out_buf_init,
+ .buf_cleanup = video_meta_out_buf_cleanup,
+ .buf_prepare = video_meta_out_buf_prepare,
+ .buf_queue = video_buf_queue,
+ .prepare_streaming = video_prepare_streaming,
+ .start_streaming = video_meta_start_streaming,
+ .stop_streaming = video_meta_stop_streaming,
+ .unprepare_streaming = video_unprepare_streaming,
+};
+
/* -----------------------------------------------------------------------------
* V4L2 ioctls
*/
@@ -721,6 +780,23 @@ static const struct v4l2_ioctl_ops msm_vid_meta_ioctl_ops = {
.vidioc_streamoff = vb2_ioctl_streamoff,
};

+static const struct v4l2_ioctl_ops msm_vid_meta_out_ioctl_ops = {
+ .vidioc_querycap = video_querycap,
+ .vidioc_enum_fmt_meta_out = video_meta_enum_fmt,
+ .vidioc_g_fmt_meta_out = video_meta_g_fmt,
+ .vidioc_s_fmt_meta_out = video_meta_g_fmt,
+ .vidioc_try_fmt_meta_out = video_meta_g_fmt,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
/* -----------------------------------------------------------------------------
* V4L2 file operations
*/
@@ -796,7 +872,8 @@ static int msm_video_init_format(struct camss_video *video)
int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev,
const char *name)
{
- bool meta = video->type == V4L2_BUF_TYPE_META_CAPTURE;
+ bool meta_out = video->type == V4L2_BUF_TYPE_META_OUTPUT;
+ bool meta = meta_out || video->type == V4L2_BUF_TYPE_META_CAPTURE;
struct media_pad *pad = &video->pad;
struct video_device *vdev;
struct vb2_queue *q;
@@ -809,9 +886,13 @@ int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev,
q = &video->vb2_q;
q->drv_priv = video;
q->mem_ops = meta ? &vb2_vmalloc_memops : &vb2_dma_sg_memops;
- q->ops = meta ? &msm_video_meta_vb2_q_ops : &msm_video_vb2_q_ops;
- q->type = meta ? V4L2_BUF_TYPE_META_CAPTURE :
- V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ if (meta_out)
+ q->ops = &msm_video_meta_out_vb2_q_ops;
+ else if (meta)
+ q->ops = &msm_video_meta_vb2_q_ops;
+ else
+ q->ops = &msm_video_vb2_q_ops;
+ q->type = meta ? video->type : V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
q->io_modes = meta ? VB2_DMABUF | VB2_MMAP :
VB2_DMABUF | VB2_MMAP | VB2_READ;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
@@ -824,7 +905,7 @@ int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev,
goto error_vb2_init;
}

- pad->flags = MEDIA_PAD_FL_SINK;
+ pad->flags = meta_out ? MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK;
ret = media_entity_pads_init(&vdev->entity, 1, pad);
if (ret < 0) {
dev_err(v4l2_dev->dev, "Failed to init video entity: %d\n",
@@ -836,7 +917,7 @@ int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev,

if (meta) {
/* dataformat and buffersize are set by the producer */
- video->active_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
+ video->active_fmt.type = video->type;
} else {
ret = msm_video_init_format(video);
if (ret < 0) {
@@ -847,7 +928,11 @@ int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev,
}

vdev->fops = &msm_vid_fops;
- if (meta) {
+ if (meta_out) {
+ vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING |
+ V4L2_CAP_IO_MC;
+ vdev->ioctl_ops = &msm_vid_meta_out_ioctl_ops;
+ } else if (meta) {
vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING |
V4L2_CAP_IO_MC;
vdev->ioctl_ops = &msm_vid_meta_ioctl_ops;
@@ -859,7 +944,7 @@ int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev,
}
vdev->release = msm_video_release;
vdev->v4l2_dev = v4l2_dev;
- vdev->vfl_dir = VFL_DIR_RX;
+ vdev->vfl_dir = meta_out ? VFL_DIR_TX : VFL_DIR_RX;
vdev->queue = &video->vb2_q;
vdev->lock = &video->lock;
strscpy(vdev->name, name, sizeof(vdev->name));
diff --git a/drivers/media/platform/qcom/camss/camss-video.h b/drivers/media/platform/qcom/camss/camss-video.h
index d3e56e240a888..21a951b7fe9bc 100644
--- a/drivers/media/platform/qcom/camss/camss-video.h
+++ b/drivers/media/platform/qcom/camss/camss-video.h
@@ -23,6 +23,9 @@ struct camss_buffer {
struct vb2_v4l2_buffer vb;
dma_addr_t addr[3];
struct list_head queue;
+
+ /* Metadata output nodes: kernel copy of the buffer, validated at prepare */
+ void *meta;
};

struct camss_video;
@@ -31,6 +34,11 @@ struct camss_video_ops {
int (*queue_buffer)(struct camss_video *vid, struct camss_buffer *buf);
int (*flush_buffers)(struct camss_video *vid,
enum vb2_buffer_state state);
+ /*
+ * Optional, metadata output nodes: validate the kernel copy of a
+ * buffer, buf->meta, when it is prepared
+ */
+ int (*prepare_buffer)(struct camss_video *vid, struct camss_buffer *buf);
};

struct camss_video {

--
2.55.0