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 <gnu...@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 <acour...@google.com> > > Signed-off-by: Alexandre Courbot <gnu...@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-aest...@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-ac8406814...@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-81e2549b8...@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 <gnu...@gmail.com> > > +L: linux-me...@vger.kernel.org > > +S: Maintained > > +F: drivers/media/virtio/ > > + > > VIRTIO MEM DRIVER > > M: David Hildenbrand <da...@redhat.com> > > L: virtualizat...@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 <gnu...@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