Re: [PATCH v7 4/4] media: virtio: Add ioctl operations and driver logic
From: Dorinda Bassey
Date: Wed Aug 19 2026 - 07:02:27 EST
> From: Alexandre Courbot <gnurou@xxxxxxxxx>
> This patch adds the ioctl operations and the remaining driver logic
> for polling and mmapping.
> It adds drivers/media/virtio/virtio_media_ioctls.c and updates
> virtio_media_driver.c to support poll, mmap, and ioctls.
> Signed-off-by: Alexandre Courbot <gnurou@xxxxxxxxx>
> Assisted-by: Antigravity:gemini-3.5-flash
> Co-developed-by: Brian Daniels <briandaniels@xxxxxxxxxx>
> Signed-off-by: Brian Daniels <briandaniels@xxxxxxxxxx>
Tested-by: Dorinda Bassey <dbassey@xxxxxxxxxx>
On Thu, Aug 13, 2026 at 8:01 PM Brian Daniels <briandaniels@xxxxxxxxxx> wrote:
>
> From: Alexandre Courbot <gnurou@xxxxxxxxx>
>
> This patch adds the ioctl operations and the remaining driver logic
> for polling and mmapping.
>
> It adds drivers/media/virtio/virtio_media_ioctls.c and updates
> virtio_media_driver.c to support poll, mmap, and ioctls.
>
> Signed-off-by: Alexandre Courbot <gnurou@xxxxxxxxx>
> Assisted-by: Antigravity:gemini-3.5-flash
> Co-developed-by: Brian Daniels <briandaniels@xxxxxxxxxx>
> Signed-off-by: Brian Daniels <briandaniels@xxxxxxxxxx>
> ---
> drivers/media/virtio/Makefile | 2 +-
> drivers/media/virtio/virtio_media_driver.c | 195 +++
> drivers/media/virtio/virtio_media_ioctls.c | 1319 ++++++++++++++++++++
> 3 files changed, 1515 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/virtio/virtio_media_ioctls.c
>
> diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile
> index 8290d8506..f1bc8a3ce 100644
> --- a/drivers/media/virtio/Makefile
> +++ b/drivers/media/virtio/Makefile
> @@ -2,6 +2,6 @@
> #
> # Makefile for the virtio-media device driver.
>
> -virtio-media-objs := scatterlist_builder.o virtio_media_driver.o
> +virtio-media-objs := scatterlist_builder.o virtio_media_ioctls.o virtio_media_driver.o
>
> obj-$(CONFIG_MEDIA_VIRTIO) += virtio-media.o
> diff --git a/drivers/media/virtio/virtio_media_driver.c b/drivers/media/virtio/virtio_media_driver.c
> index ef8065e34..5840bb349 100644
> --- a/drivers/media/virtio/virtio_media_driver.c
> +++ b/drivers/media/virtio/virtio_media_driver.c
> @@ -6,6 +6,7 @@
> * Copyright (c) 2024-2026 Google LLC.
> */
>
> +#include <linux/bits.h>
> #include <linux/device.h>
> #include <linux/dev_printk.h>
> #include <linux/mutex.h>
> @@ -19,6 +20,8 @@
> #include <linux/vmalloc.h>
> #include <linux/workqueue.h>
> #include <linux/dma-mapping.h>
> +#include <linux/poll.h>
> +#include <linux/mm.h>
>
> #include <media/v4l2-dev.h>
> #include <media/v4l2-device.h>
> @@ -31,6 +34,12 @@
>
> #define VIRTIO_MEDIA_NUM_EVENT_BUFS 16
>
> +/* ID of the SHM region into which MMAP buffer will be mapped. */
> +#define VIRTIO_MEDIA_SHM_MMAP 0
> +
> +/* Bit mask for the VIRTIO_MEDIA_MMAP_FLAG_RW flag */
> +#define VIRTIO_MEDIA_MMAP_FLAG_RW_MASK BIT(VIRTIO_MEDIA_MMAP_FLAG_RW)
> +
> /**
> * virtio_media_session_alloc() - Allocate a new session.
> * @vv: virtio-media device the session belongs to.
> @@ -593,10 +602,191 @@ static int virtio_media_device_close(struct file *file)
> return virtio_media_session_close(vv, session);
> }
>
> +/**
> + * virtio_media_device_poll() - Poll logic for a virtio-media device.
> + * @file: file of the session to poll.
> + * @wait: poll table to wait on.
> + */
> +static __poll_t virtio_media_device_poll(struct file *file, poll_table *wait)
> +{
> + struct virtio_media_session *session =
> + fh_to_session(file->private_data);
> + enum v4l2_buf_type capture_type =
> + session->uses_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
> + V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + enum v4l2_buf_type output_type =
> + session->uses_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE :
> + V4L2_BUF_TYPE_VIDEO_OUTPUT;
> + struct virtio_media_queue_state *capture_queue =
> + &session->queues[capture_type];
> + struct virtio_media_queue_state *output_queue =
> + &session->queues[output_type];
> + __poll_t req_events = poll_requested_events(wait);
> + __poll_t rc = 0;
> +
> + poll_wait(file, &session->dqbuf_wait, wait);
> + poll_wait(file, &session->fh.wait, wait);
> +
> + mutex_lock(&session->queues_lock);
> + if (req_events & (EPOLLIN | EPOLLRDNORM)) {
> + if (!capture_queue->streaming ||
> + (capture_queue->queued_bufs == 0 &&
> + list_empty(&capture_queue->pending_dqbufs)))
> + rc |= EPOLLERR;
> + else if (!list_empty(&capture_queue->pending_dqbufs))
> + rc |= EPOLLIN | EPOLLRDNORM;
> + }
> + if (req_events & (EPOLLOUT | EPOLLWRNORM)) {
> + if (!output_queue->streaming)
> + rc |= EPOLLERR;
> + else if (output_queue->queued_bufs <
> + output_queue->allocated_bufs)
> + rc |= EPOLLOUT | EPOLLWRNORM;
> + }
> + mutex_unlock(&session->queues_lock);
> +
> + if (v4l2_event_pending(&session->fh))
> + rc |= EPOLLPRI;
> +
> + return rc;
> +}
> +
> +static void virtio_media_vma_close_locked(struct vm_area_struct *vma)
> +{
> + struct virtio_media *vv = vma->vm_private_data;
> + struct virtio_media_cmd_munmap *cmd_munmap = &vv->cmd.munmap;
> + struct virtio_media_resp_munmap *resp_munmap = &vv->resp.munmap;
> + struct scatterlist cmd_sg = {}, resp_sg = {};
> + struct scatterlist *sgs[2] = { &cmd_sg, &resp_sg };
> + int ret;
> +
> + sg_set_buf(&cmd_sg, cmd_munmap, sizeof(*cmd_munmap));
> + sg_mark_end(&cmd_sg);
> +
> + sg_set_buf(&resp_sg, resp_munmap, sizeof(*resp_munmap));
> + sg_mark_end(&resp_sg);
> +
> + cmd_munmap->hdr.cmd = VIRTIO_MEDIA_CMD_MUNMAP;
> + cmd_munmap->driver_addr =
> + (vma->vm_pgoff << PAGE_SHIFT) - vv->mmap_region.addr;
> + ret = virtio_media_send_command(vv, sgs, 1, 1, sizeof(*resp_munmap),
> + NULL);
> + if (ret < 0) {
> + v4l2_err(&vv->v4l2_dev, "host failed to unmap buffer: %d\n",
> + ret);
> + }
> +}
> +
> +/**
> + * virtio_media_vma_close() - Close a MMAP buffer mapping.
> + * @vma: VMA of the mapping to close.
> + *
> + * Inform the host that a previously created MMAP mapping is no longer needed
> + * and can be removed.
> + */
> +static void virtio_media_vma_close(struct vm_area_struct *vma)
> +{
> + struct virtio_media *vv = vma->vm_private_data;
> +
> + mutex_lock(&vv->vlock);
> + virtio_media_vma_close_locked(vma);
> + mutex_unlock(&vv->vlock);
> +}
> +
> +static const struct vm_operations_struct virtio_media_vm_ops = {
> + .close = virtio_media_vma_close,
> +};
> +
> +/**
> + * virtio_media_device_mmap() - Perform a mmap request from userspace.
> + * @file: opened file of the session to map for.
> + * @vma: VM area struct describing the desired mapping.
> + *
> + * This requests the host to map a MMAP buffer for us, so we can then make that
> + * mapping visible into user-space address space.
> + */
> +static int virtio_media_device_mmap(struct file *file,
> + struct vm_area_struct *vma)
> +{
> + struct video_device *video_dev = video_devdata(file);
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_session *session =
> + fh_to_session(file->private_data);
> + struct virtio_media_cmd_mmap *cmd_mmap = &session->cmd.mmap;
> + struct virtio_media_resp_mmap *resp_mmap = &session->resp.mmap;
> + struct scatterlist cmd_sg = {}, resp_sg = {};
> + struct scatterlist *sgs[2] = { &cmd_sg, &resp_sg };
> + int ret;
> +
> + if (!(vma->vm_flags & VM_SHARED))
> + return -EINVAL;
> + if (!(vma->vm_flags & (VM_READ | VM_WRITE)))
> + return -EINVAL;
> +
> + mutex_lock(&vv->vlock);
> +
> + cmd_mmap->hdr.cmd = VIRTIO_MEDIA_CMD_MMAP;
> + cmd_mmap->session_id = session->id;
> + cmd_mmap->flags =
> + (vma->vm_flags & VM_WRITE) ? VIRTIO_MEDIA_MMAP_FLAG_RW_MASK : 0;
> + cmd_mmap->offset = vma->vm_pgoff << PAGE_SHIFT;
> +
> + sg_set_buf(&cmd_sg, cmd_mmap, sizeof(*cmd_mmap));
> + sg_mark_end(&cmd_sg);
> +
> + sg_set_buf(&resp_sg, resp_mmap, sizeof(*resp_mmap));
> + sg_mark_end(&resp_sg);
> +
> + /*
> + * The host performs reference counting and is smart enough to return
> + * the same guest physical address if this is called several times on
> + * the same
> + * buffer.
> + */
> + ret = virtio_media_send_command(vv, sgs, 1, 1, sizeof(*resp_mmap),
> + NULL);
> + if (ret < 0)
> + goto end;
> +
> + vma->vm_private_data = vv;
> + /*
> + * Keep the guest address at which the buffer is mapped since we will
> + * use that to unmap.
> + */
> + vma->vm_pgoff = (resp_mmap->driver_addr + vv->mmap_region.addr) >>
> + PAGE_SHIFT;
> +
> + /*
> + * We cannot let the mapping be larger than the buffer.
> + */
> + if (vma->vm_end - vma->vm_start > PAGE_ALIGN(resp_mmap->len)) {
> + dev_dbg(&video_dev->dev,
> + "invalid MMAP, as it would overflow buffer length\n");
> + virtio_media_vma_close_locked(vma);
> + ret = -EINVAL;
> + goto end;
> + }
> +
> + ret = io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
> + vma->vm_end - vma->vm_start,
> + vma->vm_page_prot);
> + if (ret)
> + goto end;
> +
> + vma->vm_ops = &virtio_media_vm_ops;
> +
> +end:
> + mutex_unlock(&vv->vlock);
> + return ret;
> +}
> +
> static const struct v4l2_file_operations virtio_media_fops = {
> .owner = THIS_MODULE,
> .open = virtio_media_device_open,
> .release = virtio_media_device_close,
> + .poll = virtio_media_device_poll,
> + .unlocked_ioctl = virtio_media_device_ioctl,
> + .mmap = virtio_media_device_mmap,
> };
>
> static int virtio_media_probe(struct virtio_device *virtio_dev)
> @@ -654,9 +844,14 @@ static int virtio_media_probe(struct virtio_device *virtio_dev)
> vv->eventq = vqs[1];
> INIT_WORK(&vv->eventq_work, virtio_media_event_work);
>
> + /* Get MMAP buffer mapping SHM region */
> + virtio_get_shm_region(virtio_dev, &vv->mmap_region,
> + VIRTIO_MEDIA_SHM_MMAP);
> +
> vd = &vv->video_dev;
> vd->v4l2_dev = &vv->v4l2_dev;
> vd->vfl_type = VFL_TYPE_VIDEO;
> + vd->ioctl_ops = &virtio_media_ioctl_ops;
> vd->fops = &virtio_media_fops;
> vd->release = video_device_release_empty;
> strscpy(vd->name, "virtio-media", sizeof(vd->name));
> diff --git a/drivers/media/virtio/virtio_media_ioctls.c b/drivers/media/virtio/virtio_media_ioctls.c
> new file mode 100644
> index 000000000..f0b82b5ec
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media_ioctls.c
> @@ -0,0 +1,1319 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> +
> +/*
> + * Ioctl implementations for the virtio-media driver.
> + *
> + * Copyright (c) 2024-2026 Google LLC.
> + */
> +
> +#include <linux/mutex.h>
> +#include <linux/videodev2.h>
> +#include <linux/virtio_config.h>
> +#include <linux/vmalloc.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +
> +#include "scatterlist_builder.h"
> +#include "virtio_media.h"
> +
> +/**
> + * virtio_media_send_r_ioctl() - Send a read-only ioctl to the device.
> + * @fh: file handler of the session doing the ioctl.
> + * @ioctl: ``VIDIOC_*`` ioctl code.
> + * @ioctl_data: pointer to the ioctl payload.
> + * @ioctl_data_len: length in bytes of the ioctl payload.
> + *
> + * Send an ioctl that has no driver payload, but expects a response from the
> + * host (i.e. an ioctl specified with ``_IOR``).
> + */
> +static int virtio_media_send_r_ioctl(struct v4l2_fh *fh, u32 ioctl,
> + void *ioctl_data, size_t ioctl_data_len)
> +{
> + struct video_device *video_dev = fh->vdev;
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_session *session = fh_to_session(fh);
> + struct scatterlist *sgs[3];
> + struct scatterlist_builder builder = {
> + .descs = session->command_sgs.sgl,
> + .num_descs = DESC_CHAIN_MAX_LEN,
> + .cur_desc = 0,
> + .shadow_buffer = session->shadow_buf,
> + .shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
> + .shadow_buffer_pos = 0,
> + .sgs = sgs,
> + .num_sgs = ARRAY_SIZE(sgs),
> + .cur_sg = 0,
> + };
> +
> + /* Command descriptor */
> + int ret = scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl);
> +
> + if (ret)
> + return ret;
> +
> + /* Response descriptor */
> + ret = scatterlist_builder_add_ioctl_resp(&builder, session);
> + if (ret)
> + return ret;
> +
> + /* Response payload */
> + ret = scatterlist_builder_add_data(&builder, ioctl_data,
> + ioctl_data_len);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to prepare command descriptor chain\n");
> + return ret;
> + }
> +
> + ret = virtio_media_send_command(vv, sgs, 1, 2,
> + sizeof(struct virtio_media_resp_ioctl) +
> + ioctl_data_len, NULL);
> + if (ret < 0)
> + return ret;
> +
> + ret = scatterlist_builder_retrieve_data(&builder, 2, ioctl_data);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to retrieve response descriptor chain\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_send_w_ioctl() - Send a write-only ioctl to the device.
> + * @fh: file handler of the session doing the ioctl.
> + * @ioctl: ``VIDIOC_*`` ioctl code.
> + * @ioctl_data: pointer to the ioctl payload.
> + * @ioctl_data_len: length in bytes of the ioctl payload.
> + *
> + * Send an ioctl that does not expect a reply beyond an error status (i.e. an
> + * ioctl specified with ``_IOW``) to the host.
> + */
> +static int virtio_media_send_w_ioctl(struct v4l2_fh *fh, u32 ioctl,
> + const void *ioctl_data,
> + size_t ioctl_data_len)
> +{
> + struct video_device *video_dev = fh->vdev;
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_session *session = fh_to_session(fh);
> + struct scatterlist *sgs[3];
> + struct scatterlist_builder builder = {
> + .descs = session->command_sgs.sgl,
> + .num_descs = DESC_CHAIN_MAX_LEN,
> + .cur_desc = 0,
> + .shadow_buffer = session->shadow_buf,
> + .shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
> + .shadow_buffer_pos = 0,
> + .sgs = sgs,
> + .num_sgs = ARRAY_SIZE(sgs),
> + .cur_sg = 0,
> + };
> +
> + /* Command descriptor */
> + int ret = scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl);
> +
> + if (ret)
> + return ret;
> +
> + /* Command payload */
> + ret = scatterlist_builder_add_data(&builder, (void *)ioctl_data,
> + ioctl_data_len);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to prepare command descriptor chain\n");
> + return ret;
> + }
> +
> + /* Response descriptor */
> + ret = scatterlist_builder_add_ioctl_resp(&builder, session);
> + if (ret)
> + return ret;
> +
> + ret = virtio_media_send_command(vv, sgs, 2, 1,
> + sizeof(struct virtio_media_resp_ioctl),
> + NULL);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_send_wr_ioctl() - Send a read-write ioctl to the device.
> + * @fh: file handler of the session doing the ioctl.
> + * @ioctl: ``VIDIOC_*`` ioctl code.
> + * @ioctl_data: pointer to the ioctl payload.
> + * @ioctl_data_len: length in bytes of the ioctl payload.
> + * @minimum_resp_payload: minimum expected length of the response's payload.
> + *
> + * Sends an ioctl that expects a response of exactly the same size as the
> + * input (i.e. an ioctl specified with ``_IOWR``) to the host.
> + *
> + * This corresponds to what most V4L2 ioctls do. For instance
> + * ``VIDIOC_ENUM_FMT`` takes a partially-initialized &struct v4l2_fmtdesc
> + * and returns its filled version.
> + */
> +static int virtio_media_send_wr_ioctl(struct v4l2_fh *fh, u32 ioctl,
> + void *ioctl_data, size_t ioctl_data_len,
> + size_t minimum_resp_payload)
> +{
> + struct video_device *video_dev = fh->vdev;
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_session *session = fh_to_session(fh);
> + struct scatterlist *sgs[4];
> + struct scatterlist_builder builder = {
> + .descs = session->command_sgs.sgl,
> + .num_descs = DESC_CHAIN_MAX_LEN,
> + .cur_desc = 0,
> + .shadow_buffer = session->shadow_buf,
> + .shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
> + .shadow_buffer_pos = 0,
> + .sgs = sgs,
> + .num_sgs = ARRAY_SIZE(sgs),
> + .cur_sg = 0,
> + };
> +
> + /* Command descriptor */
> + int ret = scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl);
> +
> + if (ret)
> + return ret;
> +
> + /* Command payload */
> + ret = scatterlist_builder_add_data(&builder, ioctl_data,
> + ioctl_data_len);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to prepare command descriptor chain\n");
> + return ret;
> + }
> +
> + /* Response descriptor */
> + ret = scatterlist_builder_add_ioctl_resp(&builder, session);
> + if (ret)
> + return ret;
> +
> + /* Response payload, same as command */
> + ret = scatterlist_builder_add_descriptor(&builder, 1);
> + if (ret)
> + return ret;
> +
> + ret = virtio_media_send_command(vv, sgs, 2, 2,
> + sizeof(struct virtio_media_resp_ioctl) +
> + minimum_resp_payload,
> + NULL);
> + if (ret < 0)
> + return ret;
> +
> + ret = scatterlist_builder_retrieve_data(&builder, 3, ioctl_data);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to retrieve response descriptor chain\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_send_buffer_ioctl() - Send an ioctl taking a buffer as
> + * parameter to the device.
> + * @fh: file handler of the session doing the ioctl.
> + * @ioctl: ``VIDIOC_*`` ioctl code.
> + * @b: &struct v4l2_buffer to be sent as the ioctl payload.
> + *
> + * Buffers can require an additional descriptor to send their planes array, and
> + * can have pointers to userspace memory hence this dedicated function.
> + */
> +static int virtio_media_send_buffer_ioctl(struct v4l2_fh *fh, u32 ioctl,
> + struct v4l2_buffer *b)
> +{
> + struct video_device *video_dev = fh->vdev;
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_session *session = fh_to_session(fh);
> + struct v4l2_plane *orig_planes = NULL;
> + struct scatterlist *sgs[64];
> + /*
> + * End of the device-readable buffer SGs, to reuse in device-writable
> + * section.
> + */
> + size_t num_cmd_sgs;
> + size_t end_buf_sg;
> + struct scatterlist_builder builder = {
> + .descs = session->command_sgs.sgl,
> + .num_descs = DESC_CHAIN_MAX_LEN,
> + .cur_desc = 0,
> + .shadow_buffer = session->shadow_buf,
> + .shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
> + .shadow_buffer_pos = 0,
> + .sgs = sgs,
> + .num_sgs = ARRAY_SIZE(sgs),
> + .cur_sg = 0,
> + };
> + size_t resp_len;
> + int ret;
> + int i;
> +
> + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type))
> + orig_planes = b->m.planes;
> +
> + /* Command descriptor */
> + ret = scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl);
> + if (ret)
> + return ret;
> +
> + /* Command payload (struct v4l2_buffer) */
> + ret = scatterlist_builder_add_buffer(&builder, b);
> + if (ret < 0)
> + return ret;
> +
> + end_buf_sg = builder.cur_sg;
> + num_cmd_sgs = builder.cur_sg;
> +
> + /* Response descriptor */
> + ret = scatterlist_builder_add_ioctl_resp(&builder, session);
> + if (ret)
> + return ret;
> +
> + /* Response payload (same as input, but no userptr mapping) */
> + for (i = 1; i < end_buf_sg; i++) {
> + ret = scatterlist_builder_add_descriptor(&builder, i);
> + if (ret < 0)
> + return ret;
> + }
> +
> + ret = virtio_media_send_command(vv, builder.sgs, num_cmd_sgs,
> + builder.cur_sg - num_cmd_sgs,
> + sizeof(struct virtio_media_resp_ioctl) +
> + sizeof(*b), &resp_len);
> + if (ret < 0)
> + return ret;
> +
> + resp_len -= sizeof(struct virtio_media_resp_ioctl);
> +
> + /* Make sure that the reply length covers our v4l2_buffer */
> + if (resp_len < sizeof(*b))
> + return -EINVAL;
> +
> + ret = scatterlist_builder_retrieve_buffer(&builder, num_cmd_sgs + 1, b,
> + orig_planes);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to retrieve response descriptor chain\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_send_ext_controls_ioctl() - Send an ioctl taking extended
> + * controls as parameters to the device.
> + * @fh: file handler of the session doing the ioctl.
> + * @ioctl: ``VIDIOC_*`` ioctl code.
> + * @ctrls: &struct v4l2_ext_controls to be sent as the ioctl payload.
> + *
> + * Queues an ioctl that sends a &struct v4l2_ext_controls to the host and
> + * receives an updated version.
> + *
> + * &struct v4l2_ext_controls has a pointer to an array of
> + * &struct v4l2_ext_control, and also potentially pointers to user-space memory
> + * that we need to map properly, hence the dedicated function.
> + */
> +static int virtio_media_send_ext_controls_ioctl(struct v4l2_fh *fh, u32 ioctl,
> + struct v4l2_ext_controls *ctrls)
> +{
> + struct video_device *video_dev = fh->vdev;
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_session *session = fh_to_session(fh);
> + size_t num_cmd_sgs;
> + size_t end_ctrls_sg;
> + struct v4l2_ext_control *controls_backup = ctrls->controls;
> + const u32 num_ctrls = ctrls->count;
> + struct scatterlist *sgs[64];
> + struct scatterlist_builder builder = {
> + .descs = session->command_sgs.sgl,
> + .num_descs = DESC_CHAIN_MAX_LEN,
> + .cur_desc = 0,
> + .shadow_buffer = session->shadow_buf,
> + .shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
> + .shadow_buffer_pos = 0,
> + .sgs = sgs,
> + .num_sgs = ARRAY_SIZE(sgs),
> + .cur_sg = 0,
> + };
> + size_t resp_len = 0;
> + int i;
> +
> + /* Command descriptor */
> + int ret = scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl);
> +
> + if (ret)
> + return ret;
> +
> + /* v4l2_controls */
> + ret = scatterlist_builder_add_ext_ctrls(&builder, ctrls);
> + if (ret)
> + return ret;
> +
> + end_ctrls_sg = builder.cur_sg;
> +
> + ret = scatterlist_builder_add_ext_ctrls_userptrs(&builder, ctrls);
> + if (ret)
> + return ret;
> +
> + num_cmd_sgs = builder.cur_sg;
> +
> + /* Response descriptor */
> + ret = scatterlist_builder_add_ioctl_resp(&builder, session);
> + if (ret)
> + return ret;
> +
> + /* Response payload (same as input but without userptrs) */
> + for (i = 1; i < end_ctrls_sg; i++) {
> + ret = scatterlist_builder_add_descriptor(&builder, i);
> + if (ret < 0)
> + return ret;
> + }
> +
> + ret = virtio_media_send_command(vv, builder.sgs, num_cmd_sgs,
> + builder.cur_sg - num_cmd_sgs,
> + sizeof(struct virtio_media_resp_ioctl) +
> + sizeof(*ctrls),
> + &resp_len);
> +
> + /* Just in case the host touched these. */
> + ctrls->controls = controls_backup;
> + if (ctrls->count != num_ctrls) {
> + v4l2_err(&vv->v4l2_dev,
> + "device returned a number of controls different than the one submitted\n");
> + }
> + if (ctrls->count > num_ctrls)
> + return -ENOSPC;
> +
> + /*
> + * Even if we have received an error, we may need to read our payload
> + * back.
> + */
> + if (ret < 0 && resp_len >= sizeof(struct virtio_media_resp_ioctl) +
> + sizeof(*ctrls)) {
> + /*
> + * Deliberately ignore the error here as we want to return the
> + * previous one.
> + */
> + scatterlist_builder_retrieve_ext_ctrls(&builder,
> + num_cmd_sgs + 1, ctrls);
> + return ret;
> + }
> +
> + resp_len -= sizeof(struct virtio_media_resp_ioctl);
> +
> + /* Make sure that the reply's length covers our v4l2_ext_controls */
> + if (resp_len < sizeof(*ctrls))
> + return -EINVAL;
> +
> + ret = scatterlist_builder_retrieve_ext_ctrls(&builder, num_cmd_sgs + 1,
> + ctrls);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_clear_queue() - clear all pending buffers on a streamed-off
> + * queue.
> + * @session: session which the queue to clear belongs to.
> + * @queue: state of the queue to clear.
> + *
> + * Helper function to clear the list of buffers waiting to be dequeued on a
> + * queue that has just been streamed off.
> + */
> +static void virtio_media_clear_queue(struct virtio_media_session *session,
> + struct virtio_media_queue_state *queue)
> +{
> + struct list_head *p, *n;
> + int i;
> +
> + mutex_lock(&session->queues_lock);
> +
> + list_for_each_safe(p, n, &queue->pending_dqbufs) {
> + struct virtio_media_buffer *dqbuf =
> + list_entry(p, struct virtio_media_buffer, list);
> +
> + list_del(&dqbuf->list);
> + }
> +
> + /* All buffers are now dequeued. */
> + for (i = 0; i < queue->allocated_bufs; i++)
> + queue->buffers[i].buffer.flags = 0;
> +
> + queue->queued_bufs = 0;
> + queue->streaming = false;
> + queue->is_capture_last = false;
> +
> + mutex_unlock(&session->queues_lock);
> +}
> +
> +/*
> + * Macros suitable for defining ioctls with a constant size payload.
> + */
> +
> +#define SIMPLE_WR_IOCTL(name, ioctl, payload_t) \
> + static int virtio_media_##name(struct file *file, void *fh, \
> + payload_t *payload) \
> + { \
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file); \
> + return virtio_media_send_wr_ioctl(vfh, ioctl, payload,\
> + sizeof(*payload), \
> + sizeof(*payload)); \
> + }
> +#define SIMPLE_R_IOCTL(name, ioctl, payload_t) \
> + static int virtio_media_##name(struct file *file, void *fh, \
> + payload_t *payload) \
> + { \
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file); \
> + return virtio_media_send_r_ioctl(vfh, ioctl, payload,\
> + sizeof(*payload)); \
> + }
> +#define SIMPLE_W_IOCTL(name, ioctl, payload_t) \
> + static int virtio_media_##name(struct file *file, void *fh, \
> + payload_t *payload) \
> + { \
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file); \
> + return virtio_media_send_w_ioctl(vfh, ioctl, payload,\
> + sizeof(*payload)); \
> + }
> +
> +/*
> + * V4L2 ioctl handlers.
> + *
> + * Most of these functions just forward the ioctl to the host, for these we can
> + * use one of the SIMPLE_*_IOCTL macros. Exceptions that have their own
> + * standalone function follow.
> + */
> +
> +SIMPLE_WR_IOCTL(enum_fmt, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc)
> +SIMPLE_WR_IOCTL(g_fmt, VIDIOC_G_FMT, struct v4l2_format)
> +SIMPLE_WR_IOCTL(s_fmt, VIDIOC_S_FMT, struct v4l2_format)
> +SIMPLE_WR_IOCTL(try_fmt, VIDIOC_TRY_FMT, struct v4l2_format)
> +SIMPLE_WR_IOCTL(enum_framesizes, VIDIOC_ENUM_FRAMESIZES,
> + struct v4l2_frmsizeenum)
> +SIMPLE_WR_IOCTL(enum_frameintervals, VIDIOC_ENUM_FRAMEINTERVALS,
> + struct v4l2_frmivalenum)
> +SIMPLE_WR_IOCTL(query_ext_ctrl, VIDIOC_QUERY_EXT_CTRL,
> + struct v4l2_query_ext_ctrl)
> +SIMPLE_WR_IOCTL(s_dv_timings, VIDIOC_S_DV_TIMINGS, struct v4l2_dv_timings)
> +SIMPLE_WR_IOCTL(g_dv_timings, VIDIOC_G_DV_TIMINGS, struct v4l2_dv_timings)
> +SIMPLE_R_IOCTL(query_dv_timings, VIDIOC_QUERY_DV_TIMINGS,
> + struct v4l2_dv_timings)
> +SIMPLE_WR_IOCTL(enum_dv_timings, VIDIOC_ENUM_DV_TIMINGS,
> + struct v4l2_enum_dv_timings)
> +SIMPLE_WR_IOCTL(dv_timings_cap, VIDIOC_DV_TIMINGS_CAP,
> + struct v4l2_dv_timings_cap)
> +SIMPLE_WR_IOCTL(enuminput, VIDIOC_ENUMINPUT, struct v4l2_input)
> +SIMPLE_WR_IOCTL(querymenu, VIDIOC_QUERYMENU, struct v4l2_querymenu)
> +SIMPLE_WR_IOCTL(enumoutput, VIDIOC_ENUMOUTPUT, struct v4l2_output)
> +SIMPLE_WR_IOCTL(enumaudio, VIDIOC_ENUMAUDIO, struct v4l2_audio)
> +SIMPLE_R_IOCTL(g_audio, VIDIOC_G_AUDIO, struct v4l2_audio)
> +SIMPLE_W_IOCTL(s_audio, VIDIOC_S_AUDIO, const struct v4l2_audio)
> +SIMPLE_WR_IOCTL(enumaudout, VIDIOC_ENUMAUDOUT, struct v4l2_audioout)
> +SIMPLE_R_IOCTL(g_audout, VIDIOC_G_AUDOUT, struct v4l2_audioout)
> +SIMPLE_W_IOCTL(s_audout, VIDIOC_S_AUDOUT, const struct v4l2_audioout)
> +SIMPLE_WR_IOCTL(g_modulator, VIDIOC_G_MODULATOR, struct v4l2_modulator)
> +SIMPLE_W_IOCTL(s_modulator, VIDIOC_S_MODULATOR, const struct v4l2_modulator)
> +SIMPLE_WR_IOCTL(g_selection, VIDIOC_G_SELECTION, struct v4l2_selection)
> +SIMPLE_WR_IOCTL(s_selection, VIDIOC_S_SELECTION, struct v4l2_selection)
> +SIMPLE_R_IOCTL(g_enc_index, VIDIOC_G_ENC_INDEX, struct v4l2_enc_idx)
> +SIMPLE_WR_IOCTL(encoder_cmd, VIDIOC_ENCODER_CMD, struct v4l2_encoder_cmd)
> +SIMPLE_WR_IOCTL(try_encoder_cmd, VIDIOC_TRY_ENCODER_CMD,
> + struct v4l2_encoder_cmd)
> +SIMPLE_WR_IOCTL(try_decoder_cmd, VIDIOC_TRY_DECODER_CMD,
> + struct v4l2_decoder_cmd)
> +SIMPLE_WR_IOCTL(g_parm, VIDIOC_G_PARM, struct v4l2_streamparm)
> +SIMPLE_WR_IOCTL(s_parm, VIDIOC_S_PARM, struct v4l2_streamparm)
> +SIMPLE_R_IOCTL(g_std, VIDIOC_G_STD, v4l2_std_id)
> +SIMPLE_R_IOCTL(querystd, VIDIOC_QUERYSTD, v4l2_std_id)
> +SIMPLE_WR_IOCTL(enumstd, VIDIOC_ENUMSTD, struct v4l2_standard)
> +SIMPLE_WR_IOCTL(g_tuner, VIDIOC_G_TUNER, struct v4l2_tuner)
> +SIMPLE_W_IOCTL(s_tuner, VIDIOC_S_TUNER, const struct v4l2_tuner)
> +SIMPLE_WR_IOCTL(g_frequency, VIDIOC_G_FREQUENCY, struct v4l2_frequency)
> +SIMPLE_W_IOCTL(s_frequency, VIDIOC_S_FREQUENCY, const struct v4l2_frequency)
> +SIMPLE_WR_IOCTL(enum_freq_bands, VIDIOC_ENUM_FREQ_BANDS,
> + struct v4l2_frequency_band)
> +SIMPLE_WR_IOCTL(g_sliced_vbi_cap, VIDIOC_G_SLICED_VBI_CAP,
> + struct v4l2_sliced_vbi_cap)
> +SIMPLE_W_IOCTL(s_hw_freq_seek, VIDIOC_S_HW_FREQ_SEEK,
> + const struct v4l2_hw_freq_seek)
> +
> +/*
> + * QUERYCAP is handled by reading the configuration area.
> + */
> +
> +static int virtio_media_querycap(struct file *file, void *fh,
> + struct v4l2_capability *cap)
> +{
> + struct video_device *video_dev = video_devdata(file);
> + struct virtio_media *vv = to_virtio_media(video_dev);
> +
> + strscpy(cap->bus_info, "platform:virtio-media");
> + strscpy(cap->driver, VIRTIO_MEDIA_DEFAULT_DRIVER_NAME);
> +
> + virtio_cread_bytes(vv->virtio_dev, 8, cap->card, sizeof(cap->card));
> +
> + cap->capabilities = video_dev->device_caps | V4L2_CAP_DEVICE_CAPS;
> + cap->device_caps = video_dev->device_caps;
> +
> + return 0;
> +}
> +
> +/*
> + * Extended control ioctls are handled mostly identically.
> + */
> +
> +static int virtio_media_g_ext_ctrls(struct file *file, void *fh,
> + struct v4l2_ext_controls *ctrls)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> +
> + return virtio_media_send_ext_controls_ioctl(vfh, VIDIOC_G_EXT_CTRLS,
> + ctrls);
> +}
> +
> +static int virtio_media_s_ext_ctrls(struct file *file, void *fh,
> + struct v4l2_ext_controls *ctrls)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> +
> + return virtio_media_send_ext_controls_ioctl(vfh, VIDIOC_S_EXT_CTRLS,
> + ctrls);
> +}
> +
> +static int virtio_media_try_ext_ctrls(struct file *file, void *fh,
> + struct v4l2_ext_controls *ctrls)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> +
> + return virtio_media_send_ext_controls_ioctl(vfh, VIDIOC_TRY_EXT_CTRLS,
> + ctrls);
> +}
> +
> +/*
> + * Subscribe/unsubscribe from an event.
> + */
> +
> +static int
> +virtio_media_subscribe_event(struct v4l2_fh *fh,
> + const struct v4l2_event_subscription *sub)
> +{
> + struct video_device *video_dev = fh->vdev;
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + int ret;
> +
> + /* First subscribe to the event in the guest. */
> + switch (sub->type) {
> + case V4L2_EVENT_SOURCE_CHANGE:
> + ret = v4l2_src_change_event_subscribe(fh, sub);
> + break;
> + default:
> + ret = v4l2_event_subscribe(fh, sub, 1, NULL);
> + break;
> + }
> + if (ret)
> + return ret;
> +
> + /* Then ask the host to signal us these events. */
> + ret = virtio_media_send_w_ioctl(fh, VIDIOC_SUBSCRIBE_EVENT, sub,
> + sizeof(*sub));
> + if (ret < 0) {
> + v4l2_event_unsubscribe(fh, sub);
> + return ret;
> + }
> +
> + /*
> + * Subscribing to an event may result in that event being signaled
> + * immediately. Process all pending events to make sure we don't
> + * miss it.
> + */
> + if (sub->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL)
> + virtio_media_process_events(vv);
> +
> + return 0;
> +}
> +
> +static int
> +virtio_media_unsubscribe_event(struct v4l2_fh *fh,
> + const struct v4l2_event_subscription *sub)
> +{
> + int ret = virtio_media_send_w_ioctl(fh, VIDIOC_UNSUBSCRIBE_EVENT, sub,
> + sizeof(*sub));
> + if (ret < 0)
> + return ret;
> +
> + ret = v4l2_event_unsubscribe(fh, sub);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +/*
> + * Streamon/off affect the local queue state.
> + */
> +
> +static int virtio_media_streamon(struct file *file, void *fh,
> + enum v4l2_buf_type i)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> + struct virtio_media_session *session = fh_to_session(vfh);
> + int ret;
> +
> + if (i > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + ret = virtio_media_send_w_ioctl(vfh, VIDIOC_STREAMON, &i, sizeof(i));
> + if (ret < 0)
> + return ret;
> +
> + session->queues[i].streaming = true;
> +
> + return 0;
> +}
> +
> +static int virtio_media_streamoff(struct file *file, void *fh,
> + enum v4l2_buf_type i)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> + struct virtio_media_session *session = fh_to_session(vfh);
> + int ret;
> +
> + if (i > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + ret = virtio_media_send_w_ioctl(vfh, VIDIOC_STREAMOFF, &i, sizeof(i));
> + if (ret < 0)
> + return ret;
> +
> + virtio_media_clear_queue(session, &session->queues[i]);
> +
> + return 0;
> +}
> +
> +/*
> + * Buffer creation/queuing functions deal with the local driver state.
> + */
> +
> +static int virtio_media_reqbufs(struct file *file, void *fh,
> + struct v4l2_requestbuffers *b)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> + struct virtio_media_session *session = fh_to_session(vfh);
> + struct virtio_media_queue_state *queue;
> + int ret;
> +
> + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + if (b->memory == V4L2_MEMORY_USERPTR)
> + return -EINVAL;
> +
> + ret = virtio_media_send_wr_ioctl(vfh, VIDIOC_REQBUFS, b, sizeof(*b),
> + sizeof(*b));
> + if (ret)
> + return ret;
> +
> + queue = &session->queues[b->type];
> +
> + /* REQBUFS(0) is an implicit STREAMOFF. */
> + if (b->count == 0)
> + virtio_media_clear_queue(session, queue);
> +
> + vfree(queue->buffers);
> + queue->buffers = NULL;
> +
> + if (b->count > 0) {
> + queue->buffers =
> + vzalloc(sizeof(struct virtio_media_buffer) * b->count);
> + if (!queue->buffers)
> + return -ENOMEM;
> + }
> +
> + queue->allocated_bufs = b->count;
> +
> + /*
> + * If a multiplanar queue is successfully used here, this means
> + * we are using the multiplanar interface.
> + */
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type))
> + session->uses_mplane = true;
> +
> + b->capabilities &= ~V4L2_BUF_CAP_SUPPORTS_USERPTR;
> +
> + /* We do not support DMABUF yet. */
> + b->capabilities &= ~V4L2_BUF_CAP_SUPPORTS_DMABUF;
> +
> + return 0;
> +}
> +
> +static int virtio_media_querybuf(struct file *file, void *fh,
> + struct v4l2_buffer *b)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> + struct virtio_media_session *session = fh_to_session(vfh);
> + struct virtio_media_queue_state *queue;
> + struct virtio_media_buffer *buffer;
> +
> + int ret = virtio_media_send_buffer_ioctl(vfh, VIDIOC_QUERYBUF, b);
> +
> + if (ret)
> + return ret;
> +
> + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + queue = &session->queues[b->type];
> + if (b->index >= queue->allocated_bufs)
> + return -EINVAL;
> +
> + buffer = &queue->buffers[b->index];
> + /*
> + * Set the DONE flag if the buffer is waiting in our own dequeue
> + * queue.
> + */
> + b->flags |= (buffer->buffer.flags & V4L2_BUF_FLAG_DONE);
> +
> + return 0;
> +}
> +
> +static int virtio_media_create_bufs(struct file *file, void *fh,
> + struct v4l2_create_buffers *b)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> + struct virtio_media_session *session = fh_to_session(vfh);
> + struct virtio_media_queue_state *queue;
> + struct virtio_media_buffer *buffers;
> + u32 type = b->format.type;
> + int ret;
> +
> + if (type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + queue = &session->queues[type];
> +
> + ret = virtio_media_send_wr_ioctl(vfh, VIDIOC_CREATE_BUFS, b, sizeof(*b),
> + sizeof(*b));
> + if (ret)
> + return ret;
> +
> + /* If count is zero, we were just checking for format. */
> + if (b->count == 0)
> + return 0;
> +
> + buffers = queue->buffers;
> +
> + queue->buffers =
> + vzalloc(sizeof(*queue->buffers) * (b->index + b->count));
> + if (!queue->buffers) {
> + queue->buffers = buffers;
> + return -ENOMEM;
> + }
> +
> + memcpy(queue->buffers, buffers,
> + sizeof(*buffers) * queue->allocated_bufs);
> + vfree(buffers);
> +
> + queue->allocated_bufs = b->index + b->count;
> +
> + return 0;
> +}
> +
> +static int virtio_media_prepare_buf(struct file *file, void *fh,
> + struct v4l2_buffer *b)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> + struct virtio_media_session *session = fh_to_session(vfh);
> + struct virtio_media_queue_state *queue;
> + struct virtio_media_buffer *buffer;
> + int i, ret;
> +
> + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> + queue = &session->queues[b->type];
> + if (b->index >= queue->allocated_bufs)
> + return -EINVAL;
> + buffer = &queue->buffers[b->index];
> +
> + buffer->buffer.m = b->m;
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
> + if (b->length > VIDEO_MAX_PLANES)
> + return -EINVAL;
> + for (i = 0; i < b->length; i++)
> + buffer->planes[i].m = b->m.planes[i].m;
> + }
> +
> + ret = virtio_media_send_buffer_ioctl(vfh, VIDIOC_PREPARE_BUF, b);
> + if (ret)
> + return ret;
> +
> + buffer->buffer.flags = V4L2_BUF_FLAG_PREPARED;
> +
> + return 0;
> +}
> +
> +static int virtio_media_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> + struct virtio_media_session *session = fh_to_session(vfh);
> + struct virtio_media_queue_state *queue;
> + struct virtio_media_buffer *buffer;
> + bool prepared;
> + u32 old_flags;
> + int i, ret;
> +
> + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> + queue = &session->queues[b->type];
> + if (b->index >= queue->allocated_bufs)
> + return -EINVAL;
> + buffer = &queue->buffers[b->index];
> + prepared = buffer->buffer.flags & V4L2_BUF_FLAG_PREPARED;
> +
> + /*
> + * Store the buffer and plane `m` information so we can retrieve
> + * it again when DQBUF occurs.
> + */
> + if (!prepared) {
> + buffer->buffer.m = b->m;
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
> + if (b->length > VIDEO_MAX_PLANES)
> + return -EINVAL;
> + for (i = 0; i < b->length; i++)
> + buffer->planes[i].m = b->m.planes[i].m;
> + }
> + }
> + old_flags = buffer->buffer.flags;
> + buffer->buffer.flags = V4L2_BUF_FLAG_QUEUED;
> +
> + ret = virtio_media_send_buffer_ioctl(vfh, VIDIOC_QBUF, b);
> + if (ret) {
> + /* Rollback the previous flags as the buffer is not queued. */
> + buffer->buffer.flags = old_flags;
> + return ret;
> + }
> +
> + queue->queued_bufs += 1;
> +
> + return 0;
> +}
> +
> +static int virtio_media_dqbuf(struct file *file, void *fh,
> + struct v4l2_buffer *b)
> +{
> + struct video_device *video_dev = video_devdata(file);
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_session *session =
> + fh_to_session(file_to_v4l2_fh(file));
> + struct virtio_media_buffer *dqbuf;
> + struct virtio_media_queue_state *queue;
> + struct list_head *buffer_queue;
> + struct v4l2_plane *planes_backup = NULL;
> + const bool is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(b->type);
> + int ret;
> +
> + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + queue = &session->queues[b->type];
> +
> + /*
> + * If a buffer with the LAST flag has been returned, subsequent
> + * calls to DQBUF must return -EPIPE until the queue is cleared.
> + */
> + if (queue->is_capture_last)
> + return -EPIPE;
> +
> + buffer_queue = &queue->pending_dqbufs;
> +
> + if (session->nonblocking_dequeue) {
> + if (list_empty(buffer_queue))
> + return -EAGAIN;
> + } else if (queue->allocated_bufs == 0) {
> + return -EINVAL;
> + } else if (!queue->streaming) {
> + return -EINVAL;
> + }
> +
> + /*
> + * vv->lock has been acquired by virtio_media_device_ioctl. Release it
> + * while we wait so that other ioctls for this session can be processed
> + * and potentially trigger dqbuf_wait.
> + */
> + mutex_unlock(&vv->vlock);
> + ret = wait_event_interruptible(session->dqbuf_wait,
> + !list_empty(buffer_queue));
> + mutex_lock(&vv->vlock);
> + if (ret)
> + return -EINTR;
> +
> + mutex_lock(&session->queues_lock);
> + dqbuf = list_first_entry(buffer_queue, struct virtio_media_buffer,
> + list);
> + list_del(&dqbuf->list);
> + mutex_unlock(&session->queues_lock);
> +
> + /* Clear the DONE flag as the buffer is now being dequeued. */
> + dqbuf->buffer.flags &= ~V4L2_BUF_FLAG_DONE;
> +
> + if (is_multiplanar) {
> + size_t nb_planes = min_t(u32, b->length, VIDEO_MAX_PLANES);
> +
> + memcpy(b->m.planes, dqbuf->planes,
> + nb_planes * sizeof(struct v4l2_plane));
> + planes_backup = b->m.planes;
> + }
> +
> + memcpy(b, &dqbuf->buffer, sizeof(*b));
> +
> + if (is_multiplanar)
> + b->m.planes = planes_backup;
> +
> + if (V4L2_TYPE_IS_CAPTURE(b->type) && b->flags & V4L2_BUF_FLAG_LAST)
> + queue->is_capture_last = true;
> +
> + return 0;
> +}
> +
> +/*
> + * s/g_input/output work with an unsigned int - recast this to a u32 so the
> + * size is unambiguous.
> + */
> +
> +static int virtio_media_g_input(struct file *file, void *fh, unsigned int *i)
> +{
> + u32 input;
> +
> + int ret = virtio_media_send_wr_ioctl(file_to_v4l2_fh(file),
> + VIDIOC_G_INPUT, &input,
> + sizeof(input), sizeof(input));
> + if (ret)
> + return ret;
> +
> + *i = input;
> +
> + return 0;
> +}
> +
> +static int virtio_media_s_input(struct file *file, void *fh, unsigned int i)
> +{
> + u32 input = i;
> +
> + return virtio_media_send_wr_ioctl(file_to_v4l2_fh(file),
> + VIDIOC_S_INPUT, &input,
> + sizeof(input), sizeof(input));
> +}
> +
> +static int virtio_media_g_output(struct file *file, void *fh, unsigned int *o)
> +{
> + u32 output;
> +
> + int ret = virtio_media_send_wr_ioctl(file_to_v4l2_fh(file),
> + VIDIOC_G_OUTPUT, &output,
> + sizeof(output), sizeof(output));
> + if (ret)
> + return ret;
> +
> + *o = output;
> +
> + return 0;
> +}
> +
> +static int virtio_media_s_output(struct file *file, void *fh, unsigned int o)
> +{
> + u32 output = o;
> +
> + return virtio_media_send_wr_ioctl(file_to_v4l2_fh(file),
> + VIDIOC_S_OUTPUT, &output,
> + sizeof(output), sizeof(output));
> +}
> +
> +/*
> + * decoder_cmd can affect the state of the CAPTURE queue.
> + */
> +
> +static int virtio_media_decoder_cmd(struct file *file, void *fh,
> + struct v4l2_decoder_cmd *cmd)
> +{
> + struct v4l2_fh *vfh = file_to_v4l2_fh(file);
> + struct virtio_media_session *session = fh_to_session(vfh);
> +
> + int ret = virtio_media_send_wr_ioctl(vfh, VIDIOC_DECODER_CMD, cmd,
> + sizeof(*cmd), sizeof(*cmd));
> + if (ret)
> + return ret;
> +
> + /* A START command makes the CAPTURE queue able to dequeue again. */
> + if (cmd->cmd == V4L2_DEC_CMD_START) {
> + session->queues[V4L2_BUF_TYPE_VIDEO_CAPTURE].is_capture_last =
> + false;
> + session->queues[V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE]
> + .is_capture_last = false;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * s_std doesn't work with a pointer, so we cannot use SIMPLE_W_IOCTL.
> + */
> +
> +static int virtio_media_s_std(struct file *file, void *fh, v4l2_std_id s)
> +{
> + int ret = virtio_media_send_w_ioctl(file_to_v4l2_fh(file), VIDIOC_S_STD,
> + &s, sizeof(s));
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +const struct v4l2_ioctl_ops virtio_media_ioctl_ops = {
> + /* VIDIOC_QUERYCAP handler */
> + .vidioc_querycap = virtio_media_querycap,
> +
> + /* VIDIOC_ENUM_FMT handlers */
> + .vidioc_enum_fmt_vid_cap = virtio_media_enum_fmt,
> + .vidioc_enum_fmt_vid_overlay = virtio_media_enum_fmt,
> + .vidioc_enum_fmt_vid_out = virtio_media_enum_fmt,
> + .vidioc_enum_fmt_sdr_cap = virtio_media_enum_fmt,
> + .vidioc_enum_fmt_sdr_out = virtio_media_enum_fmt,
> + .vidioc_enum_fmt_meta_cap = virtio_media_enum_fmt,
> + .vidioc_enum_fmt_meta_out = virtio_media_enum_fmt,
> +
> + /* VIDIOC_G_FMT handlers */
> + .vidioc_g_fmt_vid_cap = virtio_media_g_fmt,
> + .vidioc_g_fmt_vid_overlay = virtio_media_g_fmt,
> + .vidioc_g_fmt_vid_out = virtio_media_g_fmt,
> + .vidioc_g_fmt_vid_out_overlay = virtio_media_g_fmt,
> + .vidioc_g_fmt_vbi_cap = virtio_media_g_fmt,
> + .vidioc_g_fmt_vbi_out = virtio_media_g_fmt,
> + .vidioc_g_fmt_sliced_vbi_cap = virtio_media_g_fmt,
> + .vidioc_g_fmt_sliced_vbi_out = virtio_media_g_fmt,
> + .vidioc_g_fmt_vid_cap_mplane = virtio_media_g_fmt,
> + .vidioc_g_fmt_vid_out_mplane = virtio_media_g_fmt,
> + .vidioc_g_fmt_sdr_cap = virtio_media_g_fmt,
> + .vidioc_g_fmt_sdr_out = virtio_media_g_fmt,
> + .vidioc_g_fmt_meta_cap = virtio_media_g_fmt,
> + .vidioc_g_fmt_meta_out = virtio_media_g_fmt,
> +
> + /* VIDIOC_S_FMT handlers */
> + .vidioc_s_fmt_vid_cap = virtio_media_s_fmt,
> + .vidioc_s_fmt_vid_overlay = virtio_media_s_fmt,
> + .vidioc_s_fmt_vid_out = virtio_media_s_fmt,
> + .vidioc_s_fmt_vid_out_overlay = virtio_media_s_fmt,
> + .vidioc_s_fmt_vbi_cap = virtio_media_s_fmt,
> + .vidioc_s_fmt_vbi_out = virtio_media_s_fmt,
> + .vidioc_s_fmt_sliced_vbi_cap = virtio_media_s_fmt,
> + .vidioc_s_fmt_sliced_vbi_out = virtio_media_s_fmt,
> + .vidioc_s_fmt_vid_cap_mplane = virtio_media_s_fmt,
> + .vidioc_s_fmt_vid_out_mplane = virtio_media_s_fmt,
> + .vidioc_s_fmt_sdr_cap = virtio_media_s_fmt,
> + .vidioc_s_fmt_sdr_out = virtio_media_s_fmt,
> + .vidioc_s_fmt_meta_cap = virtio_media_s_fmt,
> + .vidioc_s_fmt_meta_out = virtio_media_s_fmt,
> +
> + /* VIDIOC_TRY_FMT handlers */
> + .vidioc_try_fmt_vid_cap = virtio_media_try_fmt,
> + .vidioc_try_fmt_vid_overlay = virtio_media_try_fmt,
> + .vidioc_try_fmt_vid_out = virtio_media_try_fmt,
> + .vidioc_try_fmt_vid_out_overlay = virtio_media_try_fmt,
> + .vidioc_try_fmt_vbi_cap = virtio_media_try_fmt,
> + .vidioc_try_fmt_vbi_out = virtio_media_try_fmt,
> + .vidioc_try_fmt_sliced_vbi_cap = virtio_media_try_fmt,
> + .vidioc_try_fmt_sliced_vbi_out = virtio_media_try_fmt,
> + .vidioc_try_fmt_vid_cap_mplane = virtio_media_try_fmt,
> + .vidioc_try_fmt_vid_out_mplane = virtio_media_try_fmt,
> + .vidioc_try_fmt_sdr_cap = virtio_media_try_fmt,
> + .vidioc_try_fmt_sdr_out = virtio_media_try_fmt,
> + .vidioc_try_fmt_meta_cap = virtio_media_try_fmt,
> + .vidioc_try_fmt_meta_out = virtio_media_try_fmt,
> +
> + /* Buffer handlers */
> + .vidioc_reqbufs = virtio_media_reqbufs,
> + .vidioc_querybuf = virtio_media_querybuf,
> + .vidioc_qbuf = virtio_media_qbuf,
> + .vidioc_expbuf = NULL,
> + .vidioc_dqbuf = virtio_media_dqbuf,
> + .vidioc_create_bufs = virtio_media_create_bufs,
> + .vidioc_prepare_buf = virtio_media_prepare_buf,
> + /* Overlay interface not supported yet */
> + .vidioc_overlay = NULL,
> + /* Overlay interface not supported yet */
> + .vidioc_g_fbuf = NULL,
> + /* Overlay interface not supported yet */
> + .vidioc_s_fbuf = NULL,
> +
> + /* Stream on/off */
> + .vidioc_streamon = virtio_media_streamon,
> + .vidioc_streamoff = virtio_media_streamoff,
> +
> + /* Standard handling */
> + .vidioc_g_std = virtio_media_g_std,
> + .vidioc_s_std = virtio_media_s_std,
> + .vidioc_querystd = virtio_media_querystd,
> +
> + /* Input handling */
> + .vidioc_enum_input = virtio_media_enuminput,
> + .vidioc_g_input = virtio_media_g_input,
> + .vidioc_s_input = virtio_media_s_input,
> +
> + /* Output handling */
> + .vidioc_enum_output = virtio_media_enumoutput,
> + .vidioc_g_output = virtio_media_g_output,
> + .vidioc_s_output = virtio_media_s_output,
> +
> + /* Control handling */
> + .vidioc_query_ext_ctrl = virtio_media_query_ext_ctrl,
> + .vidioc_g_ext_ctrls = virtio_media_g_ext_ctrls,
> + .vidioc_s_ext_ctrls = virtio_media_s_ext_ctrls,
> + .vidioc_try_ext_ctrls = virtio_media_try_ext_ctrls,
> + .vidioc_querymenu = virtio_media_querymenu,
> +
> + /* Audio ioctls */
> + .vidioc_enumaudio = virtio_media_enumaudio,
> + .vidioc_g_audio = virtio_media_g_audio,
> + .vidioc_s_audio = virtio_media_s_audio,
> +
> + /* Audio out ioctls */
> + .vidioc_enumaudout = virtio_media_enumaudout,
> + .vidioc_g_audout = virtio_media_g_audout,
> + .vidioc_s_audout = virtio_media_s_audout,
> + .vidioc_g_modulator = virtio_media_g_modulator,
> + .vidioc_s_modulator = virtio_media_s_modulator,
> +
> + /* Crop ioctls */
> + /*
> + * Not directly an ioctl (part of VIDIOC_CROPCAP), so no need to
> + * implement.
> + */
> + .vidioc_g_pixelaspect = NULL,
> + .vidioc_g_selection = virtio_media_g_selection,
> + .vidioc_s_selection = virtio_media_s_selection,
> +
> + /* Compression ioctls */
> + /* Deprecated in V4L2. */
> + .vidioc_g_jpegcomp = NULL,
> + /* Deprecated in V4L2. */
> + .vidioc_s_jpegcomp = NULL,
> + .vidioc_g_enc_index = virtio_media_g_enc_index,
> + .vidioc_encoder_cmd = virtio_media_encoder_cmd,
> + .vidioc_try_encoder_cmd = virtio_media_try_encoder_cmd,
> + .vidioc_decoder_cmd = virtio_media_decoder_cmd,
> + .vidioc_try_decoder_cmd = virtio_media_try_decoder_cmd,
> +
> + /* Stream type-dependent parameter ioctls */
> + .vidioc_g_parm = virtio_media_g_parm,
> + .vidioc_s_parm = virtio_media_s_parm,
> +
> + /* Tuner ioctls */
> + .vidioc_g_tuner = virtio_media_g_tuner,
> + .vidioc_s_tuner = virtio_media_s_tuner,
> + .vidioc_g_frequency = virtio_media_g_frequency,
> + .vidioc_s_frequency = virtio_media_s_frequency,
> + .vidioc_enum_freq_bands = virtio_media_enum_freq_bands,
> +
> + /* Sliced VBI cap */
> + .vidioc_g_sliced_vbi_cap = virtio_media_g_sliced_vbi_cap,
> +
> + /* Log status ioctl */
> + /* Guest-only operation */
> + .vidioc_log_status = NULL,
> +
> + .vidioc_s_hw_freq_seek = virtio_media_s_hw_freq_seek,
> +
> + .vidioc_enum_framesizes = virtio_media_enum_framesizes,
> + .vidioc_enum_frameintervals = virtio_media_enum_frameintervals,
> +
> + /* DV Timings IOCTLs */
> + .vidioc_s_dv_timings = virtio_media_s_dv_timings,
> + .vidioc_g_dv_timings = virtio_media_g_dv_timings,
> + .vidioc_query_dv_timings = virtio_media_query_dv_timings,
> + .vidioc_enum_dv_timings = virtio_media_enum_dv_timings,
> + .vidioc_dv_timings_cap = virtio_media_dv_timings_cap,
> + .vidioc_g_edid = NULL,
> + .vidioc_s_edid = NULL,
> +
> + .vidioc_subscribe_event = virtio_media_subscribe_event,
> + .vidioc_unsubscribe_event = virtio_media_unsubscribe_event,
> +
> + /* For other private ioctls */
> + .vidioc_default = NULL,
> +};
> +
> +long virtio_media_device_ioctl(struct file *file, unsigned int cmd,
> + unsigned long arg)
> +{
> + struct video_device *video_dev = video_devdata(file);
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct v4l2_fh *vfh = NULL;
> + struct v4l2_standard standard;
> + v4l2_std_id std_id = 0;
> + int ret;
> +
> + if (test_bit(V4L2_FL_USES_V4L2_FH, &video_dev->flags))
> + vfh = file_to_v4l2_fh(file);
> +
> + mutex_lock(&vv->vlock);
> +
> + /*
> + * We need to handle a few ioctls manually because their results
> + * rely on vfd->tvnorms, which is normally updated by the driver
> + * as S_INPUT is called. Since we want to just pass these ioctls
> + * through, we have to hijack them from here.
> + */
> + switch (cmd) {
> + case VIDIOC_S_STD:
> + ret = copy_from_user(&std_id, (void __user *)arg,
> + sizeof(std_id));
> + if (ret) {
> + ret = -EINVAL;
> + break;
> + }
> + ret = virtio_media_s_std(file, vfh, std_id);
> + break;
> + case VIDIOC_ENUMSTD:
> + ret = copy_from_user(&standard, (void __user *)arg,
> + sizeof(standard));
> + if (ret) {
> + ret = -EINVAL;
> + break;
> + }
> + ret = virtio_media_enumstd(file, vfh, &standard);
> + if (ret)
> + break;
> + ret = copy_to_user((void __user *)arg, &standard,
> + sizeof(standard));
> + if (ret)
> + ret = -EINVAL;
> + break;
> + case VIDIOC_QUERYSTD:
> + ret = virtio_media_querystd(file, vfh, &std_id);
> + if (ret)
> + break;
> + ret = copy_to_user((void __user *)arg, &std_id, sizeof(std_id));
> + if (ret)
> + ret = -EINVAL;
> + break;
> + default:
> + ret = video_ioctl2(file, cmd, arg);
> + break;
> + }
> +
> + mutex_unlock(&vv->vlock);
> +
> + return ret;
> +}
> --
> 2.55.0.737.g08866a6d13-goog
>