* [PATCH v3] media: add virtio-media driver
@ 2025-04-12 4:08 Alexandre Courbot
2025-04-12 14:27 ` Markus Elfring
` (2 more replies)
0 siblings, 3 replies; 26+ messages in thread
From: Alexandre Courbot @ 2025-04-12 4:08 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil, Albert Esteve,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez
Cc: gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization,
Alexandre Courbot, Alexandre Courbot
Add the first version of the virtio-media driver.
This driver acts roughly as a V4L2 relay between user-space and the
virtio virtual device on the host, so it is relatively simple, yet
unconventional. It doesn't use VB2 or other frameworks typically used in
a V4L2 driver, and most of its complexity resides in correctly and
efficiently building the virtio descriptor chain to pass to the host,
avoiding copies whenever possible. This is done by
scatterlist_builder.[ch].
virtio_media_ioctls.c proxies each supported ioctl to the host, using
code generated through macros for ioctls that can be forwarded directly,
which is most of them.
virtio_media_driver.c provides the expected driver hooks, and support
for mmapping and polling.
This version supports MMAP buffers, while USERPTR buffers can also be
enabled through a driver option. DMABUF support is still pending.
Signed-off-by: Alexandre Courbot <acourbot@google.com>
Signed-off-by: Alexandre Courbot <gnurou@gmail.com>
---
This patch adds the virtio-media kernel driver. Virtio-media [1]
encapsulates the V4L2 structures and protocol to enable the
virtualization of host media devices into a guest. It's specification is
in the final stages [2] of being merged and the virtualization of
cameras and video accelerator devices has already been demonstrated
using crosvm [3] and QEmu. v4l2-compliance also passes on all tested
devices, which includes the "simple" virtual test device, proxied host
UVC and vivid devices, and the FFmpeg virtual decoder devices (refer to
[3] in order to test these if desired).
Virtio-media is merged in AOSP [4] and ChromeOS. Upstreaming of the
driver is overdue, but I hope we can start the review process and
converge into something that can be merged.
Limitations:
- The driver is currently only available to little-endian, 64-bit
kernels. This is because some of the V4L2 structures used for
communication between guest and host have a layout dependent on the
architecture, and the virtio-media protocol is standardized on the
little-endian 64-bit versions. This can be fixed with a conversion
layer similar to the one used to convert 32-bit ioctls to their 64-bit
counterpart.
- DMABUF support is currently missing. It should be implemented using
virtio objects, with possible support for memfds using the
SHARED_PAGES memory type.
- No support for the media API and requests. While the use-case for
these is less important on virtual devices where we want to present an
abstraction as high as possible to limit VM exits, they do exist and
it would be nice to add behind a virtio feature bit.
- Locking in the driver is still very basic. This is something I want to
improve before merging, but I didn't want to delay upstream review any
further.
[1] https://github.com/chromeos/virtio-media
[2] https://lore.kernel.org/virtio-comment/20250304130134.1856056-1-aesteve@redhat.com/
[3] https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
[4] https://android.googlesource.com/platform/external/virtio-media/
---
Changes in v3:
- Rebased on top of v6.15-rc1 and removes obsolete control callbacks.
- Link to v2: https://lore.kernel.org/r/20250201-virtio-media-v2-1-ac840681452d@gmail.com
Changes in v2:
- Fixed kernel test robot and media CI warnings (ignored a few false
positives).
- Changed in-driver email address to personal one since my Google one
will soon become invalid.
- Link to v1: https://lore.kernel.org/r/20250123-virtio-media-v1-1-81e2549b86b9@gmail.com
---
MAINTAINERS | 6 +
drivers/media/Kconfig | 13 +
drivers/media/Makefile | 2 +
drivers/media/virtio/Makefile | 9 +
drivers/media/virtio/protocol.h | 288 ++++++
drivers/media/virtio/scatterlist_builder.c | 563 ++++++++++++
drivers/media/virtio/scatterlist_builder.h | 111 +++
drivers/media/virtio/session.h | 109 +++
drivers/media/virtio/virtio_media.h | 93 ++
drivers/media/virtio/virtio_media_driver.c | 959 ++++++++++++++++++++
drivers/media/virtio/virtio_media_ioctls.c | 1297 ++++++++++++++++++++++++++++
include/uapi/linux/virtio_ids.h | 1 +
12 files changed, 3451 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 96b82704950184bd71623ff41fc4df31e4c7fe87..f60e17011124fe8c0be0343d4f87e1458f311dcc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25641,6 +25641,12 @@ S: Maintained
F: drivers/iommu/virtio-iommu.c
F: include/uapi/linux/virtio_iommu.h
+VIRTIO MEDIA DRIVER
+M: Alexandre Courbot <gnurou@gmail.com>
+L: linux-media@vger.kernel.org
+S: Maintained
+F: drivers/media/virtio/
+
VIRTIO MEM DRIVER
M: David Hildenbrand <david@redhat.com>
L: virtualization@lists.linux.dev
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
index 6abc9302cd84d8563b7877d3d3da4b7e05a6b5d2..12bbb169c0b04565271092c7ac608b0fb11c0244 100644
--- a/drivers/media/Kconfig
+++ b/drivers/media/Kconfig
@@ -230,6 +230,19 @@ source "drivers/media/platform/Kconfig"
source "drivers/media/mmc/Kconfig"
endif
+config MEDIA_VIRTIO
+ tristate "Virtio-media Driver"
+ depends on VIRTIO && VIDEO_DEV && 64BIT && (X86 || (ARM && CPU_LITTLE_ENDIAN))
+ select VIDEOBUF2_CORE
+ select VIDEOBUF2_MEMOPS
+ help
+ Enables the virtio-media driver.
+
+ This driver is used to virtualize media devices such as cameras or
+ decoders from a host into a guest using the V4L2 protocol.
+
+ If unsure, say N.
+
if MEDIA_TEST_SUPPORT
source "drivers/media/test-drivers/Kconfig"
endif
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index 20fac24e4f0f13134c12cd859141c8b0387030fa..7a1377661919701f27f4fa2b5ee2dcb1045deb3c 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -25,6 +25,8 @@ obj-y += rc/
obj-$(CONFIG_CEC_CORE) += cec/
+obj-$(CONFIG_MEDIA_VIRTIO) += virtio/
+
#
# Finally, merge the drivers that require the core
#
diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..16f91304420d70e1212cc46f3b12f314a510c051
--- /dev/null
+++ b/drivers/media/virtio/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the virtio-media device driver.
+
+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/protocol.h b/drivers/media/virtio/protocol.h
new file mode 100644
index 0000000000000000000000000000000000000000..a22758cda5aabe75c5c94ce8d1b40583c8652710
--- /dev/null
+++ b/drivers/media/virtio/protocol.h
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
+
+/*
+ * Definitions of virtio-media protocol structures.
+ *
+ * Copyright (c) 2024-2025 Google LLC.
+ */
+
+#ifndef __VIRTIO_MEDIA_PROTOCOL_H
+#define __VIRTIO_MEDIA_PROTOCOL_H
+
+#include <linux/videodev2.h>
+
+/*
+ * Virtio protocol definition.
+ */
+
+/**
+ * struct virtio_media_cmd_header - Header for all virtio-media commands.
+ * @cmd: one of VIRTIO_MEDIA_CMD_*.
+ * @__reserved: must be set to zero by the driver.
+ *
+ * This header starts all commands from the driver to the device on the
+ * commandq.
+ */
+struct virtio_media_cmd_header {
+ u32 cmd;
+ u32 __reserved;
+};
+
+/**
+ * struct virtio_media_resp_header - Header for all virtio-media responses.
+ * @status: 0 if the command was successful, or one of the standard Linux error
+ * codes.
+ * @__reserved: must be set to zero by the device.
+ *
+ * This header starts all responses from the device to the driver on the
+ * commandq.
+ */
+struct virtio_media_resp_header {
+ u32 status;
+ u32 __reserved;
+};
+
+/**
+ * VIRTIO_MEDIA_CMD_OPEN - Command for creating a new session.
+ *
+ * This is the equivalent of calling `open` on a V4L2 device node. Upon
+ * success, a session id is returned which can be used to perform other
+ * commands on the session, notably ioctls.
+ */
+#define VIRTIO_MEDIA_CMD_OPEN 1
+
+/**
+ * struct virtio_media_cmd_open - Driver command for VIRTIO_MEDIA_CMD_OPEN.
+ * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_OPEN.
+ */
+struct virtio_media_cmd_open {
+ struct virtio_media_cmd_header hdr;
+};
+
+/**
+ * struct virtio_media_resp_open - Device response for VIRTIO_MEDIA_CMD_OPEN.
+ * @hdr: header containing the status of the command.
+ * @session_id: if hdr.status == 0, contains the id of the newly created session.
+ * @__reserved: must be set to zero by the device.
+ */
+struct virtio_media_resp_open {
+ struct virtio_media_resp_header hdr;
+ u32 session_id;
+ u32 __reserved;
+};
+
+/**
+ * VIRTIO_MEDIA_CMD_CLOSE - Command for closing an active session.
+ *
+ * This is the equivalent of calling `close` on a previously opened V4L2
+ * session. All resources associated with this session will be freed and the
+ * session ID shall not be used again after queueing this command.
+ *
+ * This command does not require a response from the device.
+ */
+#define VIRTIO_MEDIA_CMD_CLOSE 2
+
+/**
+ * struct virtio_media_cmd_close - Driver command for VIRTIO_MEDIA_CMD_CLOSE.
+ * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_CLOSE.
+ * @session_id: id of the session to close.
+ * @__reserved: must be set to zero by the driver.
+ */
+struct virtio_media_cmd_close {
+ struct virtio_media_cmd_header hdr;
+ u32 session_id;
+ u32 __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;
+ u32 session_id;
+ u32 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;
+};
+
+/**
+ * struct virtio_media_sg_entry - Description of part of a scattered guest memory.
+ * @start: start guest address of the memory segment.
+ * @len: length of this memory segment.
+ * @__reserved: must be set to zero by the driver.
+ */
+struct virtio_media_sg_entry {
+ u64 start;
+ u32 len;
+ u32 __reserved;
+};
+
+/**
+ * enum virtio_media_memory - Memory types supported by virtio-media.
+ * @VIRTIO_MEDIA_MMAP: memory allocated and managed by device. Can be mapped
+ * into the guest using VIRTIO_MEDIA_CMD_MMAP.
+ * @VIRTIO_MEDIA_SHARED_PAGES: memory allocated by the driver. Passed to the
+ * device using virtio_media_sg_entry.
+ * @VIRTIO_MEDIA_OBJECT: memory backed by a virtio object.
+ */
+enum virtio_media_memory {
+ VIRTIO_MEDIA_MMAP = V4L2_MEMORY_MMAP,
+ VIRTIO_MEDIA_SHARED_PAGES = V4L2_MEMORY_USERPTR,
+ VIRTIO_MEDIA_OBJECT = V4L2_MEMORY_DMABUF,
+};
+
+#define VIRTIO_MEDIA_MMAP_FLAG_RW (1 << 0)
+
+/**
+ * VIRTIO_MEDIA_CMD_MMAP - Command for mapping a MMAP buffer into the driver's
+ * address space.
+ *
+ */
+#define VIRTIO_MEDIA_CMD_MMAP 4
+
+/**
+ * struct virtio_media_cmd_mmap - Driver command for VIRTIO_MEDIA_CMD_MMAP.
+ * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_MMAP.
+ * @session_id: ID of the session we are mapping for.
+ * @flags: combination of VIRTIO_MEDIA_MMAP_FLAG_*.
+ * @offset: mem_offset field of the plane to map, as returned by VIDIOC_QUERYBUF.
+ */
+struct virtio_media_cmd_mmap {
+ struct virtio_media_cmd_header hdr;
+ u32 session_id;
+ u32 flags;
+ u32 offset;
+};
+
+/**
+ * struct virtio_media_resp_mmap - Device response for VIRTIO_MEDIA_CMD_MMAP.
+ * @hdr: header containing the status of the command.
+ * @driver_addr: offset into SHM region 0 of the start of the mapping.
+ * @len: length of the mapping.
+ */
+struct virtio_media_resp_mmap {
+ struct virtio_media_resp_header hdr;
+ u64 driver_addr;
+ u64 len;
+};
+
+/**
+ * VIRTIO_MEDIA_CMD_MUNMAP - Unmap a MMAP buffer previously mapped using
+ * VIRTIO_MEDIA_CMD_MMAP.
+ */
+#define VIRTIO_MEDIA_CMD_MUNMAP 5
+
+/**
+ * struct virtio_media_cmd_munmap - Driver command for VIRTIO_MEDIA_CMD_MUNMAP.
+ * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_MUNMAP.
+ * @driver_addr: offset into SHM region 0 at which the buffer has been previously
+ * mapped.
+ */
+struct virtio_media_cmd_munmap {
+ struct virtio_media_cmd_header hdr;
+ u64 driver_addr;
+};
+
+/**
+ * struct virtio_media_resp_munmap - Device response for VIRTIO_MEDIA_CMD_MUNMAP.
+ * @hdr: header containing the status of the command.
+ */
+struct virtio_media_resp_munmap {
+ struct virtio_media_resp_header hdr;
+};
+
+#define VIRTIO_MEDIA_EVT_ERROR 0
+#define VIRTIO_MEDIA_EVT_DQBUF 1
+#define VIRTIO_MEDIA_EVT_EVENT 2
+
+/**
+ * struct virtio_media_event_header - Header for events on the eventq.
+ * @event: one of VIRTIO_MEDIA_EVT_*
+ * @session_id: ID of the session the event applies to.
+ */
+struct virtio_media_event_header {
+ u32 event;
+ u32 session_id;
+};
+
+/**
+ * struct virtio_media_event_error - Unrecoverable device-side error.
+ * @hdr: header for the event.
+ * @errno: error code describing the kind of error that occurred.
+ * @__reserved: must to set to zero by the device.
+ *
+ * Upon receiving this event, the session mentioned in the header is considered
+ * corrupted and closed.
+ *
+ */
+struct virtio_media_event_error {
+ struct virtio_media_event_header hdr;
+ u32 errno;
+ u32 __reserved;
+};
+
+#define VIRTIO_MEDIA_MAX_PLANES VIDEO_MAX_PLANES
+
+/**
+ * struct virtio_media_event_dqbuf - Dequeued buffer event.
+ * @hdr: header for the event.
+ * @buffer: struct v4l2_buffer describing the buffer that has been dequeued.
+ * @planes: plane information for the dequeued buffer.
+ *
+ * This event is used to signal that a buffer is not being used anymore by the
+ * device and is returned to the driver.
+ */
+struct virtio_media_event_dqbuf {
+ struct virtio_media_event_header hdr;
+ struct v4l2_buffer buffer;
+ struct v4l2_plane planes[VIRTIO_MEDIA_MAX_PLANES];
+};
+
+/**
+ * struct virtio_media_event_event - V4L2 event.
+ * @hdr: header for the event.
+ * @event: description of the event that occurred.
+ *
+ * This event signals that a V4L2 event has been emitted for a session.
+ */
+struct virtio_media_event_event {
+ struct virtio_media_event_header hdr;
+ struct v4l2_event event;
+};
+
+/* Maximum size of an event. We will queue descriptors of this size on the eventq. */
+#define VIRTIO_MEDIA_EVENT_MAX_SIZE sizeof(struct virtio_media_event_dqbuf)
+
+#endif // __VIRTIO_MEDIA_PROTOCOL_H
diff --git a/drivers/media/virtio/scatterlist_builder.c b/drivers/media/virtio/scatterlist_builder.c
new file mode 100644
index 0000000000000000000000000000000000000000..2837689f385e81c0c0a99ffd67ac583b426bf186
--- /dev/null
+++ b/drivers/media/virtio/scatterlist_builder.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
+
+/*
+ * Scatterlist builder helpers for virtio-media.
+ *
+ * Copyright (c) 2024-2025 Google LLC.
+ */
+
+#include <linux/moduleparam.h>
+#include <linux/scatterlist.h>
+#include <linux/videodev2.h>
+#include <media/videobuf2-memops.h>
+
+#include "protocol.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 ``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 + 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)) {
+ int prev_pfn = -2;
+
+ /*
+ * 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);
+ int 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 (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;
+ }
+ } 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;
+ }
+
+ 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 + VIRTIO_SHADOW_BUF_SIZE) {
+ if (kaddr + sg->length >= shadow_buf + VIRTIO_SHADOW_BUF_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->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));
+}
+
+/**
+ * __scatterlist_builder_add_userptr() - Add user pages to @builder.
+ * @builder: builder to use.
+ * @userptr: pointer to userspace memory that we want to add.
+ * @length: length of the data to add.
+ * @sg_list: output parameter. Upon success, points to the area of the shadow
+ * buffer containing the array of SG entries to be added to the descriptor
+ * chain.
+ * @nents: output parameter. Upon success, contains the number of entries
+ * pointed to by @sg_list.
+ *
+ * Data referenced by userspace pointers can be potentially large and very
+ * scattered, which could overwhelm the descriptor chain if added as-is. For
+ * these, we instead build an array of ``struct virtio_media_sg_entry`` in the
+ * shadow buffer and reference it using a single descriptor.
+ *
+ * This function is a helper to perform that. Callers should then add the
+ * descriptor to the chain properly.
+ *
+ * Returns -EFAULT if @userptr is not a valid user address, which is a case the
+ * driver should consider as "normal" operation. All other failures signal a
+ * problem with the driver.
+ */
+static int
+__scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
+ unsigned long userptr, unsigned long length,
+ struct virtio_media_sg_entry **sg_list,
+ int *nents)
+{
+ struct sg_table sg_table = {};
+ struct frame_vector *framevec;
+ struct scatterlist *sg_iter;
+ struct page **pages;
+ const unsigned int offset = userptr & ~PAGE_MASK;
+ unsigned int pages_count;
+ size_t entries_size;
+ int i;
+ int ret;
+
+ framevec = vb2_create_framevec(userptr, length, true);
+ if (IS_ERR(framevec)) {
+ if (PTR_ERR(framevec) != -EFAULT) {
+ pr_warn("error %ld creating frame vector for userptr 0x%lx, length 0x%lx\n",
+ PTR_ERR(framevec), userptr, length);
+ } else {
+ /* -EINVAL is expected in case of invalid userptr. */
+ framevec = ERR_PTR(-EINVAL);
+ }
+ return PTR_ERR(framevec);
+ }
+
+ pages = frame_vector_pages(framevec);
+ if (IS_ERR(pages)) {
+ pr_warn("error getting vector pages\n");
+ ret = PTR_ERR(pages);
+ goto done;
+ }
+ pages_count = frame_vector_count(framevec);
+ ret = sg_alloc_table_from_pages(&sg_table, pages, pages_count, offset,
+ length, 0);
+ if (ret) {
+ pr_warn("error creating sg table\n");
+ goto done;
+ }
+
+ /* Allocate our actual SG in the shadow buffer. */
+ *nents = sg_nents(sg_table.sgl);
+ entries_size = sizeof(**sg_list) * *nents;
+ if (builder->shadow_buffer_pos + entries_size >
+ builder->shadow_buffer_size) {
+ ret = -ENOMEM;
+ goto free_sg;
+ }
+
+ *sg_list = builder->shadow_buffer + builder->shadow_buffer_pos;
+ builder->shadow_buffer_pos += entries_size;
+
+ for_each_sgtable_sg(&sg_table, sg_iter, i) {
+ struct virtio_media_sg_entry *sg_entry = &(*sg_list)[i];
+
+ sg_entry->start = sg_phys(sg_iter);
+ sg_entry->len = sg_iter->length;
+ }
+
+free_sg:
+ sg_free_table(&sg_table);
+
+done:
+ vb2_destroy_framevec(framevec);
+ return ret;
+}
+
+/**
+ * scatterlist_builder_add_userptr() - Add a user-memory buffer using an array
+ * of ``struct virtio_media_sg_entry``.
+ * @builder: builder to use.
+ * @userptr: pointer to userspace memory that we want to add.
+ * @length: length of the data to add.
+ *
+ * Upon success, an array of ``struct virtio_media_sg_entry`` referencing
+ * @userptr has been built into the shadow buffer, and that array added to the
+ * descriptor chain.
+ */
+static int scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
+ unsigned long userptr,
+ unsigned long length)
+{
+ int ret;
+ int nents;
+ struct virtio_media_sg_entry *sg_list;
+
+ ret = __scatterlist_builder_add_userptr(builder, userptr, length,
+ &sg_list, &nents);
+ if (ret)
+ return ret;
+
+ ret = scatterlist_builder_add_data(builder, sg_list,
+ sizeof(*sg_list) * nents);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * scatterlist_builder_add_buffer() - Add a ``v4l2_buffer`` and its planes to
+ * the descriptor chain.
+ * @builder: builder to use.
+ * @b: ``v4l2_buffer`` to add.
+ */
+int scatterlist_builder_add_buffer(struct scatterlist_builder *builder,
+ struct v4l2_buffer *b)
+{
+ int i;
+ int ret;
+
+ /* Fixup: plane length must be zero if userptr is NULL */
+ if (!V4L2_TYPE_IS_MULTIPLANAR(b->type) &&
+ b->memory == V4L2_MEMORY_USERPTR && b->m.userptr == 0)
+ b->length = 0;
+
+ /* v4l2_buffer */
+ ret = scatterlist_builder_add_data(builder, b, sizeof(*b));
+ if (ret)
+ return ret;
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(b->type) && b->length > 0) {
+ /* Fixup: plane length must be zero if userptr is NULL */
+ if (b->memory == V4L2_MEMORY_USERPTR) {
+ for (i = 0; i < b->length; i++) {
+ struct v4l2_plane *plane = &b->m.planes[i];
+
+ if (plane->m.userptr == 0)
+ plane->length = 0;
+ }
+ }
+
+ /* Array of v4l2_planes */
+ ret = scatterlist_builder_add_data(builder, b->m.planes,
+ sizeof(struct v4l2_plane) *
+ b->length);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * scatterlist_builder_add_buffer_userptr() - Add the payload of a ``USERTPR``
+ * v4l2_buffer to the descriptor chain.
+ * @builder: builder to use.
+ * @b: ``v4l2_buffer`` which ``USERPTR`` payload we want to add.
+ *
+ * Add an array of ``virtio_media_sg_entry`` pointing to a ``USERPTR`` buffer's
+ * contents. Does nothing if the buffer is not of type ``USERPTR``. This is
+ * split out of :ref:`scatterlist_builder_add_buffer` because we only want to
+ * add these to the device-readable part of the descriptor chain.
+ */
+int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *builder,
+ struct v4l2_buffer *b)
+{
+ int i;
+ int ret;
+
+ if (b->memory != V4L2_MEMORY_USERPTR)
+ return 0;
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
+ for (i = 0; i < b->length; i++) {
+ struct v4l2_plane *plane = &b->m.planes[i];
+
+ if (b->memory == V4L2_MEMORY_USERPTR &&
+ plane->length > 0) {
+ ret = scatterlist_builder_add_userptr(
+ builder, plane->m.userptr,
+ plane->length);
+ if (ret)
+ return ret;
+ }
+ }
+ } else if (b->length > 0) {
+ ret = scatterlist_builder_add_userptr(builder, b->m.userptr,
+ b->length);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * scatterlist_builder_retrieve_buffer() - Retrieve a v4l2_buffer written by
+ * the device on the shadow buffer, if needed.
+ * @builder: builder to use.
+ * @sg_index: index of the first SG entry of the buffer in the builder's
+ * descriptor chain.
+ * @b: v4l2_buffer to copy shadow buffer data into.
+ * @orig_planes: the original ``planes`` pointer, to be restored if the buffer
+ * is multi-planar.
+ *
+ * If the v4l2_buffer pointed by @buffer_sgs was copied into the shadow buffer,
+ * then its updated content is copied back into @b. Otherwise nothing is done
+ * as the device has written into @b directly.
+ *
+ * @orig_planes is used to restore the original ``planes`` pointer in case it
+ * gets modified by the host. The specification stipulates that the host should
+ * not modify it, but we enforce this for additional safety.
+ */
+int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builder,
+ size_t sg_index, struct v4l2_buffer *b,
+ struct v4l2_plane *orig_planes)
+{
+ int ret;
+
+ ret = scatterlist_builder_retrieve_data(builder, sg_index++, b);
+ if (ret)
+ return ret;
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
+ b->m.planes = orig_planes;
+
+ if (orig_planes != NULL) {
+ ret = scatterlist_builder_retrieve_data(
+ builder, sg_index++, b->m.planes);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * scatterlist_builder_add_ext_ctrls() - Add a v4l2_ext_controls and its
+ * controls to @builder.
+ * @builder: builder to use.
+ * @ctrls: ``struct v4l2_ext_controls`` to add.
+ *
+ * Add @ctrls and its array of `struct v4l2_ext_control` to the descriptor chain.
+ */
+int scatterlist_builder_add_ext_ctrls(struct scatterlist_builder *builder,
+ struct v4l2_ext_controls *ctrls)
+{
+ int ret;
+
+ /* v4l2_ext_controls */
+ ret = scatterlist_builder_add_data(builder, ctrls, sizeof(*ctrls));
+ if (ret)
+ return ret;
+
+ if (ctrls->count > 0) {
+ /* array of v4l2_controls */
+ ret = scatterlist_builder_add_data(builder, ctrls->controls,
+ sizeof(ctrls->controls[0]) *
+ ctrls->count);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * scatterlist_builder_add_ext_ctrls_userptrs() - Add the userspace payloads of
+ * a ``struct v4l2_ext_controls`` to the descriptor chain.
+ * @builder: builder to use.
+ * @ctrls: ``struct v4l2_ext_controls`` from which we want to add the userspace payload of.
+ *
+ * Add the userspace payloads of @ctrls to the descriptor chain. This is split
+ * out of :ref:`scatterlist_builder_add_ext_ctrls` because we only want to add
+ * these to the device-readable part of the descriptor chain.
+ */
+int scatterlist_builder_add_ext_ctrls_userptrs(
+ struct scatterlist_builder *builder, struct v4l2_ext_controls *ctrls)
+{
+ int i;
+ int ret;
+
+ /* Pointers to user memory in individual controls */
+ for (i = 0; i < ctrls->count; i++) {
+ struct v4l2_ext_control *ctrl = &ctrls->controls[i];
+
+ if (ctrl->size > 0) {
+ ret = scatterlist_builder_add_userptr(
+ builder, (unsigned long)ctrl->ptr, ctrl->size);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * scatterlist_builder_retrieve_ext_ctrls() - Retrieve controls written by the
+ * device on the shadow buffer, if needed.
+ * @builder: builder to use.
+ * @sg_index: index of the first SG entry of the controls in the builder's
+ * descriptor chain.
+ * @ctrls: ``struct v4l2_ext_controls`` to copy shadow buffer data into.
+ *
+ * If the shadow buffer is pointed to by @sg, copy its content back into @ctrls.
+ */
+int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *builder,
+ size_t sg_index,
+ struct v4l2_ext_controls *ctrls)
+{
+ struct v4l2_ext_control *controls_backup = ctrls->controls;
+ int ret;
+
+ ret = scatterlist_builder_retrieve_data(builder, sg_index++, ctrls);
+ if (ret)
+ return ret;
+
+ ctrls->controls = controls_backup;
+
+ if (ctrls->count > 0 && ctrls->controls) {
+ ret = scatterlist_builder_retrieve_data(builder, sg_index++,
+ ctrls->controls);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/drivers/media/virtio/scatterlist_builder.h b/drivers/media/virtio/scatterlist_builder.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8323c31ac21953580a0b4a4cb366841e510666f
--- /dev/null
+++ b/drivers/media/virtio/scatterlist_builder.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
+
+/*
+ * Scatterlist builder helpers for virtio-media.
+ *
+ * Copyright (c) 2024-2025 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 ``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);
+
+int scatterlist_builder_add_buffer(struct scatterlist_builder *builder,
+ struct v4l2_buffer *buffer);
+
+int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *builder,
+ struct v4l2_buffer *b);
+
+int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builder,
+ size_t sg_index,
+ struct v4l2_buffer *buffer,
+ struct v4l2_plane *orig_planes);
+
+int scatterlist_builder_add_ext_ctrls(struct scatterlist_builder *builder,
+ struct v4l2_ext_controls *ctrls);
+
+int scatterlist_builder_add_ext_ctrls_userptrs(
+ struct scatterlist_builder *builder, struct v4l2_ext_controls *ctrls);
+
+int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *builder,
+ size_t sg_index,
+ struct v4l2_ext_controls *ctrls);
+
+#endif // __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H
diff --git a/drivers/media/virtio/session.h b/drivers/media/virtio/session.h
new file mode 100644
index 0000000000000000000000000000000000000000..b643d0d950477d56d4bb5db481818a3912af5c1f
--- /dev/null
+++ b/drivers/media/virtio/session.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
+
+/*
+ * Definitions of virtio-media session related structures.
+ *
+ * Copyright (c) 2024-2025 Google LLC.
+ */
+
+#ifndef __VIRTIO_MEDIA_SESSION_H
+#define __VIRTIO_MEDIA_SESSION_H
+
+#include <linux/scatterlist.h>
+#include <media/v4l2-fh.h>
+
+#include "protocol.h"
+
+#define VIRTIO_MEDIA_LAST_QUEUE (V4L2_BUF_TYPE_META_OUTPUT)
+
+/*
+ * 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_buffer - Current state of a buffer.
+ * @buffer: ``struct v4l2_buffer`` with current information about the buffer.
+ * @planes: backing planes array for @buffer.
+ * @list: link into the list of buffers pending dequeue.
+ */
+struct virtio_media_buffer {
+ struct v4l2_buffer buffer;
+ struct v4l2_plane planes[VIDEO_MAX_PLANES];
+ struct list_head list;
+};
+
+/**
+ * struct virtio_media_queue_state - Represents the state of a V4L2 queue.
+ * @streaming: Whether the queue is currently streaming.
+ * @allocated_bufs: How many buffers are currently allocated.
+ * @is_capture_last: set to true when the last buffer has been received on a
+ * capture queue, so we can return -EPIPE on subsequent DQBUF requests.
+ * @buffers: Buffer state array of size @allocated_bufs.
+ * @queued_bufs: How many buffers are currently queued on the device.
+ * @pending_dqbufs: Buffers that are available for being dequeued.
+ */
+struct virtio_media_queue_state {
+ bool streaming;
+ size_t allocated_bufs;
+ bool is_capture_last;
+
+ struct virtio_media_buffer *buffers;
+ size_t queued_bufs;
+ struct list_head pending_dqbufs;
+};
+
+/**
+ * struct virtio_media_session - A session on a virtio_media device.
+ * @fh: file handler for the session.
+ * @id: session ID used to communicate with the device.
+ * @nonblocking_dequeue: whether dequeue should block or not (nonblocking if
+ * file opened with O_NONBLOCK).
+ * @uses_mplane: whether the queues for this session use the MPLANE API or not.
+ * @cmd: union of session-related commands. A session can have one command currently running.
+ * @resp: union of session-related responses. A session can wait on one command only.
+ * @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.
+ * @queues: state of all the queues for this session.
+ * @queues_lock: protects all members fo the queues for this session.
+ * virtio_media_queue_state`.
+ * @dqbuf_wait: waitqueue for dequeued buffers, if ``VIDIOC_DQBUF`` needs to
+ * block or when polling.
+ * @list: link into the list of sessions for the device.
+ */
+struct virtio_media_session {
+ struct v4l2_fh fh;
+ u32 id;
+ bool nonblocking_dequeue;
+ bool uses_mplane;
+
+ union {
+ struct virtio_media_cmd_close close;
+ struct virtio_media_cmd_ioctl ioctl;
+ struct virtio_media_cmd_mmap mmap;
+ } cmd;
+
+ union {
+ struct virtio_media_resp_ioctl ioctl;
+ struct virtio_media_resp_mmap mmap;
+ } resp;
+
+ void *shadow_buf;
+
+ struct sg_table command_sgs;
+
+ struct virtio_media_queue_state queues[VIRTIO_MEDIA_LAST_QUEUE + 1];
+ struct mutex queues_lock;
+ wait_queue_head_t dqbuf_wait;
+
+ struct list_head list;
+};
+
+static inline struct virtio_media_session *fh_to_session(struct v4l2_fh *fh)
+{
+ return container_of(fh, struct virtio_media_session, fh);
+}
+
+#endif // __VIRTIO_MEDIA_SESSION_H
diff --git a/drivers/media/virtio/virtio_media.h b/drivers/media/virtio/virtio_media.h
new file mode 100644
index 0000000000000000000000000000000000000000..0aa503defdd6a08e12335276f7ccbabc3d53df09
--- /dev/null
+++ b/drivers/media/virtio/virtio_media.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
+
+/*
+ * Virtio-media structures & functions declarations.
+ *
+ * Copyright (c) 2024-2025 Google LLC.
+ */
+
+#ifndef __VIRTIO_MEDIA_H
+#define __VIRTIO_MEDIA_H
+
+#include <linux/virtio_config.h>
+#include <media/v4l2-device.h>
+
+#include "protocol.h"
+
+#define DESC_CHAIN_MAX_LEN SG_MAX_SINGLE_ALLOC
+
+#define VIRTIO_MEDIA_DEFAULT_DRIVER_NAME "virtio-media"
+
+extern char *virtio_media_driver_name;
+extern bool virtio_media_allow_userptr;
+
+/**
+ * struct virtio_media - Virtio-media device.
+ * @v4l2_dev: v4l2_device for the media device.
+ * @video_dev: video_device for the media device.
+ * @virtio_dev: virtio device for the media device.
+ * @commandq: virtio command queue.
+ * @eventq: virtio event queue.
+ * @eventq_work: work to run when events are received on @eventq.
+ * @mmap_region: region into which MMAP buffers are mapped by the host.
+ * @event_buffer: buffer for event descriptors.
+ * @sessions: list of active sessions on the device.
+ * @sessions_lock: protects @sessions and ``virtio_media_session::list``.
+ * @events_lock: prevents concurrent processing of events.
+ * @cmd: union of device-related commands.
+ * @resp: union of device-related responses.
+ * @vlock: serializes access to the command queue.
+ * @wq: waitqueue for host responses on the command queue.
+ */
+struct virtio_media {
+ struct v4l2_device v4l2_dev;
+ struct video_device video_dev;
+
+ struct virtio_device *virtio_dev;
+ struct virtqueue *commandq;
+ struct virtqueue *eventq;
+ struct work_struct eventq_work;
+
+ struct virtio_shm_region mmap_region;
+
+ void *event_buffer;
+
+ struct list_head sessions;
+ struct mutex sessions_lock;
+
+ struct mutex events_lock;
+
+ union {
+ struct virtio_media_cmd_open open;
+ struct virtio_media_cmd_munmap munmap;
+ } cmd;
+
+ union {
+ struct virtio_media_resp_open open;
+ struct virtio_media_resp_munmap munmap;
+ } resp;
+
+ struct mutex vlock;
+ wait_queue_head_t wq;
+};
+
+static inline struct virtio_media *
+to_virtio_media(struct video_device *video_dev)
+{
+ return container_of(video_dev, struct virtio_media, video_dev);
+}
+
+/* virtio_media_driver.c */
+
+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);
+void virtio_media_process_events(struct virtio_media *vv);
+
+/* 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
new file mode 100644
index 0000000000000000000000000000000000000000..e8d6dc453f2240c7809152c2a04813120bd3aca2
--- /dev/null
+++ b/drivers/media/virtio/virtio_media_driver.c
@@ -0,0 +1,959 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
+
+/*
+ * Virtio-media driver.
+ *
+ * Copyright (c) 2024-2025 Google LLC.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/scatterlist.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ids.h>
+
+#include <media/frame_vector.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-memops.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+
+#include "protocol.h"
+#include "session.h"
+#include "virtio_media.h"
+
+#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
+
+/*
+ * Name of the driver to expose to user-space.
+ *
+ * This is configurable because v4l2-compliance has workarounds specific to
+ * some drivers. When proxying these directly from the host, this allows it to
+ * apply them as needed.
+ */
+char *virtio_media_driver_name;
+module_param_named(driver_name, virtio_media_driver_name, charp, 0660);
+
+/*
+ * Whether USERPTR buffers are allowed.
+ *
+ * This is disabled by default as USERPTR buffers are dangerous, but the option
+ * is left to enable them if desired.
+ */
+bool virtio_media_allow_userptr;
+module_param_named(allow_userptr, virtio_media_allow_userptr, bool, 0660);
+
+/**
+ * virtio_media_session_alloc - Allocate a new session.
+ * @vv: virtio-media device the session belongs to.
+ * @id: ID of the session.
+ * @nonblocking_dequeue: whether dequeuing of buffers should be blocking or
+ * not.
+ *
+ * The ``id`` and ``list`` fields must still be set by the caller.
+ */
+static struct virtio_media_session *
+virtio_media_session_alloc(struct virtio_media *vv, u32 id,
+ bool nonblocking_dequeue)
+{
+ struct virtio_media_session *session;
+ int i;
+ int ret;
+
+ session = kzalloc(sizeof(*session), GFP_KERNEL);
+ if (!session)
+ 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;
+
+ session->id = id;
+ session->nonblocking_dequeue = nonblocking_dequeue;
+
+ INIT_LIST_HEAD(&session->list);
+ v4l2_fh_init(&session->fh, &vv->video_dev);
+ v4l2_fh_add(&session->fh);
+
+ for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
+ INIT_LIST_HEAD(&session->queues[i].pending_dqbufs);
+ mutex_init(&session->queues_lock);
+
+ init_waitqueue_head(&session->dqbuf_wait);
+
+ mutex_lock(&vv->sessions_lock);
+ list_add_tail(&session->list, &vv->sessions);
+ mutex_unlock(&vv->sessions_lock);
+
+ return session;
+
+err_payload_sgs:
+ kfree(session->shadow_buf);
+err_shadow_buf:
+ kfree(session);
+err_session:
+ return ERR_PTR(-ENOMEM);
+}
+
+/**
+ * virtio_media_session_free - Free all resources of a session.
+ * @vv: virtio-media device the session belongs to.
+ * @session: session to destroy.
+ *
+ * All the resources of @sesssion, as well as the backing memory of @session
+ * itself, are freed.
+ */
+static void virtio_media_session_free(struct virtio_media *vv,
+ struct virtio_media_session *session)
+{
+ int i;
+
+ mutex_lock(&vv->sessions_lock);
+ list_del(&session->list);
+ mutex_unlock(&vv->sessions_lock);
+
+ v4l2_fh_del(&session->fh);
+ v4l2_fh_exit(&session->fh);
+
+ sg_free_table(&session->command_sgs);
+
+ for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
+ vfree(session->queues[i].buffers);
+
+ kfree(session->shadow_buf);
+ kfree(session);
+}
+
+/**
+ * virtio_media_session_close - Close and free a session.
+ * @vv: virtio-media device the session belongs to.
+ * @session: session to close and destroy.
+ *
+ * This send the ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and frees
+ * all resources used by @session.
+ */
+static int virtio_media_session_close(struct virtio_media *vv,
+ struct virtio_media_session *session)
+{
+ struct virtio_media_cmd_close *cmd_close = &session->cmd.close;
+ struct scatterlist cmd_sg = {};
+ struct scatterlist *sgs[1] = { &cmd_sg };
+ int ret;
+
+ mutex_lock(&vv->vlock);
+
+ cmd_close->hdr.cmd = VIRTIO_MEDIA_CMD_CLOSE;
+ cmd_close->session_id = session->id;
+
+ sg_set_buf(&cmd_sg, cmd_close, sizeof(*cmd_close));
+ sg_mark_end(&cmd_sg);
+
+ ret = virtio_media_send_command(vv, sgs, 1, 0, 0, NULL);
+ mutex_unlock(&vv->vlock);
+ if (ret < 0)
+ return ret;
+
+ virtio_media_session_free(vv, session);
+
+ return 0;
+}
+
+/**
+ * virtio_media_find_session - Lookup for the session with a given ID.
+ * @vv: virtio-media device to lookup the session from.
+ * @id: ID of the session to lookup.
+ */
+static struct virtio_media_session *
+virtio_media_find_session(struct virtio_media *vv, u32 id)
+{
+ struct list_head *p;
+ struct virtio_media_session *session = NULL;
+
+ mutex_lock(&vv->sessions_lock);
+ list_for_each(p, &vv->sessions) {
+ struct virtio_media_session *s =
+ list_entry(p, struct virtio_media_session, list);
+ if (s->id == id) {
+ session = s;
+ break;
+ }
+ }
+ mutex_unlock(&vv->sessions_lock);
+
+ return session;
+}
+
+/**
+ * struct virtio_media_cmd_callback_param - Callback parameters to the virtio command queue.
+ * @vv: virtio-media device in use.
+ * @done: flag to be switched once the command is completed.
+ * @resp_len: length of the received response from the command. Only valid
+ * after @done_flag has switched to ``true``.
+ */
+struct virtio_media_cmd_callback_param {
+ struct virtio_media *vv;
+ bool done;
+ size_t resp_len;
+};
+
+/**
+ * commandq_callback: Callback for the command queue.
+ * @queue: command virtqueue.
+ *
+ * This just wakes up the thread that was waiting on the command to complete.
+ */
+static void commandq_callback(struct virtqueue *queue)
+{
+ unsigned int len;
+ struct virtio_media_cmd_callback_param *param;
+
+process_bufs:
+ while ((param = virtqueue_get_buf(queue, &len))) {
+ param->done = true;
+ param->resp_len = len;
+ wake_up(¶m->vv->wq);
+ }
+
+ if (!virtqueue_enable_cb(queue)) {
+ virtqueue_disable_cb(queue);
+ goto process_bufs;
+ }
+}
+
+/**
+ * virtio_media_kick_command - send a command to the commandq.
+ * @vv: virtio-media device in use.
+ * @sgs: descriptor chain to send.
+ * @out_sgs: number of device-readable descriptors in @sgs.
+ * @in_sgs: number of device-writable descriptors in @sgs.
+ * @resp_len: output parameter. Upon success, contains the size of the response
+ * in bytes.
+ *
+ */
+static int virtio_media_kick_command(struct virtio_media *vv,
+ struct scatterlist **sgs,
+ const size_t out_sgs, const size_t in_sgs,
+ size_t *resp_len)
+{
+ struct virtio_media_cmd_callback_param cb_param = {
+ .vv = vv,
+ .done = false,
+ .resp_len = 0,
+ };
+ struct virtio_media_resp_header *resp_header;
+ int ret;
+
+ ret = virtqueue_add_sgs(vv->commandq, sgs, out_sgs, in_sgs, &cb_param,
+ GFP_ATOMIC);
+ if (ret) {
+ v4l2_err(&vv->v4l2_dev,
+ "failed to add sgs to command virtqueue\n");
+ return ret;
+ }
+
+ if (!virtqueue_kick(vv->commandq)) {
+ v4l2_err(&vv->v4l2_dev, "failed to kick command virtqueue\n");
+ return -EINVAL;
+ }
+
+ /* Wait for the response. */
+ ret = wait_event_timeout(vv->wq, cb_param.done, 5 * HZ);
+ if (ret == 0) {
+ v4l2_err(&vv->v4l2_dev,
+ "timed out waiting for response to command\n");
+ return -ETIMEDOUT;
+ }
+
+ if (resp_len)
+ *resp_len = cb_param.resp_len;
+
+ if (in_sgs > 0) {
+ /*
+ * If we expect a response, make sure we have at least a
+ * response header - anything shorter is invalid.
+ */
+ if (cb_param.resp_len < sizeof(*resp_header)) {
+ v4l2_err(&vv->v4l2_dev,
+ "received response header is too short\n");
+ return -EINVAL;
+ }
+
+ resp_header = sg_virt(sgs[out_sgs]);
+ if (resp_header->status)
+ /* Host returns a positive error code. */
+ return -resp_header->status;
+ }
+
+ return 0;
+}
+
+/**
+ * virtio_media_send_command - Send a command to the device and wait for its
+ * response.
+ * @vv: virtio-media device in use.
+ * @sgs: descriptor chain to send.
+ * @out_sgs: number of device-readable descriptors in @sgs.
+ * @in_sgs: number of device-writable descriptors in @sgs.
+ * @minimum_resp_len: minimum length of the response expected by the caller
+ * when the command is successful. Anything shorter than that will result in
+ * ``-EINVAL`` being returned.
+ * @resp_len: output parameter. Upon success, contains the size of the response
+ * in bytes.
+ */
+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)
+{
+ size_t local_resp_len = resp_len ? *resp_len : 0;
+ int ret = virtio_media_kick_command(vv, sgs, out_sgs, in_sgs,
+ &local_resp_len);
+ if (resp_len)
+ *resp_len = local_resp_len;
+
+ /* If the host could not process the command, there is no valid response */
+ if (ret < 0)
+ return ret;
+
+ /* Make sure the host wrote a complete reply. */
+ if (local_resp_len < minimum_resp_len) {
+ v4l2_err(
+ &vv->v4l2_dev,
+ "received response is too short: received %zu, expected at least %zu\n",
+ local_resp_len, minimum_resp_len);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * virtio_media_send_event_buffer() - Sends an event buffer to the host so it
+ * can return it with an event.
+ * @vv: virtio-media device in use.
+ * @event_buffer: pointer to the event buffer to send to the device.
+ */
+static int virtio_media_send_event_buffer(struct virtio_media *vv,
+ void *event_buffer)
+{
+ struct scatterlist *sgs[1], vresp;
+ int ret;
+
+ sg_init_one(&vresp, event_buffer, VIRTIO_MEDIA_EVENT_MAX_SIZE);
+ sgs[0] = &vresp;
+
+ ret = virtqueue_add_sgs(vv->eventq, sgs, 0, 1, event_buffer,
+ GFP_ATOMIC);
+ if (ret) {
+ v4l2_err(&vv->v4l2_dev,
+ "failed to add sgs to event virtqueue\n");
+ return ret;
+ }
+
+ if (!virtqueue_kick(vv->eventq)) {
+ v4l2_err(&vv->v4l2_dev, "failed to kick event virtqueue\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * eventq_callback() - Callback for the event queue.
+ * @queue: event virtqueue.
+ *
+ * This just schedules for event work to be run.
+ */
+static void eventq_callback(struct virtqueue *queue)
+{
+ struct virtio_media *vv = queue->vdev->priv;
+
+ schedule_work(&vv->eventq_work);
+}
+
+/**
+ * virtio_media_process_dqbuf_event() - Process a dequeued event for a session.
+ * @vv: virtio-media device in use.
+ * @session: session the event is addressed to.
+ * @dqbuf_evt: the dequeued event to process.
+ *
+ * Invalid events are ignored with an error log.
+ */
+static void
+virtio_media_process_dqbuf_event(struct virtio_media *vv,
+ struct virtio_media_session *session,
+ struct virtio_media_event_dqbuf *dqbuf_evt)
+{
+ struct virtio_media_buffer *dqbuf;
+ const enum v4l2_buf_type queue_type = dqbuf_evt->buffer.type;
+ struct virtio_media_queue_state *queue;
+ typeof(dqbuf->buffer.m) buffer_m;
+ typeof(dqbuf->buffer.m.planes[0].m) plane_m;
+ int i;
+
+ if (queue_type >= ARRAY_SIZE(session->queues)) {
+ v4l2_err(&vv->v4l2_dev,
+ "unmanaged queue %d passed to dqbuf event",
+ dqbuf_evt->buffer.type);
+ return;
+ }
+ queue = &session->queues[queue_type];
+
+ if (dqbuf_evt->buffer.index >= queue->allocated_bufs) {
+ v4l2_err(&vv->v4l2_dev,
+ "invalid buffer ID %d for queue %d in dqbuf event",
+ dqbuf_evt->buffer.index, dqbuf_evt->buffer.type);
+ return;
+ }
+
+ dqbuf = &queue->buffers[dqbuf_evt->buffer.index];
+
+ /*
+ * Preserve the 'm' union that was passed to us during QBUF so userspace
+ * gets back the information it submitted.
+ */
+ buffer_m = dqbuf->buffer.m;
+ memcpy(&dqbuf->buffer, &dqbuf_evt->buffer, sizeof(dqbuf->buffer));
+ dqbuf->buffer.m = buffer_m;
+ if (V4L2_TYPE_IS_MULTIPLANAR(dqbuf->buffer.type)) {
+ if (dqbuf->buffer.length > VIDEO_MAX_PLANES) {
+ v4l2_err(
+ &vv->v4l2_dev,
+ "invalid number of planes received from host for a multiplanar buffer\n");
+ return;
+ }
+ for (i = 0; i < dqbuf->buffer.length; i++) {
+ plane_m = dqbuf->planes[i].m;
+ memcpy(&dqbuf->planes[i], &dqbuf_evt->planes[i],
+ sizeof(struct v4l2_plane));
+ dqbuf->planes[i].m = plane_m;
+ }
+ }
+
+ /* Set the DONE flag as the buffer is waiting for being dequeued. */
+ dqbuf->buffer.flags |= V4L2_BUF_FLAG_DONE;
+
+ mutex_lock(&session->queues_lock);
+ list_add_tail(&dqbuf->list, &queue->pending_dqbufs);
+ queue->queued_bufs -= 1;
+ mutex_unlock(&session->queues_lock);
+
+ wake_up(&session->dqbuf_wait);
+}
+
+/**
+ * virtio_media_process_events() - Process all pending events on a device.
+ * @vv: device which pending events we want to process.
+ *
+ * Retrieves all pending events on @vv's event queue and dispatch them to their
+ * corresponding session.
+ *
+ * Invalid events are ignored with an error log.
+ */
+void virtio_media_process_events(struct virtio_media *vv)
+{
+ struct virtio_media_event_error *error_evt;
+ struct virtio_media_event_dqbuf *dqbuf_evt;
+ struct virtio_media_event_event *event_evt;
+ struct virtio_media_session *session;
+ struct virtio_media_event_header *evt;
+ unsigned int len;
+
+ mutex_lock(&vv->events_lock);
+
+process_bufs:
+ while ((evt = virtqueue_get_buf(vv->eventq, &len))) {
+ /* Make sure we received enough data */
+ if (len < sizeof(*evt)) {
+ v4l2_err(
+ &vv->v4l2_dev,
+ "event is too short: got %u, expected at least %zu\n",
+ len, sizeof(*evt));
+ goto end_of_event;
+ }
+
+ session = virtio_media_find_session(vv, evt->session_id);
+ if (!session) {
+ v4l2_err(&vv->v4l2_dev, "cannot find session %d\n",
+ evt->session_id);
+ goto end_of_event;
+ }
+
+ switch (evt->event) {
+ case VIRTIO_MEDIA_EVT_ERROR:
+ if (len < sizeof(*error_evt)) {
+ v4l2_err(
+ &vv->v4l2_dev,
+ "error event is too short: got %u, expected %zu\n",
+ len, sizeof(*error_evt));
+ break;
+ }
+ error_evt = (struct virtio_media_event_error *)evt;
+ v4l2_err(&vv->v4l2_dev,
+ "received error %d for session %d",
+ error_evt->errno, error_evt->hdr.session_id);
+ virtio_media_session_close(vv, session);
+ break;
+
+ /*
+ * Dequeued buffer: put it into the right queue so user-space can dequeue
+ * it.
+ */
+ case VIRTIO_MEDIA_EVT_DQBUF:
+ if (len < sizeof(*dqbuf_evt)) {
+ v4l2_err(
+ &vv->v4l2_dev,
+ "dqbuf event is too short: got %u, expected %zu\n",
+ len, sizeof(*dqbuf_evt));
+ break;
+ }
+ dqbuf_evt = (struct virtio_media_event_dqbuf *)evt;
+ virtio_media_process_dqbuf_event(vv, session,
+ dqbuf_evt);
+ break;
+
+ case VIRTIO_MEDIA_EVT_EVENT:
+ if (len < sizeof(*event_evt)) {
+ v4l2_err(
+ &vv->v4l2_dev,
+ "session event is too short: got %u expected %zu\n",
+ len, sizeof(*event_evt));
+ break;
+ }
+
+ event_evt = (struct virtio_media_event_event *)evt;
+ v4l2_event_queue_fh(&session->fh, &event_evt->event);
+ break;
+
+ default:
+ v4l2_err(&vv->v4l2_dev, "unknown event type %d\n",
+ evt->event);
+ break;
+ }
+
+end_of_event:
+ virtio_media_send_event_buffer(vv, evt);
+ }
+
+ if (!virtqueue_enable_cb(vv->eventq)) {
+ virtqueue_disable_cb(vv->eventq);
+ goto process_bufs;
+ }
+
+ mutex_unlock(&vv->events_lock);
+}
+
+static void virtio_media_event_work(struct work_struct *work)
+{
+ struct virtio_media *vv =
+ container_of(work, struct virtio_media, eventq_work);
+
+ virtio_media_process_events(vv);
+}
+
+/**
+ * virtio_media_device_open() - Create a new session from an opened file.
+ * @file: opened file for the session.
+ */
+static int virtio_media_device_open(struct file *file)
+{
+ struct video_device *video_dev = video_devdata(file);
+ struct virtio_media *vv = to_virtio_media(video_dev);
+ struct virtio_media_cmd_open *cmd_open = &vv->cmd.open;
+ struct virtio_media_resp_open *resp_open = &vv->resp.open;
+ struct scatterlist cmd_sg = {}, resp_sg = {};
+ struct scatterlist *sgs[2] = { &cmd_sg, &resp_sg };
+ struct virtio_media_session *session;
+ u32 session_id;
+ int ret;
+
+ mutex_lock(&vv->vlock);
+
+ sg_set_buf(&cmd_sg, cmd_open, sizeof(*cmd_open));
+ sg_mark_end(&cmd_sg);
+
+ sg_set_buf(&resp_sg, resp_open, sizeof(*resp_open));
+ sg_mark_end(&resp_sg);
+
+ cmd_open->hdr.cmd = VIRTIO_MEDIA_CMD_OPEN;
+ ret = virtio_media_send_command(vv, sgs, 1, 1, sizeof(*resp_open),
+ NULL);
+ session_id = resp_open->session_id;
+ mutex_unlock(&vv->vlock);
+ if (ret < 0)
+ return ret;
+
+ session = virtio_media_session_alloc(vv, session_id,
+ (file->f_flags & O_NONBLOCK));
+ if (IS_ERR(session))
+ return PTR_ERR(session);
+
+ file->private_data = &session->fh;
+
+ return 0;
+}
+
+/**
+ * virtio_media_device_close() - Close a previously opened session.
+ * @file: file of the session to close.
+ *
+ * This sends to ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and close
+ * the session on the driver side.
+ */
+static int virtio_media_device_close(struct file *file)
+{
+ 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);
+
+ 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 : 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)
+{
+ struct device *dev = &virtio_dev->dev;
+ struct virtqueue *vqs[2];
+ static struct virtqueue_info vq_info[2] = {
+ {
+ .name = "command",
+ .callback = commandq_callback,
+ },
+ {
+ .name = "event",
+ .callback = eventq_callback,
+ },
+ };
+ struct virtio_media *vv;
+ struct video_device *vd;
+ int i;
+ int ret;
+
+ vv = devm_kzalloc(dev, sizeof(*vv), GFP_KERNEL);
+ if (!vv)
+ return -ENOMEM;
+
+ vv->event_buffer = devm_kzalloc(
+ dev, VIRTIO_MEDIA_EVENT_MAX_SIZE * VIRTIO_MEDIA_NUM_EVENT_BUFS,
+ GFP_KERNEL);
+ if (!vv->event_buffer)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&vv->sessions);
+ mutex_init(&vv->sessions_lock);
+ mutex_init(&vv->events_lock);
+ mutex_init(&vv->vlock);
+
+ vv->virtio_dev = virtio_dev;
+ virtio_dev->priv = vv;
+
+ init_waitqueue_head(&vv->wq);
+
+ ret = v4l2_device_register(dev, &vv->v4l2_dev);
+ if (ret)
+ return ret;
+
+ ret = virtio_find_vqs(virtio_dev, 2, vqs, vq_info, NULL);
+ if (ret)
+ goto err_find_vqs;
+
+ vv->commandq = vqs[0];
+ 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->device_caps = virtio_cread32(virtio_dev, 0);
+ if (vd->device_caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))
+ vd->vfl_dir = VFL_DIR_M2M;
+ else if (vd->device_caps &
+ (V4L2_CAP_VIDEO_OUTPUT | V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE))
+ vd->vfl_dir = VFL_DIR_TX;
+ else
+ vd->vfl_dir = VFL_DIR_RX;
+ vd->release = video_device_release_empty;
+ strscpy(vd->name, "virtio-media", sizeof(vd->name));
+
+ video_set_drvdata(vd, vv);
+
+ ret = video_register_device(vd, virtio_cread32(virtio_dev, 4), 0);
+ if (ret)
+ goto err_register_device;
+
+ for (i = 0; i < VIRTIO_MEDIA_NUM_EVENT_BUFS; i++) {
+ ret = virtio_media_send_event_buffer(
+ vv, vv->event_buffer + VIRTIO_MEDIA_EVENT_MAX_SIZE * i);
+ if (ret)
+ goto err_send_event_buffer;
+ }
+
+ virtio_device_ready(virtio_dev);
+
+ return 0;
+
+err_send_event_buffer:
+ video_unregister_device(&vv->video_dev);
+err_register_device:
+ virtio_dev->config->del_vqs(virtio_dev);
+err_find_vqs:
+ v4l2_device_unregister(&vv->v4l2_dev);
+
+ return ret;
+}
+
+static void virtio_media_remove(struct virtio_device *virtio_dev)
+{
+ struct virtio_media *vv = virtio_dev->priv;
+ struct list_head *p, *n;
+
+ cancel_work_sync(&vv->eventq_work);
+ virtio_reset_device(virtio_dev);
+
+ v4l2_device_unregister(&vv->v4l2_dev);
+ virtio_dev->config->del_vqs(virtio_dev);
+ video_unregister_device(&vv->video_dev);
+
+ list_for_each_safe(p, n, &vv->sessions) {
+ struct virtio_media_session *s =
+ list_entry(p, struct virtio_media_session, list);
+
+ virtio_media_session_free(vv, s);
+ }
+}
+
+static struct virtio_device_id id_table[] = {
+ { VIRTIO_ID_MEDIA, VIRTIO_DEV_ANY_ID },
+ { 0 },
+};
+
+static unsigned int features[] = {};
+
+static struct virtio_driver virtio_media_driver = {
+ .feature_table = features,
+ .feature_table_size = ARRAY_SIZE(features),
+ .driver.name = VIRTIO_MEDIA_DEFAULT_DRIVER_NAME,
+ .driver.owner = THIS_MODULE,
+ .id_table = id_table,
+ .probe = virtio_media_probe,
+ .remove = virtio_media_remove,
+};
+
+module_virtio_driver(virtio_media_driver);
+
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_DESCRIPTION("virtio media driver");
+MODULE_AUTHOR("Alexandre Courbot <gnurou@gmail.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/media/virtio/virtio_media_ioctls.c b/drivers/media/virtio/virtio_media_ioctls.c
new file mode 100644
index 0000000000000000000000000000000000000000..863cdfbaaadc7241110c82ce6880bc5675c23894
--- /dev/null
+++ b/drivers/media/virtio/virtio_media_ioctls.c
@@ -0,0 +1,1297 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
+
+/*
+ * Ioctls implementations for the virtio-media driver.
+ *
+ * Copyright (c) 2024-2025 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,
+ };
+ int ret;
+
+ /* Command descriptor */
+ 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,
+ };
+ int ret;
+
+ /* Command descriptor */
+ 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,
+ };
+ int ret;
+
+ /* Command descriptor */
+ 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: ``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;
+
+ /* Payload of SHARED_PAGES buffers, if relevant */
+ ret = scatterlist_builder_add_buffer_userptr(&builder, b);
+ if (ret < 0)
+ 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 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: ``v4l2_ext_controls`` to be sent as the ioctl payload.
+ *
+ * Queues an ioctl that sends a ``v4l2_ext_controls`` to the host and receives
+ * an updated version.
+ *
+ * ``v4l2_ext_controls`` has a pointer to an array of ``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 ret;
+ int i;
+
+ /* Command descriptor */
+ 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;
+
+ /* Event 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) \
+ { \
+ return virtio_media_send_wr_ioctl(fh, 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) \
+ { \
+ return virtio_media_send_r_ioctl(fh, 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) \
+ { \
+ return virtio_media_send_w_ioctl(fh, 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");
+
+ if (!virtio_media_driver_name)
+ strscpy(cap->driver, VIRTIO_MEDIA_DEFAULT_DRIVER_NAME);
+ else
+ strscpy(cap->driver, virtio_media_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)
+{
+ return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_G_EXT_CTRLS,
+ ctrls);
+}
+
+static int virtio_media_s_ext_ctrls(struct file *file, void *fh,
+ struct v4l2_ext_controls *ctrls)
+{
+ return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_S_EXT_CTRLS,
+ ctrls);
+}
+
+static int virtio_media_try_ext_ctrls(struct file *file, void *fh,
+ struct v4l2_ext_controls *ctrls)
+{
+ return virtio_media_send_ext_controls_ioctl(fh, 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;
+
+ 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 virtio_media_session *session = fh_to_session(fh);
+ int ret;
+
+ if (i > VIRTIO_MEDIA_LAST_QUEUE)
+ return -EINVAL;
+
+ ret = virtio_media_send_w_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
+ int ret;
+
+ if (i > VIRTIO_MEDIA_LAST_QUEUE)
+ return -EINVAL;
+
+ ret = virtio_media_send_w_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
+ struct virtio_media_queue_state *queue;
+ int ret;
+
+ if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
+ return -EINVAL;
+
+ if (b->memory == V4L2_MEMORY_USERPTR && !virtio_media_allow_userptr)
+ return -EINVAL;
+
+ ret = virtio_media_send_wr_ioctl(fh, 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;
+
+ if (!virtio_media_allow_userptr)
+ 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 virtio_media_session *session = fh_to_session(fh);
+ struct virtio_media_queue_state *queue;
+ struct virtio_media_buffer *buffer;
+ int ret;
+
+ ret = virtio_media_send_buffer_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
+ 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
+ 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
+ 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(fh, 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->private_data);
+ 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 want to 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;
+
+ ret = virtio_media_send_wr_ioctl(fh, 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(fh, 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;
+
+ ret = virtio_media_send_wr_ioctl(fh, 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
+ int ret;
+
+ ret = virtio_media_send_wr_ioctl(fh, 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;
+
+ ret = virtio_media_send_w_ioctl(fh, 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->private_data;
+
+ mutex_lock(&vv->vlock);
+
+ /*
+ * We need to handle a few ioctls manually because their result 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;
+}
diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
index 7aa2eb76620508fdc915533f74973d76308d3ef5..b4bb0ace0b26e37224c975f89bbf669c51921816 100644
--- a/include/uapi/linux/virtio_ids.h
+++ b/include/uapi/linux/virtio_ids.h
@@ -68,6 +68,7 @@
#define VIRTIO_ID_AUDIO_POLICY 39 /* virtio audio policy */
#define VIRTIO_ID_BT 40 /* virtio bluetooth */
#define VIRTIO_ID_GPIO 41 /* virtio gpio */
+#define VIRTIO_ID_MEDIA 48 /* virtio media */
/*
* Virtio Transitional IDs
---
base-commit: 0af2f6be1b4281385b618cb86ad946eded089ac8
change-id: 20241229-virtio-media-25067bb27526
Best regards,
--
Alexandre Courbot <gnurou@gmail.com>
^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-04-12 4:08 [PATCH v3] media: add virtio-media driver Alexandre Courbot
@ 2025-04-12 14:27 ` Markus Elfring
2025-05-26 12:13 ` Mauro Carvalho Chehab
2025-05-28 16:23 ` Ricardo Ribalda
2 siblings, 0 replies; 26+ messages in thread
From: Markus Elfring @ 2025-04-12 14:27 UTC (permalink / raw)
To: Alexandre Courbot, linux-media, virtualization
Cc: Alexandre Courbot, LKML, Albert Esteve, Alistair Delva,
Changyeon Jo, Daniel Almeida, Eugenio Pérez, Gurchetan Singh,
Hans Verkuil, Jason Wang, Mauro Carvalho Chehab,
Michael S. Tsirkin, Nicolas Dufresne, Xuan Zhuo
…
> +++ b/drivers/media/virtio/virtio_media_driver.c
> @@ -0,0 +1,959 @@
…
> +static struct virtio_media_session *
> +virtio_media_session_alloc(struct virtio_media *vv, u32 id,
> + bool nonblocking_dequeue)
> +{
…
> + init_waitqueue_head(&session->dqbuf_wait);
> +
> + mutex_lock(&vv->sessions_lock);
> + list_add_tail(&session->list, &vv->sessions);
> + mutex_unlock(&vv->sessions_lock);
> +
> + return session;
…
Under which circumstances would you become interested to apply a statement
like “guard(mutex)(&vv->sessions_lock);”?
https://elixir.bootlin.com/linux/v6.14-rc6/source/include/linux/mutex.h#L201
Regards,
Markus
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-04-12 4:08 [PATCH v3] media: add virtio-media driver Alexandre Courbot
2025-04-12 14:27 ` Markus Elfring
@ 2025-05-26 12:13 ` Mauro Carvalho Chehab
2025-05-27 6:14 ` Alexandre Courbot
2025-05-27 14:23 ` Michael S. Tsirkin
2025-05-28 16:23 ` Ricardo Ribalda
2 siblings, 2 replies; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-05-26 12:13 UTC (permalink / raw)
To: Michael S. Tsirkin
Cc: Alexandre Courbot, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization,
Alexandre Courbot
Hi Michael,
Em Sat, 12 Apr 2025 13:08:01 +0900
Alexandre Courbot <gnurou@gmail.com> escreveu:
> Add the first version of the virtio-media driver.
>
> This driver acts roughly as a V4L2 relay between user-space and the
> virtio virtual device on the host, so it is relatively simple, yet
> unconventional. It doesn't use VB2 or other frameworks typically used in
> a V4L2 driver, and most of its complexity resides in correctly and
> efficiently building the virtio descriptor chain to pass to the host,
> avoiding copies whenever possible. This is done by
> scatterlist_builder.[ch].
>
> virtio_media_ioctls.c proxies each supported ioctl to the host, using
> code generated through macros for ioctls that can be forwarded directly,
> which is most of them.
>
> virtio_media_driver.c provides the expected driver hooks, and support
> for mmapping and polling.
>
> This version supports MMAP buffers, while USERPTR buffers can also be
> enabled through a driver option. DMABUF support is still pending.
It sounds that you applied this one at the virtio tree, but it hasn't
being reviewed or acked by media maintainers.
Please drop it.
Alexandre,
Please send media patches to media maintainers, c/c other subsystem
maintainers, as otherwise they might end being merged without a
proper review.
In this particular case, we need to double-check if this won't cause
any issues, in special with regards to media locks and mutexes.
I'll try to look on it after this merge window, as it is too late
for it to be applied during this one.
Regards,
Mauro
>
> Signed-off-by: Alexandre Courbot <acourbot@google.com>
> Signed-off-by: Alexandre Courbot <gnurou@gmail.com>
> ---
> This patch adds the virtio-media kernel driver. Virtio-media [1]
> encapsulates the V4L2 structures and protocol to enable the
> virtualization of host media devices into a guest. It's specification is
> in the final stages [2] of being merged and the virtualization of
> cameras and video accelerator devices has already been demonstrated
> using crosvm [3] and QEmu. v4l2-compliance also passes on all tested
> devices, which includes the "simple" virtual test device, proxied host
> UVC and vivid devices, and the FFmpeg virtual decoder devices (refer to
> [3] in order to test these if desired).
>
> Virtio-media is merged in AOSP [4] and ChromeOS. Upstreaming of the
> driver is overdue, but I hope we can start the review process and
> converge into something that can be merged.
>
> Limitations:
>
> - The driver is currently only available to little-endian, 64-bit
> kernels. This is because some of the V4L2 structures used for
> communication between guest and host have a layout dependent on the
> architecture, and the virtio-media protocol is standardized on the
> little-endian 64-bit versions. This can be fixed with a conversion
> layer similar to the one used to convert 32-bit ioctls to their 64-bit
> counterpart.
> - DMABUF support is currently missing. It should be implemented using
> virtio objects, with possible support for memfds using the
> SHARED_PAGES memory type.
> - No support for the media API and requests. While the use-case for
> these is less important on virtual devices where we want to present an
> abstraction as high as possible to limit VM exits, they do exist and
> it would be nice to add behind a virtio feature bit.
> - Locking in the driver is still very basic. This is something I want to
> improve before merging, but I didn't want to delay upstream review any
> further.
>
> [1] https://github.com/chromeos/virtio-media
> [2] https://lore.kernel.org/virtio-comment/20250304130134.1856056-1-aesteve@redhat.com/
> [3] https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
> [4] https://android.googlesource.com/platform/external/virtio-media/
> ---
> Changes in v3:
> - Rebased on top of v6.15-rc1 and removes obsolete control callbacks.
> - Link to v2: https://lore.kernel.org/r/20250201-virtio-media-v2-1-ac840681452d@gmail.com
>
> Changes in v2:
> - Fixed kernel test robot and media CI warnings (ignored a few false
> positives).
> - Changed in-driver email address to personal one since my Google one
> will soon become invalid.
> - Link to v1: https://lore.kernel.org/r/20250123-virtio-media-v1-1-81e2549b86b9@gmail.com
> ---
> MAINTAINERS | 6 +
> drivers/media/Kconfig | 13 +
> drivers/media/Makefile | 2 +
> drivers/media/virtio/Makefile | 9 +
> drivers/media/virtio/protocol.h | 288 ++++++
> drivers/media/virtio/scatterlist_builder.c | 563 ++++++++++++
> drivers/media/virtio/scatterlist_builder.h | 111 +++
> drivers/media/virtio/session.h | 109 +++
> drivers/media/virtio/virtio_media.h | 93 ++
> drivers/media/virtio/virtio_media_driver.c | 959 ++++++++++++++++++++
> drivers/media/virtio/virtio_media_ioctls.c | 1297 ++++++++++++++++++++++++++++
> include/uapi/linux/virtio_ids.h | 1 +
> 12 files changed, 3451 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 96b82704950184bd71623ff41fc4df31e4c7fe87..f60e17011124fe8c0be0343d4f87e1458f311dcc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -25641,6 +25641,12 @@ S: Maintained
> F: drivers/iommu/virtio-iommu.c
> F: include/uapi/linux/virtio_iommu.h
>
> +VIRTIO MEDIA DRIVER
> +M: Alexandre Courbot <gnurou@gmail.com>
> +L: linux-media@vger.kernel.org
> +S: Maintained
> +F: drivers/media/virtio/
> +
> VIRTIO MEM DRIVER
> M: David Hildenbrand <david@redhat.com>
> L: virtualization@lists.linux.dev
> diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
> index 6abc9302cd84d8563b7877d3d3da4b7e05a6b5d2..12bbb169c0b04565271092c7ac608b0fb11c0244 100644
> --- a/drivers/media/Kconfig
> +++ b/drivers/media/Kconfig
> @@ -230,6 +230,19 @@ source "drivers/media/platform/Kconfig"
> source "drivers/media/mmc/Kconfig"
> endif
>
> +config MEDIA_VIRTIO
> + tristate "Virtio-media Driver"
> + depends on VIRTIO && VIDEO_DEV && 64BIT && (X86 || (ARM && CPU_LITTLE_ENDIAN))
> + select VIDEOBUF2_CORE
> + select VIDEOBUF2_MEMOPS
> + help
> + Enables the virtio-media driver.
> +
> + This driver is used to virtualize media devices such as cameras or
> + decoders from a host into a guest using the V4L2 protocol.
> +
> + If unsure, say N.
> +
> if MEDIA_TEST_SUPPORT
> source "drivers/media/test-drivers/Kconfig"
> endif
> diff --git a/drivers/media/Makefile b/drivers/media/Makefile
> index 20fac24e4f0f13134c12cd859141c8b0387030fa..7a1377661919701f27f4fa2b5ee2dcb1045deb3c 100644
> --- a/drivers/media/Makefile
> +++ b/drivers/media/Makefile
> @@ -25,6 +25,8 @@ obj-y += rc/
>
> obj-$(CONFIG_CEC_CORE) += cec/
>
> +obj-$(CONFIG_MEDIA_VIRTIO) += virtio/
> +
> #
> # Finally, merge the drivers that require the core
> #
> diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..16f91304420d70e1212cc46f3b12f314a510c051
> --- /dev/null
> +++ b/drivers/media/virtio/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for the virtio-media device driver.
> +
> +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/protocol.h b/drivers/media/virtio/protocol.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..a22758cda5aabe75c5c94ce8d1b40583c8652710
> --- /dev/null
> +++ b/drivers/media/virtio/protocol.h
> @@ -0,0 +1,288 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Definitions of virtio-media protocol structures.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#ifndef __VIRTIO_MEDIA_PROTOCOL_H
> +#define __VIRTIO_MEDIA_PROTOCOL_H
> +
> +#include <linux/videodev2.h>
> +
> +/*
> + * Virtio protocol definition.
> + */
> +
> +/**
> + * struct virtio_media_cmd_header - Header for all virtio-media commands.
> + * @cmd: one of VIRTIO_MEDIA_CMD_*.
> + * @__reserved: must be set to zero by the driver.
> + *
> + * This header starts all commands from the driver to the device on the
> + * commandq.
> + */
> +struct virtio_media_cmd_header {
> + u32 cmd;
> + u32 __reserved;
> +};
> +
> +/**
> + * struct virtio_media_resp_header - Header for all virtio-media responses.
> + * @status: 0 if the command was successful, or one of the standard Linux error
> + * codes.
> + * @__reserved: must be set to zero by the device.
> + *
> + * This header starts all responses from the device to the driver on the
> + * commandq.
> + */
> +struct virtio_media_resp_header {
> + u32 status;
> + u32 __reserved;
> +};
> +
> +/**
> + * VIRTIO_MEDIA_CMD_OPEN - Command for creating a new session.
> + *
> + * This is the equivalent of calling `open` on a V4L2 device node. Upon
> + * success, a session id is returned which can be used to perform other
> + * commands on the session, notably ioctls.
> + */
> +#define VIRTIO_MEDIA_CMD_OPEN 1
> +
> +/**
> + * struct virtio_media_cmd_open - Driver command for VIRTIO_MEDIA_CMD_OPEN.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_OPEN.
> + */
> +struct virtio_media_cmd_open {
> + struct virtio_media_cmd_header hdr;
> +};
> +
> +/**
> + * struct virtio_media_resp_open - Device response for VIRTIO_MEDIA_CMD_OPEN.
> + * @hdr: header containing the status of the command.
> + * @session_id: if hdr.status == 0, contains the id of the newly created session.
> + * @__reserved: must be set to zero by the device.
> + */
> +struct virtio_media_resp_open {
> + struct virtio_media_resp_header hdr;
> + u32 session_id;
> + u32 __reserved;
> +};
> +
> +/**
> + * VIRTIO_MEDIA_CMD_CLOSE - Command for closing an active session.
> + *
> + * This is the equivalent of calling `close` on a previously opened V4L2
> + * session. All resources associated with this session will be freed and the
> + * session ID shall not be used again after queueing this command.
> + *
> + * This command does not require a response from the device.
> + */
> +#define VIRTIO_MEDIA_CMD_CLOSE 2
> +
> +/**
> + * struct virtio_media_cmd_close - Driver command for VIRTIO_MEDIA_CMD_CLOSE.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_CLOSE.
> + * @session_id: id of the session to close.
> + * @__reserved: must be set to zero by the driver.
> + */
> +struct virtio_media_cmd_close {
> + struct virtio_media_cmd_header hdr;
> + u32 session_id;
> + u32 __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;
> + u32 session_id;
> + u32 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;
> +};
> +
> +/**
> + * struct virtio_media_sg_entry - Description of part of a scattered guest memory.
> + * @start: start guest address of the memory segment.
> + * @len: length of this memory segment.
> + * @__reserved: must be set to zero by the driver.
> + */
> +struct virtio_media_sg_entry {
> + u64 start;
> + u32 len;
> + u32 __reserved;
> +};
> +
> +/**
> + * enum virtio_media_memory - Memory types supported by virtio-media.
> + * @VIRTIO_MEDIA_MMAP: memory allocated and managed by device. Can be mapped
> + * into the guest using VIRTIO_MEDIA_CMD_MMAP.
> + * @VIRTIO_MEDIA_SHARED_PAGES: memory allocated by the driver. Passed to the
> + * device using virtio_media_sg_entry.
> + * @VIRTIO_MEDIA_OBJECT: memory backed by a virtio object.
> + */
> +enum virtio_media_memory {
> + VIRTIO_MEDIA_MMAP = V4L2_MEMORY_MMAP,
> + VIRTIO_MEDIA_SHARED_PAGES = V4L2_MEMORY_USERPTR,
> + VIRTIO_MEDIA_OBJECT = V4L2_MEMORY_DMABUF,
> +};
> +
> +#define VIRTIO_MEDIA_MMAP_FLAG_RW (1 << 0)
> +
> +/**
> + * VIRTIO_MEDIA_CMD_MMAP - Command for mapping a MMAP buffer into the driver's
> + * address space.
> + *
> + */
> +#define VIRTIO_MEDIA_CMD_MMAP 4
> +
> +/**
> + * struct virtio_media_cmd_mmap - Driver command for VIRTIO_MEDIA_CMD_MMAP.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_MMAP.
> + * @session_id: ID of the session we are mapping for.
> + * @flags: combination of VIRTIO_MEDIA_MMAP_FLAG_*.
> + * @offset: mem_offset field of the plane to map, as returned by VIDIOC_QUERYBUF.
> + */
> +struct virtio_media_cmd_mmap {
> + struct virtio_media_cmd_header hdr;
> + u32 session_id;
> + u32 flags;
> + u32 offset;
> +};
> +
> +/**
> + * struct virtio_media_resp_mmap - Device response for VIRTIO_MEDIA_CMD_MMAP.
> + * @hdr: header containing the status of the command.
> + * @driver_addr: offset into SHM region 0 of the start of the mapping.
> + * @len: length of the mapping.
> + */
> +struct virtio_media_resp_mmap {
> + struct virtio_media_resp_header hdr;
> + u64 driver_addr;
> + u64 len;
> +};
> +
> +/**
> + * VIRTIO_MEDIA_CMD_MUNMAP - Unmap a MMAP buffer previously mapped using
> + * VIRTIO_MEDIA_CMD_MMAP.
> + */
> +#define VIRTIO_MEDIA_CMD_MUNMAP 5
> +
> +/**
> + * struct virtio_media_cmd_munmap - Driver command for VIRTIO_MEDIA_CMD_MUNMAP.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_MUNMAP.
> + * @driver_addr: offset into SHM region 0 at which the buffer has been previously
> + * mapped.
> + */
> +struct virtio_media_cmd_munmap {
> + struct virtio_media_cmd_header hdr;
> + u64 driver_addr;
> +};
> +
> +/**
> + * struct virtio_media_resp_munmap - Device response for VIRTIO_MEDIA_CMD_MUNMAP.
> + * @hdr: header containing the status of the command.
> + */
> +struct virtio_media_resp_munmap {
> + struct virtio_media_resp_header hdr;
> +};
> +
> +#define VIRTIO_MEDIA_EVT_ERROR 0
> +#define VIRTIO_MEDIA_EVT_DQBUF 1
> +#define VIRTIO_MEDIA_EVT_EVENT 2
> +
> +/**
> + * struct virtio_media_event_header - Header for events on the eventq.
> + * @event: one of VIRTIO_MEDIA_EVT_*
> + * @session_id: ID of the session the event applies to.
> + */
> +struct virtio_media_event_header {
> + u32 event;
> + u32 session_id;
> +};
> +
> +/**
> + * struct virtio_media_event_error - Unrecoverable device-side error.
> + * @hdr: header for the event.
> + * @errno: error code describing the kind of error that occurred.
> + * @__reserved: must to set to zero by the device.
> + *
> + * Upon receiving this event, the session mentioned in the header is considered
> + * corrupted and closed.
> + *
> + */
> +struct virtio_media_event_error {
> + struct virtio_media_event_header hdr;
> + u32 errno;
> + u32 __reserved;
> +};
> +
> +#define VIRTIO_MEDIA_MAX_PLANES VIDEO_MAX_PLANES
> +
> +/**
> + * struct virtio_media_event_dqbuf - Dequeued buffer event.
> + * @hdr: header for the event.
> + * @buffer: struct v4l2_buffer describing the buffer that has been dequeued.
> + * @planes: plane information for the dequeued buffer.
> + *
> + * This event is used to signal that a buffer is not being used anymore by the
> + * device and is returned to the driver.
> + */
> +struct virtio_media_event_dqbuf {
> + struct virtio_media_event_header hdr;
> + struct v4l2_buffer buffer;
> + struct v4l2_plane planes[VIRTIO_MEDIA_MAX_PLANES];
> +};
> +
> +/**
> + * struct virtio_media_event_event - V4L2 event.
> + * @hdr: header for the event.
> + * @event: description of the event that occurred.
> + *
> + * This event signals that a V4L2 event has been emitted for a session.
> + */
> +struct virtio_media_event_event {
> + struct virtio_media_event_header hdr;
> + struct v4l2_event event;
> +};
> +
> +/* Maximum size of an event. We will queue descriptors of this size on the eventq. */
> +#define VIRTIO_MEDIA_EVENT_MAX_SIZE sizeof(struct virtio_media_event_dqbuf)
> +
> +#endif // __VIRTIO_MEDIA_PROTOCOL_H
> diff --git a/drivers/media/virtio/scatterlist_builder.c b/drivers/media/virtio/scatterlist_builder.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..2837689f385e81c0c0a99ffd67ac583b426bf186
> --- /dev/null
> +++ b/drivers/media/virtio/scatterlist_builder.c
> @@ -0,0 +1,563 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> +
> +/*
> + * Scatterlist builder helpers for virtio-media.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#include <linux/moduleparam.h>
> +#include <linux/scatterlist.h>
> +#include <linux/videodev2.h>
> +#include <media/videobuf2-memops.h>
> +
> +#include "protocol.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 ``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 + 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)) {
> + int prev_pfn = -2;
> +
> + /*
> + * 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);
> + int 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 (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;
> + }
> + } 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;
> + }
> +
> + 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 + VIRTIO_SHADOW_BUF_SIZE) {
> + if (kaddr + sg->length >= shadow_buf + VIRTIO_SHADOW_BUF_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->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));
> +}
> +
> +/**
> + * __scatterlist_builder_add_userptr() - Add user pages to @builder.
> + * @builder: builder to use.
> + * @userptr: pointer to userspace memory that we want to add.
> + * @length: length of the data to add.
> + * @sg_list: output parameter. Upon success, points to the area of the shadow
> + * buffer containing the array of SG entries to be added to the descriptor
> + * chain.
> + * @nents: output parameter. Upon success, contains the number of entries
> + * pointed to by @sg_list.
> + *
> + * Data referenced by userspace pointers can be potentially large and very
> + * scattered, which could overwhelm the descriptor chain if added as-is. For
> + * these, we instead build an array of ``struct virtio_media_sg_entry`` in the
> + * shadow buffer and reference it using a single descriptor.
> + *
> + * This function is a helper to perform that. Callers should then add the
> + * descriptor to the chain properly.
> + *
> + * Returns -EFAULT if @userptr is not a valid user address, which is a case the
> + * driver should consider as "normal" operation. All other failures signal a
> + * problem with the driver.
> + */
> +static int
> +__scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> + unsigned long userptr, unsigned long length,
> + struct virtio_media_sg_entry **sg_list,
> + int *nents)
> +{
> + struct sg_table sg_table = {};
> + struct frame_vector *framevec;
> + struct scatterlist *sg_iter;
> + struct page **pages;
> + const unsigned int offset = userptr & ~PAGE_MASK;
> + unsigned int pages_count;
> + size_t entries_size;
> + int i;
> + int ret;
> +
> + framevec = vb2_create_framevec(userptr, length, true);
> + if (IS_ERR(framevec)) {
> + if (PTR_ERR(framevec) != -EFAULT) {
> + pr_warn("error %ld creating frame vector for userptr 0x%lx, length 0x%lx\n",
> + PTR_ERR(framevec), userptr, length);
> + } else {
> + /* -EINVAL is expected in case of invalid userptr. */
> + framevec = ERR_PTR(-EINVAL);
> + }
> + return PTR_ERR(framevec);
> + }
> +
> + pages = frame_vector_pages(framevec);
> + if (IS_ERR(pages)) {
> + pr_warn("error getting vector pages\n");
> + ret = PTR_ERR(pages);
> + goto done;
> + }
> + pages_count = frame_vector_count(framevec);
> + ret = sg_alloc_table_from_pages(&sg_table, pages, pages_count, offset,
> + length, 0);
> + if (ret) {
> + pr_warn("error creating sg table\n");
> + goto done;
> + }
> +
> + /* Allocate our actual SG in the shadow buffer. */
> + *nents = sg_nents(sg_table.sgl);
> + entries_size = sizeof(**sg_list) * *nents;
> + if (builder->shadow_buffer_pos + entries_size >
> + builder->shadow_buffer_size) {
> + ret = -ENOMEM;
> + goto free_sg;
> + }
> +
> + *sg_list = builder->shadow_buffer + builder->shadow_buffer_pos;
> + builder->shadow_buffer_pos += entries_size;
> +
> + for_each_sgtable_sg(&sg_table, sg_iter, i) {
> + struct virtio_media_sg_entry *sg_entry = &(*sg_list)[i];
> +
> + sg_entry->start = sg_phys(sg_iter);
> + sg_entry->len = sg_iter->length;
> + }
> +
> +free_sg:
> + sg_free_table(&sg_table);
> +
> +done:
> + vb2_destroy_framevec(framevec);
> + return ret;
> +}
> +
> +/**
> + * scatterlist_builder_add_userptr() - Add a user-memory buffer using an array
> + * of ``struct virtio_media_sg_entry``.
> + * @builder: builder to use.
> + * @userptr: pointer to userspace memory that we want to add.
> + * @length: length of the data to add.
> + *
> + * Upon success, an array of ``struct virtio_media_sg_entry`` referencing
> + * @userptr has been built into the shadow buffer, and that array added to the
> + * descriptor chain.
> + */
> +static int scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> + unsigned long userptr,
> + unsigned long length)
> +{
> + int ret;
> + int nents;
> + struct virtio_media_sg_entry *sg_list;
> +
> + ret = __scatterlist_builder_add_userptr(builder, userptr, length,
> + &sg_list, &nents);
> + if (ret)
> + return ret;
> +
> + ret = scatterlist_builder_add_data(builder, sg_list,
> + sizeof(*sg_list) * nents);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_buffer() - Add a ``v4l2_buffer`` and its planes to
> + * the descriptor chain.
> + * @builder: builder to use.
> + * @b: ``v4l2_buffer`` to add.
> + */
> +int scatterlist_builder_add_buffer(struct scatterlist_builder *builder,
> + struct v4l2_buffer *b)
> +{
> + int i;
> + int ret;
> +
> + /* Fixup: plane length must be zero if userptr is NULL */
> + if (!V4L2_TYPE_IS_MULTIPLANAR(b->type) &&
> + b->memory == V4L2_MEMORY_USERPTR && b->m.userptr == 0)
> + b->length = 0;
> +
> + /* v4l2_buffer */
> + ret = scatterlist_builder_add_data(builder, b, sizeof(*b));
> + if (ret)
> + return ret;
> +
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type) && b->length > 0) {
> + /* Fixup: plane length must be zero if userptr is NULL */
> + if (b->memory == V4L2_MEMORY_USERPTR) {
> + for (i = 0; i < b->length; i++) {
> + struct v4l2_plane *plane = &b->m.planes[i];
> +
> + if (plane->m.userptr == 0)
> + plane->length = 0;
> + }
> + }
> +
> + /* Array of v4l2_planes */
> + ret = scatterlist_builder_add_data(builder, b->m.planes,
> + sizeof(struct v4l2_plane) *
> + b->length);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_buffer_userptr() - Add the payload of a ``USERTPR``
> + * v4l2_buffer to the descriptor chain.
> + * @builder: builder to use.
> + * @b: ``v4l2_buffer`` which ``USERPTR`` payload we want to add.
> + *
> + * Add an array of ``virtio_media_sg_entry`` pointing to a ``USERPTR`` buffer's
> + * contents. Does nothing if the buffer is not of type ``USERPTR``. This is
> + * split out of :ref:`scatterlist_builder_add_buffer` because we only want to
> + * add these to the device-readable part of the descriptor chain.
> + */
> +int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *builder,
> + struct v4l2_buffer *b)
> +{
> + int i;
> + int ret;
> +
> + if (b->memory != V4L2_MEMORY_USERPTR)
> + return 0;
> +
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
> + for (i = 0; i < b->length; i++) {
> + struct v4l2_plane *plane = &b->m.planes[i];
> +
> + if (b->memory == V4L2_MEMORY_USERPTR &&
> + plane->length > 0) {
> + ret = scatterlist_builder_add_userptr(
> + builder, plane->m.userptr,
> + plane->length);
> + if (ret)
> + return ret;
> + }
> + }
> + } else if (b->length > 0) {
> + ret = scatterlist_builder_add_userptr(builder, b->m.userptr,
> + b->length);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_retrieve_buffer() - Retrieve a v4l2_buffer written by
> + * the device on the shadow buffer, if needed.
> + * @builder: builder to use.
> + * @sg_index: index of the first SG entry of the buffer in the builder's
> + * descriptor chain.
> + * @b: v4l2_buffer to copy shadow buffer data into.
> + * @orig_planes: the original ``planes`` pointer, to be restored if the buffer
> + * is multi-planar.
> + *
> + * If the v4l2_buffer pointed by @buffer_sgs was copied into the shadow buffer,
> + * then its updated content is copied back into @b. Otherwise nothing is done
> + * as the device has written into @b directly.
> + *
> + * @orig_planes is used to restore the original ``planes`` pointer in case it
> + * gets modified by the host. The specification stipulates that the host should
> + * not modify it, but we enforce this for additional safety.
> + */
> +int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builder,
> + size_t sg_index, struct v4l2_buffer *b,
> + struct v4l2_plane *orig_planes)
> +{
> + int ret;
> +
> + ret = scatterlist_builder_retrieve_data(builder, sg_index++, b);
> + if (ret)
> + return ret;
> +
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
> + b->m.planes = orig_planes;
> +
> + if (orig_planes != NULL) {
> + ret = scatterlist_builder_retrieve_data(
> + builder, sg_index++, b->m.planes);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_ext_ctrls() - Add a v4l2_ext_controls and its
> + * controls to @builder.
> + * @builder: builder to use.
> + * @ctrls: ``struct v4l2_ext_controls`` to add.
> + *
> + * Add @ctrls and its array of `struct v4l2_ext_control` to the descriptor chain.
> + */
> +int scatterlist_builder_add_ext_ctrls(struct scatterlist_builder *builder,
> + struct v4l2_ext_controls *ctrls)
> +{
> + int ret;
> +
> + /* v4l2_ext_controls */
> + ret = scatterlist_builder_add_data(builder, ctrls, sizeof(*ctrls));
> + if (ret)
> + return ret;
> +
> + if (ctrls->count > 0) {
> + /* array of v4l2_controls */
> + ret = scatterlist_builder_add_data(builder, ctrls->controls,
> + sizeof(ctrls->controls[0]) *
> + ctrls->count);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_ext_ctrls_userptrs() - Add the userspace payloads of
> + * a ``struct v4l2_ext_controls`` to the descriptor chain.
> + * @builder: builder to use.
> + * @ctrls: ``struct v4l2_ext_controls`` from which we want to add the userspace payload of.
> + *
> + * Add the userspace payloads of @ctrls to the descriptor chain. This is split
> + * out of :ref:`scatterlist_builder_add_ext_ctrls` because we only want to add
> + * these to the device-readable part of the descriptor chain.
> + */
> +int scatterlist_builder_add_ext_ctrls_userptrs(
> + struct scatterlist_builder *builder, struct v4l2_ext_controls *ctrls)
> +{
> + int i;
> + int ret;
> +
> + /* Pointers to user memory in individual controls */
> + for (i = 0; i < ctrls->count; i++) {
> + struct v4l2_ext_control *ctrl = &ctrls->controls[i];
> +
> + if (ctrl->size > 0) {
> + ret = scatterlist_builder_add_userptr(
> + builder, (unsigned long)ctrl->ptr, ctrl->size);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_retrieve_ext_ctrls() - Retrieve controls written by the
> + * device on the shadow buffer, if needed.
> + * @builder: builder to use.
> + * @sg_index: index of the first SG entry of the controls in the builder's
> + * descriptor chain.
> + * @ctrls: ``struct v4l2_ext_controls`` to copy shadow buffer data into.
> + *
> + * If the shadow buffer is pointed to by @sg, copy its content back into @ctrls.
> + */
> +int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *builder,
> + size_t sg_index,
> + struct v4l2_ext_controls *ctrls)
> +{
> + struct v4l2_ext_control *controls_backup = ctrls->controls;
> + int ret;
> +
> + ret = scatterlist_builder_retrieve_data(builder, sg_index++, ctrls);
> + if (ret)
> + return ret;
> +
> + ctrls->controls = controls_backup;
> +
> + if (ctrls->count > 0 && ctrls->controls) {
> + ret = scatterlist_builder_retrieve_data(builder, sg_index++,
> + ctrls->controls);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/media/virtio/scatterlist_builder.h b/drivers/media/virtio/scatterlist_builder.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..c8323c31ac21953580a0b4a4cb366841e510666f
> --- /dev/null
> +++ b/drivers/media/virtio/scatterlist_builder.h
> @@ -0,0 +1,111 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Scatterlist builder helpers for virtio-media.
> + *
> + * Copyright (c) 2024-2025 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 ``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);
> +
> +int scatterlist_builder_add_buffer(struct scatterlist_builder *builder,
> + struct v4l2_buffer *buffer);
> +
> +int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *builder,
> + struct v4l2_buffer *b);
> +
> +int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builder,
> + size_t sg_index,
> + struct v4l2_buffer *buffer,
> + struct v4l2_plane *orig_planes);
> +
> +int scatterlist_builder_add_ext_ctrls(struct scatterlist_builder *builder,
> + struct v4l2_ext_controls *ctrls);
> +
> +int scatterlist_builder_add_ext_ctrls_userptrs(
> + struct scatterlist_builder *builder, struct v4l2_ext_controls *ctrls);
> +
> +int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *builder,
> + size_t sg_index,
> + struct v4l2_ext_controls *ctrls);
> +
> +#endif // __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H
> diff --git a/drivers/media/virtio/session.h b/drivers/media/virtio/session.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..b643d0d950477d56d4bb5db481818a3912af5c1f
> --- /dev/null
> +++ b/drivers/media/virtio/session.h
> @@ -0,0 +1,109 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Definitions of virtio-media session related structures.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#ifndef __VIRTIO_MEDIA_SESSION_H
> +#define __VIRTIO_MEDIA_SESSION_H
> +
> +#include <linux/scatterlist.h>
> +#include <media/v4l2-fh.h>
> +
> +#include "protocol.h"
> +
> +#define VIRTIO_MEDIA_LAST_QUEUE (V4L2_BUF_TYPE_META_OUTPUT)
> +
> +/*
> + * 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_buffer - Current state of a buffer.
> + * @buffer: ``struct v4l2_buffer`` with current information about the buffer.
> + * @planes: backing planes array for @buffer.
> + * @list: link into the list of buffers pending dequeue.
> + */
> +struct virtio_media_buffer {
> + struct v4l2_buffer buffer;
> + struct v4l2_plane planes[VIDEO_MAX_PLANES];
> + struct list_head list;
> +};
> +
> +/**
> + * struct virtio_media_queue_state - Represents the state of a V4L2 queue.
> + * @streaming: Whether the queue is currently streaming.
> + * @allocated_bufs: How many buffers are currently allocated.
> + * @is_capture_last: set to true when the last buffer has been received on a
> + * capture queue, so we can return -EPIPE on subsequent DQBUF requests.
> + * @buffers: Buffer state array of size @allocated_bufs.
> + * @queued_bufs: How many buffers are currently queued on the device.
> + * @pending_dqbufs: Buffers that are available for being dequeued.
> + */
> +struct virtio_media_queue_state {
> + bool streaming;
> + size_t allocated_bufs;
> + bool is_capture_last;
> +
> + struct virtio_media_buffer *buffers;
> + size_t queued_bufs;
> + struct list_head pending_dqbufs;
> +};
> +
> +/**
> + * struct virtio_media_session - A session on a virtio_media device.
> + * @fh: file handler for the session.
> + * @id: session ID used to communicate with the device.
> + * @nonblocking_dequeue: whether dequeue should block or not (nonblocking if
> + * file opened with O_NONBLOCK).
> + * @uses_mplane: whether the queues for this session use the MPLANE API or not.
> + * @cmd: union of session-related commands. A session can have one command currently running.
> + * @resp: union of session-related responses. A session can wait on one command only.
> + * @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.
> + * @queues: state of all the queues for this session.
> + * @queues_lock: protects all members fo the queues for this session.
> + * virtio_media_queue_state`.
> + * @dqbuf_wait: waitqueue for dequeued buffers, if ``VIDIOC_DQBUF`` needs to
> + * block or when polling.
> + * @list: link into the list of sessions for the device.
> + */
> +struct virtio_media_session {
> + struct v4l2_fh fh;
> + u32 id;
> + bool nonblocking_dequeue;
> + bool uses_mplane;
> +
> + union {
> + struct virtio_media_cmd_close close;
> + struct virtio_media_cmd_ioctl ioctl;
> + struct virtio_media_cmd_mmap mmap;
> + } cmd;
> +
> + union {
> + struct virtio_media_resp_ioctl ioctl;
> + struct virtio_media_resp_mmap mmap;
> + } resp;
> +
> + void *shadow_buf;
> +
> + struct sg_table command_sgs;
> +
> + struct virtio_media_queue_state queues[VIRTIO_MEDIA_LAST_QUEUE + 1];
> + struct mutex queues_lock;
> + wait_queue_head_t dqbuf_wait;
> +
> + struct list_head list;
> +};
> +
> +static inline struct virtio_media_session *fh_to_session(struct v4l2_fh *fh)
> +{
> + return container_of(fh, struct virtio_media_session, fh);
> +}
> +
> +#endif // __VIRTIO_MEDIA_SESSION_H
> diff --git a/drivers/media/virtio/virtio_media.h b/drivers/media/virtio/virtio_media.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..0aa503defdd6a08e12335276f7ccbabc3d53df09
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media.h
> @@ -0,0 +1,93 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Virtio-media structures & functions declarations.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#ifndef __VIRTIO_MEDIA_H
> +#define __VIRTIO_MEDIA_H
> +
> +#include <linux/virtio_config.h>
> +#include <media/v4l2-device.h>
> +
> +#include "protocol.h"
> +
> +#define DESC_CHAIN_MAX_LEN SG_MAX_SINGLE_ALLOC
> +
> +#define VIRTIO_MEDIA_DEFAULT_DRIVER_NAME "virtio-media"
> +
> +extern char *virtio_media_driver_name;
> +extern bool virtio_media_allow_userptr;
> +
> +/**
> + * struct virtio_media - Virtio-media device.
> + * @v4l2_dev: v4l2_device for the media device.
> + * @video_dev: video_device for the media device.
> + * @virtio_dev: virtio device for the media device.
> + * @commandq: virtio command queue.
> + * @eventq: virtio event queue.
> + * @eventq_work: work to run when events are received on @eventq.
> + * @mmap_region: region into which MMAP buffers are mapped by the host.
> + * @event_buffer: buffer for event descriptors.
> + * @sessions: list of active sessions on the device.
> + * @sessions_lock: protects @sessions and ``virtio_media_session::list``.
> + * @events_lock: prevents concurrent processing of events.
> + * @cmd: union of device-related commands.
> + * @resp: union of device-related responses.
> + * @vlock: serializes access to the command queue.
> + * @wq: waitqueue for host responses on the command queue.
> + */
> +struct virtio_media {
> + struct v4l2_device v4l2_dev;
> + struct video_device video_dev;
> +
> + struct virtio_device *virtio_dev;
> + struct virtqueue *commandq;
> + struct virtqueue *eventq;
> + struct work_struct eventq_work;
> +
> + struct virtio_shm_region mmap_region;
> +
> + void *event_buffer;
> +
> + struct list_head sessions;
> + struct mutex sessions_lock;
> +
> + struct mutex events_lock;
> +
> + union {
> + struct virtio_media_cmd_open open;
> + struct virtio_media_cmd_munmap munmap;
> + } cmd;
> +
> + union {
> + struct virtio_media_resp_open open;
> + struct virtio_media_resp_munmap munmap;
> + } resp;
> +
> + struct mutex vlock;
> + wait_queue_head_t wq;
> +};
> +
> +static inline struct virtio_media *
> +to_virtio_media(struct video_device *video_dev)
> +{
> + return container_of(video_dev, struct virtio_media, video_dev);
> +}
> +
> +/* virtio_media_driver.c */
> +
> +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);
> +void virtio_media_process_events(struct virtio_media *vv);
> +
> +/* 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
> new file mode 100644
> index 0000000000000000000000000000000000000000..e8d6dc453f2240c7809152c2a04813120bd3aca2
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media_driver.c
> @@ -0,0 +1,959 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> +
> +/*
> + * Virtio-media driver.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dev_printk.h>
> +#include <linux/mm.h>
> +#include <linux/mutex.h>
> +#include <linux/scatterlist.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/vmalloc.h>
> +#include <linux/wait.h>
> +#include <linux/workqueue.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/virtio.h>
> +#include <linux/virtio_config.h>
> +#include <linux/virtio_ids.h>
> +
> +#include <media/frame_vector.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-event.h>
> +#include <media/videobuf2-memops.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-ioctl.h>
> +
> +#include "protocol.h"
> +#include "session.h"
> +#include "virtio_media.h"
> +
> +#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
> +
> +/*
> + * Name of the driver to expose to user-space.
> + *
> + * This is configurable because v4l2-compliance has workarounds specific to
> + * some drivers. When proxying these directly from the host, this allows it to
> + * apply them as needed.
> + */
> +char *virtio_media_driver_name;
> +module_param_named(driver_name, virtio_media_driver_name, charp, 0660);
> +
> +/*
> + * Whether USERPTR buffers are allowed.
> + *
> + * This is disabled by default as USERPTR buffers are dangerous, but the option
> + * is left to enable them if desired.
> + */
> +bool virtio_media_allow_userptr;
> +module_param_named(allow_userptr, virtio_media_allow_userptr, bool, 0660);
> +
> +/**
> + * virtio_media_session_alloc - Allocate a new session.
> + * @vv: virtio-media device the session belongs to.
> + * @id: ID of the session.
> + * @nonblocking_dequeue: whether dequeuing of buffers should be blocking or
> + * not.
> + *
> + * The ``id`` and ``list`` fields must still be set by the caller.
> + */
> +static struct virtio_media_session *
> +virtio_media_session_alloc(struct virtio_media *vv, u32 id,
> + bool nonblocking_dequeue)
> +{
> + struct virtio_media_session *session;
> + int i;
> + int ret;
> +
> + session = kzalloc(sizeof(*session), GFP_KERNEL);
> + if (!session)
> + 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;
> +
> + session->id = id;
> + session->nonblocking_dequeue = nonblocking_dequeue;
> +
> + INIT_LIST_HEAD(&session->list);
> + v4l2_fh_init(&session->fh, &vv->video_dev);
> + v4l2_fh_add(&session->fh);
> +
> + for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
> + INIT_LIST_HEAD(&session->queues[i].pending_dqbufs);
> + mutex_init(&session->queues_lock);
> +
> + init_waitqueue_head(&session->dqbuf_wait);
> +
> + mutex_lock(&vv->sessions_lock);
> + list_add_tail(&session->list, &vv->sessions);
> + mutex_unlock(&vv->sessions_lock);
> +
> + return session;
> +
> +err_payload_sgs:
> + kfree(session->shadow_buf);
> +err_shadow_buf:
> + kfree(session);
> +err_session:
> + return ERR_PTR(-ENOMEM);
> +}
> +
> +/**
> + * virtio_media_session_free - Free all resources of a session.
> + * @vv: virtio-media device the session belongs to.
> + * @session: session to destroy.
> + *
> + * All the resources of @sesssion, as well as the backing memory of @session
> + * itself, are freed.
> + */
> +static void virtio_media_session_free(struct virtio_media *vv,
> + struct virtio_media_session *session)
> +{
> + int i;
> +
> + mutex_lock(&vv->sessions_lock);
> + list_del(&session->list);
> + mutex_unlock(&vv->sessions_lock);
> +
> + v4l2_fh_del(&session->fh);
> + v4l2_fh_exit(&session->fh);
> +
> + sg_free_table(&session->command_sgs);
> +
> + for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
> + vfree(session->queues[i].buffers);
> +
> + kfree(session->shadow_buf);
> + kfree(session);
> +}
> +
> +/**
> + * virtio_media_session_close - Close and free a session.
> + * @vv: virtio-media device the session belongs to.
> + * @session: session to close and destroy.
> + *
> + * This send the ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and frees
> + * all resources used by @session.
> + */
> +static int virtio_media_session_close(struct virtio_media *vv,
> + struct virtio_media_session *session)
> +{
> + struct virtio_media_cmd_close *cmd_close = &session->cmd.close;
> + struct scatterlist cmd_sg = {};
> + struct scatterlist *sgs[1] = { &cmd_sg };
> + int ret;
> +
> + mutex_lock(&vv->vlock);
> +
> + cmd_close->hdr.cmd = VIRTIO_MEDIA_CMD_CLOSE;
> + cmd_close->session_id = session->id;
> +
> + sg_set_buf(&cmd_sg, cmd_close, sizeof(*cmd_close));
> + sg_mark_end(&cmd_sg);
> +
> + ret = virtio_media_send_command(vv, sgs, 1, 0, 0, NULL);
> + mutex_unlock(&vv->vlock);
> + if (ret < 0)
> + return ret;
> +
> + virtio_media_session_free(vv, session);
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_find_session - Lookup for the session with a given ID.
> + * @vv: virtio-media device to lookup the session from.
> + * @id: ID of the session to lookup.
> + */
> +static struct virtio_media_session *
> +virtio_media_find_session(struct virtio_media *vv, u32 id)
> +{
> + struct list_head *p;
> + struct virtio_media_session *session = NULL;
> +
> + mutex_lock(&vv->sessions_lock);
> + list_for_each(p, &vv->sessions) {
> + struct virtio_media_session *s =
> + list_entry(p, struct virtio_media_session, list);
> + if (s->id == id) {
> + session = s;
> + break;
> + }
> + }
> + mutex_unlock(&vv->sessions_lock);
> +
> + return session;
> +}
> +
> +/**
> + * struct virtio_media_cmd_callback_param - Callback parameters to the virtio command queue.
> + * @vv: virtio-media device in use.
> + * @done: flag to be switched once the command is completed.
> + * @resp_len: length of the received response from the command. Only valid
> + * after @done_flag has switched to ``true``.
> + */
> +struct virtio_media_cmd_callback_param {
> + struct virtio_media *vv;
> + bool done;
> + size_t resp_len;
> +};
> +
> +/**
> + * commandq_callback: Callback for the command queue.
> + * @queue: command virtqueue.
> + *
> + * This just wakes up the thread that was waiting on the command to complete.
> + */
> +static void commandq_callback(struct virtqueue *queue)
> +{
> + unsigned int len;
> + struct virtio_media_cmd_callback_param *param;
> +
> +process_bufs:
> + while ((param = virtqueue_get_buf(queue, &len))) {
> + param->done = true;
> + param->resp_len = len;
> + wake_up(¶m->vv->wq);
> + }
> +
> + if (!virtqueue_enable_cb(queue)) {
> + virtqueue_disable_cb(queue);
> + goto process_bufs;
> + }
> +}
> +
> +/**
> + * virtio_media_kick_command - send a command to the commandq.
> + * @vv: virtio-media device in use.
> + * @sgs: descriptor chain to send.
> + * @out_sgs: number of device-readable descriptors in @sgs.
> + * @in_sgs: number of device-writable descriptors in @sgs.
> + * @resp_len: output parameter. Upon success, contains the size of the response
> + * in bytes.
> + *
> + */
> +static int virtio_media_kick_command(struct virtio_media *vv,
> + struct scatterlist **sgs,
> + const size_t out_sgs, const size_t in_sgs,
> + size_t *resp_len)
> +{
> + struct virtio_media_cmd_callback_param cb_param = {
> + .vv = vv,
> + .done = false,
> + .resp_len = 0,
> + };
> + struct virtio_media_resp_header *resp_header;
> + int ret;
> +
> + ret = virtqueue_add_sgs(vv->commandq, sgs, out_sgs, in_sgs, &cb_param,
> + GFP_ATOMIC);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to add sgs to command virtqueue\n");
> + return ret;
> + }
> +
> + if (!virtqueue_kick(vv->commandq)) {
> + v4l2_err(&vv->v4l2_dev, "failed to kick command virtqueue\n");
> + return -EINVAL;
> + }
> +
> + /* Wait for the response. */
> + ret = wait_event_timeout(vv->wq, cb_param.done, 5 * HZ);
> + if (ret == 0) {
> + v4l2_err(&vv->v4l2_dev,
> + "timed out waiting for response to command\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (resp_len)
> + *resp_len = cb_param.resp_len;
> +
> + if (in_sgs > 0) {
> + /*
> + * If we expect a response, make sure we have at least a
> + * response header - anything shorter is invalid.
> + */
> + if (cb_param.resp_len < sizeof(*resp_header)) {
> + v4l2_err(&vv->v4l2_dev,
> + "received response header is too short\n");
> + return -EINVAL;
> + }
> +
> + resp_header = sg_virt(sgs[out_sgs]);
> + if (resp_header->status)
> + /* Host returns a positive error code. */
> + return -resp_header->status;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_send_command - Send a command to the device and wait for its
> + * response.
> + * @vv: virtio-media device in use.
> + * @sgs: descriptor chain to send.
> + * @out_sgs: number of device-readable descriptors in @sgs.
> + * @in_sgs: number of device-writable descriptors in @sgs.
> + * @minimum_resp_len: minimum length of the response expected by the caller
> + * when the command is successful. Anything shorter than that will result in
> + * ``-EINVAL`` being returned.
> + * @resp_len: output parameter. Upon success, contains the size of the response
> + * in bytes.
> + */
> +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)
> +{
> + size_t local_resp_len = resp_len ? *resp_len : 0;
> + int ret = virtio_media_kick_command(vv, sgs, out_sgs, in_sgs,
> + &local_resp_len);
> + if (resp_len)
> + *resp_len = local_resp_len;
> +
> + /* If the host could not process the command, there is no valid response */
> + if (ret < 0)
> + return ret;
> +
> + /* Make sure the host wrote a complete reply. */
> + if (local_resp_len < minimum_resp_len) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "received response is too short: received %zu, expected at least %zu\n",
> + local_resp_len, minimum_resp_len);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_send_event_buffer() - Sends an event buffer to the host so it
> + * can return it with an event.
> + * @vv: virtio-media device in use.
> + * @event_buffer: pointer to the event buffer to send to the device.
> + */
> +static int virtio_media_send_event_buffer(struct virtio_media *vv,
> + void *event_buffer)
> +{
> + struct scatterlist *sgs[1], vresp;
> + int ret;
> +
> + sg_init_one(&vresp, event_buffer, VIRTIO_MEDIA_EVENT_MAX_SIZE);
> + sgs[0] = &vresp;
> +
> + ret = virtqueue_add_sgs(vv->eventq, sgs, 0, 1, event_buffer,
> + GFP_ATOMIC);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to add sgs to event virtqueue\n");
> + return ret;
> + }
> +
> + if (!virtqueue_kick(vv->eventq)) {
> + v4l2_err(&vv->v4l2_dev, "failed to kick event virtqueue\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * eventq_callback() - Callback for the event queue.
> + * @queue: event virtqueue.
> + *
> + * This just schedules for event work to be run.
> + */
> +static void eventq_callback(struct virtqueue *queue)
> +{
> + struct virtio_media *vv = queue->vdev->priv;
> +
> + schedule_work(&vv->eventq_work);
> +}
> +
> +/**
> + * virtio_media_process_dqbuf_event() - Process a dequeued event for a session.
> + * @vv: virtio-media device in use.
> + * @session: session the event is addressed to.
> + * @dqbuf_evt: the dequeued event to process.
> + *
> + * Invalid events are ignored with an error log.
> + */
> +static void
> +virtio_media_process_dqbuf_event(struct virtio_media *vv,
> + struct virtio_media_session *session,
> + struct virtio_media_event_dqbuf *dqbuf_evt)
> +{
> + struct virtio_media_buffer *dqbuf;
> + const enum v4l2_buf_type queue_type = dqbuf_evt->buffer.type;
> + struct virtio_media_queue_state *queue;
> + typeof(dqbuf->buffer.m) buffer_m;
> + typeof(dqbuf->buffer.m.planes[0].m) plane_m;
> + int i;
> +
> + if (queue_type >= ARRAY_SIZE(session->queues)) {
> + v4l2_err(&vv->v4l2_dev,
> + "unmanaged queue %d passed to dqbuf event",
> + dqbuf_evt->buffer.type);
> + return;
> + }
> + queue = &session->queues[queue_type];
> +
> + if (dqbuf_evt->buffer.index >= queue->allocated_bufs) {
> + v4l2_err(&vv->v4l2_dev,
> + "invalid buffer ID %d for queue %d in dqbuf event",
> + dqbuf_evt->buffer.index, dqbuf_evt->buffer.type);
> + return;
> + }
> +
> + dqbuf = &queue->buffers[dqbuf_evt->buffer.index];
> +
> + /*
> + * Preserve the 'm' union that was passed to us during QBUF so userspace
> + * gets back the information it submitted.
> + */
> + buffer_m = dqbuf->buffer.m;
> + memcpy(&dqbuf->buffer, &dqbuf_evt->buffer, sizeof(dqbuf->buffer));
> + dqbuf->buffer.m = buffer_m;
> + if (V4L2_TYPE_IS_MULTIPLANAR(dqbuf->buffer.type)) {
> + if (dqbuf->buffer.length > VIDEO_MAX_PLANES) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "invalid number of planes received from host for a multiplanar buffer\n");
> + return;
> + }
> + for (i = 0; i < dqbuf->buffer.length; i++) {
> + plane_m = dqbuf->planes[i].m;
> + memcpy(&dqbuf->planes[i], &dqbuf_evt->planes[i],
> + sizeof(struct v4l2_plane));
> + dqbuf->planes[i].m = plane_m;
> + }
> + }
> +
> + /* Set the DONE flag as the buffer is waiting for being dequeued. */
> + dqbuf->buffer.flags |= V4L2_BUF_FLAG_DONE;
> +
> + mutex_lock(&session->queues_lock);
> + list_add_tail(&dqbuf->list, &queue->pending_dqbufs);
> + queue->queued_bufs -= 1;
> + mutex_unlock(&session->queues_lock);
> +
> + wake_up(&session->dqbuf_wait);
> +}
> +
> +/**
> + * virtio_media_process_events() - Process all pending events on a device.
> + * @vv: device which pending events we want to process.
> + *
> + * Retrieves all pending events on @vv's event queue and dispatch them to their
> + * corresponding session.
> + *
> + * Invalid events are ignored with an error log.
> + */
> +void virtio_media_process_events(struct virtio_media *vv)
> +{
> + struct virtio_media_event_error *error_evt;
> + struct virtio_media_event_dqbuf *dqbuf_evt;
> + struct virtio_media_event_event *event_evt;
> + struct virtio_media_session *session;
> + struct virtio_media_event_header *evt;
> + unsigned int len;
> +
> + mutex_lock(&vv->events_lock);
> +
> +process_bufs:
> + while ((evt = virtqueue_get_buf(vv->eventq, &len))) {
> + /* Make sure we received enough data */
> + if (len < sizeof(*evt)) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "event is too short: got %u, expected at least %zu\n",
> + len, sizeof(*evt));
> + goto end_of_event;
> + }
> +
> + session = virtio_media_find_session(vv, evt->session_id);
> + if (!session) {
> + v4l2_err(&vv->v4l2_dev, "cannot find session %d\n",
> + evt->session_id);
> + goto end_of_event;
> + }
> +
> + switch (evt->event) {
> + case VIRTIO_MEDIA_EVT_ERROR:
> + if (len < sizeof(*error_evt)) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "error event is too short: got %u, expected %zu\n",
> + len, sizeof(*error_evt));
> + break;
> + }
> + error_evt = (struct virtio_media_event_error *)evt;
> + v4l2_err(&vv->v4l2_dev,
> + "received error %d for session %d",
> + error_evt->errno, error_evt->hdr.session_id);
> + virtio_media_session_close(vv, session);
> + break;
> +
> + /*
> + * Dequeued buffer: put it into the right queue so user-space can dequeue
> + * it.
> + */
> + case VIRTIO_MEDIA_EVT_DQBUF:
> + if (len < sizeof(*dqbuf_evt)) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "dqbuf event is too short: got %u, expected %zu\n",
> + len, sizeof(*dqbuf_evt));
> + break;
> + }
> + dqbuf_evt = (struct virtio_media_event_dqbuf *)evt;
> + virtio_media_process_dqbuf_event(vv, session,
> + dqbuf_evt);
> + break;
> +
> + case VIRTIO_MEDIA_EVT_EVENT:
> + if (len < sizeof(*event_evt)) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "session event is too short: got %u expected %zu\n",
> + len, sizeof(*event_evt));
> + break;
> + }
> +
> + event_evt = (struct virtio_media_event_event *)evt;
> + v4l2_event_queue_fh(&session->fh, &event_evt->event);
> + break;
> +
> + default:
> + v4l2_err(&vv->v4l2_dev, "unknown event type %d\n",
> + evt->event);
> + break;
> + }
> +
> +end_of_event:
> + virtio_media_send_event_buffer(vv, evt);
> + }
> +
> + if (!virtqueue_enable_cb(vv->eventq)) {
> + virtqueue_disable_cb(vv->eventq);
> + goto process_bufs;
> + }
> +
> + mutex_unlock(&vv->events_lock);
> +}
> +
> +static void virtio_media_event_work(struct work_struct *work)
> +{
> + struct virtio_media *vv =
> + container_of(work, struct virtio_media, eventq_work);
> +
> + virtio_media_process_events(vv);
> +}
> +
> +/**
> + * virtio_media_device_open() - Create a new session from an opened file.
> + * @file: opened file for the session.
> + */
> +static int virtio_media_device_open(struct file *file)
> +{
> + struct video_device *video_dev = video_devdata(file);
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_cmd_open *cmd_open = &vv->cmd.open;
> + struct virtio_media_resp_open *resp_open = &vv->resp.open;
> + struct scatterlist cmd_sg = {}, resp_sg = {};
> + struct scatterlist *sgs[2] = { &cmd_sg, &resp_sg };
> + struct virtio_media_session *session;
> + u32 session_id;
> + int ret;
> +
> + mutex_lock(&vv->vlock);
> +
> + sg_set_buf(&cmd_sg, cmd_open, sizeof(*cmd_open));
> + sg_mark_end(&cmd_sg);
> +
> + sg_set_buf(&resp_sg, resp_open, sizeof(*resp_open));
> + sg_mark_end(&resp_sg);
> +
> + cmd_open->hdr.cmd = VIRTIO_MEDIA_CMD_OPEN;
> + ret = virtio_media_send_command(vv, sgs, 1, 1, sizeof(*resp_open),
> + NULL);
> + session_id = resp_open->session_id;
> + mutex_unlock(&vv->vlock);
> + if (ret < 0)
> + return ret;
> +
> + session = virtio_media_session_alloc(vv, session_id,
> + (file->f_flags & O_NONBLOCK));
> + if (IS_ERR(session))
> + return PTR_ERR(session);
> +
> + file->private_data = &session->fh;
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_device_close() - Close a previously opened session.
> + * @file: file of the session to close.
> + *
> + * This sends to ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and close
> + * the session on the driver side.
> + */
> +static int virtio_media_device_close(struct file *file)
> +{
> + 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);
> +
> + 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 : 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)
> +{
> + struct device *dev = &virtio_dev->dev;
> + struct virtqueue *vqs[2];
> + static struct virtqueue_info vq_info[2] = {
> + {
> + .name = "command",
> + .callback = commandq_callback,
> + },
> + {
> + .name = "event",
> + .callback = eventq_callback,
> + },
> + };
> + struct virtio_media *vv;
> + struct video_device *vd;
> + int i;
> + int ret;
> +
> + vv = devm_kzalloc(dev, sizeof(*vv), GFP_KERNEL);
> + if (!vv)
> + return -ENOMEM;
> +
> + vv->event_buffer = devm_kzalloc(
> + dev, VIRTIO_MEDIA_EVENT_MAX_SIZE * VIRTIO_MEDIA_NUM_EVENT_BUFS,
> + GFP_KERNEL);
> + if (!vv->event_buffer)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&vv->sessions);
> + mutex_init(&vv->sessions_lock);
> + mutex_init(&vv->events_lock);
> + mutex_init(&vv->vlock);
> +
> + vv->virtio_dev = virtio_dev;
> + virtio_dev->priv = vv;
> +
> + init_waitqueue_head(&vv->wq);
> +
> + ret = v4l2_device_register(dev, &vv->v4l2_dev);
> + if (ret)
> + return ret;
> +
> + ret = virtio_find_vqs(virtio_dev, 2, vqs, vq_info, NULL);
> + if (ret)
> + goto err_find_vqs;
> +
> + vv->commandq = vqs[0];
> + 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->device_caps = virtio_cread32(virtio_dev, 0);
> + if (vd->device_caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))
> + vd->vfl_dir = VFL_DIR_M2M;
> + else if (vd->device_caps &
> + (V4L2_CAP_VIDEO_OUTPUT | V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE))
> + vd->vfl_dir = VFL_DIR_TX;
> + else
> + vd->vfl_dir = VFL_DIR_RX;
> + vd->release = video_device_release_empty;
> + strscpy(vd->name, "virtio-media", sizeof(vd->name));
> +
> + video_set_drvdata(vd, vv);
> +
> + ret = video_register_device(vd, virtio_cread32(virtio_dev, 4), 0);
> + if (ret)
> + goto err_register_device;
> +
> + for (i = 0; i < VIRTIO_MEDIA_NUM_EVENT_BUFS; i++) {
> + ret = virtio_media_send_event_buffer(
> + vv, vv->event_buffer + VIRTIO_MEDIA_EVENT_MAX_SIZE * i);
> + if (ret)
> + goto err_send_event_buffer;
> + }
> +
> + virtio_device_ready(virtio_dev);
> +
> + return 0;
> +
> +err_send_event_buffer:
> + video_unregister_device(&vv->video_dev);
> +err_register_device:
> + virtio_dev->config->del_vqs(virtio_dev);
> +err_find_vqs:
> + v4l2_device_unregister(&vv->v4l2_dev);
> +
> + return ret;
> +}
> +
> +static void virtio_media_remove(struct virtio_device *virtio_dev)
> +{
> + struct virtio_media *vv = virtio_dev->priv;
> + struct list_head *p, *n;
> +
> + cancel_work_sync(&vv->eventq_work);
> + virtio_reset_device(virtio_dev);
> +
> + v4l2_device_unregister(&vv->v4l2_dev);
> + virtio_dev->config->del_vqs(virtio_dev);
> + video_unregister_device(&vv->video_dev);
> +
> + list_for_each_safe(p, n, &vv->sessions) {
> + struct virtio_media_session *s =
> + list_entry(p, struct virtio_media_session, list);
> +
> + virtio_media_session_free(vv, s);
> + }
> +}
> +
> +static struct virtio_device_id id_table[] = {
> + { VIRTIO_ID_MEDIA, VIRTIO_DEV_ANY_ID },
> + { 0 },
> +};
> +
> +static unsigned int features[] = {};
> +
> +static struct virtio_driver virtio_media_driver = {
> + .feature_table = features,
> + .feature_table_size = ARRAY_SIZE(features),
> + .driver.name = VIRTIO_MEDIA_DEFAULT_DRIVER_NAME,
> + .driver.owner = THIS_MODULE,
> + .id_table = id_table,
> + .probe = virtio_media_probe,
> + .remove = virtio_media_remove,
> +};
> +
> +module_virtio_driver(virtio_media_driver);
> +
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +MODULE_DESCRIPTION("virtio media driver");
> +MODULE_AUTHOR("Alexandre Courbot <gnurou@gmail.com>");
> +MODULE_LICENSE("Dual BSD/GPL");
> diff --git a/drivers/media/virtio/virtio_media_ioctls.c b/drivers/media/virtio/virtio_media_ioctls.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..863cdfbaaadc7241110c82ce6880bc5675c23894
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media_ioctls.c
> @@ -0,0 +1,1297 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> +
> +/*
> + * Ioctls implementations for the virtio-media driver.
> + *
> + * Copyright (c) 2024-2025 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,
> + };
> + int ret;
> +
> + /* Command descriptor */
> + 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,
> + };
> + int ret;
> +
> + /* Command descriptor */
> + 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,
> + };
> + int ret;
> +
> + /* Command descriptor */
> + 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: ``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;
> +
> + /* Payload of SHARED_PAGES buffers, if relevant */
> + ret = scatterlist_builder_add_buffer_userptr(&builder, b);
> + if (ret < 0)
> + 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 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: ``v4l2_ext_controls`` to be sent as the ioctl payload.
> + *
> + * Queues an ioctl that sends a ``v4l2_ext_controls`` to the host and receives
> + * an updated version.
> + *
> + * ``v4l2_ext_controls`` has a pointer to an array of ``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 ret;
> + int i;
> +
> + /* Command descriptor */
> + 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;
> +
> + /* Event 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) \
> + { \
> + return virtio_media_send_wr_ioctl(fh, 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) \
> + { \
> + return virtio_media_send_r_ioctl(fh, 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) \
> + { \
> + return virtio_media_send_w_ioctl(fh, 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");
> +
> + if (!virtio_media_driver_name)
> + strscpy(cap->driver, VIRTIO_MEDIA_DEFAULT_DRIVER_NAME);
> + else
> + strscpy(cap->driver, virtio_media_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)
> +{
> + return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_G_EXT_CTRLS,
> + ctrls);
> +}
> +
> +static int virtio_media_s_ext_ctrls(struct file *file, void *fh,
> + struct v4l2_ext_controls *ctrls)
> +{
> + return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_S_EXT_CTRLS,
> + ctrls);
> +}
> +
> +static int virtio_media_try_ext_ctrls(struct file *file, void *fh,
> + struct v4l2_ext_controls *ctrls)
> +{
> + return virtio_media_send_ext_controls_ioctl(fh, 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;
> +
> + 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 virtio_media_session *session = fh_to_session(fh);
> + int ret;
> +
> + if (i > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + ret = virtio_media_send_w_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + int ret;
> +
> + if (i > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + ret = virtio_media_send_w_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + struct virtio_media_queue_state *queue;
> + int ret;
> +
> + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + if (b->memory == V4L2_MEMORY_USERPTR && !virtio_media_allow_userptr)
> + return -EINVAL;
> +
> + ret = virtio_media_send_wr_ioctl(fh, 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;
> +
> + if (!virtio_media_allow_userptr)
> + 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 virtio_media_session *session = fh_to_session(fh);
> + struct virtio_media_queue_state *queue;
> + struct virtio_media_buffer *buffer;
> + int ret;
> +
> + ret = virtio_media_send_buffer_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + 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(fh, 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->private_data);
> + 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 want to 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;
> +
> + ret = virtio_media_send_wr_ioctl(fh, 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(fh, 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;
> +
> + ret = virtio_media_send_wr_ioctl(fh, 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + int ret;
> +
> + ret = virtio_media_send_wr_ioctl(fh, 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;
> +
> + ret = virtio_media_send_w_ioctl(fh, 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->private_data;
> +
> + mutex_lock(&vv->vlock);
> +
> + /*
> + * We need to handle a few ioctls manually because their result 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;
> +}
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index 7aa2eb76620508fdc915533f74973d76308d3ef5..b4bb0ace0b26e37224c975f89bbf669c51921816 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -68,6 +68,7 @@
> #define VIRTIO_ID_AUDIO_POLICY 39 /* virtio audio policy */
> #define VIRTIO_ID_BT 40 /* virtio bluetooth */
> #define VIRTIO_ID_GPIO 41 /* virtio gpio */
> +#define VIRTIO_ID_MEDIA 48 /* virtio media */
>
> /*
> * Virtio Transitional IDs
>
> ---
> base-commit: 0af2f6be1b4281385b618cb86ad946eded089ac8
> change-id: 20241229-virtio-media-25067bb27526
>
> Best regards,
Thanks,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-26 12:13 ` Mauro Carvalho Chehab
@ 2025-05-27 6:14 ` Alexandre Courbot
2025-05-27 9:13 ` Mauro Carvalho Chehab
2025-05-27 14:23 ` Michael S. Tsirkin
1 sibling, 1 reply; 26+ messages in thread
From: Alexandre Courbot @ 2025-05-27 6:14 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Michael S. Tsirkin
Cc: Mauro Carvalho Chehab, Hans Verkuil, Albert Esteve, Jason Wang,
Xuan Zhuo, Eugenio Pérez, gurchetansingh, daniel.almeida,
adelva, changyeon, nicolas.dufresne, linux-kernel, linux-media,
virtualization, Alexandre Courbot
Hi Mauro,
On Mon May 26, 2025 at 9:13 PM JST, Mauro Carvalho Chehab wrote:
> Hi Michael,
>
> Em Sat, 12 Apr 2025 13:08:01 +0900
> Alexandre Courbot <gnurou@gmail.com> escreveu:
>
>> Add the first version of the virtio-media driver.
>>
>> This driver acts roughly as a V4L2 relay between user-space and the
>> virtio virtual device on the host, so it is relatively simple, yet
>> unconventional. It doesn't use VB2 or other frameworks typically used in
>> a V4L2 driver, and most of its complexity resides in correctly and
>> efficiently building the virtio descriptor chain to pass to the host,
>> avoiding copies whenever possible. This is done by
>> scatterlist_builder.[ch].
>>
>> virtio_media_ioctls.c proxies each supported ioctl to the host, using
>> code generated through macros for ioctls that can be forwarded directly,
>> which is most of them.
>>
>> virtio_media_driver.c provides the expected driver hooks, and support
>> for mmapping and polling.
>>
>> This version supports MMAP buffers, while USERPTR buffers can also be
>> enabled through a driver option. DMABUF support is still pending.
>
> It sounds that you applied this one at the virtio tree, but it hasn't
> being reviewed or acked by media maintainers.
>
> Please drop it.
>
> Alexandre,
>
> Please send media patches to media maintainers, c/c other subsystem
> maintainers, as otherwise they might end being merged without a
> proper review.
Sorry about that, I put everyone in "To:" without giving it a second
thought.
>
> In this particular case, we need to double-check if this won't cause
> any issues, in special with regards to media locks and mutexes.
Agreed, I am not 100% confident about that part myself.
>
> I'll try to look on it after this merge window, as it is too late
> for it to be applied during this one.
Appreciate that - given the high traffic on the list I was worried that
this patch would eventually be overlooked. Not making it for this merge
window should not be a problem, so please take the time you need.
Cheers,
Alex.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 6:14 ` Alexandre Courbot
@ 2025-05-27 9:13 ` Mauro Carvalho Chehab
2025-05-27 13:21 ` Alexandre Courbot
0 siblings, 1 reply; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-05-27 9:13 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Michael S. Tsirkin, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization,
Alexandre Courbot
Em Tue, 27 May 2025 15:14:50 +0900
"Alexandre Courbot" <gnurou@gmail.com> escreveu:
> Hi Mauro,
>
> On Mon May 26, 2025 at 9:13 PM JST, Mauro Carvalho Chehab wrote:
> > Hi Michael,
> >
> > Em Sat, 12 Apr 2025 13:08:01 +0900
> > Alexandre Courbot <gnurou@gmail.com> escreveu:
> >
> >> Add the first version of the virtio-media driver.
> >>
> >> This driver acts roughly as a V4L2 relay between user-space and the
> >> virtio virtual device on the host, so it is relatively simple, yet
> >> unconventional. It doesn't use VB2 or other frameworks typically used in
> >> a V4L2 driver, and most of its complexity resides in correctly and
> >> efficiently building the virtio descriptor chain to pass to the host,
> >> avoiding copies whenever possible. This is done by
> >> scatterlist_builder.[ch].
> >>
> >> virtio_media_ioctls.c proxies each supported ioctl to the host, using
> >> code generated through macros for ioctls that can be forwarded directly,
> >> which is most of them.
> >>
> >> virtio_media_driver.c provides the expected driver hooks, and support
> >> for mmapping and polling.
> >>
> >> This version supports MMAP buffers, while USERPTR buffers can also be
> >> enabled through a driver option. DMABUF support is still pending.
> >
> > It sounds that you applied this one at the virtio tree, but it hasn't
> > being reviewed or acked by media maintainers.
> >
> > Please drop it.
> >
> > Alexandre,
> >
> > Please send media patches to media maintainers, c/c other subsystem
> > maintainers, as otherwise they might end being merged without a
> > proper review.
>
> Sorry about that, I put everyone in "To:" without giving it a second
> thought.
>
> >
> > In this particular case, we need to double-check if this won't cause
> > any issues, in special with regards to media locks and mutexes.
>
> Agreed, I am not 100% confident about that part myself.
>
> >
> > I'll try to look on it after this merge window, as it is too late
> > for it to be applied during this one.
>
> Appreciate that - given the high traffic on the list I was worried that
> this patch would eventually be overlooked. Not making it for this merge
> window should not be a problem, so please take the time you need.
Provided that your patch was caught by patchwork, it won't be lost:
https://patchwork.linuxtv.org/project/linux-media/patch/20250412-virtio-media-v3-1-97dc94c18398@gmail.com/
Please notice that our CI got a number of checkpatch issues there.
Please check and fix the non-false-positive ones.
Btw, I was looking at:
https://github.com/chromeos/virtio-media
(I'm assuming that this is the QEMU counterpart, right?)
And I noticed something weird there:
"Unsupported ioctls
A few ioctls are replaced by other, more suitable mechanisms. If being requested these ioctls, the device must return the same response as it would for an unknown ioctl, i.e. ENOTTY.
VIDIOC_QUERYCAP is replaced by reading the configuration area.
VIDIOC_DQBUF is replaced by a dedicated event.
VIDIOC_DQEVENT is replaced by a dedicated event."
While this could be ok for cromeOS, this will be broken for guests with
Linux, as all Linux applications rely on VIDIOC_QUERYCAP and VIDIOC_DQBUF
to work. Please implement support for it, as otherwise we won't even be
able to test the driver with the v4l2-compliance tool (*).
(*) Passing at v4l2-compliance is a requirement for any media driver
to be merged.
With regards to testing, what's the expected testing scenario?
My guess is that, as a virtio device, a possible test scenario would be
to have the UVC camera from the host OS mapped using virtio-camera into
the guest OS, allowing a V4L2 application running at the guest to map the
camera from the host, right?
Regards,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 9:13 ` Mauro Carvalho Chehab
@ 2025-05-27 13:21 ` Alexandre Courbot
2025-05-27 13:35 ` Mauro Carvalho Chehab
0 siblings, 1 reply; 26+ messages in thread
From: Alexandre Courbot @ 2025-05-27 13:21 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Michael S. Tsirkin, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
On Tue, May 27, 2025 at 6:13 PM Mauro Carvalho Chehab
<mchehab+huawei@kernel.org> wrote:
>
> Em Tue, 27 May 2025 15:14:50 +0900
> "Alexandre Courbot" <gnurou@gmail.com> escreveu:
>
> > Hi Mauro,
> >
> > On Mon May 26, 2025 at 9:13 PM JST, Mauro Carvalho Chehab wrote:
> > > Hi Michael,
> > >
> > > Em Sat, 12 Apr 2025 13:08:01 +0900
> > > Alexandre Courbot <gnurou@gmail.com> escreveu:
> > >
> > >> Add the first version of the virtio-media driver.
> > >>
> > >> This driver acts roughly as a V4L2 relay between user-space and the
> > >> virtio virtual device on the host, so it is relatively simple, yet
> > >> unconventional. It doesn't use VB2 or other frameworks typically used in
> > >> a V4L2 driver, and most of its complexity resides in correctly and
> > >> efficiently building the virtio descriptor chain to pass to the host,
> > >> avoiding copies whenever possible. This is done by
> > >> scatterlist_builder.[ch].
> > >>
> > >> virtio_media_ioctls.c proxies each supported ioctl to the host, using
> > >> code generated through macros for ioctls that can be forwarded directly,
> > >> which is most of them.
> > >>
> > >> virtio_media_driver.c provides the expected driver hooks, and support
> > >> for mmapping and polling.
> > >>
> > >> This version supports MMAP buffers, while USERPTR buffers can also be
> > >> enabled through a driver option. DMABUF support is still pending.
> > >
> > > It sounds that you applied this one at the virtio tree, but it hasn't
> > > being reviewed or acked by media maintainers.
> > >
> > > Please drop it.
> > >
> > > Alexandre,
> > >
> > > Please send media patches to media maintainers, c/c other subsystem
> > > maintainers, as otherwise they might end being merged without a
> > > proper review.
> >
> > Sorry about that, I put everyone in "To:" without giving it a second
> > thought.
> >
> > >
> > > In this particular case, we need to double-check if this won't cause
> > > any issues, in special with regards to media locks and mutexes.
> >
> > Agreed, I am not 100% confident about that part myself.
> >
> > >
> > > I'll try to look on it after this merge window, as it is too late
> > > for it to be applied during this one.
> >
> > Appreciate that - given the high traffic on the list I was worried that
> > this patch would eventually be overlooked. Not making it for this merge
> > window should not be a problem, so please take the time you need.
>
> Provided that your patch was caught by patchwork, it won't be lost:
> https://patchwork.linuxtv.org/project/linux-media/patch/20250412-virtio-media-v3-1-97dc94c18398@gmail.com/
>
> Please notice that our CI got a number of checkpatch issues there.
> Please check and fix the non-false-positive ones.
Will do. The macro-related ones are false-positives AFAICT. Some of
the "lines should not end with a '('" are actually the result of
applying clang-format with the kernel-provided style...
>
> Btw, I was looking at:
>
> https://github.com/chromeos/virtio-media
>
> (I'm assuming that this is the QEMU counterpart, right?)
crosvm actually, but QEMU support is also being worked on.
>
> And I noticed something weird there:
>
> "Unsupported ioctls
>
> A few ioctls are replaced by other, more suitable mechanisms. If being requested these ioctls, the device must return the same response as it would for an unknown ioctl, i.e. ENOTTY.
>
> VIDIOC_QUERYCAP is replaced by reading the configuration area.
> VIDIOC_DQBUF is replaced by a dedicated event.
> VIDIOC_DQEVENT is replaced by a dedicated event."
>
> While this could be ok for cromeOS, this will be broken for guests with
> Linux, as all Linux applications rely on VIDIOC_QUERYCAP and VIDIOC_DQBUF
> to work. Please implement support for it, as otherwise we won't even be
> able to test the driver with the v4l2-compliance tool (*).
The phrasing was a bit confusing. The guest driver does support these
ioctls, and passes v4l2-compliance. So there is no problem here.
Where these ioctls are not supported is between the guest and the
host, i.e. as ioctls encapsulated into a virtio request. For QUERYCAP,
that's because the virtio configuration area already provides the same
information. For DQBUF and DQEVENT, that's because we want to serve
these asynchronously for performance reasons (hence the dedicated
events).
But these differences don't affect guest user applications which will
be able to perform these ioctls exactly as they expect.
>
> (*) Passing at v4l2-compliance is a requirement for any media driver
> to be merged.
>
> With regards to testing, what's the expected testing scenario?
> My guess is that, as a virtio device, a possible test scenario would be
> to have the UVC camera from the host OS mapped using virtio-camera into
> the guest OS, allowing a V4L2 application running at the guest to map the
> camera from the host, right?
That's one of the scenarios, yes. Another one is to expose an
accelerated decoder on the host to the guest. One can also create
"fake" devices backed by software on the host for testing purposes.
That's actually the benefit of using V4L2 as a guest/host protocol:
the same guest and the same software stack on the host can be used to
virtualize multiple kinds of media devices, removing the need to have
e.g. virtio-camera and virtio-codec. This removes a ton of work (and
code).
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 13:21 ` Alexandre Courbot
@ 2025-05-27 13:35 ` Mauro Carvalho Chehab
2025-05-27 14:03 ` Alexandre Courbot
0 siblings, 1 reply; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-05-27 13:35 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Michael S. Tsirkin, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Em Tue, 27 May 2025 22:21:42 +0900
Alexandre Courbot <gnurou@gmail.com> escreveu:
> On Tue, May 27, 2025 at 6:13 PM Mauro Carvalho Chehab
> <mchehab+huawei@kernel.org> wrote:
> >
> > Em Tue, 27 May 2025 15:14:50 +0900
> > "Alexandre Courbot" <gnurou@gmail.com> escreveu:
> >
> > > Hi Mauro,
> > >
> > > On Mon May 26, 2025 at 9:13 PM JST, Mauro Carvalho Chehab wrote:
> > > > Hi Michael,
> > > >
> > > > Em Sat, 12 Apr 2025 13:08:01 +0900
> > > > Alexandre Courbot <gnurou@gmail.com> escreveu:
> > > >
> > > >> Add the first version of the virtio-media driver.
> > > >>
> > > >> This driver acts roughly as a V4L2 relay between user-space and the
> > > >> virtio virtual device on the host, so it is relatively simple, yet
> > > >> unconventional. It doesn't use VB2 or other frameworks typically used in
> > > >> a V4L2 driver, and most of its complexity resides in correctly and
> > > >> efficiently building the virtio descriptor chain to pass to the host,
> > > >> avoiding copies whenever possible. This is done by
> > > >> scatterlist_builder.[ch].
> > > >>
> > > >> virtio_media_ioctls.c proxies each supported ioctl to the host, using
> > > >> code generated through macros for ioctls that can be forwarded directly,
> > > >> which is most of them.
> > > >>
> > > >> virtio_media_driver.c provides the expected driver hooks, and support
> > > >> for mmapping and polling.
> > > >>
> > > >> This version supports MMAP buffers, while USERPTR buffers can also be
> > > >> enabled through a driver option. DMABUF support is still pending.
> > > >
> > > > It sounds that you applied this one at the virtio tree, but it hasn't
> > > > being reviewed or acked by media maintainers.
> > > >
> > > > Please drop it.
> > > >
> > > > Alexandre,
> > > >
> > > > Please send media patches to media maintainers, c/c other subsystem
> > > > maintainers, as otherwise they might end being merged without a
> > > > proper review.
> > >
> > > Sorry about that, I put everyone in "To:" without giving it a second
> > > thought.
> > >
> > > >
> > > > In this particular case, we need to double-check if this won't cause
> > > > any issues, in special with regards to media locks and mutexes.
> > >
> > > Agreed, I am not 100% confident about that part myself.
> > >
> > > >
> > > > I'll try to look on it after this merge window, as it is too late
> > > > for it to be applied during this one.
> > >
> > > Appreciate that - given the high traffic on the list I was worried that
> > > this patch would eventually be overlooked. Not making it for this merge
> > > window should not be a problem, so please take the time you need.
> >
> > Provided that your patch was caught by patchwork, it won't be lost:
> > https://patchwork.linuxtv.org/project/linux-media/patch/20250412-virtio-media-v3-1-97dc94c18398@gmail.com/
> >
> > Please notice that our CI got a number of checkpatch issues there.
> > Please check and fix the non-false-positive ones.
>
> Will do. The macro-related ones are false-positives AFAICT. Some of
> the "lines should not end with a '('" are actually the result of
> applying clang-format with the kernel-provided style...
I don't know any lint tool that honors kernel style. The best one
is checkpatch with the auto-correcting parameter in pedantic mode,
but still one needs to manually review its output.
>
> >
> > Btw, I was looking at:
> >
> > https://github.com/chromeos/virtio-media
> >
> > (I'm assuming that this is the QEMU counterpart, right?)
>
> crosvm actually, but QEMU support is also being worked on.
Do you have already QEMU patches? The best is to have the Kernel driver
submitted altogether with QEMU, as Kernel developers need it to do the
tests. In my case, I never use crosvm, and I don't have any Chromebook
anymore.
> > And I noticed something weird there:
> >
> > "Unsupported ioctls
> >
> > A few ioctls are replaced by other, more suitable mechanisms. If being requested these ioctls, the device must return the same response as it would for an unknown ioctl, i.e. ENOTTY.
> >
> > VIDIOC_QUERYCAP is replaced by reading the configuration area.
> > VIDIOC_DQBUF is replaced by a dedicated event.
> > VIDIOC_DQEVENT is replaced by a dedicated event."
> >
> > While this could be ok for cromeOS, this will be broken for guests with
> > Linux, as all Linux applications rely on VIDIOC_QUERYCAP and VIDIOC_DQBUF
> > to work. Please implement support for it, as otherwise we won't even be
> > able to test the driver with the v4l2-compliance tool (*).
>
> The phrasing was a bit confusing. The guest driver does support these
> ioctls, and passes v4l2-compliance. So there is no problem here.
Please add v4l2-compliance output on the next patch series.
> Where these ioctls are not supported is between the guest and the
> host, i.e. as ioctls encapsulated into a virtio request. For QUERYCAP,
> that's because the virtio configuration area already provides the same
> information. For DQBUF and DQEVENT, that's because we want to serve
> these asynchronously for performance reasons (hence the dedicated
> events).
>
> But these differences don't affect guest user applications which will
> be able to perform these ioctls exactly as they expect.
OK. Better to let it clear then at the documentation.
> >
> > (*) Passing at v4l2-compliance is a requirement for any media driver
> > to be merged.
> >
> > With regards to testing, what's the expected testing scenario?
> > My guess is that, as a virtio device, a possible test scenario would be
> > to have the UVC camera from the host OS mapped using virtio-camera into
> > the guest OS, allowing a V4L2 application running at the guest to map the
> > camera from the host, right?
>
> That's one of the scenarios, yes.
Ok, this is the easiest test scenario for media developers.
> Another one is to expose an accelerated decoder on the host to the guest.
> One can also create
> "fake" devices backed by software on the host for testing purposes.
That sounds interesting. It probably makes sense to have some test
scenario using such fake devices plus v4l2-compliance test to check
for regressions running on some CI engine.
> That's actually the benefit of using V4L2 as a guest/host protocol:
> the same guest and the same software stack on the host can be used to
> virtualize multiple kinds of media devices, removing the need to have
> e.g. virtio-camera and virtio-codec. This removes a ton of work (and
> code).
Makes sense.
Regards,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 13:35 ` Mauro Carvalho Chehab
@ 2025-05-27 14:03 ` Alexandre Courbot
2025-05-27 14:42 ` Mauro Carvalho Chehab
2025-06-17 8:49 ` Mauro Carvalho Chehab
0 siblings, 2 replies; 26+ messages in thread
From: Alexandre Courbot @ 2025-05-27 14:03 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Albert Esteve
Cc: Michael S. Tsirkin, Mauro Carvalho Chehab, Hans Verkuil,
Jason Wang, Xuan Zhuo, Eugenio Pérez, gurchetansingh,
daniel.almeida, adelva, changyeon, nicolas.dufresne, linux-kernel,
linux-media, virtualization
On Tue, May 27, 2025 at 10:35 PM Mauro Carvalho Chehab
<mchehab+huawei@kernel.org> wrote:
>
> Em Tue, 27 May 2025 22:21:42 +0900
> Alexandre Courbot <gnurou@gmail.com> escreveu:
>
> > On Tue, May 27, 2025 at 6:13 PM Mauro Carvalho Chehab
> > <mchehab+huawei@kernel.org> wrote:
> > >
> > > Em Tue, 27 May 2025 15:14:50 +0900
> > > "Alexandre Courbot" <gnurou@gmail.com> escreveu:
> > >
> > > > Hi Mauro,
> > > >
> > > > On Mon May 26, 2025 at 9:13 PM JST, Mauro Carvalho Chehab wrote:
> > > > > Hi Michael,
> > > > >
> > > > > Em Sat, 12 Apr 2025 13:08:01 +0900
> > > > > Alexandre Courbot <gnurou@gmail.com> escreveu:
> > > > >
> > > > >> Add the first version of the virtio-media driver.
> > > > >>
> > > > >> This driver acts roughly as a V4L2 relay between user-space and the
> > > > >> virtio virtual device on the host, so it is relatively simple, yet
> > > > >> unconventional. It doesn't use VB2 or other frameworks typically used in
> > > > >> a V4L2 driver, and most of its complexity resides in correctly and
> > > > >> efficiently building the virtio descriptor chain to pass to the host,
> > > > >> avoiding copies whenever possible. This is done by
> > > > >> scatterlist_builder.[ch].
> > > > >>
> > > > >> virtio_media_ioctls.c proxies each supported ioctl to the host, using
> > > > >> code generated through macros for ioctls that can be forwarded directly,
> > > > >> which is most of them.
> > > > >>
> > > > >> virtio_media_driver.c provides the expected driver hooks, and support
> > > > >> for mmapping and polling.
> > > > >>
> > > > >> This version supports MMAP buffers, while USERPTR buffers can also be
> > > > >> enabled through a driver option. DMABUF support is still pending.
> > > > >
> > > > > It sounds that you applied this one at the virtio tree, but it hasn't
> > > > > being reviewed or acked by media maintainers.
> > > > >
> > > > > Please drop it.
> > > > >
> > > > > Alexandre,
> > > > >
> > > > > Please send media patches to media maintainers, c/c other subsystem
> > > > > maintainers, as otherwise they might end being merged without a
> > > > > proper review.
> > > >
> > > > Sorry about that, I put everyone in "To:" without giving it a second
> > > > thought.
> > > >
> > > > >
> > > > > In this particular case, we need to double-check if this won't cause
> > > > > any issues, in special with regards to media locks and mutexes.
> > > >
> > > > Agreed, I am not 100% confident about that part myself.
> > > >
> > > > >
> > > > > I'll try to look on it after this merge window, as it is too late
> > > > > for it to be applied during this one.
> > > >
> > > > Appreciate that - given the high traffic on the list I was worried that
> > > > this patch would eventually be overlooked. Not making it for this merge
> > > > window should not be a problem, so please take the time you need.
> > >
> > > Provided that your patch was caught by patchwork, it won't be lost:
> > > https://patchwork.linuxtv.org/project/linux-media/patch/20250412-virtio-media-v3-1-97dc94c18398@gmail.com/
> > >
> > > Please notice that our CI got a number of checkpatch issues there.
> > > Please check and fix the non-false-positive ones.
> >
> > Will do. The macro-related ones are false-positives AFAICT. Some of
> > the "lines should not end with a '('" are actually the result of
> > applying clang-format with the kernel-provided style...
>
> I don't know any lint tool that honors kernel style. The best one
> is checkpatch with the auto-correcting parameter in pedantic mode,
> but still one needs to manually review its output.
>
> >
> > >
> > > Btw, I was looking at:
> > >
> > > https://github.com/chromeos/virtio-media
> > >
> > > (I'm assuming that this is the QEMU counterpart, right?)
> >
> > crosvm actually, but QEMU support is also being worked on.
>
> Do you have already QEMU patches? The best is to have the Kernel driver
> submitted altogether with QEMU, as Kernel developers need it to do the
> tests. In my case, I never use crosvm, and I don't have any Chromebook
> anymore.
IIRC Albert Esteve was working on this, maybe he can share the current status.
Note that crosvm does not require a Chromebook, you can build and run
it pretty easily on a regular PC. I have put together a document to
help with that:
https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
It also shows how to proxy a host UVC camera into the guest and use
some of the "fake" devices I talked about earlier. If following these
steps is too much friction, just reading through the document should
give you a decent idea of what virtio-media can do.
>
> > > And I noticed something weird there:
> > >
> > > "Unsupported ioctls
> > >
> > > A few ioctls are replaced by other, more suitable mechanisms. If being requested these ioctls, the device must return the same response as it would for an unknown ioctl, i.e. ENOTTY.
> > >
> > > VIDIOC_QUERYCAP is replaced by reading the configuration area.
> > > VIDIOC_DQBUF is replaced by a dedicated event.
> > > VIDIOC_DQEVENT is replaced by a dedicated event."
> > >
> > > While this could be ok for cromeOS, this will be broken for guests with
> > > Linux, as all Linux applications rely on VIDIOC_QUERYCAP and VIDIOC_DQBUF
> > > to work. Please implement support for it, as otherwise we won't even be
> > > able to test the driver with the v4l2-compliance tool (*).
> >
> > The phrasing was a bit confusing. The guest driver does support these
> > ioctls, and passes v4l2-compliance. So there is no problem here.
>
> Please add v4l2-compliance output on the next patch series.
Certainly. Note that the output is dependent on what the host provides
for a device. If it e.g. proxies something that is not fully
compliant, the guest will reflect the same errors.
>
> > Where these ioctls are not supported is between the guest and the
> > host, i.e. as ioctls encapsulated into a virtio request. For QUERYCAP,
> > that's because the virtio configuration area already provides the same
> > information. For DQBUF and DQEVENT, that's because we want to serve
> > these asynchronously for performance reasons (hence the dedicated
> > events).
> >
> > But these differences don't affect guest user applications which will
> > be able to perform these ioctls exactly as they expect.
>
> OK. Better to let it clear then at the documentation.
>
> > >
> > > (*) Passing at v4l2-compliance is a requirement for any media driver
> > > to be merged.
> > >
> > > With regards to testing, what's the expected testing scenario?
> > > My guess is that, as a virtio device, a possible test scenario would be
> > > to have the UVC camera from the host OS mapped using virtio-camera into
> > > the guest OS, allowing a V4L2 application running at the guest to map the
> > > camera from the host, right?
> >
> > That's one of the scenarios, yes.
>
> Ok, this is the easiest test scenario for media developers.
>
> > Another one is to expose an accelerated decoder on the host to the guest.
>
> > One can also create
> > "fake" devices backed by software on the host for testing purposes.
>
> That sounds interesting. It probably makes sense to have some test
> scenario using such fake devices plus v4l2-compliance test to check
> for regressions running on some CI engine.
Yes, regression catching in a CI is one of the use-cases we thought about.
>
> > That's actually the benefit of using V4L2 as a guest/host protocol:
> > the same guest and the same software stack on the host can be used to
I made a typo here: "the same guest DRIVER". That's an important point. :)
Cheers,
Alex.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-26 12:13 ` Mauro Carvalho Chehab
2025-05-27 6:14 ` Alexandre Courbot
@ 2025-05-27 14:23 ` Michael S. Tsirkin
2025-05-27 14:39 ` Mauro Carvalho Chehab
1 sibling, 1 reply; 26+ messages in thread
From: Michael S. Tsirkin @ 2025-05-27 14:23 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Alexandre Courbot, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization,
Alexandre Courbot
On Mon, May 26, 2025 at 02:13:16PM +0200, Mauro Carvalho Chehab wrote:
> Hi Michael,
>
> Em Sat, 12 Apr 2025 13:08:01 +0900
> Alexandre Courbot <gnurou@gmail.com> escreveu:
>
> > Add the first version of the virtio-media driver.
> >
> > This driver acts roughly as a V4L2 relay between user-space and the
> > virtio virtual device on the host, so it is relatively simple, yet
> > unconventional. It doesn't use VB2 or other frameworks typically used in
> > a V4L2 driver, and most of its complexity resides in correctly and
> > efficiently building the virtio descriptor chain to pass to the host,
> > avoiding copies whenever possible. This is done by
> > scatterlist_builder.[ch].
> >
> > virtio_media_ioctls.c proxies each supported ioctl to the host, using
> > code generated through macros for ioctls that can be forwarded directly,
> > which is most of them.
> >
> > virtio_media_driver.c provides the expected driver hooks, and support
> > for mmapping and polling.
> >
> > This version supports MMAP buffers, while USERPTR buffers can also be
> > enabled through a driver option. DMABUF support is still pending.
>
> It sounds that you applied this one at the virtio tree, but it hasn't
> being reviewed or acked by media maintainers.
>
> Please drop it.
>
> Alexandre,
>
> Please send media patches to media maintainers, c/c other subsystem
> maintainers, as otherwise they might end being merged without a
> proper review.
>
> In this particular case, we need to double-check if this won't cause
> any issues, in special with regards to media locks and mutexes.
>
> I'll try to look on it after this merge window, as it is too late
> for it to be applied during this one.
>
> Regards,
> Mauro
New drivers generally can be merged during the merge window,
especially early. It's up to you though.
I can keep it in next for now, so it gets some coverage by
tools scanning that tree.
> >
> > Signed-off-by: Alexandre Courbot <acourbot@google.com>
> > Signed-off-by: Alexandre Courbot <gnurou@gmail.com>
> > ---
> > This patch adds the virtio-media kernel driver. Virtio-media [1]
> > encapsulates the V4L2 structures and protocol to enable the
> > virtualization of host media devices into a guest. It's specification is
> > in the final stages [2] of being merged and the virtualization of
> > cameras and video accelerator devices has already been demonstrated
> > using crosvm [3] and QEmu. v4l2-compliance also passes on all tested
> > devices, which includes the "simple" virtual test device, proxied host
> > UVC and vivid devices, and the FFmpeg virtual decoder devices (refer to
> > [3] in order to test these if desired).
> >
> > Virtio-media is merged in AOSP [4] and ChromeOS. Upstreaming of the
> > driver is overdue, but I hope we can start the review process and
> > converge into something that can be merged.
> >
> > Limitations:
> >
> > - The driver is currently only available to little-endian, 64-bit
> > kernels. This is because some of the V4L2 structures used for
> > communication between guest and host have a layout dependent on the
> > architecture, and the virtio-media protocol is standardized on the
> > little-endian 64-bit versions. This can be fixed with a conversion
> > layer similar to the one used to convert 32-bit ioctls to their 64-bit
> > counterpart.
> > - DMABUF support is currently missing. It should be implemented using
> > virtio objects, with possible support for memfds using the
> > SHARED_PAGES memory type.
> > - No support for the media API and requests. While the use-case for
> > these is less important on virtual devices where we want to present an
> > abstraction as high as possible to limit VM exits, they do exist and
> > it would be nice to add behind a virtio feature bit.
> > - Locking in the driver is still very basic. This is something I want to
> > improve before merging, but I didn't want to delay upstream review any
> > further.
> >
> > [1] https://github.com/chromeos/virtio-media
> > [2] https://lore.kernel.org/virtio-comment/20250304130134.1856056-1-aesteve@redhat.com/
> > [3] https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
> > [4] https://android.googlesource.com/platform/external/virtio-media/
> > ---
> > Changes in v3:
> > - Rebased on top of v6.15-rc1 and removes obsolete control callbacks.
> > - Link to v2: https://lore.kernel.org/r/20250201-virtio-media-v2-1-ac840681452d@gmail.com
> >
> > Changes in v2:
> > - Fixed kernel test robot and media CI warnings (ignored a few false
> > positives).
> > - Changed in-driver email address to personal one since my Google one
> > will soon become invalid.
> > - Link to v1: https://lore.kernel.org/r/20250123-virtio-media-v1-1-81e2549b86b9@gmail.com
> > ---
> > MAINTAINERS | 6 +
> > drivers/media/Kconfig | 13 +
> > drivers/media/Makefile | 2 +
> > drivers/media/virtio/Makefile | 9 +
> > drivers/media/virtio/protocol.h | 288 ++++++
> > drivers/media/virtio/scatterlist_builder.c | 563 ++++++++++++
> > drivers/media/virtio/scatterlist_builder.h | 111 +++
> > drivers/media/virtio/session.h | 109 +++
> > drivers/media/virtio/virtio_media.h | 93 ++
> > drivers/media/virtio/virtio_media_driver.c | 959 ++++++++++++++++++++
> > drivers/media/virtio/virtio_media_ioctls.c | 1297 ++++++++++++++++++++++++++++
> > include/uapi/linux/virtio_ids.h | 1 +
> > 12 files changed, 3451 insertions(+)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 96b82704950184bd71623ff41fc4df31e4c7fe87..f60e17011124fe8c0be0343d4f87e1458f311dcc 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -25641,6 +25641,12 @@ S: Maintained
> > F: drivers/iommu/virtio-iommu.c
> > F: include/uapi/linux/virtio_iommu.h
> >
> > +VIRTIO MEDIA DRIVER
> > +M: Alexandre Courbot <gnurou@gmail.com>
> > +L: linux-media@vger.kernel.org
> > +S: Maintained
> > +F: drivers/media/virtio/
> > +
> > VIRTIO MEM DRIVER
> > M: David Hildenbrand <david@redhat.com>
> > L: virtualization@lists.linux.dev
> > diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
> > index 6abc9302cd84d8563b7877d3d3da4b7e05a6b5d2..12bbb169c0b04565271092c7ac608b0fb11c0244 100644
> > --- a/drivers/media/Kconfig
> > +++ b/drivers/media/Kconfig
> > @@ -230,6 +230,19 @@ source "drivers/media/platform/Kconfig"
> > source "drivers/media/mmc/Kconfig"
> > endif
> >
> > +config MEDIA_VIRTIO
> > + tristate "Virtio-media Driver"
> > + depends on VIRTIO && VIDEO_DEV && 64BIT && (X86 || (ARM && CPU_LITTLE_ENDIAN))
> > + select VIDEOBUF2_CORE
> > + select VIDEOBUF2_MEMOPS
> > + help
> > + Enables the virtio-media driver.
> > +
> > + This driver is used to virtualize media devices such as cameras or
> > + decoders from a host into a guest using the V4L2 protocol.
> > +
> > + If unsure, say N.
> > +
> > if MEDIA_TEST_SUPPORT
> > source "drivers/media/test-drivers/Kconfig"
> > endif
> > diff --git a/drivers/media/Makefile b/drivers/media/Makefile
> > index 20fac24e4f0f13134c12cd859141c8b0387030fa..7a1377661919701f27f4fa2b5ee2dcb1045deb3c 100644
> > --- a/drivers/media/Makefile
> > +++ b/drivers/media/Makefile
> > @@ -25,6 +25,8 @@ obj-y += rc/
> >
> > obj-$(CONFIG_CEC_CORE) += cec/
> >
> > +obj-$(CONFIG_MEDIA_VIRTIO) += virtio/
> > +
> > #
> > # Finally, merge the drivers that require the core
> > #
> > diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..16f91304420d70e1212cc46f3b12f314a510c051
> > --- /dev/null
> > +++ b/drivers/media/virtio/Makefile
> > @@ -0,0 +1,9 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +#
> > +# Makefile for the virtio-media device driver.
> > +
> > +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/protocol.h b/drivers/media/virtio/protocol.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..a22758cda5aabe75c5c94ce8d1b40583c8652710
> > --- /dev/null
> > +++ b/drivers/media/virtio/protocol.h
> > @@ -0,0 +1,288 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> > +
> > +/*
> > + * Definitions of virtio-media protocol structures.
> > + *
> > + * Copyright (c) 2024-2025 Google LLC.
> > + */
> > +
> > +#ifndef __VIRTIO_MEDIA_PROTOCOL_H
> > +#define __VIRTIO_MEDIA_PROTOCOL_H
> > +
> > +#include <linux/videodev2.h>
> > +
> > +/*
> > + * Virtio protocol definition.
> > + */
> > +
> > +/**
> > + * struct virtio_media_cmd_header - Header for all virtio-media commands.
> > + * @cmd: one of VIRTIO_MEDIA_CMD_*.
> > + * @__reserved: must be set to zero by the driver.
> > + *
> > + * This header starts all commands from the driver to the device on the
> > + * commandq.
> > + */
> > +struct virtio_media_cmd_header {
> > + u32 cmd;
> > + u32 __reserved;
> > +};
> > +
> > +/**
> > + * struct virtio_media_resp_header - Header for all virtio-media responses.
> > + * @status: 0 if the command was successful, or one of the standard Linux error
> > + * codes.
> > + * @__reserved: must be set to zero by the device.
> > + *
> > + * This header starts all responses from the device to the driver on the
> > + * commandq.
> > + */
> > +struct virtio_media_resp_header {
> > + u32 status;
> > + u32 __reserved;
> > +};
> > +
> > +/**
> > + * VIRTIO_MEDIA_CMD_OPEN - Command for creating a new session.
> > + *
> > + * This is the equivalent of calling `open` on a V4L2 device node. Upon
> > + * success, a session id is returned which can be used to perform other
> > + * commands on the session, notably ioctls.
> > + */
> > +#define VIRTIO_MEDIA_CMD_OPEN 1
> > +
> > +/**
> > + * struct virtio_media_cmd_open - Driver command for VIRTIO_MEDIA_CMD_OPEN.
> > + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_OPEN.
> > + */
> > +struct virtio_media_cmd_open {
> > + struct virtio_media_cmd_header hdr;
> > +};
> > +
> > +/**
> > + * struct virtio_media_resp_open - Device response for VIRTIO_MEDIA_CMD_OPEN.
> > + * @hdr: header containing the status of the command.
> > + * @session_id: if hdr.status == 0, contains the id of the newly created session.
> > + * @__reserved: must be set to zero by the device.
> > + */
> > +struct virtio_media_resp_open {
> > + struct virtio_media_resp_header hdr;
> > + u32 session_id;
> > + u32 __reserved;
> > +};
> > +
> > +/**
> > + * VIRTIO_MEDIA_CMD_CLOSE - Command for closing an active session.
> > + *
> > + * This is the equivalent of calling `close` on a previously opened V4L2
> > + * session. All resources associated with this session will be freed and the
> > + * session ID shall not be used again after queueing this command.
> > + *
> > + * This command does not require a response from the device.
> > + */
> > +#define VIRTIO_MEDIA_CMD_CLOSE 2
> > +
> > +/**
> > + * struct virtio_media_cmd_close - Driver command for VIRTIO_MEDIA_CMD_CLOSE.
> > + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_CLOSE.
> > + * @session_id: id of the session to close.
> > + * @__reserved: must be set to zero by the driver.
> > + */
> > +struct virtio_media_cmd_close {
> > + struct virtio_media_cmd_header hdr;
> > + u32 session_id;
> > + u32 __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;
> > + u32 session_id;
> > + u32 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;
> > +};
> > +
> > +/**
> > + * struct virtio_media_sg_entry - Description of part of a scattered guest memory.
> > + * @start: start guest address of the memory segment.
> > + * @len: length of this memory segment.
> > + * @__reserved: must be set to zero by the driver.
> > + */
> > +struct virtio_media_sg_entry {
> > + u64 start;
> > + u32 len;
> > + u32 __reserved;
> > +};
> > +
> > +/**
> > + * enum virtio_media_memory - Memory types supported by virtio-media.
> > + * @VIRTIO_MEDIA_MMAP: memory allocated and managed by device. Can be mapped
> > + * into the guest using VIRTIO_MEDIA_CMD_MMAP.
> > + * @VIRTIO_MEDIA_SHARED_PAGES: memory allocated by the driver. Passed to the
> > + * device using virtio_media_sg_entry.
> > + * @VIRTIO_MEDIA_OBJECT: memory backed by a virtio object.
> > + */
> > +enum virtio_media_memory {
> > + VIRTIO_MEDIA_MMAP = V4L2_MEMORY_MMAP,
> > + VIRTIO_MEDIA_SHARED_PAGES = V4L2_MEMORY_USERPTR,
> > + VIRTIO_MEDIA_OBJECT = V4L2_MEMORY_DMABUF,
> > +};
> > +
> > +#define VIRTIO_MEDIA_MMAP_FLAG_RW (1 << 0)
> > +
> > +/**
> > + * VIRTIO_MEDIA_CMD_MMAP - Command for mapping a MMAP buffer into the driver's
> > + * address space.
> > + *
> > + */
> > +#define VIRTIO_MEDIA_CMD_MMAP 4
> > +
> > +/**
> > + * struct virtio_media_cmd_mmap - Driver command for VIRTIO_MEDIA_CMD_MMAP.
> > + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_MMAP.
> > + * @session_id: ID of the session we are mapping for.
> > + * @flags: combination of VIRTIO_MEDIA_MMAP_FLAG_*.
> > + * @offset: mem_offset field of the plane to map, as returned by VIDIOC_QUERYBUF.
> > + */
> > +struct virtio_media_cmd_mmap {
> > + struct virtio_media_cmd_header hdr;
> > + u32 session_id;
> > + u32 flags;
> > + u32 offset;
> > +};
> > +
> > +/**
> > + * struct virtio_media_resp_mmap - Device response for VIRTIO_MEDIA_CMD_MMAP.
> > + * @hdr: header containing the status of the command.
> > + * @driver_addr: offset into SHM region 0 of the start of the mapping.
> > + * @len: length of the mapping.
> > + */
> > +struct virtio_media_resp_mmap {
> > + struct virtio_media_resp_header hdr;
> > + u64 driver_addr;
> > + u64 len;
> > +};
> > +
> > +/**
> > + * VIRTIO_MEDIA_CMD_MUNMAP - Unmap a MMAP buffer previously mapped using
> > + * VIRTIO_MEDIA_CMD_MMAP.
> > + */
> > +#define VIRTIO_MEDIA_CMD_MUNMAP 5
> > +
> > +/**
> > + * struct virtio_media_cmd_munmap - Driver command for VIRTIO_MEDIA_CMD_MUNMAP.
> > + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_MUNMAP.
> > + * @driver_addr: offset into SHM region 0 at which the buffer has been previously
> > + * mapped.
> > + */
> > +struct virtio_media_cmd_munmap {
> > + struct virtio_media_cmd_header hdr;
> > + u64 driver_addr;
> > +};
> > +
> > +/**
> > + * struct virtio_media_resp_munmap - Device response for VIRTIO_MEDIA_CMD_MUNMAP.
> > + * @hdr: header containing the status of the command.
> > + */
> > +struct virtio_media_resp_munmap {
> > + struct virtio_media_resp_header hdr;
> > +};
> > +
> > +#define VIRTIO_MEDIA_EVT_ERROR 0
> > +#define VIRTIO_MEDIA_EVT_DQBUF 1
> > +#define VIRTIO_MEDIA_EVT_EVENT 2
> > +
> > +/**
> > + * struct virtio_media_event_header - Header for events on the eventq.
> > + * @event: one of VIRTIO_MEDIA_EVT_*
> > + * @session_id: ID of the session the event applies to.
> > + */
> > +struct virtio_media_event_header {
> > + u32 event;
> > + u32 session_id;
> > +};
> > +
> > +/**
> > + * struct virtio_media_event_error - Unrecoverable device-side error.
> > + * @hdr: header for the event.
> > + * @errno: error code describing the kind of error that occurred.
> > + * @__reserved: must to set to zero by the device.
> > + *
> > + * Upon receiving this event, the session mentioned in the header is considered
> > + * corrupted and closed.
> > + *
> > + */
> > +struct virtio_media_event_error {
> > + struct virtio_media_event_header hdr;
> > + u32 errno;
> > + u32 __reserved;
> > +};
> > +
> > +#define VIRTIO_MEDIA_MAX_PLANES VIDEO_MAX_PLANES
> > +
> > +/**
> > + * struct virtio_media_event_dqbuf - Dequeued buffer event.
> > + * @hdr: header for the event.
> > + * @buffer: struct v4l2_buffer describing the buffer that has been dequeued.
> > + * @planes: plane information for the dequeued buffer.
> > + *
> > + * This event is used to signal that a buffer is not being used anymore by the
> > + * device and is returned to the driver.
> > + */
> > +struct virtio_media_event_dqbuf {
> > + struct virtio_media_event_header hdr;
> > + struct v4l2_buffer buffer;
> > + struct v4l2_plane planes[VIRTIO_MEDIA_MAX_PLANES];
> > +};
> > +
> > +/**
> > + * struct virtio_media_event_event - V4L2 event.
> > + * @hdr: header for the event.
> > + * @event: description of the event that occurred.
> > + *
> > + * This event signals that a V4L2 event has been emitted for a session.
> > + */
> > +struct virtio_media_event_event {
> > + struct virtio_media_event_header hdr;
> > + struct v4l2_event event;
> > +};
> > +
> > +/* Maximum size of an event. We will queue descriptors of this size on the eventq. */
> > +#define VIRTIO_MEDIA_EVENT_MAX_SIZE sizeof(struct virtio_media_event_dqbuf)
> > +
> > +#endif // __VIRTIO_MEDIA_PROTOCOL_H
> > diff --git a/drivers/media/virtio/scatterlist_builder.c b/drivers/media/virtio/scatterlist_builder.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..2837689f385e81c0c0a99ffd67ac583b426bf186
> > --- /dev/null
> > +++ b/drivers/media/virtio/scatterlist_builder.c
> > @@ -0,0 +1,563 @@
> > +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> > +
> > +/*
> > + * Scatterlist builder helpers for virtio-media.
> > + *
> > + * Copyright (c) 2024-2025 Google LLC.
> > + */
> > +
> > +#include <linux/moduleparam.h>
> > +#include <linux/scatterlist.h>
> > +#include <linux/videodev2.h>
> > +#include <media/videobuf2-memops.h>
> > +
> > +#include "protocol.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 ``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 + 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)) {
> > + int prev_pfn = -2;
> > +
> > + /*
> > + * 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);
> > + int 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 (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;
> > + }
> > + } 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;
> > + }
> > +
> > + 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 + VIRTIO_SHADOW_BUF_SIZE) {
> > + if (kaddr + sg->length >= shadow_buf + VIRTIO_SHADOW_BUF_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->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));
> > +}
> > +
> > +/**
> > + * __scatterlist_builder_add_userptr() - Add user pages to @builder.
> > + * @builder: builder to use.
> > + * @userptr: pointer to userspace memory that we want to add.
> > + * @length: length of the data to add.
> > + * @sg_list: output parameter. Upon success, points to the area of the shadow
> > + * buffer containing the array of SG entries to be added to the descriptor
> > + * chain.
> > + * @nents: output parameter. Upon success, contains the number of entries
> > + * pointed to by @sg_list.
> > + *
> > + * Data referenced by userspace pointers can be potentially large and very
> > + * scattered, which could overwhelm the descriptor chain if added as-is. For
> > + * these, we instead build an array of ``struct virtio_media_sg_entry`` in the
> > + * shadow buffer and reference it using a single descriptor.
> > + *
> > + * This function is a helper to perform that. Callers should then add the
> > + * descriptor to the chain properly.
> > + *
> > + * Returns -EFAULT if @userptr is not a valid user address, which is a case the
> > + * driver should consider as "normal" operation. All other failures signal a
> > + * problem with the driver.
> > + */
> > +static int
> > +__scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> > + unsigned long userptr, unsigned long length,
> > + struct virtio_media_sg_entry **sg_list,
> > + int *nents)
> > +{
> > + struct sg_table sg_table = {};
> > + struct frame_vector *framevec;
> > + struct scatterlist *sg_iter;
> > + struct page **pages;
> > + const unsigned int offset = userptr & ~PAGE_MASK;
> > + unsigned int pages_count;
> > + size_t entries_size;
> > + int i;
> > + int ret;
> > +
> > + framevec = vb2_create_framevec(userptr, length, true);
> > + if (IS_ERR(framevec)) {
> > + if (PTR_ERR(framevec) != -EFAULT) {
> > + pr_warn("error %ld creating frame vector for userptr 0x%lx, length 0x%lx\n",
> > + PTR_ERR(framevec), userptr, length);
> > + } else {
> > + /* -EINVAL is expected in case of invalid userptr. */
> > + framevec = ERR_PTR(-EINVAL);
> > + }
> > + return PTR_ERR(framevec);
> > + }
> > +
> > + pages = frame_vector_pages(framevec);
> > + if (IS_ERR(pages)) {
> > + pr_warn("error getting vector pages\n");
> > + ret = PTR_ERR(pages);
> > + goto done;
> > + }
> > + pages_count = frame_vector_count(framevec);
> > + ret = sg_alloc_table_from_pages(&sg_table, pages, pages_count, offset,
> > + length, 0);
> > + if (ret) {
> > + pr_warn("error creating sg table\n");
> > + goto done;
> > + }
> > +
> > + /* Allocate our actual SG in the shadow buffer. */
> > + *nents = sg_nents(sg_table.sgl);
> > + entries_size = sizeof(**sg_list) * *nents;
> > + if (builder->shadow_buffer_pos + entries_size >
> > + builder->shadow_buffer_size) {
> > + ret = -ENOMEM;
> > + goto free_sg;
> > + }
> > +
> > + *sg_list = builder->shadow_buffer + builder->shadow_buffer_pos;
> > + builder->shadow_buffer_pos += entries_size;
> > +
> > + for_each_sgtable_sg(&sg_table, sg_iter, i) {
> > + struct virtio_media_sg_entry *sg_entry = &(*sg_list)[i];
> > +
> > + sg_entry->start = sg_phys(sg_iter);
> > + sg_entry->len = sg_iter->length;
> > + }
> > +
> > +free_sg:
> > + sg_free_table(&sg_table);
> > +
> > +done:
> > + vb2_destroy_framevec(framevec);
> > + return ret;
> > +}
> > +
> > +/**
> > + * scatterlist_builder_add_userptr() - Add a user-memory buffer using an array
> > + * of ``struct virtio_media_sg_entry``.
> > + * @builder: builder to use.
> > + * @userptr: pointer to userspace memory that we want to add.
> > + * @length: length of the data to add.
> > + *
> > + * Upon success, an array of ``struct virtio_media_sg_entry`` referencing
> > + * @userptr has been built into the shadow buffer, and that array added to the
> > + * descriptor chain.
> > + */
> > +static int scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> > + unsigned long userptr,
> > + unsigned long length)
> > +{
> > + int ret;
> > + int nents;
> > + struct virtio_media_sg_entry *sg_list;
> > +
> > + ret = __scatterlist_builder_add_userptr(builder, userptr, length,
> > + &sg_list, &nents);
> > + if (ret)
> > + return ret;
> > +
> > + ret = scatterlist_builder_add_data(builder, sg_list,
> > + sizeof(*sg_list) * nents);
> > + if (ret)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * scatterlist_builder_add_buffer() - Add a ``v4l2_buffer`` and its planes to
> > + * the descriptor chain.
> > + * @builder: builder to use.
> > + * @b: ``v4l2_buffer`` to add.
> > + */
> > +int scatterlist_builder_add_buffer(struct scatterlist_builder *builder,
> > + struct v4l2_buffer *b)
> > +{
> > + int i;
> > + int ret;
> > +
> > + /* Fixup: plane length must be zero if userptr is NULL */
> > + if (!V4L2_TYPE_IS_MULTIPLANAR(b->type) &&
> > + b->memory == V4L2_MEMORY_USERPTR && b->m.userptr == 0)
> > + b->length = 0;
> > +
> > + /* v4l2_buffer */
> > + ret = scatterlist_builder_add_data(builder, b, sizeof(*b));
> > + if (ret)
> > + return ret;
> > +
> > + if (V4L2_TYPE_IS_MULTIPLANAR(b->type) && b->length > 0) {
> > + /* Fixup: plane length must be zero if userptr is NULL */
> > + if (b->memory == V4L2_MEMORY_USERPTR) {
> > + for (i = 0; i < b->length; i++) {
> > + struct v4l2_plane *plane = &b->m.planes[i];
> > +
> > + if (plane->m.userptr == 0)
> > + plane->length = 0;
> > + }
> > + }
> > +
> > + /* Array of v4l2_planes */
> > + ret = scatterlist_builder_add_data(builder, b->m.planes,
> > + sizeof(struct v4l2_plane) *
> > + b->length);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * scatterlist_builder_add_buffer_userptr() - Add the payload of a ``USERTPR``
> > + * v4l2_buffer to the descriptor chain.
> > + * @builder: builder to use.
> > + * @b: ``v4l2_buffer`` which ``USERPTR`` payload we want to add.
> > + *
> > + * Add an array of ``virtio_media_sg_entry`` pointing to a ``USERPTR`` buffer's
> > + * contents. Does nothing if the buffer is not of type ``USERPTR``. This is
> > + * split out of :ref:`scatterlist_builder_add_buffer` because we only want to
> > + * add these to the device-readable part of the descriptor chain.
> > + */
> > +int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *builder,
> > + struct v4l2_buffer *b)
> > +{
> > + int i;
> > + int ret;
> > +
> > + if (b->memory != V4L2_MEMORY_USERPTR)
> > + return 0;
> > +
> > + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
> > + for (i = 0; i < b->length; i++) {
> > + struct v4l2_plane *plane = &b->m.planes[i];
> > +
> > + if (b->memory == V4L2_MEMORY_USERPTR &&
> > + plane->length > 0) {
> > + ret = scatterlist_builder_add_userptr(
> > + builder, plane->m.userptr,
> > + plane->length);
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > + } else if (b->length > 0) {
> > + ret = scatterlist_builder_add_userptr(builder, b->m.userptr,
> > + b->length);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * scatterlist_builder_retrieve_buffer() - Retrieve a v4l2_buffer written by
> > + * the device on the shadow buffer, if needed.
> > + * @builder: builder to use.
> > + * @sg_index: index of the first SG entry of the buffer in the builder's
> > + * descriptor chain.
> > + * @b: v4l2_buffer to copy shadow buffer data into.
> > + * @orig_planes: the original ``planes`` pointer, to be restored if the buffer
> > + * is multi-planar.
> > + *
> > + * If the v4l2_buffer pointed by @buffer_sgs was copied into the shadow buffer,
> > + * then its updated content is copied back into @b. Otherwise nothing is done
> > + * as the device has written into @b directly.
> > + *
> > + * @orig_planes is used to restore the original ``planes`` pointer in case it
> > + * gets modified by the host. The specification stipulates that the host should
> > + * not modify it, but we enforce this for additional safety.
> > + */
> > +int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builder,
> > + size_t sg_index, struct v4l2_buffer *b,
> > + struct v4l2_plane *orig_planes)
> > +{
> > + int ret;
> > +
> > + ret = scatterlist_builder_retrieve_data(builder, sg_index++, b);
> > + if (ret)
> > + return ret;
> > +
> > + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
> > + b->m.planes = orig_planes;
> > +
> > + if (orig_planes != NULL) {
> > + ret = scatterlist_builder_retrieve_data(
> > + builder, sg_index++, b->m.planes);
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * scatterlist_builder_add_ext_ctrls() - Add a v4l2_ext_controls and its
> > + * controls to @builder.
> > + * @builder: builder to use.
> > + * @ctrls: ``struct v4l2_ext_controls`` to add.
> > + *
> > + * Add @ctrls and its array of `struct v4l2_ext_control` to the descriptor chain.
> > + */
> > +int scatterlist_builder_add_ext_ctrls(struct scatterlist_builder *builder,
> > + struct v4l2_ext_controls *ctrls)
> > +{
> > + int ret;
> > +
> > + /* v4l2_ext_controls */
> > + ret = scatterlist_builder_add_data(builder, ctrls, sizeof(*ctrls));
> > + if (ret)
> > + return ret;
> > +
> > + if (ctrls->count > 0) {
> > + /* array of v4l2_controls */
> > + ret = scatterlist_builder_add_data(builder, ctrls->controls,
> > + sizeof(ctrls->controls[0]) *
> > + ctrls->count);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * scatterlist_builder_add_ext_ctrls_userptrs() - Add the userspace payloads of
> > + * a ``struct v4l2_ext_controls`` to the descriptor chain.
> > + * @builder: builder to use.
> > + * @ctrls: ``struct v4l2_ext_controls`` from which we want to add the userspace payload of.
> > + *
> > + * Add the userspace payloads of @ctrls to the descriptor chain. This is split
> > + * out of :ref:`scatterlist_builder_add_ext_ctrls` because we only want to add
> > + * these to the device-readable part of the descriptor chain.
> > + */
> > +int scatterlist_builder_add_ext_ctrls_userptrs(
> > + struct scatterlist_builder *builder, struct v4l2_ext_controls *ctrls)
> > +{
> > + int i;
> > + int ret;
> > +
> > + /* Pointers to user memory in individual controls */
> > + for (i = 0; i < ctrls->count; i++) {
> > + struct v4l2_ext_control *ctrl = &ctrls->controls[i];
> > +
> > + if (ctrl->size > 0) {
> > + ret = scatterlist_builder_add_userptr(
> > + builder, (unsigned long)ctrl->ptr, ctrl->size);
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * scatterlist_builder_retrieve_ext_ctrls() - Retrieve controls written by the
> > + * device on the shadow buffer, if needed.
> > + * @builder: builder to use.
> > + * @sg_index: index of the first SG entry of the controls in the builder's
> > + * descriptor chain.
> > + * @ctrls: ``struct v4l2_ext_controls`` to copy shadow buffer data into.
> > + *
> > + * If the shadow buffer is pointed to by @sg, copy its content back into @ctrls.
> > + */
> > +int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *builder,
> > + size_t sg_index,
> > + struct v4l2_ext_controls *ctrls)
> > +{
> > + struct v4l2_ext_control *controls_backup = ctrls->controls;
> > + int ret;
> > +
> > + ret = scatterlist_builder_retrieve_data(builder, sg_index++, ctrls);
> > + if (ret)
> > + return ret;
> > +
> > + ctrls->controls = controls_backup;
> > +
> > + if (ctrls->count > 0 && ctrls->controls) {
> > + ret = scatterlist_builder_retrieve_data(builder, sg_index++,
> > + ctrls->controls);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > diff --git a/drivers/media/virtio/scatterlist_builder.h b/drivers/media/virtio/scatterlist_builder.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..c8323c31ac21953580a0b4a4cb366841e510666f
> > --- /dev/null
> > +++ b/drivers/media/virtio/scatterlist_builder.h
> > @@ -0,0 +1,111 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> > +
> > +/*
> > + * Scatterlist builder helpers for virtio-media.
> > + *
> > + * Copyright (c) 2024-2025 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 ``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);
> > +
> > +int scatterlist_builder_add_buffer(struct scatterlist_builder *builder,
> > + struct v4l2_buffer *buffer);
> > +
> > +int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *builder,
> > + struct v4l2_buffer *b);
> > +
> > +int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builder,
> > + size_t sg_index,
> > + struct v4l2_buffer *buffer,
> > + struct v4l2_plane *orig_planes);
> > +
> > +int scatterlist_builder_add_ext_ctrls(struct scatterlist_builder *builder,
> > + struct v4l2_ext_controls *ctrls);
> > +
> > +int scatterlist_builder_add_ext_ctrls_userptrs(
> > + struct scatterlist_builder *builder, struct v4l2_ext_controls *ctrls);
> > +
> > +int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *builder,
> > + size_t sg_index,
> > + struct v4l2_ext_controls *ctrls);
> > +
> > +#endif // __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H
> > diff --git a/drivers/media/virtio/session.h b/drivers/media/virtio/session.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..b643d0d950477d56d4bb5db481818a3912af5c1f
> > --- /dev/null
> > +++ b/drivers/media/virtio/session.h
> > @@ -0,0 +1,109 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> > +
> > +/*
> > + * Definitions of virtio-media session related structures.
> > + *
> > + * Copyright (c) 2024-2025 Google LLC.
> > + */
> > +
> > +#ifndef __VIRTIO_MEDIA_SESSION_H
> > +#define __VIRTIO_MEDIA_SESSION_H
> > +
> > +#include <linux/scatterlist.h>
> > +#include <media/v4l2-fh.h>
> > +
> > +#include "protocol.h"
> > +
> > +#define VIRTIO_MEDIA_LAST_QUEUE (V4L2_BUF_TYPE_META_OUTPUT)
> > +
> > +/*
> > + * 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_buffer - Current state of a buffer.
> > + * @buffer: ``struct v4l2_buffer`` with current information about the buffer.
> > + * @planes: backing planes array for @buffer.
> > + * @list: link into the list of buffers pending dequeue.
> > + */
> > +struct virtio_media_buffer {
> > + struct v4l2_buffer buffer;
> > + struct v4l2_plane planes[VIDEO_MAX_PLANES];
> > + struct list_head list;
> > +};
> > +
> > +/**
> > + * struct virtio_media_queue_state - Represents the state of a V4L2 queue.
> > + * @streaming: Whether the queue is currently streaming.
> > + * @allocated_bufs: How many buffers are currently allocated.
> > + * @is_capture_last: set to true when the last buffer has been received on a
> > + * capture queue, so we can return -EPIPE on subsequent DQBUF requests.
> > + * @buffers: Buffer state array of size @allocated_bufs.
> > + * @queued_bufs: How many buffers are currently queued on the device.
> > + * @pending_dqbufs: Buffers that are available for being dequeued.
> > + */
> > +struct virtio_media_queue_state {
> > + bool streaming;
> > + size_t allocated_bufs;
> > + bool is_capture_last;
> > +
> > + struct virtio_media_buffer *buffers;
> > + size_t queued_bufs;
> > + struct list_head pending_dqbufs;
> > +};
> > +
> > +/**
> > + * struct virtio_media_session - A session on a virtio_media device.
> > + * @fh: file handler for the session.
> > + * @id: session ID used to communicate with the device.
> > + * @nonblocking_dequeue: whether dequeue should block or not (nonblocking if
> > + * file opened with O_NONBLOCK).
> > + * @uses_mplane: whether the queues for this session use the MPLANE API or not.
> > + * @cmd: union of session-related commands. A session can have one command currently running.
> > + * @resp: union of session-related responses. A session can wait on one command only.
> > + * @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.
> > + * @queues: state of all the queues for this session.
> > + * @queues_lock: protects all members fo the queues for this session.
> > + * virtio_media_queue_state`.
> > + * @dqbuf_wait: waitqueue for dequeued buffers, if ``VIDIOC_DQBUF`` needs to
> > + * block or when polling.
> > + * @list: link into the list of sessions for the device.
> > + */
> > +struct virtio_media_session {
> > + struct v4l2_fh fh;
> > + u32 id;
> > + bool nonblocking_dequeue;
> > + bool uses_mplane;
> > +
> > + union {
> > + struct virtio_media_cmd_close close;
> > + struct virtio_media_cmd_ioctl ioctl;
> > + struct virtio_media_cmd_mmap mmap;
> > + } cmd;
> > +
> > + union {
> > + struct virtio_media_resp_ioctl ioctl;
> > + struct virtio_media_resp_mmap mmap;
> > + } resp;
> > +
> > + void *shadow_buf;
> > +
> > + struct sg_table command_sgs;
> > +
> > + struct virtio_media_queue_state queues[VIRTIO_MEDIA_LAST_QUEUE + 1];
> > + struct mutex queues_lock;
> > + wait_queue_head_t dqbuf_wait;
> > +
> > + struct list_head list;
> > +};
> > +
> > +static inline struct virtio_media_session *fh_to_session(struct v4l2_fh *fh)
> > +{
> > + return container_of(fh, struct virtio_media_session, fh);
> > +}
> > +
> > +#endif // __VIRTIO_MEDIA_SESSION_H
> > diff --git a/drivers/media/virtio/virtio_media.h b/drivers/media/virtio/virtio_media.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..0aa503defdd6a08e12335276f7ccbabc3d53df09
> > --- /dev/null
> > +++ b/drivers/media/virtio/virtio_media.h
> > @@ -0,0 +1,93 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> > +
> > +/*
> > + * Virtio-media structures & functions declarations.
> > + *
> > + * Copyright (c) 2024-2025 Google LLC.
> > + */
> > +
> > +#ifndef __VIRTIO_MEDIA_H
> > +#define __VIRTIO_MEDIA_H
> > +
> > +#include <linux/virtio_config.h>
> > +#include <media/v4l2-device.h>
> > +
> > +#include "protocol.h"
> > +
> > +#define DESC_CHAIN_MAX_LEN SG_MAX_SINGLE_ALLOC
> > +
> > +#define VIRTIO_MEDIA_DEFAULT_DRIVER_NAME "virtio-media"
> > +
> > +extern char *virtio_media_driver_name;
> > +extern bool virtio_media_allow_userptr;
> > +
> > +/**
> > + * struct virtio_media - Virtio-media device.
> > + * @v4l2_dev: v4l2_device for the media device.
> > + * @video_dev: video_device for the media device.
> > + * @virtio_dev: virtio device for the media device.
> > + * @commandq: virtio command queue.
> > + * @eventq: virtio event queue.
> > + * @eventq_work: work to run when events are received on @eventq.
> > + * @mmap_region: region into which MMAP buffers are mapped by the host.
> > + * @event_buffer: buffer for event descriptors.
> > + * @sessions: list of active sessions on the device.
> > + * @sessions_lock: protects @sessions and ``virtio_media_session::list``.
> > + * @events_lock: prevents concurrent processing of events.
> > + * @cmd: union of device-related commands.
> > + * @resp: union of device-related responses.
> > + * @vlock: serializes access to the command queue.
> > + * @wq: waitqueue for host responses on the command queue.
> > + */
> > +struct virtio_media {
> > + struct v4l2_device v4l2_dev;
> > + struct video_device video_dev;
> > +
> > + struct virtio_device *virtio_dev;
> > + struct virtqueue *commandq;
> > + struct virtqueue *eventq;
> > + struct work_struct eventq_work;
> > +
> > + struct virtio_shm_region mmap_region;
> > +
> > + void *event_buffer;
> > +
> > + struct list_head sessions;
> > + struct mutex sessions_lock;
> > +
> > + struct mutex events_lock;
> > +
> > + union {
> > + struct virtio_media_cmd_open open;
> > + struct virtio_media_cmd_munmap munmap;
> > + } cmd;
> > +
> > + union {
> > + struct virtio_media_resp_open open;
> > + struct virtio_media_resp_munmap munmap;
> > + } resp;
> > +
> > + struct mutex vlock;
> > + wait_queue_head_t wq;
> > +};
> > +
> > +static inline struct virtio_media *
> > +to_virtio_media(struct video_device *video_dev)
> > +{
> > + return container_of(video_dev, struct virtio_media, video_dev);
> > +}
> > +
> > +/* virtio_media_driver.c */
> > +
> > +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);
> > +void virtio_media_process_events(struct virtio_media *vv);
> > +
> > +/* 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
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..e8d6dc453f2240c7809152c2a04813120bd3aca2
> > --- /dev/null
> > +++ b/drivers/media/virtio/virtio_media_driver.c
> > @@ -0,0 +1,959 @@
> > +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> > +
> > +/*
> > + * Virtio-media driver.
> > + *
> > + * Copyright (c) 2024-2025 Google LLC.
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/dev_printk.h>
> > +#include <linux/mm.h>
> > +#include <linux/mutex.h>
> > +#include <linux/scatterlist.h>
> > +#include <linux/types.h>
> > +#include <linux/videodev2.h>
> > +#include <linux/vmalloc.h>
> > +#include <linux/wait.h>
> > +#include <linux/workqueue.h>
> > +#include <linux/module.h>
> > +#include <linux/moduleparam.h>
> > +#include <linux/virtio.h>
> > +#include <linux/virtio_config.h>
> > +#include <linux/virtio_ids.h>
> > +
> > +#include <media/frame_vector.h>
> > +#include <media/v4l2-dev.h>
> > +#include <media/v4l2-event.h>
> > +#include <media/videobuf2-memops.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-ioctl.h>
> > +
> > +#include "protocol.h"
> > +#include "session.h"
> > +#include "virtio_media.h"
> > +
> > +#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
> > +
> > +/*
> > + * Name of the driver to expose to user-space.
> > + *
> > + * This is configurable because v4l2-compliance has workarounds specific to
> > + * some drivers. When proxying these directly from the host, this allows it to
> > + * apply them as needed.
> > + */
> > +char *virtio_media_driver_name;
> > +module_param_named(driver_name, virtio_media_driver_name, charp, 0660);
> > +
> > +/*
> > + * Whether USERPTR buffers are allowed.
> > + *
> > + * This is disabled by default as USERPTR buffers are dangerous, but the option
> > + * is left to enable them if desired.
> > + */
> > +bool virtio_media_allow_userptr;
> > +module_param_named(allow_userptr, virtio_media_allow_userptr, bool, 0660);
> > +
> > +/**
> > + * virtio_media_session_alloc - Allocate a new session.
> > + * @vv: virtio-media device the session belongs to.
> > + * @id: ID of the session.
> > + * @nonblocking_dequeue: whether dequeuing of buffers should be blocking or
> > + * not.
> > + *
> > + * The ``id`` and ``list`` fields must still be set by the caller.
> > + */
> > +static struct virtio_media_session *
> > +virtio_media_session_alloc(struct virtio_media *vv, u32 id,
> > + bool nonblocking_dequeue)
> > +{
> > + struct virtio_media_session *session;
> > + int i;
> > + int ret;
> > +
> > + session = kzalloc(sizeof(*session), GFP_KERNEL);
> > + if (!session)
> > + 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;
> > +
> > + session->id = id;
> > + session->nonblocking_dequeue = nonblocking_dequeue;
> > +
> > + INIT_LIST_HEAD(&session->list);
> > + v4l2_fh_init(&session->fh, &vv->video_dev);
> > + v4l2_fh_add(&session->fh);
> > +
> > + for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
> > + INIT_LIST_HEAD(&session->queues[i].pending_dqbufs);
> > + mutex_init(&session->queues_lock);
> > +
> > + init_waitqueue_head(&session->dqbuf_wait);
> > +
> > + mutex_lock(&vv->sessions_lock);
> > + list_add_tail(&session->list, &vv->sessions);
> > + mutex_unlock(&vv->sessions_lock);
> > +
> > + return session;
> > +
> > +err_payload_sgs:
> > + kfree(session->shadow_buf);
> > +err_shadow_buf:
> > + kfree(session);
> > +err_session:
> > + return ERR_PTR(-ENOMEM);
> > +}
> > +
> > +/**
> > + * virtio_media_session_free - Free all resources of a session.
> > + * @vv: virtio-media device the session belongs to.
> > + * @session: session to destroy.
> > + *
> > + * All the resources of @sesssion, as well as the backing memory of @session
> > + * itself, are freed.
> > + */
> > +static void virtio_media_session_free(struct virtio_media *vv,
> > + struct virtio_media_session *session)
> > +{
> > + int i;
> > +
> > + mutex_lock(&vv->sessions_lock);
> > + list_del(&session->list);
> > + mutex_unlock(&vv->sessions_lock);
> > +
> > + v4l2_fh_del(&session->fh);
> > + v4l2_fh_exit(&session->fh);
> > +
> > + sg_free_table(&session->command_sgs);
> > +
> > + for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
> > + vfree(session->queues[i].buffers);
> > +
> > + kfree(session->shadow_buf);
> > + kfree(session);
> > +}
> > +
> > +/**
> > + * virtio_media_session_close - Close and free a session.
> > + * @vv: virtio-media device the session belongs to.
> > + * @session: session to close and destroy.
> > + *
> > + * This send the ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and frees
> > + * all resources used by @session.
> > + */
> > +static int virtio_media_session_close(struct virtio_media *vv,
> > + struct virtio_media_session *session)
> > +{
> > + struct virtio_media_cmd_close *cmd_close = &session->cmd.close;
> > + struct scatterlist cmd_sg = {};
> > + struct scatterlist *sgs[1] = { &cmd_sg };
> > + int ret;
> > +
> > + mutex_lock(&vv->vlock);
> > +
> > + cmd_close->hdr.cmd = VIRTIO_MEDIA_CMD_CLOSE;
> > + cmd_close->session_id = session->id;
> > +
> > + sg_set_buf(&cmd_sg, cmd_close, sizeof(*cmd_close));
> > + sg_mark_end(&cmd_sg);
> > +
> > + ret = virtio_media_send_command(vv, sgs, 1, 0, 0, NULL);
> > + mutex_unlock(&vv->vlock);
> > + if (ret < 0)
> > + return ret;
> > +
> > + virtio_media_session_free(vv, session);
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * virtio_media_find_session - Lookup for the session with a given ID.
> > + * @vv: virtio-media device to lookup the session from.
> > + * @id: ID of the session to lookup.
> > + */
> > +static struct virtio_media_session *
> > +virtio_media_find_session(struct virtio_media *vv, u32 id)
> > +{
> > + struct list_head *p;
> > + struct virtio_media_session *session = NULL;
> > +
> > + mutex_lock(&vv->sessions_lock);
> > + list_for_each(p, &vv->sessions) {
> > + struct virtio_media_session *s =
> > + list_entry(p, struct virtio_media_session, list);
> > + if (s->id == id) {
> > + session = s;
> > + break;
> > + }
> > + }
> > + mutex_unlock(&vv->sessions_lock);
> > +
> > + return session;
> > +}
> > +
> > +/**
> > + * struct virtio_media_cmd_callback_param - Callback parameters to the virtio command queue.
> > + * @vv: virtio-media device in use.
> > + * @done: flag to be switched once the command is completed.
> > + * @resp_len: length of the received response from the command. Only valid
> > + * after @done_flag has switched to ``true``.
> > + */
> > +struct virtio_media_cmd_callback_param {
> > + struct virtio_media *vv;
> > + bool done;
> > + size_t resp_len;
> > +};
> > +
> > +/**
> > + * commandq_callback: Callback for the command queue.
> > + * @queue: command virtqueue.
> > + *
> > + * This just wakes up the thread that was waiting on the command to complete.
> > + */
> > +static void commandq_callback(struct virtqueue *queue)
> > +{
> > + unsigned int len;
> > + struct virtio_media_cmd_callback_param *param;
> > +
> > +process_bufs:
> > + while ((param = virtqueue_get_buf(queue, &len))) {
> > + param->done = true;
> > + param->resp_len = len;
> > + wake_up(¶m->vv->wq);
> > + }
> > +
> > + if (!virtqueue_enable_cb(queue)) {
> > + virtqueue_disable_cb(queue);
> > + goto process_bufs;
> > + }
> > +}
> > +
> > +/**
> > + * virtio_media_kick_command - send a command to the commandq.
> > + * @vv: virtio-media device in use.
> > + * @sgs: descriptor chain to send.
> > + * @out_sgs: number of device-readable descriptors in @sgs.
> > + * @in_sgs: number of device-writable descriptors in @sgs.
> > + * @resp_len: output parameter. Upon success, contains the size of the response
> > + * in bytes.
> > + *
> > + */
> > +static int virtio_media_kick_command(struct virtio_media *vv,
> > + struct scatterlist **sgs,
> > + const size_t out_sgs, const size_t in_sgs,
> > + size_t *resp_len)
> > +{
> > + struct virtio_media_cmd_callback_param cb_param = {
> > + .vv = vv,
> > + .done = false,
> > + .resp_len = 0,
> > + };
> > + struct virtio_media_resp_header *resp_header;
> > + int ret;
> > +
> > + ret = virtqueue_add_sgs(vv->commandq, sgs, out_sgs, in_sgs, &cb_param,
> > + GFP_ATOMIC);
> > + if (ret) {
> > + v4l2_err(&vv->v4l2_dev,
> > + "failed to add sgs to command virtqueue\n");
> > + return ret;
> > + }
> > +
> > + if (!virtqueue_kick(vv->commandq)) {
> > + v4l2_err(&vv->v4l2_dev, "failed to kick command virtqueue\n");
> > + return -EINVAL;
> > + }
> > +
> > + /* Wait for the response. */
> > + ret = wait_event_timeout(vv->wq, cb_param.done, 5 * HZ);
> > + if (ret == 0) {
> > + v4l2_err(&vv->v4l2_dev,
> > + "timed out waiting for response to command\n");
> > + return -ETIMEDOUT;
> > + }
> > +
> > + if (resp_len)
> > + *resp_len = cb_param.resp_len;
> > +
> > + if (in_sgs > 0) {
> > + /*
> > + * If we expect a response, make sure we have at least a
> > + * response header - anything shorter is invalid.
> > + */
> > + if (cb_param.resp_len < sizeof(*resp_header)) {
> > + v4l2_err(&vv->v4l2_dev,
> > + "received response header is too short\n");
> > + return -EINVAL;
> > + }
> > +
> > + resp_header = sg_virt(sgs[out_sgs]);
> > + if (resp_header->status)
> > + /* Host returns a positive error code. */
> > + return -resp_header->status;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * virtio_media_send_command - Send a command to the device and wait for its
> > + * response.
> > + * @vv: virtio-media device in use.
> > + * @sgs: descriptor chain to send.
> > + * @out_sgs: number of device-readable descriptors in @sgs.
> > + * @in_sgs: number of device-writable descriptors in @sgs.
> > + * @minimum_resp_len: minimum length of the response expected by the caller
> > + * when the command is successful. Anything shorter than that will result in
> > + * ``-EINVAL`` being returned.
> > + * @resp_len: output parameter. Upon success, contains the size of the response
> > + * in bytes.
> > + */
> > +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)
> > +{
> > + size_t local_resp_len = resp_len ? *resp_len : 0;
> > + int ret = virtio_media_kick_command(vv, sgs, out_sgs, in_sgs,
> > + &local_resp_len);
> > + if (resp_len)
> > + *resp_len = local_resp_len;
> > +
> > + /* If the host could not process the command, there is no valid response */
> > + if (ret < 0)
> > + return ret;
> > +
> > + /* Make sure the host wrote a complete reply. */
> > + if (local_resp_len < minimum_resp_len) {
> > + v4l2_err(
> > + &vv->v4l2_dev,
> > + "received response is too short: received %zu, expected at least %zu\n",
> > + local_resp_len, minimum_resp_len);
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * virtio_media_send_event_buffer() - Sends an event buffer to the host so it
> > + * can return it with an event.
> > + * @vv: virtio-media device in use.
> > + * @event_buffer: pointer to the event buffer to send to the device.
> > + */
> > +static int virtio_media_send_event_buffer(struct virtio_media *vv,
> > + void *event_buffer)
> > +{
> > + struct scatterlist *sgs[1], vresp;
> > + int ret;
> > +
> > + sg_init_one(&vresp, event_buffer, VIRTIO_MEDIA_EVENT_MAX_SIZE);
> > + sgs[0] = &vresp;
> > +
> > + ret = virtqueue_add_sgs(vv->eventq, sgs, 0, 1, event_buffer,
> > + GFP_ATOMIC);
> > + if (ret) {
> > + v4l2_err(&vv->v4l2_dev,
> > + "failed to add sgs to event virtqueue\n");
> > + return ret;
> > + }
> > +
> > + if (!virtqueue_kick(vv->eventq)) {
> > + v4l2_err(&vv->v4l2_dev, "failed to kick event virtqueue\n");
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * eventq_callback() - Callback for the event queue.
> > + * @queue: event virtqueue.
> > + *
> > + * This just schedules for event work to be run.
> > + */
> > +static void eventq_callback(struct virtqueue *queue)
> > +{
> > + struct virtio_media *vv = queue->vdev->priv;
> > +
> > + schedule_work(&vv->eventq_work);
> > +}
> > +
> > +/**
> > + * virtio_media_process_dqbuf_event() - Process a dequeued event for a session.
> > + * @vv: virtio-media device in use.
> > + * @session: session the event is addressed to.
> > + * @dqbuf_evt: the dequeued event to process.
> > + *
> > + * Invalid events are ignored with an error log.
> > + */
> > +static void
> > +virtio_media_process_dqbuf_event(struct virtio_media *vv,
> > + struct virtio_media_session *session,
> > + struct virtio_media_event_dqbuf *dqbuf_evt)
> > +{
> > + struct virtio_media_buffer *dqbuf;
> > + const enum v4l2_buf_type queue_type = dqbuf_evt->buffer.type;
> > + struct virtio_media_queue_state *queue;
> > + typeof(dqbuf->buffer.m) buffer_m;
> > + typeof(dqbuf->buffer.m.planes[0].m) plane_m;
> > + int i;
> > +
> > + if (queue_type >= ARRAY_SIZE(session->queues)) {
> > + v4l2_err(&vv->v4l2_dev,
> > + "unmanaged queue %d passed to dqbuf event",
> > + dqbuf_evt->buffer.type);
> > + return;
> > + }
> > + queue = &session->queues[queue_type];
> > +
> > + if (dqbuf_evt->buffer.index >= queue->allocated_bufs) {
> > + v4l2_err(&vv->v4l2_dev,
> > + "invalid buffer ID %d for queue %d in dqbuf event",
> > + dqbuf_evt->buffer.index, dqbuf_evt->buffer.type);
> > + return;
> > + }
> > +
> > + dqbuf = &queue->buffers[dqbuf_evt->buffer.index];
> > +
> > + /*
> > + * Preserve the 'm' union that was passed to us during QBUF so userspace
> > + * gets back the information it submitted.
> > + */
> > + buffer_m = dqbuf->buffer.m;
> > + memcpy(&dqbuf->buffer, &dqbuf_evt->buffer, sizeof(dqbuf->buffer));
> > + dqbuf->buffer.m = buffer_m;
> > + if (V4L2_TYPE_IS_MULTIPLANAR(dqbuf->buffer.type)) {
> > + if (dqbuf->buffer.length > VIDEO_MAX_PLANES) {
> > + v4l2_err(
> > + &vv->v4l2_dev,
> > + "invalid number of planes received from host for a multiplanar buffer\n");
> > + return;
> > + }
> > + for (i = 0; i < dqbuf->buffer.length; i++) {
> > + plane_m = dqbuf->planes[i].m;
> > + memcpy(&dqbuf->planes[i], &dqbuf_evt->planes[i],
> > + sizeof(struct v4l2_plane));
> > + dqbuf->planes[i].m = plane_m;
> > + }
> > + }
> > +
> > + /* Set the DONE flag as the buffer is waiting for being dequeued. */
> > + dqbuf->buffer.flags |= V4L2_BUF_FLAG_DONE;
> > +
> > + mutex_lock(&session->queues_lock);
> > + list_add_tail(&dqbuf->list, &queue->pending_dqbufs);
> > + queue->queued_bufs -= 1;
> > + mutex_unlock(&session->queues_lock);
> > +
> > + wake_up(&session->dqbuf_wait);
> > +}
> > +
> > +/**
> > + * virtio_media_process_events() - Process all pending events on a device.
> > + * @vv: device which pending events we want to process.
> > + *
> > + * Retrieves all pending events on @vv's event queue and dispatch them to their
> > + * corresponding session.
> > + *
> > + * Invalid events are ignored with an error log.
> > + */
> > +void virtio_media_process_events(struct virtio_media *vv)
> > +{
> > + struct virtio_media_event_error *error_evt;
> > + struct virtio_media_event_dqbuf *dqbuf_evt;
> > + struct virtio_media_event_event *event_evt;
> > + struct virtio_media_session *session;
> > + struct virtio_media_event_header *evt;
> > + unsigned int len;
> > +
> > + mutex_lock(&vv->events_lock);
> > +
> > +process_bufs:
> > + while ((evt = virtqueue_get_buf(vv->eventq, &len))) {
> > + /* Make sure we received enough data */
> > + if (len < sizeof(*evt)) {
> > + v4l2_err(
> > + &vv->v4l2_dev,
> > + "event is too short: got %u, expected at least %zu\n",
> > + len, sizeof(*evt));
> > + goto end_of_event;
> > + }
> > +
> > + session = virtio_media_find_session(vv, evt->session_id);
> > + if (!session) {
> > + v4l2_err(&vv->v4l2_dev, "cannot find session %d\n",
> > + evt->session_id);
> > + goto end_of_event;
> > + }
> > +
> > + switch (evt->event) {
> > + case VIRTIO_MEDIA_EVT_ERROR:
> > + if (len < sizeof(*error_evt)) {
> > + v4l2_err(
> > + &vv->v4l2_dev,
> > + "error event is too short: got %u, expected %zu\n",
> > + len, sizeof(*error_evt));
> > + break;
> > + }
> > + error_evt = (struct virtio_media_event_error *)evt;
> > + v4l2_err(&vv->v4l2_dev,
> > + "received error %d for session %d",
> > + error_evt->errno, error_evt->hdr.session_id);
> > + virtio_media_session_close(vv, session);
> > + break;
> > +
> > + /*
> > + * Dequeued buffer: put it into the right queue so user-space can dequeue
> > + * it.
> > + */
> > + case VIRTIO_MEDIA_EVT_DQBUF:
> > + if (len < sizeof(*dqbuf_evt)) {
> > + v4l2_err(
> > + &vv->v4l2_dev,
> > + "dqbuf event is too short: got %u, expected %zu\n",
> > + len, sizeof(*dqbuf_evt));
> > + break;
> > + }
> > + dqbuf_evt = (struct virtio_media_event_dqbuf *)evt;
> > + virtio_media_process_dqbuf_event(vv, session,
> > + dqbuf_evt);
> > + break;
> > +
> > + case VIRTIO_MEDIA_EVT_EVENT:
> > + if (len < sizeof(*event_evt)) {
> > + v4l2_err(
> > + &vv->v4l2_dev,
> > + "session event is too short: got %u expected %zu\n",
> > + len, sizeof(*event_evt));
> > + break;
> > + }
> > +
> > + event_evt = (struct virtio_media_event_event *)evt;
> > + v4l2_event_queue_fh(&session->fh, &event_evt->event);
> > + break;
> > +
> > + default:
> > + v4l2_err(&vv->v4l2_dev, "unknown event type %d\n",
> > + evt->event);
> > + break;
> > + }
> > +
> > +end_of_event:
> > + virtio_media_send_event_buffer(vv, evt);
> > + }
> > +
> > + if (!virtqueue_enable_cb(vv->eventq)) {
> > + virtqueue_disable_cb(vv->eventq);
> > + goto process_bufs;
> > + }
> > +
> > + mutex_unlock(&vv->events_lock);
> > +}
> > +
> > +static void virtio_media_event_work(struct work_struct *work)
> > +{
> > + struct virtio_media *vv =
> > + container_of(work, struct virtio_media, eventq_work);
> > +
> > + virtio_media_process_events(vv);
> > +}
> > +
> > +/**
> > + * virtio_media_device_open() - Create a new session from an opened file.
> > + * @file: opened file for the session.
> > + */
> > +static int virtio_media_device_open(struct file *file)
> > +{
> > + struct video_device *video_dev = video_devdata(file);
> > + struct virtio_media *vv = to_virtio_media(video_dev);
> > + struct virtio_media_cmd_open *cmd_open = &vv->cmd.open;
> > + struct virtio_media_resp_open *resp_open = &vv->resp.open;
> > + struct scatterlist cmd_sg = {}, resp_sg = {};
> > + struct scatterlist *sgs[2] = { &cmd_sg, &resp_sg };
> > + struct virtio_media_session *session;
> > + u32 session_id;
> > + int ret;
> > +
> > + mutex_lock(&vv->vlock);
> > +
> > + sg_set_buf(&cmd_sg, cmd_open, sizeof(*cmd_open));
> > + sg_mark_end(&cmd_sg);
> > +
> > + sg_set_buf(&resp_sg, resp_open, sizeof(*resp_open));
> > + sg_mark_end(&resp_sg);
> > +
> > + cmd_open->hdr.cmd = VIRTIO_MEDIA_CMD_OPEN;
> > + ret = virtio_media_send_command(vv, sgs, 1, 1, sizeof(*resp_open),
> > + NULL);
> > + session_id = resp_open->session_id;
> > + mutex_unlock(&vv->vlock);
> > + if (ret < 0)
> > + return ret;
> > +
> > + session = virtio_media_session_alloc(vv, session_id,
> > + (file->f_flags & O_NONBLOCK));
> > + if (IS_ERR(session))
> > + return PTR_ERR(session);
> > +
> > + file->private_data = &session->fh;
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * virtio_media_device_close() - Close a previously opened session.
> > + * @file: file of the session to close.
> > + *
> > + * This sends to ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and close
> > + * the session on the driver side.
> > + */
> > +static int virtio_media_device_close(struct file *file)
> > +{
> > + 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);
> > +
> > + 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 : 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)
> > +{
> > + struct device *dev = &virtio_dev->dev;
> > + struct virtqueue *vqs[2];
> > + static struct virtqueue_info vq_info[2] = {
> > + {
> > + .name = "command",
> > + .callback = commandq_callback,
> > + },
> > + {
> > + .name = "event",
> > + .callback = eventq_callback,
> > + },
> > + };
> > + struct virtio_media *vv;
> > + struct video_device *vd;
> > + int i;
> > + int ret;
> > +
> > + vv = devm_kzalloc(dev, sizeof(*vv), GFP_KERNEL);
> > + if (!vv)
> > + return -ENOMEM;
> > +
> > + vv->event_buffer = devm_kzalloc(
> > + dev, VIRTIO_MEDIA_EVENT_MAX_SIZE * VIRTIO_MEDIA_NUM_EVENT_BUFS,
> > + GFP_KERNEL);
> > + if (!vv->event_buffer)
> > + return -ENOMEM;
> > +
> > + INIT_LIST_HEAD(&vv->sessions);
> > + mutex_init(&vv->sessions_lock);
> > + mutex_init(&vv->events_lock);
> > + mutex_init(&vv->vlock);
> > +
> > + vv->virtio_dev = virtio_dev;
> > + virtio_dev->priv = vv;
> > +
> > + init_waitqueue_head(&vv->wq);
> > +
> > + ret = v4l2_device_register(dev, &vv->v4l2_dev);
> > + if (ret)
> > + return ret;
> > +
> > + ret = virtio_find_vqs(virtio_dev, 2, vqs, vq_info, NULL);
> > + if (ret)
> > + goto err_find_vqs;
> > +
> > + vv->commandq = vqs[0];
> > + 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->device_caps = virtio_cread32(virtio_dev, 0);
> > + if (vd->device_caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))
> > + vd->vfl_dir = VFL_DIR_M2M;
> > + else if (vd->device_caps &
> > + (V4L2_CAP_VIDEO_OUTPUT | V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE))
> > + vd->vfl_dir = VFL_DIR_TX;
> > + else
> > + vd->vfl_dir = VFL_DIR_RX;
> > + vd->release = video_device_release_empty;
> > + strscpy(vd->name, "virtio-media", sizeof(vd->name));
> > +
> > + video_set_drvdata(vd, vv);
> > +
> > + ret = video_register_device(vd, virtio_cread32(virtio_dev, 4), 0);
> > + if (ret)
> > + goto err_register_device;
> > +
> > + for (i = 0; i < VIRTIO_MEDIA_NUM_EVENT_BUFS; i++) {
> > + ret = virtio_media_send_event_buffer(
> > + vv, vv->event_buffer + VIRTIO_MEDIA_EVENT_MAX_SIZE * i);
> > + if (ret)
> > + goto err_send_event_buffer;
> > + }
> > +
> > + virtio_device_ready(virtio_dev);
> > +
> > + return 0;
> > +
> > +err_send_event_buffer:
> > + video_unregister_device(&vv->video_dev);
> > +err_register_device:
> > + virtio_dev->config->del_vqs(virtio_dev);
> > +err_find_vqs:
> > + v4l2_device_unregister(&vv->v4l2_dev);
> > +
> > + return ret;
> > +}
> > +
> > +static void virtio_media_remove(struct virtio_device *virtio_dev)
> > +{
> > + struct virtio_media *vv = virtio_dev->priv;
> > + struct list_head *p, *n;
> > +
> > + cancel_work_sync(&vv->eventq_work);
> > + virtio_reset_device(virtio_dev);
> > +
> > + v4l2_device_unregister(&vv->v4l2_dev);
> > + virtio_dev->config->del_vqs(virtio_dev);
> > + video_unregister_device(&vv->video_dev);
> > +
> > + list_for_each_safe(p, n, &vv->sessions) {
> > + struct virtio_media_session *s =
> > + list_entry(p, struct virtio_media_session, list);
> > +
> > + virtio_media_session_free(vv, s);
> > + }
> > +}
> > +
> > +static struct virtio_device_id id_table[] = {
> > + { VIRTIO_ID_MEDIA, VIRTIO_DEV_ANY_ID },
> > + { 0 },
> > +};
> > +
> > +static unsigned int features[] = {};
> > +
> > +static struct virtio_driver virtio_media_driver = {
> > + .feature_table = features,
> > + .feature_table_size = ARRAY_SIZE(features),
> > + .driver.name = VIRTIO_MEDIA_DEFAULT_DRIVER_NAME,
> > + .driver.owner = THIS_MODULE,
> > + .id_table = id_table,
> > + .probe = virtio_media_probe,
> > + .remove = virtio_media_remove,
> > +};
> > +
> > +module_virtio_driver(virtio_media_driver);
> > +
> > +MODULE_DEVICE_TABLE(virtio, id_table);
> > +MODULE_DESCRIPTION("virtio media driver");
> > +MODULE_AUTHOR("Alexandre Courbot <gnurou@gmail.com>");
> > +MODULE_LICENSE("Dual BSD/GPL");
> > diff --git a/drivers/media/virtio/virtio_media_ioctls.c b/drivers/media/virtio/virtio_media_ioctls.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..863cdfbaaadc7241110c82ce6880bc5675c23894
> > --- /dev/null
> > +++ b/drivers/media/virtio/virtio_media_ioctls.c
> > @@ -0,0 +1,1297 @@
> > +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> > +
> > +/*
> > + * Ioctls implementations for the virtio-media driver.
> > + *
> > + * Copyright (c) 2024-2025 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,
> > + };
> > + int ret;
> > +
> > + /* Command descriptor */
> > + 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,
> > + };
> > + int ret;
> > +
> > + /* Command descriptor */
> > + 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,
> > + };
> > + int ret;
> > +
> > + /* Command descriptor */
> > + 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: ``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;
> > +
> > + /* Payload of SHARED_PAGES buffers, if relevant */
> > + ret = scatterlist_builder_add_buffer_userptr(&builder, b);
> > + if (ret < 0)
> > + 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 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: ``v4l2_ext_controls`` to be sent as the ioctl payload.
> > + *
> > + * Queues an ioctl that sends a ``v4l2_ext_controls`` to the host and receives
> > + * an updated version.
> > + *
> > + * ``v4l2_ext_controls`` has a pointer to an array of ``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 ret;
> > + int i;
> > +
> > + /* Command descriptor */
> > + 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;
> > +
> > + /* Event 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) \
> > + { \
> > + return virtio_media_send_wr_ioctl(fh, 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) \
> > + { \
> > + return virtio_media_send_r_ioctl(fh, 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) \
> > + { \
> > + return virtio_media_send_w_ioctl(fh, 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");
> > +
> > + if (!virtio_media_driver_name)
> > + strscpy(cap->driver, VIRTIO_MEDIA_DEFAULT_DRIVER_NAME);
> > + else
> > + strscpy(cap->driver, virtio_media_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)
> > +{
> > + return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_G_EXT_CTRLS,
> > + ctrls);
> > +}
> > +
> > +static int virtio_media_s_ext_ctrls(struct file *file, void *fh,
> > + struct v4l2_ext_controls *ctrls)
> > +{
> > + return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_S_EXT_CTRLS,
> > + ctrls);
> > +}
> > +
> > +static int virtio_media_try_ext_ctrls(struct file *file, void *fh,
> > + struct v4l2_ext_controls *ctrls)
> > +{
> > + return virtio_media_send_ext_controls_ioctl(fh, 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;
> > +
> > + 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 virtio_media_session *session = fh_to_session(fh);
> > + int ret;
> > +
> > + if (i > VIRTIO_MEDIA_LAST_QUEUE)
> > + return -EINVAL;
> > +
> > + ret = virtio_media_send_w_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> > + int ret;
> > +
> > + if (i > VIRTIO_MEDIA_LAST_QUEUE)
> > + return -EINVAL;
> > +
> > + ret = virtio_media_send_w_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> > + struct virtio_media_queue_state *queue;
> > + int ret;
> > +
> > + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> > + return -EINVAL;
> > +
> > + if (b->memory == V4L2_MEMORY_USERPTR && !virtio_media_allow_userptr)
> > + return -EINVAL;
> > +
> > + ret = virtio_media_send_wr_ioctl(fh, 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;
> > +
> > + if (!virtio_media_allow_userptr)
> > + 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 virtio_media_session *session = fh_to_session(fh);
> > + struct virtio_media_queue_state *queue;
> > + struct virtio_media_buffer *buffer;
> > + int ret;
> > +
> > + ret = virtio_media_send_buffer_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> > + 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> > + 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> > + 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(fh, 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->private_data);
> > + 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 want to 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;
> > +
> > + ret = virtio_media_send_wr_ioctl(fh, 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(fh, 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;
> > +
> > + ret = virtio_media_send_wr_ioctl(fh, 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> > + int ret;
> > +
> > + ret = virtio_media_send_wr_ioctl(fh, 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;
> > +
> > + ret = virtio_media_send_w_ioctl(fh, 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->private_data;
> > +
> > + mutex_lock(&vv->vlock);
> > +
> > + /*
> > + * We need to handle a few ioctls manually because their result 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;
> > +}
> > diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> > index 7aa2eb76620508fdc915533f74973d76308d3ef5..b4bb0ace0b26e37224c975f89bbf669c51921816 100644
> > --- a/include/uapi/linux/virtio_ids.h
> > +++ b/include/uapi/linux/virtio_ids.h
> > @@ -68,6 +68,7 @@
> > #define VIRTIO_ID_AUDIO_POLICY 39 /* virtio audio policy */
> > #define VIRTIO_ID_BT 40 /* virtio bluetooth */
> > #define VIRTIO_ID_GPIO 41 /* virtio gpio */
> > +#define VIRTIO_ID_MEDIA 48 /* virtio media */
> >
> > /*
> > * Virtio Transitional IDs
> >
> > ---
> > base-commit: 0af2f6be1b4281385b618cb86ad946eded089ac8
> > change-id: 20241229-virtio-media-25067bb27526
> >
> > Best regards,
>
>
>
> Thanks,
> Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 14:23 ` Michael S. Tsirkin
@ 2025-05-27 14:39 ` Mauro Carvalho Chehab
2025-05-27 15:06 ` Michael S. Tsirkin
0 siblings, 1 reply; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-05-27 14:39 UTC (permalink / raw)
To: Michael S. Tsirkin
Cc: Alexandre Courbot, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization,
Alexandre Courbot
Em Tue, 27 May 2025 10:23:32 -0400
"Michael S. Tsirkin" <mst@redhat.com> escreveu:
> On Mon, May 26, 2025 at 02:13:16PM +0200, Mauro Carvalho Chehab wrote:
> > Hi Michael,
> >
> > Em Sat, 12 Apr 2025 13:08:01 +0900
> > Alexandre Courbot <gnurou@gmail.com> escreveu:
> >
> > > Add the first version of the virtio-media driver.
> > >
> > > This driver acts roughly as a V4L2 relay between user-space and the
> > > virtio virtual device on the host, so it is relatively simple, yet
> > > unconventional. It doesn't use VB2 or other frameworks typically used in
> > > a V4L2 driver, and most of its complexity resides in correctly and
> > > efficiently building the virtio descriptor chain to pass to the host,
> > > avoiding copies whenever possible. This is done by
> > > scatterlist_builder.[ch].
> > >
> > > virtio_media_ioctls.c proxies each supported ioctl to the host, using
> > > code generated through macros for ioctls that can be forwarded directly,
> > > which is most of them.
> > >
> > > virtio_media_driver.c provides the expected driver hooks, and support
> > > for mmapping and polling.
> > >
> > > This version supports MMAP buffers, while USERPTR buffers can also be
> > > enabled through a driver option. DMABUF support is still pending.
> >
> > It sounds that you applied this one at the virtio tree, but it hasn't
> > being reviewed or acked by media maintainers.
> >
> > Please drop it.
> >
> > Alexandre,
> >
> > Please send media patches to media maintainers, c/c other subsystem
> > maintainers, as otherwise they might end being merged without a
> > proper review.
> >
> > In this particular case, we need to double-check if this won't cause
> > any issues, in special with regards to media locks and mutexes.
> >
> > I'll try to look on it after this merge window, as it is too late
> > for it to be applied during this one.
> >
> > Regards,
> > Mauro
>
> New drivers generally can be merged during the merge window,
> especially early.
Sure, but this one was not reviewed or tested yet by media maintainers,
nor its submission came with the tests from the regression tool
we use (v4l2-compliance). In particular, we need to double-check
if it won't cause any issues with the complex set of mutexes and
spinlocks that we have within the core.
There is an additional concern related to V4L2: on media, only one
process is allowed to have exclusive streaming access to the
device: all other opens on the same device get permission denied
(by default - there is an optional ioctl that allows a process
to "abdicate" its streaming rights). We need to double-check how this
is implemented and how this would behavior when multiple VMs have
the driver installed and might try to use (or not), and how this
would affect the host access to the device.
There are also some coding style issues that cause our CI to
complain. Those are minor and could be fixed by a separate patch,
but better to have them placed altogether as otherwise our CI
will keep complaining about until the fix is merged.
On other words, this driver is not ready for merge yet.
We need some time to test and review it properly.
> It's up to you though.
> I can keep it in next for now, so it gets some coverage by
> tools scanning that tree.
Sure, feel free to keep it on next if you prefer so. Just
please don't submit it upstream while we don't review and
properly test it.
Thanks!
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 14:03 ` Alexandre Courbot
@ 2025-05-27 14:42 ` Mauro Carvalho Chehab
2025-06-17 8:49 ` Mauro Carvalho Chehab
1 sibling, 0 replies; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-05-27 14:42 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Em Tue, 27 May 2025 23:03:39 +0900
Alexandre Courbot <gnurou@gmail.com> escreveu:
> On Tue, May 27, 2025 at 10:35 PM Mauro Carvalho Chehab
> <mchehab+huawei@kernel.org> wrote:
> >
> > Em Tue, 27 May 2025 22:21:42 +0900
> > Alexandre Courbot <gnurou@gmail.com> escreveu:
> >
> > > On Tue, May 27, 2025 at 6:13 PM Mauro Carvalho Chehab
> > > <mchehab+huawei@kernel.org> wrote:
> > > >
> > > > Em Tue, 27 May 2025 15:14:50 +0900
> > > > "Alexandre Courbot" <gnurou@gmail.com> escreveu:
> > > >
> > > > > Hi Mauro,
> > > > >
> > > > > On Mon May 26, 2025 at 9:13 PM JST, Mauro Carvalho Chehab wrote:
> > > > > > Hi Michael,
> > > > > >
> > > > > > Em Sat, 12 Apr 2025 13:08:01 +0900
> > > > > > Alexandre Courbot <gnurou@gmail.com> escreveu:
> > > > > >
> > > > > >> Add the first version of the virtio-media driver.
> > > > > >>
> > > > > >> This driver acts roughly as a V4L2 relay between user-space and the
> > > > > >> virtio virtual device on the host, so it is relatively simple, yet
> > > > > >> unconventional. It doesn't use VB2 or other frameworks typically used in
> > > > > >> a V4L2 driver, and most of its complexity resides in correctly and
> > > > > >> efficiently building the virtio descriptor chain to pass to the host,
> > > > > >> avoiding copies whenever possible. This is done by
> > > > > >> scatterlist_builder.[ch].
> > > > > >>
> > > > > >> virtio_media_ioctls.c proxies each supported ioctl to the host, using
> > > > > >> code generated through macros for ioctls that can be forwarded directly,
> > > > > >> which is most of them.
> > > > > >>
> > > > > >> virtio_media_driver.c provides the expected driver hooks, and support
> > > > > >> for mmapping and polling.
> > > > > >>
> > > > > >> This version supports MMAP buffers, while USERPTR buffers can also be
> > > > > >> enabled through a driver option. DMABUF support is still pending.
> > > > > >
> > > > > > It sounds that you applied this one at the virtio tree, but it hasn't
> > > > > > being reviewed or acked by media maintainers.
> > > > > >
> > > > > > Please drop it.
> > > > > >
> > > > > > Alexandre,
> > > > > >
> > > > > > Please send media patches to media maintainers, c/c other subsystem
> > > > > > maintainers, as otherwise they might end being merged without a
> > > > > > proper review.
> > > > >
> > > > > Sorry about that, I put everyone in "To:" without giving it a second
> > > > > thought.
> > > > >
> > > > > >
> > > > > > In this particular case, we need to double-check if this won't cause
> > > > > > any issues, in special with regards to media locks and mutexes.
> > > > >
> > > > > Agreed, I am not 100% confident about that part myself.
> > > > >
> > > > > >
> > > > > > I'll try to look on it after this merge window, as it is too late
> > > > > > for it to be applied during this one.
> > > > >
> > > > > Appreciate that - given the high traffic on the list I was worried that
> > > > > this patch would eventually be overlooked. Not making it for this merge
> > > > > window should not be a problem, so please take the time you need.
> > > >
> > > > Provided that your patch was caught by patchwork, it won't be lost:
> > > > https://patchwork.linuxtv.org/project/linux-media/patch/20250412-virtio-media-v3-1-97dc94c18398@gmail.com/
> > > >
> > > > Please notice that our CI got a number of checkpatch issues there.
> > > > Please check and fix the non-false-positive ones.
> > >
> > > Will do. The macro-related ones are false-positives AFAICT. Some of
> > > the "lines should not end with a '('" are actually the result of
> > > applying clang-format with the kernel-provided style...
> >
> > I don't know any lint tool that honors kernel style. The best one
> > is checkpatch with the auto-correcting parameter in pedantic mode,
> > but still one needs to manually review its output.
> >
> > >
> > > >
> > > > Btw, I was looking at:
> > > >
> > > > https://github.com/chromeos/virtio-media
> > > >
> > > > (I'm assuming that this is the QEMU counterpart, right?)
> > >
> > > crosvm actually, but QEMU support is also being worked on.
> >
> > Do you have already QEMU patches? The best is to have the Kernel driver
> > submitted altogether with QEMU, as Kernel developers need it to do the
> > tests. In my case, I never use crosvm, and I don't have any Chromebook
> > anymore.
>
> IIRC Albert Esteve was working on this, maybe he can share the current status.
That would be nice.
>
> Note that crosvm does not require a Chromebook, you can build and run
> it pretty easily on a regular PC. I have put together a document to
> help with that:
>
> https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
>
> It also shows how to proxy a host UVC camera into the guest and use
> some of the "fake" devices I talked about earlier.
Ok, I'll look on it, thanks for the hint!
> If following these
> steps is too much friction, just reading through the document should
> give you a decent idea of what virtio-media can do.
>
> >
> > > > And I noticed something weird there:
> > > >
> > > > "Unsupported ioctls
> > > >
> > > > A few ioctls are replaced by other, more suitable mechanisms. If being requested these ioctls, the device must return the same response as it would for an unknown ioctl, i.e. ENOTTY.
> > > >
> > > > VIDIOC_QUERYCAP is replaced by reading the configuration area.
> > > > VIDIOC_DQBUF is replaced by a dedicated event.
> > > > VIDIOC_DQEVENT is replaced by a dedicated event."
> > > >
> > > > While this could be ok for cromeOS, this will be broken for guests with
> > > > Linux, as all Linux applications rely on VIDIOC_QUERYCAP and VIDIOC_DQBUF
> > > > to work. Please implement support for it, as otherwise we won't even be
> > > > able to test the driver with the v4l2-compliance tool (*).
> > >
> > > The phrasing was a bit confusing. The guest driver does support these
> > > ioctls, and passes v4l2-compliance. So there is no problem here.
> >
> > Please add v4l2-compliance output on the next patch series.
>
> Certainly. Note that the output is dependent on what the host provides
> for a device. If it e.g. proxies something that is not fully
> compliant, the guest will reflect the same errors.
Makes sense. In this case, the best is to add at the PR the v4l2-compliance
output for both the host real V4L2 driver and the guest one.
>
> >
> > > Where these ioctls are not supported is between the guest and the
> > > host, i.e. as ioctls encapsulated into a virtio request. For QUERYCAP,
> > > that's because the virtio configuration area already provides the same
> > > information. For DQBUF and DQEVENT, that's because we want to serve
> > > these asynchronously for performance reasons (hence the dedicated
> > > events).
> > >
> > > But these differences don't affect guest user applications which will
> > > be able to perform these ioctls exactly as they expect.
> >
> > OK. Better to let it clear then at the documentation.
> >
> > > >
> > > > (*) Passing at v4l2-compliance is a requirement for any media driver
> > > > to be merged.
> > > >
> > > > With regards to testing, what's the expected testing scenario?
> > > > My guess is that, as a virtio device, a possible test scenario would be
> > > > to have the UVC camera from the host OS mapped using virtio-camera into
> > > > the guest OS, allowing a V4L2 application running at the guest to map the
> > > > camera from the host, right?
> > >
> > > That's one of the scenarios, yes.
> >
> > Ok, this is the easiest test scenario for media developers.
> >
> > > Another one is to expose an accelerated decoder on the host to the guest.
> >
> > > One can also create
> > > "fake" devices backed by software on the host for testing purposes.
> >
> > That sounds interesting. It probably makes sense to have some test
> > scenario using such fake devices plus v4l2-compliance test to check
> > for regressions running on some CI engine.
>
> Yes, regression catching in a CI is one of the use-cases we thought about.
>
> >
> > > That's actually the benefit of using V4L2 as a guest/host protocol:
> > > the same guest and the same software stack on the host can be used to
>
> I made a typo here: "the same guest DRIVER". That's an important point. :)
It was clear enough to me ;-)
>
> Cheers,
> Alex.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 14:39 ` Mauro Carvalho Chehab
@ 2025-05-27 15:06 ` Michael S. Tsirkin
2025-05-28 11:07 ` Alexandre Courbot
0 siblings, 1 reply; 26+ messages in thread
From: Michael S. Tsirkin @ 2025-05-27 15:06 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Alexandre Courbot, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization,
Alexandre Courbot
On Tue, May 27, 2025 at 04:39:27PM +0200, Mauro Carvalho Chehab wrote:
> > It's up to you though.
> > I can keep it in next for now, so it gets some coverage by
> > tools scanning that tree.
>
> Sure, feel free to keep it on next if you prefer so. Just
> please don't submit it upstream while we don't review and
> properly test it.
No prob. I just want to see it get reviewed and merged.
My understanding is, it wasn't because maintainers were
not Cc'd so that should be all ironed out now.
Alexandre, do you want this in next for now or just drop it?
--
MST
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 15:06 ` Michael S. Tsirkin
@ 2025-05-28 11:07 ` Alexandre Courbot
0 siblings, 0 replies; 26+ messages in thread
From: Alexandre Courbot @ 2025-05-28 11:07 UTC (permalink / raw)
To: Michael S. Tsirkin
Cc: Mauro Carvalho Chehab, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Hi Michael,
On Wed, May 28, 2025 at 12:06 AM Michael S. Tsirkin <mst@redhat.com> wrote:
>
> On Tue, May 27, 2025 at 04:39:27PM +0200, Mauro Carvalho Chehab wrote:
> > > It's up to you though.
> > > I can keep it in next for now, so it gets some coverage by
> > > tools scanning that tree.
> >
> > Sure, feel free to keep it on next if you prefer so. Just
> > please don't submit it upstream while we don't review and
> > properly test it.
>
> No prob. I just want to see it get reviewed and merged.
> My understanding is, it wasn't because maintainers were
> not Cc'd so that should be all ironed out now.
> Alexandre, do you want this in next for now or just drop it?
I think it doesn't hurt to give some exposure to this driver, if Mauro
is ok with it (and it eventually gets merged through the media tree).
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-04-12 4:08 [PATCH v3] media: add virtio-media driver Alexandre Courbot
2025-04-12 14:27 ` Markus Elfring
2025-05-26 12:13 ` Mauro Carvalho Chehab
@ 2025-05-28 16:23 ` Ricardo Ribalda
2025-06-01 9:34 ` Mauro Carvalho Chehab
2 siblings, 1 reply; 26+ messages in thread
From: Ricardo Ribalda @ 2025-05-28 16:23 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Mauro Carvalho Chehab, Hans Verkuil, Albert Esteve,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization,
Alexandre Courbot
Hi Alexandre
On Sat, 12 Apr 2025 at 06:08, Alexandre Courbot <gnurou@gmail.com> wrote:
>
> Add the first version of the virtio-media driver.
>
> This driver acts roughly as a V4L2 relay between user-space and the
> virtio virtual device on the host, so it is relatively simple, yet
> unconventional. It doesn't use VB2 or other frameworks typically used in
> a V4L2 driver, and most of its complexity resides in correctly and
> efficiently building the virtio descriptor chain to pass to the host,
> avoiding copies whenever possible. This is done by
> scatterlist_builder.[ch].
>
> virtio_media_ioctls.c proxies each supported ioctl to the host, using
> code generated through macros for ioctls that can be forwarded directly,
> which is most of them.
>
> virtio_media_driver.c provides the expected driver hooks, and support
> for mmapping and polling.
>
> This version supports MMAP buffers, while USERPTR buffers can also be
> enabled through a driver option. DMABUF support is still pending.
>
> Signed-off-by: Alexandre Courbot <acourbot@google.com>
> Signed-off-by: Alexandre Courbot <gnurou@gmail.com>
> ---
> This patch adds the virtio-media kernel driver. Virtio-media [1]
> encapsulates the V4L2 structures and protocol to enable the
> virtualization of host media devices into a guest. It's specification is
> in the final stages [2] of being merged and the virtualization of
> cameras and video accelerator devices has already been demonstrated
> using crosvm [3] and QEmu. v4l2-compliance also passes on all tested
> devices, which includes the "simple" virtual test device, proxied host
> UVC and vivid devices, and the FFmpeg virtual decoder devices (refer to
> [3] in order to test these if desired).
>
> Virtio-media is merged in AOSP [4] and ChromeOS. Upstreaming of the
> driver is overdue, but I hope we can start the review process and
> converge into something that can be merged.
>
> Limitations:
>
> - The driver is currently only available to little-endian, 64-bit
> kernels. This is because some of the V4L2 structures used for
> communication between guest and host have a layout dependent on the
> architecture, and the virtio-media protocol is standardized on the
> little-endian 64-bit versions. This can be fixed with a conversion
> layer similar to the one used to convert 32-bit ioctls to their 64-bit
> counterpart.
> - DMABUF support is currently missing. It should be implemented using
> virtio objects, with possible support for memfds using the
> SHARED_PAGES memory type.
> - No support for the media API and requests. While the use-case for
> these is less important on virtual devices where we want to present an
> abstraction as high as possible to limit VM exits, they do exist and
> it would be nice to add behind a virtio feature bit.
> - Locking in the driver is still very basic. This is something I want to
> improve before merging, but I didn't want to delay upstream review any
> further.
>
> [1] https://github.com/chromeos/virtio-media
> [2] https://lore.kernel.org/virtio-comment/20250304130134.1856056-1-aesteve@redhat.com/
> [3] https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
> [4] https://android.googlesource.com/platform/external/virtio-media/
> ---
> Changes in v3:
> - Rebased on top of v6.15-rc1 and removes obsolete control callbacks.
> - Link to v2: https://lore.kernel.org/r/20250201-virtio-media-v2-1-ac840681452d@gmail.com
>
> Changes in v2:
> - Fixed kernel test robot and media CI warnings (ignored a few false
> positives).
> - Changed in-driver email address to personal one since my Google one
> will soon become invalid.
> - Link to v1: https://lore.kernel.org/r/20250123-virtio-media-v1-1-81e2549b86b9@gmail.com
> ---
> MAINTAINERS | 6 +
> drivers/media/Kconfig | 13 +
> drivers/media/Makefile | 2 +
> drivers/media/virtio/Makefile | 9 +
> drivers/media/virtio/protocol.h | 288 ++++++
> drivers/media/virtio/scatterlist_builder.c | 563 ++++++++++++
> drivers/media/virtio/scatterlist_builder.h | 111 +++
> drivers/media/virtio/session.h | 109 +++
> drivers/media/virtio/virtio_media.h | 93 ++
> drivers/media/virtio/virtio_media_driver.c | 959 ++++++++++++++++++++
> drivers/media/virtio/virtio_media_ioctls.c | 1297 ++++++++++++++++++++++++++++
> include/uapi/linux/virtio_ids.h | 1 +
> 12 files changed, 3451 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 96b82704950184bd71623ff41fc4df31e4c7fe87..f60e17011124fe8c0be0343d4f87e1458f311dcc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -25641,6 +25641,12 @@ S: Maintained
> F: drivers/iommu/virtio-iommu.c
> F: include/uapi/linux/virtio_iommu.h
>
> +VIRTIO MEDIA DRIVER
> +M: Alexandre Courbot <gnurou@gmail.com>
> +L: linux-media@vger.kernel.org
> +S: Maintained
> +F: drivers/media/virtio/
> +
> VIRTIO MEM DRIVER
> M: David Hildenbrand <david@redhat.com>
> L: virtualization@lists.linux.dev
> diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
> index 6abc9302cd84d8563b7877d3d3da4b7e05a6b5d2..12bbb169c0b04565271092c7ac608b0fb11c0244 100644
> --- a/drivers/media/Kconfig
> +++ b/drivers/media/Kconfig
> @@ -230,6 +230,19 @@ source "drivers/media/platform/Kconfig"
> source "drivers/media/mmc/Kconfig"
> endif
>
> +config MEDIA_VIRTIO
> + tristate "Virtio-media Driver"
> + depends on VIRTIO && VIDEO_DEV && 64BIT && (X86 || (ARM && CPU_LITTLE_ENDIAN))
> + select VIDEOBUF2_CORE
> + select VIDEOBUF2_MEMOPS
> + help
> + Enables the virtio-media driver.
> +
> + This driver is used to virtualize media devices such as cameras or
> + decoders from a host into a guest using the V4L2 protocol.
> +
> + If unsure, say N.
> +
> if MEDIA_TEST_SUPPORT
> source "drivers/media/test-drivers/Kconfig"
> endif
> diff --git a/drivers/media/Makefile b/drivers/media/Makefile
> index 20fac24e4f0f13134c12cd859141c8b0387030fa..7a1377661919701f27f4fa2b5ee2dcb1045deb3c 100644
> --- a/drivers/media/Makefile
> +++ b/drivers/media/Makefile
> @@ -25,6 +25,8 @@ obj-y += rc/
>
> obj-$(CONFIG_CEC_CORE) += cec/
>
> +obj-$(CONFIG_MEDIA_VIRTIO) += virtio/
> +
> #
> # Finally, merge the drivers that require the core
> #
> diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..16f91304420d70e1212cc46f3b12f314a510c051
> --- /dev/null
> +++ b/drivers/media/virtio/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for the virtio-media device driver.
> +
> +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/protocol.h b/drivers/media/virtio/protocol.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..a22758cda5aabe75c5c94ce8d1b40583c8652710
> --- /dev/null
> +++ b/drivers/media/virtio/protocol.h
> @@ -0,0 +1,288 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Definitions of virtio-media protocol structures.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#ifndef __VIRTIO_MEDIA_PROTOCOL_H
> +#define __VIRTIO_MEDIA_PROTOCOL_H
> +
> +#include <linux/videodev2.h>
> +
> +/*
> + * Virtio protocol definition.
> + */
> +
> +/**
> + * struct virtio_media_cmd_header - Header for all virtio-media commands.
> + * @cmd: one of VIRTIO_MEDIA_CMD_*.
> + * @__reserved: must be set to zero by the driver.
> + *
> + * This header starts all commands from the driver to the device on the
> + * commandq.
> + */
> +struct virtio_media_cmd_header {
> + u32 cmd;
> + u32 __reserved;
> +};
> +
> +/**
> + * struct virtio_media_resp_header - Header for all virtio-media responses.
> + * @status: 0 if the command was successful, or one of the standard Linux error
> + * codes.
> + * @__reserved: must be set to zero by the device.
> + *
> + * This header starts all responses from the device to the driver on the
> + * commandq.
> + */
> +struct virtio_media_resp_header {
> + u32 status;
> + u32 __reserved;
> +};
> +
> +/**
> + * VIRTIO_MEDIA_CMD_OPEN - Command for creating a new session.
> + *
> + * This is the equivalent of calling `open` on a V4L2 device node. Upon
> + * success, a session id is returned which can be used to perform other
> + * commands on the session, notably ioctls.
> + */
> +#define VIRTIO_MEDIA_CMD_OPEN 1
> +
> +/**
> + * struct virtio_media_cmd_open - Driver command for VIRTIO_MEDIA_CMD_OPEN.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_OPEN.
> + */
> +struct virtio_media_cmd_open {
> + struct virtio_media_cmd_header hdr;
> +};
> +
> +/**
> + * struct virtio_media_resp_open - Device response for VIRTIO_MEDIA_CMD_OPEN.
> + * @hdr: header containing the status of the command.
> + * @session_id: if hdr.status == 0, contains the id of the newly created session.
> + * @__reserved: must be set to zero by the device.
> + */
> +struct virtio_media_resp_open {
> + struct virtio_media_resp_header hdr;
> + u32 session_id;
> + u32 __reserved;
> +};
> +
> +/**
> + * VIRTIO_MEDIA_CMD_CLOSE - Command for closing an active session.
> + *
> + * This is the equivalent of calling `close` on a previously opened V4L2
> + * session. All resources associated with this session will be freed and the
> + * session ID shall not be used again after queueing this command.
> + *
> + * This command does not require a response from the device.
> + */
> +#define VIRTIO_MEDIA_CMD_CLOSE 2
> +
> +/**
> + * struct virtio_media_cmd_close - Driver command for VIRTIO_MEDIA_CMD_CLOSE.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_CLOSE.
> + * @session_id: id of the session to close.
> + * @__reserved: must be set to zero by the driver.
> + */
> +struct virtio_media_cmd_close {
> + struct virtio_media_cmd_header hdr;
> + u32 session_id;
> + u32 __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;
> + u32 session_id;
> + u32 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;
> +};
> +
> +/**
> + * struct virtio_media_sg_entry - Description of part of a scattered guest memory.
> + * @start: start guest address of the memory segment.
> + * @len: length of this memory segment.
> + * @__reserved: must be set to zero by the driver.
> + */
> +struct virtio_media_sg_entry {
> + u64 start;
> + u32 len;
> + u32 __reserved;
> +};
> +
> +/**
> + * enum virtio_media_memory - Memory types supported by virtio-media.
> + * @VIRTIO_MEDIA_MMAP: memory allocated and managed by device. Can be mapped
> + * into the guest using VIRTIO_MEDIA_CMD_MMAP.
> + * @VIRTIO_MEDIA_SHARED_PAGES: memory allocated by the driver. Passed to the
> + * device using virtio_media_sg_entry.
> + * @VIRTIO_MEDIA_OBJECT: memory backed by a virtio object.
> + */
> +enum virtio_media_memory {
> + VIRTIO_MEDIA_MMAP = V4L2_MEMORY_MMAP,
> + VIRTIO_MEDIA_SHARED_PAGES = V4L2_MEMORY_USERPTR,
> + VIRTIO_MEDIA_OBJECT = V4L2_MEMORY_DMABUF,
> +};
> +
> +#define VIRTIO_MEDIA_MMAP_FLAG_RW (1 << 0)
> +
> +/**
> + * VIRTIO_MEDIA_CMD_MMAP - Command for mapping a MMAP buffer into the driver's
> + * address space.
> + *
> + */
> +#define VIRTIO_MEDIA_CMD_MMAP 4
> +
> +/**
> + * struct virtio_media_cmd_mmap - Driver command for VIRTIO_MEDIA_CMD_MMAP.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_MMAP.
> + * @session_id: ID of the session we are mapping for.
> + * @flags: combination of VIRTIO_MEDIA_MMAP_FLAG_*.
> + * @offset: mem_offset field of the plane to map, as returned by VIDIOC_QUERYBUF.
> + */
> +struct virtio_media_cmd_mmap {
> + struct virtio_media_cmd_header hdr;
> + u32 session_id;
> + u32 flags;
> + u32 offset;
> +};
> +
> +/**
> + * struct virtio_media_resp_mmap - Device response for VIRTIO_MEDIA_CMD_MMAP.
> + * @hdr: header containing the status of the command.
> + * @driver_addr: offset into SHM region 0 of the start of the mapping.
> + * @len: length of the mapping.
> + */
> +struct virtio_media_resp_mmap {
> + struct virtio_media_resp_header hdr;
> + u64 driver_addr;
> + u64 len;
> +};
> +
> +/**
> + * VIRTIO_MEDIA_CMD_MUNMAP - Unmap a MMAP buffer previously mapped using
> + * VIRTIO_MEDIA_CMD_MMAP.
> + */
> +#define VIRTIO_MEDIA_CMD_MUNMAP 5
> +
> +/**
> + * struct virtio_media_cmd_munmap - Driver command for VIRTIO_MEDIA_CMD_MUNMAP.
> + * @hdr: header with cmd member set to VIRTIO_MEDIA_CMD_MUNMAP.
> + * @driver_addr: offset into SHM region 0 at which the buffer has been previously
> + * mapped.
> + */
> +struct virtio_media_cmd_munmap {
> + struct virtio_media_cmd_header hdr;
> + u64 driver_addr;
> +};
> +
> +/**
> + * struct virtio_media_resp_munmap - Device response for VIRTIO_MEDIA_CMD_MUNMAP.
> + * @hdr: header containing the status of the command.
> + */
> +struct virtio_media_resp_munmap {
> + struct virtio_media_resp_header hdr;
> +};
> +
> +#define VIRTIO_MEDIA_EVT_ERROR 0
> +#define VIRTIO_MEDIA_EVT_DQBUF 1
> +#define VIRTIO_MEDIA_EVT_EVENT 2
> +
> +/**
> + * struct virtio_media_event_header - Header for events on the eventq.
> + * @event: one of VIRTIO_MEDIA_EVT_*
> + * @session_id: ID of the session the event applies to.
> + */
> +struct virtio_media_event_header {
> + u32 event;
> + u32 session_id;
> +};
> +
> +/**
> + * struct virtio_media_event_error - Unrecoverable device-side error.
> + * @hdr: header for the event.
> + * @errno: error code describing the kind of error that occurred.
> + * @__reserved: must to set to zero by the device.
> + *
> + * Upon receiving this event, the session mentioned in the header is considered
> + * corrupted and closed.
> + *
> + */
> +struct virtio_media_event_error {
> + struct virtio_media_event_header hdr;
> + u32 errno;
> + u32 __reserved;
> +};
> +
> +#define VIRTIO_MEDIA_MAX_PLANES VIDEO_MAX_PLANES
> +
> +/**
> + * struct virtio_media_event_dqbuf - Dequeued buffer event.
> + * @hdr: header for the event.
> + * @buffer: struct v4l2_buffer describing the buffer that has been dequeued.
> + * @planes: plane information for the dequeued buffer.
> + *
> + * This event is used to signal that a buffer is not being used anymore by the
> + * device and is returned to the driver.
> + */
> +struct virtio_media_event_dqbuf {
> + struct virtio_media_event_header hdr;
> + struct v4l2_buffer buffer;
> + struct v4l2_plane planes[VIRTIO_MEDIA_MAX_PLANES];
> +};
> +
> +/**
> + * struct virtio_media_event_event - V4L2 event.
> + * @hdr: header for the event.
> + * @event: description of the event that occurred.
> + *
> + * This event signals that a V4L2 event has been emitted for a session.
> + */
> +struct virtio_media_event_event {
> + struct virtio_media_event_header hdr;
> + struct v4l2_event event;
> +};
> +
> +/* Maximum size of an event. We will queue descriptors of this size on the eventq. */
> +#define VIRTIO_MEDIA_EVENT_MAX_SIZE sizeof(struct virtio_media_event_dqbuf)
> +
> +#endif // __VIRTIO_MEDIA_PROTOCOL_H
> diff --git a/drivers/media/virtio/scatterlist_builder.c b/drivers/media/virtio/scatterlist_builder.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..2837689f385e81c0c0a99ffd67ac583b426bf186
> --- /dev/null
> +++ b/drivers/media/virtio/scatterlist_builder.c
> @@ -0,0 +1,563 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> +
> +/*
> + * Scatterlist builder helpers for virtio-media.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#include <linux/moduleparam.h>
> +#include <linux/scatterlist.h>
> +#include <linux/videodev2.h>
> +#include <media/videobuf2-memops.h>
> +
> +#include "protocol.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 ``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 + 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)) {
> + int prev_pfn = -2;
> +
> + /*
> + * 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);
> + int 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 (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;
> + }
> + } 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;
> + }
> +
> + 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 + VIRTIO_SHADOW_BUF_SIZE) {
> + if (kaddr + sg->length >= shadow_buf + VIRTIO_SHADOW_BUF_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->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));
> +}
> +
> +/**
> + * __scatterlist_builder_add_userptr() - Add user pages to @builder.
> + * @builder: builder to use.
> + * @userptr: pointer to userspace memory that we want to add.
> + * @length: length of the data to add.
> + * @sg_list: output parameter. Upon success, points to the area of the shadow
> + * buffer containing the array of SG entries to be added to the descriptor
> + * chain.
> + * @nents: output parameter. Upon success, contains the number of entries
> + * pointed to by @sg_list.
> + *
> + * Data referenced by userspace pointers can be potentially large and very
> + * scattered, which could overwhelm the descriptor chain if added as-is. For
> + * these, we instead build an array of ``struct virtio_media_sg_entry`` in the
> + * shadow buffer and reference it using a single descriptor.
> + *
> + * This function is a helper to perform that. Callers should then add the
> + * descriptor to the chain properly.
> + *
> + * Returns -EFAULT if @userptr is not a valid user address, which is a case the
> + * driver should consider as "normal" operation. All other failures signal a
> + * problem with the driver.
> + */
> +static int
> +__scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> + unsigned long userptr, unsigned long length,
> + struct virtio_media_sg_entry **sg_list,
> + int *nents)
> +{
> + struct sg_table sg_table = {};
> + struct frame_vector *framevec;
> + struct scatterlist *sg_iter;
> + struct page **pages;
> + const unsigned int offset = userptr & ~PAGE_MASK;
> + unsigned int pages_count;
> + size_t entries_size;
> + int i;
> + int ret;
> +
> + framevec = vb2_create_framevec(userptr, length, true);
> + if (IS_ERR(framevec)) {
> + if (PTR_ERR(framevec) != -EFAULT) {
> + pr_warn("error %ld creating frame vector for userptr 0x%lx, length 0x%lx\n",
> + PTR_ERR(framevec), userptr, length);
> + } else {
> + /* -EINVAL is expected in case of invalid userptr. */
> + framevec = ERR_PTR(-EINVAL);
> + }
> + return PTR_ERR(framevec);
> + }
> +
> + pages = frame_vector_pages(framevec);
> + if (IS_ERR(pages)) {
> + pr_warn("error getting vector pages\n");
> + ret = PTR_ERR(pages);
> + goto done;
> + }
> + pages_count = frame_vector_count(framevec);
> + ret = sg_alloc_table_from_pages(&sg_table, pages, pages_count, offset,
> + length, 0);
> + if (ret) {
> + pr_warn("error creating sg table\n");
> + goto done;
> + }
> +
> + /* Allocate our actual SG in the shadow buffer. */
> + *nents = sg_nents(sg_table.sgl);
> + entries_size = sizeof(**sg_list) * *nents;
> + if (builder->shadow_buffer_pos + entries_size >
> + builder->shadow_buffer_size) {
> + ret = -ENOMEM;
> + goto free_sg;
> + }
> +
> + *sg_list = builder->shadow_buffer + builder->shadow_buffer_pos;
> + builder->shadow_buffer_pos += entries_size;
> +
> + for_each_sgtable_sg(&sg_table, sg_iter, i) {
> + struct virtio_media_sg_entry *sg_entry = &(*sg_list)[i];
> +
> + sg_entry->start = sg_phys(sg_iter);
> + sg_entry->len = sg_iter->length;
> + }
> +
> +free_sg:
> + sg_free_table(&sg_table);
> +
> +done:
> + vb2_destroy_framevec(framevec);
> + return ret;
> +}
> +
> +/**
> + * scatterlist_builder_add_userptr() - Add a user-memory buffer using an array
> + * of ``struct virtio_media_sg_entry``.
> + * @builder: builder to use.
> + * @userptr: pointer to userspace memory that we want to add.
> + * @length: length of the data to add.
> + *
> + * Upon success, an array of ``struct virtio_media_sg_entry`` referencing
> + * @userptr has been built into the shadow buffer, and that array added to the
> + * descriptor chain.
> + */
> +static int scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> + unsigned long userptr,
> + unsigned long length)
> +{
> + int ret;
> + int nents;
Could you initialize nents and sg_list?
old versions of gcc are a bit picky
https://gitlab.freedesktop.org/linux-media/users/ribalda/-/jobs/77042562#L4381
> + struct virtio_media_sg_entry *sg_list;
> +
> + ret = __scatterlist_builder_add_userptr(builder, userptr, length,
> + &sg_list, &nents);
> + if (ret)
> + return ret;
> +
> + ret = scatterlist_builder_add_data(builder, sg_list,
> + sizeof(*sg_list) * nents);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_buffer() - Add a ``v4l2_buffer`` and its planes to
> + * the descriptor chain.
> + * @builder: builder to use.
> + * @b: ``v4l2_buffer`` to add.
> + */
> +int scatterlist_builder_add_buffer(struct scatterlist_builder *builder,
> + struct v4l2_buffer *b)
> +{
> + int i;
> + int ret;
> +
> + /* Fixup: plane length must be zero if userptr is NULL */
> + if (!V4L2_TYPE_IS_MULTIPLANAR(b->type) &&
> + b->memory == V4L2_MEMORY_USERPTR && b->m.userptr == 0)
> + b->length = 0;
> +
> + /* v4l2_buffer */
> + ret = scatterlist_builder_add_data(builder, b, sizeof(*b));
> + if (ret)
> + return ret;
> +
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type) && b->length > 0) {
> + /* Fixup: plane length must be zero if userptr is NULL */
> + if (b->memory == V4L2_MEMORY_USERPTR) {
> + for (i = 0; i < b->length; i++) {
> + struct v4l2_plane *plane = &b->m.planes[i];
> +
> + if (plane->m.userptr == 0)
> + plane->length = 0;
> + }
> + }
> +
> + /* Array of v4l2_planes */
> + ret = scatterlist_builder_add_data(builder, b->m.planes,
> + sizeof(struct v4l2_plane) *
> + b->length);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_buffer_userptr() - Add the payload of a ``USERTPR``
> + * v4l2_buffer to the descriptor chain.
> + * @builder: builder to use.
> + * @b: ``v4l2_buffer`` which ``USERPTR`` payload we want to add.
> + *
> + * Add an array of ``virtio_media_sg_entry`` pointing to a ``USERPTR`` buffer's
> + * contents. Does nothing if the buffer is not of type ``USERPTR``. This is
> + * split out of :ref:`scatterlist_builder_add_buffer` because we only want to
> + * add these to the device-readable part of the descriptor chain.
> + */
> +int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *builder,
> + struct v4l2_buffer *b)
> +{
> + int i;
> + int ret;
> +
> + if (b->memory != V4L2_MEMORY_USERPTR)
> + return 0;
> +
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
> + for (i = 0; i < b->length; i++) {
> + struct v4l2_plane *plane = &b->m.planes[i];
> +
> + if (b->memory == V4L2_MEMORY_USERPTR &&
> + plane->length > 0) {
> + ret = scatterlist_builder_add_userptr(
> + builder, plane->m.userptr,
> + plane->length);
> + if (ret)
> + return ret;
> + }
> + }
> + } else if (b->length > 0) {
> + ret = scatterlist_builder_add_userptr(builder, b->m.userptr,
> + b->length);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_retrieve_buffer() - Retrieve a v4l2_buffer written by
> + * the device on the shadow buffer, if needed.
> + * @builder: builder to use.
> + * @sg_index: index of the first SG entry of the buffer in the builder's
> + * descriptor chain.
> + * @b: v4l2_buffer to copy shadow buffer data into.
> + * @orig_planes: the original ``planes`` pointer, to be restored if the buffer
> + * is multi-planar.
> + *
> + * If the v4l2_buffer pointed by @buffer_sgs was copied into the shadow buffer,
> + * then its updated content is copied back into @b. Otherwise nothing is done
> + * as the device has written into @b directly.
> + *
> + * @orig_planes is used to restore the original ``planes`` pointer in case it
> + * gets modified by the host. The specification stipulates that the host should
> + * not modify it, but we enforce this for additional safety.
> + */
> +int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builder,
> + size_t sg_index, struct v4l2_buffer *b,
> + struct v4l2_plane *orig_planes)
> +{
> + int ret;
> +
> + ret = scatterlist_builder_retrieve_data(builder, sg_index++, b);
> + if (ret)
> + return ret;
> +
> + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
> + b->m.planes = orig_planes;
> +
> + if (orig_planes != NULL) {
> + ret = scatterlist_builder_retrieve_data(
> + builder, sg_index++, b->m.planes);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_ext_ctrls() - Add a v4l2_ext_controls and its
> + * controls to @builder.
> + * @builder: builder to use.
> + * @ctrls: ``struct v4l2_ext_controls`` to add.
> + *
> + * Add @ctrls and its array of `struct v4l2_ext_control` to the descriptor chain.
> + */
> +int scatterlist_builder_add_ext_ctrls(struct scatterlist_builder *builder,
> + struct v4l2_ext_controls *ctrls)
> +{
> + int ret;
> +
> + /* v4l2_ext_controls */
> + ret = scatterlist_builder_add_data(builder, ctrls, sizeof(*ctrls));
> + if (ret)
> + return ret;
> +
> + if (ctrls->count > 0) {
> + /* array of v4l2_controls */
> + ret = scatterlist_builder_add_data(builder, ctrls->controls,
> + sizeof(ctrls->controls[0]) *
> + ctrls->count);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_add_ext_ctrls_userptrs() - Add the userspace payloads of
> + * a ``struct v4l2_ext_controls`` to the descriptor chain.
> + * @builder: builder to use.
> + * @ctrls: ``struct v4l2_ext_controls`` from which we want to add the userspace payload of.
> + *
> + * Add the userspace payloads of @ctrls to the descriptor chain. This is split
> + * out of :ref:`scatterlist_builder_add_ext_ctrls` because we only want to add
> + * these to the device-readable part of the descriptor chain.
> + */
> +int scatterlist_builder_add_ext_ctrls_userptrs(
> + struct scatterlist_builder *builder, struct v4l2_ext_controls *ctrls)
> +{
> + int i;
> + int ret;
> +
> + /* Pointers to user memory in individual controls */
> + for (i = 0; i < ctrls->count; i++) {
> + struct v4l2_ext_control *ctrl = &ctrls->controls[i];
> +
> + if (ctrl->size > 0) {
> + ret = scatterlist_builder_add_userptr(
> + builder, (unsigned long)ctrl->ptr, ctrl->size);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * scatterlist_builder_retrieve_ext_ctrls() - Retrieve controls written by the
> + * device on the shadow buffer, if needed.
> + * @builder: builder to use.
> + * @sg_index: index of the first SG entry of the controls in the builder's
> + * descriptor chain.
> + * @ctrls: ``struct v4l2_ext_controls`` to copy shadow buffer data into.
> + *
> + * If the shadow buffer is pointed to by @sg, copy its content back into @ctrls.
> + */
> +int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *builder,
> + size_t sg_index,
> + struct v4l2_ext_controls *ctrls)
> +{
> + struct v4l2_ext_control *controls_backup = ctrls->controls;
> + int ret;
> +
> + ret = scatterlist_builder_retrieve_data(builder, sg_index++, ctrls);
> + if (ret)
> + return ret;
> +
> + ctrls->controls = controls_backup;
> +
> + if (ctrls->count > 0 && ctrls->controls) {
> + ret = scatterlist_builder_retrieve_data(builder, sg_index++,
> + ctrls->controls);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/media/virtio/scatterlist_builder.h b/drivers/media/virtio/scatterlist_builder.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..c8323c31ac21953580a0b4a4cb366841e510666f
> --- /dev/null
> +++ b/drivers/media/virtio/scatterlist_builder.h
> @@ -0,0 +1,111 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Scatterlist builder helpers for virtio-media.
> + *
> + * Copyright (c) 2024-2025 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 ``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);
> +
> +int scatterlist_builder_add_buffer(struct scatterlist_builder *builder,
> + struct v4l2_buffer *buffer);
> +
> +int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *builder,
> + struct v4l2_buffer *b);
> +
> +int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builder,
> + size_t sg_index,
> + struct v4l2_buffer *buffer,
> + struct v4l2_plane *orig_planes);
> +
> +int scatterlist_builder_add_ext_ctrls(struct scatterlist_builder *builder,
> + struct v4l2_ext_controls *ctrls);
> +
> +int scatterlist_builder_add_ext_ctrls_userptrs(
> + struct scatterlist_builder *builder, struct v4l2_ext_controls *ctrls);
> +
> +int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *builder,
> + size_t sg_index,
> + struct v4l2_ext_controls *ctrls);
> +
> +#endif // __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H
> diff --git a/drivers/media/virtio/session.h b/drivers/media/virtio/session.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..b643d0d950477d56d4bb5db481818a3912af5c1f
> --- /dev/null
> +++ b/drivers/media/virtio/session.h
> @@ -0,0 +1,109 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Definitions of virtio-media session related structures.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#ifndef __VIRTIO_MEDIA_SESSION_H
> +#define __VIRTIO_MEDIA_SESSION_H
> +
> +#include <linux/scatterlist.h>
> +#include <media/v4l2-fh.h>
> +
> +#include "protocol.h"
> +
> +#define VIRTIO_MEDIA_LAST_QUEUE (V4L2_BUF_TYPE_META_OUTPUT)
> +
> +/*
> + * 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_buffer - Current state of a buffer.
> + * @buffer: ``struct v4l2_buffer`` with current information about the buffer.
> + * @planes: backing planes array for @buffer.
> + * @list: link into the list of buffers pending dequeue.
> + */
> +struct virtio_media_buffer {
> + struct v4l2_buffer buffer;
> + struct v4l2_plane planes[VIDEO_MAX_PLANES];
> + struct list_head list;
> +};
> +
> +/**
> + * struct virtio_media_queue_state - Represents the state of a V4L2 queue.
> + * @streaming: Whether the queue is currently streaming.
> + * @allocated_bufs: How many buffers are currently allocated.
> + * @is_capture_last: set to true when the last buffer has been received on a
> + * capture queue, so we can return -EPIPE on subsequent DQBUF requests.
> + * @buffers: Buffer state array of size @allocated_bufs.
> + * @queued_bufs: How many buffers are currently queued on the device.
> + * @pending_dqbufs: Buffers that are available for being dequeued.
> + */
> +struct virtio_media_queue_state {
> + bool streaming;
> + size_t allocated_bufs;
> + bool is_capture_last;
> +
> + struct virtio_media_buffer *buffers;
> + size_t queued_bufs;
> + struct list_head pending_dqbufs;
> +};
> +
> +/**
> + * struct virtio_media_session - A session on a virtio_media device.
> + * @fh: file handler for the session.
> + * @id: session ID used to communicate with the device.
> + * @nonblocking_dequeue: whether dequeue should block or not (nonblocking if
> + * file opened with O_NONBLOCK).
> + * @uses_mplane: whether the queues for this session use the MPLANE API or not.
> + * @cmd: union of session-related commands. A session can have one command currently running.
> + * @resp: union of session-related responses. A session can wait on one command only.
> + * @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.
> + * @queues: state of all the queues for this session.
> + * @queues_lock: protects all members fo the queues for this session.
> + * virtio_media_queue_state`.
> + * @dqbuf_wait: waitqueue for dequeued buffers, if ``VIDIOC_DQBUF`` needs to
> + * block or when polling.
> + * @list: link into the list of sessions for the device.
> + */
> +struct virtio_media_session {
> + struct v4l2_fh fh;
> + u32 id;
> + bool nonblocking_dequeue;
> + bool uses_mplane;
> +
> + union {
> + struct virtio_media_cmd_close close;
> + struct virtio_media_cmd_ioctl ioctl;
> + struct virtio_media_cmd_mmap mmap;
> + } cmd;
> +
> + union {
> + struct virtio_media_resp_ioctl ioctl;
> + struct virtio_media_resp_mmap mmap;
> + } resp;
> +
> + void *shadow_buf;
> +
> + struct sg_table command_sgs;
> +
> + struct virtio_media_queue_state queues[VIRTIO_MEDIA_LAST_QUEUE + 1];
> + struct mutex queues_lock;
> + wait_queue_head_t dqbuf_wait;
> +
> + struct list_head list;
> +};
> +
> +static inline struct virtio_media_session *fh_to_session(struct v4l2_fh *fh)
> +{
> + return container_of(fh, struct virtio_media_session, fh);
> +}
> +
> +#endif // __VIRTIO_MEDIA_SESSION_H
> diff --git a/drivers/media/virtio/virtio_media.h b/drivers/media/virtio/virtio_media.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..0aa503defdd6a08e12335276f7ccbabc3d53df09
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media.h
> @@ -0,0 +1,93 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> +
> +/*
> + * Virtio-media structures & functions declarations.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#ifndef __VIRTIO_MEDIA_H
> +#define __VIRTIO_MEDIA_H
> +
> +#include <linux/virtio_config.h>
> +#include <media/v4l2-device.h>
> +
> +#include "protocol.h"
> +
> +#define DESC_CHAIN_MAX_LEN SG_MAX_SINGLE_ALLOC
> +
> +#define VIRTIO_MEDIA_DEFAULT_DRIVER_NAME "virtio-media"
> +
> +extern char *virtio_media_driver_name;
> +extern bool virtio_media_allow_userptr;
> +
> +/**
> + * struct virtio_media - Virtio-media device.
> + * @v4l2_dev: v4l2_device for the media device.
> + * @video_dev: video_device for the media device.
> + * @virtio_dev: virtio device for the media device.
> + * @commandq: virtio command queue.
> + * @eventq: virtio event queue.
> + * @eventq_work: work to run when events are received on @eventq.
> + * @mmap_region: region into which MMAP buffers are mapped by the host.
> + * @event_buffer: buffer for event descriptors.
> + * @sessions: list of active sessions on the device.
> + * @sessions_lock: protects @sessions and ``virtio_media_session::list``.
> + * @events_lock: prevents concurrent processing of events.
> + * @cmd: union of device-related commands.
> + * @resp: union of device-related responses.
> + * @vlock: serializes access to the command queue.
> + * @wq: waitqueue for host responses on the command queue.
> + */
> +struct virtio_media {
> + struct v4l2_device v4l2_dev;
> + struct video_device video_dev;
> +
> + struct virtio_device *virtio_dev;
> + struct virtqueue *commandq;
> + struct virtqueue *eventq;
> + struct work_struct eventq_work;
> +
> + struct virtio_shm_region mmap_region;
> +
> + void *event_buffer;
> +
> + struct list_head sessions;
> + struct mutex sessions_lock;
> +
> + struct mutex events_lock;
> +
> + union {
> + struct virtio_media_cmd_open open;
> + struct virtio_media_cmd_munmap munmap;
> + } cmd;
> +
> + union {
> + struct virtio_media_resp_open open;
> + struct virtio_media_resp_munmap munmap;
> + } resp;
> +
> + struct mutex vlock;
> + wait_queue_head_t wq;
> +};
> +
> +static inline struct virtio_media *
> +to_virtio_media(struct video_device *video_dev)
> +{
> + return container_of(video_dev, struct virtio_media, video_dev);
> +}
> +
> +/* virtio_media_driver.c */
> +
> +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);
> +void virtio_media_process_events(struct virtio_media *vv);
> +
> +/* 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
> new file mode 100644
> index 0000000000000000000000000000000000000000..e8d6dc453f2240c7809152c2a04813120bd3aca2
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media_driver.c
> @@ -0,0 +1,959 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> +
> +/*
> + * Virtio-media driver.
> + *
> + * Copyright (c) 2024-2025 Google LLC.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dev_printk.h>
> +#include <linux/mm.h>
> +#include <linux/mutex.h>
> +#include <linux/scatterlist.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/vmalloc.h>
> +#include <linux/wait.h>
> +#include <linux/workqueue.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/virtio.h>
> +#include <linux/virtio_config.h>
> +#include <linux/virtio_ids.h>
> +
> +#include <media/frame_vector.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-event.h>
> +#include <media/videobuf2-memops.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-ioctl.h>
> +
> +#include "protocol.h"
> +#include "session.h"
> +#include "virtio_media.h"
> +
> +#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
> +
> +/*
> + * Name of the driver to expose to user-space.
> + *
> + * This is configurable because v4l2-compliance has workarounds specific to
> + * some drivers. When proxying these directly from the host, this allows it to
> + * apply them as needed.
> + */
> +char *virtio_media_driver_name;
> +module_param_named(driver_name, virtio_media_driver_name, charp, 0660);
> +
> +/*
> + * Whether USERPTR buffers are allowed.
> + *
> + * This is disabled by default as USERPTR buffers are dangerous, but the option
> + * is left to enable them if desired.
> + */
> +bool virtio_media_allow_userptr;
> +module_param_named(allow_userptr, virtio_media_allow_userptr, bool, 0660);
> +
> +/**
> + * virtio_media_session_alloc - Allocate a new session.
> + * @vv: virtio-media device the session belongs to.
> + * @id: ID of the session.
> + * @nonblocking_dequeue: whether dequeuing of buffers should be blocking or
> + * not.
> + *
> + * The ``id`` and ``list`` fields must still be set by the caller.
> + */
> +static struct virtio_media_session *
> +virtio_media_session_alloc(struct virtio_media *vv, u32 id,
> + bool nonblocking_dequeue)
> +{
> + struct virtio_media_session *session;
> + int i;
> + int ret;
> +
> + session = kzalloc(sizeof(*session), GFP_KERNEL);
> + if (!session)
> + 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;
> +
> + session->id = id;
> + session->nonblocking_dequeue = nonblocking_dequeue;
> +
> + INIT_LIST_HEAD(&session->list);
> + v4l2_fh_init(&session->fh, &vv->video_dev);
> + v4l2_fh_add(&session->fh);
> +
> + for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
> + INIT_LIST_HEAD(&session->queues[i].pending_dqbufs);
> + mutex_init(&session->queues_lock);
> +
> + init_waitqueue_head(&session->dqbuf_wait);
> +
> + mutex_lock(&vv->sessions_lock);
> + list_add_tail(&session->list, &vv->sessions);
> + mutex_unlock(&vv->sessions_lock);
> +
> + return session;
> +
> +err_payload_sgs:
> + kfree(session->shadow_buf);
> +err_shadow_buf:
> + kfree(session);
> +err_session:
> + return ERR_PTR(-ENOMEM);
> +}
> +
> +/**
> + * virtio_media_session_free - Free all resources of a session.
> + * @vv: virtio-media device the session belongs to.
> + * @session: session to destroy.
> + *
> + * All the resources of @sesssion, as well as the backing memory of @session
> + * itself, are freed.
> + */
> +static void virtio_media_session_free(struct virtio_media *vv,
> + struct virtio_media_session *session)
> +{
> + int i;
> +
> + mutex_lock(&vv->sessions_lock);
> + list_del(&session->list);
> + mutex_unlock(&vv->sessions_lock);
> +
> + v4l2_fh_del(&session->fh);
> + v4l2_fh_exit(&session->fh);
> +
> + sg_free_table(&session->command_sgs);
> +
> + for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
> + vfree(session->queues[i].buffers);
> +
> + kfree(session->shadow_buf);
> + kfree(session);
> +}
> +
> +/**
> + * virtio_media_session_close - Close and free a session.
> + * @vv: virtio-media device the session belongs to.
> + * @session: session to close and destroy.
> + *
> + * This send the ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and frees
> + * all resources used by @session.
> + */
> +static int virtio_media_session_close(struct virtio_media *vv,
> + struct virtio_media_session *session)
> +{
> + struct virtio_media_cmd_close *cmd_close = &session->cmd.close;
> + struct scatterlist cmd_sg = {};
> + struct scatterlist *sgs[1] = { &cmd_sg };
> + int ret;
> +
> + mutex_lock(&vv->vlock);
> +
> + cmd_close->hdr.cmd = VIRTIO_MEDIA_CMD_CLOSE;
> + cmd_close->session_id = session->id;
> +
> + sg_set_buf(&cmd_sg, cmd_close, sizeof(*cmd_close));
> + sg_mark_end(&cmd_sg);
> +
> + ret = virtio_media_send_command(vv, sgs, 1, 0, 0, NULL);
> + mutex_unlock(&vv->vlock);
> + if (ret < 0)
> + return ret;
> +
> + virtio_media_session_free(vv, session);
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_find_session - Lookup for the session with a given ID.
> + * @vv: virtio-media device to lookup the session from.
> + * @id: ID of the session to lookup.
> + */
> +static struct virtio_media_session *
> +virtio_media_find_session(struct virtio_media *vv, u32 id)
> +{
> + struct list_head *p;
> + struct virtio_media_session *session = NULL;
> +
> + mutex_lock(&vv->sessions_lock);
> + list_for_each(p, &vv->sessions) {
> + struct virtio_media_session *s =
> + list_entry(p, struct virtio_media_session, list);
> + if (s->id == id) {
> + session = s;
> + break;
> + }
> + }
> + mutex_unlock(&vv->sessions_lock);
> +
> + return session;
> +}
> +
> +/**
> + * struct virtio_media_cmd_callback_param - Callback parameters to the virtio command queue.
> + * @vv: virtio-media device in use.
> + * @done: flag to be switched once the command is completed.
> + * @resp_len: length of the received response from the command. Only valid
> + * after @done_flag has switched to ``true``.
> + */
> +struct virtio_media_cmd_callback_param {
> + struct virtio_media *vv;
> + bool done;
> + size_t resp_len;
> +};
> +
> +/**
> + * commandq_callback: Callback for the command queue.
> + * @queue: command virtqueue.
> + *
> + * This just wakes up the thread that was waiting on the command to complete.
> + */
> +static void commandq_callback(struct virtqueue *queue)
> +{
> + unsigned int len;
> + struct virtio_media_cmd_callback_param *param;
> +
> +process_bufs:
> + while ((param = virtqueue_get_buf(queue, &len))) {
> + param->done = true;
> + param->resp_len = len;
> + wake_up(¶m->vv->wq);
> + }
> +
> + if (!virtqueue_enable_cb(queue)) {
> + virtqueue_disable_cb(queue);
> + goto process_bufs;
> + }
> +}
> +
> +/**
> + * virtio_media_kick_command - send a command to the commandq.
> + * @vv: virtio-media device in use.
> + * @sgs: descriptor chain to send.
> + * @out_sgs: number of device-readable descriptors in @sgs.
> + * @in_sgs: number of device-writable descriptors in @sgs.
> + * @resp_len: output parameter. Upon success, contains the size of the response
> + * in bytes.
> + *
> + */
> +static int virtio_media_kick_command(struct virtio_media *vv,
> + struct scatterlist **sgs,
> + const size_t out_sgs, const size_t in_sgs,
> + size_t *resp_len)
> +{
> + struct virtio_media_cmd_callback_param cb_param = {
> + .vv = vv,
> + .done = false,
> + .resp_len = 0,
> + };
> + struct virtio_media_resp_header *resp_header;
> + int ret;
> +
> + ret = virtqueue_add_sgs(vv->commandq, sgs, out_sgs, in_sgs, &cb_param,
> + GFP_ATOMIC);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to add sgs to command virtqueue\n");
> + return ret;
> + }
> +
> + if (!virtqueue_kick(vv->commandq)) {
> + v4l2_err(&vv->v4l2_dev, "failed to kick command virtqueue\n");
> + return -EINVAL;
> + }
> +
> + /* Wait for the response. */
> + ret = wait_event_timeout(vv->wq, cb_param.done, 5 * HZ);
> + if (ret == 0) {
> + v4l2_err(&vv->v4l2_dev,
> + "timed out waiting for response to command\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (resp_len)
> + *resp_len = cb_param.resp_len;
> +
> + if (in_sgs > 0) {
> + /*
> + * If we expect a response, make sure we have at least a
> + * response header - anything shorter is invalid.
> + */
> + if (cb_param.resp_len < sizeof(*resp_header)) {
> + v4l2_err(&vv->v4l2_dev,
> + "received response header is too short\n");
> + return -EINVAL;
> + }
> +
> + resp_header = sg_virt(sgs[out_sgs]);
> + if (resp_header->status)
> + /* Host returns a positive error code. */
> + return -resp_header->status;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_send_command - Send a command to the device and wait for its
> + * response.
> + * @vv: virtio-media device in use.
> + * @sgs: descriptor chain to send.
> + * @out_sgs: number of device-readable descriptors in @sgs.
> + * @in_sgs: number of device-writable descriptors in @sgs.
> + * @minimum_resp_len: minimum length of the response expected by the caller
> + * when the command is successful. Anything shorter than that will result in
> + * ``-EINVAL`` being returned.
> + * @resp_len: output parameter. Upon success, contains the size of the response
> + * in bytes.
> + */
> +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)
> +{
> + size_t local_resp_len = resp_len ? *resp_len : 0;
> + int ret = virtio_media_kick_command(vv, sgs, out_sgs, in_sgs,
> + &local_resp_len);
> + if (resp_len)
> + *resp_len = local_resp_len;
> +
> + /* If the host could not process the command, there is no valid response */
> + if (ret < 0)
> + return ret;
> +
> + /* Make sure the host wrote a complete reply. */
> + if (local_resp_len < minimum_resp_len) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "received response is too short: received %zu, expected at least %zu\n",
> + local_resp_len, minimum_resp_len);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_send_event_buffer() - Sends an event buffer to the host so it
> + * can return it with an event.
> + * @vv: virtio-media device in use.
> + * @event_buffer: pointer to the event buffer to send to the device.
> + */
> +static int virtio_media_send_event_buffer(struct virtio_media *vv,
> + void *event_buffer)
> +{
> + struct scatterlist *sgs[1], vresp;
> + int ret;
> +
> + sg_init_one(&vresp, event_buffer, VIRTIO_MEDIA_EVENT_MAX_SIZE);
> + sgs[0] = &vresp;
> +
> + ret = virtqueue_add_sgs(vv->eventq, sgs, 0, 1, event_buffer,
> + GFP_ATOMIC);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to add sgs to event virtqueue\n");
> + return ret;
> + }
> +
> + if (!virtqueue_kick(vv->eventq)) {
> + v4l2_err(&vv->v4l2_dev, "failed to kick event virtqueue\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * eventq_callback() - Callback for the event queue.
> + * @queue: event virtqueue.
> + *
> + * This just schedules for event work to be run.
> + */
> +static void eventq_callback(struct virtqueue *queue)
> +{
> + struct virtio_media *vv = queue->vdev->priv;
> +
> + schedule_work(&vv->eventq_work);
> +}
> +
> +/**
> + * virtio_media_process_dqbuf_event() - Process a dequeued event for a session.
> + * @vv: virtio-media device in use.
> + * @session: session the event is addressed to.
> + * @dqbuf_evt: the dequeued event to process.
> + *
> + * Invalid events are ignored with an error log.
> + */
> +static void
> +virtio_media_process_dqbuf_event(struct virtio_media *vv,
> + struct virtio_media_session *session,
> + struct virtio_media_event_dqbuf *dqbuf_evt)
> +{
> + struct virtio_media_buffer *dqbuf;
> + const enum v4l2_buf_type queue_type = dqbuf_evt->buffer.type;
> + struct virtio_media_queue_state *queue;
> + typeof(dqbuf->buffer.m) buffer_m;
> + typeof(dqbuf->buffer.m.planes[0].m) plane_m;
> + int i;
> +
> + if (queue_type >= ARRAY_SIZE(session->queues)) {
> + v4l2_err(&vv->v4l2_dev,
> + "unmanaged queue %d passed to dqbuf event",
> + dqbuf_evt->buffer.type);
> + return;
> + }
> + queue = &session->queues[queue_type];
> +
> + if (dqbuf_evt->buffer.index >= queue->allocated_bufs) {
> + v4l2_err(&vv->v4l2_dev,
> + "invalid buffer ID %d for queue %d in dqbuf event",
> + dqbuf_evt->buffer.index, dqbuf_evt->buffer.type);
> + return;
> + }
> +
> + dqbuf = &queue->buffers[dqbuf_evt->buffer.index];
> +
> + /*
> + * Preserve the 'm' union that was passed to us during QBUF so userspace
> + * gets back the information it submitted.
> + */
> + buffer_m = dqbuf->buffer.m;
> + memcpy(&dqbuf->buffer, &dqbuf_evt->buffer, sizeof(dqbuf->buffer));
> + dqbuf->buffer.m = buffer_m;
> + if (V4L2_TYPE_IS_MULTIPLANAR(dqbuf->buffer.type)) {
> + if (dqbuf->buffer.length > VIDEO_MAX_PLANES) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "invalid number of planes received from host for a multiplanar buffer\n");
> + return;
> + }
> + for (i = 0; i < dqbuf->buffer.length; i++) {
> + plane_m = dqbuf->planes[i].m;
> + memcpy(&dqbuf->planes[i], &dqbuf_evt->planes[i],
> + sizeof(struct v4l2_plane));
> + dqbuf->planes[i].m = plane_m;
> + }
> + }
> +
> + /* Set the DONE flag as the buffer is waiting for being dequeued. */
> + dqbuf->buffer.flags |= V4L2_BUF_FLAG_DONE;
> +
> + mutex_lock(&session->queues_lock);
> + list_add_tail(&dqbuf->list, &queue->pending_dqbufs);
> + queue->queued_bufs -= 1;
> + mutex_unlock(&session->queues_lock);
> +
> + wake_up(&session->dqbuf_wait);
> +}
> +
> +/**
> + * virtio_media_process_events() - Process all pending events on a device.
> + * @vv: device which pending events we want to process.
> + *
> + * Retrieves all pending events on @vv's event queue and dispatch them to their
> + * corresponding session.
> + *
> + * Invalid events are ignored with an error log.
> + */
> +void virtio_media_process_events(struct virtio_media *vv)
> +{
> + struct virtio_media_event_error *error_evt;
> + struct virtio_media_event_dqbuf *dqbuf_evt;
> + struct virtio_media_event_event *event_evt;
> + struct virtio_media_session *session;
> + struct virtio_media_event_header *evt;
> + unsigned int len;
> +
> + mutex_lock(&vv->events_lock);
> +
> +process_bufs:
> + while ((evt = virtqueue_get_buf(vv->eventq, &len))) {
> + /* Make sure we received enough data */
> + if (len < sizeof(*evt)) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "event is too short: got %u, expected at least %zu\n",
> + len, sizeof(*evt));
> + goto end_of_event;
> + }
> +
> + session = virtio_media_find_session(vv, evt->session_id);
> + if (!session) {
> + v4l2_err(&vv->v4l2_dev, "cannot find session %d\n",
> + evt->session_id);
> + goto end_of_event;
> + }
> +
> + switch (evt->event) {
> + case VIRTIO_MEDIA_EVT_ERROR:
> + if (len < sizeof(*error_evt)) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "error event is too short: got %u, expected %zu\n",
> + len, sizeof(*error_evt));
> + break;
> + }
> + error_evt = (struct virtio_media_event_error *)evt;
> + v4l2_err(&vv->v4l2_dev,
> + "received error %d for session %d",
> + error_evt->errno, error_evt->hdr.session_id);
> + virtio_media_session_close(vv, session);
> + break;
> +
> + /*
> + * Dequeued buffer: put it into the right queue so user-space can dequeue
> + * it.
> + */
> + case VIRTIO_MEDIA_EVT_DQBUF:
> + if (len < sizeof(*dqbuf_evt)) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "dqbuf event is too short: got %u, expected %zu\n",
> + len, sizeof(*dqbuf_evt));
> + break;
> + }
> + dqbuf_evt = (struct virtio_media_event_dqbuf *)evt;
> + virtio_media_process_dqbuf_event(vv, session,
> + dqbuf_evt);
> + break;
> +
> + case VIRTIO_MEDIA_EVT_EVENT:
> + if (len < sizeof(*event_evt)) {
> + v4l2_err(
> + &vv->v4l2_dev,
> + "session event is too short: got %u expected %zu\n",
> + len, sizeof(*event_evt));
> + break;
> + }
> +
> + event_evt = (struct virtio_media_event_event *)evt;
> + v4l2_event_queue_fh(&session->fh, &event_evt->event);
> + break;
> +
> + default:
> + v4l2_err(&vv->v4l2_dev, "unknown event type %d\n",
> + evt->event);
> + break;
> + }
> +
> +end_of_event:
> + virtio_media_send_event_buffer(vv, evt);
> + }
> +
> + if (!virtqueue_enable_cb(vv->eventq)) {
> + virtqueue_disable_cb(vv->eventq);
> + goto process_bufs;
> + }
> +
> + mutex_unlock(&vv->events_lock);
> +}
> +
> +static void virtio_media_event_work(struct work_struct *work)
> +{
> + struct virtio_media *vv =
> + container_of(work, struct virtio_media, eventq_work);
> +
> + virtio_media_process_events(vv);
> +}
> +
> +/**
> + * virtio_media_device_open() - Create a new session from an opened file.
> + * @file: opened file for the session.
> + */
> +static int virtio_media_device_open(struct file *file)
> +{
> + struct video_device *video_dev = video_devdata(file);
> + struct virtio_media *vv = to_virtio_media(video_dev);
> + struct virtio_media_cmd_open *cmd_open = &vv->cmd.open;
> + struct virtio_media_resp_open *resp_open = &vv->resp.open;
> + struct scatterlist cmd_sg = {}, resp_sg = {};
> + struct scatterlist *sgs[2] = { &cmd_sg, &resp_sg };
> + struct virtio_media_session *session;
> + u32 session_id;
> + int ret;
> +
> + mutex_lock(&vv->vlock);
> +
> + sg_set_buf(&cmd_sg, cmd_open, sizeof(*cmd_open));
> + sg_mark_end(&cmd_sg);
> +
> + sg_set_buf(&resp_sg, resp_open, sizeof(*resp_open));
> + sg_mark_end(&resp_sg);
> +
> + cmd_open->hdr.cmd = VIRTIO_MEDIA_CMD_OPEN;
> + ret = virtio_media_send_command(vv, sgs, 1, 1, sizeof(*resp_open),
> + NULL);
> + session_id = resp_open->session_id;
> + mutex_unlock(&vv->vlock);
> + if (ret < 0)
> + return ret;
> +
> + session = virtio_media_session_alloc(vv, session_id,
> + (file->f_flags & O_NONBLOCK));
> + if (IS_ERR(session))
> + return PTR_ERR(session);
> +
> + file->private_data = &session->fh;
> +
> + return 0;
> +}
> +
> +/**
> + * virtio_media_device_close() - Close a previously opened session.
> + * @file: file of the session to close.
> + *
> + * This sends to ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and close
> + * the session on the driver side.
> + */
> +static int virtio_media_device_close(struct file *file)
> +{
> + 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);
> +
> + 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 : 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)
> +{
> + struct device *dev = &virtio_dev->dev;
> + struct virtqueue *vqs[2];
> + static struct virtqueue_info vq_info[2] = {
> + {
> + .name = "command",
> + .callback = commandq_callback,
> + },
> + {
> + .name = "event",
> + .callback = eventq_callback,
> + },
> + };
> + struct virtio_media *vv;
> + struct video_device *vd;
> + int i;
> + int ret;
> +
> + vv = devm_kzalloc(dev, sizeof(*vv), GFP_KERNEL);
> + if (!vv)
> + return -ENOMEM;
> +
> + vv->event_buffer = devm_kzalloc(
> + dev, VIRTIO_MEDIA_EVENT_MAX_SIZE * VIRTIO_MEDIA_NUM_EVENT_BUFS,
> + GFP_KERNEL);
> + if (!vv->event_buffer)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&vv->sessions);
> + mutex_init(&vv->sessions_lock);
> + mutex_init(&vv->events_lock);
> + mutex_init(&vv->vlock);
> +
> + vv->virtio_dev = virtio_dev;
> + virtio_dev->priv = vv;
> +
> + init_waitqueue_head(&vv->wq);
> +
> + ret = v4l2_device_register(dev, &vv->v4l2_dev);
> + if (ret)
> + return ret;
> +
> + ret = virtio_find_vqs(virtio_dev, 2, vqs, vq_info, NULL);
> + if (ret)
> + goto err_find_vqs;
> +
> + vv->commandq = vqs[0];
> + 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->device_caps = virtio_cread32(virtio_dev, 0);
> + if (vd->device_caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))
> + vd->vfl_dir = VFL_DIR_M2M;
> + else if (vd->device_caps &
> + (V4L2_CAP_VIDEO_OUTPUT | V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE))
> + vd->vfl_dir = VFL_DIR_TX;
> + else
> + vd->vfl_dir = VFL_DIR_RX;
> + vd->release = video_device_release_empty;
> + strscpy(vd->name, "virtio-media", sizeof(vd->name));
> +
> + video_set_drvdata(vd, vv);
> +
> + ret = video_register_device(vd, virtio_cread32(virtio_dev, 4), 0);
> + if (ret)
> + goto err_register_device;
> +
> + for (i = 0; i < VIRTIO_MEDIA_NUM_EVENT_BUFS; i++) {
> + ret = virtio_media_send_event_buffer(
> + vv, vv->event_buffer + VIRTIO_MEDIA_EVENT_MAX_SIZE * i);
> + if (ret)
> + goto err_send_event_buffer;
> + }
> +
> + virtio_device_ready(virtio_dev);
> +
> + return 0;
> +
> +err_send_event_buffer:
> + video_unregister_device(&vv->video_dev);
> +err_register_device:
> + virtio_dev->config->del_vqs(virtio_dev);
> +err_find_vqs:
> + v4l2_device_unregister(&vv->v4l2_dev);
> +
> + return ret;
> +}
> +
> +static void virtio_media_remove(struct virtio_device *virtio_dev)
> +{
> + struct virtio_media *vv = virtio_dev->priv;
> + struct list_head *p, *n;
> +
> + cancel_work_sync(&vv->eventq_work);
> + virtio_reset_device(virtio_dev);
> +
> + v4l2_device_unregister(&vv->v4l2_dev);
> + virtio_dev->config->del_vqs(virtio_dev);
> + video_unregister_device(&vv->video_dev);
> +
> + list_for_each_safe(p, n, &vv->sessions) {
> + struct virtio_media_session *s =
> + list_entry(p, struct virtio_media_session, list);
> +
> + virtio_media_session_free(vv, s);
> + }
> +}
> +
> +static struct virtio_device_id id_table[] = {
> + { VIRTIO_ID_MEDIA, VIRTIO_DEV_ANY_ID },
> + { 0 },
> +};
> +
> +static unsigned int features[] = {};
> +
> +static struct virtio_driver virtio_media_driver = {
> + .feature_table = features,
> + .feature_table_size = ARRAY_SIZE(features),
> + .driver.name = VIRTIO_MEDIA_DEFAULT_DRIVER_NAME,
> + .driver.owner = THIS_MODULE,
> + .id_table = id_table,
> + .probe = virtio_media_probe,
> + .remove = virtio_media_remove,
> +};
> +
> +module_virtio_driver(virtio_media_driver);
> +
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +MODULE_DESCRIPTION("virtio media driver");
> +MODULE_AUTHOR("Alexandre Courbot <gnurou@gmail.com>");
> +MODULE_LICENSE("Dual BSD/GPL");
> diff --git a/drivers/media/virtio/virtio_media_ioctls.c b/drivers/media/virtio/virtio_media_ioctls.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..863cdfbaaadc7241110c82ce6880bc5675c23894
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media_ioctls.c
> @@ -0,0 +1,1297 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> +
> +/*
> + * Ioctls implementations for the virtio-media driver.
> + *
> + * Copyright (c) 2024-2025 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,
> + };
> + int ret;
> +
> + /* Command descriptor */
> + 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,
> + };
> + int ret;
> +
> + /* Command descriptor */
> + 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,
> + };
> + int ret;
> +
> + /* Command descriptor */
> + 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: ``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;
> +
> + /* Payload of SHARED_PAGES buffers, if relevant */
> + ret = scatterlist_builder_add_buffer_userptr(&builder, b);
> + if (ret < 0)
> + 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 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: ``v4l2_ext_controls`` to be sent as the ioctl payload.
> + *
> + * Queues an ioctl that sends a ``v4l2_ext_controls`` to the host and receives
> + * an updated version.
> + *
> + * ``v4l2_ext_controls`` has a pointer to an array of ``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 ret;
> + int i;
> +
> + /* Command descriptor */
> + 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;
> +
> + /* Event 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) \
> + { \
> + return virtio_media_send_wr_ioctl(fh, 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) \
> + { \
> + return virtio_media_send_r_ioctl(fh, 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) \
> + { \
> + return virtio_media_send_w_ioctl(fh, 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");
> +
> + if (!virtio_media_driver_name)
> + strscpy(cap->driver, VIRTIO_MEDIA_DEFAULT_DRIVER_NAME);
> + else
> + strscpy(cap->driver, virtio_media_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)
> +{
> + return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_G_EXT_CTRLS,
> + ctrls);
> +}
> +
> +static int virtio_media_s_ext_ctrls(struct file *file, void *fh,
> + struct v4l2_ext_controls *ctrls)
> +{
> + return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_S_EXT_CTRLS,
> + ctrls);
> +}
> +
> +static int virtio_media_try_ext_ctrls(struct file *file, void *fh,
> + struct v4l2_ext_controls *ctrls)
> +{
> + return virtio_media_send_ext_controls_ioctl(fh, 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;
> +
> + 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 virtio_media_session *session = fh_to_session(fh);
> + int ret;
> +
> + if (i > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + ret = virtio_media_send_w_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + int ret;
> +
> + if (i > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + ret = virtio_media_send_w_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + struct virtio_media_queue_state *queue;
> + int ret;
> +
> + if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
> + return -EINVAL;
> +
> + if (b->memory == V4L2_MEMORY_USERPTR && !virtio_media_allow_userptr)
> + return -EINVAL;
> +
> + ret = virtio_media_send_wr_ioctl(fh, 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;
> +
> + if (!virtio_media_allow_userptr)
> + 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 virtio_media_session *session = fh_to_session(fh);
> + struct virtio_media_queue_state *queue;
> + struct virtio_media_buffer *buffer;
> + int ret;
> +
> + ret = virtio_media_send_buffer_ioctl(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + 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(fh, 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->private_data);
> + 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 want to 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;
> +
> + ret = virtio_media_send_wr_ioctl(fh, 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(fh, 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;
> +
> + ret = virtio_media_send_wr_ioctl(fh, 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(fh, 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 virtio_media_session *session = fh_to_session(fh);
> + int ret;
> +
> + ret = virtio_media_send_wr_ioctl(fh, 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;
> +
> + ret = virtio_media_send_w_ioctl(fh, 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->private_data;
> +
> + mutex_lock(&vv->vlock);
> +
> + /*
> + * We need to handle a few ioctls manually because their result 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;
> +}
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index 7aa2eb76620508fdc915533f74973d76308d3ef5..b4bb0ace0b26e37224c975f89bbf669c51921816 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -68,6 +68,7 @@
> #define VIRTIO_ID_AUDIO_POLICY 39 /* virtio audio policy */
> #define VIRTIO_ID_BT 40 /* virtio bluetooth */
> #define VIRTIO_ID_GPIO 41 /* virtio gpio */
> +#define VIRTIO_ID_MEDIA 48 /* virtio media */
>
> /*
> * Virtio Transitional IDs
>
> ---
> base-commit: 0af2f6be1b4281385b618cb86ad946eded089ac8
> change-id: 20241229-virtio-media-25067bb27526
>
> Best regards,
> --
> Alexandre Courbot <gnurou@gmail.com>
>
>
--
Ricardo Ribalda
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-28 16:23 ` Ricardo Ribalda
@ 2025-06-01 9:34 ` Mauro Carvalho Chehab
2025-06-01 10:01 ` Ricardo Ribalda
0 siblings, 1 reply; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-06-01 9:34 UTC (permalink / raw)
To: Ricardo Ribalda
Cc: Alexandre Courbot, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Michael S. Tsirkin, Jason Wang, Xuan Zhuo,
Eugenio Pérez, gurchetansingh, daniel.almeida, adelva,
changyeon, nicolas.dufresne, linux-kernel, linux-media,
virtualization, Alexandre Courbot
Em Wed, 28 May 2025 18:23:02 +0200
Ricardo Ribalda <ribalda@chromium.org> escreveu:
> > +static int scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> > + unsigned long userptr,
> > + unsigned long length)
> > +{
> > + int ret;
> > + int nents;
> Could you initialize nents and sg_list?
> old versions of gcc are a bit picky
> https://gitlab.freedesktop.org/linux-media/users/ribalda/-/jobs/77042562#L4381
Please don't. In this specific case, ret is always initialized:
> + struct virtio_media_sg_entry *sg_list;
> +
> + ret = __scatterlist_builder_add_userptr(builder, userptr, length,
> + &sg_list, &nents);
nents and sg_list may or may not be initialized at the function,
but initializing it is wrong, as, when they are not initialized, the
ret code shall catch it (and if not, we *do* want gcc to warn).
So, if our CI is warning about that due to an old version, please upgrade
the version at the CI runner.
Regards,
Mauro
Thanks,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-01 9:34 ` Mauro Carvalho Chehab
@ 2025-06-01 10:01 ` Ricardo Ribalda
2025-07-24 17:24 ` Mauro Carvalho Chehab
0 siblings, 1 reply; 26+ messages in thread
From: Ricardo Ribalda @ 2025-06-01 10:01 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Alexandre Courbot, Mauro Carvalho Chehab, Hans Verkuil,
Albert Esteve, Michael S. Tsirkin, Jason Wang, Xuan Zhuo,
Eugenio Pérez, gurchetansingh, daniel.almeida, adelva,
changyeon, nicolas.dufresne, linux-kernel, linux-media,
virtualization, Alexandre Courbot
Hi Mauro
On Sun, 1 Jun 2025 at 11:34, Mauro Carvalho Chehab
<mchehab+huawei@kernel.org> wrote:
>
> Em Wed, 28 May 2025 18:23:02 +0200
> Ricardo Ribalda <ribalda@chromium.org> escreveu:
>
> > > +static int scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> > > + unsigned long userptr,
> > > + unsigned long length)
> > > +{
> > > + int ret;
> > > + int nents;
> > Could you initialize nents and sg_list?
> > old versions of gcc are a bit picky
> > https://gitlab.freedesktop.org/linux-media/users/ribalda/-/jobs/77042562#L4381
>
> Please don't. In this specific case, ret is always initialized:
>
> > + struct virtio_media_sg_entry *sg_list;
> > +
> > + ret = __scatterlist_builder_add_userptr(builder, userptr, length,
> > + &sg_list, &nents);
>
> nents and sg_list may or may not be initialized at the function,
> but initializing it is wrong, as, when they are not initialized, the
> ret code shall catch it (and if not, we *do* want gcc to warn).
>
> So, if our CI is warning about that due to an old version, please upgrade
> the version at the CI runner.
The main version of gcc works fine. It is the minimal version (8.1) required by
https://www.kernel.org/doc/html/next/process/changes.html
that complains.
>
> Regards,
> Mauro
>
>
> Thanks,
> Mauro
--
Ricardo Ribalda
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-05-27 14:03 ` Alexandre Courbot
2025-05-27 14:42 ` Mauro Carvalho Chehab
@ 2025-06-17 8:49 ` Mauro Carvalho Chehab
2025-06-17 9:03 ` Mauro Carvalho Chehab
2025-06-18 14:16 ` Alexandre Courbot
1 sibling, 2 replies; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-06-17 8:49 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Hi Alex,
Em Tue, 27 May 2025 23:03:39 +0900
Alexandre Courbot <gnurou@gmail.com> escreveu:
> > > > Btw, I was looking at:
> > > >
> > > > https://github.com/chromeos/virtio-media
> > > >
> > > > (I'm assuming that this is the QEMU counterpart, right?)
> > >
> > > crosvm actually, but QEMU support is also being worked on.
> >
> > Do you have already QEMU patches? The best is to have the Kernel driver
> > submitted altogether with QEMU, as Kernel developers need it to do the
> > tests. In my case, I never use crosvm, and I don't have any Chromebook
> > anymore.
>
> IIRC Albert Esteve was working on this, maybe he can share the current status.
Any news regards to it?
> Note that crosvm does not require a Chromebook, you can build and run
> it pretty easily on a regular PC. I have put together a document to
> help with that:
>
> https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
I started looking on it today. Already installed crossvm (I had to
install libcap-devel to build it). Still, I'm not familiar with
crossvm, which is a little be painful. In particular, how can I
enable network on it and speedup it? With suggested parameters,
it picked only one CPU, and very few memory on it:
# cat /proc/cpuinfo|grep processor
processor : 0
# free
total used free shared buff/cache available
Mem: 221876 34780 139712 272 56096 187096
Swap: 0 0 0
I'd like to be able to compile things on it and use ssh/scp. So,
the VM needs more CPUs, more memory, more network and GPU.
Btw, on a quick test with v4l2-compliance, something looks weird:
I started a camera application at the host. Still, v4l2-compliance
said successfully excecuted mmap:
Streaming ioctls:
test read/write: OK (Not Supported)
test blocking wait: OK
test MMAP (no poll): OK
test MMAP (select): OK
Vide[2025-06-17T08:44:49.177972817+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
[2025-06-17T08:44:49.178164554+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
o Capturtest MMAP (epoll): OK
test USERPTR (no poll): OK (Not Supported)
test USERPTR (select): OK (Not Supported)
test DMABUF (no poll): OK (Not Supported)
test DMABUF (select): OK (Not Supported)
Which doesn't make any sense, as the host OS should not allow access
to mmap while streaming.
Thanks,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-17 8:49 ` Mauro Carvalho Chehab
@ 2025-06-17 9:03 ` Mauro Carvalho Chehab
2025-06-17 10:20 ` Mauro Carvalho Chehab
2025-06-18 14:16 ` Alexandre Courbot
1 sibling, 1 reply; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-06-17 9:03 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Em Tue, 17 Jun 2025 10:49:38 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> escreveu:
> Hi Alex,
>
> Em Tue, 27 May 2025 23:03:39 +0900
> Alexandre Courbot <gnurou@gmail.com> escreveu:
>
> > > > > Btw, I was looking at:
> > > > >
> > > > > https://github.com/chromeos/virtio-media
> > > > >
> > > > > (I'm assuming that this is the QEMU counterpart, right?)
> > > >
> > > > crosvm actually, but QEMU support is also being worked on.
> > >
> > > Do you have already QEMU patches? The best is to have the Kernel driver
> > > submitted altogether with QEMU, as Kernel developers need it to do the
> > > tests. In my case, I never use crosvm, and I don't have any Chromebook
> > > anymore.
> >
> > IIRC Albert Esteve was working on this, maybe he can share the current status.
>
> Any news regards to it?
>
> > Note that crosvm does not require a Chromebook, you can build and run
> > it pretty easily on a regular PC. I have put together a document to
> > help with that:
> >
> > https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
>
> I started looking on it today. Already installed crossvm (I had to
> install libcap-devel to build it). Still, I'm not familiar with
> crossvm, which is a little be painful. In particular, how can I
> enable network on it and speedup it? With suggested parameters,
> it picked only one CPU, and very few memory on it:
>
> # cat /proc/cpuinfo|grep processor
> processor : 0
>
> # free
> total used free shared buff/cache available
> Mem: 221876 34780 139712 272 56096 187096
> Swap: 0 0 0
>
> I'd like to be able to compile things on it and use ssh/scp. So,
> the VM needs more CPUs, more memory, more network and GPU.
>
> Btw, on a quick test with v4l2-compliance, something looks weird:
> I started a camera application at the host. Still, v4l2-compliance
> said successfully excecuted mmap:
>
> Streaming ioctls:
> test read/write: OK (Not Supported)
> test blocking wait: OK
> test MMAP (no poll): OK
> test MMAP (select): OK
> Vide[2025-06-17T08:44:49.177972817+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> [2025-06-17T08:44:49.178164554+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> o Capturtest MMAP (epoll): OK
> test USERPTR (no poll): OK (Not Supported)
> test USERPTR (select): OK (Not Supported)
> test DMABUF (no poll): OK (Not Supported)
> test DMABUF (select): OK (Not Supported)
>
> Which doesn't make any sense, as the host OS should not allow access
> to mmap while streaming.
Ah, this was with the "simple" device, not with the proxy one.
With the proxy one, I'm getting:
# v4l2-ctl --all
Driver Info:
Driver name : virtio-media
Card type : usb video: usb video
Bus info : platform:virtio-media
Driver version : 6.15.0
Capabilities : 0x84200001
Video Capture
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04200001
Video Capture
Streaming
Extended Pix Format
Priority: 2
Video input : 0 (Camera 1: ok)
Format Video Capture:
Width/Height : 1280/720
Pixel Format : 'MJPG' (Motion-JPEG)
Field : None
Bytes per Line : 0
Size Image : 1843200
Colorspace : sRGB
Transfer Function : Rec. 709
YCbCr/HSV Encoding: ITU-R 601
Quantization : Default (maps to Full Range)
Flags :
Crop Capability Video Capture:
Bounds : Left 0, Top 0, Width 1280, Height 720
Default : Left 0, Top 0, Width 1280, Height 720
Pixel Aspect: 1/1
Selection Video Capture: crop_default, Left 0, Top 0, Width 1280, Height 720, Flags:
Selection Video Capture: crop_bounds, Left 0, Top 0, Width 1280, Height 720, Flags:
Streaming Parameters Video Capture:
Capabilities : timeperframe
Frames per second: 30.000 (30/1)
Read buffers : 0
User Controls
brightness 0x00980900 (int) : min=-128 max=127 step=1 default=-11 value=-11
contrast 0x00980901 (int) : min=0 max=255 step=1 default=148 value=148
saturation 0x00980902 (int) : min=0 max=255 step=1 default=180 value=180
hue 0x00980903 (int) : min=-128 max=127 step=1 default=0 value=0
# v4l2-compliance -d0 -s
Streaming ioctls:
test read/write: OK (Not Supported)
test blocking wait: OK
fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
test MMAP (no poll): FAIL
fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
test MMAP (select): FAIL
fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
test MMAP (epoll): FAIL
test USERPTR (no poll): OK (Not Supported)
test USERPTR (select): OK (Not Supported)
[2025-06-17T08:55:20.768760714+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
test DMABUF (no poll): OK (Not Supported)
[2025-06-17T08:55:20.769745707+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
test DMABUF (select): OK (Not Supported)
At the host, I'm getting:
Streaming ioctls:
test read/write: OK (Not Supported)
test blocking wait: OK
fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
test MMAP (no poll): FAIL
fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
test MMAP (select): FAIL
fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
test MMAP (epoll): FAIL
test USERPTR (no poll): OK
test USERPTR (select): OK
test DMABUF: Cannot test, specify --expbuf-device
The device I'm using for test is a UVC HDMI capture board:
Bus 005 Device 008: ID 534d:2109 MacroSilicon usb video
Thanks,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-17 9:03 ` Mauro Carvalho Chehab
@ 2025-06-17 10:20 ` Mauro Carvalho Chehab
2025-06-18 14:27 ` Alexandre Courbot
0 siblings, 1 reply; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-06-17 10:20 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Em Tue, 17 Jun 2025 11:03:18 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> escreveu:
> Em Tue, 17 Jun 2025 10:49:38 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> escreveu:
>
> > Hi Alex,
> >
> > Em Tue, 27 May 2025 23:03:39 +0900
> > Alexandre Courbot <gnurou@gmail.com> escreveu:
> >
> > > > > > Btw, I was looking at:
> > > > > >
> > > > > > https://github.com/chromeos/virtio-media
> > > > > >
> > > > > > (I'm assuming that this is the QEMU counterpart, right?)
> > > > >
> > > > > crosvm actually, but QEMU support is also being worked on.
> > > >
> > > > Do you have already QEMU patches? The best is to have the Kernel driver
> > > > submitted altogether with QEMU, as Kernel developers need it to do the
> > > > tests. In my case, I never use crosvm, and I don't have any Chromebook
> > > > anymore.
> > >
> > > IIRC Albert Esteve was working on this, maybe he can share the current status.
> >
> > Any news regards to it?
> >
> > > Note that crosvm does not require a Chromebook, you can build and run
> > > it pretty easily on a regular PC. I have put together a document to
> > > help with that:
> > >
> > > https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
> >
> > I started looking on it today. Already installed crossvm (I had to
> > install libcap-devel to build it). Still, I'm not familiar with
> > crossvm, which is a little be painful. In particular, how can I
> > enable network on it and speedup it? With suggested parameters,
> > it picked only one CPU, and very few memory on it:
> >
> > # cat /proc/cpuinfo|grep processor
> > processor : 0
> >
> > # free
> > total used free shared buff/cache available
> > Mem: 221876 34780 139712 272 56096 187096
> > Swap: 0 0 0
> >
> > I'd like to be able to compile things on it and use ssh/scp. So,
> > the VM needs more CPUs, more memory, more network and GPU.
Found how to setup cpus and memory, but didn't find a way to setup
network without running it as root. The gpu parameter has several
options. Not sure what backend works well for media apps like qv4l2,
camorama, X11, ...
> >
> > Btw, on a quick test with v4l2-compliance, something looks weird:
> > I started a camera application at the host. Still, v4l2-compliance
> > said successfully excecuted mmap:
> >
> > Streaming ioctls:
> > test read/write: OK (Not Supported)
> > test blocking wait: OK
> > test MMAP (no poll): OK
> > test MMAP (select): OK
> > Vide[2025-06-17T08:44:49.177972817+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> > [2025-06-17T08:44:49.178164554+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> > o Capturtest MMAP (epoll): OK
> > test USERPTR (no poll): OK (Not Supported)
> > test USERPTR (select): OK (Not Supported)
> > test DMABUF (no poll): OK (Not Supported)
> > test DMABUF (select): OK (Not Supported)
> >
> > Which doesn't make any sense, as the host OS should not allow access
> > to mmap while streaming.
>
> Ah, this was with the "simple" device, not with the proxy one.
> With the proxy one, I'm getting:
>
> # v4l2-ctl --all
> Driver Info:
> Driver name : virtio-media
> Card type : usb video: usb video
> Bus info : platform:virtio-media
> Driver version : 6.15.0
> Capabilities : 0x84200001
> Video Capture
> Streaming
> Extended Pix Format
> Device Capabilities
> Device Caps : 0x04200001
> Video Capture
> Streaming
> Extended Pix Format
> Priority: 2
> Video input : 0 (Camera 1: ok)
> Format Video Capture:
> Width/Height : 1280/720
> Pixel Format : 'MJPG' (Motion-JPEG)
> Field : None
> Bytes per Line : 0
> Size Image : 1843200
> Colorspace : sRGB
> Transfer Function : Rec. 709
> YCbCr/HSV Encoding: ITU-R 601
> Quantization : Default (maps to Full Range)
> Flags :
> Crop Capability Video Capture:
> Bounds : Left 0, Top 0, Width 1280, Height 720
> Default : Left 0, Top 0, Width 1280, Height 720
> Pixel Aspect: 1/1
> Selection Video Capture: crop_default, Left 0, Top 0, Width 1280, Height 720, Flags:
> Selection Video Capture: crop_bounds, Left 0, Top 0, Width 1280, Height 720, Flags:
> Streaming Parameters Video Capture:
> Capabilities : timeperframe
> Frames per second: 30.000 (30/1)
> Read buffers : 0
>
> User Controls
>
> brightness 0x00980900 (int) : min=-128 max=127 step=1 default=-11 value=-11
> contrast 0x00980901 (int) : min=0 max=255 step=1 default=148 value=148
> saturation 0x00980902 (int) : min=0 max=255 step=1 default=180 value=180
> hue 0x00980903 (int) : min=-128 max=127 step=1 default=0 value=0
>
> # v4l2-compliance -d0 -s
>
> Streaming ioctls:
> test read/write: OK (Not Supported)
> test blocking wait: OK
> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
> test MMAP (no poll): FAIL
> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
> test MMAP (select): FAIL
> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
> test MMAP (epoll): FAIL
> test USERPTR (no poll): OK (Not Supported)
> test USERPTR (select): OK (Not Supported)
> [2025-06-17T08:55:20.768760714+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> test DMABUF (no poll): OK (Not Supported)
> [2025-06-17T08:55:20.769745707+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> test DMABUF (select): OK (Not Supported)
>
> At the host, I'm getting:
>
> Streaming ioctls:
> test read/write: OK (Not Supported)
> test blocking wait: OK
> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
> test MMAP (no poll): FAIL
> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
> test MMAP (select): FAIL
> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
> test MMAP (epoll): FAIL
> test USERPTR (no poll): OK
> test USERPTR (select): OK
> test DMABUF: Cannot test, specify --expbuf-device
>
> The device I'm using for test is a UVC HDMI capture board:
>
> Bus 005 Device 008: ID 534d:2109 MacroSilicon usb video
>
> Thanks,
> Mauro
Thanks,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-17 8:49 ` Mauro Carvalho Chehab
2025-06-17 9:03 ` Mauro Carvalho Chehab
@ 2025-06-18 14:16 ` Alexandre Courbot
2025-06-18 14:40 ` Mauro Carvalho Chehab
1 sibling, 1 reply; 26+ messages in thread
From: Alexandre Courbot @ 2025-06-18 14:16 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Hi Mauro,
On Tue Jun 17, 2025 at 5:49 PM JST, Mauro Carvalho Chehab wrote:
> Hi Alex,
>
> Em Tue, 27 May 2025 23:03:39 +0900
> Alexandre Courbot <gnurou@gmail.com> escreveu:
>
>> > > > Btw, I was looking at:
>> > > >
>> > > > https://github.com/chromeos/virtio-media
>> > > >
>> > > > (I'm assuming that this is the QEMU counterpart, right?)
>> > >
>> > > crosvm actually, but QEMU support is also being worked on.
>> >
>> > Do you have already QEMU patches? The best is to have the Kernel driver
>> > submitted altogether with QEMU, as Kernel developers need it to do the
>> > tests. In my case, I never use crosvm, and I don't have any Chromebook
>> > anymore.
>>
>> IIRC Albert Esteve was working on this, maybe he can share the current status.
>
> Any news regards to it?
Albert shared the latest status. There is one in-flight patch series
required in qemu [1], and then this branch of vhost-device should
contain the necessary support [2]. Albert is waiting for the virtio spec
to get merged before sending a pull request IIUC.
[1] https://patchew.org/QEMU/20250217164012.246727-1-aesteve@redhat.com/
[2] https://github.com/aesteve-rh/vhost-device/tree/virtio-media
>
>> Note that crosvm does not require a Chromebook, you can build and run
>> it pretty easily on a regular PC. I have put together a document to
>> help with that:
>>
>> https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
>
> I started looking on it today. Already installed crossvm (I had to
> install libcap-devel to build it). Still, I'm not familiar with
> crossvm, which is a little be painful. In particular, how can I
> enable network on it and speedup it?
There is a "./tools/examples/setup_network" in the crosvm repository that
will setup a TAP device. Once this is done, you can pass the "--net
tap-name=crosvm_tap" argument to crosvm, and the network device should
be visible and usable.
Let me reply to the rest of your questions in your latest mail, with the
most recent logs.
Cheers,
Alex.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-17 10:20 ` Mauro Carvalho Chehab
@ 2025-06-18 14:27 ` Alexandre Courbot
2025-06-18 15:05 ` Mauro Carvalho Chehab
0 siblings, 1 reply; 26+ messages in thread
From: Alexandre Courbot @ 2025-06-18 14:27 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
On Tue Jun 17, 2025 at 7:20 PM JST, Mauro Carvalho Chehab wrote:
> Em Tue, 17 Jun 2025 11:03:18 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> escreveu:
>
>> Em Tue, 17 Jun 2025 10:49:38 +0200
>> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> escreveu:
>>
>> > Hi Alex,
>> >
>> > Em Tue, 27 May 2025 23:03:39 +0900
>> > Alexandre Courbot <gnurou@gmail.com> escreveu:
>> >
>> > > > > > Btw, I was looking at:
>> > > > > >
>> > > > > > https://github.com/chromeos/virtio-media
>> > > > > >
>> > > > > > (I'm assuming that this is the QEMU counterpart, right?)
>> > > > >
>> > > > > crosvm actually, but QEMU support is also being worked on.
>> > > >
>> > > > Do you have already QEMU patches? The best is to have the Kernel driver
>> > > > submitted altogether with QEMU, as Kernel developers need it to do the
>> > > > tests. In my case, I never use crosvm, and I don't have any Chromebook
>> > > > anymore.
>> > >
>> > > IIRC Albert Esteve was working on this, maybe he can share the current status.
>> >
>> > Any news regards to it?
>> >
>> > > Note that crosvm does not require a Chromebook, you can build and run
>> > > it pretty easily on a regular PC. I have put together a document to
>> > > help with that:
>> > >
>> > > https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
>> >
>> > I started looking on it today. Already installed crossvm (I had to
>> > install libcap-devel to build it). Still, I'm not familiar with
>> > crossvm, which is a little be painful. In particular, how can I
>> > enable network on it and speedup it? With suggested parameters,
>> > it picked only one CPU, and very few memory on it:
>> >
>> > # cat /proc/cpuinfo|grep processor
>> > processor : 0
>> >
>> > # free
>> > total used free shared buff/cache available
>> > Mem: 221876 34780 139712 272 56096 187096
>> > Swap: 0 0 0
>> >
>> > I'd like to be able to compile things on it and use ssh/scp. So,
>> > the VM needs more CPUs, more memory, more network and GPU.
>
> Found how to setup cpus and memory, but didn't find a way to setup
> network without running it as root. The gpu parameter has several
> options. Not sure what backend works well for media apps like qv4l2,
> camorama, X11, ...
I'm afraid getting GPU and graphics in general to work is more involved
and tricky on a regular Linux setup (crosvm was primarily designed for
ChromeOS). If you really need it I can do some more research; most of my
tests have been done using v4l2-ctl or ffmpeg and saving the output on
disk for later inspection.
>
>> >
>> > Btw, on a quick test with v4l2-compliance, something looks weird:
>> > I started a camera application at the host. Still, v4l2-compliance
>> > said successfully excecuted mmap:
>> >
>> > Streaming ioctls:
>> > test read/write: OK (Not Supported)
>> > test blocking wait: OK
>> > test MMAP (no poll): OK
>> > test MMAP (select): OK
>> > Vide[2025-06-17T08:44:49.177972817+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
>> > [2025-06-17T08:44:49.178164554+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
>> > o Capturtest MMAP (epoll): OK
>> > test USERPTR (no poll): OK (Not Supported)
>> > test USERPTR (select): OK (Not Supported)
>> > test DMABUF (no poll): OK (Not Supported)
>> > test DMABUF (select): OK (Not Supported)
>> >
>> > Which doesn't make any sense, as the host OS should not allow access
>> > to mmap while streaming.
>>
>> Ah, this was with the "simple" device, not with the proxy one.
>> With the proxy one, I'm getting:
>>
>> # v4l2-ctl --all
>> Driver Info:
>> Driver name : virtio-media
>> Card type : usb video: usb video
>> Bus info : platform:virtio-media
>> Driver version : 6.15.0
>> Capabilities : 0x84200001
>> Video Capture
>> Streaming
>> Extended Pix Format
>> Device Capabilities
>> Device Caps : 0x04200001
>> Video Capture
>> Streaming
>> Extended Pix Format
>> Priority: 2
>> Video input : 0 (Camera 1: ok)
>> Format Video Capture:
>> Width/Height : 1280/720
>> Pixel Format : 'MJPG' (Motion-JPEG)
>> Field : None
>> Bytes per Line : 0
>> Size Image : 1843200
>> Colorspace : sRGB
>> Transfer Function : Rec. 709
>> YCbCr/HSV Encoding: ITU-R 601
>> Quantization : Default (maps to Full Range)
>> Flags :
>> Crop Capability Video Capture:
>> Bounds : Left 0, Top 0, Width 1280, Height 720
>> Default : Left 0, Top 0, Width 1280, Height 720
>> Pixel Aspect: 1/1
>> Selection Video Capture: crop_default, Left 0, Top 0, Width 1280, Height 720, Flags:
>> Selection Video Capture: crop_bounds, Left 0, Top 0, Width 1280, Height 720, Flags:
>> Streaming Parameters Video Capture:
>> Capabilities : timeperframe
>> Frames per second: 30.000 (30/1)
>> Read buffers : 0
>>
>> User Controls
>>
>> brightness 0x00980900 (int) : min=-128 max=127 step=1 default=-11 value=-11
>> contrast 0x00980901 (int) : min=0 max=255 step=1 default=148 value=148
>> saturation 0x00980902 (int) : min=0 max=255 step=1 default=180 value=180
>> hue 0x00980903 (int) : min=-128 max=127 step=1 default=0 value=0
>>
>> # v4l2-compliance -d0 -s
>>
>> Streaming ioctls:
>> test read/write: OK (Not Supported)
>> test blocking wait: OK
>> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
>> test MMAP (no poll): FAIL
>> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
>> test MMAP (select): FAIL
>> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
>> test MMAP (epoll): FAIL
>> test USERPTR (no poll): OK (Not Supported)
>> test USERPTR (select): OK (Not Supported)
>> [2025-06-17T08:55:20.768760714+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
>> test DMABUF (no poll): OK (Not Supported)
>> [2025-06-17T08:55:20.769745707+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
>> test DMABUF (select): OK (Not Supported)
>>
>> At the host, I'm getting:
>>
>> Streaming ioctls:
>> test read/write: OK (Not Supported)
>> test blocking wait: OK
>> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
>> test MMAP (no poll): FAIL
>> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
>> test MMAP (select): FAIL
>> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
>> test MMAP (epoll): FAIL
>> test USERPTR (no poll): OK
>> test USERPTR (select): OK
>> test DMABUF: Cannot test, specify --expbuf-device
These logs look ok to me: the MMAP tests are failing on the host, so
they are also expected to fail on the guest (still I expect regular
streaming to work on both). USERPTR is not supported on the guest, as
per your request to not support this memory type in new drivers. DMABUF
is not supported at all at the moment.
If the host cannot pass compliance, the guest will inevitably suffer
from the same shortcomings. :) But at least on the devices I tested I
was still able to stream something onto the disk and the result was
correct.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-18 14:16 ` Alexandre Courbot
@ 2025-06-18 14:40 ` Mauro Carvalho Chehab
0 siblings, 0 replies; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-06-18 14:40 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Em Wed, 18 Jun 2025 23:16:47 +0900
"Alexandre Courbot" <gnurou@gmail.com> escreveu:
> Hi Mauro,
>
> On Tue Jun 17, 2025 at 5:49 PM JST, Mauro Carvalho Chehab wrote:
> > Hi Alex,
> >
> > Em Tue, 27 May 2025 23:03:39 +0900
> > Alexandre Courbot <gnurou@gmail.com> escreveu:
> >
> >> > > > Btw, I was looking at:
> >> > > >
> >> > > > https://github.com/chromeos/virtio-media
> >> > > >
> >> > > > (I'm assuming that this is the QEMU counterpart, right?)
> >> > >
> >> > > crosvm actually, but QEMU support is also being worked on.
> >> >
> >> > Do you have already QEMU patches? The best is to have the Kernel driver
> >> > submitted altogether with QEMU, as Kernel developers need it to do the
> >> > tests. In my case, I never use crosvm, and I don't have any Chromebook
> >> > anymore.
> >>
> >> IIRC Albert Esteve was working on this, maybe he can share the current status.
> >
> > Any news regards to it?
>
> Albert shared the latest status. There is one in-flight patch series
> required in qemu [1], and then this branch of vhost-device should
> contain the necessary support [2]. Albert is waiting for the virtio spec
> to get merged before sending a pull request IIUC.
>
> [1] https://patchew.org/QEMU/20250217164012.246727-1-aesteve@redhat.com/
> [2] https://github.com/aesteve-rh/vhost-device/tree/virtio-media
>
> >
> >> Note that crosvm does not require a Chromebook, you can build and run
> >> it pretty easily on a regular PC. I have put together a document to
> >> help with that:
> >>
> >> https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
> >
> > I started looking on it today. Already installed crossvm (I had to
> > install libcap-devel to build it). Still, I'm not familiar with
> > crossvm, which is a little be painful. In particular, how can I
> > enable network on it and speedup it?
>
> There is a "./tools/examples/setup_network" in the crosvm repository that
> will setup a TAP device. Once this is done, you can pass the "--net
> tap-name=crosvm_tap" argument to crosvm, and the network device should
> be visible and usable.
>
> Let me reply to the rest of your questions in your latest mail, with the
> most recent logs.
Heh, I just managed to get it work maybe 10 minutes before your e-mail...
I'm building crossvm with:
cargo build --release --features "gpu,media,virgl_renderer,x"
To also have GPU working.
Network setup required a rather complex script to set it up without
breaking my ssh section with the machine where I'm running crossvm.
In case you need, I'm enclosing it.
Now, I need to allocate another time slot for tests and review.
---
#!/bin/bash
PHY=enp0s25
BRIDGE=crossvm_br
TAP=crossvm_tap
IP_MASK=$(ip -br addr show $PHY | awk '{print $3}' | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+')
if [ -z "$IP_MASK" ]; then
echo "Failed to detect IP address on $PHY. Exiting."
exit 1
fi
GW=$(ip -br route show default dev $PHY | awk '{print $3}')
if [ -z "$GW" ]; then
echo "Failed to detect default gateway on $PHY. Exiting."
exit 1
fi
restore_network() {
echo "Restoring original network config on $PHY..."
sudo ip link set dev $BRIDGE down || true
sudo ip link del $BRIDGE || true
sudo ip link set dev $TAP down || true
sudo ip tuntap del dev $TAP mode tap || true
sudo ip addr flush dev $PHY
sudo ip addr add ${IP_MASK} dev $PHY
sudo ip link set dev $PHY up
sudo ip route add default via $GW
}
if ! lsmod | grep -q '^bridge'; then
sudo modprobe bridge
fi
trap 'catch $LINENO "$BASH_COMMAND"' ERR
catch() {
echo "Error on line $1: $2"
restore_network
exit 1
}
if ip link show $TAP &>/dev/null; then
echo "Removing existing tap $TAP"
sudo ip link set dev $TAP down
sudo ip tuntap del dev $TAP mode tap
fi
if ip link show $BRIDGE &>/dev/null; then
echo "Removing existing bridge $BRIDGE"
sudo ip link set dev $BRIDGE down
sudo ip link del $BRIDGE
fi
# Create bridge device
sudo ip link add name $BRIDGE type bridge
sudo ip link set dev $BRIDGE up
sudo ip link set dev $BRIDGE type bridge forward_delay 0
# Add physical interface to bridge
sudo ip link set dev $PHY master $BRIDGE
# Create tap device
sudo ip tuntap add dev $TAP mode tap
sudo ip link set dev $TAP up
# Add tap to bridge
sudo ip link set dev $TAP master $BRIDGE
# Get an address to the bridge
sudo dhclient $BRIDGE
# Start crossvm
sudo ./crosvm/target/release/crosvm run \
linux/arch/x86/boot/bzImage \
-c 4 -m size=4096 \
--disable-sandbox \
--block debian-12.img \
-p "root=/dev/vda1" \
--v4l2-proxy /dev/video0 \
--gpu backend=virglrenderer \
--net tap-name=crossvm_tap,vhost-net
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-18 14:27 ` Alexandre Courbot
@ 2025-06-18 15:05 ` Mauro Carvalho Chehab
2025-06-20 12:03 ` Alexandre Courbot
0 siblings, 1 reply; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-06-18 15:05 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Em Wed, 18 Jun 2025 23:27:13 +0900
"Alexandre Courbot" <gnurou@gmail.com> escreveu:
> On Tue Jun 17, 2025 at 7:20 PM JST, Mauro Carvalho Chehab wrote:
> > Em Tue, 17 Jun 2025 11:03:18 +0200
> > Mauro Carvalho Chehab <mchehab+huawei@kernel.org> escreveu:
> >
> >> Em Tue, 17 Jun 2025 10:49:38 +0200
> >> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> escreveu:
> >>
> >> > Hi Alex,
> >> >
> >> > Em Tue, 27 May 2025 23:03:39 +0900
> >> > Alexandre Courbot <gnurou@gmail.com> escreveu:
> >> >
> >> > > > > > Btw, I was looking at:
> >> > > > > >
> >> > > > > > https://github.com/chromeos/virtio-media
> >> > > > > >
> >> > > > > > (I'm assuming that this is the QEMU counterpart, right?)
> >> > > > >
> >> > > > > crosvm actually, but QEMU support is also being worked on.
> >> > > >
> >> > > > Do you have already QEMU patches? The best is to have the Kernel driver
> >> > > > submitted altogether with QEMU, as Kernel developers need it to do the
> >> > > > tests. In my case, I never use crosvm, and I don't have any Chromebook
> >> > > > anymore.
> >> > >
> >> > > IIRC Albert Esteve was working on this, maybe he can share the current status.
> >> >
> >> > Any news regards to it?
> >> >
> >> > > Note that crosvm does not require a Chromebook, you can build and run
> >> > > it pretty easily on a regular PC. I have put together a document to
> >> > > help with that:
> >> > >
> >> > > https://github.com/chromeos/virtio-media/blob/main/TRY_IT_OUT.md
> >> >
> >> > I started looking on it today. Already installed crossvm (I had to
> >> > install libcap-devel to build it). Still, I'm not familiar with
> >> > crossvm, which is a little be painful. In particular, how can I
> >> > enable network on it and speedup it? With suggested parameters,
> >> > it picked only one CPU, and very few memory on it:
> >> >
> >> > # cat /proc/cpuinfo|grep processor
> >> > processor : 0
> >> >
> >> > # free
> >> > total used free shared buff/cache available
> >> > Mem: 221876 34780 139712 272 56096 187096
> >> > Swap: 0 0 0
> >> >
> >> > I'd like to be able to compile things on it and use ssh/scp. So,
> >> > the VM needs more CPUs, more memory, more network and GPU.
> >
> > Found how to setup cpus and memory, but didn't find a way to setup
> > network without running it as root. The gpu parameter has several
> > options. Not sure what backend works well for media apps like qv4l2,
> > camorama, X11, ...
>
> I'm afraid getting GPU and graphics in general to work is more involved
> and tricky on a regular Linux setup (crosvm was primarily designed for
> ChromeOS). If you really need it I can do some more research; most of my
> tests have been done using v4l2-ctl or ffmpeg and saving the output on
> disk for later inspection.
It was actually easier than what I expected, but it had to run
as root. Due to that, I had to move it to a test machine that I
use just for such kind of tests. I updated it to the Ubuntu
version 24.10, but crossvm refused to build even. I end needing
to install rust via rustup, as only version 1.81.0 had what it is
required to run with the needed features (network, media and gpu).
> >> > Btw, on a quick test with v4l2-compliance, something looks weird:
> >> > I started a camera application at the host. Still, v4l2-compliance
> >> > said successfully excecuted mmap:
> >> >
> >> > Streaming ioctls:
> >> > test read/write: OK (Not Supported)
> >> > test blocking wait: OK
> >> > test MMAP (no poll): OK
> >> > test MMAP (select): OK
> >> > Vide[2025-06-17T08:44:49.177972817+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> >> > [2025-06-17T08:44:49.178164554+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> >> > o Capturtest MMAP (epoll): OK
> >> > test USERPTR (no poll): OK (Not Supported)
> >> > test USERPTR (select): OK (Not Supported)
> >> > test DMABUF (no poll): OK (Not Supported)
> >> > test DMABUF (select): OK (Not Supported)
> >> >
> >> > Which doesn't make any sense, as the host OS should not allow access
> >> > to mmap while streaming.
> >>
> >> Ah, this was with the "simple" device, not with the proxy one.
> >> With the proxy one, I'm getting:
> >>
> >> # v4l2-ctl --all
> >> Driver Info:
> >> Driver name : virtio-media
> >> Card type : usb video: usb video
> >> Bus info : platform:virtio-media
> >> Driver version : 6.15.0
> >> Capabilities : 0x84200001
> >> Video Capture
> >> Streaming
> >> Extended Pix Format
> >> Device Capabilities
> >> Device Caps : 0x04200001
> >> Video Capture
> >> Streaming
> >> Extended Pix Format
> >> Priority: 2
> >> Video input : 0 (Camera 1: ok)
> >> Format Video Capture:
> >> Width/Height : 1280/720
> >> Pixel Format : 'MJPG' (Motion-JPEG)
> >> Field : None
> >> Bytes per Line : 0
> >> Size Image : 1843200
> >> Colorspace : sRGB
> >> Transfer Function : Rec. 709
> >> YCbCr/HSV Encoding: ITU-R 601
> >> Quantization : Default (maps to Full Range)
> >> Flags :
> >> Crop Capability Video Capture:
> >> Bounds : Left 0, Top 0, Width 1280, Height 720
> >> Default : Left 0, Top 0, Width 1280, Height 720
> >> Pixel Aspect: 1/1
> >> Selection Video Capture: crop_default, Left 0, Top 0, Width 1280, Height 720, Flags:
> >> Selection Video Capture: crop_bounds, Left 0, Top 0, Width 1280, Height 720, Flags:
> >> Streaming Parameters Video Capture:
> >> Capabilities : timeperframe
> >> Frames per second: 30.000 (30/1)
> >> Read buffers : 0
> >>
> >> User Controls
> >>
> >> brightness 0x00980900 (int) : min=-128 max=127 step=1 default=-11 value=-11
> >> contrast 0x00980901 (int) : min=0 max=255 step=1 default=148 value=148
> >> saturation 0x00980902 (int) : min=0 max=255 step=1 default=180 value=180
> >> hue 0x00980903 (int) : min=-128 max=127 step=1 default=0 value=0
> >>
> >> # v4l2-compliance -d0 -s
> >>
> >> Streaming ioctls:
> >> test read/write: OK (Not Supported)
> >> test blocking wait: OK
> >> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
> >> test MMAP (no poll): FAIL
> >> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
> >> test MMAP (select): FAIL
> >> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
> >> test MMAP (epoll): FAIL
> >> test USERPTR (no poll): OK (Not Supported)
> >> test USERPTR (select): OK (Not Supported)
> >> [2025-06-17T08:55:20.768760714+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> >> test DMABUF (no poll): OK (Not Supported)
> >> [2025-06-17T08:55:20.769745707+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
> >> test DMABUF (select): OK (Not Supported)
> >>
> >> At the host, I'm getting:
> >>
> >> Streaming ioctls:
> >> test read/write: OK (Not Supported)
> >> test blocking wait: OK
> >> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
> >> test MMAP (no poll): FAIL
> >> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
> >> test MMAP (select): FAIL
> >> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
> >> test MMAP (epoll): FAIL
> >> test USERPTR (no poll): OK
> >> test USERPTR (select): OK
> >> test DMABUF: Cannot test, specify --expbuf-device
>
> These logs look ok to me: the MMAP tests are failing on the host, so
> they are also expected to fail on the guest (still I expect regular
> streaming to work on both). USERPTR is not supported on the guest, as
> per your request to not support this memory type in new drivers. DMABUF
> is not supported at all at the moment.
In the specific case of a virtio driver, while it is OK for the first
versions to support MMAP only, USERPTR support could make sense, as
this is not a real driver for a certain hardware, but instead it is
replicating at the guest whatever the host driver has, which may or
may not have MMAP.
That's said, I don't recall any driver with USERPTR and without MMAP
those days. I did a quick check: VB2 devices always seem to have MMAP.
-
There is one case where only read ioctl is supported: pvrusb2, which
is probably not interesting enough those days, but IMHO, for the few
cases where a device can't be used at the guest due to the lack of a
compatible streaming API, virtio-media should not expose it to the
guest and/or issue an error or warning.
> If the host cannot pass compliance, the guest will inevitably suffer
> from the same shortcomings. :) But at least on the devices I tested I
> was still able to stream something onto the disk and the result was
> correct.
Makes sense.
I'll do some tests later and check how it works with
a GUI app and some real devices.
Regards,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-18 15:05 ` Mauro Carvalho Chehab
@ 2025-06-20 12:03 ` Alexandre Courbot
0 siblings, 0 replies; 26+ messages in thread
From: Alexandre Courbot @ 2025-06-20 12:03 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Albert Esteve, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Jason Wang, Xuan Zhuo, Eugenio Pérez,
gurchetansingh, daniel.almeida, adelva, changyeon,
nicolas.dufresne, linux-kernel, linux-media, virtualization
Hi Mauro,
Really appreciating the time you are spending reviewing and testing
this! m(__)m Thanks also for sharing your script, I've learned a few
things I didn't know about crosvm. :P
On Thu Jun 19, 2025 at 12:05 AM JST, Mauro Carvalho Chehab wrote:
<snip>
>> > Found how to setup cpus and memory, but didn't find a way to setup
>> > network without running it as root. The gpu parameter has several
>> > options. Not sure what backend works well for media apps like qv4l2,
>> > camorama, X11, ...
>>
>> I'm afraid getting GPU and graphics in general to work is more involved
>> and tricky on a regular Linux setup (crosvm was primarily designed for
>> ChromeOS). If you really need it I can do some more research; most of my
>> tests have been done using v4l2-ctl or ffmpeg and saving the output on
>> disk for later inspection.
>
> It was actually easier than what I expected, but it had to run
> as root. Due to that, I had to move it to a test machine that I
> use just for such kind of tests. I updated it to the Ubuntu
> version 24.10, but crossvm refused to build even. I end needing
> to install rust via rustup, as only version 1.81.0 had what it is
> required to run with the needed features (network, media and gpu).
Yes, rustup is the preferred way (if not traditional from the point of
view of Linux distros) to get the latest Rust toolchain.
>
>> >> > Btw, on a quick test with v4l2-compliance, something looks weird:
>> >> > I started a camera application at the host. Still, v4l2-compliance
>> >> > said successfully excecuted mmap:
>> >> >
>> >> > Streaming ioctls:
>> >> > test read/write: OK (Not Supported)
>> >> > test blocking wait: OK
>> >> > test MMAP (no poll): OK
>> >> > test MMAP (select): OK
>> >> > Vide[2025-06-17T08:44:49.177972817+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
>> >> > [2025-06-17T08:44:49.178164554+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
>> >> > o Capturtest MMAP (epoll): OK
>> >> > test USERPTR (no poll): OK (Not Supported)
>> >> > test USERPTR (select): OK (Not Supported)
>> >> > test DMABUF (no poll): OK (Not Supported)
>> >> > test DMABUF (select): OK (Not Supported)
>> >> >
>> >> > Which doesn't make any sense, as the host OS should not allow access
>> >> > to mmap while streaming.
>> >>
>> >> Ah, this was with the "simple" device, not with the proxy one.
>> >> With the proxy one, I'm getting:
>> >>
>> >> # v4l2-ctl --all
>> >> Driver Info:
>> >> Driver name : virtio-media
>> >> Card type : usb video: usb video
>> >> Bus info : platform:virtio-media
>> >> Driver version : 6.15.0
>> >> Capabilities : 0x84200001
>> >> Video Capture
>> >> Streaming
>> >> Extended Pix Format
>> >> Device Capabilities
>> >> Device Caps : 0x04200001
>> >> Video Capture
>> >> Streaming
>> >> Extended Pix Format
>> >> Priority: 2
>> >> Video input : 0 (Camera 1: ok)
>> >> Format Video Capture:
>> >> Width/Height : 1280/720
>> >> Pixel Format : 'MJPG' (Motion-JPEG)
>> >> Field : None
>> >> Bytes per Line : 0
>> >> Size Image : 1843200
>> >> Colorspace : sRGB
>> >> Transfer Function : Rec. 709
>> >> YCbCr/HSV Encoding: ITU-R 601
>> >> Quantization : Default (maps to Full Range)
>> >> Flags :
>> >> Crop Capability Video Capture:
>> >> Bounds : Left 0, Top 0, Width 1280, Height 720
>> >> Default : Left 0, Top 0, Width 1280, Height 720
>> >> Pixel Aspect: 1/1
>> >> Selection Video Capture: crop_default, Left 0, Top 0, Width 1280, Height 720, Flags:
>> >> Selection Video Capture: crop_bounds, Left 0, Top 0, Width 1280, Height 720, Flags:
>> >> Streaming Parameters Video Capture:
>> >> Capabilities : timeperframe
>> >> Frames per second: 30.000 (30/1)
>> >> Read buffers : 0
>> >>
>> >> User Controls
>> >>
>> >> brightness 0x00980900 (int) : min=-128 max=127 step=1 default=-11 value=-11
>> >> contrast 0x00980901 (int) : min=0 max=255 step=1 default=148 value=148
>> >> saturation 0x00980902 (int) : min=0 max=255 step=1 default=180 value=180
>> >> hue 0x00980903 (int) : min=-128 max=127 step=1 default=0 value=0
>> >>
>> >> # v4l2-compliance -d0 -s
>> >>
>> >> Streaming ioctls:
>> >> test read/write: OK (Not Supported)
>> >> test blocking wait: OK
>> >> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
>> >> test MMAP (no poll): FAIL
>> >> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
>> >> test MMAP (select): FAIL
>> >> fail: v4l2-test-buffers.cpp(1345): node->streamon(q.g_type()) != EINVAL
>> >> test MMAP (epoll): FAIL
>> >> test USERPTR (no poll): OK (Not Supported)
>> >> test USERPTR (select): OK (Not Supported)
>> >> [2025-06-17T08:55:20.768760714+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
>> >> test DMABUF (no poll): OK (Not Supported)
>> >> [2025-06-17T08:55:20.769745707+00:00 ERROR virtio_media::ioctl] VIDIOC_REQBUFS: memory type DmaBuf is currently unsupported
>> >> test DMABUF (select): OK (Not Supported)
>> >>
>> >> At the host, I'm getting:
>> >>
>> >> Streaming ioctls:
>> >> test read/write: OK (Not Supported)
>> >> test blocking wait: OK
>> >> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
>> >> test MMAP (no poll): FAIL
>> >> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
>> >> test MMAP (select): FAIL
>> >> fail: ../utils/v4l2-compliance/v4l2-test-buffers.cpp(1346): node->streamon(q.g_type()) != EINVAL
>> >> test MMAP (epoll): FAIL
>> >> test USERPTR (no poll): OK
>> >> test USERPTR (select): OK
>> >> test DMABUF: Cannot test, specify --expbuf-device
>>
>> These logs look ok to me: the MMAP tests are failing on the host, so
>> they are also expected to fail on the guest (still I expect regular
>> streaming to work on both). USERPTR is not supported on the guest, as
>> per your request to not support this memory type in new drivers. DMABUF
>> is not supported at all at the moment.
>
> In the specific case of a virtio driver, while it is OK for the first
> versions to support MMAP only, USERPTR support could make sense, as
> this is not a real driver for a certain hardware, but instead it is
> replicating at the guest whatever the host driver has, which may or
> may not have MMAP.
There is a module parameter (allow_userptr) which you can set to enable
USERPTR buffers, in case you want to try this as well.
>
> That's said, I don't recall any driver with USERPTR and without MMAP
> those days. I did a quick check: VB2 devices always seem to have MMAP.
>
> -
>
> There is one case where only read ioctl is supported: pvrusb2, which
> is probably not interesting enough those days, but IMHO, for the few
> cases where a device can't be used at the guest due to the lack of a
> compatible streaming API, virtio-media should not expose it to the
> guest and/or issue an error or warning.
I've never tested virtio-media with a PVR driver, to be honest. :) Only
regular cameras and video accelerators. So I cannot guarantee other
kinds of devices will work properly - there may also be limitations in
the crosvm proxy device that will prevent these devices from working.
Just a heads-up.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-06-01 10:01 ` Ricardo Ribalda
@ 2025-07-24 17:24 ` Mauro Carvalho Chehab
2025-07-28 11:51 ` Alexandre Courbot
0 siblings, 1 reply; 26+ messages in thread
From: Mauro Carvalho Chehab @ 2025-07-24 17:24 UTC (permalink / raw)
To: Ricardo Ribalda, Alexandre Courbot, Michael S. Tsirkin
Cc: Mauro Carvalho Chehab, Hans Verkuil, Albert Esteve, Jason Wang,
Xuan Zhuo, Eugenio Pérez, gurchetansingh, daniel.almeida,
adelva, changyeon, nicolas.dufresne, linux-kernel, linux-media,
virtualization, Alexandre Courbot
Em Sun, 1 Jun 2025 12:01:22 +0200
Ricardo Ribalda <ribalda@chromium.org> escreveu:
> Hi Mauro
>
> On Sun, 1 Jun 2025 at 11:34, Mauro Carvalho Chehab
> <mchehab+huawei@kernel.org> wrote:
> >
> > Em Wed, 28 May 2025 18:23:02 +0200
> > Ricardo Ribalda <ribalda@chromium.org> escreveu:
> >
> > > > +static int scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> > > > + unsigned long userptr,
> > > > + unsigned long length)
> > > > +{
> > > > + int ret;
> > > > + int nents;
> > > Could you initialize nents and sg_list?
> > > old versions of gcc are a bit picky
> > > https://gitlab.freedesktop.org/linux-media/users/ribalda/-/jobs/77042562#L4381
> >
> > Please don't. In this specific case, ret is always initialized:
> >
> > > + struct virtio_media_sg_entry *sg_list;
> > > +
> > > + ret = __scatterlist_builder_add_userptr(builder, userptr, length,
> > > + &sg_list, &nents);
> >
> > nents and sg_list may or may not be initialized at the function,
> > but initializing it is wrong, as, when they are not initialized, the
> > ret code shall catch it (and if not, we *do* want gcc to warn).
> >
> > So, if our CI is warning about that due to an old version, please upgrade
> > the version at the CI runner.
>
> The main version of gcc works fine. It is the minimal version (8.1) required by
> https://www.kernel.org/doc/html/next/process/changes.html
> that complains.
Ricardo,
gcc 8.1 was released in May 2, 2018. I don't think it makes sense to
address bogus warnings with that old gcc versions. I would just disable
WERROR for such versions on our CI tests.
---
Alexandre/Michael,
I need a couple of full days to properly review virtio-media.
I was planning to do it during this Kernel cycle, but I ended
allocating too much time just to be able to create a crossvm
that would allow testing it. Afterwards, I got sidetracked with other
issues. I won't be able to review it in time for this merge window.
I'm planning to do it at the beginning of the next merge cycle.
Thanks,
Mauro
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3] media: add virtio-media driver
2025-07-24 17:24 ` Mauro Carvalho Chehab
@ 2025-07-28 11:51 ` Alexandre Courbot
0 siblings, 0 replies; 26+ messages in thread
From: Alexandre Courbot @ 2025-07-28 11:51 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Ricardo Ribalda, Michael S. Tsirkin, Mauro Carvalho Chehab,
Hans Verkuil, Albert Esteve, Jason Wang, Xuan Zhuo,
Eugenio Pérez, gurchetansingh, daniel.almeida, adelva,
changyeon, nicolas.dufresne, linux-kernel, linux-media,
virtualization, Alexandre Courbot
Hi Mauro,
On Fri, Jul 25, 2025 at 2:24 AM Mauro Carvalho Chehab
<mchehab+huawei@kernel.org> wrote:
>
> Em Sun, 1 Jun 2025 12:01:22 +0200
> Ricardo Ribalda <ribalda@chromium.org> escreveu:
>
> > Hi Mauro
> >
> > On Sun, 1 Jun 2025 at 11:34, Mauro Carvalho Chehab
> > <mchehab+huawei@kernel.org> wrote:
> > >
> > > Em Wed, 28 May 2025 18:23:02 +0200
> > > Ricardo Ribalda <ribalda@chromium.org> escreveu:
> > >
> > > > > +static int scatterlist_builder_add_userptr(struct scatterlist_builder *builder,
> > > > > + unsigned long userptr,
> > > > > + unsigned long length)
> > > > > +{
> > > > > + int ret;
> > > > > + int nents;
> > > > Could you initialize nents and sg_list?
> > > > old versions of gcc are a bit picky
> > > > https://gitlab.freedesktop.org/linux-media/users/ribalda/-/jobs/77042562#L4381
> > >
> > > Please don't. In this specific case, ret is always initialized:
> > >
> > > > + struct virtio_media_sg_entry *sg_list;
> > > > +
> > > > + ret = __scatterlist_builder_add_userptr(builder, userptr, length,
> > > > + &sg_list, &nents);
> > >
> > > nents and sg_list may or may not be initialized at the function,
> > > but initializing it is wrong, as, when they are not initialized, the
> > > ret code shall catch it (and if not, we *do* want gcc to warn).
> > >
> > > So, if our CI is warning about that due to an old version, please upgrade
> > > the version at the CI runner.
> >
> > The main version of gcc works fine. It is the minimal version (8.1) required by
> > https://www.kernel.org/doc/html/next/process/changes.html
> > that complains.
>
> Ricardo,
>
> gcc 8.1 was released in May 2, 2018. I don't think it makes sense to
> address bogus warnings with that old gcc versions. I would just disable
> WERROR for such versions on our CI tests.
>
> ---
>
> Alexandre/Michael,
>
> I need a couple of full days to properly review virtio-media.
> I was planning to do it during this Kernel cycle, but I ended
> allocating too much time just to be able to create a crossvm
> that would allow testing it. Afterwards, I got sidetracked with other
> issues. I won't be able to review it in time for this merge window.
>
> I'm planning to do it at the beginning of the next merge cycle.
Not a worry at all and I appreciate the time you are putting aside to
review this properly!
Thanks,
Alex.
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2025-07-28 11:51 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-12 4:08 [PATCH v3] media: add virtio-media driver Alexandre Courbot
2025-04-12 14:27 ` Markus Elfring
2025-05-26 12:13 ` Mauro Carvalho Chehab
2025-05-27 6:14 ` Alexandre Courbot
2025-05-27 9:13 ` Mauro Carvalho Chehab
2025-05-27 13:21 ` Alexandre Courbot
2025-05-27 13:35 ` Mauro Carvalho Chehab
2025-05-27 14:03 ` Alexandre Courbot
2025-05-27 14:42 ` Mauro Carvalho Chehab
2025-06-17 8:49 ` Mauro Carvalho Chehab
2025-06-17 9:03 ` Mauro Carvalho Chehab
2025-06-17 10:20 ` Mauro Carvalho Chehab
2025-06-18 14:27 ` Alexandre Courbot
2025-06-18 15:05 ` Mauro Carvalho Chehab
2025-06-20 12:03 ` Alexandre Courbot
2025-06-18 14:16 ` Alexandre Courbot
2025-06-18 14:40 ` Mauro Carvalho Chehab
2025-05-27 14:23 ` Michael S. Tsirkin
2025-05-27 14:39 ` Mauro Carvalho Chehab
2025-05-27 15:06 ` Michael S. Tsirkin
2025-05-28 11:07 ` Alexandre Courbot
2025-05-28 16:23 ` Ricardo Ribalda
2025-06-01 9:34 ` Mauro Carvalho Chehab
2025-06-01 10:01 ` Ricardo Ribalda
2025-07-24 17:24 ` Mauro Carvalho Chehab
2025-07-28 11:51 ` Alexandre Courbot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).