From nobody Fri Jul 24 04:50:45 2026 Received: from mail-yx1-f70.google.com (mail-yx1-f70.google.com [74.125.224.70]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1746B3BD22F for ; Thu, 23 Jul 2026 18:32:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.70 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831570; cv=none; b=fxmkMBsRksTOmRn+2kS3uMEPUB6NXF8zXOFG2ubzVSyeREYHLogAOeOo0nJnW+74ZYCzTAbjpZMBIDvKSrr0mbF7maNbHx7MZNtBXOjzepBs27oJWW+zAzCvGAgIAw8g9/2vFoCBVSANBmiM82FMSl8wkIzdMe4kiGEZwsO095I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831570; c=relaxed/simple; bh=aF9A/FCgNIS4jOX54s1G8w70lJPpMHXK508TzR31w04=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=teE7vemDS7vLJl8P2k6yK9R6ltGF0XjAwNmA2fUSTh6KKwjMvTYHl0IQ/01CiH5aZmXoERl0iIo/YlahJGPSGFfjnSfoVs1b6Mtmj9Jjzq30q4r0R4wfTChButc2ObpGMQiqoXdhi/O0hA4JZSdU28bQYmnhJDLGbOdNhFP2SJg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=kMeX6ATJ; arc=none smtp.client-ip=74.125.224.70 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="kMeX6ATJ" Received: by mail-yx1-f70.google.com with SMTP id 956f58d0204a3-668432d53c5so301389d50.1 for ; Thu, 23 Jul 2026 11:32:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784831558; x=1785436358; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=GJb49RO2LPnuct6XuS+q/9xxHDm8ErPy1eyGobZI6Io=; b=kMeX6ATJQuT8M+dt8uhIWsGkPFwxuJ1Xdpi16lAo3OCzECLVlhxjcwsov1JHT43T+b ncMxHQLrNL9+xv9QyDb92dSjQ8/sBdfMijO+jb8eWHT/01TGJrCOc84uMcUBt+MGv2G0 Tf8pFNc+MQE33erb2RdWSXZOmv+uw9dbf9JVPIL203qyQFj5Uy045bIoBjiMRzeE2QqS AYCyl9hnn1Bc4hOVQNxihATf7+fAI6liFeI4naJr4yWIOngVsMsXdOJC0dkl3Wx63DYl cTWP/z2Q+sugRLzrOyQ3i92hIS7ptx3QnGYsgWEggvicgxiUhE+evTlJN8rpWsmDGLOw 5BwQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784831558; x=1785436358; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=GJb49RO2LPnuct6XuS+q/9xxHDm8ErPy1eyGobZI6Io=; b=rLlx01eQkAjVNzo+++y/AM3N/beo7Cp/9o6Zd425AErQoeozraNkCsRfS5Mx62bQSk boIOiexeEU85/IFzUcwCmgh9HdB9LkkIb7YywSczq7T3cYWvwvh5VCRt+EAtZHq5M6QF Svx3PErcP3GmA0qVZMfvz5NYphqrY/jQwYgi1z+RlfwI2mzvQYN/+7zraOIs823VmPAX zz0Atr2SnF4vR/purFrCI9CD7XNn1Nmz/fk5S3vosKSwphnKVQfZ+hSoWqnkGVF5FxSu QjPbeATyekuQXR3hs4UNETZuJ6kOBt8/rx85sghqxowIgAGMunnSggKEwW6zkV8Rail/ d9qQ== X-Forwarded-Encrypted: i=1; AHgh+RqAAM68VeWRiZTlj2qfi5Xv+r8p6OB6Ewg8cE5R5hd0BqGLyOBb5Gq8nhdoEr2ladSVP62W5TZ8/zy14y0=@vger.kernel.org X-Gm-Message-State: AOJu0Yz3C4QlJVEkXWkrRcsnOrC2Jzg4jgivYLF7JOiAC8juONOnjyeK aW7v7sEVg3Df6mrj28cNcBMVC0PMahNKXERh3edZOcLKR2WfDE/882n5ylu7YdjuAHobsALNbrd DX22FDMDaGOKwyienv+OwZDsNXg8x X-Received: from yxqz15.prod.google.com ([2002:a53:a9cf:0:b0:668:430b:914d]) (user=briandaniels job=prod-delivery.src-stubby-dispatcher) by 2002:a53:cb03:0:b0:668:49fb:b680 with SMTP id 956f58d0204a3-668a4c3549bmr828002d50.23.1784831557689; Thu, 23 Jul 2026 11:32:37 -0700 (PDT) Date: Thu, 23 Jul 2026 14:32:15 -0400 In-Reply-To: <20260723183219.737296-1-briandaniels@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260723183219.737296-1-briandaniels@google.com> X-Mailer: git-send-email 2.55.0.229.g6434b31f56-goog Message-ID: <20260723183219.737296-2-briandaniels@google.com> Subject: [PATCH v5 1/5] media: virtio: Add skeleton virtio-media driver From: Brian Daniels To: Mauro Carvalho Chehab Cc: adelva@google.com, aesteve@redhat.com, changyeon@google.com, daniel.almeida@collabora.com, eperezma@redhat.com, gnurou@gmail.com, gurchetansingh@google.com, hverkuil@xs4all.nl, jasowang@redhat.com, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, mst@redhat.com, nicolas.dufresne@collabora.com, virtualization@lists.linux.dev, xuanzhuo@linux.alibaba.com, Brian Daniels Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alexandre Courbot This patch adds a minimum viable virtio-media driver that binds to the virtio device and registers a V4L2 device and a video device, but lacks any actual functionality. It adds the UAPI header defining the protocol, internal driver headers, Kconfig and Makefile entries, and MAINTAINERS entry. Many of the structs in the protocol add reserved bits. These are present to ensure 64-bit alignment. They are not intended to be used as reserved expansion for the protocol in the future, so more reserved space is not required. Signed-off-by: Alexandre Courbot Assisted-by: Antigravity:gemini-3.5-flash Co-developed-by: Brian Daniels Signed-off-by: Brian Daniels --- MAINTAINERS | 8 + drivers/media/Kconfig | 13 + drivers/media/Makefile | 2 + drivers/media/virtio/Makefile | 7 + drivers/media/virtio/virtio_media.h | 97 +++++++ drivers/media/virtio/virtio_media_driver.c | 146 +++++++++++ include/uapi/linux/virtio_media.h | 287 +++++++++++++++++++++ 7 files changed, 560 insertions(+) create mode 100644 drivers/media/virtio/Makefile create mode 100644 drivers/media/virtio/virtio_media.h create mode 100644 drivers/media/virtio/virtio_media_driver.c create mode 100644 include/uapi/linux/virtio_media.h diff --git a/MAINTAINERS b/MAINTAINERS index efbf80806..879bff12d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28215,6 +28215,7 @@ F: Documentation/devicetree/bindings/virtio/ F: Documentation/driver-api/virtio/ F: drivers/block/virtio_blk.c F: drivers/crypto/virtio/ +F: drivers/media/virtio/ F: drivers/vdpa/ F: drivers/virtio/ F: include/linux/vdpa.h @@ -28327,6 +28328,13 @@ S: Maintained F: drivers/iommu/virtio-iommu.c F: include/uapi/linux/virtio_iommu.h =20 +VIRTIO MEDIA DRIVER +M: Brian Daniels +L: linux-media@vger.kernel.org +S: Maintained +F: drivers/media/virtio/ +F: include/uapi/linux/virtio_media.h + VIRTIO MEM DRIVER M: David Hildenbrand L: virtualization@lists.linux.dev diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 6abc9302c..7bc7306fa 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -136,6 +136,19 @@ config MEDIA_PLATFORM_SUPPORT =20 Say Y when you want to be able to see such devices. =20 +config MEDIA_VIRTIO + tristate "Virtio-media Driver" + depends on VIRTIO && VIDEO_DEV && 64BIT && (X86 || (ARM && CPU_LITTLE_END= IAN)) + 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. + config MEDIA_TEST_SUPPORT bool prompt "Test drivers" if MEDIA_SUPPORT_FILTER diff --git a/drivers/media/Makefile b/drivers/media/Makefile index 20fac24e4..357e786cc 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile @@ -23,6 +23,8 @@ obj-$(CONFIG_DVB_CORE) +=3D dvb-core/ # There are both core and drivers at RC subtree - merge before drivers obj-y +=3D rc/ =20 +obj-$(CONFIG_MEDIA_VIRTIO) +=3D virtio/ + obj-$(CONFIG_CEC_CORE) +=3D cec/ =20 # diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile new file mode 100644 index 000000000..09d9834da --- /dev/null +++ b/drivers/media/virtio/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the virtio-media device driver. + +virtio-media-objs :=3D virtio_media_driver.o + +obj-$(CONFIG_MEDIA_VIRTIO) +=3D virtio-media.o diff --git a/drivers/media/virtio/virtio_media.h b/drivers/media/virtio/vir= tio_media.h new file mode 100644 index 000000000..3fd240a46 --- /dev/null +++ b/drivers/media/virtio/virtio_media.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */ + +/* + * Virtio-media structures & functions declarations. + * + * Copyright (c) 2024-2026 Google LLC. + */ + +#ifndef __VIRTIO_MEDIA_H +#define __VIRTIO_MEDIA_H + +#include +#include + +#include "uapi/linux/virtio_media.h" + +#define DESC_CHAIN_MAX_LEN SG_MAX_SINGLE_ALLOC + +#define VIRTIO_MEDIA_DEFAULT_DRIVER_NAME "virtio-media" + +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 &struct virtio_media_session.lis= t. + * @events_lock: prevents concurrent processing of events. + * @cmd: union of the device commands ``open`` and ``munmap``. The other + * commands are handled by &struct virtio_media_session + * @resp: union of responses to device commands ``open`` and ``munmap``. T= he + * other responses are handled by &struct virtio_media_session + * @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; /* protects sessions list */ + + struct mutex events_lock; /* prevents concurrent event processing */ + + __dma_from_device_group_begin(); + 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; + __dma_from_device_group_end(); + + struct mutex vlock; /* serializes command queue access */ + 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/vir= tio/virtio_media_driver.c new file mode 100644 index 000000000..144e2f077 --- /dev/null +++ b/drivers/media/virtio/virtio_media_driver.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ + +/* + * Virtio-media driver. + * + * Copyright (c) 2024-2026 Google LLC. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "uapi/linux/virtio_media.h" +#include "virtio_media.h" + +static void commandq_callback(struct virtqueue *vq) +{ +} + +static void eventq_callback(struct virtqueue *vq) +{ +} + +static const struct v4l2_file_operations virtio_media_fops =3D { + .owner =3D THIS_MODULE, + .open =3D v4l2_fh_open, + .release =3D v4l2_fh_release, +}; + +static int virtio_media_probe(struct virtio_device *virtio_dev) +{ + struct device *dev =3D &virtio_dev->dev; + struct virtqueue *vqs[2]; + static struct virtqueue_info vq_info[2] =3D { + { + .name =3D "command", + .callback =3D commandq_callback, + }, + { + .name =3D "event", + .callback =3D eventq_callback, + }, + }; + struct virtio_media *vv; + struct video_device *vd; + int ret; + + vv =3D devm_kzalloc(dev, sizeof(*vv), GFP_KERNEL); + if (!vv) + return -ENOMEM; + + INIT_LIST_HEAD(&vv->sessions); + mutex_init(&vv->sessions_lock); + mutex_init(&vv->events_lock); + mutex_init(&vv->vlock); + + vv->virtio_dev =3D virtio_dev; + virtio_dev->priv =3D vv; + + init_waitqueue_head(&vv->wq); + + ret =3D v4l2_device_register(dev, &vv->v4l2_dev); + if (ret) + return ret; + + ret =3D virtio_find_vqs(virtio_dev, 2, vqs, vq_info, NULL); + if (ret) + goto err_find_vqs; + + vv->commandq =3D vqs[0]; + vv->eventq =3D vqs[1]; + + vd =3D &vv->video_dev; + vd->v4l2_dev =3D &vv->v4l2_dev; + vd->vfl_type =3D VFL_TYPE_VIDEO; + vd->fops =3D &virtio_media_fops; + vd->release =3D video_device_release_empty; + strscpy(vd->name, "virtio-media", sizeof(vd->name)); + + video_set_drvdata(vd, vv); + + vd->device_caps =3D virtio_cread32(virtio_dev, 0); + if (vd->device_caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE)) + vd->vfl_dir =3D VFL_DIR_M2M; + else if (vd->device_caps & + (V4L2_CAP_VIDEO_OUTPUT | V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) + vd->vfl_dir =3D VFL_DIR_TX; + else + vd->vfl_dir =3D VFL_DIR_RX; + + ret =3D video_register_device(vd, virtio_cread32(virtio_dev, 4), 0); + if (ret) + goto err_register_device; + + virtio_device_ready(virtio_dev); + + return 0; + +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 =3D virtio_dev->priv; + + virtio_reset_device(virtio_dev); + v4l2_device_unregister(&vv->v4l2_dev); + virtio_dev->config->del_vqs(virtio_dev); + video_unregister_device(&vv->video_dev); +} + +static struct virtio_device_id id_table[] =3D { + { VIRTIO_ID_MEDIA, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static unsigned int features[] =3D {}; + +static struct virtio_driver virtio_media_driver =3D { + .feature_table =3D features, + .feature_table_size =3D ARRAY_SIZE(features), + .driver.name =3D VIRTIO_MEDIA_DEFAULT_DRIVER_NAME, + .driver.owner =3D THIS_MODULE, + .id_table =3D id_table, + .probe =3D virtio_media_probe, + .remove =3D virtio_media_remove, +}; + +module_virtio_driver(virtio_media_driver); + +MODULE_DEVICE_TABLE(virtio, id_table); +MODULE_DESCRIPTION("virtio media driver"); +MODULE_AUTHOR("Alexandre Courbot "); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/include/uapi/linux/virtio_media.h b/include/uapi/linux/virtio_= media.h new file mode 100644 index 000000000..371dbe811 --- /dev/null +++ b/include/uapi/linux/virtio_media.h @@ -0,0 +1,287 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */ + +/* + * Definitions of virtio-media protocol structures. + * + * Copyright (c) 2024-2026 Google LLC. + */ + +#ifndef __VIRTIO_MEDIA_PROTOCOL_H +#define __VIRTIO_MEDIA_PROTOCOL_H + +#include + +/* + * 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_OP= EN. + * @hdr: header containing the status of the command. + * @session_id: if &struct virtio_media_resp_header.status =3D=3D 0, conta= ins 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 t= he + * 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_CLO= SE. + * @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 t= he + * 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*`` ma= cro. + * + * 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 w= ill + * write into. + * + */ +#define VIRTIO_MEDIA_CMD_IOCTL 3 + +/** + * struct virtio_media_cmd_ioctl - Driver command for VIRTIO_MEDIA_CMD_IOC= TL. + * @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_I= OCTL. + * @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; +}; + +/** + * VIRTIO_MEDIA_MMAP_FLAG_RW - Bit position of the VIRTIO_MEDIA_MMAP_FLAG_= RW + * flag. + */ +#define VIRTIO_MEDIA_MMAP_FLAG_RW 0 + +/** + * VIRTIO_MEDIA_CMD_MMAP - Command for mapping a MMAP buffer into the driv= er'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_MM= AP. + * @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_MU= NMAP. + * @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; +}; + +/* The values for these events are set by the virtio-media specification. = */ +#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 be set to zero by the device. + * + * Upon receiving this event, the session mentioned in the header is consi= dered + * corrupted and closed. + */ +struct virtio_media_event_error { + struct virtio_media_event_header hdr; + u32 errno; + u32 __reserved; +}; + +/* This is set to VIDEO_MAX_PLANES defined in include/uapi/linux/videodev2= .h. + * It is renamed here to match the constant that is defined in the virtio-= media + * specification. + */ +#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 dequeu= ed. + * @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 --=20 2.55.0.229.g6434b31f56-goog From nobody Fri Jul 24 04:50:45 2026 Received: from mail-qv1-f70.google.com (mail-qv1-f70.google.com [209.85.219.70]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EB7974734D6 for ; Thu, 23 Jul 2026 18:32:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.70 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831573; cv=none; b=hO3p/D351yoreCUShlrGqTeVaYowG6oB2HMaTCOS5EYTkEbgVB0T6eWGhsvNUTtSzLqk0KIPd/EYG8K1wzLxAu7X+ikFJvAwLuRjDNLO+Jle8GblRMYqMQVFpoFcDI59oDD2t2uxNKCOYFeONmJ+acIMZs13rexp0SFLil58hNw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831573; c=relaxed/simple; bh=mR2Y638YXJNwOUgeTIpqSHIXGsam8xOUl45XcCb3ges=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=uwCHuLq5ad0GwvA4/CvMw5KiMb+w/MU/I0ADLfO5bMestRhSmepaXD3cHYKagmVMjNnamcLDfHzZn4levnsVV9riFm9ICL9TMDG3UzDdOLqQwFbXde4VhWbq1mgwr9Dz3z20vPQdJJzIsOT+4qcIF8lUe4R1Ne2FjW3O4EusWbo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=UHdOHwwl; arc=none smtp.client-ip=209.85.219.70 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="UHdOHwwl" Received: by mail-qv1-f70.google.com with SMTP id 6a1803df08f44-8ee2847cbd4so20844806d6.3 for ; Thu, 23 Jul 2026 11:32:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784831563; x=1785436363; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=K7pQmT36wHno7PyO9DeXzUpuKAVczC0Y8WOgBkHKT8Q=; b=UHdOHwwlFzZRVDDoFXyerU1sjbh8cfLUtQP8LdI70/UsvXRM1YtkHT/5cDCMBrkpYM 8MOirye3n5DUtpioITK7slDIyGAuinBHWMgmRi7yWiWiBXTsxvMdFGtISpnM6mB3vFZ+ 2dqk7tnmbPoClSCv0HsVAQvSQb1R7xZIyKbxkf0jXtCF9mA+wuA+H/TZKz/c8rL8FtsJ CXlIVeIocAeoU/WhghgO5rMlj0e5T1QTA55OwzrHDpyOcqkdIrBn/lWG6QYsUPvH1zME K0cjq7MOkOOvAg/TI8EuhGHJ11cPuxUWycy4tTqP/2t/96Gw7oONXu8+MgEde54QJd7d iKGQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784831563; x=1785436363; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=K7pQmT36wHno7PyO9DeXzUpuKAVczC0Y8WOgBkHKT8Q=; b=nIp/l0+KxE6Tx9NacSAzjCflaSYNzoUHZHg/G4r5sMNYdCYVxtn8lMLxZUyJku+qsU 9goSkXKbwQnBQ+8kPFXb2JgE0e54l3V5kPVBY5+ayQoQtBu8AezfUErnvuklMgSayjZv w0uOcqLmbZRQFmtWId9oiBJs3M1glKS3o/jX5Igx49EJIiwABw4v1RN/nTTG0E6jNdQK uu0ugU+k6FMh7dSv6l3QRR+d5eprt9H8UFwfeB2vUlBF5iXcusGGHEnRFkCjvoR4WKey cImj2D7qYmMgahGoL4k9X1J9mmTHwmn3OBWp6S1gXEQlzJZmBft0o7wehzDHHo6JOuZi mnNA== X-Forwarded-Encrypted: i=1; AHgh+RpK+AHUJkxR5EgKhEZAWTK7NzcSW/hIV/bMjLJrLcjht64KkGGOft4ouOySMBiAsoMlhKxqymJSqipSzZk=@vger.kernel.org X-Gm-Message-State: AOJu0YwbPuBAodIlLxI4hAahg8BwZHfeVbegZ4LyW6GToVfMe4xG7KXs XOR5FVmJtoFZJybanOG8bjp1c3jmB/9XbNLE+jkQVqnMgOyDEfUAFFvOmTQcctB/nI2O9pvhlU+ 1A0nrEvbWLV15ptWPrv4TlWLeu9iG X-Received: from qkak28-n2.prod.google.com ([2002:a05:620a:a0dc:20b0:930:f631:93df]) (user=briandaniels job=prod-delivery.src-stubby-dispatcher) by 2002:a05:620a:6989:b0:92e:4773:5a00 with SMTP id af79cd13be357-9310370bfdfmr468637385a.48.1784831563132; Thu, 23 Jul 2026 11:32:43 -0700 (PDT) Date: Thu, 23 Jul 2026 14:32:16 -0400 In-Reply-To: <20260723183219.737296-1-briandaniels@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260723183219.737296-1-briandaniels@google.com> X-Mailer: git-send-email 2.55.0.229.g6434b31f56-goog Message-ID: <20260723183219.737296-3-briandaniels@google.com> Subject: [PATCH v5 2/5] media: virtio: Add session management From: Brian Daniels To: Mauro Carvalho Chehab Cc: adelva@google.com, aesteve@redhat.com, changyeon@google.com, daniel.almeida@collabora.com, eperezma@redhat.com, gnurou@gmail.com, gurchetansingh@google.com, hverkuil@xs4all.nl, jasowang@redhat.com, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, mst@redhat.com, nicolas.dufresne@collabora.com, virtualization@lists.linux.dev, xuanzhuo@linux.alibaba.com, Brian Daniels Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alexandre Courbot This patch adds session management to the virtio-media driver. A session is created when the /dev/videoX device is opened, and destroyed when it is closed. Opening a session sends VIRTIO_MEDIA_CMD_OPEN to the host, and closing it sends VIRTIO_MEDIA_CMD_CLOSE. It adds drivers/media/virtio/session.h and implements the open and release fops. Signed-off-by: Alexandre Courbot Assisted-by: Antigravity:gemini-3.5-flash Co-developed-by: Brian Daniels Signed-off-by: Brian Daniels --- drivers/media/virtio/session.h | 132 +++++ drivers/media/virtio/virtio_media_driver.c | 604 ++++++++++++++++++++- 2 files changed, 732 insertions(+), 4 deletions(-) create mode 100644 drivers/media/virtio/session.h diff --git a/drivers/media/virtio/session.h b/drivers/media/virtio/session.h new file mode 100644 index 000000000..d523c8171 --- /dev/null +++ b/drivers/media/virtio/session.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */ + +/* + * Definitions of virtio-media session related structures. + * + * Copyright (c) 2024-2026 Google LLC. + */ + +#ifndef __VIRTIO_MEDIA_SESSION_H +#define __VIRTIO_MEDIA_SESSION_H + +#include +#include + +#include "uapi/linux/virtio_media.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. + * @file: file pointer associated with the session's file handler. + * @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 commands ``close``, ``ioctl``, and ``mmap``. A + * session can have one command currently running. The rest of the + * commands are handled by &struct virtio_media. + * @resp: union of responses to session commands ``close``, ``ioctl``, and + * ``mmap``. A session can wait on one command only. The rest of the + * responses are handled by &struct virtio_media. + * @shadow_buf: shadow buffer where data to be added to the descriptor cha= in 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 for the queues for this session. + * @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; + struct file *file; + u32 id; + bool nonblocking_dequeue; + bool uses_mplane; + + __dma_from_device_group_begin(); + 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; + __dma_from_device_group_end(); + + void *shadow_buf; + + struct sg_table command_sgs; + + struct virtio_media_queue_state queues[VIRTIO_MEDIA_LAST_QUEUE + 1]; + struct mutex queues_lock; /* protects queues array and states */ + wait_queue_head_t dqbuf_wait; + + struct list_head list; +}; + +static inline struct virtio_media_session *fh_to_session(struct v4l2_fh *f= h) +{ + return container_of(fh, struct virtio_media_session, fh); +} + +static inline void +virtio_media_session_fh_add(struct virtio_media_session *session, + struct file *file) +{ + v4l2_fh_add(&session->fh, file); + session->file =3D file; +} + +static inline void +virtio_media_session_fh_del(struct virtio_media_session *session) +{ + v4l2_fh_del(&session->fh, session->file); +} + +#endif // __VIRTIO_MEDIA_SESSION_H diff --git a/drivers/media/virtio/virtio_media_driver.c b/drivers/media/vir= tio/virtio_media_driver.c index 144e2f077..938786b05 100644 --- a/drivers/media/virtio/virtio_media_driver.c +++ b/drivers/media/virtio/virtio_media_driver.c @@ -14,25 +14,589 @@ #include #include #include +#include +#include +#include +#include +#include =20 #include #include +#include +#include =20 #include "uapi/linux/virtio_media.h" +#include "session.h" #include "virtio_media.h" =20 -static void commandq_callback(struct virtqueue *vq) +#define VIRTIO_MEDIA_NUM_EVENT_BUFS 16 + +/** + * virtio_media_session_alloc() - Allocate a new session. + * @vv: virtio-media device the session belongs to. + * @id: ID of the session. + * @file: file associated with the session. + */ +static struct virtio_media_session * +virtio_media_session_alloc(struct virtio_media *vv, u32 id, + struct file *file) +{ + struct virtio_media_session *session; + int i; + int ret; + + session =3D kzalloc_obj(*session, GFP_KERNEL); + if (!session) + goto err_session; + + session->shadow_buf =3D kzalloc(VIRTIO_SHADOW_BUF_SIZE, GFP_KERNEL); + if (!session->shadow_buf) + goto err_shadow_buf; + + ret =3D sg_alloc_table(&session->command_sgs, DESC_CHAIN_MAX_LEN, + GFP_KERNEL); + if (ret) + goto err_payload_sgs; + + session->id =3D id; + session->nonblocking_dequeue =3D file->f_flags & O_NONBLOCK; + + INIT_LIST_HEAD(&session->list); + v4l2_fh_init(&session->fh, &vv->video_dev); + virtio_media_session_fh_add(session, file); + + for (i =3D 0; i <=3D 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 @session, 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); + + virtio_media_session_fh_del(session); + v4l2_fh_exit(&session->fh); + + sg_free_table(&session->command_sgs); + + for (i =3D 0; i <=3D 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 sends the ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and fr= ees + * 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 =3D &session->cmd.close; + struct scatterlist cmd_sg =3D {}; + struct scatterlist *sgs[1] =3D { &cmd_sg }; + int ret; + + mutex_lock(&vv->vlock); + + cmd_close->hdr.cmd =3D VIRTIO_MEDIA_CMD_CLOSE; + cmd_close->session_id =3D session->id; + + sg_set_buf(&cmd_sg, cmd_close, sizeof(*cmd_close)); + sg_mark_end(&cmd_sg); + + ret =3D 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() - Look up a 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 =3D NULL; + + mutex_lock(&vv->sessions_lock); + list_for_each(p, &vv->sessions) { + struct virtio_media_session *s =3D + list_entry(p, struct virtio_media_session, list); + if (s->id =3D=3D id) { + session =3D s; + break; + } + } + mutex_unlock(&vv->sessions_lock); + + return session; +} + +/** + * struct virtio_media_cmd_callback_param - Callback parameters to the vir= tio + * 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 comple= te. + */ +static void commandq_callback(struct virtqueue *queue) +{ + unsigned int len; + struct virtio_media_cmd_callback_param *param; + +process_bufs: + while ((param =3D virtqueue_get_buf(queue, &len))) { + param->done =3D true; + param->resp_len =3D 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 res= ponse + * 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 =3D { + .vv =3D vv, + .done =3D false, + .resp_len =3D 0, + }; + struct virtio_media_resp_header *resp_header; + int ret =3D 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 =3D wait_event_timeout(vv->wq, cb_param.done, 5 * HZ); + if (ret =3D=3D 0) { + v4l2_err(&vv->v4l2_dev, + "timed out waiting for response to command\n"); + return -ETIMEDOUT; + } + + if (resp_len) + *resp_len =3D 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 =3D 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 res= ponse + * 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 =3D resp_len ? *resp_len : 0; + int ret =3D virtio_media_kick_command(vv, sgs, out_sgs, in_sgs, + &local_resp_len); + if (resp_len) + *resp_len =3D 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] =3D &vresp; + + ret =3D 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 =3D queue->vdev->priv; + + schedule_work(&vv->eventq_work); +} + +/** + * virtio_media_process_dqbuf_event() - Process a dequeued event for a ses= sion. + * @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 =3D 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 >=3D ARRAY_SIZE(session->queues)) { + v4l2_err(&vv->v4l2_dev, + "unmanaged queue %d passed to dqbuf event", + dqbuf_evt->buffer.type); + return; + } + queue =3D &session->queues[queue_type]; + + if (dqbuf_evt->buffer.index >=3D 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 =3D &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 =3D dqbuf->buffer.m; + memcpy(&dqbuf->buffer, &dqbuf_evt->buffer, sizeof(dqbuf->buffer)); + dqbuf->buffer.m =3D 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 =3D 0; i < dqbuf->buffer.length; i++) { + plane_m =3D dqbuf->planes[i].m; + memcpy(&dqbuf->planes[i], &dqbuf_evt->planes[i], + sizeof(struct v4l2_plane)); + dqbuf->planes[i].m =3D plane_m; + } + } + + /* Set the DONE flag as the buffer is waiting to be dequeued. */ + dqbuf->buffer.flags |=3D V4L2_BUF_FLAG_DONE; + + mutex_lock(&session->queues_lock); + list_add_tail(&dqbuf->list, &queue->pending_dqbufs); + queue->queued_bufs -=3D 1; + mutex_unlock(&session->queues_lock); + + wake_up(&session->dqbuf_wait); +} + +/** + * virtio_media_process_events() - Process all pending events on a device. + * @vv: device whose pending events we want to process. + * + * Retrieves all pending events on @vv's event queue and dispatches 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 =3D 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 =3D 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 =3D (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 =3D (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 =3D (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 =3D + 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 =3D video_devdata(file); + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_cmd_open *cmd_open =3D &vv->cmd.open; + struct virtio_media_resp_open *resp_open =3D &vv->resp.open; + struct scatterlist cmd_sg =3D {}, resp_sg =3D {}; + struct scatterlist *sgs[2] =3D { &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 =3D VIRTIO_MEDIA_CMD_OPEN; + ret =3D virtio_media_send_command(vv, sgs, 1, 1, sizeof(*resp_open), + NULL); + session_id =3D resp_open->session_id; + mutex_unlock(&vv->vlock); + if (ret < 0) + return ret; + + session =3D virtio_media_session_alloc(vv, session_id, file); + if (IS_ERR(session)) + return PTR_ERR(session); + + file->private_data =3D &session->fh; + + return 0; } =20 -static void eventq_callback(struct virtqueue *vq) +/** + * virtio_media_device_close() - Close a previously opened session. + * @file: file of the session to close. + * + * This sends the ``VIRTIO_MEDIA_CMD_CLOSE`` command to the device, and cl= oses + * the session on the driver side. + */ +static int virtio_media_device_close(struct file *file) { + struct video_device *video_dev =3D video_devdata(file); + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_session *session =3D + fh_to_session(file->private_data); + + return virtio_media_session_close(vv, session); } =20 static const struct v4l2_file_operations virtio_media_fops =3D { .owner =3D THIS_MODULE, - .open =3D v4l2_fh_open, - .release =3D v4l2_fh_release, + .open =3D virtio_media_device_open, + .release =3D virtio_media_device_close, }; =20 static int virtio_media_probe(struct virtio_device *virtio_dev) @@ -51,12 +615,23 @@ static int virtio_media_probe(struct virtio_device *vi= rtio_dev) }; struct virtio_media *vv; struct video_device *vd; + int i; int ret; =20 vv =3D devm_kzalloc(dev, sizeof(*vv), GFP_KERNEL); if (!vv) return -ENOMEM; =20 + const size_t virtio_media_event_aligned_size =3D + ALIGN(VIRTIO_MEDIA_EVENT_MAX_SIZE, dma_get_cache_alignment()); + + vv->event_buffer =3D devm_kzalloc(dev, + virtio_media_event_aligned_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); @@ -77,6 +652,7 @@ static int virtio_media_probe(struct virtio_device *virt= io_dev) =20 vv->commandq =3D vqs[0]; vv->eventq =3D vqs[1]; + INIT_WORK(&vv->eventq_work, virtio_media_event_work); =20 vd =3D &vv->video_dev; vd->v4l2_dev =3D &vv->v4l2_dev; @@ -100,10 +676,21 @@ static int virtio_media_probe(struct virtio_device *v= irtio_dev) if (ret) goto err_register_device; =20 + for (i =3D 0; i < VIRTIO_MEDIA_NUM_EVENT_BUFS; i++) { + void *ebuf =3D vv->event_buffer + + virtio_media_event_aligned_size * i; + + ret =3D virtio_media_send_event_buffer(vv, ebuf); + if (ret) + goto err_send_event_buffer; + } + virtio_device_ready(virtio_dev); =20 return 0; =20 +err_send_event_buffer: + video_unregister_device(&vv->video_dev); err_register_device: virtio_dev->config->del_vqs(virtio_dev); err_find_vqs: @@ -114,11 +701,20 @@ static int virtio_media_probe(struct virtio_device *v= irtio_dev) static void virtio_media_remove(struct virtio_device *virtio_dev) { struct virtio_media *vv =3D virtio_dev->priv; + struct list_head *p, *n; =20 + 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 =3D + list_entry(p, struct virtio_media_session, list); + + virtio_media_session_free(vv, s); + } } =20 static struct virtio_device_id id_table[] =3D { --=20 2.55.0.229.g6434b31f56-goog From nobody Fri Jul 24 04:50:45 2026 Received: from mail-qt1-f198.google.com (mail-qt1-f198.google.com [209.85.160.198]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 52F2F45D18E for ; Thu, 23 Jul 2026 18:32:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.198 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831582; cv=none; b=eUclTzd/Q3PFajQYvT6wxP3lJ/Uz9Hif/jsmnuODKqGD4bG4QOMOhvbe3vdPSbSXZ4pn/uzAGXtWiWXFV1h6ybWEK/D+nC/MdDqMCFB5BXgcZwSUmMJeliTrvgnSgmJBVpVUp/JCPB9GzMVKxrM1AThy5RHp5BIS/Xx/MYTRPFk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831582; c=relaxed/simple; bh=IPklzax1sX3JMecp1sHDk9yzLkie2zC7+TkNXOiCpfM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=lyTZ8EUXbzoIOju7C/xcF1hP0WAGgr88iLuFJ7Pk+MWLwR7sFLCmBnc6ODwR4n0+xvySSPbjV/oMnn1UnwtyT7OelL91bHOmYOExRwk1nQsuSDewCTBa5zEIRkmiO0ZcFPOPJbog4+ol5ZoSZMm5AoCzuaTxk6VaiBzoB7AHZ1s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=twdWr+2Q; arc=none smtp.client-ip=209.85.160.198 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="twdWr+2Q" Received: by mail-qt1-f198.google.com with SMTP id d75a77b69052e-51c1a97644aso25450901cf.2 for ; Thu, 23 Jul 2026 11:32:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784831568; x=1785436368; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=quGVCnraxnYQQECAxjbCFZOkewp1s0ghYIHCu6WLLM4=; b=twdWr+2Qw6Nwp+/rpX4/XWuz/V5VdFlqN3qnYk7Q3IeAOIWKviYwTUzNInJGcs4VOR y3uAhxY06DLrXyw5fFfJiJFbARl08wkVHs9lO909UM2q6mRfui2cjxGGyS3UH/IOQ3BR BFzAQz1SOo6HiR4gUkOA36BBwTyXvlae3eHFctbxJcW89NfG26+vRPASst22nO088uD4 YdXddzhyxmXeaE2fHwmd8DZClB3bdMTORvmGRW/f/34yN1VbpnzwtVQEL37NFSJ/HAoi x7/IwvmtclajOVUMF2V/ehcVmvuvIIQT/C2yaCGM9Pj+iqnwR1rWFX8tqLSLgROMfaSR uOpQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784831568; x=1785436368; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=quGVCnraxnYQQECAxjbCFZOkewp1s0ghYIHCu6WLLM4=; b=ffPYGEto1txdYGlxnBnY71QKLSpKUtWhvtHIixcH8/MHJ86eOWGzkLIhNYH8cQDXA1 3/UTFRCCqdIk5NPbmzdXYYH9BGvYtCVoiOIwM8XgsCW97wOnG8cV6CwAcqYp99mS5WQb terQJ00McQ6ew3UclM3ci9DcQBAaXIpQ7KXVMZSClZjmd+ib8GZbewSIvaZYQlszPY9L 2IofJjPAzIDY1eBb16Gz7euOYrky5IWyaHvjQY9U9B3wr0EflN+zm7L6m4Gaxb2rS8qN wSW66ytRQqU78JPE7KM1WZxRJ7aTHWEuS9+0G8q1pDKVJ6qg5K+SAhFpPjpwI7pABDc4 rFwA== X-Forwarded-Encrypted: i=1; AHgh+RrdTk70w6LIuySPYL9RrateY4uvjpvvj/YKVmlBI1EtO+1KCJT32BYr25ZiZX1+1M4EVHNAsDy5Cm1fZZo=@vger.kernel.org X-Gm-Message-State: AOJu0Yw13EN3/TRzGX83ok8jNElRUVbvv9YO9OnNXBZFlIXya7iDDwda MYSwJv/MgTqhws30P4txFHcR0aHC9jWJGOxHw6aZ4tWdsDrXRpsaU2Xdh0GFVZkETQHWIcJ/vIx xkCbFHRxv8YRdP94h9E1SUtIqXNXZ X-Received: from qvzt16.prod.google.com ([2002:a05:6214:5d0:b0:907:61d4:a0c7]) (user=briandaniels job=prod-delivery.src-stubby-dispatcher) by 2002:a05:622a:15d2:b0:51c:7b11:41ad with SMTP id d75a77b69052e-5283df5e260mr41044381cf.73.1784831567409; Thu, 23 Jul 2026 11:32:47 -0700 (PDT) Date: Thu, 23 Jul 2026 14:32:17 -0400 In-Reply-To: <20260723183219.737296-1-briandaniels@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260723183219.737296-1-briandaniels@google.com> X-Mailer: git-send-email 2.55.0.229.g6434b31f56-goog Message-ID: <20260723183219.737296-4-briandaniels@google.com> Subject: [PATCH v5 3/5] media: virtio: Add scatterlist builder From: Brian Daniels To: Mauro Carvalho Chehab Cc: adelva@google.com, aesteve@redhat.com, changyeon@google.com, daniel.almeida@collabora.com, eperezma@redhat.com, gnurou@gmail.com, gurchetansingh@google.com, hverkuil@xs4all.nl, jasowang@redhat.com, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, mst@redhat.com, nicolas.dufresne@collabora.com, virtualization@lists.linux.dev, xuanzhuo@linux.alibaba.com, Brian Daniels Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alexandre Courbot This patch adds the scatterlist builder, which is used to construct scatterlists for virtio commands from V4L2 structures. It adds drivers/media/virtio/scatterlist_builder.c and drivers/media/virtio/scatterlist_builder.h. Signed-off-by: Alexandre Courbot Assisted-by: Antigravity:gemini-3.5-flash Co-developed-by: Brian Daniels Signed-off-by: Brian Daniels --- drivers/media/virtio/Makefile | 2 +- drivers/media/virtio/scatterlist_builder.c | 515 +++++++++++++++++++++ drivers/media/virtio/scatterlist_builder.h | 109 +++++ 3 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 drivers/media/virtio/scatterlist_builder.c create mode 100644 drivers/media/virtio/scatterlist_builder.h diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile index 09d9834da..8290d8506 100644 --- a/drivers/media/virtio/Makefile +++ b/drivers/media/virtio/Makefile @@ -2,6 +2,6 @@ # # Makefile for the virtio-media device driver. =20 -virtio-media-objs :=3D virtio_media_driver.o +virtio-media-objs :=3D scatterlist_builder.o virtio_media_driver.o =20 obj-$(CONFIG_MEDIA_VIRTIO) +=3D virtio-media.o diff --git a/drivers/media/virtio/scatterlist_builder.c b/drivers/media/vir= tio/scatterlist_builder.c new file mode 100644 index 000000000..97925b277 --- /dev/null +++ b/drivers/media/virtio/scatterlist_builder.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ + +/* + * Scatterlist builder helpers for virtio-media. + * + * Copyright (c) 2024-2026 Google LLC. + */ + +#include +#include +#include +#include + +#include "uapi/linux/virtio_media.h" +#include "scatterlist_builder.h" +#include "session.h" + +/* + * If set to %true, then the driver will always copy the data passed to the + * host into the shadow buffer (instead of trying to map the source memory= into + * the SG table directly when possible). + */ +static bool always_use_shadow_buffer; +module_param(always_use_shadow_buffer, bool, 0660); + +/* Convert a V4L2 IOCTL into the IOCTL code we can give to the host */ +#define VIRTIO_MEDIA_IOCTL_CODE(IOCTL) (((IOCTL) >> _IOC_NRSHIFT) & _IOC_N= RMASK) + +/** + * scatterlist_builder_add_descriptor() - Add a descriptor to the chain. + * @builder: builder to use. + * @desc_index: index of the descriptor to add. + * + * Returns %-ENOSPC if @builder->sgs is already full. + */ +int scatterlist_builder_add_descriptor(struct scatterlist_builder *builder, + size_t desc_index) +{ + if (builder->cur_sg >=3D builder->num_sgs) + return -ENOSPC; + builder->sgs[builder->cur_sg++] =3D &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 buf= fer + * to be referenced from there. + */ +int scatterlist_builder_add_data(struct scatterlist_builder *builder, + void *data, size_t len) +{ + const size_t cur_desc =3D builder->cur_desc; + + if (len =3D=3D 0) + return 0; + + if (builder->cur_desc >=3D 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 =3D virt_to_page(data); + size_t offset =3D (((size_t)data) & ~PAGE_MASK); + struct scatterlist *next_desc =3D + &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 =3D -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 =3D vmalloc_to_page(data); + int cur_pfn =3D page_to_pfn(page); + /* All pages but the first will start at offset 0. */ + unsigned long offset =3D + (((unsigned long)data) & ~PAGE_MASK); + size_t len_in_page =3D min(PAGE_SIZE - offset, len); + struct scatterlist *next_desc =3D + &builder->descs[builder->cur_desc]; + + if (builder->cur_desc >=3D builder->num_descs) + return -ENOSPC; + + /* Optimize contiguous pages */ + if (cur_pfn =3D=3D prev_pfn + 1) { + (next_desc - 1)->length +=3D 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 +=3D len_in_page; + len -=3D len_in_page; + prev_pfn =3D 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 =3D + builder->shadow_buffer + builder->shadow_buffer_pos; + struct page *page =3D virt_to_page(shadow_buffer); + unsigned long offset =3D + (((unsigned long)shadow_buffer) & ~PAGE_MASK); + struct scatterlist *next_desc =3D + &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 +=3D 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 direc= tly. + * + * @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 =3D builder->shadow_buffer; + struct scatterlist *sg; + void *kaddr; + + /* We can only retrieve from the range of sgs currently set. */ + if (sg_index >=3D builder->cur_sg) + return -ERANGE; + + sg =3D builder->sgs[sg_index]; + kaddr =3D pfn_to_kaddr(page_to_pfn(sg_page(sg))) + sg->offset; + + if (kaddr >=3D shadow_buf && + kaddr < shadow_buf + VIRTIO_SHADOW_BUF_SIZE) { + if (kaddr + sg->length >=3D 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 descr= iptor + * 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 =3D &session->cmd.ioctl; + + cmd_ioctl->hdr.cmd =3D VIRTIO_MEDIA_CMD_IOCTL; + cmd_ioctl->session_id =3D session->id; + cmd_ioctl->code =3D 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 =3D &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 sha= dow + * 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. F= or + * 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 ca= se 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 =3D {}; + struct frame_vector *framevec; + struct scatterlist *sg_iter; + struct page **pages; + const unsigned int offset =3D userptr & ~PAGE_MASK; + unsigned int pages_count; + size_t entries_size; + int i; + int ret; + + framevec =3D vb2_create_framevec(userptr, length, true); + if (IS_ERR(framevec)) { + if (PTR_ERR(framevec) !=3D -EFAULT) { + pr_warn("error %ld creating frame vector for userptr 0x%lx, length 0x%l= x\n", + PTR_ERR(framevec), userptr, length); + } else { + /* -EINVAL is expected in case of invalid userptr. */ + framevec =3D ERR_PTR(-EINVAL); + } + return PTR_ERR(framevec); + } + + pages =3D frame_vector_pages(framevec); + if (IS_ERR(pages)) { + pr_warn("error getting vector pages\n"); + ret =3D PTR_ERR(pages); + goto done; + } + pages_count =3D frame_vector_count(framevec); + ret =3D 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 =3D sg_nents(sg_table.sgl); + entries_size =3D sizeof(**sg_list) * *nents; + if (builder->shadow_buffer_pos + entries_size > + builder->shadow_buffer_size) { + ret =3D -ENOMEM; + goto free_sg; + } + + *sg_list =3D builder->shadow_buffer + builder->shadow_buffer_pos; + builder->shadow_buffer_pos +=3D entries_size; + + for_each_sgtable_sg(&sg_table, sg_iter, i) { + struct virtio_media_sg_entry *sg_entry =3D &(*sg_list)[i]; + + sg_entry->start =3D sg_phys(sg_iter); + sg_entry->len =3D 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 a= rray + * 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 *bui= lder, + unsigned long userptr, + unsigned long length) +{ + int ret; + int nents; + struct virtio_media_sg_entry *sg_list; + + ret =3D __scatterlist_builder_add_userptr(builder, userptr, length, + &sg_list, &nents); + if (ret) + return ret; + + ret =3D scatterlist_builder_add_data(builder, sg_list, + sizeof(*sg_list) * nents); + if (ret) + return ret; + + return 0; +} + +/** + * scatterlist_builder_add_buffer() - Add a &struct v4l2_buffer and its pl= anes + * to the descriptor chain. + * @builder: builder to use. + * @b: &struct v4l2_buffer to add. + */ +int scatterlist_builder_add_buffer(struct scatterlist_builder *builder, + struct v4l2_buffer *b) +{ + int ret; + + /* v4l2_buffer */ + ret =3D scatterlist_builder_add_data(builder, b, sizeof(*b)); + if (ret) + return ret; + + if (V4L2_TYPE_IS_MULTIPLANAR(b->type) && b->length > 0) { + /* Array of v4l2_planes */ + ret =3D scatterlist_builder_add_data(builder, b->m.planes, + sizeof(struct v4l2_plane) * + b->length); + if (ret) + return ret; + } + + return 0; +} + +/** + * scatterlist_builder_retrieve_buffer() - Retrieve a &struct v4l2_buffer + * written by the device on the sh= adow + * 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: &struct v4l2_buffer to copy shadow buffer data into. + * @orig_planes: the original ``planes`` pointer, to be restored if the bu= ffer + * is multi-planar. + * + * If the &struct v4l2_buffer pointed to by @sg_index 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 s= hould + * not modify it, but we enforce this for additional safety. + */ +int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builde= r, + size_t sg_index, struct v4l2_buffer *b, + struct v4l2_plane *orig_planes) +{ + int ret; + + ret =3D scatterlist_builder_retrieve_data(builder, sg_index++, b); + if (ret) + return ret; + + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) { + b->m.planes =3D orig_planes; + + if (orig_planes) { + ret =3D scatterlist_builder_retrieve_data(builder, + sg_index++, + b->m.planes); + if (ret) + return ret; + } + } + + return 0; +} + +/** + * scatterlist_builder_add_ext_ctrls() - Add a &struct v4l2_ext_controls a= nd 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 =3D scatterlist_builder_add_data(builder, ctrls, sizeof(*ctrls)); + if (ret) + return ret; + + if (ctrls->count > 0) { + /* array of v4l2_controls */ + ret =3D 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 payloa= ds of + * a &struct v4l2_ext_contr= ols + * to the descriptor chain. + * @builder: builder to use. + * @ctrls: &struct v4l2_ext_controls from which we want to add the + * userspace payload. + * + * Add the userspace payloads of @ctrls to the descriptor chain. This is s= plit + * out of 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 *bui= lder, + struct v4l2_ext_controls *ctrls) +{ + int i; + int ret; + + /* Pointers to user memory in individual controls */ + for (i =3D 0; i < ctrls->count; i++) { + struct v4l2_ext_control *ctrl =3D &ctrls->controls[i]; + + if (ctrl->size > 0) { + unsigned long uptr =3D (unsigned long)ctrl->ptr; + + ret =3D scatterlist_builder_add_userptr(builder, uptr, + 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_index, copy its content back = into + * @ctrls. + */ +int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *bui= lder, + size_t sg_index, + struct v4l2_ext_controls *ctrls) +{ + struct v4l2_ext_control *controls_backup =3D ctrls->controls; + int ret; + + ret =3D scatterlist_builder_retrieve_data(builder, sg_index++, ctrls); + if (ret) + return ret; + + ctrls->controls =3D controls_backup; + + if (ctrls->count > 0 && ctrls->controls) { + ret =3D 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/vir= tio/scatterlist_builder.h new file mode 100644 index 000000000..47bfd7ae0 --- /dev/null +++ b/drivers/media/virtio/scatterlist_builder.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */ + +/* + * Scatterlist builder helpers for virtio-media. + * + * Copyright (c) 2024-2026 Google LLC. + */ + +#ifndef __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H +#define __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H + +#include + +#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 t= his. + * + * virtio-media descriptor chains are typically made of the following part= s: + * + * Device-readable: + * - A command structure, i.e. ``virtio_media_cmd_*``, + * - An ioctl payload (one of the regular ioctl parameters), + * - (optionally) arrays of &struct virtio_media_sg_entry describing the + * content of buffers in guest memory. + * + * Device-writable: + * - A response structure, i.e. ``virtio_media_resp_*``, + * - An ioctl payload, that the device will write to. + * + * This structure helps laying out the descriptor chain into its @sgs memb= er in + * an optimal way, by building a scatterlist adapted to the originating me= mory + * 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_retrieve_buffer(struct scatterlist_builder *builde= r, + 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 *bui= lder, + struct v4l2_ext_controls *ctrls); + +int scatterlist_builder_retrieve_ext_ctrls(struct scatterlist_builder *bui= lder, + size_t sg_index, + struct v4l2_ext_controls *ctrls); + +#endif // __VIRTIO_MEDIA_SCATTERLIST_BUILDER_H --=20 2.55.0.229.g6434b31f56-goog From nobody Fri Jul 24 04:50:45 2026 Received: from mail-qk1-f199.google.com (mail-qk1-f199.google.com [209.85.222.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6619C4766A3 for ; Thu, 23 Jul 2026 18:33:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831589; cv=none; b=g47Hx/Wn/C5/Ws1pTjQJ2O8h42C5ijK5E3aPi3yuTieNbTeQkFuf3n/tW7QUVna2VUQWjMCBNQh2Preys3gKkXr5QnUdv6x+dVUb4xS3TGBIG2ra+ruuEmjInKxFQJjILIt02i+xybqI4Bn4tkajZSFXgikxileispUXIG7LoKk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831589; c=relaxed/simple; bh=TqpULZeSJwnpV1tmrk7sIv+EOu4U5dJhvjkSHT5ZwBk=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=XV+tRGg2kTSC5aswRs5aHRFmH++gMfZLzglRUo3+YSOkCfr1BMgnO0FLrqZJoPvSZH+jnWFJdl24odLmhzrdzR2+GBJr8lWuruBcKTwo8L5NlOKI/fIZ+GJkaafzmjs8tSyVQTcuqMF9OckvoKcqHcYpj5J6cda6pi97gkTGfXM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=JXVnKH2L; arc=none smtp.client-ip=209.85.222.199 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="JXVnKH2L" Received: by mail-qk1-f199.google.com with SMTP id af79cd13be357-93106d8af18so142379885a.0 for ; Thu, 23 Jul 2026 11:33:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784831576; x=1785436376; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=LPveiruIQWQVV7ylEH05yJMi7A5h+qN6rjmy0Zw0t/E=; b=JXVnKH2LG40Ecug6qCXDWF5DXXIseMY7TlQsI3eAD1uEjZK2vOpuCZ95hoAEIcnXXx Kz3WV9ffmUTWr/me2OPbCIqbPEaPVqZTC9tLqmKXXu+NpcOGuOwEc1TOWu2TyN0dph+f 2fB/FdhweGQ4elw1accjnvY8wPtLiIj8cEoZy9kl1rA8IGYcxouYsiNeUlfhEFRlqzhQ bj59DUPuTg4+oMv0R8Bc5QQdZpz4Hxfb4oapCBwBheQ4FsexssSw8IXJ1j+K+txopkXU 8ZgSC55QKi8Zee+E1jTvkmZ4Vk7tVaykZhDtIGvrVbZY3ILjmeSTM/V9w2Mwz0IAW0Gc J3nA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784831576; x=1785436376; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=LPveiruIQWQVV7ylEH05yJMi7A5h+qN6rjmy0Zw0t/E=; b=FHxz83jSKbFuQXvrvBD2jgorDaVeinACXHaVLGNKgUX683J/tNo8tZx09xyFcrJjqr eMys2ACftm73xTFw3dQh4k4/HblNgoZyHlzK2YdWeOSzDfew9rb024CsOLtkpEnmvqrT KMSomVjETJEkwo6qXiyz1NqxUCOEIlRFkU+7usRLyxMU0qZAwfsLQ18VpSuqd2hdbeZF k5LIe1LvZaUBvEsGz1EZ8Ft41I62GtEm3y+WAD4vX2UpV40esHI/mpTnewSyMYSB7Qfa t+qbmu8pI2SKTiwffg60wZZKZKDLY1YdJ+V7yaQbn+V5EMgctJ79nZvVm0+iSuYrrxeK pB9Q== X-Forwarded-Encrypted: i=1; AHgh+Rr2ic2sL/ZPdwK6zoC/QdD9x74yyQ3gxWa0m1X0pSwjx/RnH9mYqETKGPkXyEhvlG/BKrd8ckr53ht5GiM=@vger.kernel.org X-Gm-Message-State: AOJu0YzQ2cDotb54EbUmjdYAOcQf/Nu+DqviKpu8TAwn0qTULWl0mh2i gf1ZGakbIsmd8Pr091upxGXH+KQqgYXpiv4J3zYP+iuU6yE+9trEryN4uBn67aSq7ZyTK3N49j3 SXAs2C2uRn03kcw8sc9XIruErK6jW X-Received: from qkle6.prod.google.com ([2002:a05:620a:12c6:b0:92e:85ff:7581]) (user=briandaniels job=prod-delivery.src-stubby-dispatcher) by 2002:a05:620a:25d0:b0:930:b084:288e with SMTP id af79cd13be357-93103a800fbmr412750185a.80.1784831575904; Thu, 23 Jul 2026 11:32:55 -0700 (PDT) Date: Thu, 23 Jul 2026 14:32:18 -0400 In-Reply-To: <20260723183219.737296-1-briandaniels@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260723183219.737296-1-briandaniels@google.com> X-Mailer: git-send-email 2.55.0.229.g6434b31f56-goog Message-ID: <20260723183219.737296-5-briandaniels@google.com> Subject: [PATCH v5 4/5] media: virtio: Add ioctl operations and driver logic From: Brian Daniels To: Mauro Carvalho Chehab Cc: adelva@google.com, aesteve@redhat.com, changyeon@google.com, daniel.almeida@collabora.com, eperezma@redhat.com, gnurou@gmail.com, gurchetansingh@google.com, hverkuil@xs4all.nl, jasowang@redhat.com, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, mst@redhat.com, nicolas.dufresne@collabora.com, virtualization@lists.linux.dev, xuanzhuo@linux.alibaba.com, Brian Daniels Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alexandre Courbot This patch adds the ioctl operations and the remaining driver logic for polling and mmapping. It adds drivers/media/virtio/virtio_media_ioctls.c and updates virtio_media_driver.c to support poll, mmap, and ioctls. Signed-off-by: Alexandre Courbot Assisted-by: Antigravity:gemini-3.5-flash Co-developed-by: Brian Daniels Signed-off-by: Brian Daniels --- drivers/media/virtio/Makefile | 2 +- drivers/media/virtio/virtio_media_driver.c | 195 +++ drivers/media/virtio/virtio_media_ioctls.c | 1319 ++++++++++++++++++++ 3 files changed, 1515 insertions(+), 1 deletion(-) create mode 100644 drivers/media/virtio/virtio_media_ioctls.c diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile index 8290d8506..f1bc8a3ce 100644 --- a/drivers/media/virtio/Makefile +++ b/drivers/media/virtio/Makefile @@ -2,6 +2,6 @@ # # Makefile for the virtio-media device driver. =20 -virtio-media-objs :=3D scatterlist_builder.o virtio_media_driver.o +virtio-media-objs :=3D scatterlist_builder.o virtio_media_ioctls.o virtio_= media_driver.o =20 obj-$(CONFIG_MEDIA_VIRTIO) +=3D virtio-media.o diff --git a/drivers/media/virtio/virtio_media_driver.c b/drivers/media/vir= tio/virtio_media_driver.c index 938786b05..c431c3eb2 100644 --- a/drivers/media/virtio/virtio_media_driver.c +++ b/drivers/media/virtio/virtio_media_driver.c @@ -6,6 +6,7 @@ * Copyright (c) 2024-2026 Google LLC. */ =20 +#include #include #include #include @@ -19,6 +20,8 @@ #include #include #include +#include +#include =20 #include #include @@ -31,6 +34,12 @@ =20 #define VIRTIO_MEDIA_NUM_EVENT_BUFS 16 =20 +/* ID of the SHM region into which MMAP buffer will be mapped. */ +#define VIRTIO_MEDIA_SHM_MMAP 0 + +/* Bit mask for the VIRTIO_MEDIA_MMAP_FLAG_RW flag */ +#define VIRTIO_MEDIA_MMAP_FLAG_RW_MASK BIT(VIRTIO_MEDIA_MMAP_FLAG_RW) + /** * virtio_media_session_alloc() - Allocate a new session. * @vv: virtio-media device the session belongs to. @@ -593,10 +602,191 @@ static int virtio_media_device_close(struct file *fi= le) return virtio_media_session_close(vv, session); } =20 +/** + * 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 *wa= it) +{ + struct virtio_media_session *session =3D + fh_to_session(file->private_data); + enum v4l2_buf_type capture_type =3D + session->uses_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : + V4L2_BUF_TYPE_VIDEO_CAPTURE; + enum v4l2_buf_type output_type =3D + session->uses_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : + V4L2_BUF_TYPE_VIDEO_OUTPUT; + struct virtio_media_queue_state *capture_queue =3D + &session->queues[capture_type]; + struct virtio_media_queue_state *output_queue =3D + &session->queues[output_type]; + __poll_t req_events =3D poll_requested_events(wait); + __poll_t rc =3D 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 =3D=3D 0 && + list_empty(&capture_queue->pending_dqbufs))) + rc |=3D EPOLLERR; + else if (!list_empty(&capture_queue->pending_dqbufs)) + rc |=3D EPOLLIN | EPOLLRDNORM; + } + if (req_events & (EPOLLOUT | EPOLLWRNORM)) { + if (!output_queue->streaming) + rc |=3D EPOLLERR; + else if (output_queue->queued_bufs < + output_queue->allocated_bufs) + rc |=3D EPOLLOUT | EPOLLWRNORM; + } + mutex_unlock(&session->queues_lock); + + if (v4l2_event_pending(&session->fh)) + rc |=3D EPOLLPRI; + + return rc; +} + +static void virtio_media_vma_close_locked(struct vm_area_struct *vma) +{ + struct virtio_media *vv =3D vma->vm_private_data; + struct virtio_media_cmd_munmap *cmd_munmap =3D &vv->cmd.munmap; + struct virtio_media_resp_munmap *resp_munmap =3D &vv->resp.munmap; + struct scatterlist cmd_sg =3D {}, resp_sg =3D {}; + struct scatterlist *sgs[2] =3D { &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 =3D VIRTIO_MEDIA_CMD_MUNMAP; + cmd_munmap->driver_addr =3D + (vma->vm_pgoff << PAGE_SHIFT) - vv->mmap_region.addr; + ret =3D 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 nee= ded + * and can be removed. + */ +static void virtio_media_vma_close(struct vm_area_struct *vma) +{ + struct virtio_media *vv =3D 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 =3D { + .close =3D 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 =3D video_devdata(file); + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_session *session =3D + fh_to_session(file->private_data); + struct virtio_media_cmd_mmap *cmd_mmap =3D &session->cmd.mmap; + struct virtio_media_resp_mmap *resp_mmap =3D &session->resp.mmap; + struct scatterlist cmd_sg =3D {}, resp_sg =3D {}; + struct scatterlist *sgs[2] =3D { &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 =3D VIRTIO_MEDIA_CMD_MMAP; + cmd_mmap->session_id =3D session->id; + cmd_mmap->flags =3D + (vma->vm_flags & VM_WRITE) ? VIRTIO_MEDIA_MMAP_FLAG_RW_MASK : 0; + cmd_mmap->offset =3D 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 =3D virtio_media_send_command(vv, sgs, 1, 1, sizeof(*resp_mmap), + NULL); + if (ret < 0) + goto end; + + vma->vm_private_data =3D vv; + /* + * Keep the guest address at which the buffer is mapped since we will + * use that to unmap. + */ + vma->vm_pgoff =3D (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 =3D -EINVAL; + goto end; + } + + ret =3D 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 =3D &virtio_media_vm_ops; + +end: + mutex_unlock(&vv->vlock); + return ret; +} + static const struct v4l2_file_operations virtio_media_fops =3D { .owner =3D THIS_MODULE, .open =3D virtio_media_device_open, .release =3D virtio_media_device_close, + .poll =3D virtio_media_device_poll, + .unlocked_ioctl =3D virtio_media_device_ioctl, + .mmap =3D virtio_media_device_mmap, }; =20 static int virtio_media_probe(struct virtio_device *virtio_dev) @@ -654,9 +844,14 @@ static int virtio_media_probe(struct virtio_device *vi= rtio_dev) vv->eventq =3D vqs[1]; INIT_WORK(&vv->eventq_work, virtio_media_event_work); =20 + /* Get MMAP buffer mapping SHM region */ + virtio_get_shm_region(virtio_dev, &vv->mmap_region, + VIRTIO_MEDIA_SHM_MMAP); + vd =3D &vv->video_dev; vd->v4l2_dev =3D &vv->v4l2_dev; vd->vfl_type =3D VFL_TYPE_VIDEO; + vd->ioctl_ops =3D &virtio_media_ioctl_ops; vd->fops =3D &virtio_media_fops; vd->release =3D video_device_release_empty; strscpy(vd->name, "virtio-media", sizeof(vd->name)); diff --git a/drivers/media/virtio/virtio_media_ioctls.c b/drivers/media/vir= tio/virtio_media_ioctls.c new file mode 100644 index 000000000..f0b82b5ec --- /dev/null +++ b/drivers/media/virtio/virtio_media_ioctls.c @@ -0,0 +1,1319 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ + +/* + * Ioctl implementations for the virtio-media driver. + * + * Copyright (c) 2024-2026 Google LLC. + */ + +#include +#include +#include +#include +#include +#include + +#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 t= he + * 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 =3D fh->vdev; + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_session *session =3D fh_to_session(fh); + struct scatterlist *sgs[3]; + struct scatterlist_builder builder =3D { + .descs =3D session->command_sgs.sgl, + .num_descs =3D DESC_CHAIN_MAX_LEN, + .cur_desc =3D 0, + .shadow_buffer =3D session->shadow_buf, + .shadow_buffer_size =3D VIRTIO_SHADOW_BUF_SIZE, + .shadow_buffer_pos =3D 0, + .sgs =3D sgs, + .num_sgs =3D ARRAY_SIZE(sgs), + .cur_sg =3D 0, + }; + + /* Command descriptor */ + int ret =3D scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl); + + if (ret) + return ret; + + /* Response descriptor */ + ret =3D scatterlist_builder_add_ioctl_resp(&builder, session); + if (ret) + return ret; + + /* Response payload */ + ret =3D 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 =3D virtio_media_send_command(vv, sgs, 1, 2, + sizeof(struct virtio_media_resp_ioctl) + + ioctl_data_len, NULL); + if (ret < 0) + return ret; + + ret =3D 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 =3D fh->vdev; + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_session *session =3D fh_to_session(fh); + struct scatterlist *sgs[3]; + struct scatterlist_builder builder =3D { + .descs =3D session->command_sgs.sgl, + .num_descs =3D DESC_CHAIN_MAX_LEN, + .cur_desc =3D 0, + .shadow_buffer =3D session->shadow_buf, + .shadow_buffer_size =3D VIRTIO_SHADOW_BUF_SIZE, + .shadow_buffer_pos =3D 0, + .sgs =3D sgs, + .num_sgs =3D ARRAY_SIZE(sgs), + .cur_sg =3D 0, + }; + + /* Command descriptor */ + int ret =3D scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl); + + if (ret) + return ret; + + /* Command payload */ + ret =3D 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 =3D scatterlist_builder_add_ioctl_resp(&builder, session); + if (ret) + return ret; + + ret =3D 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 payloa= d. + * + * 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 =3D fh->vdev; + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_session *session =3D fh_to_session(fh); + struct scatterlist *sgs[4]; + struct scatterlist_builder builder =3D { + .descs =3D session->command_sgs.sgl, + .num_descs =3D DESC_CHAIN_MAX_LEN, + .cur_desc =3D 0, + .shadow_buffer =3D session->shadow_buf, + .shadow_buffer_size =3D VIRTIO_SHADOW_BUF_SIZE, + .shadow_buffer_pos =3D 0, + .sgs =3D sgs, + .num_sgs =3D ARRAY_SIZE(sgs), + .cur_sg =3D 0, + }; + + /* Command descriptor */ + int ret =3D scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl); + + if (ret) + return ret; + + /* Command payload */ + ret =3D 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 =3D scatterlist_builder_add_ioctl_resp(&builder, session); + if (ret) + return ret; + + /* Response payload, same as command */ + ret =3D scatterlist_builder_add_descriptor(&builder, 1); + if (ret) + return ret; + + ret =3D virtio_media_send_command(vv, sgs, 2, 2, + sizeof(struct virtio_media_resp_ioctl) + + minimum_resp_payload, + NULL); + if (ret < 0) + return ret; + + ret =3D scatterlist_builder_retrieve_data(&builder, 3, ioctl_data); + if (ret) { + v4l2_err(&vv->v4l2_dev, + "failed to retrieve response descriptor chain\n"); + return ret; + } + + return 0; +} + +/** + * virtio_media_send_buffer_ioctl() - Send an ioctl taking a buffer as + * parameter to the device. + * @fh: file handler of the session doing the ioctl. + * @ioctl: ``VIDIOC_*`` ioctl code. + * @b: &struct v4l2_buffer to be sent as the ioctl payload. + * + * Buffers can require an additional descriptor to send their planes array= , and + * can have pointers to userspace memory hence this dedicated function. + */ +static int virtio_media_send_buffer_ioctl(struct v4l2_fh *fh, u32 ioctl, + struct v4l2_buffer *b) +{ + struct video_device *video_dev =3D fh->vdev; + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_session *session =3D fh_to_session(fh); + struct v4l2_plane *orig_planes =3D 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 =3D { + .descs =3D session->command_sgs.sgl, + .num_descs =3D DESC_CHAIN_MAX_LEN, + .cur_desc =3D 0, + .shadow_buffer =3D session->shadow_buf, + .shadow_buffer_size =3D VIRTIO_SHADOW_BUF_SIZE, + .shadow_buffer_pos =3D 0, + .sgs =3D sgs, + .num_sgs =3D ARRAY_SIZE(sgs), + .cur_sg =3D 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 =3D b->m.planes; + + /* Command descriptor */ + ret =3D scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl); + if (ret) + return ret; + + /* Command payload (struct v4l2_buffer) */ + ret =3D scatterlist_builder_add_buffer(&builder, b); + if (ret < 0) + return ret; + + end_buf_sg =3D builder.cur_sg; + num_cmd_sgs =3D builder.cur_sg; + + /* Response descriptor */ + ret =3D scatterlist_builder_add_ioctl_resp(&builder, session); + if (ret) + return ret; + + /* Response payload (same as input, but no userptr mapping) */ + for (i =3D 1; i < end_buf_sg; i++) { + ret =3D scatterlist_builder_add_descriptor(&builder, i); + if (ret < 0) + return ret; + } + + ret =3D 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 -=3D sizeof(struct virtio_media_resp_ioctl); + + /* Make sure that the reply length covers our v4l2_buffer */ + if (resp_len < sizeof(*b)) + return -EINVAL; + + ret =3D scatterlist_builder_retrieve_buffer(&builder, num_cmd_sgs + 1, b, + orig_planes); + if (ret) { + v4l2_err(&vv->v4l2_dev, + "failed to retrieve response descriptor chain\n"); + return ret; + } + + return 0; +} + +/** + * virtio_media_send_ext_controls_ioctl() - Send an ioctl taking extended + * controls as parameters to the device. + * @fh: file handler of the session doing the ioctl. + * @ioctl: ``VIDIOC_*`` ioctl code. + * @ctrls: &struct v4l2_ext_controls to be sent as the ioctl payload. + * + * Queues an ioctl that sends a &struct v4l2_ext_controls to the host and + * receives an updated version. + * + * &struct v4l2_ext_controls has a pointer to an array of + * &struct v4l2_ext_control, and also potentially pointers to user-space m= emory + * that we need to map properly, hence the dedicated function. + */ +static int virtio_media_send_ext_controls_ioctl(struct v4l2_fh *fh, u32 io= ctl, + struct v4l2_ext_controls *ctrls) +{ + struct video_device *video_dev =3D fh->vdev; + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_session *session =3D fh_to_session(fh); + size_t num_cmd_sgs; + size_t end_ctrls_sg; + struct v4l2_ext_control *controls_backup =3D ctrls->controls; + const u32 num_ctrls =3D ctrls->count; + struct scatterlist *sgs[64]; + struct scatterlist_builder builder =3D { + .descs =3D session->command_sgs.sgl, + .num_descs =3D DESC_CHAIN_MAX_LEN, + .cur_desc =3D 0, + .shadow_buffer =3D session->shadow_buf, + .shadow_buffer_size =3D VIRTIO_SHADOW_BUF_SIZE, + .shadow_buffer_pos =3D 0, + .sgs =3D sgs, + .num_sgs =3D ARRAY_SIZE(sgs), + .cur_sg =3D 0, + }; + size_t resp_len =3D 0; + int i; + + /* Command descriptor */ + int ret =3D scatterlist_builder_add_ioctl_cmd(&builder, session, ioctl); + + if (ret) + return ret; + + /* v4l2_controls */ + ret =3D scatterlist_builder_add_ext_ctrls(&builder, ctrls); + if (ret) + return ret; + + end_ctrls_sg =3D builder.cur_sg; + + ret =3D scatterlist_builder_add_ext_ctrls_userptrs(&builder, ctrls); + if (ret) + return ret; + + num_cmd_sgs =3D builder.cur_sg; + + /* Response descriptor */ + ret =3D scatterlist_builder_add_ioctl_resp(&builder, session); + if (ret) + return ret; + + /* Response payload (same as input but without userptrs) */ + for (i =3D 1; i < end_ctrls_sg; i++) { + ret =3D scatterlist_builder_add_descriptor(&builder, i); + if (ret < 0) + return ret; + } + + ret =3D 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 =3D controls_backup; + if (ctrls->count !=3D num_ctrls) { + v4l2_err(&vv->v4l2_dev, + "device returned a number of controls different than the one submitted= \n"); + } + if (ctrls->count > num_ctrls) + return -ENOSPC; + + /* + * Even if we have received an error, we may need to read our payload + * back. + */ + if (ret < 0 && resp_len >=3D 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 -=3D 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 =3D 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 =3D + list_entry(p, struct virtio_media_buffer, list); + + list_del(&dqbuf->list); + } + + /* All buffers are now dequeued. */ + for (i =3D 0; i < queue->allocated_bufs; i++) + queue->buffers[i].buffer.flags =3D 0; + + queue->queued_bufs =3D 0; + queue->streaming =3D false; + queue->is_capture_last =3D false; + + mutex_unlock(&session->queues_lock); +} + +/* + * Macros suitable for defining ioctls with a constant size payload. + */ + +#define SIMPLE_WR_IOCTL(name, ioctl, payload_t) \ + static int virtio_media_##name(struct file *file, void *fh, \ + payload_t *payload) \ + { \ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); \ + return virtio_media_send_wr_ioctl(vfh, ioctl, payload,\ + sizeof(*payload), \ + sizeof(*payload)); \ + } +#define SIMPLE_R_IOCTL(name, ioctl, payload_t) \ + static int virtio_media_##name(struct file *file, void *fh, \ + payload_t *payload) \ + { \ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); \ + return virtio_media_send_r_ioctl(vfh, ioctl, payload,\ + sizeof(*payload)); \ + } +#define SIMPLE_W_IOCTL(name, ioctl, payload_t) \ + static int virtio_media_##name(struct file *file, void *fh, \ + payload_t *payload) \ + { \ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); \ + return virtio_media_send_w_ioctl(vfh, ioctl, payload,\ + sizeof(*payload)); \ + } + +/* + * V4L2 ioctl handlers. + * + * Most of these functions just forward the ioctl to the host, for these w= e 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_modulato= r) +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_frequenc= y) +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 =3D video_devdata(file); + struct virtio_media *vv =3D to_virtio_media(video_dev); + + strscpy(cap->bus_info, "platform:virtio-media"); + strscpy(cap->driver, VIRTIO_MEDIA_DEFAULT_DRIVER_NAME); + + virtio_cread_bytes(vv->virtio_dev, 8, cap->card, sizeof(cap->card)); + + cap->capabilities =3D video_dev->device_caps | V4L2_CAP_DEVICE_CAPS; + cap->device_caps =3D video_dev->device_caps; + + return 0; +} + +/* + * Extended control ioctls are handled mostly identically. + */ + +static int virtio_media_g_ext_ctrls(struct file *file, void *fh, + struct v4l2_ext_controls *ctrls) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + + return virtio_media_send_ext_controls_ioctl(vfh, VIDIOC_G_EXT_CTRLS, + ctrls); +} + +static int virtio_media_s_ext_ctrls(struct file *file, void *fh, + struct v4l2_ext_controls *ctrls) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + + return virtio_media_send_ext_controls_ioctl(vfh, VIDIOC_S_EXT_CTRLS, + ctrls); +} + +static int virtio_media_try_ext_ctrls(struct file *file, void *fh, + struct v4l2_ext_controls *ctrls) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + + return virtio_media_send_ext_controls_ioctl(vfh, VIDIOC_TRY_EXT_CTRLS, + ctrls); +} + +/* + * Subscribe/unsubscribe from an event. + */ + +static int +virtio_media_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + struct video_device *video_dev =3D fh->vdev; + struct virtio_media *vv =3D to_virtio_media(video_dev); + int ret; + + /* First subscribe to the event in the guest. */ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + ret =3D v4l2_src_change_event_subscribe(fh, sub); + break; + default: + ret =3D v4l2_event_subscribe(fh, sub, 1, NULL); + break; + } + if (ret) + return ret; + + /* Then ask the host to signal us these events. */ + ret =3D 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 =3D virtio_media_send_w_ioctl(fh, VIDIOC_UNSUBSCRIBE_EVENT, sub, + sizeof(*sub)); + if (ret < 0) + return ret; + + ret =3D v4l2_event_unsubscribe(fh, sub); + if (ret) + return ret; + + return 0; +} + +/* + * Streamon/off affect the local queue state. + */ + +static int virtio_media_streamon(struct file *file, void *fh, + enum v4l2_buf_type i) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + struct virtio_media_session *session =3D fh_to_session(vfh); + int ret; + + if (i > VIRTIO_MEDIA_LAST_QUEUE) + return -EINVAL; + + ret =3D virtio_media_send_w_ioctl(vfh, VIDIOC_STREAMON, &i, sizeof(i)); + if (ret < 0) + return ret; + + session->queues[i].streaming =3D true; + + return 0; +} + +static int virtio_media_streamoff(struct file *file, void *fh, + enum v4l2_buf_type i) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + struct virtio_media_session *session =3D fh_to_session(vfh); + int ret; + + if (i > VIRTIO_MEDIA_LAST_QUEUE) + return -EINVAL; + + ret =3D virtio_media_send_w_ioctl(vfh, VIDIOC_STREAMOFF, &i, sizeof(i)); + if (ret < 0) + return ret; + + virtio_media_clear_queue(session, &session->queues[i]); + + return 0; +} + +/* + * Buffer creation/queuing functions deal with the local driver state. + */ + +static int virtio_media_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *b) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + struct virtio_media_session *session =3D fh_to_session(vfh); + struct virtio_media_queue_state *queue; + int ret; + + if (b->type > VIRTIO_MEDIA_LAST_QUEUE) + return -EINVAL; + + if (b->memory =3D=3D V4L2_MEMORY_USERPTR) + return -EINVAL; + + ret =3D virtio_media_send_wr_ioctl(vfh, VIDIOC_REQBUFS, b, sizeof(*b), + sizeof(*b)); + if (ret) + return ret; + + queue =3D &session->queues[b->type]; + + /* REQBUFS(0) is an implicit STREAMOFF. */ + if (b->count =3D=3D 0) + virtio_media_clear_queue(session, queue); + + vfree(queue->buffers); + queue->buffers =3D NULL; + + if (b->count > 0) { + queue->buffers =3D + vzalloc(sizeof(struct virtio_media_buffer) * b->count); + if (!queue->buffers) + return -ENOMEM; + } + + queue->allocated_bufs =3D 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 =3D true; + + b->capabilities &=3D ~V4L2_BUF_CAP_SUPPORTS_USERPTR; + + /* We do not support DMABUF yet. */ + b->capabilities &=3D ~V4L2_BUF_CAP_SUPPORTS_DMABUF; + + return 0; +} + +static int virtio_media_querybuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + struct virtio_media_session *session =3D fh_to_session(vfh); + struct virtio_media_queue_state *queue; + struct virtio_media_buffer *buffer; + + int ret =3D virtio_media_send_buffer_ioctl(vfh, VIDIOC_QUERYBUF, b); + + if (ret) + return ret; + + if (b->type > VIRTIO_MEDIA_LAST_QUEUE) + return -EINVAL; + + queue =3D &session->queues[b->type]; + if (b->index >=3D queue->allocated_bufs) + return -EINVAL; + + buffer =3D &queue->buffers[b->index]; + /* + * Set the DONE flag if the buffer is waiting in our own dequeue + * queue. + */ + b->flags |=3D (buffer->buffer.flags & V4L2_BUF_FLAG_DONE); + + return 0; +} + +static int virtio_media_create_bufs(struct file *file, void *fh, + struct v4l2_create_buffers *b) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + struct virtio_media_session *session =3D fh_to_session(vfh); + struct virtio_media_queue_state *queue; + struct virtio_media_buffer *buffers; + u32 type =3D b->format.type; + int ret; + + if (type > VIRTIO_MEDIA_LAST_QUEUE) + return -EINVAL; + + queue =3D &session->queues[type]; + + ret =3D virtio_media_send_wr_ioctl(vfh, VIDIOC_CREATE_BUFS, b, sizeof(*b), + sizeof(*b)); + if (ret) + return ret; + + /* If count is zero, we were just checking for format. */ + if (b->count =3D=3D 0) + return 0; + + buffers =3D queue->buffers; + + queue->buffers =3D + vzalloc(sizeof(*queue->buffers) * (b->index + b->count)); + if (!queue->buffers) { + queue->buffers =3D buffers; + return -ENOMEM; + } + + memcpy(queue->buffers, buffers, + sizeof(*buffers) * queue->allocated_bufs); + vfree(buffers); + + queue->allocated_bufs =3D b->index + b->count; + + return 0; +} + +static int virtio_media_prepare_buf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + struct virtio_media_session *session =3D fh_to_session(vfh); + struct virtio_media_queue_state *queue; + struct virtio_media_buffer *buffer; + int i, ret; + + if (b->type > VIRTIO_MEDIA_LAST_QUEUE) + return -EINVAL; + queue =3D &session->queues[b->type]; + if (b->index >=3D queue->allocated_bufs) + return -EINVAL; + buffer =3D &queue->buffers[b->index]; + + buffer->buffer.m =3D b->m; + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) { + if (b->length > VIDEO_MAX_PLANES) + return -EINVAL; + for (i =3D 0; i < b->length; i++) + buffer->planes[i].m =3D b->m.planes[i].m; + } + + ret =3D virtio_media_send_buffer_ioctl(vfh, VIDIOC_PREPARE_BUF, b); + if (ret) + return ret; + + buffer->buffer.flags =3D V4L2_BUF_FLAG_PREPARED; + + return 0; +} + +static int virtio_media_qbuf(struct file *file, void *fh, struct v4l2_buff= er *b) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + struct virtio_media_session *session =3D fh_to_session(vfh); + struct virtio_media_queue_state *queue; + struct virtio_media_buffer *buffer; + bool prepared; + u32 old_flags; + int i, ret; + + if (b->type > VIRTIO_MEDIA_LAST_QUEUE) + return -EINVAL; + queue =3D &session->queues[b->type]; + if (b->index >=3D queue->allocated_bufs) + return -EINVAL; + buffer =3D &queue->buffers[b->index]; + prepared =3D 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 =3D b->m; + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) { + if (b->length > VIDEO_MAX_PLANES) + return -EINVAL; + for (i =3D 0; i < b->length; i++) + buffer->planes[i].m =3D b->m.planes[i].m; + } + } + old_flags =3D buffer->buffer.flags; + buffer->buffer.flags =3D V4L2_BUF_FLAG_QUEUED; + + ret =3D virtio_media_send_buffer_ioctl(vfh, VIDIOC_QBUF, b); + if (ret) { + /* Rollback the previous flags as the buffer is not queued. */ + buffer->buffer.flags =3D old_flags; + return ret; + } + + queue->queued_bufs +=3D 1; + + return 0; +} + +static int virtio_media_dqbuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct video_device *video_dev =3D video_devdata(file); + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct virtio_media_session *session =3D + fh_to_session(file_to_v4l2_fh(file)); + struct virtio_media_buffer *dqbuf; + struct virtio_media_queue_state *queue; + struct list_head *buffer_queue; + struct v4l2_plane *planes_backup =3D NULL; + const bool is_multiplanar =3D V4L2_TYPE_IS_MULTIPLANAR(b->type); + int ret; + + if (b->type > VIRTIO_MEDIA_LAST_QUEUE) + return -EINVAL; + + queue =3D &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 =3D &queue->pending_dqbufs; + + if (session->nonblocking_dequeue) { + if (list_empty(buffer_queue)) + return -EAGAIN; + } else if (queue->allocated_bufs =3D=3D 0) { + return -EINVAL; + } else if (!queue->streaming) { + return -EINVAL; + } + + /* + * vv->lock has been acquired by virtio_media_device_ioctl. Release it + * while we wait so that other ioctls for this session can be processed + * and potentially trigger dqbuf_wait. + */ + mutex_unlock(&vv->vlock); + ret =3D wait_event_interruptible(session->dqbuf_wait, + !list_empty(buffer_queue)); + mutex_lock(&vv->vlock); + if (ret) + return -EINTR; + + mutex_lock(&session->queues_lock); + dqbuf =3D 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 &=3D ~V4L2_BUF_FLAG_DONE; + + if (is_multiplanar) { + size_t nb_planes =3D min_t(u32, b->length, VIDEO_MAX_PLANES); + + memcpy(b->m.planes, dqbuf->planes, + nb_planes * sizeof(struct v4l2_plane)); + planes_backup =3D b->m.planes; + } + + memcpy(b, &dqbuf->buffer, sizeof(*b)); + + if (is_multiplanar) + b->m.planes =3D planes_backup; + + if (V4L2_TYPE_IS_CAPTURE(b->type) && b->flags & V4L2_BUF_FLAG_LAST) + queue->is_capture_last =3D 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 =3D virtio_media_send_wr_ioctl(file_to_v4l2_fh(file), + VIDIOC_G_INPUT, &input, + sizeof(input), sizeof(input)); + if (ret) + return ret; + + *i =3D input; + + return 0; +} + +static int virtio_media_s_input(struct file *file, void *fh, unsigned int = i) +{ + u32 input =3D i; + + return virtio_media_send_wr_ioctl(file_to_v4l2_fh(file), + VIDIOC_S_INPUT, &input, + sizeof(input), sizeof(input)); +} + +static int virtio_media_g_output(struct file *file, void *fh, unsigned int= *o) +{ + u32 output; + + int ret =3D virtio_media_send_wr_ioctl(file_to_v4l2_fh(file), + VIDIOC_G_OUTPUT, &output, + sizeof(output), sizeof(output)); + if (ret) + return ret; + + *o =3D output; + + return 0; +} + +static int virtio_media_s_output(struct file *file, void *fh, unsigned int= o) +{ + u32 output =3D o; + + return virtio_media_send_wr_ioctl(file_to_v4l2_fh(file), + VIDIOC_S_OUTPUT, &output, + sizeof(output), sizeof(output)); +} + +/* + * decoder_cmd can affect the state of the CAPTURE queue. + */ + +static int virtio_media_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *cmd) +{ + struct v4l2_fh *vfh =3D file_to_v4l2_fh(file); + struct virtio_media_session *session =3D fh_to_session(vfh); + + int ret =3D virtio_media_send_wr_ioctl(vfh, VIDIOC_DECODER_CMD, cmd, + sizeof(*cmd), sizeof(*cmd)); + if (ret) + return ret; + + /* A START command makes the CAPTURE queue able to dequeue again. */ + if (cmd->cmd =3D=3D V4L2_DEC_CMD_START) { + session->queues[V4L2_BUF_TYPE_VIDEO_CAPTURE].is_capture_last =3D + false; + session->queues[V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE] + .is_capture_last =3D 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 =3D virtio_media_send_w_ioctl(file_to_v4l2_fh(file), VIDIOC_S_STD, + &s, sizeof(s)); + if (ret) + return ret; + + return 0; +} + +const struct v4l2_ioctl_ops virtio_media_ioctl_ops =3D { + /* VIDIOC_QUERYCAP handler */ + .vidioc_querycap =3D virtio_media_querycap, + + /* VIDIOC_ENUM_FMT handlers */ + .vidioc_enum_fmt_vid_cap =3D virtio_media_enum_fmt, + .vidioc_enum_fmt_vid_overlay =3D virtio_media_enum_fmt, + .vidioc_enum_fmt_vid_out =3D virtio_media_enum_fmt, + .vidioc_enum_fmt_sdr_cap =3D virtio_media_enum_fmt, + .vidioc_enum_fmt_sdr_out =3D virtio_media_enum_fmt, + .vidioc_enum_fmt_meta_cap =3D virtio_media_enum_fmt, + .vidioc_enum_fmt_meta_out =3D virtio_media_enum_fmt, + + /* VIDIOC_G_FMT handlers */ + .vidioc_g_fmt_vid_cap =3D virtio_media_g_fmt, + .vidioc_g_fmt_vid_overlay =3D virtio_media_g_fmt, + .vidioc_g_fmt_vid_out =3D virtio_media_g_fmt, + .vidioc_g_fmt_vid_out_overlay =3D virtio_media_g_fmt, + .vidioc_g_fmt_vbi_cap =3D virtio_media_g_fmt, + .vidioc_g_fmt_vbi_out =3D virtio_media_g_fmt, + .vidioc_g_fmt_sliced_vbi_cap =3D virtio_media_g_fmt, + .vidioc_g_fmt_sliced_vbi_out =3D virtio_media_g_fmt, + .vidioc_g_fmt_vid_cap_mplane =3D virtio_media_g_fmt, + .vidioc_g_fmt_vid_out_mplane =3D virtio_media_g_fmt, + .vidioc_g_fmt_sdr_cap =3D virtio_media_g_fmt, + .vidioc_g_fmt_sdr_out =3D virtio_media_g_fmt, + .vidioc_g_fmt_meta_cap =3D virtio_media_g_fmt, + .vidioc_g_fmt_meta_out =3D virtio_media_g_fmt, + + /* VIDIOC_S_FMT handlers */ + .vidioc_s_fmt_vid_cap =3D virtio_media_s_fmt, + .vidioc_s_fmt_vid_overlay =3D virtio_media_s_fmt, + .vidioc_s_fmt_vid_out =3D virtio_media_s_fmt, + .vidioc_s_fmt_vid_out_overlay =3D virtio_media_s_fmt, + .vidioc_s_fmt_vbi_cap =3D virtio_media_s_fmt, + .vidioc_s_fmt_vbi_out =3D virtio_media_s_fmt, + .vidioc_s_fmt_sliced_vbi_cap =3D virtio_media_s_fmt, + .vidioc_s_fmt_sliced_vbi_out =3D virtio_media_s_fmt, + .vidioc_s_fmt_vid_cap_mplane =3D virtio_media_s_fmt, + .vidioc_s_fmt_vid_out_mplane =3D virtio_media_s_fmt, + .vidioc_s_fmt_sdr_cap =3D virtio_media_s_fmt, + .vidioc_s_fmt_sdr_out =3D virtio_media_s_fmt, + .vidioc_s_fmt_meta_cap =3D virtio_media_s_fmt, + .vidioc_s_fmt_meta_out =3D virtio_media_s_fmt, + + /* VIDIOC_TRY_FMT handlers */ + .vidioc_try_fmt_vid_cap =3D virtio_media_try_fmt, + .vidioc_try_fmt_vid_overlay =3D virtio_media_try_fmt, + .vidioc_try_fmt_vid_out =3D virtio_media_try_fmt, + .vidioc_try_fmt_vid_out_overlay =3D virtio_media_try_fmt, + .vidioc_try_fmt_vbi_cap =3D virtio_media_try_fmt, + .vidioc_try_fmt_vbi_out =3D virtio_media_try_fmt, + .vidioc_try_fmt_sliced_vbi_cap =3D virtio_media_try_fmt, + .vidioc_try_fmt_sliced_vbi_out =3D virtio_media_try_fmt, + .vidioc_try_fmt_vid_cap_mplane =3D virtio_media_try_fmt, + .vidioc_try_fmt_vid_out_mplane =3D virtio_media_try_fmt, + .vidioc_try_fmt_sdr_cap =3D virtio_media_try_fmt, + .vidioc_try_fmt_sdr_out =3D virtio_media_try_fmt, + .vidioc_try_fmt_meta_cap =3D virtio_media_try_fmt, + .vidioc_try_fmt_meta_out =3D virtio_media_try_fmt, + + /* Buffer handlers */ + .vidioc_reqbufs =3D virtio_media_reqbufs, + .vidioc_querybuf =3D virtio_media_querybuf, + .vidioc_qbuf =3D virtio_media_qbuf, + .vidioc_expbuf =3D NULL, + .vidioc_dqbuf =3D virtio_media_dqbuf, + .vidioc_create_bufs =3D virtio_media_create_bufs, + .vidioc_prepare_buf =3D virtio_media_prepare_buf, + /* Overlay interface not supported yet */ + .vidioc_overlay =3D NULL, + /* Overlay interface not supported yet */ + .vidioc_g_fbuf =3D NULL, + /* Overlay interface not supported yet */ + .vidioc_s_fbuf =3D NULL, + + /* Stream on/off */ + .vidioc_streamon =3D virtio_media_streamon, + .vidioc_streamoff =3D virtio_media_streamoff, + + /* Standard handling */ + .vidioc_g_std =3D virtio_media_g_std, + .vidioc_s_std =3D virtio_media_s_std, + .vidioc_querystd =3D virtio_media_querystd, + + /* Input handling */ + .vidioc_enum_input =3D virtio_media_enuminput, + .vidioc_g_input =3D virtio_media_g_input, + .vidioc_s_input =3D virtio_media_s_input, + + /* Output handling */ + .vidioc_enum_output =3D virtio_media_enumoutput, + .vidioc_g_output =3D virtio_media_g_output, + .vidioc_s_output =3D virtio_media_s_output, + + /* Control handling */ + .vidioc_query_ext_ctrl =3D virtio_media_query_ext_ctrl, + .vidioc_g_ext_ctrls =3D virtio_media_g_ext_ctrls, + .vidioc_s_ext_ctrls =3D virtio_media_s_ext_ctrls, + .vidioc_try_ext_ctrls =3D virtio_media_try_ext_ctrls, + .vidioc_querymenu =3D virtio_media_querymenu, + + /* Audio ioctls */ + .vidioc_enumaudio =3D virtio_media_enumaudio, + .vidioc_g_audio =3D virtio_media_g_audio, + .vidioc_s_audio =3D virtio_media_s_audio, + + /* Audio out ioctls */ + .vidioc_enumaudout =3D virtio_media_enumaudout, + .vidioc_g_audout =3D virtio_media_g_audout, + .vidioc_s_audout =3D virtio_media_s_audout, + .vidioc_g_modulator =3D virtio_media_g_modulator, + .vidioc_s_modulator =3D virtio_media_s_modulator, + + /* Crop ioctls */ + /* + * Not directly an ioctl (part of VIDIOC_CROPCAP), so no need to + * implement. + */ + .vidioc_g_pixelaspect =3D NULL, + .vidioc_g_selection =3D virtio_media_g_selection, + .vidioc_s_selection =3D virtio_media_s_selection, + + /* Compression ioctls */ + /* Deprecated in V4L2. */ + .vidioc_g_jpegcomp =3D NULL, + /* Deprecated in V4L2. */ + .vidioc_s_jpegcomp =3D NULL, + .vidioc_g_enc_index =3D virtio_media_g_enc_index, + .vidioc_encoder_cmd =3D virtio_media_encoder_cmd, + .vidioc_try_encoder_cmd =3D virtio_media_try_encoder_cmd, + .vidioc_decoder_cmd =3D virtio_media_decoder_cmd, + .vidioc_try_decoder_cmd =3D virtio_media_try_decoder_cmd, + + /* Stream type-dependent parameter ioctls */ + .vidioc_g_parm =3D virtio_media_g_parm, + .vidioc_s_parm =3D virtio_media_s_parm, + + /* Tuner ioctls */ + .vidioc_g_tuner =3D virtio_media_g_tuner, + .vidioc_s_tuner =3D virtio_media_s_tuner, + .vidioc_g_frequency =3D virtio_media_g_frequency, + .vidioc_s_frequency =3D virtio_media_s_frequency, + .vidioc_enum_freq_bands =3D virtio_media_enum_freq_bands, + + /* Sliced VBI cap */ + .vidioc_g_sliced_vbi_cap =3D virtio_media_g_sliced_vbi_cap, + + /* Log status ioctl */ + /* Guest-only operation */ + .vidioc_log_status =3D NULL, + + .vidioc_s_hw_freq_seek =3D virtio_media_s_hw_freq_seek, + + .vidioc_enum_framesizes =3D virtio_media_enum_framesizes, + .vidioc_enum_frameintervals =3D virtio_media_enum_frameintervals, + + /* DV Timings IOCTLs */ + .vidioc_s_dv_timings =3D virtio_media_s_dv_timings, + .vidioc_g_dv_timings =3D virtio_media_g_dv_timings, + .vidioc_query_dv_timings =3D virtio_media_query_dv_timings, + .vidioc_enum_dv_timings =3D virtio_media_enum_dv_timings, + .vidioc_dv_timings_cap =3D virtio_media_dv_timings_cap, + .vidioc_g_edid =3D NULL, + .vidioc_s_edid =3D NULL, + + .vidioc_subscribe_event =3D virtio_media_subscribe_event, + .vidioc_unsubscribe_event =3D virtio_media_unsubscribe_event, + + /* For other private ioctls */ + .vidioc_default =3D NULL, +}; + +long virtio_media_device_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct video_device *video_dev =3D video_devdata(file); + struct virtio_media *vv =3D to_virtio_media(video_dev); + struct v4l2_fh *vfh =3D NULL; + struct v4l2_standard standard; + v4l2_std_id std_id =3D 0; + int ret; + + if (test_bit(V4L2_FL_USES_V4L2_FH, &video_dev->flags)) + vfh =3D file_to_v4l2_fh(file); + + mutex_lock(&vv->vlock); + + /* + * We need to handle a few ioctls manually because their results + * rely on vfd->tvnorms, which is normally updated by the driver + * as S_INPUT is called. Since we want to just pass these ioctls + * through, we have to hijack them from here. + */ + switch (cmd) { + case VIDIOC_S_STD: + ret =3D copy_from_user(&std_id, (void __user *)arg, + sizeof(std_id)); + if (ret) { + ret =3D -EINVAL; + break; + } + ret =3D virtio_media_s_std(file, vfh, std_id); + break; + case VIDIOC_ENUMSTD: + ret =3D copy_from_user(&standard, (void __user *)arg, + sizeof(standard)); + if (ret) { + ret =3D -EINVAL; + break; + } + ret =3D virtio_media_enumstd(file, vfh, &standard); + if (ret) + break; + ret =3D copy_to_user((void __user *)arg, &standard, + sizeof(standard)); + if (ret) + ret =3D -EINVAL; + break; + case VIDIOC_QUERYSTD: + ret =3D virtio_media_querystd(file, vfh, &std_id); + if (ret) + break; + ret =3D copy_to_user((void __user *)arg, &std_id, sizeof(std_id)); + if (ret) + ret =3D -EINVAL; + break; + default: + ret =3D video_ioctl2(file, cmd, arg); + break; + } + + mutex_unlock(&vv->vlock); + + return ret; +} --=20 2.55.0.229.g6434b31f56-goog From nobody Fri Jul 24 04:50:45 2026 Received: from mail-qt1-f197.google.com (mail-qt1-f197.google.com [209.85.160.197]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7046135DA40 for ; Thu, 23 Jul 2026 18:33:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.197 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831590; cv=none; b=XOkSOqG22bYB34wXD+E26lafAcQsXhKAsvumJuu0Q/F/Y/QMBQkyLAxGVzxaf6m3edhPhMncKtxSJLDrxkVPxZ8+iKotDiAIH5rlQ3G4zp9riwMqe/HxvPLgncs2CkVgB/MKEtHh1FirkpKdhd7JMZ95oEehtRuwbn6G6tHYlQ8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784831590; c=relaxed/simple; bh=vDZeQinpdFoDcNuImf1N/0AjNHp4brVkSFrAhi9TCPw=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=bH/UL2pFvgufusfQT0n5m3QoSdqw9qA17jY4h48awzecmBRy7PS+k/QQhJrzeK1o1FOtUnuqtJU36FkItTNtHX5b8d02/I7Eq/MtRTrUY6maMTAUJgut4I150a4pv7fzkp1byZ11+Lm7V3zvS+pOb7pwxp4jHzUYGKPDYwU33rc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=uAGn3DgF; arc=none smtp.client-ip=209.85.160.197 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--briandaniels.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="uAGn3DgF" Received: by mail-qt1-f197.google.com with SMTP id d75a77b69052e-51bfe3fa93bso19240851cf.2 for ; Thu, 23 Jul 2026 11:33:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784831581; x=1785436381; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=lqiWQikS8RfwgMFdKdTRhH3oYvJSP9R+oKdTrkEuptE=; b=uAGn3DgFLb/cZPH4Kw68dulrAsC1o4k1LWSIRqxncvOenAZMwHa96JSzOfqfTj5Amk umUNU4QDukMf3KVUDzbmIZnkTIFiPa92jWpALj5kIh+Rnze5buhCv/k89fmaTrrJ4tir N2SD+mM9YFHmklTUw9CvlCe3ZtDA6rLnvXPC9sOdz1DVYg7riybgauqCLCK/1w7sg3us J2WDleloZ5ewjWLDT2D+/iZU3BCbzE6HTdfJ6Vvnu3qsyPob1DcblWOk+x5nKwaJQjYS cU9t/fyCOMJEhrv1by00XpAN7/r061HMwHe9NBOK4aVXoz5Fyk6N093bU04BEO5ifWBT 2wYg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784831581; x=1785436381; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=lqiWQikS8RfwgMFdKdTRhH3oYvJSP9R+oKdTrkEuptE=; b=aH+qfNBwqxIFODjxworVa+nPOFmomWbuiyLFrG6pBD3vG6jRnMa5XbAV+JRelHLBHv yC2MhJTNE3VI5iPm6YdhDFhAhMZycUGPrRUVL2FZkvS7IiDCVOUsdJQRTAzYpLHCF9no 9ppPMlL48qIwwssnO9ekemYH5rgzQaeJaWdXXzDVKsqBvHimdZC0vUltFAFLcFLy76Tk Ki3WI5ZBOaMALZLjs7o74KZFd3SxhVDhLxLlVT/FyoxOIwbjYA6Gp6XPimJvEI/CK/aL rzwHO7PAfMjeA55VEVITmfZlVorPG2rMhpEFiMO6kXm2Iw4JYa5RxQeuudqKk/sdRryb i0uQ== X-Forwarded-Encrypted: i=1; AHgh+RqYKuU1CbnajXEJfaM5C3m8yySZps2Sf+Z+QTXV2IxpqJC54GcVqrI229V0F88Itt8V7OHf8uYDYo/zsbk=@vger.kernel.org X-Gm-Message-State: AOJu0YzraLoj5C1RURjLLOjgcvuGW/l6V4WS3ZKwl0FrHs8Nd/5h9soI kuwBEhxhacYp/7H5yRmWwtcSMPRUW1Gq2KrHgp/hKv0dNo7qywjTwioGwohEg4cpff6jVZcME8+ jwNQ4z84Twr/blCpXQAR6EuLb/iij X-Received: from qtbob13.prod.google.com ([2002:a05:622a:7d8d:b0:520:227f:57c8]) (user=briandaniels job=prod-delivery.src-stubby-dispatcher) by 2002:a05:622a:830f:b0:526:f9e1:9e50 with SMTP id d75a77b69052e-528459f1a4dmr20080541cf.47.1784831580814; Thu, 23 Jul 2026 11:33:00 -0700 (PDT) Date: Thu, 23 Jul 2026 14:32:19 -0400 In-Reply-To: <20260723183219.737296-1-briandaniels@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260723183219.737296-1-briandaniels@google.com> X-Mailer: git-send-email 2.55.0.229.g6434b31f56-goog Message-ID: <20260723183219.737296-6-briandaniels@google.com> Subject: [PATCH v5 5/5] media: virtio: Add USERPTR memory type support From: Brian Daniels To: Mauro Carvalho Chehab Cc: adelva@google.com, aesteve@redhat.com, changyeon@google.com, daniel.almeida@collabora.com, eperezma@redhat.com, gnurou@gmail.com, gurchetansingh@google.com, hverkuil@xs4all.nl, jasowang@redhat.com, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org, mst@redhat.com, nicolas.dufresne@collabora.com, virtualization@lists.linux.dev, xuanzhuo@linux.alibaba.com, Brian Daniels Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alexandre Courbot This patch adds support for the USERPTR memory type to the virtio-media driver. It adds the allow_userptr module parameter, implements the userptr mapping logic in the scatterlist builder, and enables USERPTR in reqbufs if allowed. Signed-off-by: Alexandre Courbot Assisted-by: Antigravity:gemini-3.5-flash Co-developed-by: Brian Daniels Signed-off-by: Brian Daniels --- drivers/media/virtio/scatterlist_builder.c | 64 ++++++++++++++++++++++ drivers/media/virtio/scatterlist_builder.h | 3 + drivers/media/virtio/virtio_media_driver.c | 42 +++++++++----- drivers/media/virtio/virtio_media_ioctls.c | 11 +++- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/drivers/media/virtio/scatterlist_builder.c b/drivers/media/vir= tio/scatterlist_builder.c index 97925b277..85c6a36b4 100644 --- a/drivers/media/virtio/scatterlist_builder.c +++ b/drivers/media/virtio/scatterlist_builder.c @@ -349,14 +349,30 @@ static int scatterlist_builder_add_userptr(struct sca= tterlist_builder *builder, int scatterlist_builder_add_buffer(struct scatterlist_builder *builder, struct v4l2_buffer *b) { + int i; int ret; =20 + /* Fixup: plane length must be zero if userptr is NULL */ + if (!V4L2_TYPE_IS_MULTIPLANAR(b->type) && + b->memory =3D=3D V4L2_MEMORY_USERPTR && b->m.userptr =3D=3D 0) + b->length =3D 0; + /* v4l2_buffer */ ret =3D scatterlist_builder_add_data(builder, b, sizeof(*b)); if (ret) return ret; =20 if (V4L2_TYPE_IS_MULTIPLANAR(b->type) && b->length > 0) { + /* Fixup: plane length must be zero if userptr is NULL */ + if (b->memory =3D=3D V4L2_MEMORY_USERPTR) { + for (i =3D 0; i < b->length; i++) { + struct v4l2_plane *plane =3D &b->m.planes[i]; + + if (plane->m.userptr =3D=3D 0) + plane->length =3D 0; + } + } + /* Array of v4l2_planes */ ret =3D scatterlist_builder_add_data(builder, b->m.planes, sizeof(struct v4l2_plane) * @@ -368,6 +384,54 @@ int scatterlist_builder_add_buffer(struct scatterlist_= builder *builder, return 0; } =20 +/** + * scatterlist_builder_add_buffer_userptr() - Add the payload of a ``USERP= TR`` + * &struct v4l2_buffer to the + * descriptor chain. + * @builder: builder to use. + * @b: &struct v4l2_buffer whose ``USERPTR`` payload we want to add. + * + * Add an array of &struct 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 scatterlist_builder_add_buffer() because we only w= ant + * to add these to the device-readable part of the descriptor chain. + */ +int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *bui= lder, + struct v4l2_buffer *b) +{ + int i; + int ret; + + if (b->memory !=3D V4L2_MEMORY_USERPTR) + return 0; + + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) { + for (i =3D 0; i < b->length; i++) { + struct v4l2_plane *plane =3D &b->m.planes[i]; + + if (b->memory =3D=3D V4L2_MEMORY_USERPTR && + plane->length > 0) { + unsigned long uptr =3D plane->m.userptr; + unsigned long len =3D plane->length; + + ret =3D + scatterlist_builder_add_userptr(builder, + uptr, + len); + if (ret) + return ret; + } + } + } else if (b->length > 0) { + ret =3D scatterlist_builder_add_userptr(builder, b->m.userptr, + b->length); + if (ret) + return ret; + } + + return 0; +} + /** * scatterlist_builder_retrieve_buffer() - Retrieve a &struct v4l2_buffer * written by the device on the sh= adow diff --git a/drivers/media/virtio/scatterlist_builder.h b/drivers/media/vir= tio/scatterlist_builder.h index 47bfd7ae0..53d964a48 100644 --- a/drivers/media/virtio/scatterlist_builder.h +++ b/drivers/media/virtio/scatterlist_builder.h @@ -90,6 +90,9 @@ int scatterlist_builder_add_ioctl_resp(struct scatterlist= _builder *builder, int scatterlist_builder_add_buffer(struct scatterlist_builder *builder, struct v4l2_buffer *buffer); =20 +int scatterlist_builder_add_buffer_userptr(struct scatterlist_builder *bui= lder, + struct v4l2_buffer *b); + int scatterlist_builder_retrieve_buffer(struct scatterlist_builder *builde= r, size_t sg_index, struct v4l2_buffer *buffer, diff --git a/drivers/media/virtio/virtio_media_driver.c b/drivers/media/vir= tio/virtio_media_driver.c index c431c3eb2..b6f79593d 100644 --- a/drivers/media/virtio/virtio_media_driver.c +++ b/drivers/media/virtio/virtio_media_driver.c @@ -7,26 +7,29 @@ */ =20 #include +#include #include #include +#include #include +#include #include +#include +#include +#include +#include #include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include =20 +#include #include -#include -#include #include +#include +#include +#include =20 #include "uapi/linux/virtio_media.h" #include "session.h" @@ -40,6 +43,15 @@ /* Bit mask for the VIRTIO_MEDIA_MMAP_FLAG_RW flag */ #define VIRTIO_MEDIA_MMAP_FLAG_RW_MASK BIT(VIRTIO_MEDIA_MMAP_FLAG_RW) =20 +/* + * Whether USERPTR buffers are allowed. + * + * This is disabled by default as USERPTR buffers are dangerous, but the o= ption + * 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. @@ -849,15 +861,11 @@ static int virtio_media_probe(struct virtio_device *v= irtio_dev) VIRTIO_MEDIA_SHM_MMAP); =20 vd =3D &vv->video_dev; + vd->v4l2_dev =3D &vv->v4l2_dev; vd->vfl_type =3D VFL_TYPE_VIDEO; vd->ioctl_ops =3D &virtio_media_ioctl_ops; vd->fops =3D &virtio_media_fops; - vd->release =3D video_device_release_empty; - strscpy(vd->name, "virtio-media", sizeof(vd->name)); - - video_set_drvdata(vd, vv); - vd->device_caps =3D virtio_cread32(virtio_dev, 0); if (vd->device_caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE)) vd->vfl_dir =3D VFL_DIR_M2M; @@ -866,6 +874,10 @@ static int virtio_media_probe(struct virtio_device *vi= rtio_dev) vd->vfl_dir =3D VFL_DIR_TX; else vd->vfl_dir =3D VFL_DIR_RX; + vd->release =3D video_device_release_empty; + strscpy(vd->name, "virtio-media", sizeof(vd->name)); + + video_set_drvdata(vd, vv); =20 ret =3D video_register_device(vd, virtio_cread32(virtio_dev, 4), 0); if (ret) @@ -890,6 +902,7 @@ static int virtio_media_probe(struct virtio_device *vir= tio_dev) virtio_dev->config->del_vqs(virtio_dev); err_find_vqs: v4l2_device_unregister(&vv->v4l2_dev); + return ret; } =20 @@ -900,6 +913,7 @@ static void virtio_media_remove(struct virtio_device *v= irtio_dev) =20 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); diff --git a/drivers/media/virtio/virtio_media_ioctls.c b/drivers/media/vir= tio/virtio_media_ioctls.c index f0b82b5ec..88465f239 100644 --- a/drivers/media/virtio/virtio_media_ioctls.c +++ b/drivers/media/virtio/virtio_media_ioctls.c @@ -273,6 +273,12 @@ static int virtio_media_send_buffer_ioctl(struct v4l2_= fh *fh, u32 ioctl, return ret; =20 end_buf_sg =3D builder.cur_sg; + + /* Payload of SHARED_PAGES buffers, if relevant */ + ret =3D scatterlist_builder_add_buffer_userptr(&builder, b); + if (ret < 0) + return ret; + num_cmd_sgs =3D builder.cur_sg; =20 /* Response descriptor */ @@ -719,7 +725,7 @@ static int virtio_media_reqbufs(struct file *file, void= *fh, if (b->type > VIRTIO_MEDIA_LAST_QUEUE) return -EINVAL; =20 - if (b->memory =3D=3D V4L2_MEMORY_USERPTR) + if (b->memory =3D=3D V4L2_MEMORY_USERPTR && !virtio_media_allow_userptr) return -EINVAL; =20 ret =3D virtio_media_send_wr_ioctl(vfh, VIDIOC_REQBUFS, b, sizeof(*b), @@ -752,7 +758,8 @@ static int virtio_media_reqbufs(struct file *file, void= *fh, if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) session->uses_mplane =3D true; =20 - b->capabilities &=3D ~V4L2_BUF_CAP_SUPPORTS_USERPTR; + if (!virtio_media_allow_userptr) + b->capabilities &=3D ~V4L2_BUF_CAP_SUPPORTS_USERPTR; =20 /* We do not support DMABUF yet. */ b->capabilities &=3D ~V4L2_BUF_CAP_SUPPORTS_DMABUF; --=20 2.55.0.229.g6434b31f56-goog