diff options
author | Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com> | 2021-11-22 12:38:06 +0100 |
---|---|---|
committer | Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com> | 2021-12-07 20:47:22 +0100 |
commit | 87b7cce6fb8a0afde96227423b3385d36e6fe0a1 (patch) | |
tree | 052311f8fd70ed9ed4efa0fc2f43bc2d5c452b85 | |
parent | 3c2b0cdba520c260a4342d9b968d9efb7892a643 (diff) |
virtualization: Add virtio-video driver as external module.
This driver should conform WIP spec v3 [1] with some updates for spec v4
[2], and, some unspecified features such as VIRTIO_VIDEO_DEVICE_CAMERA.
Imported from internal OpenSynergy's revision:
bcc33b6b9e0156b381a70c54d2df02c57b63d270
Kernel was configured with necessary features for this driver:
enable MEDIA_SUPPORT
disable MEDIA_SUBDRV_AUTOSELECT
enable MEDIA_PLATFORM_SUPPORT
enable VIDEO_VIRTIO
Keep driver as an external module to simplify future updates.
[1]: https://lists.oasis-open.org/archives/virtio-dev/202002/msg00002.html
[2]: https://lists.oasis-open.org/archives/virtio-dev/202006/msg00072.html
Bug-AGL: SPEC-4148
Change-Id: Iea339194b22443f67b3e2ffddca84118357a2f15
Signed-off-by: Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>
16 files changed, 5847 insertions, 0 deletions
diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/Kbuild b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/Kbuild new file mode 100644 index 00000000..f16a686b --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/Kbuild @@ -0,0 +1,22 @@ +# +# Kbuild for the virtio-video driver +# + +# --tags to take into account non-annotated tags +# --dirty to mark version with uncommitted changes as dirty +GIT_VERSION = $(shell git -C "$(MODULE_GIT_REPOSITORY_DIR)" describe --tags --dirty | sed 's/^v//') + +ccflags-y := -I"$(src)/include/uapi" +ccflags-y += -DDRIVER_VERSION=\"$(GIT_VERSION)\" + +virtio_video-y := \ + virtio_video_driver.o \ + virtio_video_vq.o \ + virtio_video_device.o \ + virtio_video_dec.o \ + virtio_video_enc.o \ + virtio_video_cam.o \ + virtio_video_caps.o \ + virtio_video_helpers.o + +obj-m += virtio_video.o diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/Makefile b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/Makefile new file mode 100644 index 00000000..e5a34b76 --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/Makefile @@ -0,0 +1,33 @@ +# +# Makefile for mod coqos +# +KDIR ?= $(KERNEL_SRC) +ifeq ($(KDIR),) +$(error "KDIR must be specified.") +endif + +# The variable "M" is used to point to the location of this module, and it is +# passed to kbuild to build this module. +# +# Allow to specify variable "M" from outside. It is needed to set it to the +# relative path to this module. It must be relative to the kernel source +# directory. +# +# If kernel is built with "O" option then setting "M" to the relative path +# empowers the build system to put output/object files (.o, .ko.) into a +# directory different from the module source directory. +M ?= $$PWD + +# Some build systems may rsync module sources out of git repository to have +# sources directory untouched during build. Then, they should specify Git +# repository directory separately for 'git describe' to work properly. +MODULE_GIT_REPOSITORY_DIR ?= $(M) + +default: + $(MAKE) -C $(KDIR) M=$(M) MODULE_GIT_REPOSITORY_DIR=$(MODULE_GIT_REPOSITORY_DIR) + +modules_install: + $(MAKE) -C $(KDIR) M=$(M) $@ + +clean: + $(MAKE) -C $(KDIR) M=$(M) $@ diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/include/uapi/linux/virtio_video.h b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/include/uapi/linux/virtio_video.h new file mode 100644 index 00000000..12792129 --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/include/uapi/linux/virtio_video.h @@ -0,0 +1,493 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Virtio Video Device + * + * This header is BSD licensed so anyone can use the definitions + * to implement compatible drivers/servers: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of IBM nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Copyright (C) 2020 OpenSynergy GmbH. + */ + +#ifndef _UAPI_LINUX_VIRTIO_VIDEO_H +#define _UAPI_LINUX_VIRTIO_VIDEO_H + +#include <linux/types.h> +#include <linux/virtio_config.h> + +enum virtio_video_device_type { + VIRTIO_VIDEO_DEVICE_ENCODER = 0x0100, + VIRTIO_VIDEO_DEVICE_DECODER, + VIRTIO_VIDEO_DEVICE_CAMERA, +}; + +/* + * Feature bits + */ + +/* Guest pages can be used for video buffers. */ +#define VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES 0 +/* + * The host can process buffers even if they are non-contiguous memory such as + * scatter-gather lists. + */ +#define VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG 1 + +#define VIRTIO_VIDEO_MAX_PLANES 8 + +/* + * Image formats + */ + +enum virtio_video_format { + /* Raw formats */ + VIRTIO_VIDEO_FORMAT_RAW_MIN = 1, + VIRTIO_VIDEO_FORMAT_ARGB8888 = VIRTIO_VIDEO_FORMAT_RAW_MIN, + VIRTIO_VIDEO_FORMAT_BGRA8888, + VIRTIO_VIDEO_FORMAT_RGBA8888, + VIRTIO_VIDEO_FORMAT_NV12, /* 12 Y/CbCr 4:2:0 */ + VIRTIO_VIDEO_FORMAT_YUV420, /* 12 YUV 4:2:0 */ + VIRTIO_VIDEO_FORMAT_YVU420, /* 12 YVU 4:2:0 */ + VIRTIO_VIDEO_FORMAT_YUV422, /* 16 YUV 4:2:2 */ + VIRTIO_VIDEO_FORMAT_RAW_MAX = VIRTIO_VIDEO_FORMAT_YUV422, + + /* Coded formats */ + VIRTIO_VIDEO_FORMAT_CODED_MIN = 0x1000, + VIRTIO_VIDEO_FORMAT_MPEG2 = + VIRTIO_VIDEO_FORMAT_CODED_MIN, /* MPEG-2 Part 2 */ + VIRTIO_VIDEO_FORMAT_MPEG4, /* MPEG-4 Part 2 */ + VIRTIO_VIDEO_FORMAT_H264, /* H.264 */ + VIRTIO_VIDEO_FORMAT_HEVC, /* HEVC aka H.265*/ + VIRTIO_VIDEO_FORMAT_VP8, /* VP8 */ + VIRTIO_VIDEO_FORMAT_VP9, /* VP9 */ + VIRTIO_VIDEO_FORMAT_CODED_MAX = VIRTIO_VIDEO_FORMAT_VP9, +}; + +enum virtio_video_profile { + /* H.264 */ + VIRTIO_VIDEO_PROFILE_H264_MIN = 0x100, + VIRTIO_VIDEO_PROFILE_H264_BASELINE = VIRTIO_VIDEO_PROFILE_H264_MIN, + VIRTIO_VIDEO_PROFILE_H264_MAIN, + VIRTIO_VIDEO_PROFILE_H264_EXTENDED, + VIRTIO_VIDEO_PROFILE_H264_HIGH, + VIRTIO_VIDEO_PROFILE_H264_HIGH10PROFILE, + VIRTIO_VIDEO_PROFILE_H264_HIGH422PROFILE, + VIRTIO_VIDEO_PROFILE_H264_HIGH444PREDICTIVEPROFILE, + VIRTIO_VIDEO_PROFILE_H264_SCALABLEBASELINE, + VIRTIO_VIDEO_PROFILE_H264_SCALABLEHIGH, + VIRTIO_VIDEO_PROFILE_H264_STEREOHIGH, + VIRTIO_VIDEO_PROFILE_H264_MULTIVIEWHIGH, + VIRTIO_VIDEO_PROFILE_H264_MAX = VIRTIO_VIDEO_PROFILE_H264_MULTIVIEWHIGH, + + /* HEVC */ + VIRTIO_VIDEO_PROFILE_HEVC_MIN = 0x200, + VIRTIO_VIDEO_PROFILE_HEVC_MAIN = VIRTIO_VIDEO_PROFILE_HEVC_MIN, + VIRTIO_VIDEO_PROFILE_HEVC_MAIN10, + VIRTIO_VIDEO_PROFILE_HEVC_MAIN_STILL_PICTURE, + VIRTIO_VIDEO_PROFILE_HEVC_MAX = + VIRTIO_VIDEO_PROFILE_HEVC_MAIN_STILL_PICTURE, + + /* VP8 */ + VIRTIO_VIDEO_PROFILE_VP8_MIN = 0x300, + VIRTIO_VIDEO_PROFILE_VP8_PROFILE0 = VIRTIO_VIDEO_PROFILE_VP8_MIN, + VIRTIO_VIDEO_PROFILE_VP8_PROFILE1, + VIRTIO_VIDEO_PROFILE_VP8_PROFILE2, + VIRTIO_VIDEO_PROFILE_VP8_PROFILE3, + VIRTIO_VIDEO_PROFILE_VP8_MAX = VIRTIO_VIDEO_PROFILE_VP8_PROFILE3, + + /* VP9 */ + VIRTIO_VIDEO_PROFILE_VP9_MIN = 0x400, + VIRTIO_VIDEO_PROFILE_VP9_PROFILE0 = VIRTIO_VIDEO_PROFILE_VP9_MIN, + VIRTIO_VIDEO_PROFILE_VP9_PROFILE1, + VIRTIO_VIDEO_PROFILE_VP9_PROFILE2, + VIRTIO_VIDEO_PROFILE_VP9_PROFILE3, + VIRTIO_VIDEO_PROFILE_VP9_MAX = VIRTIO_VIDEO_PROFILE_VP9_PROFILE3, +}; + +enum virtio_video_level { + /* H.264 */ + VIRTIO_VIDEO_LEVEL_H264_MIN = 0x100, + VIRTIO_VIDEO_LEVEL_H264_1_0 = VIRTIO_VIDEO_LEVEL_H264_MIN, + VIRTIO_VIDEO_LEVEL_H264_1_1, + VIRTIO_VIDEO_LEVEL_H264_1_2, + VIRTIO_VIDEO_LEVEL_H264_1_3, + VIRTIO_VIDEO_LEVEL_H264_2_0, + VIRTIO_VIDEO_LEVEL_H264_2_1, + VIRTIO_VIDEO_LEVEL_H264_2_2, + VIRTIO_VIDEO_LEVEL_H264_3_0, + VIRTIO_VIDEO_LEVEL_H264_3_1, + VIRTIO_VIDEO_LEVEL_H264_3_2, + VIRTIO_VIDEO_LEVEL_H264_4_0, + VIRTIO_VIDEO_LEVEL_H264_4_1, + VIRTIO_VIDEO_LEVEL_H264_4_2, + VIRTIO_VIDEO_LEVEL_H264_5_0, + VIRTIO_VIDEO_LEVEL_H264_5_1, + VIRTIO_VIDEO_LEVEL_H264_MAX = VIRTIO_VIDEO_LEVEL_H264_5_1, +}; + +/* + * Config + */ + +struct virtio_video_config { + __le32 version; + __le32 max_caps_length; + __le32 max_resp_length; +}; + +/* + * Commands + */ + +enum virtio_video_cmd_type { + /* Command */ + VIRTIO_VIDEO_CMD_QUERY_CAPABILITY = 0x0100, + VIRTIO_VIDEO_CMD_STREAM_CREATE, + VIRTIO_VIDEO_CMD_STREAM_DESTROY, + VIRTIO_VIDEO_CMD_STREAM_DRAIN, + VIRTIO_VIDEO_CMD_RESOURCE_ATTACH, + VIRTIO_VIDEO_CMD_RESOURCE_QUEUE, + VIRTIO_VIDEO_CMD_QUEUE_DETACH_RESOURCES, + VIRTIO_VIDEO_CMD_QUEUE_CLEAR, + VIRTIO_VIDEO_CMD_GET_PARAMS, + VIRTIO_VIDEO_CMD_SET_PARAMS, + VIRTIO_VIDEO_CMD_QUERY_CONTROL, + VIRTIO_VIDEO_CMD_GET_CONTROL, + VIRTIO_VIDEO_CMD_SET_CONTROL, + + /* Response */ + VIRTIO_VIDEO_RESP_OK_NODATA = 0x0200, + VIRTIO_VIDEO_RESP_OK_QUERY_CAPABILITY, + VIRTIO_VIDEO_RESP_OK_RESOURCE_QUEUE, + VIRTIO_VIDEO_RESP_OK_GET_PARAMS, + VIRTIO_VIDEO_RESP_OK_QUERY_CONTROL, + VIRTIO_VIDEO_RESP_OK_GET_CONTROL, + + VIRTIO_VIDEO_RESP_ERR_INVALID_OPERATION = 0x0300, + VIRTIO_VIDEO_RESP_ERR_OUT_OF_MEMORY, + VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID, + VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID, + VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER, + VIRTIO_VIDEO_RESP_ERR_UNSUPPORTED_CONTROL, +}; + +struct virtio_video_cmd_hdr { + __le32 type; /* One of enum virtio_video_cmd_type */ + __le32 stream_id; +}; + +/* VIRTIO_VIDEO_CMD_QUERY_CAPABILITY */ +enum virtio_video_queue_type { + VIRTIO_VIDEO_QUEUE_TYPE_INPUT = 0x100, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT, +}; + +struct virtio_video_query_capability { + struct virtio_video_cmd_hdr hdr; + __le32 queue_type; /* One of VIRTIO_VIDEO_QUEUE_TYPE_* types */ + __u8 padding[4]; +}; + +enum virtio_video_planes_layout_flag { + VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER = 1 << 0, + VIRTIO_VIDEO_PLANES_LAYOUT_PER_PLANE = 1 << 1, +}; + +struct virtio_video_format_range { + __le32 min; + __le32 max; + __le32 step; + __u8 padding[4]; +}; + +struct virtio_video_format_frame { + struct virtio_video_format_range width; + struct virtio_video_format_range height; + __le32 num_rates; + __u8 padding[4]; + /* Followed by struct virtio_video_format_range frame_rates[] */ +}; + +struct virtio_video_format_desc { + __le64 mask; + __le32 format; /* One of VIRTIO_VIDEO_FORMAT_* types */ + __le32 planes_layout; /* Bitmask with VIRTIO_VIDEO_PLANES_LAYOUT_* */ + __le32 plane_align; + __le32 num_frames; + /* Followed by struct virtio_video_format_frame frames[] */ +}; + +struct virtio_video_query_capability_resp { + struct virtio_video_cmd_hdr hdr; + __le32 num_descs; + __u8 padding[4]; + /* Followed by struct virtio_video_format_desc descs[] */ +}; + +/* VIRTIO_VIDEO_CMD_STREAM_CREATE */ +enum virtio_video_mem_type { + VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES, +}; + +struct virtio_video_stream_create { + struct virtio_video_cmd_hdr hdr; + __le32 in_mem_type; /* One of VIRTIO_VIDEO_MEM_TYPE_* types */ + __le32 out_mem_type; /* One of VIRTIO_VIDEO_MEM_TYPE_* types */ + __le32 coded_format; /* One of VIRTIO_VIDEO_FORMAT_* types */ + __u8 padding[4]; + __u8 tag[64]; +}; + +/* VIRTIO_VIDEO_CMD_STREAM_DESTROY */ +struct virtio_video_stream_destroy { + struct virtio_video_cmd_hdr hdr; +}; + +/* VIRTIO_VIDEO_CMD_STREAM_DRAIN */ +struct virtio_video_stream_drain { + struct virtio_video_cmd_hdr hdr; +}; + +/* VIRTIO_VIDEO_CMD_RESOURCE_ATTACH */ +struct virtio_video_resource_object { + __u8 uuid [16]; +}; + +struct virtio_video_resource_sg_entry { + __le64 addr; + __le32 length; + __u8 padding[4]; +}; + +struct virtio_video_resource_sg_list { + __le32 num_entries; + __u8 padding[4]; + struct virtio_video_resource_sg_entry entries[]; +}; +#define VIRTIO_VIDEO_RESOURCE_SG_SIZE(n) \ + offsetof(struct virtio_video_resource_sg_list, entries[n]) + +union virtio_video_resource { + struct virtio_video_resource_sg_list sg_list; + struct virtio_video_resource_object object; +}; + +struct virtio_video_resource_attach { + __le32 cmd_type; + __le32 stream_id; + __le32 queue_type; /* VIRTIO_VIDEO_QUEUE_TYPE_* */ + __le32 resource_id; + /* Followed by struct virtio_video_resource resources[] */ +}; + +/* VIRTIO_VIDEO_CMD_RESOURCE_QUEUE */ +struct virtio_video_resource_queue { + __le32 cmd_type; + __le32 stream_id; + __le32 queue_type; /* VIRTIO_VIDEO_QUEUE_TYPE_* */ + __le32 resource_id; + __le32 flags; /* Bitmask with VIRTIO_VIDEO_ENQUEUE_FLAG_ * */ + __u8 padding[4]; + __le64 timestamp; + __le32 data_sizes[VIRTIO_VIDEO_MAX_PLANES]; +}; + +enum virtio_video_dequeue_flag { + VIRTIO_VIDEO_DEQUEUE_FLAG_ERR = 0, + VIRTIO_VIDEO_DEQUEUE_FLAG_EOS, + + /* Encoder only */ + VIRTIO_VIDEO_DEQUEUE_FLAG_KEY_FRAME, + VIRTIO_VIDEO_DEQUEUE_FLAG_PFRAME, + VIRTIO_VIDEO_DEQUEUE_FLAG_BFRAME, +}; + +struct virtio_video_resource_queue_resp { + struct virtio_video_cmd_hdr hdr; + __le32 flags; + __le64 timestamp; + __le32 data_sizes[VIRTIO_VIDEO_MAX_PLANES]; +}; + +/* VIRTIO_VIDEO_CMD_QUEUE_DETACH_RESOURCES */ +struct virtio_video_queue_detach_resources { + __le32 cmd_type; + __le32 stream_id; + __le32 queue_type; /* One of VIRTIO_VIDEO_QUEUE_TYPE_* types */ + __u8 padding[4]; +}; + +/* VIRTIO_VIDEO_CMD_QUEUE_CLEAR */ +struct virtio_video_queue_clear { + struct virtio_video_cmd_hdr hdr; + __le32 queue_type; /* One of VIRTIO_VIDEO_QUEUE_TYPE_* types */ + __u8 padding[4]; +}; + +/* VIRTIO_VIDEO_CMD_GET_PARAMS */ +struct virtio_video_plane_format { + __le32 plane_size; + __le32 stride; +}; + +struct virtio_video_crop { + __le32 left; + __le32 top; + __le32 width; + __le32 height; +}; + +struct virtio_video_params { + __le32 queue_type; /* One of VIRTIO_VIDEO_QUEUE_TYPE_* types */ + __le32 format; /* One of VIRTIO_VIDEO_FORMAT_* types */ + __le32 frame_width; + __le32 frame_height; + __le32 min_buffers; + __le32 max_buffers; + struct virtio_video_crop crop; + __le32 frame_rate; + __le32 num_planes; + struct virtio_video_plane_format plane_formats[VIRTIO_VIDEO_MAX_PLANES]; +}; + +struct virtio_video_get_params { + struct virtio_video_cmd_hdr hdr; + __le32 queue_type; /* One of VIRTIO_VIDEO_QUEUE_TYPE_* types */ + __u8 padding[4]; +}; + +struct virtio_video_get_params_resp { + struct virtio_video_cmd_hdr hdr; + struct virtio_video_params params; +}; + +/* VIRTIO_VIDEO_CMD_SET_PARAMS */ +struct virtio_video_set_params { + struct virtio_video_cmd_hdr hdr; + struct virtio_video_params params; +}; + +/* VIRTIO_VIDEO_CMD_QUERY_CONTROL */ +enum virtio_video_control_type { + VIRTIO_VIDEO_CONTROL_BITRATE = 1, + VIRTIO_VIDEO_CONTROL_PROFILE, + VIRTIO_VIDEO_CONTROL_LEVEL, +}; + +struct virtio_video_query_control_profile { + __le32 format; /* One of VIRTIO_VIDEO_FORMAT_* */ + __u8 padding[4]; +}; + +struct virtio_video_query_control_level { + __le32 format; /* One of VIRTIO_VIDEO_FORMAT_* */ + __u8 padding[4]; +}; + +struct virtio_video_query_control { + struct virtio_video_cmd_hdr hdr; + __le32 control; /* One of VIRTIO_VIDEO_CONTROL_* types */ + __u8 padding[4]; + /* + * Followed by a value of struct virtio_video_query_control_* + * in accordance with the value of control. + */ +}; + +struct virtio_video_query_control_resp_profile { + __le32 num; + __u8 padding[4]; + /* Followed by an array le32 profiles[] */ +}; + +struct virtio_video_query_control_resp_level { + __le32 num; + __u8 padding[4]; + /* Followed by an array le32 level[] */ +}; + +struct virtio_video_query_control_resp { + struct virtio_video_cmd_hdr hdr; + /* Followed by one of struct virtio_video_query_control_resp_* */ +}; + +/* VIRTIO_VIDEO_CMD_GET_CONTROL */ +struct virtio_video_get_control { + struct virtio_video_cmd_hdr hdr; + __le32 control; /* One of VIRTIO_VIDEO_CONTROL_* types */ + __u8 padding[4]; +}; + +struct virtio_video_control_val_bitrate { + __le32 bitrate; + __u8 padding[4]; +}; + +struct virtio_video_control_val_profile { + __le32 profile; + __u8 padding[4]; +}; + +struct virtio_video_control_val_level { + __le32 level; + __u8 padding[4]; +}; + +struct virtio_video_get_control_resp { + struct virtio_video_cmd_hdr hdr; + /* Followed by one of struct virtio_video_control_val_* */ +}; + +/* VIRTIO_VIDEO_CMD_SET_CONTROL */ +struct virtio_video_set_control { + struct virtio_video_cmd_hdr hdr; + __le32 control; /* One of VIRTIO_VIDEO_CONTROL_* types */ + __u8 padding[4]; + /* Followed by one of struct virtio_video_control_val_* */ +}; + +struct virtio_video_set_control_resp { + struct virtio_video_cmd_hdr hdr; +}; + +/* + * Events + */ + +enum virtio_video_event_type { + /* For all devices */ + VIRTIO_VIDEO_EVENT_ERROR = 0x0100, + + /* For decoder only */ + VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED = 0x0200, +}; + +struct virtio_video_event { + __le32 event_type; /* One of VIRTIO_VIDEO_EVENT_* types */ + __le32 stream_id; +}; + +#endif /* _UAPI_LINUX_VIRTIO_VIDEO_H */ diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video.h b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video.h new file mode 100644 index 00000000..9ece57db --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video.h @@ -0,0 +1,446 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Common header for virtio video driver. + * + * Copyright 2020 OpenSynergy GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _VIRTIO_VIDEO_H +#define _VIRTIO_VIDEO_H + +#include <linux/virtio.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_config.h> +#include <linux/virtio_video.h> +#include <linux/list.h> +#include <linux/completion.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-ctrls.h> +#include <media/videobuf2-dma-sg.h> +#include <media/videobuf2-dma-contig.h> + +#define DRIVER_NAME "virtio-video" + +#define VIRTIO_ID_VIDEO_DEC 31 +#define VIRTIO_ID_VIDEO_ENC 30 +#define VIRTIO_ID_VIDEO_CAM 100 + +#define MIN_BUFS_MIN 0 +#define MIN_BUFS_MAX VIDEO_MAX_FRAME +#define MIN_BUFS_STEP 1 +#define MIN_BUFS_DEF 1 + +struct video_format_frame { + struct virtio_video_format_frame frame; + struct virtio_video_format_range *frame_rates; +}; + +struct video_format { + struct list_head formats_list_entry; + struct virtio_video_format_desc desc; + struct video_format_frame *frames; +}; + +struct video_control_fmt_data { + uint32_t min; + uint32_t max; + uint32_t num; + uint32_t skip_mask; + uint32_t *entries; +}; + +struct video_control_format { + struct list_head controls_list_entry; + uint32_t format; + struct video_control_fmt_data *profile; + struct video_control_fmt_data *level; +}; + +struct video_plane_format { + uint32_t plane_size; + uint32_t stride; +}; + +struct video_format_info { + uint32_t fourcc_format; + uint32_t frame_rate; + uint32_t frame_width; + uint32_t frame_height; + uint32_t min_buffers; + uint32_t max_buffers; + struct virtio_video_crop crop; + uint32_t num_planes; + struct video_plane_format plane_format[VIRTIO_VIDEO_MAX_PLANES]; +}; + +struct video_control_info { + uint32_t profile; + uint32_t level; + uint32_t bitrate; +}; + +struct virtio_video_device; +struct virtio_video_vbuffer; + +typedef void (*virtio_video_resp_cb)(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf); + +struct virtio_video_vbuffer { + char *buf; + int size; + uint32_t id; + + void *data_buf; + uint32_t data_size; + + char *resp_buf; + int resp_size; + + void *priv; + virtio_video_resp_cb resp_cb; + + bool is_sync; + struct completion reclaimed; + + struct list_head pending_list_entry; +}; + +struct virtio_video_cmd_queue { + struct virtqueue *vq; + bool ready; + spinlock_t qlock; + wait_queue_head_t reclaim_queue; +}; + +struct virtio_video_event_queue { + struct virtqueue *vq; + bool ready; + struct work_struct work; +}; + +enum video_stream_state { + STREAM_STATE_IDLE = 0, + STREAM_STATE_INIT, + STREAM_STATE_DYNAMIC_RES_CHANGE, /* specific to decoder */ + STREAM_STATE_RUNNING, + STREAM_STATE_DRAIN, + STREAM_STATE_STOPPED, + STREAM_STATE_RESET, /* specific to encoder */ + STREAM_STATE_ERROR, +}; + +struct virtio_video_stream { + uint32_t stream_id; + atomic_t state; + struct video_device *video_dev; + struct v4l2_fh fh; + struct mutex vq_mutex; + struct v4l2_ctrl_handler ctrl_handler; + struct video_format_info in_info; + struct video_format_info out_info; + struct video_control_info control; + struct video_format_frame *current_frame; +}; + +struct virtio_video_device { + struct virtio_device *vdev; + struct virtio_video_cmd_queue commandq; + struct virtio_video_event_queue eventq; + wait_queue_head_t wq; + + struct kmem_cache *vbufs; + struct virtio_video_event *evts; + + struct idr resource_idr; + spinlock_t resource_idr_lock; + struct idr stream_idr; + spinlock_t stream_idr_lock; + + uint32_t max_caps_len; + uint32_t max_resp_len; + + bool has_iommu; + bool supp_non_contig; + + int debug; + int use_dma_mem; + + struct v4l2_device v4l2_dev; + struct video_device video_dev; + struct mutex video_dev_mutex; + + bool is_m2m_dev; + struct v4l2_m2m_dev *m2m_dev; + + /* non-m2m queue (camera) */ + struct vb2_queue vb2_output_queue; + struct list_head pending_buf_list; + spinlock_t pending_buf_list_lock; + + uint32_t vbufs_sent; + struct list_head pending_vbuf_list; + + /* device_busy - to block multiple opens for non-m2m (camera) */ + bool device_busy; + + /* vid_dev_nr - try register starting at video device number */ + int vid_dev_nr; + + /* is_mplane_cam - camera has multiplanar capabilities (default true) */ + bool is_mplane_cam; + + /* VIRTIO_VIDEO_FUNC_ */ + uint32_t type; + + uint32_t num_input_fmts; + struct list_head input_fmt_list; + + uint32_t num_output_fmts; + struct list_head output_fmt_list; + + struct list_head controls_fmt_list; + struct virtio_video_device_ops *ops; +}; + +struct virtio_video_device_ops { + int (*init_ctrls)(struct virtio_video_stream *stream); + int (*init_queues)(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); + void* (*get_fmt_list)(struct virtio_video_device *vvd); +}; + +struct virtio_video_buffer { + struct v4l2_m2m_buffer v4l2_m2m_vb; + uint32_t resource_id; + bool queued; + struct list_head list; +}; + +static inline gfp_t +virtio_video_gfp_flags(struct virtio_video_device *vvd) +{ + if (vvd->use_dma_mem) + return GFP_DMA; + else + return 0; +} + +static inline const struct vb2_mem_ops * +virtio_video_mem_ops(struct virtio_video_device *vvd) +{ + if (vvd->supp_non_contig) + return &vb2_dma_sg_memops; + else + return &vb2_dma_contig_memops; +} + +static inline struct virtio_video_device * +to_virtio_vd(struct video_device *video_dev) +{ + return container_of(video_dev, struct virtio_video_device, + video_dev); +} + +static inline struct virtio_video_stream *file2stream(struct file *file) +{ + return container_of(file->private_data, struct virtio_video_stream, fh); +} + +static inline struct virtio_video_stream *ctrl2stream(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct virtio_video_stream, + ctrl_handler); +} + +static inline struct virtio_video_buffer *to_virtio_vb(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_vb = to_vb2_v4l2_buffer(vb); + + return container_of(v4l2_vb, struct virtio_video_buffer, + v4l2_m2m_vb.vb); +} + +static inline enum virtio_video_queue_type +to_virtio_queue_type(enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return VIRTIO_VIDEO_QUEUE_TYPE_INPUT; + else + return VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT; +} + +static inline bool within_range(uint32_t min, uint32_t val, uint32_t max) +{ + return ((min <= val) && (val <= max)); +} + +static inline bool needs_alignment(uint32_t val, uint32_t a) +{ + if (a == 0 || IS_ALIGNED(val, a)) + return false; + + return true; +} + +enum video_stream_state virtio_video_state(struct virtio_video_stream *stream); +void virtio_video_state_reset(struct virtio_video_stream *stream); +void virtio_video_state_update(struct virtio_video_stream *stream, + enum video_stream_state new_state); + +int virtio_video_alloc_vbufs(struct virtio_video_device *vvd); +void virtio_video_free_vbufs(struct virtio_video_device *vvd); +int virtio_video_alloc_events(struct virtio_video_device *vvd); + +int virtio_video_device_init(struct virtio_video_device *vvd); +void virtio_video_device_deinit(struct virtio_video_device *vvd); + +int virtio_video_dec_init(struct virtio_video_device *vvd); +int virtio_video_enc_init(struct virtio_video_device *vvd); +int virtio_video_cam_init(struct virtio_video_device *vvd); + +void virtio_video_stream_id_get(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + uint32_t *id); +void virtio_video_stream_id_put(struct virtio_video_device *vvd, uint32_t id); +void virtio_video_resource_id_get(struct virtio_video_device *vvd, + uint32_t *id); +void virtio_video_resource_id_put(struct virtio_video_device *vvd, uint32_t id); + +int virtio_video_cmd_stream_create(struct virtio_video_device *vvd, + uint32_t stream_id, + enum virtio_video_format format, + const char *tag); +int virtio_video_cmd_stream_destroy(struct virtio_video_device *vvd, + uint32_t stream_id); +int virtio_video_cmd_stream_drain(struct virtio_video_device *vvd, + uint32_t stream_id); +int virtio_video_cmd_resource_attach(struct virtio_video_device *vvd, + uint32_t stream_id, uint32_t resource_id, + enum virtio_video_queue_type queue_type, + void *buf, size_t buf_size); +int virtio_video_cmd_resource_queue(struct virtio_video_device *vvd, + uint32_t stream_id, + struct virtio_video_buffer *virtio_vb, + uint32_t data_size[], uint8_t num_data_size, + enum virtio_video_queue_type queue_type); +int virtio_video_cmd_queue_detach_resources(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum virtio_video_queue_type queue_type); +int virtio_video_cmd_queue_clear(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum virtio_video_queue_type queue_type); +int virtio_video_cmd_query_capability(struct virtio_video_device *vvd, + void *resp_buf, size_t resp_size, + enum virtio_video_queue_type queue_type); +int virtio_video_query_control_profile(struct virtio_video_device *vvd, + void *resp_buf, size_t resp_size, + enum virtio_video_format format); +int virtio_video_query_control_level(struct virtio_video_device *vvd, + void *resp_buf, size_t resp_size, + enum virtio_video_format format); +int virtio_video_cmd_set_params(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + struct video_format_info *format_info, + enum virtio_video_queue_type queue_type); +int virtio_video_cmd_get_params(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum virtio_video_queue_type queue_type); +int virtio_video_cmd_set_control(struct virtio_video_device *vvd, + uint32_t stream_id, + enum virtio_video_control_type control, + uint32_t value); +int virtio_video_cmd_get_control(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum virtio_video_control_type control); + +void virtio_video_queue_res_chg_event(struct virtio_video_stream *stream); +void virtio_video_queue_eos_event(struct virtio_video_stream *stream); +void virtio_video_handle_error(struct virtio_video_stream *stream); +int virtio_video_queue_release_buffers(struct virtio_video_stream *stream, + enum virtio_video_queue_type queue_type); + +void virtio_video_cmd_cb(struct virtqueue *vq); +void virtio_video_event_cb(struct virtqueue *vq); +void virtio_video_process_events(struct work_struct *work); + +void virtio_video_buf_done(struct virtio_video_buffer *virtio_vb, + uint32_t flags, uint64_t timestamp, + uint32_t data_sizes[]); +int virtio_video_buf_plane_init(uint32_t idx,uint32_t resource_id, + struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + struct vb2_buffer *vb); +int virtio_video_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]); +int virtio_video_buf_init(struct vb2_buffer *vb); +void virtio_video_buf_cleanup(struct vb2_buffer *vb); +void virtio_video_buf_queue(struct vb2_buffer *vb); +int virtio_video_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf); +int virtio_video_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf); +int virtio_video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap); +int virtio_video_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *f); +int virtio_video_enum_framemintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *f); +int virtio_video_g_fmt(struct file *file, void *fh, struct v4l2_format *f); +int virtio_video_s_fmt(struct file *file, void *fh, struct v4l2_format *f); +int virtio_video_try_fmt(struct virtio_video_stream *stream, + struct v4l2_format *f); +int virtio_video_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb); +int virtio_video_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub); + +void virtio_video_free_caps_list(struct list_head *caps_list); +int virtio_video_parse_virtio_capabilities(struct virtio_video_device *vvd, + void *input_buf, void *output_buf); +void virtio_video_clean_capability(struct virtio_video_device *vvd); +int virtio_video_parse_virtio_control(struct virtio_video_device *vvd); +void virtio_video_clean_control(struct virtio_video_device *vvd); + +uint32_t virtio_video_format_to_v4l2(uint32_t format); +uint32_t virtio_video_control_to_v4l2(uint32_t control); +uint32_t virtio_video_profile_to_v4l2(uint32_t profile); +uint32_t virtio_video_level_to_v4l2(uint32_t level); +uint32_t virtio_video_v4l2_format_to_virtio(uint32_t v4l2_format); +uint32_t virtio_video_v4l2_control_to_virtio(uint32_t v4l2_control); +uint32_t virtio_video_v4l2_profile_to_virtio(uint32_t v4l2_profile); +uint32_t virtio_video_v4l2_level_to_virtio(uint32_t v4l2_level); + +struct video_format *virtio_video_find_video_format(struct list_head *fmts_list, + uint32_t fourcc); +void virtio_video_format_from_info(struct video_format_info *info, + struct v4l2_pix_format_mplane *pix_mp); +void virtio_video_format_fill_default_info(struct video_format_info *dst_info, + struct video_format_info *src_info); +void virtio_video_pix_fmt_sp2mp(const struct v4l2_pix_format *pix, + struct v4l2_pix_format_mplane *pix_mp); +void virtio_video_pix_fmt_mp2sp(const struct v4l2_pix_format_mplane *pix_mp, + struct v4l2_pix_format *pix); + +int virtio_video_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel); + +int virtio_video_stream_get_params(struct virtio_video_device *vvd, + struct virtio_video_stream *stream); +int virtio_video_stream_get_controls(struct virtio_video_device *vvd, + struct virtio_video_stream *stream); + +#endif /* _VIRTIO_VIDEO_H */ diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_cam.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_cam.c new file mode 100644 index 00000000..d657ba8a --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_cam.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Capture for virtio video device. + * + * Copyright 2021 OpenSynergy GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/version.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> + +#include "virtio_video.h" + +static int virtio_video_cam_start_streaming(struct vb2_queue *vq, + unsigned int count) +{ + struct virtio_video_stream *stream = vb2_get_drv_priv(vq); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (virtio_video_state(stream) >= STREAM_STATE_INIT) + virtio_video_state_update(stream, STREAM_STATE_RUNNING); + + return 0; +} + +static void virtio_video_cam_stop_streaming(struct vb2_queue *vq) +{ + struct virtio_video_stream *stream = vb2_get_drv_priv(vq); + + virtio_video_queue_release_buffers(stream, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); + + vb2_wait_for_all_buffers(vq); +} + +static const struct vb2_ops virtio_video_cam_qops = { + .queue_setup = virtio_video_queue_setup, + .buf_init = virtio_video_buf_init, + .buf_cleanup = virtio_video_buf_cleanup, + .buf_queue = virtio_video_buf_queue, + .start_streaming = virtio_video_cam_start_streaming, + .stop_streaming = virtio_video_cam_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int virtio_video_cam_g_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + struct virtio_video_stream *stream = ctrl2stream(ctrl); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + if (virtio_video_state(stream) >= + STREAM_STATE_DYNAMIC_RES_CHANGE) + ctrl->val = stream->out_info.min_buffers; + else + ctrl->val = 0; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops virtio_video_cam_ctrl_ops = { + .g_volatile_ctrl = virtio_video_cam_g_ctrl, +}; + +int virtio_video_cam_init_ctrls(struct virtio_video_stream *stream) +{ + struct v4l2_ctrl *ctrl; + + v4l2_ctrl_handler_init(&stream->ctrl_handler, 2); + + ctrl = v4l2_ctrl_new_std(&stream->ctrl_handler, + &virtio_video_cam_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, + MIN_BUFS_MIN, MIN_BUFS_MAX, MIN_BUFS_STEP, + MIN_BUFS_DEF); + + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + if (stream->ctrl_handler.error) + return stream->ctrl_handler.error; + + v4l2_ctrl_handler_setup(&stream->ctrl_handler); + + return 0; +} + +int virtio_video_cam_init_queues(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct virtio_video_stream *stream = priv; + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct device *dev = vvd->v4l2_dev.dev; + int vq_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + if (!vvd->is_mplane_cam) + vq_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + dst_vq->type = vq_type; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = stream; + dst_vq->buf_struct_size = sizeof(struct virtio_video_buffer); + dst_vq->ops = &virtio_video_cam_qops; + dst_vq->mem_ops = virtio_video_mem_ops(vvd); + dst_vq->min_buffers_needed = stream->out_info.min_buffers; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + dst_vq->lock = &stream->vq_mutex; + dst_vq->gfp_flags = virtio_video_gfp_flags(vvd); + dst_vq->dev = dev; + + return vb2_queue_init(dst_vq); +} + +int virtio_video_cam_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct v4l2_format *fmt_try = f; + struct v4l2_format fmt_mp = { 0 }; + int ret; + + if (!vvd->is_mplane_cam) { + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + fmt_mp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + fmt_try = &fmt_mp; + + virtio_video_pix_fmt_sp2mp(&f->fmt.pix, &fmt_try->fmt.pix_mp); + } + + ret = virtio_video_try_fmt(stream, fmt_try); + if (ret) + return ret; + + if (!vvd->is_mplane_cam) { + if (fmt_try->fmt.pix_mp.num_planes != 1) + return -EINVAL; + + virtio_video_pix_fmt_mp2sp(&fmt_try->fmt.pix_mp, &f->fmt.pix); + } + + return 0; +} + +static int virtio_video_cam_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format *fmt; + int idx = 0; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + if (f->index >= vvd->num_output_fmts) + return -EINVAL; + + list_for_each_entry(fmt, &vvd->output_fmt_list, formats_list_entry) { + if (f->index == idx) { + f->pixelformat = fmt->desc.format; + return 0; + } + idx++; + } + return -EINVAL; +} + +static int virtio_video_cam_g_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct v4l2_format fmt_mp = { 0 }; + struct v4l2_format *fmt_get = f; + int ret; + + if (!vvd->is_mplane_cam) { + fmt_mp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + fmt_get = &fmt_mp; + } + + ret = virtio_video_g_fmt(file, fh, fmt_get); + if (ret) + return ret; + + if (virtio_video_state(stream) == STREAM_STATE_IDLE) + virtio_video_state_update(stream, STREAM_STATE_INIT); + + if (!vvd->is_mplane_cam) { + if (fmt_get->fmt.pix_mp.num_planes != 1) + return -EINVAL; + + virtio_video_pix_fmt_mp2sp(&fmt_get->fmt.pix_mp, &f->fmt.pix); + } + + return 0; +} + +static int virtio_video_cam_s_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + int ret; + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct v4l2_format fmt_mp = { 0 }; + struct v4l2_format *fmt_set = f; + + if (!vvd->is_mplane_cam) { + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + fmt_mp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + fmt_set = &fmt_mp; + + virtio_video_pix_fmt_sp2mp(&f->fmt.pix, &fmt_set->fmt.pix_mp); + } + + ret = virtio_video_s_fmt(file, fh, fmt_set); + if (ret) + return ret; + + if (virtio_video_state(stream) == STREAM_STATE_IDLE) + virtio_video_state_update(stream, STREAM_STATE_INIT); + + if (!vvd->is_mplane_cam) { + if (fmt_set->fmt.pix_mp.num_planes != 1) + return -EINVAL; + + virtio_video_pix_fmt_mp2sp(&fmt_set->fmt.pix_mp, &f->fmt.pix); + } + + return 0; +} + +static int virtio_video_cam_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + int ret; + + if (V4L2_TYPE_IS_OUTPUT(sel->type)) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + stream->out_info.crop.top = sel->r.top; + stream->out_info.crop.left = sel->r.left; + stream->out_info.crop.width = sel->r.width; + stream->out_info.crop.height = sel->r.height; + v4l2_info(&vvd->v4l2_dev, + "Set : top:%d, left:%d, w:%d, h:%d\n", + sel->r.top, sel->r.left, sel->r.width, sel->r.height); + break; + default: + return -EINVAL; + } + + ret = virtio_video_cmd_set_params(vvd, stream, &stream->out_info, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); + if (ret) + return -EINVAL; + + ret = virtio_video_cmd_get_params(vvd, stream, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); + return ret; +} + +static const struct v4l2_ioctl_ops virtio_video_cam_ioctl_ops = { + .vidioc_querycap = virtio_video_querycap, + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0)) + .vidioc_enum_fmt_vid_cap_mplane = virtio_video_cam_enum_fmt_vid_cap, +#endif + .vidioc_try_fmt_vid_cap_mplane = virtio_video_cam_try_fmt, + .vidioc_g_fmt_vid_cap_mplane = virtio_video_cam_g_fmt, + .vidioc_s_fmt_vid_cap_mplane = virtio_video_cam_s_fmt, + + .vidioc_enum_fmt_vid_cap = virtio_video_cam_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = virtio_video_cam_try_fmt, + .vidioc_g_fmt_vid_cap = virtio_video_cam_g_fmt, + .vidioc_s_fmt_vid_cap = virtio_video_cam_s_fmt, + + .vidioc_g_selection = virtio_video_g_selection, + .vidioc_s_selection = virtio_video_cam_s_selection, + + .vidioc_enum_frameintervals = virtio_video_enum_framemintervals, + .vidioc_enum_framesizes = virtio_video_enum_framesizes, + + .vidioc_reqbufs = virtio_video_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = virtio_video_qbuf, + .vidioc_dqbuf = virtio_video_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_expbuf = vb2_ioctl_expbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_subscribe_event = virtio_video_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void *virtio_video_cam_get_fmt_list(struct virtio_video_device *vvd) +{ + return &vvd->output_fmt_list; +} + +static struct virtio_video_device_ops virtio_video_cam_ops = { + .init_ctrls = virtio_video_cam_init_ctrls, + .init_queues = virtio_video_cam_init_queues, + .get_fmt_list = virtio_video_cam_get_fmt_list, +}; + +int virtio_video_cam_init(struct virtio_video_device *vvd) +{ + ssize_t num; + struct video_device *vd = &vvd->video_dev; + + vd->ioctl_ops = &virtio_video_cam_ioctl_ops; + vvd->ops = &virtio_video_cam_ops; + + num = strscpy(vd->name, "camera", sizeof(vd->name)); + if (num < 0) + return num; + + return 0; +} diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c new file mode 100644 index 00000000..fc815b18 --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Driver for virtio video device. + * + * Copyright 2020 OpenSynergy GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-sg.h> + +#include "virtio_video.h" + +static void virtio_video_free_frame_rates(struct video_format_frame *frame) +{ + kfree(frame->frame_rates); +} + +static void virtio_video_free_frames(struct video_format *fmt) +{ + size_t idx = 0; + + for (idx = 0; idx < fmt->desc.num_frames; idx++) + virtio_video_free_frame_rates(&fmt->frames[idx]); + kfree(fmt->frames); +} + +static void virtio_video_free_fmt(struct list_head *fmts_list) +{ + struct video_format *fmt, *tmp; + + list_for_each_entry_safe(fmt, tmp, fmts_list, formats_list_entry) { + list_del(&fmt->formats_list_entry); + virtio_video_free_frames(fmt); + kfree(fmt); + } +} + +static void virtio_video_free_fmts(struct virtio_video_device *vvd) +{ + virtio_video_free_fmt(&vvd->input_fmt_list); + virtio_video_free_fmt(&vvd->output_fmt_list); +} + +static void virtio_video_copy_fmt_range(struct virtio_video_format_range *d_rge, + struct virtio_video_format_range *s_rge) +{ + d_rge->min = le32_to_cpu(s_rge->min); + d_rge->max = le32_to_cpu(s_rge->max); + d_rge->step = le32_to_cpu(s_rge->step); +} + +static size_t +virtio_video_parse_virtio_frame_rate(struct virtio_video_device *vvd, + struct virtio_video_format_range *f_rate, + void *buf) +{ + struct virtio_video_format_range *virtio_frame_rate; + + virtio_frame_rate = buf; + virtio_video_copy_fmt_range(f_rate, virtio_frame_rate); + + return sizeof(struct virtio_video_format_range); +} + +static size_t virtio_video_parse_virtio_frame(struct virtio_video_device *vvd, + struct video_format_frame *frm, + void *buf) +{ + struct virtio_video_format_frame *virtio_frame; + struct virtio_video_format_frame *frame = &frm->frame; + struct virtio_video_format_range *rate; + size_t idx, offset, extra_size; + + virtio_frame = buf; + + virtio_video_copy_fmt_range(&frame->width, &virtio_frame->width); + virtio_video_copy_fmt_range(&frame->height, &virtio_frame->height); + + frame->num_rates = le32_to_cpu(virtio_frame->num_rates); + frm->frame_rates = kcalloc(frame->num_rates, + sizeof(struct virtio_video_format_range), + GFP_KERNEL); + + offset = sizeof(struct virtio_video_format_frame); + for (idx = 0; idx < frame->num_rates; idx++) { + rate = &frm->frame_rates[idx]; + extra_size = + virtio_video_parse_virtio_frame_rate(vvd, rate, + buf + offset); + if (extra_size == 0) { + kfree(frm->frame_rates); + v4l2_err(&vvd->v4l2_dev, + "failed to parse frame rate\n"); + return 0; + } + offset += extra_size; + } + + return offset; +} + +static size_t virtio_video_parse_virtio_fmt(struct virtio_video_device *vvd, + struct video_format *fmt, void *buf) +{ + struct virtio_video_format_desc *virtio_fmt_desc; + struct virtio_video_format_desc *fmt_desc; + struct video_format_frame *frame; + size_t idx, offset, extra_size; + + virtio_fmt_desc = buf; + fmt_desc = &fmt->desc; + + fmt_desc->format = + virtio_video_format_to_v4l2 + (le32_to_cpu(virtio_fmt_desc->format)); + fmt_desc->mask = le64_to_cpu(virtio_fmt_desc->mask); + fmt_desc->planes_layout = le32_to_cpu(virtio_fmt_desc->planes_layout); + + fmt_desc->num_frames = le32_to_cpu(virtio_fmt_desc->num_frames); + fmt->frames = kcalloc(fmt_desc->num_frames, + sizeof(struct video_format_frame), + GFP_KERNEL); + + offset = sizeof(struct virtio_video_format_desc); + for (idx = 0; idx < fmt_desc->num_frames; idx++) { + frame = &fmt->frames[idx]; + extra_size = + virtio_video_parse_virtio_frame(vvd, frame, + buf + offset); + if (extra_size == 0) { + kfree(fmt->frames); + v4l2_err(&vvd->v4l2_dev, "failed to parse frame\n"); + return 0; + } + offset += extra_size; + } + + return offset; +} + +int virtio_video_parse_virtio_capability(struct virtio_video_device *vvd, + void *resp_buf, + struct list_head *ret_fmt_list, + uint32_t *ret_num_fmts) +{ + struct virtio_video_query_capability_resp *resp = resp_buf; + struct video_format *fmt; + uint32_t fmt_count; + int fmt_idx; + size_t offset; + int ret; + + if (!resp || ret_fmt_list == NULL || ret_num_fmts == NULL) { + v4l2_err(&vvd->v4l2_dev, "invalid arguments!\n"); + return -EINVAL; + } + + if (le32_to_cpu(resp->num_descs) <= 0) { + v4l2_err(&vvd->v4l2_dev, "invalid capability response\n"); + return -EINVAL; + } + + fmt_count = le32_to_cpu(resp->num_descs); + offset = sizeof(struct virtio_video_query_capability_resp); + + for (fmt_idx = 0; fmt_idx < fmt_count; fmt_idx++) { + size_t fmt_size = 0; + + fmt = kzalloc(sizeof(*fmt), GFP_KERNEL); + if (!fmt) { + ret = -ENOMEM; + goto alloc_err; + } + + fmt_size = virtio_video_parse_virtio_fmt(vvd, fmt, + resp_buf + offset); + if (fmt_size == 0) { + v4l2_err(&vvd->v4l2_dev, "failed to parse fmt\n"); + ret = -ENOENT; + goto parse_fmt_err; + } + offset += fmt_size; + list_add(&fmt->formats_list_entry, ret_fmt_list); + } + + *ret_num_fmts = fmt_count; + return 0; + +parse_fmt_err: + kfree(fmt); +alloc_err: + virtio_video_free_fmts(vvd); + return ret; +} + +int virtio_video_parse_virtio_capabilities(struct virtio_video_device *vvd, + void *input_buf, void *output_buf) +{ + int ret; + + if (input_buf) { + ret = virtio_video_parse_virtio_capability(vvd, input_buf, + &vvd->input_fmt_list, + &vvd->num_input_fmts); + if (ret) { + v4l2_err(&vvd->v4l2_dev, + "Failed to parse input capability: %d\n", + ret); + return ret; + } + } + + if (output_buf) { + ret = virtio_video_parse_virtio_capability(vvd, output_buf, + &vvd->output_fmt_list, + &vvd->num_output_fmts); + if (ret) { + v4l2_err(&vvd->v4l2_dev, + "Failed to parse output capability: %d\n", + ret); + return ret; + } + } + + return 0; +} + +void virtio_video_clean_capability(struct virtio_video_device *vvd) +{ + virtio_video_free_fmts(vvd); +} + +static void +virtio_video_free_control_fmt_data(struct video_control_fmt_data *data) +{ + kfree(data->entries); + kfree(data); +} + +static void virtio_video_free_control_formats(struct virtio_video_device *vvd) +{ + struct video_control_format *c_fmt, *tmp; + + list_for_each_entry_safe(c_fmt, tmp, &vvd->controls_fmt_list, + controls_list_entry) { + list_del(&c_fmt->controls_list_entry); + virtio_video_free_control_fmt_data(c_fmt->profile); + virtio_video_free_control_fmt_data(c_fmt->level); + kfree(c_fmt); + } +} + +static int virtio_video_parse_control_levels(struct virtio_video_device *vvd, + struct video_control_format *fmt) +{ + int idx, ret; + struct virtio_video_query_control_resp *resp_buf; + struct virtio_video_query_control_resp_level *l_resp_buf; + struct video_control_fmt_data *level; + enum virtio_video_format virtio_format; + uint32_t *virtio_levels; + uint32_t num_levels, mask = 0; + int max = 0, min = UINT_MAX; + size_t resp_size; + + resp_size = vvd->max_resp_len; + + virtio_format = virtio_video_v4l2_format_to_virtio(fmt->format); + + resp_buf = kzalloc(resp_size, GFP_KERNEL); + if (IS_ERR(resp_buf)) { + ret = PTR_ERR(resp_buf); + goto lvl_err; + } + + ret = virtio_video_query_control_level(vvd, resp_buf, resp_size, + virtio_format); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to query level\n"); + goto lvl_err; + } + + l_resp_buf = (void *)((char *)resp_buf + sizeof(*resp_buf)); + num_levels = le32_to_cpu(l_resp_buf->num); + if (num_levels == 0) + goto lvl_err; + + fmt->level = kzalloc(sizeof(*level), GFP_KERNEL); + if (!fmt->level) { + ret = -ENOMEM; + goto lvl_err; + } + + level = fmt->level; + level->entries = kcalloc(num_levels, sizeof(uint32_t), GFP_KERNEL); + if (!level->entries) { + kfree(fmt->level); + ret = -ENOMEM; + goto lvl_err; + } + + virtio_levels = (void *)((char *)l_resp_buf + sizeof(*l_resp_buf)); + + for (idx = 0; idx < num_levels; idx++) { + level->entries[idx] = + virtio_video_level_to_v4l2 + (le32_to_cpu(virtio_levels[idx])); + + mask = mask | (1 << level->entries[idx]); + if (level->entries[idx] > max) + max = level->entries[idx]; + if (level->entries[idx] < min) + min = level->entries[idx]; + } + level->min = min; + level->max = max; + level->num = num_levels; + level->skip_mask = ~mask; + +lvl_err: + kfree(resp_buf); + + return ret; +} + +static int virtio_video_parse_control_profiles(struct virtio_video_device *vvd, + struct video_control_format *fmt) +{ + int idx, ret; + struct virtio_video_query_control_resp *resp_buf; + struct virtio_video_query_control_resp_profile *p_resp_buf; + struct video_control_fmt_data *profile; + uint32_t virtio_format, num_profiles, mask = 0; + uint32_t *virtio_profiles; + int max = 0, min = UINT_MAX; + size_t resp_size; + + resp_size = vvd->max_resp_len; + virtio_format = virtio_video_v4l2_format_to_virtio(fmt->format); + resp_buf = kzalloc(resp_size, GFP_KERNEL); + if (IS_ERR(resp_buf)) { + ret = PTR_ERR(resp_buf); + goto prf_err; + } + + ret = virtio_video_query_control_profile(vvd, resp_buf, resp_size, + virtio_format); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to query profile\n"); + goto prf_err; + } + + p_resp_buf = (void *)((char *)resp_buf + sizeof(*resp_buf)); + num_profiles = le32_to_cpu(p_resp_buf->num); + if (num_profiles == 0) + goto prf_err; + + fmt->profile = kzalloc(sizeof(*profile), GFP_KERNEL); + if (!fmt->profile) { + ret = -ENOMEM; + goto prf_err; + } + + profile = fmt->profile; + profile->entries = kcalloc(num_profiles, sizeof(uint32_t), GFP_KERNEL); + if (!profile->entries) { + kfree(fmt->profile); + ret = -ENOMEM; + goto prf_err; + } + + virtio_profiles = (void *)((char *)p_resp_buf + sizeof(*p_resp_buf)); + + for (idx = 0; idx < num_profiles; idx++) { + profile->entries[idx] = + virtio_video_profile_to_v4l2 + (le32_to_cpu(virtio_profiles[idx])); + + mask = mask | (1 << profile->entries[idx]); + if (profile->entries[idx] > max) + max = profile->entries[idx]; + if (profile->entries[idx] < min) + min = profile->entries[idx]; + } + profile->min = min; + profile->max = max; + profile->num = num_profiles; + profile->skip_mask = ~mask; + +prf_err: + kfree(resp_buf); + + return ret; +} + +int virtio_video_parse_virtio_control(struct virtio_video_device *vvd) +{ + struct video_format *fmt; + struct video_control_format *c_fmt; + uint32_t virtio_format; + int ret; + + list_for_each_entry(fmt, &vvd->output_fmt_list, formats_list_entry) { + virtio_format = + virtio_video_v4l2_format_to_virtio(fmt->desc.format); + if (virtio_format < VIRTIO_VIDEO_FORMAT_CODED_MIN || + virtio_format > VIRTIO_VIDEO_FORMAT_CODED_MAX) + continue; + + c_fmt = kzalloc(sizeof(*c_fmt), GFP_KERNEL); + if (!c_fmt) { + ret = -ENOMEM; + goto parse_ctrl_alloc_err; + } + + c_fmt->format = fmt->desc.format; + + ret = virtio_video_parse_control_profiles(vvd, c_fmt); + if (ret) { + v4l2_err(&vvd->v4l2_dev, + "failed to parse control profile\n"); + goto parse_ctrl_prf_err; + } + + ret = virtio_video_parse_control_levels(vvd, c_fmt); + if (ret) { + v4l2_err(&vvd->v4l2_dev, + "failed to parse control level\n"); + goto parse_ctrl_lvl_err; + } + list_add(&c_fmt->controls_list_entry, &vvd->controls_fmt_list); + } + return 0; + +parse_ctrl_lvl_err: + virtio_video_free_control_fmt_data(c_fmt->profile); +parse_ctrl_prf_err: + kfree(c_fmt); +parse_ctrl_alloc_err: + virtio_video_free_control_formats(vvd); + + return ret; +} + +void virtio_video_clean_control(struct virtio_video_device *vvd) +{ + virtio_video_free_control_formats(vvd); +} diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_dec.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_dec.c new file mode 100644 index 00000000..301a9a16 --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_dec.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Decoder for virtio video device. + * + * Copyright 2020 OpenSynergy GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/version.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> + +#include "virtio_video.h" + +static int virtio_video_dec_start_streaming(struct vb2_queue *vq, + unsigned int count) +{ + struct virtio_video_stream *stream = vb2_get_drv_priv(vq); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (!V4L2_TYPE_IS_OUTPUT(vq->type) && + virtio_video_state(stream) >= STREAM_STATE_INIT) + virtio_video_state_update(stream, STREAM_STATE_RUNNING); + + return 0; +} + +static void virtio_video_dec_stop_streaming(struct vb2_queue *vq) +{ + int queue_type; + struct virtio_video_stream *stream = vb2_get_drv_priv(vq); + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + queue_type = VIRTIO_VIDEO_QUEUE_TYPE_INPUT; + else + queue_type = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT; + + virtio_video_queue_release_buffers(stream, queue_type); + vb2_wait_for_all_buffers(vq); +} + +static const struct vb2_ops virtio_video_dec_qops = { + .queue_setup = virtio_video_queue_setup, + .buf_init = virtio_video_buf_init, + .buf_cleanup = virtio_video_buf_cleanup, + .buf_queue = virtio_video_buf_queue, + .start_streaming = virtio_video_dec_start_streaming, + .stop_streaming = virtio_video_dec_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int virtio_video_dec_g_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + struct virtio_video_stream *stream = ctrl2stream(ctrl); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + if (virtio_video_state(stream) >= + STREAM_STATE_DYNAMIC_RES_CHANGE) + ctrl->val = stream->out_info.min_buffers; + else + ctrl->val = 0; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops virtio_video_dec_ctrl_ops = { + .g_volatile_ctrl = virtio_video_dec_g_ctrl, +}; + +int virtio_video_dec_init_ctrls(struct virtio_video_stream *stream) +{ + struct v4l2_ctrl *ctrl; + + v4l2_ctrl_handler_init(&stream->ctrl_handler, 2); + + ctrl = v4l2_ctrl_new_std(&stream->ctrl_handler, + &virtio_video_dec_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, + MIN_BUFS_MIN, MIN_BUFS_MAX, MIN_BUFS_STEP, + MIN_BUFS_DEF); + + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + if (stream->ctrl_handler.error) + return stream->ctrl_handler.error; + + (void)v4l2_ctrl_new_std(&stream->ctrl_handler, NULL, + V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, + MIN_BUFS_MIN, MIN_BUFS_MAX, MIN_BUFS_STEP, + stream->in_info.min_buffers); + + if (stream->ctrl_handler.error) + return stream->ctrl_handler.error; + + v4l2_ctrl_handler_setup(&stream->ctrl_handler); + + return 0; +} + +int virtio_video_dec_init_queues(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + int ret; + struct virtio_video_stream *stream = priv; + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct device *dev = vvd->v4l2_dev.dev; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = stream; + src_vq->buf_struct_size = sizeof(struct virtio_video_buffer); + src_vq->ops = &virtio_video_dec_qops; + src_vq->mem_ops = virtio_video_mem_ops(vvd); + src_vq->min_buffers_needed = stream->in_info.min_buffers; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &stream->vq_mutex; + src_vq->gfp_flags = virtio_video_gfp_flags(vvd); + src_vq->dev = dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = stream; + dst_vq->buf_struct_size = sizeof(struct virtio_video_buffer); + dst_vq->ops = &virtio_video_dec_qops; + dst_vq->mem_ops = virtio_video_mem_ops(vvd); + dst_vq->min_buffers_needed = stream->out_info.min_buffers; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &stream->vq_mutex; + dst_vq->gfp_flags = virtio_video_gfp_flags(vvd); + dst_vq->dev = dev; + + return vb2_queue_init(dst_vq); +} + +static int virtio_video_try_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *cmd) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = video_drvdata(file); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (virtio_video_state(stream) == STREAM_STATE_DRAIN) + return -EBUSY; + + switch (cmd->cmd) { + case V4L2_DEC_CMD_STOP: + case V4L2_DEC_CMD_START: + if (cmd->flags != 0) { + v4l2_err(&vvd->v4l2_dev, "flags=%u are not supported", + cmd->flags); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int virtio_video_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *cmd) +{ + int ret; + struct vb2_queue *src_vq, *dst_vq; + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = video_drvdata(file); + + ret = virtio_video_try_decoder_cmd(file, fh, cmd); + if (ret < 0) + return ret; + + dst_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + switch (cmd->cmd) { + case V4L2_DEC_CMD_START: + vb2_clear_last_buffer_dequeued(dst_vq); + break; + case V4L2_DEC_CMD_STOP: + src_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + if (!vb2_is_streaming(src_vq)) { + v4l2_dbg(1, vvd->debug, + &vvd->v4l2_dev, "output is not streaming\n"); + return 0; + } + + if (!vb2_is_streaming(dst_vq)) { + v4l2_dbg(1, vvd->debug, + &vvd->v4l2_dev, "capture is not streaming\n"); + return 0; + } + + ret = virtio_video_cmd_stream_drain(vvd, stream->stream_id); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to drain stream\n"); + return ret; + } + + virtio_video_state_update(stream, STREAM_STATE_DRAIN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int virtio_video_dec_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format_info *info; + struct video_format *fmt; + unsigned long input_mask = 0; + int idx = 0, bit_num = 0; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + if (f->index >= vvd->num_output_fmts) + return -EINVAL; + + info = &stream->in_info; + list_for_each_entry(fmt, &vvd->input_fmt_list, formats_list_entry) { + if (info->fourcc_format == fmt->desc.format) { + input_mask = fmt->desc.mask; + break; + } + } + + if (input_mask == 0) + return -EINVAL; + + list_for_each_entry(fmt, &vvd->output_fmt_list, formats_list_entry) { + if (test_bit(bit_num, &input_mask)) { + if (f->index == idx) { + f->pixelformat = fmt->desc.format; + return 0; + } + idx++; + } + bit_num++; + } + return -EINVAL; +} + + +int virtio_video_dec_enum_fmt_vid_out(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format *fmt; + int idx = 0; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + if (f->index >= vvd->num_input_fmts) + return -EINVAL; + + list_for_each_entry(fmt, &vvd->input_fmt_list, formats_list_entry) { + if (f->index == idx) { + f->pixelformat = fmt->desc.format; + return 0; + } + idx++; + } + return -EINVAL; +} + +static int virtio_video_dec_s_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + int ret; + struct virtio_video_stream *stream = file2stream(file); + + ret = virtio_video_s_fmt(file, fh, f); + if (ret) + return ret; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + if (virtio_video_state(stream) == STREAM_STATE_IDLE) + virtio_video_state_update(stream, STREAM_STATE_INIT); + } + + return 0; +} + +static int virtio_video_dec_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + int ret; + + if (V4L2_TYPE_IS_OUTPUT(sel->type)) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE: + stream->out_info.crop.top = sel->r.top; + stream->out_info.crop.left = sel->r.left; + stream->out_info.crop.width = sel->r.width; + stream->out_info.crop.height = sel->r.height; + break; + default: + return -EINVAL; + } + + ret = virtio_video_cmd_set_params(vvd, stream, &stream->out_info, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); + if (ret) + return -EINVAL; + + return virtio_video_cmd_get_params(vvd, stream, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); +} + +static const struct v4l2_ioctl_ops virtio_video_dec_ioctl_ops = { + .vidioc_querycap = virtio_video_querycap, + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)) + .vidioc_enum_fmt_vid_cap = virtio_video_dec_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = virtio_video_dec_enum_fmt_vid_out, +#else + .vidioc_enum_fmt_vid_cap_mplane = virtio_video_dec_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out_mplane = virtio_video_dec_enum_fmt_vid_out, +#endif + .vidioc_g_fmt_vid_cap_mplane = virtio_video_g_fmt, + .vidioc_s_fmt_vid_cap_mplane = virtio_video_dec_s_fmt, + + .vidioc_g_fmt_vid_out_mplane = virtio_video_g_fmt, + .vidioc_s_fmt_vid_out_mplane = virtio_video_dec_s_fmt, + + .vidioc_g_selection = virtio_video_g_selection, + .vidioc_s_selection = virtio_video_dec_s_selection, + + .vidioc_try_decoder_cmd = virtio_video_try_decoder_cmd, + .vidioc_decoder_cmd = virtio_video_decoder_cmd, + .vidioc_enum_frameintervals = virtio_video_enum_framemintervals, + .vidioc_enum_framesizes = virtio_video_enum_framesizes, + + .vidioc_reqbufs = virtio_video_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = virtio_video_qbuf, + .vidioc_dqbuf = virtio_video_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = virtio_video_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void *virtio_video_dec_get_fmt_list(struct virtio_video_device *vvd) +{ + return &vvd->input_fmt_list; +} + +static struct virtio_video_device_ops virtio_video_dec_ops = { + .init_ctrls = virtio_video_dec_init_ctrls, + .init_queues = virtio_video_dec_init_queues, + .get_fmt_list = virtio_video_dec_get_fmt_list, +}; + +int virtio_video_dec_init(struct virtio_video_device *vvd) +{ + ssize_t num; + struct video_device *vd = &vvd->video_dev; + + vd->ioctl_ops = &virtio_video_dec_ioctl_ops; + vvd->ops = &virtio_video_dec_ops; + + num = strscpy(vd->name, "stateful-decoder", sizeof(vd->name)); + if (num < 0) + return num; + + return 0; +} diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c new file mode 100644 index 00000000..2e1f90ae --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c @@ -0,0 +1,1282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Driver for virtio video device. + * + * Copyright 2020 OpenSynergy GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/version.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-sg.h> + +#include "virtio_video.h" + +enum video_stream_state virtio_video_state(struct virtio_video_stream *stream) +{ + return atomic_read(&stream->state); +} + +void virtio_video_state_reset(struct virtio_video_stream *stream) +{ + atomic_set(&stream->state, STREAM_STATE_IDLE); +} + +void virtio_video_state_update(struct virtio_video_stream *stream, + enum video_stream_state new_state) +{ + enum video_stream_state prev_state; + + do { + prev_state = atomic_read(&stream->state); + if (prev_state == STREAM_STATE_ERROR) + return; + } while (atomic_cmpxchg(&stream->state, prev_state, new_state) != + prev_state); +} + +int virtio_video_pending_buf_list_empty(struct virtio_video_device *vvd) +{ + int ret = 0; + + if (vvd->is_m2m_dev) { + v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n"); + return -EPERM; + } + + spin_lock(&vvd->pending_buf_list_lock); + if (list_empty(&vvd->pending_buf_list)) + ret = 1; + spin_unlock(&vvd->pending_buf_list_lock); + + return ret; +} + +int virtio_video_pending_buf_list_pop(struct virtio_video_device *vvd, + struct virtio_video_buffer **virtio_vb) +{ + struct virtio_video_buffer *retbuf; + + if (vvd->is_m2m_dev) { + v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n"); + return -EPERM; + } + + spin_lock(&vvd->pending_buf_list_lock); + if (list_empty(&vvd->pending_buf_list)) { + spin_unlock(&vvd->pending_buf_list_lock); + return -EAGAIN; + } + + retbuf = list_first_entry(&vvd->pending_buf_list, + struct virtio_video_buffer, list); + spin_unlock(&vvd->pending_buf_list_lock); + + *virtio_vb = retbuf; + return 0; +} + +int virtio_video_pending_buf_list_add(struct virtio_video_device *vvd, + struct virtio_video_buffer *virtio_vb) +{ + if (vvd->is_m2m_dev) { + v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n"); + return -EPERM; + } + + spin_lock(&vvd->pending_buf_list_lock); + list_add_tail(&virtio_vb->list, &vvd->pending_buf_list); + spin_unlock(&vvd->pending_buf_list_lock); + + return 0; +} + +int virtio_video_pending_buf_list_del(struct virtio_video_device *vvd, + struct virtio_video_buffer *virtio_vb) +{ + struct virtio_video_buffer *vb, *vb_tmp; + int ret = -EINVAL; + + if (vvd->is_m2m_dev) { + v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n"); + return -EPERM; + } + + spin_lock(&vvd->pending_buf_list_lock); + if (list_empty(&vvd->pending_buf_list)) { + spin_unlock(&vvd->pending_buf_list_lock); + return -EAGAIN; + } + + list_for_each_entry_safe(vb, vb_tmp, &vvd->pending_buf_list, list) { + if (vb->resource_id == virtio_vb->resource_id) { + list_del(&vb->list); + ret = 0; + break; + } + } + spin_unlock(&vvd->pending_buf_list_lock); + + return ret; +} + +int virtio_video_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + int i; + struct virtio_video_stream *stream = vb2_get_drv_priv(vq); + struct video_format_info *p_info; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (*num_planes) + return 0; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + p_info = &stream->in_info; + else + p_info = &stream->out_info; + + *num_planes = p_info->num_planes; + + for (i = 0; i < p_info->num_planes; i++) + sizes[i] = p_info->plane_format[i].plane_size; + + return 0; +} + +static unsigned int +build_virtio_video_sglist_contig(struct virtio_video_resource_sg_list *sgl, + struct vb2_buffer *vb, unsigned int plane) +{ + sgl->entries[0].addr = cpu_to_le64(vb2_dma_contig_plane_dma_addr(vb, plane)); + sgl->entries[0].length = cpu_to_le32(vb->planes[plane].length); + + sgl->num_entries = 1; + + return VIRTIO_VIDEO_RESOURCE_SG_SIZE(1); +} + +static unsigned int +build_virtio_video_sglist(struct virtio_video_resource_sg_list *sgl, + struct vb2_buffer *vb, unsigned int plane, + bool has_iommu) +{ + int i; + struct scatterlist *sg; + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, plane); + + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + sgl->entries[i].addr = cpu_to_le64(has_iommu + ? sg_dma_address(sg) + : sg_phys(sg)); + sgl->entries[i].length = cpu_to_le32(sg->length); + } + + sgl->num_entries = sgt->nents; + + return VIRTIO_VIDEO_RESOURCE_SG_SIZE(sgt->nents); +} + +int virtio_video_buf_init(struct vb2_buffer *vb) +{ + int ret = 0; + void *buf; + size_t buf_size = 0; + struct virtio_video_resource_sg_list *sg_list; + unsigned int i, offset = 0, resource_id, nents = 0; + struct vb2_queue *vq = vb->vb2_queue; + enum v4l2_buf_type queue_type = vq->type; + struct virtio_video_stream *stream = vb2_get_drv_priv(vq); + struct virtio_video_buffer *virtio_vb = to_virtio_vb(vb); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + + if (vvd->supp_non_contig) { + for (i = 0; i < vb->num_planes; i++) { + nents = vb2_dma_sg_plane_desc(vb, i)->nents; + buf_size += VIRTIO_VIDEO_RESOURCE_SG_SIZE(nents); + } + + buf = kcalloc(1, buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < vb->num_planes; i++) { + sg_list = buf + offset; + offset += build_virtio_video_sglist(sg_list, vb, i, + vvd->has_iommu); + } + } else { + buf_size = vb->num_planes * VIRTIO_VIDEO_RESOURCE_SG_SIZE(nents); + + buf = kcalloc(1, buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < vb->num_planes; i++) { + sg_list = buf + offset; + offset += build_virtio_video_sglist_contig(sg_list, + vb, i); + } + } + + virtio_video_resource_id_get(vvd, &resource_id); + + ret = virtio_video_cmd_resource_attach(vvd, stream->stream_id, + resource_id, + to_virtio_queue_type(queue_type), + buf, buf_size); + if (ret) { + virtio_video_resource_id_put(vvd, resource_id); + kfree(buf); + return ret; + } + + virtio_vb->queued = false; + virtio_vb->resource_id = resource_id; + + return 0; +} + +void virtio_video_buf_cleanup(struct vb2_buffer *vb) +{ + struct virtio_video_stream *stream = vb2_get_drv_priv(vb->vb2_queue); + struct virtio_video_buffer *virtio_vb = to_virtio_vb(vb); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + + virtio_video_resource_id_put(vvd, virtio_vb->resource_id); +} + +void virtio_video_buf_queue(struct vb2_buffer *vb) +{ + int i, ret; + struct virtio_video_buffer *virtio_vb; + uint32_t data_size[VB2_MAX_PLANES] = {0}; + struct virtio_video_stream *stream = vb2_get_drv_priv(vb->vb2_queue); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + + for (i = 0; i < vb->num_planes; ++i) + data_size[i] = vb->planes[i].bytesused; + + virtio_vb = to_virtio_vb(vb); + + if (!vvd->is_m2m_dev) + virtio_video_pending_buf_list_add(vvd, virtio_vb); + + ret = virtio_video_cmd_resource_queue(vvd, stream->stream_id, + virtio_vb, data_size, + vb->num_planes, + to_virtio_queue_type(vb->type)); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to queue buffer\n"); + return; + } + + virtio_vb->queued = true; +} + +int virtio_video_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = video_drvdata(file); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (vvd->is_m2m_dev) + return v4l2_m2m_ioctl_qbuf(file, priv, buf); + + return vb2_ioctl_qbuf(file, priv, buf); +} + +int virtio_video_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = video_drvdata(file); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (vvd->is_m2m_dev) + return v4l2_m2m_ioctl_dqbuf(file, priv, buf); + + return vb2_ioctl_dqbuf(file, priv, buf); +} + +int virtio_video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct video_device *video_dev = video_devdata(file); + struct virtio_video_device *vvd = video_drvdata(file); + + if (strscpy(cap->driver, DRIVER_NAME, sizeof(cap->driver)) < 0) + v4l2_err(&vvd->v4l2_dev, "failed to copy driver name\n"); + if (strscpy(cap->card, video_dev->name, sizeof(cap->card)) < 0) + v4l2_err(&vvd->v4l2_dev, "failed to copy card name\n"); + + snprintf(cap->bus_info, sizeof(cap->bus_info), "virtio:%s", + video_dev->name); + + cap->device_caps = video_dev->device_caps; + return 0; +} + +int virtio_video_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format *fmt; + struct video_format_frame *frm; + struct virtio_video_format_frame *frame; + int idx = f->index; + + fmt = virtio_video_find_video_format(&vvd->input_fmt_list, + f->pixel_format); + if (fmt == NULL) + fmt = virtio_video_find_video_format(&vvd->output_fmt_list, + f->pixel_format); + if (fmt == NULL) + return -EINVAL; + + if (idx >= fmt->desc.num_frames) + return -EINVAL; + + frm = &fmt->frames[idx]; + frame = &frm->frame; + + if (frame->width.min == frame->width.max && + frame->height.min == frame->height.max) { + f->type = V4L2_FRMSIZE_TYPE_DISCRETE; + f->discrete.width = frame->width.min; + f->discrete.height = frame->height.min; + return 0; + } + + f->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + f->stepwise.min_width = frame->width.min; + f->stepwise.max_width = frame->width.max; + f->stepwise.min_height = frame->height.min; + f->stepwise.max_height = frame->height.max; + f->stepwise.step_width = frame->width.step; + f->stepwise.step_height = frame->height.step; + return 0; +} + +static bool in_stepped_interval(struct virtio_video_format_range range, + uint32_t point) +{ + if (point < range.min || point > range.max) + return false; + + if (range.step == 0 && range.min == range.max && range.min == point) + return true; + + if (range.step != 0 && (point - range.min) % range.step == 0) + return true; + + return false; +} + +int virtio_video_enum_framemintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format *fmt; + struct video_format_frame *frm; + struct virtio_video_format_frame *frame; + struct virtio_video_format_range *frate; + int idx = f->index; + int f_idx; + + fmt = virtio_video_find_video_format(&vvd->input_fmt_list, + f->pixel_format); + if (fmt == NULL) + fmt = virtio_video_find_video_format(&vvd->output_fmt_list, + f->pixel_format); + if (fmt == NULL) + return -EINVAL; + + for (f_idx = 0; f_idx <= fmt->desc.num_frames; f_idx++) { + frm = &fmt->frames[f_idx]; + frame = &frm->frame; + if (in_stepped_interval(frame->width, f->width) && + in_stepped_interval(frame->height, f->height)) + break; + } + + if (frame == NULL || f->index >= frame->num_rates) + return -EINVAL; + + frate = &frm->frame_rates[idx]; + if (frate->max == frate->min) { + f->type = V4L2_FRMIVAL_TYPE_DISCRETE; + f->discrete.numerator = 1; + f->discrete.denominator = frate->max; + } else { + f->stepwise.min.numerator = 1; + f->stepwise.min.denominator = frate->max; + f->stepwise.max.numerator = 1; + f->stepwise.max.denominator = frate->min; + f->stepwise.step.numerator = 1; + f->stepwise.step.denominator = frate->step; + if (frate->step == 1) + f->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + else + f->type = V4L2_FRMIVAL_TYPE_STEPWISE; + } + return 0; +} + +int virtio_video_stream_get_params(struct virtio_video_device *vvd, + struct virtio_video_stream *stream) +{ + int ret; + + if (vvd->is_m2m_dev) { + ret = virtio_video_cmd_get_params(vvd, stream, + VIRTIO_VIDEO_QUEUE_TYPE_INPUT); + if (ret) { + v4l2_err(&vvd->v4l2_dev, + "failed to get stream in params\n"); + goto err_get_parms; + } + } + + ret = virtio_video_cmd_get_params(vvd, stream, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); + if (ret) + v4l2_err(&vvd->v4l2_dev, "failed to get stream out params\n"); + +err_get_parms: + return ret; +} + +int virtio_video_stream_get_controls(struct virtio_video_device *vvd, + struct virtio_video_stream *stream) +{ + int ret; + + ret = virtio_video_cmd_get_control(vvd, stream, + VIRTIO_VIDEO_CONTROL_PROFILE); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to get stream profile\n"); + goto err_get_ctrl; + } + + ret = virtio_video_cmd_get_control(vvd, stream, + VIRTIO_VIDEO_CONTROL_LEVEL); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to get stream level\n"); + goto err_get_ctrl; + } + + ret = virtio_video_cmd_get_control(vvd, stream, + VIRTIO_VIDEO_CONTROL_BITRATE); + if (ret) + v4l2_err(&vvd->v4l2_dev, "failed to get stream bitrate\n"); + +err_get_ctrl: + return ret; +} + +int virtio_video_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct video_format_info *info; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct virtio_video_stream *stream = file2stream(file); + + if (!V4L2_TYPE_IS_OUTPUT(f->type)) + info = &stream->out_info; + else + info = &stream->in_info; + + virtio_video_format_from_info(info, pix_mp); + + return 0; +} + +int virtio_video_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + int i, ret; + struct virtio_video_stream *stream = file2stream(file); + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format_info info; + struct video_format_info *p_info; + uint32_t queue; + + ret = virtio_video_try_fmt(stream, f); + if (ret) + return ret; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + virtio_video_format_fill_default_info(&info, &stream->in_info); + queue = VIRTIO_VIDEO_QUEUE_TYPE_INPUT; + } else { + virtio_video_format_fill_default_info(&info, &stream->out_info); + queue = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT; + } + + info.frame_width = pix_mp->width; + info.frame_height = pix_mp->height; + info.num_planes = pix_mp->num_planes; + info.fourcc_format = pix_mp->pixelformat; + + for (i = 0; i < info.num_planes; i++) { + info.plane_format[i].stride = + pix_mp->plane_fmt[i].bytesperline; + info.plane_format[i].plane_size = + pix_mp->plane_fmt[i].sizeimage; + } + + virtio_video_cmd_set_params(vvd, stream, &info, queue); + virtio_video_stream_get_params(vvd, stream); + + if (V4L2_TYPE_IS_OUTPUT(f->type)) + p_info = &stream->in_info; + else + p_info = &stream->out_info; + + virtio_video_format_from_info(p_info, pix_mp); + + return 0; +} + +int virtio_video_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct video_format_info *info; + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = video_drvdata(file); + + switch (vvd->type) { + case VIRTIO_VIDEO_DEVICE_ENCODER: + if (!V4L2_TYPE_IS_OUTPUT(sel->type)) + return -EINVAL; + info = &stream->in_info; + break; + case VIRTIO_VIDEO_DEVICE_DECODER: + case VIRTIO_VIDEO_DEVICE_CAMERA: + if (V4L2_TYPE_IS_OUTPUT(sel->type)) + return -EINVAL; + info = &stream->out_info; + break; + default: + v4l2_err(&vvd->v4l2_dev, "unsupported device type\n"); + return -EINVAL; + } + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_PADDED: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + sel->r.width = info->frame_width; + sel->r.height = info->frame_height; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP: + sel->r.top = info->crop.top; + sel->r.left = info->crop.left; + sel->r.width = info->frame_width; + sel->r.height = info->frame_height; + break; + default: + v4l2_dbg(1, vvd->debug, &vvd->v4l2_dev, + "unsupported/invalid selection target: %d\n", + sel->target); + return -EINVAL; + } + + return 0; +} + +int virtio_video_try_fmt(struct virtio_video_stream *stream, + struct v4l2_format *f) +{ + int i, idx = 0; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format *fmt; + bool found = false; + struct video_format_frame *frm; + struct virtio_video_format_frame *frame; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) + fmt = virtio_video_find_video_format(&vvd->input_fmt_list, + pix_mp->pixelformat); + else + fmt = virtio_video_find_video_format(&vvd->output_fmt_list, + pix_mp->pixelformat); + + if (!fmt) { + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + virtio_video_format_from_info(&stream->out_info, + pix_mp); + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + virtio_video_format_from_info(&stream->in_info, + pix_mp); + else + return -EINVAL; + return 0; + } + + for (i = 0; i < fmt->desc.num_frames && !found; i++) { + frm = &fmt->frames[i]; + frame = &frm->frame; + if (!within_range(frame->width.min, pix_mp->width, + frame->width.max)) + continue; + + if (!within_range(frame->height.min, pix_mp->height, + frame->height.max)) + continue; + idx = i; + /* + * Try to find a more suitable frame size. Go with the current + * one otherwise. + */ + if (needs_alignment(pix_mp->width, frame->width.step)) + continue; + + if (needs_alignment(pix_mp->height, frame->height.step)) + continue; + + stream->current_frame = frm; + found = true; + } + + if (!found) { + frm = &fmt->frames[idx]; + if (!frm) + return -EINVAL; + + frame = &frm->frame; + pix_mp->width = clamp(pix_mp->width, frame->width.min, + frame->width.max); + if (frame->width.step != 0) + pix_mp->width = ALIGN(pix_mp->width, frame->width.step); + + pix_mp->height = clamp(pix_mp->height, frame->height.min, + frame->height.max); + if (frame->height.step != 0) + pix_mp->height = ALIGN(pix_mp->height, + frame->height.step); + stream->current_frame = frm; + } + + return 0; +} + +static int virtio_video_queue_free(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum v4l2_buf_type type) +{ + int ret; + enum virtio_video_queue_type queue_type = to_virtio_queue_type(type); + + ret = virtio_video_cmd_queue_detach_resources(vvd, stream, queue_type); + if (ret) { + v4l2_warn(&vvd->v4l2_dev, + "failed to destroy resources\n"); + return ret; + } + + return 0; +} + +int virtio_video_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + int ret; + struct virtio_video_stream *stream = file2stream(file); + struct v4l2_m2m_ctx *m2m_ctx = stream->fh.m2m_ctx; + struct virtio_video_device *vvd = video_drvdata(file); + struct video_device *vdev = video_devdata(file); + struct vb2_queue *vq; + + if (vvd->is_m2m_dev) + vq = v4l2_m2m_get_vq(m2m_ctx, rb->type); + else + vq = vdev->queue; + + if (rb->count == 0) { + ret = virtio_video_queue_free(vvd, stream, vq->type); + if (ret < 0) + return ret; + } + + if (vvd->is_m2m_dev) + return v4l2_m2m_reqbufs(file, m2m_ctx, rb); + else + return vb2_ioctl_reqbufs(file, priv, rb); +} + +int virtio_video_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +void virtio_video_queue_eos_event(struct virtio_video_stream *stream) +{ + static const struct v4l2_event eos_event = { + .type = V4L2_EVENT_EOS + }; + + v4l2_event_queue_fh(&stream->fh, &eos_event); +} + +void virtio_video_queue_res_chg_event(struct virtio_video_stream *stream) +{ + static const struct v4l2_event ev_src_ch = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = + V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_event_queue_fh(&stream->fh, &ev_src_ch); +} + +void virtio_video_handle_error(struct virtio_video_stream *stream) +{ + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + + if (vvd->is_m2m_dev) + virtio_video_queue_release_buffers + (stream, VIRTIO_VIDEO_QUEUE_TYPE_INPUT); + + virtio_video_queue_release_buffers + (stream, VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); +} + +int virtio_video_queue_release_buffers(struct virtio_video_stream *stream, + enum virtio_video_queue_type queue_type) +{ + int ret; + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct vb2_v4l2_buffer *v4l2_vb; + struct virtio_video_buffer *vvb; + + ret = virtio_video_cmd_queue_clear(vvd, stream, queue_type); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to clear queue\n"); + return ret; + } + + if (!vvd->is_m2m_dev) { + while (!virtio_video_pending_buf_list_pop(vvd, &vvb) && vvb) { + v4l2_vb = &vvb->v4l2_m2m_vb.vb; + v4l2_m2m_buf_done(v4l2_vb, VB2_BUF_STATE_ERROR); + } + return 0; + } + + for (;;) { + if (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) + v4l2_vb = v4l2_m2m_src_buf_remove(stream->fh.m2m_ctx); + else + v4l2_vb = v4l2_m2m_dst_buf_remove(stream->fh.m2m_ctx); + if (!v4l2_vb) + break; + v4l2_m2m_buf_done(v4l2_vb, VB2_BUF_STATE_ERROR); + } + + return 0; +} + +void virtio_video_buf_done(struct virtio_video_buffer *virtio_vb, + uint32_t flags, uint64_t timestamp, + uint32_t data_sizes[]) +{ + int i; + enum vb2_buffer_state done_state = VB2_BUF_STATE_DONE; + struct vb2_v4l2_buffer *v4l2_vb = &virtio_vb->v4l2_m2m_vb.vb; + struct vb2_buffer *vb = &v4l2_vb->vb2_buf; + struct vb2_queue *vb2_queue = vb->vb2_queue; + struct virtio_video_stream *stream = vb2_get_drv_priv(vb2_queue); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format_info *p_info; + + virtio_vb->queued = false; + + if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_ERR) + done_state = VB2_BUF_STATE_ERROR; + + if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_KEY_FRAME) + v4l2_vb->flags |= V4L2_BUF_FLAG_KEYFRAME; + + if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_BFRAME) + v4l2_vb->flags |= V4L2_BUF_FLAG_BFRAME; + + if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_PFRAME) + v4l2_vb->flags |= V4L2_BUF_FLAG_PFRAME; + + if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_EOS) { + v4l2_vb->flags |= V4L2_BUF_FLAG_LAST; + virtio_video_state_update(stream, STREAM_STATE_STOPPED); + virtio_video_queue_eos_event(stream); + } + + if ((flags & VIRTIO_VIDEO_DEQUEUE_FLAG_ERR) || + (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_EOS)) { + vb->planes[0].bytesused = 0; + + if (!vvd->is_m2m_dev) + virtio_video_pending_buf_list_del(vvd, virtio_vb); + + v4l2_m2m_buf_done(v4l2_vb, done_state); + return; + } + + if (!V4L2_TYPE_IS_OUTPUT(vb2_queue->type)) { + switch (vvd->type) { + case VIRTIO_VIDEO_DEVICE_ENCODER: + for (i = 0; i < vb->num_planes; i++) + vb->planes[i].bytesused = + le32_to_cpu(data_sizes[i]); + break; + case VIRTIO_VIDEO_DEVICE_CAMERA: + case VIRTIO_VIDEO_DEVICE_DECODER: + p_info = &stream->out_info; + for (i = 0; i < p_info->num_planes; i++) + vb->planes[i].bytesused = + p_info->plane_format[i].plane_size; + break; + } + + vb->timestamp = timestamp; + } + + if (!vvd->is_m2m_dev) + virtio_video_pending_buf_list_del(vvd, virtio_vb); + + v4l2_m2m_buf_done(v4l2_vb, done_state); +} + +static int virtio_video_set_device_busy(struct virtio_video_device *vvd) +{ + struct video_device *vd = &vvd->video_dev; + int ret = 0; + + /* Multiple open is allowed for m2m device */ + if (vvd->is_m2m_dev) + return 0; + + mutex_lock(vd->lock); + + if (vvd->device_busy) + ret = -EBUSY; + else + vvd->device_busy = true; + + mutex_unlock(vd->lock); + + return ret; +} + +static void virtio_video_clear_device_busy(struct virtio_video_device *vvd, + struct mutex *lock) +{ + /* Nothing to do for m2m device */ + if (vvd->is_m2m_dev) + return; + + if (lock) + mutex_lock(lock); + + vvd->device_busy = false; + + if (lock) + mutex_unlock(lock); +} + +static int virtio_video_device_open(struct file *file) +{ + int ret; + uint32_t stream_id; + char name[TASK_COMM_LEN]; + struct virtio_video_stream *stream; + struct video_format *default_fmt; + enum virtio_video_format format; + struct video_device *video_dev = video_devdata(file); + struct virtio_video_device *vvd = video_drvdata(file); + + ret = virtio_video_set_device_busy(vvd); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "device already in use.\n"); + return ret; + } + + default_fmt = list_first_entry_or_null(vvd->ops->get_fmt_list(vvd), + struct video_format, + formats_list_entry); + if (!default_fmt) { + v4l2_err(&vvd->v4l2_dev, "device failed to start\n"); + ret = -EIO; + goto err; + } + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) { + ret = -ENOMEM; + goto err; + } + + get_task_comm(name, current); + format = virtio_video_v4l2_format_to_virtio(default_fmt->desc.format); + virtio_video_stream_id_get(vvd, stream, &stream_id); + ret = virtio_video_cmd_stream_create(vvd, stream_id, format, name); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to create stream\n"); + goto err_stream_create; + } + + stream->video_dev = video_dev; + stream->stream_id = stream_id; + + virtio_video_state_reset(stream); + + ret = virtio_video_stream_get_params(vvd, stream); + if (ret) + goto err_stream_create; + + if (format >= VIRTIO_VIDEO_FORMAT_CODED_MIN && + format <= VIRTIO_VIDEO_FORMAT_CODED_MAX) { + ret = virtio_video_stream_get_controls(vvd, stream); + if (ret) + goto err_stream_create; + } + + mutex_init(&stream->vq_mutex); + v4l2_fh_init(&stream->fh, video_dev); + stream->fh.ctrl_handler = &stream->ctrl_handler; + + if (vvd->is_m2m_dev) { + stream->fh.m2m_ctx = v4l2_m2m_ctx_init(vvd->m2m_dev, stream, + vvd->ops->init_queues); + if (IS_ERR(stream->fh.m2m_ctx)) { + ret = PTR_ERR(stream->fh.m2m_ctx); + goto err_init_ctx; + } + + v4l2_m2m_set_src_buffered(stream->fh.m2m_ctx, true); + v4l2_m2m_set_dst_buffered(stream->fh.m2m_ctx, true); + } else { + vvd->ops->init_queues(stream, NULL, &vvd->vb2_output_queue); + /* Video dev queue is required for vb2 ioctl wrappers */ + video_dev->queue = &vvd->vb2_output_queue; + } + + file->private_data = &stream->fh; + v4l2_fh_add(&stream->fh); + + if (vvd->ops->init_ctrls) { + ret = vvd->ops->init_ctrls(stream); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to init controls\n"); + goto err_init_ctrls; + } + } + return 0; + +err_init_ctrls: + v4l2_fh_del(&stream->fh); + mutex_lock(video_dev->lock); + if (vvd->is_m2m_dev) + v4l2_m2m_ctx_release(stream->fh.m2m_ctx); + mutex_unlock(video_dev->lock); +err_init_ctx: + v4l2_fh_exit(&stream->fh); +err_stream_create: + virtio_video_stream_id_put(vvd, stream_id); + kfree(stream); +err: + virtio_video_clear_device_busy(vvd, video_dev->lock); + return ret; +} + +static int virtio_video_device_release(struct file *file) +{ + struct virtio_video_stream *stream = file2stream(file); + struct video_device *video_dev = video_devdata(file); + struct virtio_video_device *vvd = video_drvdata(file); + + mutex_lock(video_dev->lock); + + v4l2_fh_del(&stream->fh); + if (vvd->is_m2m_dev) { + v4l2_m2m_ctx_release(stream->fh.m2m_ctx); + } else if (file->private_data == video_dev->queue->owner) { + vb2_queue_release(&vvd->vb2_output_queue); + video_dev->queue->owner = NULL; + } + + v4l2_fh_exit(&stream->fh); + + virtio_video_cmd_stream_destroy(vvd, stream->stream_id); + virtio_video_stream_id_put(vvd, stream->stream_id); + + kfree(stream); + + /* Mutex already locked here, passing NULL */ + virtio_video_clear_device_busy(vvd, NULL); + + mutex_unlock(video_dev->lock); + + return 0; +} + +static const struct v4l2_file_operations virtio_video_device_m2m_fops = { + .owner = THIS_MODULE, + .open = virtio_video_device_open, + .release = virtio_video_device_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct v4l2_file_operations virtio_video_device_fops = { + .owner = THIS_MODULE, + .open = virtio_video_device_open, + .release = virtio_video_device_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static void virtio_video_device_run(void *priv) +{ + +} + +static void virtio_video_device_job_abort(void *priv) +{ + struct virtio_video_stream *stream = priv; + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + + v4l2_m2m_job_finish(vvd->m2m_dev, stream->fh.m2m_ctx); +} + +static const struct v4l2_m2m_ops virtio_video_device_m2m_ops = { + .device_run = virtio_video_device_run, + .job_abort = virtio_video_device_job_abort, +}; + +static int virtio_video_device_register(struct virtio_video_device *vvd) +{ + int ret; + struct video_device *vd; + + vd = &vvd->video_dev; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) + ret = video_register_device(vd, VFL_TYPE_VIDEO, vvd->vid_dev_nr); +#else + ret = video_register_device(vd, VFL_TYPE_GRABBER, vvd->vid_dev_nr); +#endif + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to register video device\n"); + return ret; + } + + v4l2_info(&vvd->v4l2_dev, "Device '%s' registered as /dev/video%d\n", + vd->name, vd->num); + + return 0; +} + +static void virtio_video_device_unregister(struct virtio_video_device *vvd) +{ + video_unregister_device(&vvd->video_dev); +} + +static int +virtio_video_query_capability(struct virtio_video_device *vvd, + void *resp_buf, + enum virtio_video_queue_type queue_type) +{ + int ret; + int resp_size = vvd->max_caps_len; + + ret = virtio_video_cmd_query_capability(vvd, resp_buf, resp_size, + queue_type); + if (ret) + v4l2_err(&vvd->v4l2_dev, "failed to query capability\n"); + + return ret; +} + +int virtio_video_device_init(struct virtio_video_device *vvd) +{ + int ret; + int vfl_dir; + u32 dev_caps; + struct video_device *vd; + struct v4l2_m2m_dev *m2m_dev; + const struct v4l2_file_operations *fops; + void *input_resp_buf, *output_resp_buf; + + output_resp_buf = kzalloc(vvd->max_caps_len, GFP_KERNEL); + if (!output_resp_buf) + return -ENOMEM; + + ret = virtio_video_query_capability(vvd, output_resp_buf, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to get output caps\n"); + goto err_output_cap; + } + + if (vvd->is_m2m_dev) { + input_resp_buf = kzalloc(vvd->max_caps_len, GFP_KERNEL); + if (!input_resp_buf) { + ret = -ENOMEM; + goto err_input_buf; + } + + ret = virtio_video_query_capability(vvd, input_resp_buf, + VIRTIO_VIDEO_QUEUE_TYPE_INPUT); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to get input caps\n"); + goto err_input_cap; + } + + m2m_dev = v4l2_m2m_init(&virtio_video_device_m2m_ops); + if (IS_ERR(m2m_dev)) { + v4l2_err(&vvd->v4l2_dev, "failed to init m2m device\n"); + ret = PTR_ERR(m2m_dev); + goto err_m2m_dev; + } + vfl_dir = VFL_DIR_M2M; + fops = &virtio_video_device_m2m_fops; + dev_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; + } else { + input_resp_buf = NULL; + m2m_dev = NULL; + vfl_dir = VFL_DIR_RX; + fops = &virtio_video_device_fops; + dev_caps = V4L2_CAP_STREAMING; + if (vvd->is_mplane_cam) + dev_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE; + else + dev_caps |= V4L2_CAP_VIDEO_CAPTURE; + } + + vvd->m2m_dev = m2m_dev; + mutex_init(&vvd->video_dev_mutex); + vd = &vvd->video_dev; + vd->lock = &vvd->video_dev_mutex; + vd->v4l2_dev = &vvd->v4l2_dev; + vd->vfl_dir = vfl_dir; + vd->ioctl_ops = NULL; + vd->fops = fops; + vd->device_caps = dev_caps; + vd->release = video_device_release_empty; + + /* Use the selection API instead */ + v4l2_disable_ioctl(vd, VIDIOC_CROPCAP); + v4l2_disable_ioctl(vd, VIDIOC_G_CROP); + + video_set_drvdata(vd, vvd); + + INIT_LIST_HEAD(&vvd->input_fmt_list); + INIT_LIST_HEAD(&vvd->output_fmt_list); + INIT_LIST_HEAD(&vvd->controls_fmt_list); + INIT_LIST_HEAD(&vvd->pending_buf_list); + + vvd->num_output_fmts = 0; + vvd->num_input_fmts = 0; + + switch (vvd->type) { + case VIRTIO_VIDEO_DEVICE_CAMERA: + virtio_video_cam_init(vvd); + break; + case VIRTIO_VIDEO_DEVICE_ENCODER: + virtio_video_enc_init(vvd); + break; + case VIRTIO_VIDEO_DEVICE_DECODER: + default: + virtio_video_dec_init(vvd); + break; + } + + ret = virtio_video_parse_virtio_capabilities(vvd, input_resp_buf, + output_resp_buf); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to parse a function\n"); + goto parse_cap_err; + } + + ret = virtio_video_parse_virtio_control(vvd); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to query controls\n"); + goto parse_ctrl_err; + } + + ret = virtio_video_device_register(vvd); + if (ret) { + v4l2_err(&vvd->v4l2_dev, + "failed to init virtio video device\n"); + goto register_err; + } + + goto out_cleanup; + +register_err: + virtio_video_clean_control(vvd); +parse_ctrl_err: + virtio_video_clean_capability(vvd); +parse_cap_err: + if (vvd->is_m2m_dev) + v4l2_m2m_release(vvd->m2m_dev); +err_m2m_dev: +err_input_cap: +out_cleanup: + if (vvd->is_m2m_dev) + kfree(input_resp_buf); +err_input_buf: +err_output_cap: + kfree(output_resp_buf); + + return ret; +} + +void virtio_video_device_deinit(struct virtio_video_device *vvd) +{ + vvd->commandq.ready = false; + vvd->eventq.ready = false; + + virtio_video_device_unregister(vvd); + if (vvd->is_m2m_dev) + v4l2_m2m_release(vvd->m2m_dev); + virtio_video_clean_control(vvd); + virtio_video_clean_capability(vvd); +} diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_driver.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_driver.c new file mode 100644 index 00000000..734c5b73 --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_driver.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Driver for virtio video device. + * + * Copyright 2020 OpenSynergy GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/module.h> +#include <linux/version.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0) +#include <linux/dma-map-ops.h> +#else +#include <linux/dma-mapping.h> +#endif + +#include "virtio_video.h" + +static unsigned int debug; +module_param(debug, uint, 0644); + +static unsigned int use_dma_mem; +module_param(use_dma_mem, uint, 0644); +MODULE_PARM_DESC(use_dma_mem, "Try to allocate buffers from the DMA zone"); + +static int vid_nr_dec = -1; +module_param(vid_nr_dec, int, 0644); +MODULE_PARM_DESC(vid_nr_dec, "videoN start number, -1 is autodetect"); + +static int vid_nr_enc = -1; +module_param(vid_nr_enc, int, 0644); +MODULE_PARM_DESC(vid_nr_enc, "videoN start number, -1 is autodetect"); + +static int vid_nr_cam = -1; +module_param(vid_nr_cam, int, 0644); +MODULE_PARM_DESC(vid_nr_cam, "videoN start number, -1 is autodetect"); + +static bool mplane_cam = true; +module_param(mplane_cam, bool, 0644); +MODULE_PARM_DESC(mplane_cam, + "1 (default) - multiplanar camera, 0 - single planar camera"); + +static int virtio_video_probe(struct virtio_device *vdev) +{ + int ret; + struct virtio_video_device *vvd; + struct virtqueue *vqs[2]; + struct device *dev = &vdev->dev; + struct device *pdev = dev->parent; + + static const char * const names[] = { "commandq", "eventq" }; + static vq_callback_t *callbacks[] = { + virtio_video_cmd_cb, + virtio_video_event_cb + }; + + if (!virtio_has_feature(vdev, VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES)) { + dev_err(dev, "device must support guest allocated buffers\n"); + return -ENODEV; + } + + vvd = devm_kzalloc(dev, sizeof(*vvd), GFP_KERNEL); + if (!vvd) + return -ENOMEM; + + vvd->is_m2m_dev = true; + + switch (vdev->id.device) { + case VIRTIO_ID_VIDEO_CAM: + vvd->is_m2m_dev = false; + vvd->vid_dev_nr = vid_nr_cam; + vvd->is_mplane_cam = mplane_cam; + vvd->type = VIRTIO_VIDEO_DEVICE_CAMERA; + break; + case VIRTIO_ID_VIDEO_ENC: + vvd->vid_dev_nr = vid_nr_enc; + vvd->type = VIRTIO_VIDEO_DEVICE_ENCODER; + break; + case VIRTIO_ID_VIDEO_DEC: + default: + vvd->vid_dev_nr = vid_nr_dec; + vvd->type = VIRTIO_VIDEO_DEVICE_DECODER; + break; + } + + vvd->vdev = vdev; + vvd->debug = debug; + vvd->use_dma_mem = use_dma_mem; + vdev->priv = vvd; + + spin_lock_init(&vvd->pending_buf_list_lock); + spin_lock_init(&vvd->resource_idr_lock); + idr_init(&vvd->resource_idr); + spin_lock_init(&vvd->stream_idr_lock); + idr_init(&vvd->stream_idr); + + init_waitqueue_head(&vvd->wq); + + if (virtio_has_feature(vdev, VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG)) + vvd->supp_non_contig = true; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) + vvd->has_iommu = !virtio_has_dma_quirk(vdev); +#else + vvd->has_iommu = !virtio_has_iommu_quirk(vdev); +#endif + + if (!dev->dma_ops) + set_dma_ops(dev, pdev->dma_ops); + + /* + * Set it to coherent_dma_mask by default if the architecture + * code has not set it. + */ + if (!dev->dma_mask) + dev->dma_mask = &dev->coherent_dma_mask; + + dma_set_mask(dev, *pdev->dma_mask); + + dev_set_name(dev, "%s.%i", DRIVER_NAME, vdev->index); + ret = v4l2_device_register(dev, &vvd->v4l2_dev); + if (ret) + goto err_v4l2_reg; + + spin_lock_init(&vvd->commandq.qlock); + init_waitqueue_head(&vvd->commandq.reclaim_queue); + + INIT_WORK(&vvd->eventq.work, virtio_video_process_events); + + INIT_LIST_HEAD(&vvd->pending_vbuf_list); + + ret = virtio_find_vqs(vdev, 2, vqs, callbacks, names, NULL); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to find virt queues\n"); + goto err_vqs; + } + + vvd->commandq.vq = vqs[0]; + vvd->eventq.vq = vqs[1]; + + ret = virtio_video_alloc_vbufs(vvd); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to alloc vbufs\n"); + goto err_vbufs; + } + + virtio_cread(vdev, struct virtio_video_config, max_caps_length, + &vvd->max_caps_len); + if (!vvd->max_caps_len) { + v4l2_err(&vvd->v4l2_dev, "max_caps_len is zero\n"); + ret = -EINVAL; + goto err_config; + } + + virtio_cread(vdev, struct virtio_video_config, max_resp_length, + &vvd->max_resp_len); + if (!vvd->max_resp_len) { + v4l2_err(&vvd->v4l2_dev, "max_resp_len is zero\n"); + ret = -EINVAL; + goto err_config; + } + + ret = virtio_video_alloc_events(vvd); + if (ret) + goto err_events; + + virtio_device_ready(vdev); + vvd->commandq.ready = true; + vvd->eventq.ready = true; + + ret = virtio_video_device_init(vvd); + if (ret) { + v4l2_err(&vvd->v4l2_dev, + "failed to init virtio video\n"); + goto err_init; + } + + return 0; + +err_init: +err_events: +err_config: + virtio_video_free_vbufs(vvd); +err_vbufs: + vdev->config->del_vqs(vdev); +err_vqs: + v4l2_device_unregister(&vvd->v4l2_dev); +err_v4l2_reg: + devm_kfree(&vdev->dev, vvd); + + return ret; +} + +static void virtio_video_remove(struct virtio_device *vdev) +{ + struct virtio_video_device *vvd = vdev->priv; + + virtio_video_device_deinit(vvd); + virtio_video_free_vbufs(vvd); + vdev->config->del_vqs(vdev); + v4l2_device_unregister(&vvd->v4l2_dev); + devm_kfree(&vdev->dev, vvd); +} + +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_VIDEO_DEC, VIRTIO_DEV_ANY_ID }, + { VIRTIO_ID_VIDEO_ENC, VIRTIO_DEV_ANY_ID }, + { VIRTIO_ID_VIDEO_CAM, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static unsigned int features[] = { + VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES, + VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG, +}; + +static struct virtio_driver virtio_video_driver = { + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), + .driver.name = DRIVER_NAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .probe = virtio_video_probe, + .remove = virtio_video_remove, +}; + +module_virtio_driver(virtio_video_driver); + +MODULE_DEVICE_TABLE(virtio, id_table); +MODULE_DESCRIPTION("virtio video driver"); +MODULE_AUTHOR("Dmitry Sepp <dmitry.sepp@opensynergy.com>"); +MODULE_AUTHOR("Kiran Pawar <kiran.pawar@opensynergy.com>"); +MODULE_AUTHOR("Nikolay Martyanov <nikolay.martyanov@opensynergy.com>"); +MODULE_AUTHOR("Samiullah Khawaja <samiullah.khawaja@opensynergy.com>"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_enc.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_enc.c new file mode 100644 index 00000000..66730bdb --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_enc.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Encoder for virtio video device. + * + * Copyright 2020 OpenSynergy GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/version.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> + +#include "virtio_video.h" + +static int virtio_video_enc_start_streaming(struct vb2_queue *vq, + unsigned int count) +{ + struct virtio_video_stream *stream = vb2_get_drv_priv(vq); + bool input_queue = V4L2_TYPE_IS_OUTPUT(vq->type); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (virtio_video_state(stream) == STREAM_STATE_INIT || + (!input_queue && + virtio_video_state(stream) == STREAM_STATE_RESET) || + (input_queue && + virtio_video_state(stream) == STREAM_STATE_STOPPED)) + virtio_video_state_update(stream, STREAM_STATE_RUNNING); + + return 0; +} + +static void virtio_video_enc_stop_streaming(struct vb2_queue *vq) +{ + int ret, queue_type; + struct virtio_video_stream *stream = vb2_get_drv_priv(vq); + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + queue_type = VIRTIO_VIDEO_QUEUE_TYPE_INPUT; + else + queue_type = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT; + + ret = virtio_video_queue_release_buffers(stream, queue_type); + if (ret) + return; + + vb2_wait_for_all_buffers(vq); + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + virtio_video_state_update(stream, STREAM_STATE_STOPPED); + else + virtio_video_state_update(stream, STREAM_STATE_RESET); +} + +static const struct vb2_ops virtio_video_enc_qops = { + .queue_setup = virtio_video_queue_setup, + .buf_init = virtio_video_buf_init, + .buf_cleanup = virtio_video_buf_cleanup, + .buf_queue = virtio_video_buf_queue, + .start_streaming = virtio_video_enc_start_streaming, + .stop_streaming = virtio_video_enc_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int virtio_video_enc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + struct virtio_video_stream *stream = ctrl2stream(ctrl); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + uint32_t control, value; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + control = virtio_video_v4l2_control_to_virtio(ctrl->id); + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE: + ret = virtio_video_cmd_set_control(vvd, stream->stream_id, + control, ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + value = virtio_video_v4l2_level_to_virtio(ctrl->val); + ret = virtio_video_cmd_set_control(vvd, stream->stream_id, + control, value); + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + value = virtio_video_v4l2_profile_to_virtio(ctrl->val); + ret = virtio_video_cmd_set_control(vvd, stream->stream_id, + control, value); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int virtio_video_enc_g_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + struct virtio_video_stream *stream = ctrl2stream(ctrl); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT: + if (virtio_video_state(stream) >= STREAM_STATE_INIT) + ctrl->val = stream->in_info.min_buffers; + else + ctrl->val = 0; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops virtio_video_enc_ctrl_ops = { + .g_volatile_ctrl = virtio_video_enc_g_ctrl, + .s_ctrl = virtio_video_enc_s_ctrl, +}; + +int virtio_video_enc_init_ctrls(struct virtio_video_stream *stream) +{ + struct v4l2_ctrl *ctrl; + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_control_format *c_fmt = NULL; + + v4l2_ctrl_handler_init(&stream->ctrl_handler, 1); + + ctrl = v4l2_ctrl_new_std(&stream->ctrl_handler, + &virtio_video_enc_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, + MIN_BUFS_MIN, MIN_BUFS_MAX, MIN_BUFS_STEP, + MIN_BUFS_DEF); + + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + list_for_each_entry(c_fmt, &vvd->controls_fmt_list, + controls_list_entry) { + switch (c_fmt->format) { + case V4L2_PIX_FMT_H264: + if (c_fmt->profile) + v4l2_ctrl_new_std_menu + (&stream->ctrl_handler, + &virtio_video_enc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + c_fmt->profile->max, + c_fmt->profile->skip_mask, + c_fmt->profile->min); + + if (c_fmt->level) + v4l2_ctrl_new_std_menu + (&stream->ctrl_handler, + &virtio_video_enc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + c_fmt->level->max, + c_fmt->level->skip_mask, + c_fmt->level->min); + break; + default: + v4l2_dbg(1, vvd->debug, + &vvd->v4l2_dev, "unsupported format\n"); + break; + } + } + + if (stream->control.bitrate) { + v4l2_ctrl_new_std(&stream->ctrl_handler, + &virtio_video_enc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, + 1, S32_MAX, + 1, stream->control.bitrate); + } + + if (stream->ctrl_handler.error) + return stream->ctrl_handler.error; + + v4l2_ctrl_handler_setup(&stream->ctrl_handler); + + return 0; +} + +int virtio_video_enc_init_queues(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + int ret; + struct virtio_video_stream *stream = priv; + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct device *dev = vvd->v4l2_dev.dev; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = stream; + src_vq->buf_struct_size = sizeof(struct virtio_video_buffer); + src_vq->ops = &virtio_video_enc_qops; + src_vq->mem_ops = virtio_video_mem_ops(vvd); + src_vq->min_buffers_needed = stream->in_info.min_buffers; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &stream->vq_mutex; + src_vq->gfp_flags = virtio_video_gfp_flags(vvd); + src_vq->dev = dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = stream; + dst_vq->buf_struct_size = sizeof(struct virtio_video_buffer); + dst_vq->ops = &virtio_video_enc_qops; + dst_vq->mem_ops = virtio_video_mem_ops(vvd); + dst_vq->min_buffers_needed = stream->out_info.min_buffers; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &stream->vq_mutex; + dst_vq->gfp_flags = virtio_video_gfp_flags(vvd); + dst_vq->dev = dev; + + return vb2_queue_init(dst_vq); +} + +static int virtio_video_try_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *cmd) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = video_drvdata(file); + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (virtio_video_state(stream) == STREAM_STATE_DRAIN) + return -EBUSY; + + switch (cmd->cmd) { + case V4L2_ENC_CMD_STOP: + case V4L2_ENC_CMD_START: + if (cmd->flags != 0) { + v4l2_err(&vvd->v4l2_dev, "flags=%u are not supported", + cmd->flags); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int virtio_video_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *cmd) +{ + int ret; + struct vb2_queue *src_vq, *dst_vq; + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = video_drvdata(file); + + ret = virtio_video_try_encoder_cmd(file, fh, cmd); + if (ret < 0) + return ret; + + dst_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + switch (cmd->cmd) { + case V4L2_ENC_CMD_START: + vb2_clear_last_buffer_dequeued(dst_vq); + virtio_video_state_update(stream, STREAM_STATE_RUNNING); + break; + case V4L2_ENC_CMD_STOP: + src_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + if (!vb2_is_streaming(src_vq)) { + v4l2_dbg(1, vvd->debug, + &vvd->v4l2_dev, "output is not streaming\n"); + return 0; + } + + if (!vb2_is_streaming(dst_vq)) { + v4l2_dbg(1, vvd->debug, + &vvd->v4l2_dev, "capture is not streaming\n"); + return 0; + } + + ret = virtio_video_cmd_stream_drain(vvd, stream->stream_id); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to drain stream\n"); + return ret; + } + + virtio_video_state_update(stream, STREAM_STATE_DRAIN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int virtio_video_enc_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format *fmt; + int idx = 0; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + if (f->index >= vvd->num_output_fmts) + return -EINVAL; + + list_for_each_entry(fmt, &vvd->output_fmt_list, formats_list_entry) { + if (f->index == idx) { + f->pixelformat = fmt->desc.format; + return 0; + } + idx++; + } + return -EINVAL; +} + +static int virtio_video_enc_enum_fmt_vid_out(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct video_format_info *info = NULL; + struct video_format *fmt = NULL; + unsigned long output_mask = 0; + int idx = 0, bit_num = 0; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + if (f->index >= vvd->num_input_fmts) + return -EINVAL; + + info = &stream->out_info; + list_for_each_entry(fmt, &vvd->output_fmt_list, formats_list_entry) { + if (info->fourcc_format == fmt->desc.format) { + output_mask = fmt->desc.mask; + break; + } + } + + if (output_mask == 0) + return -EINVAL; + + list_for_each_entry(fmt, &vvd->input_fmt_list, formats_list_entry) { + if (test_bit(bit_num, &output_mask)) { + if (f->index == idx) { + f->pixelformat = fmt->desc.format; + return 0; + } + idx++; + } + bit_num++; + } + return -EINVAL; +} + +static int virtio_video_enc_s_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + int ret; + struct virtio_video_stream *stream = file2stream(file); + + ret = virtio_video_s_fmt(file, fh, f); + if (ret) + return ret; + + if (!V4L2_TYPE_IS_OUTPUT(f->type)) { + if (virtio_video_state(stream) == STREAM_STATE_IDLE) + virtio_video_state_update(stream, STREAM_STATE_INIT); + } + + return 0; +} + +static int virtio_video_enc_try_framerate(struct virtio_video_stream *stream, + unsigned int fps) +{ + int rate_idx; + struct video_format_frame *frame = NULL; + + if (stream->current_frame == NULL) + return -EINVAL; + + frame = stream->current_frame; + for (rate_idx = 0; rate_idx < frame->frame.num_rates; rate_idx++) { + struct virtio_video_format_range *frame_rate = + &frame->frame_rates[rate_idx]; + + if (within_range(frame_rate->min, fps, frame_rate->max)) + return 0; + } + + return -EINVAL; +} + +static void virtio_video_timeperframe_from_info(struct video_format_info *info, + struct v4l2_fract *timeperframe) +{ + timeperframe->numerator = 1; + timeperframe->denominator = info->frame_rate; +} + +static int virtio_video_enc_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *a) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct v4l2_outputparm *out = &a->parm.output; + struct v4l2_fract *timeperframe = &out->timeperframe; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (!V4L2_TYPE_IS_OUTPUT(a->type)) { + v4l2_err(&vvd->v4l2_dev, + "getting FPS is only possible for the output queue\n"); + return -EINVAL; + } + + out->capability = V4L2_CAP_TIMEPERFRAME; + virtio_video_timeperframe_from_info(&stream->in_info, timeperframe); + + return 0; +} + +static int virtio_video_enc_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *a) +{ + int ret; + u64 frame_interval, frame_rate; + struct video_format_info info; + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + struct v4l2_outputparm *out = &a->parm.output; + struct v4l2_fract *timeperframe = &out->timeperframe; + + if (virtio_video_state(stream) == STREAM_STATE_ERROR) + return -EIO; + + if (V4L2_TYPE_IS_OUTPUT(a->type)) { + frame_interval = timeperframe->numerator * (u64)USEC_PER_SEC; + do_div(frame_interval, timeperframe->denominator); + if (!frame_interval) + return -EINVAL; + + frame_rate = (u64)USEC_PER_SEC; + do_div(frame_rate, frame_interval); + } else { + v4l2_err(&vvd->v4l2_dev, + "setting FPS is only possible for the output queue\n"); + return -EINVAL; + } + + ret = virtio_video_enc_try_framerate(stream, frame_rate); + if (ret) + return ret; + + virtio_video_format_fill_default_info(&info, &stream->in_info); + info.frame_rate = frame_rate; + + virtio_video_cmd_set_params(vvd, stream, &info, + VIRTIO_VIDEO_QUEUE_TYPE_INPUT); + virtio_video_stream_get_params(vvd, stream); + + out->capability = V4L2_CAP_TIMEPERFRAME; + virtio_video_timeperframe_from_info(&stream->in_info, timeperframe); + + return 0; +} + +static int virtio_video_enc_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct virtio_video_stream *stream = file2stream(file); + struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); + int ret; + + if (!V4L2_TYPE_IS_OUTPUT(sel->type)) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + stream->in_info.crop.top = sel->r.top; + stream->in_info.crop.left = sel->r.left; + stream->in_info.crop.width = sel->r.width; + stream->in_info.crop.height = sel->r.height; + break; + default: + return -EINVAL; + } + + ret = virtio_video_cmd_set_params(vvd, stream, &stream->in_info, + VIRTIO_VIDEO_QUEUE_TYPE_INPUT); + if (ret) + return -EINVAL; + + return virtio_video_cmd_get_params(vvd, stream, + VIRTIO_VIDEO_QUEUE_TYPE_INPUT); +} + +static const struct v4l2_ioctl_ops virtio_video_enc_ioctl_ops = { + .vidioc_querycap = virtio_video_querycap, + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)) + .vidioc_enum_fmt_vid_cap = virtio_video_enc_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = virtio_video_enc_enum_fmt_vid_out, +#else + .vidioc_enum_fmt_vid_cap_mplane = virtio_video_enc_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out_mplane = virtio_video_enc_enum_fmt_vid_out, +#endif + .vidioc_g_fmt_vid_cap_mplane = virtio_video_g_fmt, + .vidioc_s_fmt_vid_cap_mplane = virtio_video_enc_s_fmt, + + .vidioc_g_fmt_vid_out_mplane = virtio_video_g_fmt, + .vidioc_s_fmt_vid_out_mplane = virtio_video_enc_s_fmt, + + .vidioc_try_encoder_cmd = virtio_video_try_encoder_cmd, + .vidioc_encoder_cmd = virtio_video_encoder_cmd, + .vidioc_enum_frameintervals = virtio_video_enum_framemintervals, + .vidioc_enum_framesizes = virtio_video_enum_framesizes, + + .vidioc_g_selection = virtio_video_g_selection, + .vidioc_s_selection = virtio_video_enc_s_selection, + + .vidioc_reqbufs = virtio_video_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = virtio_video_qbuf, + .vidioc_dqbuf = virtio_video_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_s_parm = virtio_video_enc_s_parm, + .vidioc_g_parm = virtio_video_enc_g_parm, + + .vidioc_subscribe_event = virtio_video_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void *virtio_video_enc_get_fmt_list(struct virtio_video_device *vvd) +{ + return &vvd->output_fmt_list; +} + +static struct virtio_video_device_ops virtio_video_enc_ops = { + .init_ctrls = virtio_video_enc_init_ctrls, + .init_queues = virtio_video_enc_init_queues, + .get_fmt_list = virtio_video_enc_get_fmt_list, +}; + +int virtio_video_enc_init(struct virtio_video_device *vvd) +{ + ssize_t num; + struct video_device *vd = &vvd->video_dev; + + vd->ioctl_ops = &virtio_video_enc_ioctl_ops; + vvd->ops = &virtio_video_enc_ops; + + num = strscpy(vd->name, "stateful-encoder", sizeof(vd->name)); + if (num < 0) + return num; + + return 0; +} diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_helpers.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_helpers.c new file mode 100644 index 00000000..ac487053 --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_helpers.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Driver for virtio video device. + * + * Copyright 2020 OpenSynergy GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "virtio_video.h" + +struct virtio_video_convert_table { + uint32_t virtio_value; + uint32_t v4l2_value; +}; + +static struct virtio_video_convert_table level_table[] = { + { VIRTIO_VIDEO_LEVEL_H264_1_0, V4L2_MPEG_VIDEO_H264_LEVEL_1_0 }, + { VIRTIO_VIDEO_LEVEL_H264_1_1, V4L2_MPEG_VIDEO_H264_LEVEL_1_1 }, + { VIRTIO_VIDEO_LEVEL_H264_1_2, V4L2_MPEG_VIDEO_H264_LEVEL_1_2 }, + { VIRTIO_VIDEO_LEVEL_H264_1_3, V4L2_MPEG_VIDEO_H264_LEVEL_1_3 }, + { VIRTIO_VIDEO_LEVEL_H264_2_0, V4L2_MPEG_VIDEO_H264_LEVEL_2_0 }, + { VIRTIO_VIDEO_LEVEL_H264_2_1, V4L2_MPEG_VIDEO_H264_LEVEL_2_1 }, + { VIRTIO_VIDEO_LEVEL_H264_2_2, V4L2_MPEG_VIDEO_H264_LEVEL_2_2 }, + { VIRTIO_VIDEO_LEVEL_H264_3_0, V4L2_MPEG_VIDEO_H264_LEVEL_3_0 }, + { VIRTIO_VIDEO_LEVEL_H264_3_1, V4L2_MPEG_VIDEO_H264_LEVEL_3_1 }, + { VIRTIO_VIDEO_LEVEL_H264_3_2, V4L2_MPEG_VIDEO_H264_LEVEL_3_2 }, + { VIRTIO_VIDEO_LEVEL_H264_4_0, V4L2_MPEG_VIDEO_H264_LEVEL_4_0 }, + { VIRTIO_VIDEO_LEVEL_H264_4_1, V4L2_MPEG_VIDEO_H264_LEVEL_4_1 }, + { VIRTIO_VIDEO_LEVEL_H264_4_2, V4L2_MPEG_VIDEO_H264_LEVEL_4_2 }, + { VIRTIO_VIDEO_LEVEL_H264_5_0, V4L2_MPEG_VIDEO_H264_LEVEL_5_0 }, + { VIRTIO_VIDEO_LEVEL_H264_5_1, V4L2_MPEG_VIDEO_H264_LEVEL_5_1 }, + { 0 }, +}; + +uint32_t virtio_video_level_to_v4l2(uint32_t level) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(level_table); idx++) { + if (level_table[idx].virtio_value == level) + return level_table[idx].v4l2_value; + } + + return 0; +} + +uint32_t virtio_video_v4l2_level_to_virtio(uint32_t v4l2_level) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(level_table); idx++) { + if (level_table[idx].v4l2_value == v4l2_level) + return level_table[idx].virtio_value; + } + + return 0; +} + +static struct virtio_video_convert_table profile_table[] = { + { VIRTIO_VIDEO_PROFILE_H264_BASELINE, + V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE }, + { VIRTIO_VIDEO_PROFILE_H264_MAIN, V4L2_MPEG_VIDEO_H264_PROFILE_MAIN }, + { VIRTIO_VIDEO_PROFILE_H264_EXTENDED, + V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED }, + { VIRTIO_VIDEO_PROFILE_H264_HIGH, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH }, + { VIRTIO_VIDEO_PROFILE_H264_HIGH10PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10 }, + { VIRTIO_VIDEO_PROFILE_H264_HIGH422PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422}, + { VIRTIO_VIDEO_PROFILE_H264_HIGH444PREDICTIVEPROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE }, + { VIRTIO_VIDEO_PROFILE_H264_SCALABLEBASELINE, + V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE }, + { VIRTIO_VIDEO_PROFILE_H264_SCALABLEHIGH, + V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH }, + { VIRTIO_VIDEO_PROFILE_H264_STEREOHIGH, + V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH }, + { VIRTIO_VIDEO_PROFILE_H264_MULTIVIEWHIGH, + V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH }, + { 0 }, +}; + +uint32_t virtio_video_profile_to_v4l2(uint32_t profile) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(profile_table); idx++) { + if (profile_table[idx].virtio_value == profile) + return profile_table[idx].v4l2_value; + } + + return 0; +} + +uint32_t virtio_video_v4l2_profile_to_virtio(uint32_t v4l2_profile) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(profile_table); idx++) { + if (profile_table[idx].v4l2_value == v4l2_profile) + return profile_table[idx].virtio_value; + } + + return 0; +} + +static struct virtio_video_convert_table format_table[] = { + { VIRTIO_VIDEO_FORMAT_ARGB8888, V4L2_PIX_FMT_ARGB32 }, + { VIRTIO_VIDEO_FORMAT_BGRA8888, V4L2_PIX_FMT_ABGR32 }, + { VIRTIO_VIDEO_FORMAT_RGBA8888, V4L2_PIX_FMT_RGB32 }, + { VIRTIO_VIDEO_FORMAT_NV12, V4L2_PIX_FMT_NV12 }, + { VIRTIO_VIDEO_FORMAT_YUV420, V4L2_PIX_FMT_YUV420 }, + { VIRTIO_VIDEO_FORMAT_YVU420, V4L2_PIX_FMT_YVU420 }, + { VIRTIO_VIDEO_FORMAT_YUV422, V4L2_PIX_FMT_YUYV }, + { VIRTIO_VIDEO_FORMAT_MPEG2, V4L2_PIX_FMT_MPEG2 }, + { VIRTIO_VIDEO_FORMAT_MPEG4, V4L2_PIX_FMT_MPEG4 }, + { VIRTIO_VIDEO_FORMAT_H264, V4L2_PIX_FMT_H264 }, + { VIRTIO_VIDEO_FORMAT_HEVC, V4L2_PIX_FMT_HEVC }, + { VIRTIO_VIDEO_FORMAT_VP8, V4L2_PIX_FMT_VP8 }, + { VIRTIO_VIDEO_FORMAT_VP9, V4L2_PIX_FMT_VP9 }, + { 0 }, +}; + +uint32_t virtio_video_format_to_v4l2(uint32_t format) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(format_table); idx++) { + if (format_table[idx].virtio_value == format) + return format_table[idx].v4l2_value; + } + + return 0; +} + +uint32_t virtio_video_v4l2_format_to_virtio(uint32_t v4l2_format) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(format_table); idx++) { + if (format_table[idx].v4l2_value == v4l2_format) + return format_table[idx].virtio_value; + } + + return 0; +} + +static struct virtio_video_convert_table control_table[] = { + { VIRTIO_VIDEO_CONTROL_BITRATE, V4L2_CID_MPEG_VIDEO_BITRATE }, + { VIRTIO_VIDEO_CONTROL_PROFILE, V4L2_CID_MPEG_VIDEO_H264_PROFILE }, + { VIRTIO_VIDEO_CONTROL_LEVEL, V4L2_CID_MPEG_VIDEO_H264_LEVEL }, + { 0 }, +}; + +uint32_t virtio_video_control_to_v4l2(uint32_t control) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(control_table); idx++) { + if (control_table[idx].virtio_value == control) + return control_table[idx].v4l2_value; + } + + return 0; +} + +uint32_t virtio_video_v4l2_control_to_virtio(uint32_t v4l2_control) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(control_table); idx++) { + if (control_table[idx].v4l2_value == v4l2_control) + return control_table[idx].virtio_value; + } + + return 0; +} + +uint32_t virtio_video_get_format_from_virtio_profile(uint32_t virtio_profile) +{ + if (virtio_profile >= VIRTIO_VIDEO_PROFILE_H264_MIN && + virtio_profile <= VIRTIO_VIDEO_PROFILE_H264_MAX) + return VIRTIO_VIDEO_FORMAT_H264; + else if (virtio_profile >= VIRTIO_VIDEO_PROFILE_HEVC_MIN && + virtio_profile <= VIRTIO_VIDEO_PROFILE_HEVC_MAX) + return VIRTIO_VIDEO_FORMAT_HEVC; + else if (virtio_profile >= VIRTIO_VIDEO_PROFILE_VP8_MIN && + virtio_profile <= VIRTIO_VIDEO_PROFILE_VP8_MAX) + return VIRTIO_VIDEO_FORMAT_VP8; + else if (virtio_profile >= VIRTIO_VIDEO_PROFILE_VP9_MIN && + virtio_profile <= VIRTIO_VIDEO_PROFILE_VP9_MAX) + return VIRTIO_VIDEO_FORMAT_VP9; + + return 0; +} + +struct video_format *virtio_video_find_video_format(struct list_head *fmts_list, + uint32_t format) +{ + struct video_format *fmt = NULL; + + list_for_each_entry(fmt, fmts_list, formats_list_entry) { + if (fmt->desc.format == format) + return fmt; + } + + return NULL; +} + +void virtio_video_format_from_info(struct video_format_info *info, + struct v4l2_pix_format_mplane *pix_mp) +{ + int i; + + pix_mp->width = info->frame_width; + pix_mp->height = info->frame_height; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->colorspace = V4L2_COLORSPACE_REC709; + pix_mp->xfer_func = 0; + pix_mp->ycbcr_enc = 0; + pix_mp->quantization = 0; + memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); + memset(pix_mp->plane_fmt[0].reserved, 0, + sizeof(pix_mp->plane_fmt[0].reserved)); + + pix_mp->num_planes = info->num_planes; + pix_mp->pixelformat = info->fourcc_format; + + for (i = 0; i < info->num_planes; i++) { + pix_mp->plane_fmt[i].bytesperline = + info->plane_format[i].stride; + pix_mp->plane_fmt[i].sizeimage = + info->plane_format[i].plane_size; + } +} + +void virtio_video_format_fill_default_info(struct video_format_info *dst_info, + struct video_format_info *src_info) +{ + memcpy(dst_info, src_info, sizeof(*dst_info)); +} + +void virtio_video_pix_fmt_sp2mp(const struct v4l2_pix_format *pix, + struct v4l2_pix_format_mplane *pix_mp) +{ + memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); + memset(&pix_mp->plane_fmt[0].reserved, 0, + sizeof(pix_mp->plane_fmt[0].reserved)); + pix_mp->num_planes = 1; + pix_mp->width = pix->width; + pix_mp->height = pix->height; + pix_mp->pixelformat = pix->pixelformat; + pix_mp->field = pix->field; + pix_mp->plane_fmt[0].bytesperline = pix->bytesperline; + pix_mp->plane_fmt[0].sizeimage = pix->sizeimage; + pix_mp->colorspace = pix->colorspace; + pix_mp->flags = pix->flags; + pix_mp->ycbcr_enc = pix->ycbcr_enc; + pix_mp->quantization = pix->quantization; + pix_mp->xfer_func = pix->xfer_func; +} + +void virtio_video_pix_fmt_mp2sp(const struct v4l2_pix_format_mplane *pix_mp, + struct v4l2_pix_format *pix) +{ + pix->width = pix_mp->width; + pix->height = pix_mp->height; + pix->pixelformat = pix_mp->pixelformat; + pix->field = pix_mp->field; + pix->bytesperline = pix_mp->plane_fmt[0].bytesperline; + pix->sizeimage = pix_mp->plane_fmt[0].sizeimage; + pix->colorspace = pix_mp->colorspace; + pix->priv = 0; + pix->flags = pix_mp->flags; + pix->ycbcr_enc = pix_mp->ycbcr_enc; + pix->quantization = pix_mp->quantization; + pix->xfer_func = pix_mp->xfer_func; +} diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_vq.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_vq.c new file mode 100644 index 00000000..f3c97c7e --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_vq.c @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Driver for virtio video device. + * + * Copyright 2020 OpenSynergy GmbH. + * + * Based on drivers/gpu/drm/virtio/virtgpu_vq.c + * Copyright (C) 2015 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "virtio_video.h" + +#define MAX_INLINE_CMD_SIZE 298 +#define MAX_INLINE_RESP_SIZE 298 +#define VBUFFER_SIZE (sizeof(struct virtio_video_vbuffer) \ + + MAX_INLINE_CMD_SIZE \ + + MAX_INLINE_RESP_SIZE) + +static int virtio_video_queue_event_buffer(struct virtio_video_device *vvd, + struct virtio_video_event *evt); +static void virtio_video_handle_event(struct virtio_video_device *vvd, + struct virtio_video_event *evt); + +void virtio_video_resource_id_get(struct virtio_video_device *vvd, uint32_t *id) +{ + int handle; + + idr_preload(GFP_KERNEL); + spin_lock(&vvd->resource_idr_lock); + handle = idr_alloc(&vvd->resource_idr, NULL, 1, 0, GFP_NOWAIT); + spin_unlock(&vvd->resource_idr_lock); + idr_preload_end(); + *id = handle; +} + +void virtio_video_resource_id_put(struct virtio_video_device *vvd, uint32_t id) +{ + spin_lock(&vvd->resource_idr_lock); + idr_remove(&vvd->resource_idr, id); + spin_unlock(&vvd->resource_idr_lock); +} + +void virtio_video_stream_id_get(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + uint32_t *id) +{ + int handle; + + idr_preload(GFP_KERNEL); + spin_lock(&vvd->stream_idr_lock); + handle = idr_alloc(&vvd->stream_idr, stream, 1, 0, 0); + spin_unlock(&vvd->stream_idr_lock); + idr_preload_end(); + *id = handle; +} + +void virtio_video_stream_id_put(struct virtio_video_device *vvd, uint32_t id) +{ + spin_lock(&vvd->stream_idr_lock); + idr_remove(&vvd->stream_idr, id); + spin_unlock(&vvd->stream_idr_lock); +} + +static bool vbuf_is_pending(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + struct virtio_video_vbuffer *entry; + + list_for_each_entry(entry, &vvd->pending_vbuf_list, pending_list_entry) + { + if (entry == vbuf && entry->id == vbuf->id) + return true; + } + + return false; +} + +static void free_vbuf(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + list_del(&vbuf->pending_list_entry); + kfree(vbuf->data_buf); + kmem_cache_free(vvd->vbufs, vbuf); +} + +void virtio_video_cmd_cb(struct virtqueue *vq) +{ + struct virtio_video_device *vvd = vq->vdev->priv; + struct virtio_video_vbuffer *vbuf; + unsigned long flags; + unsigned int len; + + spin_lock_irqsave(&vvd->commandq.qlock, flags); + while (vvd->commandq.ready) { + virtqueue_disable_cb(vq); + + while ((vbuf = virtqueue_get_buf(vq, &len))) { + if (!vbuf_is_pending(vvd, vbuf)) + continue; + + if (vbuf->resp_cb) + vbuf->resp_cb(vvd, vbuf); + + if (vbuf->is_sync) + complete(&vbuf->reclaimed); + else + free_vbuf(vvd, vbuf); + } + + if (unlikely(virtqueue_is_broken(vq))) + break; + + if (virtqueue_enable_cb(vq)) + break; + } + spin_unlock_irqrestore(&vvd->commandq.qlock, flags); + + wake_up(&vvd->commandq.reclaim_queue); +} + +void virtio_video_process_events(struct work_struct *work) +{ + struct virtio_video_device *vvd = container_of(work, + struct virtio_video_device, eventq.work); + struct virtqueue *vq = vvd->eventq.vq; + struct virtio_video_event *evt; + unsigned int len; + + while (vvd->eventq.ready) { + virtqueue_disable_cb(vq); + + while ((evt = virtqueue_get_buf(vq, &len))) { + virtio_video_handle_event(vvd, evt); + virtio_video_queue_event_buffer(vvd, evt); + } + + if (unlikely(virtqueue_is_broken(vq))) + break; + + if (virtqueue_enable_cb(vq)) + break; + } +} + +void virtio_video_event_cb(struct virtqueue *vq) +{ + struct virtio_video_device *vvd = vq->vdev->priv; + + schedule_work(&vvd->eventq.work); +} + +static struct virtio_video_vbuffer * +virtio_video_get_vbuf(struct virtio_video_device *vvd, int size, int resp_size, + void *resp_buf, virtio_video_resp_cb resp_cb) +{ + struct virtio_video_vbuffer *vbuf; + + vbuf = kmem_cache_alloc(vvd->vbufs, GFP_KERNEL); + if (!vbuf) + return ERR_PTR(-ENOMEM); + memset(vbuf, 0, VBUFFER_SIZE); + + BUG_ON(size > MAX_INLINE_CMD_SIZE); + vbuf->buf = (void *)vbuf + sizeof(*vbuf); + vbuf->size = size; + + vbuf->resp_cb = resp_cb; + vbuf->resp_size = resp_size; + if (resp_size <= MAX_INLINE_RESP_SIZE && !resp_buf) + vbuf->resp_buf = (void *)vbuf->buf + size; + else + vbuf->resp_buf = resp_buf; + BUG_ON(!vbuf->resp_buf); + + return vbuf; +} + +int virtio_video_alloc_vbufs(struct virtio_video_device *vvd) +{ + vvd->vbufs = + kmem_cache_create("virtio-video-vbufs", VBUFFER_SIZE, + __alignof__(struct virtio_video_vbuffer), 0, + NULL); + if (!vvd->vbufs) + return -ENOMEM; + + return 0; +} + +void virtio_video_free_vbufs(struct virtio_video_device *vvd) +{ + struct virtio_video_vbuffer *vbuf; + + /* Release command buffers. Operation on vbufs here is lock safe, + since before device was deinitialized and queues was stopped + (in not ready state) */ + while ((vbuf = virtqueue_detach_unused_buf(vvd->commandq.vq))) { + if (vbuf_is_pending(vvd, vbuf)) + free_vbuf(vvd, vbuf); + } + + kmem_cache_destroy(vvd->vbufs); + vvd->vbufs = NULL; + + /* Release event buffers */ + while (virtqueue_detach_unused_buf(vvd->eventq.vq)); + + kfree(vvd->evts); + vvd->evts = NULL; +} + +static void *virtio_video_alloc_req(struct virtio_video_device *vvd, + struct virtio_video_vbuffer **vbuffer_p, + int size) +{ + struct virtio_video_vbuffer *vbuf; + + vbuf = virtio_video_get_vbuf(vvd, size, + sizeof(struct virtio_video_cmd_hdr), + NULL, NULL); + if (IS_ERR(vbuf)) { + *vbuffer_p = NULL; + return ERR_CAST(vbuf); + } + *vbuffer_p = vbuf; + + return vbuf->buf; +} + +static void * +virtio_video_alloc_req_resp(struct virtio_video_device *vvd, + virtio_video_resp_cb cb, + struct virtio_video_vbuffer **vbuffer_p, + int req_size, int resp_size, + void *resp_buf) +{ + struct virtio_video_vbuffer *vbuf; + + vbuf = virtio_video_get_vbuf(vvd, req_size, resp_size, resp_buf, cb); + if (IS_ERR(vbuf)) { + *vbuffer_p = NULL; + return ERR_CAST(vbuf); + } + *vbuffer_p = vbuf; + + return vbuf->buf; +} + +static int +virtio_video_queue_cmd_buffer(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + unsigned long flags; + struct virtqueue *vq = vvd->commandq.vq; + struct scatterlist *sgs[3], vreq, vout, vresp; + int outcnt = 0, incnt = 0; + int ret; + + if (!vvd->commandq.ready) + return -ENODEV; + + spin_lock_irqsave(&vvd->commandq.qlock, flags); + + vbuf->id = vvd->vbufs_sent++; + list_add_tail(&vbuf->pending_list_entry, &vvd->pending_vbuf_list); + + sg_init_one(&vreq, vbuf->buf, vbuf->size); + sgs[outcnt + incnt] = &vreq; + outcnt++; + + if (vbuf->data_size) { + sg_init_one(&vout, vbuf->data_buf, vbuf->data_size); + sgs[outcnt + incnt] = &vout; + outcnt++; + } + + if (vbuf->resp_size) { + sg_init_one(&vresp, vbuf->resp_buf, vbuf->resp_size); + sgs[outcnt + incnt] = &vresp; + incnt++; + } + +retry: + ret = virtqueue_add_sgs(vq, sgs, outcnt, incnt, vbuf, GFP_ATOMIC); + if (ret == -ENOSPC) { + spin_unlock_irqrestore(&vvd->commandq.qlock, flags); + wait_event(vvd->commandq.reclaim_queue, vq->num_free); + spin_lock_irqsave(&vvd->commandq.qlock, flags); + goto retry; + } else { + virtqueue_kick(vq); + } + + spin_unlock_irqrestore(&vvd->commandq.qlock, flags); + + return ret; +} + +static int +virtio_video_queue_cmd_buffer_sync(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + int ret; + unsigned long rem; + unsigned long flags; + + vbuf->is_sync = true; + init_completion(&vbuf->reclaimed); + + ret = virtio_video_queue_cmd_buffer(vvd, vbuf); + if (ret) + return ret; + + rem = wait_for_completion_timeout(&vbuf->reclaimed, 5 * HZ); + if (rem == 0) + ret = -ETIMEDOUT; + + spin_lock_irqsave(&vvd->commandq.qlock, flags); + if (vbuf_is_pending(vvd, vbuf)) + free_vbuf(vvd, vbuf); + spin_unlock_irqrestore(&vvd->commandq.qlock, flags); + + return ret; +} + +static int virtio_video_queue_event_buffer(struct virtio_video_device *vvd, + struct virtio_video_event *evt) +{ + int ret; + struct scatterlist sg; + struct virtqueue *vq = vvd->eventq.vq; + + memset(evt, 0, sizeof(struct virtio_video_event)); + sg_init_one(&sg, evt, sizeof(struct virtio_video_event)); + + ret = virtqueue_add_inbuf(vq, &sg, 1, evt, GFP_KERNEL); + if (ret) { + v4l2_err(&vvd->v4l2_dev, "failed to queue event buffer\n"); + return ret; + } + + virtqueue_kick(vq); + + return 0; +} + +static void virtio_video_handle_event(struct virtio_video_device *vvd, + struct virtio_video_event *evt) +{ + int ret; + struct virtio_video_stream *stream; + uint32_t stream_id = evt->stream_id; + struct video_device *vd = &vvd->video_dev; + + mutex_lock(vd->lock); + + stream = idr_find(&vvd->stream_idr, stream_id); + if (!stream) { + v4l2_warn(&vvd->v4l2_dev, "stream_id=%u not found for event\n", + stream_id); + mutex_unlock(vd->lock); + return; + } + + switch (le32_to_cpu(evt->event_type)) { + case VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED: + v4l2_dbg(1, vvd->debug, &vvd->v4l2_dev, + "stream_id=%u: resolution change event\n", stream_id); + virtio_video_cmd_get_params(vvd, stream, + VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); + virtio_video_queue_res_chg_event(stream); + if (virtio_video_state(stream) == STREAM_STATE_INIT) { + virtio_video_state_update(stream, + STREAM_STATE_DYNAMIC_RES_CHANGE); + wake_up(&vvd->wq); + } + break; + case VIRTIO_VIDEO_EVENT_ERROR: + v4l2_err(&vvd->v4l2_dev, "stream_id=%i: error event\n", + stream_id); + virtio_video_state_update(stream, STREAM_STATE_ERROR); + virtio_video_handle_error(stream); + break; + default: + v4l2_warn(&vvd->v4l2_dev, "stream_id=%i: unknown event\n", + stream_id); + break; + } + + mutex_unlock(vd->lock); +} + +int virtio_video_alloc_events(struct virtio_video_device *vvd) +{ + int ret; + size_t i; + struct virtio_video_event *evts; + size_t num = vvd->eventq.vq->num_free; + + evts = kzalloc(num * sizeof(struct virtio_video_event), GFP_KERNEL); + if (!evts) { + v4l2_err(&vvd->v4l2_dev, "failed to alloc event buffers!!!\n"); + return -ENOMEM; + } + vvd->evts = evts; + + for (i = 0; i < num; i++) { + ret = virtio_video_queue_event_buffer(vvd, &evts[i]); + if (ret) { + v4l2_err(&vvd->v4l2_dev, + "failed to queue event buffer\n"); + return ret; + } + } + + return 0; +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_cmd_stream_create(struct virtio_video_device *vvd, + uint32_t stream_id, + enum virtio_video_format format, + const char *tag) +{ + struct virtio_video_stream_create *req_p; + struct virtio_video_vbuffer *vbuf; + + req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_STREAM_CREATE); + req_p->hdr.stream_id = cpu_to_le32(stream_id); + req_p->in_mem_type = cpu_to_le32(VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES); + req_p->out_mem_type = cpu_to_le32(VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES); + req_p->coded_format = cpu_to_le32(format); + if (strscpy(req_p->tag, tag, sizeof(req_p->tag) - 1) < 0) + v4l2_err(&vvd->v4l2_dev, "failed to copy stream tag\n"); + req_p->tag[sizeof(req_p->tag) - 1] = 0; + + return virtio_video_queue_cmd_buffer(vvd, vbuf); +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_cmd_stream_destroy(struct virtio_video_device *vvd, + uint32_t stream_id) +{ + struct virtio_video_stream_destroy *req_p; + struct virtio_video_vbuffer *vbuf; + + req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_STREAM_DESTROY); + req_p->hdr.stream_id = cpu_to_le32(stream_id); + + return virtio_video_queue_cmd_buffer(vvd, vbuf); +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_cmd_stream_drain(struct virtio_video_device *vvd, + uint32_t stream_id) +{ + struct virtio_video_stream_drain *req_p; + struct virtio_video_vbuffer *vbuf; + + req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_STREAM_DRAIN); + req_p->hdr.stream_id = cpu_to_le32(stream_id); + + return virtio_video_queue_cmd_buffer(vvd, vbuf); +} + +int virtio_video_cmd_resource_attach(struct virtio_video_device *vvd, + uint32_t stream_id, uint32_t resource_id, + enum virtio_video_queue_type queue_type, + void *buf, size_t buf_size) +{ + struct virtio_video_resource_attach *req_p; + struct virtio_video_vbuffer *vbuf; + + req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->cmd_type = cpu_to_le32(VIRTIO_VIDEO_CMD_RESOURCE_ATTACH); + req_p->stream_id = cpu_to_le32(stream_id); + req_p->queue_type = cpu_to_le32(queue_type); + req_p->resource_id = cpu_to_le32(resource_id); + + vbuf->data_buf = buf; + vbuf->data_size = buf_size; + + return virtio_video_queue_cmd_buffer(vvd, vbuf); +} + +int virtio_video_cmd_queue_detach_resources(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum virtio_video_queue_type queue_type) +{ + int ret; + struct virtio_video_queue_detach_resources *req_p; + struct virtio_video_vbuffer *vbuf; + + req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->cmd_type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUEUE_DETACH_RESOURCES); + req_p->stream_id = cpu_to_le32(stream->stream_id); + req_p->queue_type = cpu_to_le32(queue_type); + + ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); + if (ret == -ETIMEDOUT) + v4l2_err(&vvd->v4l2_dev, + "timed out waiting for resource destruction for %s\n", + (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) ? + "OUTPUT" : "CAPTURE"); + return ret; +} + +static void +virtio_video_cmd_resource_queue_cb(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + uint32_t flags; + uint64_t timestamp; + struct virtio_video_buffer *virtio_vb = vbuf->priv; + struct virtio_video_resource_queue_resp *resp = + (struct virtio_video_resource_queue_resp *)vbuf->resp_buf; + + flags = le32_to_cpu(resp->flags); + timestamp = le64_to_cpu(resp->timestamp); + + virtio_video_buf_done(virtio_vb, flags, timestamp, resp->data_sizes); +} + +int virtio_video_cmd_resource_queue(struct virtio_video_device *vvd, + uint32_t stream_id, + struct virtio_video_buffer *virtio_vb, + uint32_t data_size[], + uint8_t num_data_size, + enum virtio_video_queue_type queue_type) +{ + uint8_t i; + struct virtio_video_resource_queue *req_p; + struct virtio_video_resource_queue_resp *resp_p; + struct virtio_video_vbuffer *vbuf; + size_t resp_size = sizeof(struct virtio_video_resource_queue_resp); + + req_p = virtio_video_alloc_req_resp(vvd, + &virtio_video_cmd_resource_queue_cb, + &vbuf, sizeof(*req_p), resp_size, + NULL); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->cmd_type = cpu_to_le32(VIRTIO_VIDEO_CMD_RESOURCE_QUEUE); + req_p->stream_id = cpu_to_le32(stream_id); + req_p->queue_type = cpu_to_le32(queue_type); + req_p->resource_id = cpu_to_le32(virtio_vb->resource_id); + req_p->flags = 0; + req_p->timestamp = + cpu_to_le64(virtio_vb->v4l2_m2m_vb.vb.vb2_buf.timestamp); + + for (i = 0; i < num_data_size; ++i) + req_p->data_sizes[i] = cpu_to_le32(data_size[i]); + + resp_p = (struct virtio_video_resource_queue_resp *)vbuf->resp_buf; + memset(resp_p, 0, sizeof(*resp_p)); + + vbuf->priv = virtio_vb; + + return virtio_video_queue_cmd_buffer(vvd, vbuf); +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_cmd_queue_clear(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum virtio_video_queue_type queue_type) +{ + int ret; + struct virtio_video_queue_clear *req_p; + struct virtio_video_vbuffer *vbuf; + + req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUEUE_CLEAR); + req_p->hdr.stream_id = cpu_to_le32(stream->stream_id); + req_p->queue_type = cpu_to_le32(queue_type); + + ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); + if (ret == -ETIMEDOUT) + v4l2_err(&vvd->v4l2_dev, + "timed out waiting for %s queue clear\n", + (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) ? + "OUTPUT" : "CAPTURE"); + return ret; +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_cmd_query_capability(struct virtio_video_device *vvd, + void *resp_buf, size_t resp_size, + enum virtio_video_queue_type queue_type) +{ + int ret; + struct virtio_video_query_capability *req_p; + struct virtio_video_vbuffer *vbuf; + + req_p = virtio_video_alloc_req_resp(vvd, NULL, &vbuf, sizeof(*req_p), + resp_size, resp_buf); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUERY_CAPABILITY); + req_p->queue_type = cpu_to_le32(queue_type); + + ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); + if (ret == -ETIMEDOUT) + v4l2_err(&vvd->v4l2_dev, + "timed out waiting for capabilities for %s\n", + (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) ? + "OUTPUT" : "CAPTURE"); + return ret; +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_query_control_level(struct virtio_video_device *vvd, + void *resp_buf, size_t resp_size, + enum virtio_video_format format) +{ + int ret; + struct virtio_video_query_control *req_p; + struct virtio_video_query_control_level *ctrl_l; + struct virtio_video_vbuffer *vbuf; + uint32_t req_size = 0; + + req_size = sizeof(struct virtio_video_query_control) + + sizeof(struct virtio_video_query_control_level); + + req_p = virtio_video_alloc_req_resp(vvd, NULL, &vbuf, req_size, + resp_size, resp_buf); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUERY_CONTROL); + req_p->control = cpu_to_le32(VIRTIO_VIDEO_CONTROL_LEVEL); + ctrl_l = (void *)((char *)req_p + + sizeof(struct virtio_video_query_control)); + ctrl_l->format = cpu_to_le32(format); + + ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); + if (ret == -ETIMEDOUT) + v4l2_err(&vvd->v4l2_dev, + "timed out waiting for level query\n"); + return ret; +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_query_control_profile(struct virtio_video_device *vvd, + void *resp_buf, size_t resp_size, + enum virtio_video_format format) +{ + int ret; + struct virtio_video_query_control *req_p; + struct virtio_video_query_control_profile *ctrl_p; + struct virtio_video_vbuffer *vbuf; + uint32_t req_size = 0; + + req_size = sizeof(struct virtio_video_query_control) + + sizeof(struct virtio_video_query_control_profile); + + req_p = virtio_video_alloc_req_resp(vvd, NULL, &vbuf, req_size, + resp_size, resp_buf); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUERY_CONTROL); + req_p->control = cpu_to_le32(VIRTIO_VIDEO_CONTROL_PROFILE); + ctrl_p = (void *)((char *)req_p + + sizeof(struct virtio_video_query_control)); + ctrl_p->format = cpu_to_le32(format); + + ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); + if (ret == -ETIMEDOUT) + v4l2_err(&vvd->v4l2_dev, + "timed out waiting for profile query\n"); + return ret; +} + +static void +virtio_video_cmd_get_params_cb(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + int i; + struct virtio_video_get_params_resp *resp = + (struct virtio_video_get_params_resp *)vbuf->resp_buf; + struct virtio_video_params *params = &resp->params; + struct virtio_video_stream *stream = vbuf->priv; + enum virtio_video_queue_type queue_type; + struct video_format_info *format_info; + + queue_type = le32_to_cpu(params->queue_type); + if (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) + format_info = &stream->in_info; + else + format_info = &stream->out_info; + + format_info->frame_rate = le32_to_cpu(params->frame_rate); + format_info->frame_width = le32_to_cpu(params->frame_width); + format_info->frame_height = le32_to_cpu(params->frame_height); + format_info->min_buffers = le32_to_cpu(params->min_buffers); + format_info->max_buffers = le32_to_cpu(params->max_buffers); + format_info->fourcc_format = + virtio_video_format_to_v4l2(le32_to_cpu(params->format)); + + format_info->crop.top = le32_to_cpu(params->crop.top); + format_info->crop.left = le32_to_cpu(params->crop.left); + format_info->crop.width = le32_to_cpu(params->crop.width); + format_info->crop.height = le32_to_cpu(params->crop.height); + + format_info->num_planes = le32_to_cpu(params->num_planes); + for (i = 0; i < le32_to_cpu(params->num_planes); i++) { + struct virtio_video_plane_format *plane_formats = + ¶ms->plane_formats[i]; + struct video_plane_format *plane_format = + &format_info->plane_format[i]; + + plane_format->plane_size = + le32_to_cpu(plane_formats->plane_size); + plane_format->stride = le32_to_cpu(plane_formats->stride); + } +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_cmd_get_params(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum virtio_video_queue_type queue_type) +{ + int ret; + struct virtio_video_get_params *req_p; + struct virtio_video_vbuffer *vbuf; + struct virtio_video_get_params_resp *resp_p; + size_t resp_size = sizeof(struct virtio_video_get_params_resp); + + req_p = virtio_video_alloc_req_resp(vvd, + &virtio_video_cmd_get_params_cb, + &vbuf, sizeof(*req_p), resp_size, + NULL); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_GET_PARAMS); + req_p->hdr.stream_id = cpu_to_le32(stream->stream_id); + req_p->queue_type = cpu_to_le32(queue_type); + + resp_p = (struct virtio_video_get_params_resp *)vbuf->resp_buf; + + vbuf->priv = stream; + + ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); + if (ret == -ETIMEDOUT) + v4l2_err(&vvd->v4l2_dev, + "timed out waiting for get_params\n"); + return ret; +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int +virtio_video_cmd_set_params(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + struct video_format_info *format_info, + enum virtio_video_queue_type queue_type) +{ + int i; + struct virtio_video_set_params *req_p; + struct virtio_video_vbuffer *vbuf; + + req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_SET_PARAMS); + req_p->hdr.stream_id = cpu_to_le32(stream->stream_id); + req_p->params.queue_type = cpu_to_le32(queue_type); + req_p->params.frame_rate = cpu_to_le32(format_info->frame_rate); + req_p->params.frame_width = cpu_to_le32(format_info->frame_width); + req_p->params.frame_height = cpu_to_le32(format_info->frame_height); + req_p->params.format = virtio_video_v4l2_format_to_virtio( + cpu_to_le32(format_info->fourcc_format)); + req_p->params.min_buffers = cpu_to_le32(format_info->min_buffers); + req_p->params.max_buffers = cpu_to_le32(format_info->max_buffers); + req_p->params.num_planes = cpu_to_le32(format_info->num_planes); + + for (i = 0; i < format_info->num_planes; i++) { + struct virtio_video_plane_format *plane_formats = + &req_p->params.plane_formats[i]; + struct video_plane_format *plane_format = + &format_info->plane_format[i]; + plane_formats->plane_size = + cpu_to_le32(plane_format->plane_size); + plane_formats->stride = cpu_to_le32(plane_format->stride); + } + + return virtio_video_queue_cmd_buffer(vvd, vbuf); +} + +static void +virtio_video_cmd_get_ctrl_profile_cb(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + struct virtio_video_get_control_resp *resp = + (struct virtio_video_get_control_resp *)vbuf->resp_buf; + struct virtio_video_control_val_profile *resp_p = NULL; + struct virtio_video_stream *stream = vbuf->priv; + struct video_control_info *control = &stream->control; + + resp_p = (void *)((char *)resp + + sizeof(struct virtio_video_get_control_resp)); + + control->profile = le32_to_cpu(resp_p->profile); +} + +static void +virtio_video_cmd_get_ctrl_level_cb(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + struct virtio_video_get_control_resp *resp = + (struct virtio_video_get_control_resp *)vbuf->resp_buf; + struct virtio_video_control_val_level *resp_p; + struct virtio_video_stream *stream = vbuf->priv; + struct video_control_info *control = &stream->control; + + resp_p = (void *)((char *)resp + + sizeof(struct virtio_video_get_control_resp)); + + control->level = le32_to_cpu(resp_p->level); +} + +static void +virtio_video_cmd_get_ctrl_bitrate_cb(struct virtio_video_device *vvd, + struct virtio_video_vbuffer *vbuf) +{ + struct virtio_video_get_control_resp *resp = + (struct virtio_video_get_control_resp *)vbuf->resp_buf; + struct virtio_video_control_val_bitrate *resp_p = NULL; + struct virtio_video_stream *stream = vbuf->priv; + struct video_control_info *control = &stream->control; + + resp_p = (void *)((char *) resp + + sizeof(struct virtio_video_get_control_resp)); + + control->bitrate = le32_to_cpu(resp_p->bitrate); +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_cmd_get_control(struct virtio_video_device *vvd, + struct virtio_video_stream *stream, + enum virtio_video_control_type control) +{ + int ret; + struct virtio_video_get_control *req_p; + struct virtio_video_get_control_resp *resp_p; + struct virtio_video_vbuffer *vbuf; + size_t resp_size = sizeof(struct virtio_video_get_control_resp); + virtio_video_resp_cb cb; + + switch (control) { + case VIRTIO_VIDEO_CONTROL_PROFILE: + resp_size += sizeof(struct virtio_video_control_val_profile); + cb = &virtio_video_cmd_get_ctrl_profile_cb; + break; + case VIRTIO_VIDEO_CONTROL_LEVEL: + resp_size += sizeof(struct virtio_video_control_val_level); + cb = &virtio_video_cmd_get_ctrl_level_cb; + break; + case VIRTIO_VIDEO_CONTROL_BITRATE: + resp_size += sizeof(struct virtio_video_control_val_bitrate); + cb = &virtio_video_cmd_get_ctrl_bitrate_cb; + break; + default: + return -EINVAL; + } + + req_p = virtio_video_alloc_req_resp(vvd, cb, &vbuf, + sizeof(*req_p), resp_size, NULL); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_GET_CONTROL); + req_p->hdr.stream_id = cpu_to_le32(stream->stream_id); + req_p->control = cpu_to_le32(control); + + resp_p = (struct virtio_video_get_control_resp *)vbuf->resp_buf; + + vbuf->priv = stream; + + ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); + if (ret == -ETIMEDOUT) + v4l2_err(&vvd->v4l2_dev, + "timed out waiting for get_control\n"); + return ret; +} + +// TODO: replace virtio_video_cmd_hdr accoring to specification v4 +int virtio_video_cmd_set_control(struct virtio_video_device *vvd, + uint32_t stream_id, + enum virtio_video_control_type control, + uint32_t value) +{ + struct virtio_video_set_control *req_p; + struct virtio_video_vbuffer *vbuf; + struct virtio_video_control_val_level *ctrl_l; + struct virtio_video_control_val_profile *ctrl_p; + struct virtio_video_control_val_bitrate *ctrl_b; + size_t size; + + if (value == 0) + return -EINVAL; + + switch (control) { + case VIRTIO_VIDEO_CONTROL_PROFILE: + size = sizeof(struct virtio_video_control_val_profile); + break; + case VIRTIO_VIDEO_CONTROL_LEVEL: + size = sizeof(struct virtio_video_control_val_level); + break; + case VIRTIO_VIDEO_CONTROL_BITRATE: + size = sizeof(struct virtio_video_control_val_bitrate); + break; + default: + return -EINVAL; + } + + req_p = virtio_video_alloc_req(vvd, &vbuf, size + sizeof(*req_p)); + if (IS_ERR(req_p)) + return PTR_ERR(req_p); + + req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_SET_CONTROL); + req_p->hdr.stream_id = cpu_to_le32(stream_id); + req_p->control = cpu_to_le32(control); + + switch (control) { + case VIRTIO_VIDEO_CONTROL_PROFILE: + ctrl_p = (void *)((char *)req_p + + sizeof(struct virtio_video_set_control)); + ctrl_p->profile = cpu_to_le32(value); + break; + case VIRTIO_VIDEO_CONTROL_LEVEL: + ctrl_l = (void *)((char *)req_p + + sizeof(struct virtio_video_set_control)); + ctrl_l->level = cpu_to_le32(value); + break; + case VIRTIO_VIDEO_CONTROL_BITRATE: + ctrl_b = (void *)((char *)req_p + + sizeof(struct virtio_video_set_control)); + ctrl_b->bitrate = cpu_to_le32(value); + break; + } + + return virtio_video_queue_cmd_buffer(vvd, vbuf); +} + diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/kernel-module-virtio-video.bb b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/kernel-module-virtio-video.bb new file mode 100644 index 00000000..baaa9691 --- /dev/null +++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/kernel-module-virtio-video.bb @@ -0,0 +1,12 @@ +SUMMARY = "VIRTIO video device driver" + +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://virtio_video.h;endline=17;md5=3a04f9e12610bad07c6ec369a8077ba6" + +inherit module + +SRC_URI = "file://." + +S = "${WORKDIR}" + +KERNEL_MODULE_AUTOLOAD = "virtio_video" diff --git a/meta-egvirt/recipes-kernel/linux/linux-yocto/0001-drivers-media-Add-config-option-for-virtio-video.patch b/meta-egvirt/recipes-kernel/linux/linux-yocto/0001-drivers-media-Add-config-option-for-virtio-video.patch new file mode 100644 index 00000000..ede061e6 --- /dev/null +++ b/meta-egvirt/recipes-kernel/linux/linux-yocto/0001-drivers-media-Add-config-option-for-virtio-video.patch @@ -0,0 +1,33 @@ +From 162f03095b94fe1580653c96094e50ba827f4c0f Mon Sep 17 00:00:00 2001 +From: Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com> +Date: Thu, 3 Jun 2021 07:44:30 +0200 +Subject: [PATCH] drivers/media: Add config option for virtio video. + +virtio video driver is implemented as out of tree kernel module, but it +depends on some of kernel functionality which can not be enabled +explicitly via config (e.g. VIDEOBUF2_DMA_CONTIG). +--- + drivers/media/Kconfig | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig +index a6d073f2e036..a6146edb0ea3 100644 +--- a/drivers/media/Kconfig ++++ b/drivers/media/Kconfig +@@ -235,6 +235,16 @@ endif + + source "drivers/media/firewire/Kconfig" + ++config VIDEO_VIRTIO ++ tristate "Virtio video driver" ++ depends on VIDEO_V4L2 ++ select MEDIA_CONTROLLER ++ select VIDEOBUF2_DMA_CONTIG ++ select VIDEOBUF2_DMA_SG ++ select V4L2_MEM2MEM_DEV ++ help ++ This is the virtual video driver for virtio. Say Y or M. ++ + endmenu + + # diff --git a/meta-egvirt/recipes-kernel/linux/linux-yocto/virtio_video.cfg b/meta-egvirt/recipes-kernel/linux/linux-yocto/virtio_video.cfg new file mode 100644 index 00000000..252bcfeb --- /dev/null +++ b/meta-egvirt/recipes-kernel/linux/linux-yocto/virtio_video.cfg @@ -0,0 +1,160 @@ +CONFIG_FRAME_VECTOR=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_SUPPORT_FILTER=y +# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set + +# +# Media device types +# +# CONFIG_MEDIA_CAMERA_SUPPORT is not set +# CONFIG_MEDIA_ANALOG_TV_SUPPORT is not set +# CONFIG_MEDIA_DIGITAL_TV_SUPPORT is not set +# CONFIG_MEDIA_RADIO_SUPPORT is not set +# CONFIG_MEDIA_SDR_SUPPORT is not set +CONFIG_MEDIA_PLATFORM_SUPPORT=y +# CONFIG_MEDIA_TEST_SUPPORT is not set +# end of Media device types + +CONFIG_VIDEO_DEV=y +CONFIG_MEDIA_CONTROLLER=y + +# +# Video4Linux options +# +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L2_I2C=y +# CONFIG_VIDEO_V4L2_SUBDEV_API is not set +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +CONFIG_V4L2_MEM2MEM_DEV=y +# end of Video4Linux options + +# +# Media controller options +# +# end of Media controller options + +# +# Media drivers +# + +# +# Drivers filtered as selected at 'Filter media drivers' +# +CONFIG_VIDEOBUF2_CORE=y +CONFIG_VIDEOBUF2_V4L2=y +CONFIG_VIDEOBUF2_MEMOPS=y +CONFIG_VIDEOBUF2_DMA_CONTIG=y +CONFIG_VIDEOBUF2_DMA_SG=y +# CONFIG_V4L_PLATFORM_DRIVERS is not set +# CONFIG_V4L_MEM2MEM_DRIVERS is not set +CONFIG_VIDEO_VIRTIO=y +# end of Media drivers + +# +# Media ancillary drivers +# + +# +# Audio decoders, processors and mixers +# +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_CS3308 is not set +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_UDA1342 is not set +# CONFIG_VIDEO_WM8775 is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_VP27SMPX is not set +# CONFIG_VIDEO_SONY_BTF_MPX is not set +# end of Audio decoders, processors and mixers + +# +# RDS decoders +# +# CONFIG_VIDEO_SAA6588 is not set +# end of RDS decoders + +# +# Video decoders +# +# CONFIG_VIDEO_ADV7183 is not set +# CONFIG_VIDEO_ADV748X is not set +# CONFIG_VIDEO_ADV7842 is not set +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_ML86V7667 is not set +# CONFIG_VIDEO_SAA7110 is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_TC358743 is not set +# CONFIG_VIDEO_TVP514X is not set +# CONFIG_VIDEO_TVP5150 is not set +# CONFIG_VIDEO_TVP7002 is not set +# CONFIG_VIDEO_TW2804 is not set +# CONFIG_VIDEO_TW9903 is not set +# CONFIG_VIDEO_TW9906 is not set +# CONFIG_VIDEO_TW9910 is not set +# CONFIG_VIDEO_VPX3220 is not set + +# +# Video and audio decoders +# +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_CX25840 is not set +# end of Video decoders + +# +# Video encoders +# +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set +# CONFIG_VIDEO_ADV7343 is not set +# CONFIG_VIDEO_ADV7393 is not set +# CONFIG_VIDEO_ADV7511 is not set +# CONFIG_VIDEO_AD9389B is not set +# CONFIG_VIDEO_AK881X is not set +# CONFIG_VIDEO_THS8200 is not set +# end of Video encoders + +# +# Video improvement chips +# +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# end of Video improvement chips + +# +# Audio/Video compression chips +# +# CONFIG_VIDEO_SAA6752HS is not set +# end of Audio/Video compression chips + +# +# SDR tuner chips +# +# end of SDR tuner chips + +# +# Miscellaneous helper chips +# +# CONFIG_VIDEO_THS7303 is not set +# CONFIG_VIDEO_M52790 is not set +# CONFIG_VIDEO_I2C is not set +# CONFIG_VIDEO_ST_MIPID02 is not set +# end of Miscellaneous helper chips + +# +# SPI helper chips +# +# end of SPI helper chips +# end of Media ancillary drivers diff --git a/meta-egvirt/recipes-kernel/linux/linux-yocto_%.bbappend b/meta-egvirt/recipes-kernel/linux/linux-yocto_%.bbappend index aaaff0b6..4401de95 100644 --- a/meta-egvirt/recipes-kernel/linux/linux-yocto_%.bbappend +++ b/meta-egvirt/recipes-kernel/linux/linux-yocto_%.bbappend @@ -32,3 +32,9 @@ SRC_URI += " \ file://iio-scmi/0003-iio-scmi-Add-reading-raw-attribute.patch \ file://iio_scmi.cfg \ " + +# virtio video +SRC_URI += " \ + file://0001-drivers-media-Add-config-option-for-virtio-video.patch \ + file://virtio_video.cfg \ +" |