On Thu, Oct 29, 2026 at 4:31 PM Brian Daniels <[email protected]> wrote:
>
> From: Alexandre Courbot <[email protected]>
>
> Introduce the scatterlist builder and synchronous V4L2 ioctl forwarding
> via VIRTIO_MEDIA_CMD_IOCTL.
>
> The scatterlist builder constructs virtio descriptor chains for ioctl
> commands and responses, mapping 1:1 kernel memory and vmalloc buffers
> directly into scatterlists when possible while falling back to a
> per-session shadow buffer otherwise.
>
> Use this infrastructure to implement VIDIOC_QUERYCAP from the virtio
> configuration space along with fixed-size read, write, and read-write
> V4L2 ioctls covering format negotiation, frame sizes and intervals,
> control and menu queries, inputs and outputs, audio, selection, video
> standards, tuners, and DV timings.
>
> Signed-off-by: Alexandre Courbot <[email protected]>
> Assisted-by: LLM
> Co-developed-by: Brian Daniels <[email protected]>
> Signed-off-by: Brian Daniels <[email protected]>
> ---
>  drivers/media/virtio/Makefile              |   2 +-
>  drivers/media/virtio/scatterlist_builder.c | 222 ++++++++
>  drivers/media/virtio/scatterlist_builder.h |  90 +++
>  drivers/media/virtio/session.h             |  28 +-
>  drivers/media/virtio/virtio_media.h        |   9 +
>  drivers/media/virtio/virtio_media_driver.c |  24 +-
>  drivers/media/virtio/virtio_media_ioctls.c | 606 +++++++++++++++++++++
>  include/uapi/linux/virtio_media.h          |  43 ++
>  8 files changed, 1020 insertions(+), 4 deletions(-)
>  create mode 100644 drivers/media/virtio/scatterlist_builder.c
>  create mode 100644 drivers/media/virtio/scatterlist_builder.h
>  create mode 100644 drivers/media/virtio/virtio_media_ioctls.c
>
> diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile
> index 09d9834da..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 := 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/scatterlist_builder.c 
> b/drivers/media/virtio/scatterlist_builder.c
> new file mode 100644
> index 000000000..ae8ffee82
> --- /dev/null
> +++ b/drivers/media/virtio/scatterlist_builder.c
> @@ -0,0 +1,222 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> +
> +/*
> + * Scatterlist builder helpers for virtio-media.
> + *
> + * Copyright (c) 2024-2026 Google LLC.
> + */
> +
> +#include <linux/moduleparam.h>
> +#include <linux/scatterlist.h>
> +#include <linux/videodev2.h>
> +#include <media/videobuf2-memops.h>
> +
> +#include "uapi/linux/virtio_media.h"
> +#include "scatterlist_builder.h"
> +#include "session.h"
> +
> +/*
> + * If set to %true, then the driver will always copy the data passed to the
> + * host into the shadow buffer (instead of trying to map the source memory 
> into
> + * the SG table directly when possible).
> + */
> +static bool always_use_shadow_buffer;
> +module_param(always_use_shadow_buffer, bool, 0660);
> +
> +/* Convert a V4L2 IOCTL into the IOCTL code we can give to the host */
> +#define VIRTIO_MEDIA_IOCTL_CODE(IOCTL) (((IOCTL) >> _IOC_NRSHIFT) & 
> _IOC_NRMASK)
> +
> +/**
> + * scatterlist_builder_add_descriptor() - Add a descriptor to the chain.
> + * @builder: builder to use.
> + * @desc_index: index of the descriptor to add.
> + *
> + * Returns %-ENOSPC if @builder->sgs is already full.
> + */
> +int scatterlist_builder_add_descriptor(struct scatterlist_builder *builder,
> +                                      size_t desc_index)
> +{
> +       if (builder->cur_sg >= builder->num_sgs)
> +               return -ENOSPC;
> +       builder->sgs[builder->cur_sg++] = &builder->descs[desc_index];
> +
> +       return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_data() - Append arbitrary data to the descriptor
> + *                                  chain.
> + * @builder: builder to use.
> + * @data: pointer to the data to add to the descriptor chain.
> + * @len: length of the data to add.
> + *
> + * @data will either be directly referenced, or copied into the shadow buffer
> + * to be referenced from there.
> + */
> +int scatterlist_builder_add_data(struct scatterlist_builder *builder,
> +                                void *data, size_t len)
> +{
> +       const size_t cur_desc = builder->cur_desc;
> +
> +       if (len == 0)
> +               return 0;
> +
> +       if (builder->cur_desc >= builder->num_descs)
> +               return -ENOSPC;
> +
> +       if (!always_use_shadow_buffer && virt_addr_valid(data) &&
> +           virt_addr_valid(data + len)) {
> +               /*
> +                * If "data" is in the 1:1 physical memory mapping then we can
> +                * use a single SG entry and avoid copying.
> +                */
> +               struct page *page = virt_to_page(data);
> +               size_t offset = (((size_t)data) & ~PAGE_MASK);
> +               struct scatterlist *next_desc =
> +                       &builder->descs[builder->cur_desc];
> +
> +               memset(next_desc, 0, sizeof(*next_desc));
> +               sg_set_page(next_desc, page, len, offset);
> +               builder->cur_desc++;
> +       } else if (!always_use_shadow_buffer && is_vmalloc_addr(data)) {
> +               bool prev_pfn_valid = false;
> +               unsigned long prev_pfn = 0;
> +
> +               /*
> +                * If "data" has been vmalloc'ed, we need at most one entry 
> per
> +                * memory page but can avoid copying.
> +                */
> +               while (len > 0) {
> +                       struct page *page = vmalloc_to_page(data);
> +                       unsigned long cur_pfn = page_to_pfn(page);
> +                       /* All pages but the first will start at offset 0. */
> +                       unsigned long offset =
> +                               (((unsigned long)data) & ~PAGE_MASK);
> +                       size_t len_in_page = min(PAGE_SIZE - offset, len);
> +                       struct scatterlist *next_desc =
> +                               &builder->descs[builder->cur_desc];
> +
> +                       if (builder->cur_desc >= builder->num_descs)
> +                               return -ENOSPC;
> +
> +                       /* Optimize contiguous pages */
> +                       if (prev_pfn_valid && (cur_pfn == prev_pfn + 1)) {
> +                               (next_desc - 1)->length += len_in_page;
> +                       } else {
> +                               memset(next_desc, 0, sizeof(*next_desc));
> +                               sg_set_page(next_desc, page, len_in_page,
> +                                           offset);
> +                               builder->cur_desc++;
> +                       }
> +                       data += len_in_page;
> +                       len -= len_in_page;
> +                       prev_pfn = cur_pfn;
> +                       prev_pfn_valid = true;
> +               }
> +       } else {
> +               /*
> +                * As a last resort, copy into the shadow buffer and reference
> +                * it with a single SG entry. Calling
> +                * scatterlist_builder_retrieve_data() will be necessary to 
> copy
> +                * the data written by the device back into @data.
> +                */
> +               void *shadow_buffer =
> +                       builder->shadow_buffer + builder->shadow_buffer_pos;
> +               struct page *page = virt_to_page(shadow_buffer);
> +               unsigned long offset =
> +                       (((unsigned long)shadow_buffer) & ~PAGE_MASK);
> +               struct scatterlist *next_desc =
> +                       &builder->descs[builder->cur_desc];
> +
> +               if (len >
> +                   builder->shadow_buffer_size - builder->shadow_buffer_pos)
> +                       return -ENOSPC;
> +
> +               memcpy(shadow_buffer, data, len);
> +               memset(next_desc, 0, sizeof(*next_desc));
> +               sg_set_page(next_desc, page, len, offset);
> +               builder->cur_desc++;
> +               builder->shadow_buffer_pos += len;
> +       }

Just a suggestion: scatterlist_builder_add_data() feels a bit
lengthy., and the if/else if branches could become their own static
helpers (also they have similar init + cur_desc++ sequence). Fine to
leave as is if you prefer.

Other than that, the patch looks good to me, so either way:

Reviewed-by: Albert Esteve <[email protected]>

BR,
Albert

> +
> +       sg_mark_end(&builder->descs[builder->cur_desc - 1]);
> +       return scatterlist_builder_add_descriptor(builder, cur_desc);
> +}
> +
> +/**
> + * scatterlist_builder_retrieve_data() - Retrieve a response written by the
> + *                                       device on the shadow buffer.
> + * @builder: builder to use.
> + * @sg_index: index of the descriptor to read from.
> + * @data: destination for the shadowed data.
> + *
> + * If the shadow buffer is pointed to by the descriptor at index @sg_index of
> + * the chain, then ``sg->length`` bytes are copied back from it into @data.
> + * Otherwise nothing is done since the device has written into @data 
> directly.
> + *
> + * @data must have originally been added by scatterlist_builder_add_data() as
> + * the same size as passed to scatterlist_builder_add_data() will be copied
> + * back.
> + */
> +int scatterlist_builder_retrieve_data(struct scatterlist_builder *builder,
> +                                     size_t sg_index, void *data)
> +{
> +       void *shadow_buf = builder->shadow_buffer;
> +       struct scatterlist *sg;
> +       void *kaddr;
> +
> +       /* We can only retrieve from the range of sgs currently set. */
> +       if (sg_index >= builder->cur_sg)
> +               return -ERANGE;
> +
> +       sg = builder->sgs[sg_index];
> +       kaddr = pfn_to_kaddr(page_to_pfn(sg_page(sg))) + sg->offset;
> +
> +       if (kaddr >= shadow_buf &&
> +           kaddr < shadow_buf + builder->shadow_buffer_size) {
> +               if (kaddr + sg->length >
> +                   shadow_buf + builder->shadow_buffer_size)
> +                       return -EINVAL;
> +
> +               memcpy(data, kaddr, sg->length);
> +       }
> +
> +       return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_ioctl_cmd() - Add an ioctl command to the 
> descriptor
> + *                                       chain.
> + * @builder: builder to use.
> + * @session: session on behalf of which the ioctl command is added.
> + * @ioctl_code: code of the ioctl to add (i.e. ``VIDIOC_*``).
> + */
> +int scatterlist_builder_add_ioctl_cmd(struct scatterlist_builder *builder,
> +                                     struct virtio_media_session *session,
> +                                     u32 ioctl_code)
> +{
> +       struct virtio_media_cmd_ioctl *cmd_ioctl = &session->cmd.ioctl;
> +
> +       cmd_ioctl->hdr.cmd = VIRTIO_MEDIA_CMD_IOCTL;
> +       cmd_ioctl->hdr.__reserved = 0;
> +       cmd_ioctl->session_id = session->id;
> +       cmd_ioctl->code = VIRTIO_MEDIA_IOCTL_CODE(ioctl_code);
> +
> +       return scatterlist_builder_add_data(builder, cmd_ioctl,
> +                                           sizeof(*cmd_ioctl));
> +}
> +
> +/**
> + * scatterlist_builder_add_ioctl_resp() - Add storage to receive an ioctl
> + *                                        response to the descriptor chain.
> + * @builder: builder to use.
> + * @session: session on behalf of which the ioctl response is added.
> + */
> +int scatterlist_builder_add_ioctl_resp(struct scatterlist_builder *builder,
> +                                      struct virtio_media_session *session)
> +{
> +       struct virtio_media_resp_ioctl *resp_ioctl = &session->resp.ioctl;
> +
> +       return scatterlist_builder_add_data(builder, resp_ioctl,
> +                                           sizeof(*resp_ioctl));
> +}
> diff --git a/drivers/media/virtio/scatterlist_builder.h 
> b/drivers/media/virtio/scatterlist_builder.h
> new file mode 100644
> index 000000000..044ea409a
> --- /dev/null
> +++ b/drivers/media/virtio/scatterlist_builder.h
> @@ -0,0 +1,90 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Scatterlist builder helpers for virtio-media.
> + *
> + * Copyright (c) 2024-2026 Google LLC.
> + */
> +
> +#ifndef __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H
> +#define __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H
> +
> +#include <linux/scatterlist.h>
> +
> +#include "session.h"
> +
> +/**
> + * struct scatterlist_builder - helper to build a scatterlist from data.
> + * @descs: pool of descriptors to use.
> + * @num_descs: number of entries in descs.
> + * @cur_desc: next descriptor to be used in @descs.
> + * @shadow_buffer: pointer to a shadow buffer where elements that cannot be
> + * mapped directly into the scatterlist get copied.
> + * @shadow_buffer_size: size of @shadow_buffer.
> + * @shadow_buffer_pos: current position in @shadow_buffer.
> + * @sgs: descriptor chain to eventually pass to virtio functions.
> + * @num_sgs: total number of entries in @sgs.
> + * @cur_sg: next entry in @sgs to be used.
> + *
> + * Virtio passes data from the driver to the device (through e.g.
> + * virtqueue_add_sgs()) via a scatterlist that the device interprets as a
> + * linear view over scattered driver memory.
> + *
> + * In virtio-media, the payload of ioctls from user-space can for the most 
> part
> + * be passed as-is, or after slight modification, which makes it tempting to
> + * just forward the ioctl payload received from user-space as-is instead of
> + * doing another copy into a dedicated buffer. This structure helps with 
> this.
> + *
> + * virtio-media descriptor chains are typically made of the following parts:
> + *
> + * Device-readable:
> + * - A command structure, i.e. ``virtio_media_cmd_*``,
> + * - An ioctl payload (one of the regular ioctl parameters),
> + * - (optionally) arrays of &struct virtio_media_sg_entry describing the
> + *   content of buffers in guest memory.
> + *
> + * Device-writable:
> + * - A response structure, i.e. ``virtio_media_resp_*``,
> + * - An ioctl payload, that the device will write to.
> + *
> + * This structure helps laying out the descriptor chain into its @sgs member 
> in
> + * an optimal way, by building a scatterlist adapted to the originating 
> memory
> + * of the data we want to pass to the device while avoiding copies when
> + * possible.
> + *
> + * It is made of a pool of &struct scatterlist (@descs) that is used to
> + * build the final descriptor chain @sgs, and a @shadow_buffer where data 
> that
> + * cannot (or should not) be mapped directly by the host can be temporarily
> + * copied.
> + */
> +struct scatterlist_builder {
> +       struct scatterlist *descs;
> +       size_t num_descs;
> +       size_t cur_desc;
> +
> +       void *shadow_buffer;
> +       size_t shadow_buffer_size;
> +       size_t shadow_buffer_pos;
> +
> +       struct scatterlist **sgs;
> +       size_t num_sgs;
> +       size_t cur_sg;
> +};
> +
> +int scatterlist_builder_add_descriptor(struct scatterlist_builder *builder,
> +                                      size_t desc_index);
> +
> +int scatterlist_builder_add_data(struct scatterlist_builder *builder,
> +                                void *data, size_t len);
> +
> +int scatterlist_builder_retrieve_data(struct scatterlist_builder *builder,
> +                                     size_t sg_index, void *data);
> +
> +int scatterlist_builder_add_ioctl_cmd(struct scatterlist_builder *builder,
> +                                     struct virtio_media_session *session,
> +                                     u32 ioctl_code);
> +
> +int scatterlist_builder_add_ioctl_resp(struct scatterlist_builder *builder,
> +                                      struct virtio_media_session *session);
> +
> +#endif // __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H
> diff --git a/drivers/media/virtio/session.h b/drivers/media/virtio/session.h
> index 4ade7fb9c..6fe7ff5ae 100644
> --- a/drivers/media/virtio/session.h
> +++ b/drivers/media/virtio/session.h
> @@ -9,17 +9,32 @@
>  #ifndef __VIRTIO_MEDIA_SESSION_H
>  #define __VIRTIO_MEDIA_SESSION_H
>
> +#include <linux/scatterlist.h>
>  #include <media/v4l2-fh.h>
>
>  #include "uapi/linux/virtio_media.h"
>
> +/*
> + * Size of the per-session virtio shadow and event buffers. 16K should be
> + * enough to contain everything we need.
> + */
> +#define VIRTIO_SHADOW_BUF_SIZE 0x4000
> +
>  /**
>   * struct virtio_media_session - A session on a virtio_media device.
>   * @fh: file handler for the session.
>   * @file: file pointer associated with the session's file handler.
>   * @id: session ID used to communicate with the device.
> - * @cmd: union of session-level commands. A session can have one command
> - *       currently running.
> + * @cmd: union of session commands ``close`` and ``ioctl``. A session can 
> have
> + *       one command currently running. The rest of the commands are handled 
> by
> + *       &struct virtio_media.
> + * @resp: union of responses to session commands. A session can wait on one
> + *        command only. The rest of the responses are handled by
> + *        &struct virtio_media.
> + * @shadow_buf: shadow buffer where data to be added to the descriptor chain 
> can
> + *              be staged before being sent to the device.
> + * @command_sgs: SG table gathering descriptors for a given command and its
> + *               response.
>   * @list: link into the list of sessions for the device.
>   */
>  struct virtio_media_session {
> @@ -30,9 +45,18 @@ struct virtio_media_session {
>         __dma_from_device_group_begin();
>         union {
>                 struct virtio_media_cmd_close close;
> +               struct virtio_media_cmd_ioctl ioctl;
>         } cmd;
> +
> +       union {
> +               struct virtio_media_resp_ioctl ioctl;
> +       } resp;
>         __dma_from_device_group_end();
>
> +       void *shadow_buf;
> +
> +       struct sg_table command_sgs;
> +
>         struct list_head list;
>  };
>
> diff --git a/drivers/media/virtio/virtio_media.h 
> b/drivers/media/virtio/virtio_media.h
> index 68299377a..a120c1014 100644
> --- a/drivers/media/virtio/virtio_media.h
> +++ b/drivers/media/virtio/virtio_media.h
> @@ -14,6 +14,8 @@
>
>  #include "uapi/linux/virtio_media.h"
>
> +#define DESC_CHAIN_MAX_LEN SG_MAX_SINGLE_ALLOC
> +
>  #define VIRTIO_MEDIA_DEFAULT_DRIVER_NAME "virtio-media"
>
>  /**
> @@ -66,5 +68,12 @@ to_virtio_media(struct video_device *video_dev)
>  int virtio_media_send_command(struct virtio_media *vv, struct scatterlist 
> **sgs,
>                               const size_t out_sgs, const size_t in_sgs,
>                               size_t minimum_resp_len, size_t *resp_len);
> +
> +/* virtio_media_ioctls.c */
> +
> +long virtio_media_device_ioctl(struct file *file, unsigned int cmd,
> +                              unsigned long arg);
> +extern const struct v4l2_ioctl_ops virtio_media_ioctl_ops;
> +
>  #endif // __VIRTIO_MEDIA_H
>
> diff --git a/drivers/media/virtio/virtio_media_driver.c 
> b/drivers/media/virtio/virtio_media_driver.c
> index 10073d863..2f2e95077 100644
> --- a/drivers/media/virtio/virtio_media_driver.c
> +++ b/drivers/media/virtio/virtio_media_driver.c
> @@ -37,10 +37,20 @@ static struct virtio_media_session *
>  virtio_media_session_alloc(struct virtio_media *vv, struct file *file)
>  {
>         struct virtio_media_session *session;
> +       int ret;
>
>         session = kzalloc_obj(*session, GFP_KERNEL);
>         if (!session)
> -               return ERR_PTR(-ENOMEM);
> +               goto err_session;
> +
> +       session->shadow_buf = kzalloc(VIRTIO_SHADOW_BUF_SIZE, GFP_KERNEL);
> +       if (!session->shadow_buf)
> +               goto err_shadow_buf;
> +
> +       ret = sg_alloc_table(&session->command_sgs, DESC_CHAIN_MAX_LEN,
> +                            GFP_KERNEL);
> +       if (ret)
> +               goto err_payload_sgs;
>
>         // The caller is expected to set the ID once the host assigns a value.
>         session->id = 0;
> @@ -50,6 +60,13 @@ virtio_media_session_alloc(struct virtio_media *vv, struct 
> file *file)
>         virtio_media_session_fh_add(session, file);
>
>         return session;
> +
> +err_payload_sgs:
> +       kfree(session->shadow_buf);
> +err_shadow_buf:
> +       kfree(session);
> +err_session:
> +       return ERR_PTR(-ENOMEM);
>  }
>
>  /**
> @@ -77,6 +94,9 @@ static void virtio_media_session_free(struct 
> virtio_media_session *session)
>         virtio_media_session_fh_del(session);
>         v4l2_fh_exit(&session->fh);
>
> +       sg_free_table(&session->command_sgs);
> +
> +       kfree(session->shadow_buf);
>         kfree(session);
>  }
>
> @@ -342,6 +362,7 @@ static const struct v4l2_file_operations 
> virtio_media_fops = {
>         .owner = THIS_MODULE,
>         .open = virtio_media_device_open,
>         .release = virtio_media_device_close,
> +       .unlocked_ioctl = virtio_media_device_ioctl,
>  };
>
>  static void virtio_media_release(struct v4l2_device *v4l2_dev)
> @@ -394,6 +415,7 @@ static int virtio_media_probe(struct virtio_device 
> *virtio_dev)
>
>         vd = &vv->video_dev;
>         vd->v4l2_dev = &vv->v4l2_dev;
> +       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..656ecea8a
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media_ioctls.c
> @@ -0,0 +1,606 @@
> +// 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 <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;
> +}
> +
> +/*
> + * 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;
> +}
> +
> +/*
> + * 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));
> +}
> +
> +/*
> + * 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,
> +
> +       /* 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_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_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,
> +
> +       /* 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 = -EFAULT;
> +                       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 = -EFAULT;
> +                       break;
> +               }
> +               ret = virtio_media_enumstd(file, vfh, &standard);
> +               if (ret)
> +                       break;
> +               ret = copy_to_user((void __user *)arg, &standard,
> +                                  sizeof(standard));
> +               if (ret)
> +                       ret = -EFAULT;
> +               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 = -EFAULT;
> +               break;
> +       default:
> +               ret = video_ioctl2(file, cmd, arg);
> +               break;
> +       }
> +
> +       mutex_unlock(&vv->vlock);
> +
> +       return ret;
> +}
> diff --git a/include/uapi/linux/virtio_media.h 
> b/include/uapi/linux/virtio_media.h
> index ad05d7421..c1e68d47c 100644
> --- a/include/uapi/linux/virtio_media.h
> +++ b/include/uapi/linux/virtio_media.h
> @@ -115,4 +115,47 @@ struct virtio_media_cmd_close {
>         __le32 __reserved;
>  };
>
> +/**
> + * VIRTIO_MEDIA_CMD_IOCTL - Driver command for executing an ioctl.
> + *
> + * This command asks the device to run one of the ``VIDIOC_*`` ioctls on the
> + * active session.
> + *
> + * The code of the ioctl is extracted from the VIDIOC_* definitions in
> + * ``videodev2.h``, and consists of the second argument of the ``_IO*`` 
> macro.
> + *
> + * Each ioctl has a payload, which is defined by the third argument of the
> + * ``_IO*`` macro defining it. It can be writable by the driver (``_IOW``), 
> the
> + * device (``_IOR``), or both (``_IOWR``).
> + *
> + * If an ioctl is writable by the driver, it must be followed by a
> + * driver-writable descriptor containing the payload.
> + *
> + * If an ioctl is writable by the device, it must be followed by a
> + * device-writable descriptor of the size of the payload that the device will
> + * write into.
> + *
> + */
> +#define VIRTIO_MEDIA_CMD_IOCTL 3
> +
> +/**
> + * struct virtio_media_cmd_ioctl - Driver command for VIRTIO_MEDIA_CMD_IOCTL.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_IOCTL.
> + * @session_id: id of the session to run the ioctl on.
> + * @code: code of the ioctl to run.
> + */
> +struct virtio_media_cmd_ioctl {
> +       struct virtio_media_cmd_header hdr;
> +       __le32 session_id;
> +       __le32 code;
> +};
> +
> +/**
> + * struct virtio_media_resp_ioctl - Device response for 
> VIRTIO_MEDIA_CMD_IOCTL.
> + * @hdr: header containing the status of the ioctl.
> + */
> +struct virtio_media_resp_ioctl {
> +       struct virtio_media_resp_header hdr;
> +};
> +
>  #endif // _UAPI__LINUX_VIRTIO_MEDIA_H
> --
> 2.56.0.317.g454f8302e1-goog
>


Reply via email to