new file mode 100644
@@ -0,0 +1,5 @@
+{
+ "description": "QEMU vhost-user-video",
+ "type": "video",
+ "binary": "@libexecdir@/vhost-user-video"
+}
new file mode 100644
@@ -0,0 +1,10 @@
+executable('vhost-user-video', files(
+ 'vhost-user-video.c', 'v4l2_backend.c', 'virtio_video_helpers.c'),
+ dependencies: [qemuutil, glib, gio, vhost_user],
+ install: true,
+ install_dir: get_option('libexecdir'))
+
+configure_file(input: '50-qemu-video.json.in',
+ output: '50-qemu-video.json',
+ configuration: config_host,
+ install_dir: qemu_datadir / 'vhost-user')
new file mode 100644
@@ -0,0 +1,1752 @@
+/*
+ * virtio-video video v4l2 backend
+ *
+ * The purpose of this backend is to interface with
+ * v4l2 stateful encoder and decoder devices in the kernel.
+ *
+ * v4l2 stateless devices are NOT supported currently.
+ *
+ * Some v4l2 helper functions taken from yatva
+ *
+ * Copyright (c) 2023 Red Hat, Inc.
+ * Copyright (c) 2021 Linaro Ltd
+ * Copyright (C) 2005-2010 Laurent Pinchart
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include <linux/videodev2.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+
+#include <unistd.h>
+#include "virtio_video_helpers.h"
+#include "v4l2_backend.h"
+#include "standard-headers/linux/virtio_video.h"
+#include "vuvideo.h"
+
+#define V4L2_TYPE_IS_META(type) \
+ ((type) == V4L2_BUF_TYPE_META_CAPTURE \
+ || (type) == V4L2_BUF_TYPE_META_OUTPUT)
+
+/* Function prototypes */
+static const struct v4l2_format_info *
+v4l2_format_by_fourcc(unsigned int fourcc);
+static const char *v4l2_format_name(unsigned int fourcc);
+static const char *v4l2_buf_type_name(enum v4l2_buf_type type);
+static const char *v4l2_field_name(enum v4l2_field field);
+
+static int video_enum_frame_intervals(struct v4l2_device *dev,
+ __u32 pixelformat,
+ unsigned int width, unsigned int height,
+ GList **p_vid_fmt_frm_rate_l);
+
+static int video_enum_frame_sizes(struct v4l2_device *dev, __u32 pixelformat,
+ GList **p_vid_fmt_frm_l);
+static int video_querycap(struct v4l2_device *dev);
+static GByteArray *iterate_frame_rate_list(GByteArray *resp,
+ GList *frm_rate_l);
+static GByteArray *iterate_format_frame_list(GByteArray *resp,
+ GList *fmt_frm_l);
+static GByteArray *iterate_format_desc_list(GByteArray *resp,
+ GList *fmt_desc_l);
+static void convert_to_timeval(uint64_t timestamp, struct timeval *t);
+
+/* v4l2 to str tables & helpers taken from yavta to make prettier logs */
+static struct v4l2_format_info {
+ const char *name;
+ unsigned int fourcc;
+ unsigned char n_planes;
+} pixel_formats[] = {
+ { "RGB332", V4L2_PIX_FMT_RGB332, 1 },
+ { "RGB444", V4L2_PIX_FMT_RGB444, 1 },
+ { "ARGB444", V4L2_PIX_FMT_ARGB444, 1 },
+ { "XRGB444", V4L2_PIX_FMT_XRGB444, 1 },
+ { "RGB555", V4L2_PIX_FMT_RGB555, 1 },
+ { "ARGB555", V4L2_PIX_FMT_ARGB555, 1 },
+ { "XRGB555", V4L2_PIX_FMT_XRGB555, 1 },
+ { "RGB565", V4L2_PIX_FMT_RGB565, 1 },
+ { "RGB555X", V4L2_PIX_FMT_RGB555X, 1 },
+ { "RGB565X", V4L2_PIX_FMT_RGB565X, 1 },
+ { "BGR666", V4L2_PIX_FMT_BGR666, 1 },
+ { "BGR24", V4L2_PIX_FMT_BGR24, 1 },
+ { "RGB24", V4L2_PIX_FMT_RGB24, 1 },
+ { "BGR32", V4L2_PIX_FMT_BGR32, 1 },
+ { "ABGR32", V4L2_PIX_FMT_ABGR32, 1 },
+ { "XBGR32", V4L2_PIX_FMT_XBGR32, 1 },
+ { "RGB32", V4L2_PIX_FMT_RGB32, 1 },
+ { "ARGB32", V4L2_PIX_FMT_ARGB32, 1 },
+ { "XRGB32", V4L2_PIX_FMT_XRGB32, 1 },
+ { "HSV24", V4L2_PIX_FMT_HSV24, 1 },
+ { "HSV32", V4L2_PIX_FMT_HSV32, 1 },
+ { "Y8", V4L2_PIX_FMT_GREY, 1 },
+ { "Y10", V4L2_PIX_FMT_Y10, 1 },
+ { "Y12", V4L2_PIX_FMT_Y12, 1 },
+ { "Y16", V4L2_PIX_FMT_Y16, 1 },
+ { "UYVY", V4L2_PIX_FMT_UYVY, 1 },
+ { "VYUY", V4L2_PIX_FMT_VYUY, 1 },
+ { "YUYV", V4L2_PIX_FMT_YUYV, 1 },
+ { "YVYU", V4L2_PIX_FMT_YVYU, 1 },
+ { "NV12", V4L2_PIX_FMT_NV12, 1 },
+ { "NV12M", V4L2_PIX_FMT_NV12M, 2 },
+ { "NV21", V4L2_PIX_FMT_NV21, 1 },
+ { "NV21M", V4L2_PIX_FMT_NV21M, 2 },
+ { "NV16", V4L2_PIX_FMT_NV16, 1 },
+ { "NV16M", V4L2_PIX_FMT_NV16M, 2 },
+ { "NV61", V4L2_PIX_FMT_NV61, 1 },
+ { "NV61M", V4L2_PIX_FMT_NV61M, 2 },
+ { "NV24", V4L2_PIX_FMT_NV24, 1 },
+ { "NV42", V4L2_PIX_FMT_NV42, 1 },
+ { "YU12", V4L2_PIX_FMT_YVU420, 1},
+ { "YUV420M", V4L2_PIX_FMT_YUV420M, 3 },
+ { "YUV422M", V4L2_PIX_FMT_YUV422M, 3 },
+ { "YUV444M", V4L2_PIX_FMT_YUV444M, 3 },
+ { "YVU420M", V4L2_PIX_FMT_YVU420M, 3 },
+ { "YVU422M", V4L2_PIX_FMT_YVU422M, 3 },
+ { "YVU444M", V4L2_PIX_FMT_YVU444M, 3 },
+ { "SBGGR8", V4L2_PIX_FMT_SBGGR8, 1 },
+ { "SGBRG8", V4L2_PIX_FMT_SGBRG8, 1 },
+ { "SGRBG8", V4L2_PIX_FMT_SGRBG8, 1 },
+ { "SRGGB8", V4L2_PIX_FMT_SRGGB8, 1 },
+ { "SBGGR10_DPCM8", V4L2_PIX_FMT_SBGGR10DPCM8, 1 },
+ { "SGBRG10_DPCM8", V4L2_PIX_FMT_SGBRG10DPCM8, 1 },
+ { "SGRBG10_DPCM8", V4L2_PIX_FMT_SGRBG10DPCM8, 1 },
+ { "SRGGB10_DPCM8", V4L2_PIX_FMT_SRGGB10DPCM8, 1 },
+ { "SBGGR10", V4L2_PIX_FMT_SBGGR10, 1 },
+ { "SGBRG10", V4L2_PIX_FMT_SGBRG10, 1 },
+ { "SGRBG10", V4L2_PIX_FMT_SGRBG10, 1 },
+ { "SRGGB10", V4L2_PIX_FMT_SRGGB10, 1 },
+ { "SBGGR10P", V4L2_PIX_FMT_SBGGR10P, 1 },
+ { "SGBRG10P", V4L2_PIX_FMT_SGBRG10P, 1 },
+ { "SGRBG10P", V4L2_PIX_FMT_SGRBG10P, 1 },
+ { "SRGGB10P", V4L2_PIX_FMT_SRGGB10P, 1 },
+ { "SBGGR12", V4L2_PIX_FMT_SBGGR12, 1 },
+ { "SGBRG12", V4L2_PIX_FMT_SGBRG12, 1 },
+ { "SGRBG12", V4L2_PIX_FMT_SGRBG12, 1 },
+ { "SRGGB12", V4L2_PIX_FMT_SRGGB12, 1 },
+ { "IPU3_SBGGR10", V4L2_PIX_FMT_IPU3_SBGGR10, 1 },
+ { "IPU3_SGBRG10", V4L2_PIX_FMT_IPU3_SGBRG10, 1 },
+ { "IPU3_SGRBG10", V4L2_PIX_FMT_IPU3_SGRBG10, 1 },
+ { "IPU3_SRGGB10", V4L2_PIX_FMT_IPU3_SRGGB10, 1 },
+ { "DV", V4L2_PIX_FMT_DV, 1 },
+ { "MJPEG", V4L2_PIX_FMT_MJPEG, 1 },
+ { "MPEG", V4L2_PIX_FMT_MPEG, 1 },
+ { "FWHT", V4L2_PIX_FMT_FWHT, 1 },
+};
+
+static const struct v4l2_format_info *v4l2_format_by_fourcc(unsigned int fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(pixel_formats); ++i) {
+ if (pixel_formats[i].fourcc == fourcc) {
+ return &pixel_formats[i];
+ }
+ }
+
+ return NULL;
+}
+
+static const char *v4l2_format_name(unsigned int fourcc)
+{
+ const struct v4l2_format_info *info;
+ static char name[5];
+ unsigned int i;
+
+ info = v4l2_format_by_fourcc(fourcc);
+ if (info) {
+ return info->name;
+ }
+
+ for (i = 0; i < 4; ++i) {
+ name[i] = fourcc & 0xff;
+ fourcc >>= 8;
+ }
+
+ name[4] = '\0';
+ return name;
+}
+
+static struct {
+ enum v4l2_buf_type type;
+ bool supported;
+ const char *name;
+} buf_types_array[] = {
+ { V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, 1, "Video capture mplanes", },
+ { V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, 1, "Video output mplanes", },
+ { V4L2_BUF_TYPE_VIDEO_CAPTURE, 1, "Video capture", },
+ { V4L2_BUF_TYPE_VIDEO_OUTPUT, 1, "Video output", },
+ { V4L2_BUF_TYPE_VIDEO_OVERLAY, 0, "Video overlay", },
+ { V4L2_BUF_TYPE_META_CAPTURE, 0, "Meta-data capture", },
+ { V4L2_BUF_TYPE_META_OUTPUT, 0, "Meta-data output", },
+};
+
+static const char *v4l2_buf_type_name(enum v4l2_buf_type type)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(buf_types_array); ++i) {
+ if (buf_types_array[i].type == type) {
+ return buf_types_array[i].name;
+ }
+ }
+
+ if (type & V4L2_BUF_TYPE_PRIVATE) {
+ return "Private";
+ } else {
+ return "Unknown";
+ }
+}
+
+static const struct {
+ const char *name;
+ enum v4l2_field field;
+} fields[] = {
+ { "any", V4L2_FIELD_ANY },
+ { "none", V4L2_FIELD_NONE },
+ { "top", V4L2_FIELD_TOP },
+ { "bottom", V4L2_FIELD_BOTTOM },
+ { "interlaced", V4L2_FIELD_INTERLACED },
+ { "seq-tb", V4L2_FIELD_SEQ_TB },
+ { "seq-bt", V4L2_FIELD_SEQ_BT },
+ { "alternate", V4L2_FIELD_ALTERNATE },
+ { "interlaced-tb", V4L2_FIELD_INTERLACED_TB },
+ { "interlaced-bt", V4L2_FIELD_INTERLACED_BT },
+};
+
+static const char *v4l2_field_name(enum v4l2_field field)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(fields); ++i) {
+ if (fields[i].field == field) {
+ return fields[i].name;
+ }
+ }
+
+ return "unknown";
+}
+
+int v4l2_open(const gchar *devname)
+{
+ int fd;
+
+ if (!devname) {
+ return -EINVAL;
+ }
+
+ fd = open(devname, O_RDWR | O_NONBLOCK);
+ if (fd < 0) {
+ g_printerr("Error opening device %s: %s (%d)\n", devname,
+ g_strerror(errno), errno);
+ return fd;
+ }
+
+ g_print("Device %s opened fd(%d).\n", devname, fd);
+
+ return fd;
+}
+
+int v4l2_close(int fd)
+{
+ int ret;
+ ret = close(fd);
+
+ if (ret < 0) {
+ g_printerr("Error closing device: %s (%d)\n", g_strerror(errno), errno);
+ }
+ return ret;
+}
+
+static int video_enum_frame_intervals(struct v4l2_device *dev,
+ __u32 pixelformat,
+ unsigned int width, unsigned int height,
+ GList **p_vid_fmt_frm_rate_l)
+{
+ struct v4l2_frmivalenum ival;
+ GList *vid_fmt_frm_rate_l = NULL;
+ struct video_format_frame_rates *fmt_frm_rate;
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; ; ++i) {
+ memset(&ival, 0, sizeof ival);
+ ival.index = i;
+ ival.pixel_format = pixelformat;
+ ival.width = width;
+ ival.height = height;
+ ret = ioctl(dev->fd, VIDIOC_ENUM_FRAMEINTERVALS, &ival);
+ if (ret < 0) {
+ if (errno == EINVAL) /* EINVAL means no more frame intervals */
+ ret = 0;
+ else
+ g_printerr("VIDIOC_ENUM_FRAMEINTERVALS failed: %s (%d)\n",
+ g_strerror(errno), errno);
+ break;
+ }
+
+ /* driver sanity checks */
+ if (i != ival.index)
+ g_printerr("Warning: driver returned wrong ival index "
+ "%u.\n", ival.index);
+ if (pixelformat != ival.pixel_format)
+ g_printerr("Warning: driver returned wrong ival pixel "
+ "format %08x.\n", ival.pixel_format);
+ if (width != ival.width)
+ g_printerr("Warning: driver returned wrong ival width "
+ "%u.\n", ival.width);
+ if (height != ival.height)
+ g_printerr("Warning: driver returned wrong ival height "
+ "%u.\n", ival.height);
+
+ if (i != 0) {
+ g_print(", ");
+ }
+
+ /* allocate video_format_frame */
+ fmt_frm_rate = g_new0(struct video_format_frame_rates, 1);
+ /* keep a copy of v4l2 frmsizeenum struct */
+ memcpy(&fmt_frm_rate->v4l_ival, &ival,
+ sizeof(struct v4l2_frmivalenum));
+ vid_fmt_frm_rate_l =
+ g_list_append(vid_fmt_frm_rate_l, fmt_frm_rate);
+
+ switch (ival.type) {
+ case V4L2_FRMIVAL_TYPE_DISCRETE:
+ g_debug("%u/%u",
+ ival.discrete.numerator,
+ ival.discrete.denominator);
+
+ fmt_frm_rate->frame_rates.min = ival.discrete.denominator;
+
+ break;
+
+ case V4L2_FRMIVAL_TYPE_CONTINUOUS:
+ g_debug("%u/%u - %u/%u",
+ ival.stepwise.min.numerator,
+ ival.stepwise.min.denominator,
+ ival.stepwise.max.numerator,
+ ival.stepwise.max.denominator);
+
+ fmt_frm_rate->frame_rates.min = ival.stepwise.min.denominator;
+ fmt_frm_rate->frame_rates.max = ival.stepwise.max.denominator;
+ fmt_frm_rate->frame_rates.step = 1;
+
+ goto out;
+
+ case V4L2_FRMIVAL_TYPE_STEPWISE:
+ g_debug("%u/%u - %u/%u (by %u/%u)",
+ ival.stepwise.min.numerator,
+ ival.stepwise.min.denominator,
+ ival.stepwise.max.numerator,
+ ival.stepwise.max.denominator,
+ ival.stepwise.step.numerator,
+ ival.stepwise.step.denominator);
+
+ fmt_frm_rate->frame_rates.min = ival.stepwise.min.denominator;
+ fmt_frm_rate->frame_rates.max = ival.stepwise.max.denominator;
+ fmt_frm_rate->frame_rates.step = ival.stepwise.step.denominator;
+
+ goto out;
+
+ default:
+ break;
+ }
+ }
+
+out:
+ if (ret == 0) {
+ g_print("\n%s: Enumerated %d frame intervals\n", __func__
+ , g_list_length(vid_fmt_frm_rate_l));
+ g_return_val_if_fail(i == g_list_length(vid_fmt_frm_rate_l), -EINVAL);
+ *p_vid_fmt_frm_rate_l = vid_fmt_frm_rate_l;
+ }
+
+ return ret;
+}
+
+static void video_frame_size_discrete(struct v4l2_device *dev,
+ struct video_format_frame *vid_frame,
+ struct v4l2_frmsizeenum *frame)
+{
+ g_debug("\tFrame size (D): %ux%u (", frame->discrete.width,
+ frame->discrete.height);
+
+ vid_frame->frame.width.min = htole32(frame->discrete.width);
+ vid_frame->frame.width.max = htole32(frame->discrete.width);
+ vid_frame->frame.height.min = htole32(frame->discrete.height);
+ vid_frame->frame.height.max = htole32(frame->discrete.height);
+
+ if (video_enum_frame_intervals(dev, frame->pixel_format,
+ frame->discrete.width,
+ frame->discrete.height,
+ &vid_frame->frm_rate_l) < 0) {
+ g_printerr("%s: video_enum_frame_intervals failed!", __func__);
+ }
+ g_debug(")");
+}
+
+static void video_frame_size_continuous(struct v4l2_device *dev,
+ struct video_format_frame *vid_frame,
+ struct v4l2_frmsizeenum *frame)
+{
+ g_debug("\tFrame size (C): %ux%u - %ux%u (",
+ frame->stepwise.min_width,
+ frame->stepwise.min_height,
+ frame->stepwise.max_width,
+ frame->stepwise.max_height);
+
+ vid_frame->frame.width.min = htole32(frame->stepwise.min_width);
+ vid_frame->frame.width.max = htole32(frame->stepwise.max_width);
+ vid_frame->frame.width.step = htole32(frame->stepwise.step_width);
+ vid_frame->frame.height.min = htole32(frame->stepwise.min_height);
+ vid_frame->frame.height.max = htole32(frame->stepwise.max_height);
+ vid_frame->frame.height.step = htole32(frame->stepwise.step_height);
+
+ /* driver sanity check */
+ if (frame->stepwise.step_height != 1 ||
+ frame->stepwise.step_width != 1) {
+ g_printerr("Warning: invalid step for continuous framesize");
+ }
+
+ if (video_enum_frame_intervals(dev, frame->pixel_format,
+ frame->stepwise.max_width,
+ frame->stepwise.max_height,
+ &vid_frame->frm_rate_l) < 0) {
+ g_printerr("%s: video_enum_frame_intervals failed!\n", __func__);
+ }
+
+ g_debug(")");
+}
+
+static void video_frame_size_stepwise(struct v4l2_device *dev,
+ struct video_format_frame *vid_frame,
+ struct v4l2_frmsizeenum *frame)
+{
+ g_debug("\tFrame size (S): %ux%u - %ux%u (by %ux%u) (",
+ frame->stepwise.min_width,
+ frame->stepwise.min_height,
+ frame->stepwise.max_width,
+ frame->stepwise.max_height,
+ frame->stepwise.step_width,
+ frame->stepwise.step_height);
+
+ vid_frame->frame.width.min = htole32(frame->stepwise.min_width);
+ vid_frame->frame.width.max = htole32(frame->stepwise.max_width);
+ vid_frame->frame.width.step = htole32(frame->stepwise.step_width);
+ vid_frame->frame.height.min = htole32(frame->stepwise.min_height);
+ vid_frame->frame.height.max = htole32(frame->stepwise.max_height);
+ vid_frame->frame.height.step = htole32(frame->stepwise.step_height);
+
+ if (video_enum_frame_intervals(dev, frame->pixel_format,
+ frame->stepwise.max_width,
+ frame->stepwise.max_height,
+ &vid_frame->frm_rate_l) < 0) {
+ g_printerr("%s: video_enum_frame_intervals failed!\n", __func__);
+ }
+
+ g_debug(")");
+}
+
+static int video_enum_frame_sizes(struct v4l2_device *dev,
+ __u32 pixelformat, GList **p_vid_fmt_frm_l)
+{
+ struct v4l2_frmsizeenum frame;
+ struct video_format_frame *vid_frame = NULL;
+ GList *vid_fmt_frm_l = NULL;
+ unsigned int i;
+ int ret;
+
+ if (!dev) {
+ return -EINVAL;
+ }
+
+ for (i = 0; ; ++i) {
+ memset(&frame, 0, sizeof frame);
+ frame.index = i;
+ frame.pixel_format = pixelformat;
+ ret = ioctl(dev->fd, VIDIOC_ENUM_FRAMESIZES, &frame);
+ if (ret < 0) {
+ if (errno == EINVAL) /* EINVAL means no more frame sizes */
+ ret = 0;
+ else
+ g_printerr("VIDIOC_ENUM_FRAMESIZES failed: %s (%d)\n",
+ g_strerror(errno), errno);
+ break;
+ }
+
+ /* driver sanity checks */
+ if (i != frame.index)
+ g_printerr("Warning: driver returned wrong frame index "
+ "%u.\n", frame.index);
+ if (pixelformat != frame.pixel_format)
+ g_printerr("Warning: driver returned wrong frame pixel "
+ "format %08x.\n", frame.pixel_format);
+
+ /* allocate video_format_frame */
+ vid_frame = g_new0(struct video_format_frame, 1);
+ /* keep a copy of v4l2 frmsizeenum struct */
+ memcpy(&vid_frame->v4l_framesize, &frame,
+ sizeof(struct v4l2_frmsizeenum));
+ vid_fmt_frm_l = g_list_append(vid_fmt_frm_l, vid_frame);
+
+ switch (frame.type) {
+ case V4L2_FRMSIZE_TYPE_DISCRETE:
+ video_frame_size_discrete(dev, vid_frame, &frame);
+ break;
+ case V4L2_FRMSIZE_TYPE_CONTINUOUS:
+ video_frame_size_continuous(dev, vid_frame, &frame);
+ break;
+ case V4L2_FRMSIZE_TYPE_STEPWISE:
+ video_frame_size_stepwise(dev, vid_frame, &frame);
+ break;
+ default:
+ break;
+ }
+ }
+ if (ret == 0) {
+ g_print("%s: Enumerated %d frame sizes and %d frame intervals\n",
+ __func__, g_list_length(vid_fmt_frm_l),
+ g_list_length(vid_frame->frm_rate_l));
+
+ vid_frame->frame.num_rates =
+ htole32(g_list_length(vid_frame->frm_rate_l));
+
+ g_return_val_if_fail(i == g_list_length(vid_fmt_frm_l), -EINVAL);
+ *p_vid_fmt_frm_l = vid_fmt_frm_l;
+ }
+
+ return ret;
+}
+
+int video_send_decoder_start_cmd(struct v4l2_device *dev)
+{
+ return v4l2_issue_cmd(dev->fd, /*cmd=*/V4L2_DEC_CMD_START, /*flags=*/0);
+}
+
+static int video_querycap(struct v4l2_device *dev)
+{
+ struct v4l2_capability cap;
+ unsigned int caps;
+ bool has_video;
+ bool has_meta;
+ bool has_capture;
+ bool has_output;
+ bool has_mplane;
+ int ret;
+
+ memset(&cap, 0, sizeof cap);
+ ret = ioctl(dev->fd, VIDIOC_QUERYCAP, &cap);
+ if (ret < 0) {
+ return 0;
+ }
+
+ caps = cap.capabilities & V4L2_CAP_DEVICE_CAPS ?
+ cap.device_caps :
+ cap.capabilities;
+
+ has_video = caps & (V4L2_CAP_VIDEO_CAPTURE_MPLANE |
+ V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_VIDEO_OUTPUT_MPLANE |
+ V4L2_CAP_VIDEO_OUTPUT);
+ has_meta = caps & (V4L2_CAP_META_CAPTURE |
+ V4L2_CAP_META_OUTPUT);
+ has_capture = caps & (V4L2_CAP_VIDEO_CAPTURE_MPLANE |
+ V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_META_CAPTURE);
+ has_output = caps & (V4L2_CAP_VIDEO_OUTPUT_MPLANE |
+ V4L2_CAP_VIDEO_OUTPUT |
+ V4L2_CAP_META_OUTPUT);
+ has_mplane = caps & (V4L2_CAP_VIDEO_CAPTURE_MPLANE |
+ V4L2_CAP_VIDEO_OUTPUT_MPLANE |
+ V4L2_CAP_VIDEO_M2M_MPLANE);
+
+ g_print("Device '%s' on '%s' (driver '%s') "
+ "supports%s%s%s%s %s mplanes.\n",
+ cap.card, cap.bus_info, cap.driver,
+ has_video ? " video," : "",
+ has_meta ? " meta-data," : "",
+ has_capture ? " capture," : "",
+ has_output ? " output," : "",
+ has_mplane ? "with" : "without");
+
+ dev->capabilities = caps;
+ dev->has_mplane = has_mplane;
+
+ return 0;
+}
+
+void v4l2_set_device_type(struct v4l2_device *dev, enum v4l2_buf_type type,
+ struct v4l2_fmtdesc *fmt_desc)
+{
+ if (fmt_desc->flags & V4L2_FMT_FLAG_COMPRESSED) {
+ switch (fmt_desc->pixelformat) {
+ case V4L2_PIX_FMT_H263:
+ case V4L2_PIX_FMT_H264:
+ case V4L2_PIX_FMT_H264_NO_SC:
+ case V4L2_PIX_FMT_H264_MVC:
+ case V4L2_PIX_FMT_MPEG1:
+ case V4L2_PIX_FMT_MPEG2:
+ case V4L2_PIX_FMT_MPEG4:
+ case V4L2_PIX_FMT_XVID:
+ case V4L2_PIX_FMT_VC1_ANNEX_G:
+ case V4L2_PIX_FMT_VC1_ANNEX_L:
+ case V4L2_PIX_FMT_VP8:
+ case V4L2_PIX_FMT_VP9:
+ case V4L2_PIX_FMT_HEVC:
+ case V4L2_PIX_FMT_FWHT:
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ dev->dev_type |= STATEFUL_DECODER;
+ }
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ dev->dev_type |= STATEFUL_DECODER;
+ }
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ dev->dev_type |= STATEFUL_ENCODER;
+ }
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ dev->dev_type |= STATEFUL_ENCODER;
+ }
+ break;
+ case V4L2_PIX_FMT_MPEG2_SLICE:
+ case V4L2_PIX_FMT_FWHT_STATELESS:
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ dev->dev_type |= STATELESS_DECODER;
+ }
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ dev->dev_type |= STATELESS_DECODER;
+ }
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ dev->dev_type |= STATELESS_ENCODER;
+ }
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ dev->dev_type |= STATELESS_ENCODER;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+enum v4l2_buf_type
+get_v4l2_buf_type(enum virtio_video_queue_type queue_type, bool has_mplane)
+{
+ enum v4l2_buf_type buf_type = 0;
+
+ switch (queue_type) {
+ case VIRTIO_VIDEO_QUEUE_TYPE_INPUT:
+ buf_type = has_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
+ : V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ break;
+ case VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT:
+ buf_type = has_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
+ : V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ break;
+ default:
+ g_warning("%s: Unknown queue_type!", __func__);
+ break;
+ }
+
+ g_debug("%s: queue_type(0x%x) has_mplane(%d), buf_type(%s)",
+ __func__, queue_type, has_mplane, v4l2_buf_type_name(buf_type));
+
+ return buf_type;
+}
+
+enum v4l2_memory get_v4l2_memory(enum virtio_video_mem_type mem_type)
+{
+ /* if using GUEST_PAGES queued using USERPTR mechanism */
+ enum v4l2_memory memory = V4L2_MEMORY_USERPTR;
+ if (mem_type == VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT) {
+ memory = V4L2_MEMORY_DMABUF;
+ } else {
+ assert(mem_type == VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES);
+ }
+ g_debug("%s: mem_type(0x%x) memory(0x%x)", __func__, mem_type, memory);
+ return memory;
+}
+
+enum virtio_video_mem_type
+get_queue_mem_type(struct stream *s,
+ enum virtio_video_queue_type queue_type)
+{
+ if (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) {
+ return s->vio_stream.in_mem_type;
+ } else {
+ return s->vio_stream.out_mem_type;
+ }
+}
+
+int video_free_buffers(int fd, enum v4l2_buf_type type, enum v4l2_memory memory)
+{
+ int ret, count = 0;
+ g_debug("%s: v4l2_buf_type: %s: Issuing REQBUFS 0"
+ , __func__, v4l2_buf_type_name(type));
+
+ /*
+ * Applications can call ioctl VIDIOC_REQBUFS again to change the number
+ * of buffers. Note that if any buffers are still mapped or exported via
+ * DMABUF, then ioctl VIDIOC_REQBUFS can only succeed if the
+ * V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS capability is set. Otherwise ioctl
+ * VIDIOC_REQBUFS will return the EBUSY error code. If
+ * V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS is set, then these buffers are
+ * orphanedand will be freed when they are unmapped or when the exported
+ * DMABUF fds are closed. A count value of zero frees or orphans all
+ * buffers, after aborting or finishing any DMA in progress, an implicit
+ * VIDIOC_STREAMOFF.
+ */
+
+ /* TODO support V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS */
+ ret = v4l2_ioctl_reqbuf(fd, type, memory, &count);
+ return ret;
+}
+
+int video_resource_create(struct stream *s,
+ uint32_t queue_type, uint32_t queue_len)
+{
+ int ret, count;
+ enum v4l2_buf_type buf_type = get_v4l2_buf_type(queue_type, s->has_mplane);
+ enum virtio_video_mem_type mem_type = get_queue_mem_type(s, queue_type);
+
+ count = queue_len;
+ ret = v4l2_ioctl_reqbuf(s->fd, buf_type, get_v4l2_memory(mem_type), &count);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (V4L2_TYPE_IS_OUTPUT(buf_type)) {
+ s->output_bufcount = count;
+ } else if (V4L2_TYPE_IS_CAPTURE(buf_type)) {
+ s->capture_bufcount = count;
+ }
+
+ if (count > queue_len) {
+ g_critical("Unsupported feature: driver initiated more buffers(%d) "
+ "than requested(%d)", count, queue_len);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+int v4l2_streamon(struct stream *s, enum v4l2_buf_type type)
+{
+ int ret = 0;
+ ret = ioctl(s->fd, VIDIOC_STREAMON, &type);
+ if (ret < 0) {
+ g_printerr("VIDIOC_STREAMON failed: fd=(%d) buf type=%s: %s (%d)\n",
+ s->fd, v4l2_buf_type_name(type), g_strerror(errno), errno);
+ } else {
+ g_debug("%s: VIDIOC_STREAMON OK fd=(%d) buf type: %s",
+ __func__, s->fd, v4l2_buf_type_name(type));
+ if (V4L2_TYPE_IS_OUTPUT(type)) {
+ s->output_streaming = true;
+ }
+ if (V4L2_TYPE_IS_CAPTURE(type)) {
+ s->capture_streaming = true;
+ }
+ }
+ return ret;
+}
+
+int v4l2_streamoff(struct stream *s, enum v4l2_buf_type type)
+{
+ int ret = 0;
+ ret = ioctl(s->fd, VIDIOC_STREAMOFF, &type);
+ if (ret < 0) {
+ g_printerr("VIDIOC_STREAMOFF failed: fd=(%d) buf type=%s: %s (%d)\n",
+ s->fd, v4l2_buf_type_name(type), g_strerror(errno), errno);
+ } else {
+ g_debug("%s: VIDIOC_STREAMOFF OK buf type: %s",
+ __func__, v4l2_buf_type_name(type));
+
+ if (V4L2_TYPE_IS_OUTPUT(type)) {
+ s->output_streaming = false;
+ }
+ if (V4L2_TYPE_IS_CAPTURE(type)) {
+ s->capture_streaming = false;
+ }
+
+ /*
+ * if either queue has STREAMOFF applied, then we enter STOPPED
+ * Assumes that s->mutex is held by calling function
+ */
+ s->stream_state = STREAM_STOPPED;
+ g_cond_signal(&s->stream_cond);
+ }
+ return ret;
+}
+
+/* activate streaming on both queues */
+int video_streamon(struct stream *s,
+ struct v4l2_device *dev, enum v4l2_buf_type type)
+{
+ int ret = 0;
+ uint32_t id = 0;
+
+ if (!s->subscribed_events) {
+ /* subscribe for SOURCE_CHANGE event */
+ if (dev->sup_dyn_res_switching) {
+ ret = v4l2_ioctl_subscribe_event(
+ s->fd, V4L2_EVENT_SOURCE_CHANGE, id);
+ if (ret < 0) {
+ g_printerr("V4L2_EVENT_SOURCE_CHANGE failed: %s (%d)\n",
+ g_strerror(errno), errno);
+ }
+ }
+ /* subscribe for EOS event */
+ ret = v4l2_ioctl_subscribe_event(s->fd, V4L2_EVENT_EOS, id);
+ if (ret < 0) {
+ g_printerr("V4L2_EVENT_EOS failed: %s (%d)\n",
+ g_strerror(errno), errno);
+ }
+ s->subscribed_events = true;
+ }
+
+ switch (type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ case V4L2_BUF_TYPE_META_OUTPUT:
+ if (s->output_streaming == false) {
+ ret |= v4l2_streamon(s, type);
+ }
+ break;
+
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ if (s->capture_streaming == false) {
+ ret |= v4l2_streamon(s, type);
+ }
+ break;
+
+ default:
+ g_printerr("%s: unknown v4l2 buffer type!", __func__);
+ ret = EINVAL;
+ }
+
+ if (s->stream_state != STREAM_DRAINING) {
+ s->stream_state = STREAM_STREAMING;
+ g_cond_signal(&s->stream_cond);
+ }
+
+ return ret;
+}
+
+int video_streamoff(struct stream *s, enum v4l2_buf_type type)
+{
+ int ret = 0;
+ bool is_mplane = V4L2_TYPE_IS_MULTIPLANAR(type);
+ enum v4l2_buf_type type2;
+
+ switch (type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ case V4L2_BUF_TYPE_META_OUTPUT:
+ if (s->output_streaming == true) {
+ ret |= v4l2_streamoff(s, type);
+ }
+
+ if (s->capture_streaming == true) {
+ type2 = is_mplane ?
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
+ V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ ret |= v4l2_streamoff(s, type2);
+ }
+ break;
+
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ if (s->capture_streaming == true) {
+ ret |= v4l2_streamoff(s, type);
+ }
+ if (s->output_streaming == true) {
+ type2 = is_mplane ?
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE :
+ V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ ret |= v4l2_streamoff(s, type2);
+ }
+ break;
+
+ default:
+ g_printerr("%s: unknown v4l2 buffer type!", __func__);
+ ret = EINVAL;
+ }
+
+ return ret;
+}
+
+int v4l2_queue_buffer(enum v4l2_buf_type type,
+ enum v4l2_memory memory,
+ struct virtio_video_resource_queue *qcmd,
+ struct resource *res, struct stream *s,
+ struct v4l2_device *dev)
+{
+ struct v4l2_buffer vbuf;
+ int ret = 0;
+ int fd = s->fd;
+
+ memset(&vbuf, 0, sizeof(vbuf));
+ vbuf.index = res->v4l2_index;
+
+ vbuf.type = type;
+ vbuf.memory = memory;
+ vbuf.field = V4L2_FIELD_NONE;
+ vbuf.flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+
+ g_debug("%s: type=%s memory=%d index=%d", __func__,
+ v4l2_buf_type_name(type), memory, vbuf.index);
+
+ convert_to_timeval(le64toh(qcmd->timestamp), &vbuf.timestamp);
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+ /* for mplane length field is number of elements in planes array */
+ vbuf.length = res->vio_resource.num_planes;
+ vbuf.m.planes = g_malloc0(sizeof(struct v4l2_plane)
+ * res->vio_resource.num_planes);
+
+ for (int i = 0; i < vbuf.length; i++) {
+ vbuf.m.planes[i].m.userptr = (unsigned long)res->iov[i].iov_base;
+ vbuf.m.planes[i].length = (unsigned long)res->iov[i].iov_len;
+ }
+ } else if (res->iov != NULL) {
+ /* m is a union of userptr, *planes and fd */
+ vbuf.m.userptr = (unsigned long)res->iov[0].iov_base;
+ vbuf.length = res->iov[0].iov_len;
+ g_debug("%s: iov_base = 0x%p", __func__, res->iov[0].iov_base);
+ g_debug("%s: iov_len = 0x%lx", __func__, res->iov[0].iov_len);
+ }
+
+ if (V4L2_TYPE_IS_OUTPUT(type)) {
+ if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+ for (int i = 0; i < vbuf.length; i++) {
+ vbuf.m.planes[i].bytesused = vbuf.m.planes[i].length;
+ }
+ } else {
+ vbuf.bytesused = vbuf.length;
+ }
+ }
+
+ ret = ioctl(fd, VIDIOC_QBUF, &vbuf);
+ if (ret < 0) {
+ qcmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ g_printerr("Unable to QBUF: %s (%d).\n", g_strerror(errno), errno);
+ return ret;
+ }
+
+ ret = video_streamon(s, dev, type);
+ if (ret < 0) {
+ g_printerr("video_streamon failed (%d)", ret);
+ /* only print error, as video_streamon() does both queues */
+ }
+
+ res->queued = true;
+ if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+ g_free(vbuf.m.planes);
+ }
+
+ g_debug("%s: Queued resource-id(%d) buf_type=%s v4l2_index(%d) "
+ "virtio_queue(0x%x)", __func__, res->vio_resource.resource_id,
+ v4l2_buf_type_name(type), res->v4l2_index,
+ res->vio_resource.queue_type);
+
+ return ret;
+
+}
+
+int v4l2_dequeue_buffer(int fd, enum v4l2_buf_type type,
+ enum v4l2_memory memory,
+ struct stream *s)
+{
+ struct v4l2_buffer vbuf;
+ int ret = 0;
+ struct resource *r;
+ struct virtio_video_resource_queue_resp resp;
+ struct vu_video_ctrl_command *vio_cmd;
+
+ memset(&vbuf, 0, sizeof(vbuf));
+
+ vbuf.type = type;
+ vbuf.memory = memory;
+
+ vbuf.field = V4L2_FIELD_NONE;
+ vbuf.flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+ /* for mplane length field is number of elements in planes array */
+ vbuf.length = VIRTIO_VIDEO_MAX_PLANES;
+ vbuf.m.planes = g_malloc0(sizeof(struct v4l2_plane)
+ * VIRTIO_VIDEO_MAX_PLANES);
+
+ g_debug("%s: mplane allocating planes array", __func__);
+ }
+
+ ret = ioctl(fd, VIDIOC_DQBUF, &vbuf);
+ if (ret < 0) {
+ g_printerr("Unable to DQBUF: %s (%d).\n", g_strerror(errno), errno);
+ return -errno;
+ }
+
+ g_debug("%s: VIDIOC_DQBUF OK index(%d)", __func__, vbuf.index);
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+ g_free(vbuf.m.planes);
+ }
+
+ r = find_resource_by_v4l2index(s, type, vbuf.index);
+ if (!r) {
+ g_printerr("%s: Can't find resource for dequeued buffer!", __func__);
+ return -EINVAL;
+ }
+
+ r->queued = false;
+ vio_cmd = r->vio_q_cmd;
+
+ resp.flags = 0x0;
+ resp.hdr.stream_id = r->stream_id;
+ resp.hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+ resp.timestamp = htole64(r->vio_res_q.timestamp);
+
+ /* encoder only */
+ resp.size = htole32(vbuf.bytesused);
+
+ if (vbuf.flags & V4L2_BUF_FLAG_LAST &&
+ s->stream_state == STREAM_DRAINING) {
+ resp.flags |= VIRTIO_VIDEO_BUFFER_FLAG_EOS;
+ s->stream_state = STREAM_STOPPED;
+ g_cond_signal(&s->stream_cond);
+ }
+
+ if (vbuf.flags & V4L2_BUF_FLAG_KEYFRAME) {
+ resp.flags |= VIRTIO_VIDEO_BUFFER_FLAG_IFRAME;
+ }
+ if (vbuf.flags & V4L2_BUF_FLAG_PFRAME) {
+ resp.flags |= VIRTIO_VIDEO_BUFFER_FLAG_PFRAME;
+ }
+ if (vbuf.flags & V4L2_BUF_FLAG_BFRAME) {
+ resp.flags |= VIRTIO_VIDEO_BUFFER_FLAG_PFRAME;
+ }
+
+ if (vbuf.flags & V4L2_BUF_FLAG_ERROR) {
+ resp.flags |= VIRTIO_VIDEO_BUFFER_FLAG_ERR;
+ g_critical("%s: V4L2_BUF_FLAG_ERROR\n", __func__);
+ }
+
+ g_debug("%s: Send queue_buffer reply: stream_id=%d type=0x%x "
+ "flags=0x%x resource_id=%d t=%llx", __func__,
+ resp.hdr.stream_id, resp.hdr.type, resp.flags,
+ r->vio_resource.resource_id, resp.timestamp);
+
+ send_ctrl_response(vio_cmd, (uint8_t *) &resp,
+ sizeof(struct virtio_video_resource_queue_resp));
+
+ vio_cmd->finished = true;
+ free_resource_mem(r);
+
+ return ret;
+}
+
+int v4l2_video_get_selection(int fd, enum v4l2_buf_type type,
+ struct v4l2_selection *sel)
+{
+ int ret = 0;
+
+ if (!sel) {
+ return -EINVAL;
+ }
+
+ memset(sel, 0, sizeof(struct v4l2_selection));
+
+ sel->type = type;
+
+ if (V4L2_TYPE_IS_CAPTURE(type)) {
+ sel->target = V4L2_SEL_TGT_COMPOSE;
+ } else if (V4L2_TYPE_IS_OUTPUT(type)) {
+ sel->target = V4L2_SEL_TGT_CROP;
+ }
+
+ ret = ioctl(fd, VIDIOC_G_SELECTION, sel);
+ if (ret < 0) {
+ g_printerr("Unable to get selection: %s (%d)\n",
+ g_strerror(errno), errno);
+ return ret;
+ }
+
+ g_debug("%s: VIDIOC_G_SELECTION: fd=(%d) %s: left=(%d) "
+ "top=(%d) width=(%d) height=(%d)",
+ __func__, fd, v4l2_buf_type_name(type), sel->r.left,
+ sel->r.top, sel->r.width, sel->r.height);
+
+ return ret;
+}
+
+int v4l2_video_set_selection(int fd, enum v4l2_buf_type type,
+ struct v4l2_selection *sel)
+{
+ int ret = 0;
+
+ if (!sel) {
+ return -EINVAL;
+ }
+
+ sel->type = type;
+ sel->target = V4L2_SEL_TGT_COMPOSE;
+ /* flags 0 - the driver can adjust the rect size freely */
+ sel->flags = 0;
+
+ ret = ioctl(fd, VIDIOC_S_SELECTION, sel);
+ if (ret < 0) {
+ g_printerr("Unable to set selection: fd=(%d) left=(%d) top=(%d) "
+ "width=(%d) height=(%d): %s (%d)\n",
+ fd, sel->r.left, sel->r.top, sel->r.width, sel->r.height,
+ g_strerror(errno), errno);
+ return ret;
+ }
+
+ g_debug("%s: VIDIOC_S_SELECTION: fd=(%d) left=(%d) "
+ "top=(%d) width=(%d) height=(%d)",
+ __func__, fd, sel->r.left, sel->r.top, sel->r.width, sel->r.height);
+
+ return ret;
+}
+
+int v4l2_video_get_param(int fd, enum v4l2_buf_type type,
+ struct v4l2_streamparm *sparam)
+{
+ int ret = 0;
+
+ if (!sparam) {
+ return -EINVAL;
+ }
+
+ memset(sparam, 0, sizeof(struct v4l2_streamparm));
+ sparam->type = type;
+
+ ret = ioctl(fd, VIDIOC_G_PARM, sparam);
+ if (ret < 0) {
+ g_printerr("Unable to VIDIOC_G_PARAM: %s (%d)\n",
+ g_strerror(errno), errno);
+ return ret;
+ }
+
+ g_debug("%s: VIDIOC_G_PARM timeperframe (%d/%d)", __func__,
+ sparam->parm.capture.timeperframe.numerator,
+ sparam->parm.capture.timeperframe.denominator);
+
+ return ret;
+}
+
+int v4l2_video_get_format(int fd, enum v4l2_buf_type type,
+ struct v4l2_format *fmt)
+{
+ unsigned int i;
+ int ret;
+
+ if (!fmt) {
+ return -EINVAL;
+ }
+
+ memset(fmt, 0, sizeof(struct v4l2_format));
+ fmt->type = type;
+
+ ret = ioctl(fd, VIDIOC_G_FMT, fmt);
+ if (ret < 0) {
+ g_printerr("Unable to get format: %s (%d)\n",
+ g_strerror(errno), errno);
+ return ret;
+ }
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+ g_print("Video format: %s (%08x) %ux%u field %s, %u planes:\n",
+ v4l2_format_name(fmt->fmt.pix_mp.pixelformat),
+ fmt->fmt.pix_mp.pixelformat,
+ fmt->fmt.pix_mp.width,
+ fmt->fmt.pix_mp.height,
+ v4l2_field_name(fmt->fmt.pix_mp.field),
+ fmt->fmt.pix_mp.num_planes);
+
+ for (i = 0; i < fmt->fmt.pix_mp.num_planes; i++) {
+ g_print(" * Stride %u, buffer size %u\n",
+ fmt->fmt.pix_mp.plane_fmt[i].bytesperline,
+ fmt->fmt.pix_mp.plane_fmt[i].sizeimage);
+ }
+ } else if (V4L2_TYPE_IS_META(type)) {
+ g_print("Meta-data format: %s (%08x) buffer size %u\n",
+ v4l2_format_name(fmt->fmt.meta.dataformat),
+ fmt->fmt.meta.dataformat,
+ fmt->fmt.meta.buffersize);
+ } else {
+ g_print("Video format: %s (%08x) %ux%u (stride %u) field %s "
+ "buffer size %u\n",
+ v4l2_format_name(fmt->fmt.pix.pixelformat),
+ fmt->fmt.pix.pixelformat,
+ fmt->fmt.pix.width, fmt->fmt.pix.height,
+ fmt->fmt.pix.bytesperline,
+ v4l2_field_name(fmt->fmt.pix_mp.field),
+ fmt->fmt.pix.sizeimage);
+ }
+
+ return 0;
+}
+
+int v4l2_video_set_format(int fd, enum v4l2_buf_type type,
+ struct virtio_video_params *p)
+{
+ struct v4l2_format fmt;
+ int ret = 0;
+ unsigned int i;
+ uint32_t pixfmt;
+
+ if (!p) {
+ return -EINVAL;
+ }
+
+ memset(&fmt, 0, sizeof fmt);
+ fmt.type = type;
+ pixfmt = virtio_video_format_to_v4l2(le32toh(p->format));
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+ fmt.fmt.pix_mp.width = le32toh(p->frame_width);
+ fmt.fmt.pix_mp.height = le32toh(p->frame_height);
+ fmt.fmt.pix_mp.pixelformat = pixfmt;
+ /*
+ * V4L2_FIELD_NONE - matches what Linux frontend driver does in
+ * virtio_video_format_from_info()
+ */
+ fmt.fmt.pix_mp.field = V4L2_FIELD_NONE;
+ /*fmt.fmt.pix_mp.num_planes = info->n_planes;*/
+ fmt.fmt.pix_mp.num_planes = le32toh(p->num_planes);
+ fmt.fmt.pix_mp.flags = 0;
+
+ for (i = 0; i < le32toh(p->num_planes); i++) {
+ fmt.fmt.pix_mp.plane_fmt[i].bytesperline =
+ le32toh(p->plane_formats[i].stride);
+ fmt.fmt.pix_mp.plane_fmt[i].sizeimage =
+ le32toh(p->plane_formats[i].plane_size);
+ }
+ } else if (V4L2_TYPE_IS_SINGLEPLANAR(type)) {
+ fmt.fmt.pix.width = le32toh(p->frame_width);
+ fmt.fmt.pix.height = le32toh(p->frame_height);
+ fmt.fmt.pix.pixelformat = pixfmt;
+ fmt.fmt.pix.field = V4L2_FIELD_NONE;
+ fmt.fmt.pix.bytesperline = le32toh(p->plane_formats[0].stride);
+ fmt.fmt.pix.sizeimage = le32toh(p->plane_formats[0].plane_size);
+ fmt.fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC;
+ fmt.fmt.pix.flags = 0;
+ }
+
+ ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
+ if (ret < 0) {
+ g_printerr("Unable to set format: %s (%d)\n",
+ g_strerror(errno), errno);
+ }
+ return ret;
+}
+
+int
+v4l2_set_pixel_format(int fd, enum v4l2_buf_type buf_type, uint32_t pixelformat)
+{
+ int ret = 0;
+ struct v4l2_format cur_fmt;
+
+ g_debug("%s: buf_type=0x%x pixelformat=0x%x", __func__,
+ buf_type, pixelformat);
+
+ /* get the currently set format */
+ ret = v4l2_video_get_format(fd, buf_type, &cur_fmt);
+ if (ret < 0) {
+ g_printerr("%s: v4l2_video_get_format() failed\n", __func__);
+ return ret;
+ }
+
+ /* keep defaults and set correct pixel format */
+ if (V4L2_TYPE_IS_MULTIPLANAR(cur_fmt.type)) {
+ g_print("%s: Format is mplane\n", __func__);
+ cur_fmt.fmt.pix_mp.pixelformat = pixelformat;
+ } else if (V4L2_TYPE_IS_SINGLEPLANAR(cur_fmt.type)) {
+ g_print("%s: Format is splane\n", __func__);
+ cur_fmt.fmt.pix.pixelformat = pixelformat;
+ }
+
+ ret = ioctl(fd, VIDIOC_S_FMT, &cur_fmt);
+ if (ret < 0) {
+ g_printerr("Unable to set format: %s (%d)\n",
+ g_strerror(errno), errno);
+ }
+
+ return ret;
+}
+
+int video_enum_formats(struct v4l2_device *dev, enum v4l2_buf_type type,
+ GList **p_fmt_list, bool only_enum_fmt)
+{
+ struct v4l2_fmtdesc fmt;
+ struct video_format *vid_fmt = NULL;
+ GList *fmt_list = NULL;
+ unsigned int index;
+ int ret = 0;
+
+ if (!dev) {
+ return -EINVAL;
+ }
+
+ for (index = 0; ; ++index) {
+ memset(&fmt, 0, sizeof fmt);
+ fmt.index = index;
+ fmt.type = type;
+
+ ret = ioctl(dev->fd, VIDIOC_ENUM_FMT, &fmt);
+ if (ret < 0) {
+ if (errno == EINVAL) {
+ ret = 0;
+ } else {
+ g_printerr("%s: VIDIOC_ENUM_FMT failed %s (%d)\n",
+ __func__, g_strerror(errno), errno);
+ }
+ break;
+ }
+
+ /* do some driver sanity checks */
+ if (index != fmt.index) {
+ g_warning("v4l2 driver modified index %u\n", fmt.index);
+ }
+ if (type != fmt.type) {
+ g_warning("v4l2 driver modified type %u\n", fmt.type);
+ }
+ g_debug("\tFormat %u: %s (%08x)", index,
+ v4l2_format_name(fmt.pixelformat), fmt.pixelformat);
+ g_debug("\tType: %s (%u)", v4l2_buf_type_name(fmt.type),
+ fmt.type);
+ g_debug("\tName: %.32s", fmt.description);
+ g_debug("\tFlags: 0x%x", fmt.flags);
+
+ if (fmt.flags & V4L2_FMT_FLAG_DYN_RESOLUTION &&
+ fmt.flags & V4L2_FMT_FLAG_COMPRESSED) {
+ g_print("dynamic resolution switching supported\n");
+ dev->sup_dyn_res_switching = true;
+ }
+
+ /* test if pixelformat converts to virtio */
+ if (!virtio_video_v4l2_format_to_virtio(fmt.pixelformat)) {
+ g_info("Skipping Format %s (%08x) - no virtio-video equivalent",
+ v4l2_format_name(fmt.pixelformat), fmt.pixelformat);
+ continue;
+ }
+
+ vid_fmt = g_new0(struct video_format, 1);
+ /* keep a copy of v4l2 struct */
+ memcpy(&vid_fmt->fmt, &fmt, sizeof(struct v4l2_fmtdesc));
+ /* add it to linked list */
+ fmt_list = g_list_append(fmt_list, vid_fmt);
+
+ if (!only_enum_fmt) {
+ /* pass video_format to enum_frame_sizes */
+ ret = video_enum_frame_sizes(dev, fmt.pixelformat,
+ &vid_fmt->vid_fmt_frm_l);
+ if (ret < 0) {
+ g_printerr("video_enum_frame_sizes failed\n");
+ }
+
+ /* convert to virtio format */
+ v4l2_to_virtio_fmtdesc(dev, vid_fmt, type);
+ }
+
+ /* determine type of v4l2 device */
+ v4l2_set_device_type(dev, type, &fmt);
+ }
+
+ if (ret == 0) {
+ g_print("%s: Enumerated %d formats on v4l2 %s queue",
+ __func__, index, v4l2_buf_type_name(type));
+ g_print(" %d formats are representable by virtio-video\n",
+ g_list_length(fmt_list));
+ if (!only_enum_fmt) {
+ g_print("%s: Enumerated %d frame sizes\n",
+ __func__, g_list_length(vid_fmt->vid_fmt_frm_l));
+ }
+
+ *p_fmt_list = fmt_list;
+ }
+
+ return ret;
+}
+
+void video_free_frame_intervals(GList *frm_intervals_l)
+{
+ GList *l;
+ struct video_format_frame_rates *vid_fmt_frm_rate;
+ for (l = frm_intervals_l; l != NULL; l = l->next) {
+ vid_fmt_frm_rate = l->data;
+ g_free(vid_fmt_frm_rate);
+ }
+}
+
+void video_free_frame_sizes(GList *frm_sz_l)
+{
+ GList *l;
+ struct video_format_frame *vid_frame;
+ for (l = frm_sz_l; l != NULL; l = l->next) {
+ vid_frame = l->data;
+ if (vid_frame->frm_rate_l) {
+ video_free_frame_intervals(vid_frame->frm_rate_l);
+ }
+ g_free(vid_frame);
+ }
+}
+
+void video_free_formats(GList **fmt_l)
+{
+ GList *l;
+ struct video_format *vid_fmt;
+
+ for (l = *fmt_l; l != NULL; l = l->next) {
+ vid_fmt = l->data;
+ if (vid_fmt->vid_fmt_frm_l) {
+ video_free_frame_sizes(vid_fmt->vid_fmt_frm_l);
+ }
+
+ g_free(vid_fmt);
+ }
+}
+
+
+static GByteArray *iterate_frame_rate_list(GByteArray *resp, GList *frm_rate_l)
+{
+ struct video_format_frame_rates *vid_fmt_frm_rate;
+
+ /* iterate frame_rate list */
+ for (; frm_rate_l != NULL; frm_rate_l = frm_rate_l->next) {
+ vid_fmt_frm_rate = frm_rate_l->data;
+
+ resp = g_byte_array_append(resp,
+ (guint8 *) &vid_fmt_frm_rate->frame_rates,
+ sizeof(struct virtio_video_format_range));
+ }
+ return resp;
+}
+
+static GByteArray *iterate_format_frame_list(GByteArray *resp, GList *fmt_frm_l)
+{
+ struct video_format_frame *vid_fmt_frm;
+ GList *frm_rate_l = NULL;
+
+ /* iterate format_frame list */
+ for (; fmt_frm_l != NULL; fmt_frm_l = fmt_frm_l->next) {
+ vid_fmt_frm = fmt_frm_l->data;
+
+ if (!vid_fmt_frm->frm_rate_l) {
+ vid_fmt_frm->frame.num_rates = htole32(0);
+ } else {
+ frm_rate_l = vid_fmt_frm->frm_rate_l;
+ vid_fmt_frm->frame.num_rates = htole32(g_list_length(frm_rate_l));
+ }
+
+ g_debug("%s: num_rates(%d)", __func__,
+ le32toh(vid_fmt_frm->frame.num_rates));
+
+ resp = g_byte_array_append(resp,
+ (guint8 *) &vid_fmt_frm->frame,
+ sizeof(struct virtio_video_format_frame));
+
+ if (frm_rate_l) {
+ resp = iterate_frame_rate_list(resp, frm_rate_l);
+ }
+ }
+
+ return resp;
+}
+
+static GByteArray *iterate_format_desc_list(GByteArray *resp, GList *fmt_desc_l)
+{
+ struct video_format *vid_fmt;
+ GList *fmt_frm_l = NULL;
+
+ for (; fmt_desc_l != NULL; fmt_desc_l = fmt_desc_l->next) {
+ vid_fmt = fmt_desc_l->data;
+
+ /* does video_format have a list of format_frame? */
+ if (!vid_fmt->vid_fmt_frm_l) {
+ vid_fmt->desc.num_frames = htole32(0);
+ } else {
+ fmt_frm_l = vid_fmt->vid_fmt_frm_l;
+ vid_fmt->desc.num_frames = htole32(g_list_length(fmt_frm_l));
+ }
+
+ g_debug("%s: num_frames(%d)", __func__,
+ le32toh(vid_fmt->desc.num_frames));
+
+ resp = g_byte_array_append(resp,
+ (guint8 *) &vid_fmt->desc,
+ sizeof(struct virtio_video_format_desc));
+
+ if (fmt_frm_l) {
+ resp = iterate_format_frame_list(resp, fmt_frm_l);
+ }
+ }
+
+ return resp;
+}
+
+GByteArray *create_query_cap_resp(struct virtio_video_query_capability *qcmd,
+ GList **fmt_l, GByteArray *resp)
+{
+ GList *fmt_desc_l;
+ struct virtio_video_query_capability_resp cap_resp;
+
+ fmt_desc_l = *fmt_l;
+
+ cap_resp.hdr.type = VIRTIO_VIDEO_RESP_OK_QUERY_CAPABILITY;
+ cap_resp.hdr.stream_id = qcmd->hdr.stream_id;
+ cap_resp.num_descs = htole32(g_list_length(fmt_desc_l));
+
+ assert(le32toh(cap_resp.num_descs) < MAX_FMT_DESCS);
+
+ resp = g_byte_array_append(resp, (guint8 *) &cap_resp, sizeof(cap_resp));
+ resp = iterate_format_desc_list(resp, fmt_desc_l);
+
+ return resp;
+}
+
+/* timestamp in nsecs */
+void convert_to_timeval(uint64_t timestamp, struct timeval *t)
+{
+ uint64_t f_nsecs;
+
+ uint64_t nsecs;
+
+ /* convert to seconds */
+ t->tv_sec = timestamp / 1000000000;
+
+ /* deal with fraction of a second */
+ f_nsecs = t->tv_sec * 1000000000;
+ t->tv_usec = (timestamp - f_nsecs) / 1000;
+
+ /* sanity check above conversion */
+ nsecs = t->tv_sec * 1000000000;
+ nsecs += (t->tv_usec * 1000);
+
+ if (timestamp != nsecs) {
+ g_critical("%s: timestamp != nsecs", __func__);
+ }
+}
+
+void v4l2_backend_free(struct v4l2_device *dev)
+{
+ if (dev && dev->opened) {
+ close(dev->fd);
+ }
+ g_free(dev);
+}
+
+struct v4l2_device *v4l2_backend_init(const gchar *devname)
+{
+ struct v4l2_device *dev;
+ int ret = 0;
+ GList *vid_output_fmt_l = NULL;
+ GList *vid_capture_fmt_l = NULL;
+ enum v4l2_buf_type buf_type;
+
+ if (!devname) {
+ return NULL;
+ }
+
+ dev = g_malloc0(sizeof(struct v4l2_device));
+
+ /* open the device */
+ dev->fd = v4l2_open(devname);
+ if (dev->fd < 0) {
+ g_printerr("v4l2_open() failed!\n");
+ goto err;
+ }
+
+ dev->opened = 1;
+ dev->devname = devname;
+
+ ret = video_querycap(dev);
+
+ buf_type = dev->has_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
+ : V4L2_BUF_TYPE_VIDEO_OUTPUT;
+
+ /* enumerate coded formats on OUTPUT */
+ ret = video_enum_formats(dev, buf_type,
+ &vid_output_fmt_l, true);
+ if (ret < 0) {
+ g_printerr("video_enum_formats() failed OUTPUT\n");
+ goto err;
+ }
+
+ buf_type = dev->has_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
+ : V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ /* enumerate coded formats on CAPTURE */
+ ret = video_enum_formats(dev, buf_type,
+ &vid_capture_fmt_l, true);
+ if (ret < 0) {
+ g_printerr("video_enum_formats() failed CAPTURE\n");
+ goto err2;
+ }
+
+ if (dev->dev_type & STATEFUL_ENCODER)
+ g_print("%s: %s is a stateful encoder (0x%x)!\n", __func__,
+ devname, dev->dev_type);
+
+ if (dev->dev_type & STATEFUL_DECODER)
+ g_print("%s: %s is a stateful decoder (0x%x)!\n", __func__,
+ devname, dev->dev_type);
+
+ video_free_formats(&vid_output_fmt_l);
+ video_free_formats(&vid_capture_fmt_l);
+
+ if (!(dev->dev_type & STATEFUL_ENCODER ||
+ dev->dev_type & STATEFUL_DECODER)) {
+ g_printerr("v4l2 device not supported! v4l2 backend only supports "
+ "stateful codec devices currently(%d)!\n", dev->dev_type);
+ goto err3;
+ }
+
+ g_debug("%s: success!\n", __func__);
+ return dev;
+
+err3:
+ video_free_formats(&vid_capture_fmt_l);
+err2:
+ video_free_formats(&vid_output_fmt_l);
+err:
+ v4l2_backend_free(dev);
+ return NULL;
+}
+
+int v4l2_ioctl_query_control(int fd, uint32_t control, int32_t *value)
+{
+ int ret = 0;
+ struct v4l2_queryctrl ctrl;
+
+ g_debug("%s:%d", __func__, __LINE__);
+
+ ctrl.id = control;
+
+ ret = ioctl(fd, VIDIOC_QUERYCTRL, &ctrl);
+ if (ret < 0) {
+ g_printerr("Unable to query control: %s (%d)\n",
+ g_strerror(errno), errno);
+ return ret;
+ }
+
+ *value = ctrl.type;
+ g_debug("%s: ctrl=0x%x type=0x%x", __func__, control, *value);
+
+ return ret;
+}
+
+int v4l2_ioctl_get_control(int fd , uint32_t control, int32_t *value)
+{
+ int ret = 0;
+ struct v4l2_control ctrl;
+
+ g_debug("%s:%d", __func__, __LINE__);
+
+ ctrl.id = control;
+
+ ret = ioctl(fd, VIDIOC_G_CTRL, &ctrl);
+ if (ret < 0) {
+ g_printerr("Unable to get control: %s (%d)\n",
+ g_strerror(errno), errno);
+ return ret;
+ }
+
+ *value = ctrl.value;
+ g_debug("%s: ctrl=0x%x value=0x%x", __func__, control, *value);
+
+ return ret;
+}
+
+int v4l2_ioctl_reqbuf(int fd, enum v4l2_buf_type type,
+ enum v4l2_memory memory, int *count)
+{
+ int ret;
+ struct v4l2_requestbuffers reqbuf;
+
+ memset(&reqbuf, 0, sizeof(reqbuf));
+ reqbuf.type = type;
+ reqbuf.memory = memory;
+ reqbuf.count = *count;
+
+ ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf);
+ if (ret == -1) {
+ if (errno == EBUSY) {
+ g_critical("%s: EBUSY: buffers for %s still mapped or exported!\n",
+ __func__, v4l2_buf_type_name(type));
+ } else {
+ g_printerr("VIDIOC_REQBUFS failed: %s (%d)\n",
+ g_strerror(errno), errno);
+ }
+ return ret;
+ }
+
+ *count = reqbuf.count;
+
+ g_debug("%s: VIDIOC_REQBUFS capabilities(0x%x) granted(%d)",
+ __func__, reqbuf.capabilities, reqbuf.count);
+
+ return ret;
+}
+
+int v4l2_ioctl_subscribe_event(int fd, uint32_t event_type, uint32_t id)
+{
+ int ret = 0;
+ struct v4l2_event_subscription sub;
+
+ memset(&sub, 0, sizeof(sub));
+ sub.type = event_type;
+ sub.id = 0;
+
+ if (event_type == V4L2_EVENT_SOURCE_CHANGE) {
+ sub.id = id;
+ }
+
+ ret = ioctl(fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
+ if (ret < 0) {
+ g_printerr("VIDIOC_SUBSCRIBE_EVENT failed: %s (%d)\n",
+ g_strerror(errno), errno);
+ return ret;
+ }
+
+ g_debug("%s event(0x%x) OK", __func__, event_type);
+
+ return ret;
+}
+
+int v4l2_issue_cmd(int fd, uint32_t cmd, uint32_t flags)
+{
+ int ret = 0;
+ struct v4l2_decoder_cmd decoder_cmd;
+ memset(&decoder_cmd, 0, sizeof(struct v4l2_decoder_cmd));
+
+ decoder_cmd.cmd = cmd;
+ decoder_cmd.flags = flags;
+ /* Normal speed */
+ decoder_cmd.start.speed = 1000;
+
+ ret = ioctl(fd, VIDIOC_DECODER_CMD, &decoder_cmd);
+ if (ret < 0) {
+ g_printerr("VIDIOC_DECODER_CMD(%d) failed fd=(%d): %s: (%d)\n",
+ cmd, fd, g_strerror(errno), errno);
+ return ret;
+ }
+
+ g_debug("%s: VIDIOC_DECODER_CMD(%d) fd(%d) OK\n", __func__, cmd, fd);
+
+ return ret;
+}
new file mode 100644
@@ -0,0 +1,104 @@
+/*
+ * Virtio vhost-user VIDEO Device
+ *
+ * Copyright Red Hat, Inc. 2023
+ * Copyright Linaro 2021
+ *
+ * Authors:
+ * Peter Griffin <peter.griffin@linaro.org>
+ * Albert Esteve <aesteve@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef V4L2_BACKEND_H
+#define V4L2_BACKEND_H
+
+#include "standard-headers/linux/virtio_video.h"
+#include "virtio_video_helpers.h"
+
+#define MAX_CAPS_LEN 4096
+#define MAX_FMT_DESCS 64
+
+#define STATEFUL_ENCODER (1 << 0)
+#define STATEFUL_DECODER (1 << 1)
+#define STATELESS_ENCODER (1 << 2)
+#define STATELESS_DECODER (1 << 3)
+
+#define V4L2_TYPE_IS_SINGLEPLANAR(type) \
+ ((type) == V4L2_BUF_TYPE_VIDEO_CAPTURE \
+ || (type) == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+
+/* Function protoypes */
+GByteArray *create_query_cap_resp(struct virtio_video_query_capability *qcmd,
+ GList **fmt_l, GByteArray *querycapresp);
+
+/* video to v4l2 interactions */
+int video_resource_create(struct stream *s,
+ uint32_t queue_type, uint32_t queue_len);
+int video_send_decoder_start_cmd(struct v4l2_device *dev);
+void video_free_frame_intervals(GList *frm_intervals_l);
+void video_free_frame_sizes(GList *frm_sz_l);
+int video_enum_formats(struct v4l2_device *dev, enum v4l2_buf_type type,
+ GList **p_fmt_list, bool only_enum_fmt);
+void video_free_formats(GList **fmt_l);
+int video_streamon(struct stream *s, struct v4l2_device *dev,
+ enum v4l2_buf_type type);
+int video_streamoff(struct stream *s, enum v4l2_buf_type type);
+int video_free_buffers(int fd, enum v4l2_buf_type type,
+ enum v4l2_memory memory);
+
+struct v4l2_device *v4l2_backend_init(const gchar *devname);
+void v4l2_backend_free(struct v4l2_device *dev);
+
+/* v4l2 wrappers */
+enum v4l2_buf_type
+get_v4l2_buf_type(enum virtio_video_queue_type queue_type, bool has_mplane);
+enum v4l2_memory get_v4l2_memory(enum virtio_video_mem_type mem_type);
+enum virtio_video_mem_type
+get_queue_mem_type(struct stream *s,
+ enum virtio_video_queue_type queue_type);
+
+void v4l2_set_device_type(struct v4l2_device *dev, enum v4l2_buf_type type,
+ struct v4l2_fmtdesc *fmt_desc);
+int v4l2_video_get_format(int fd, enum v4l2_buf_type type,
+ struct v4l2_format *fmt);
+int v4l2_video_set_format(int fd, enum v4l2_buf_type type,
+ struct virtio_video_params *p);
+int v4l2_set_pixel_format(int fd, enum v4l2_buf_type buf_type,
+ uint32_t pixelformat);
+
+int v4l2_queue_buffer(enum v4l2_buf_type type,
+ enum v4l2_memory memory,
+ struct virtio_video_resource_queue *qcmd,
+ struct resource *res, struct stream *s,
+ struct v4l2_device *dev);
+int v4l2_dequeue_buffer(int fd, enum v4l2_buf_type type,
+ enum v4l2_memory memory,
+ struct stream *s);
+
+int v4l2_video_get_param(int fd, enum v4l2_buf_type type,
+ struct v4l2_streamparm *param);
+
+int v4l2_video_get_selection(int fd, enum v4l2_buf_type type,
+ struct v4l2_selection *sel);
+int v4l2_video_set_selection(int fd, enum v4l2_buf_type type,
+ struct v4l2_selection *sel);
+
+int v4l2_streamon(struct stream *s, enum v4l2_buf_type type);
+int v4l2_streamoff(struct stream *s, enum v4l2_buf_type type);
+
+int v4l2_open(const gchar *devname);
+int v4l2_close(int fd);
+
+/* ioctl wrappers */
+int v4l2_ioctl_query_control(int fd, uint32_t control, int32_t *value);
+int v4l2_ioctl_get_control(int fd, uint32_t control, int32_t *value);
+int v4l2_ioctl_reqbuf(int fd, enum v4l2_buf_type type,
+ enum v4l2_memory memory, int *count);
+int v4l2_ioctl_subscribe_event(int fd, uint32_t event_type, uint32_t id);
+
+int v4l2_issue_cmd(int fd, uint32_t cmd, uint32_t flags);
+
+#endif
new file mode 100644
@@ -0,0 +1,1779 @@
+/*
+ * VIRTIO Video Emulation via vhost-user
+ *
+ * Copyright (c) 2023 Red Hat, Inc.
+ * Copyright (c) 2021 Linaro Ltd
+ *
+ * Authors:
+ * Peter Griffin <peter.griffin@linaro.org>
+ * Albert Esteve <aesteve@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#define G_LOG_DOMAIN "vhost-user-video"
+#define G_LOG_USE_STRUCTURED 1
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gio/gunixsocketaddress.h>
+#include <glib-unix.h>
+#include <glib/gstdio.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <endian.h>
+#include <assert.h>
+
+#include "libvhost-user-glib.h"
+#include "libvhost-user.h"
+#include "standard-headers/linux/virtio_video.h"
+
+#include "qemu/compiler.h"
+#include "qemu/iov.h"
+
+#include "vuvideo.h"
+#include "v4l2_backend.h"
+#include "virtio_video_helpers.h"
+
+#ifndef container_of
+#define container_of(ptr, type, member) ({ \
+ const typeof(((type *) 0)->member) * __mptr = (ptr); \
+ (type *) ((char *) __mptr - offsetof(type, member)); })
+#endif
+
+static gchar *socket_path;
+static gchar *v4l2_path;
+static gint socket_fd = -1;
+static gboolean print_cap;
+static gboolean verbose;
+static gboolean debug;
+
+static GOptionEntry options[] = {
+ { "socket-path", 0, 0, G_OPTION_ARG_FILENAME, &socket_path,
+ "Location of vhost-user Unix domain socket, "
+ "incompatible with --fd", "PATH" },
+ { "v4l2-device", 0, 0, G_OPTION_ARG_FILENAME, &v4l2_path,
+ "Location of v4l2 device node", "PATH" },
+ { "fd", 0, 0, G_OPTION_ARG_INT, &socket_fd,
+ "Specify the fd of the backend, "
+ "incompatible with --socket-path", "FD" },
+ { "print-capabilities", 0, 0, G_OPTION_ARG_NONE, &print_cap,
+ "Output to stdout the backend capabilities "
+ "in JSON format and exit", NULL},
+ { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
+ "Be more verbose in output", NULL},
+ { "debug", 0, 0, G_OPTION_ARG_NONE, &debug,
+ "Include debug output", NULL},
+ { NULL }
+};
+
+enum {
+ VHOST_USER_VIDEO_MAX_QUEUES = 2,
+};
+
+static const char *
+vv_cmd_to_string(int cmd)
+{
+#define CMD(cmd) [cmd] = #cmd
+ static const char *vg_cmd_str[] = {
+ /* Command */
+ CMD(VIRTIO_VIDEO_CMD_QUERY_CAPABILITY),
+ CMD(VIRTIO_VIDEO_CMD_STREAM_CREATE),
+ CMD(VIRTIO_VIDEO_CMD_STREAM_DESTROY),
+ CMD(VIRTIO_VIDEO_CMD_STREAM_DRAIN),
+ CMD(VIRTIO_VIDEO_CMD_RESOURCE_CREATE),
+ CMD(VIRTIO_VIDEO_CMD_RESOURCE_QUEUE),
+ CMD(VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL),
+ CMD(VIRTIO_VIDEO_CMD_QUEUE_CLEAR),
+ CMD(VIRTIO_VIDEO_CMD_QUERY_CONTROL),
+ CMD(VIRTIO_VIDEO_CMD_GET_CONTROL),
+ CMD(VIRTIO_VIDEO_CMD_SET_CONTROL),
+ CMD(VIRTIO_VIDEO_CMD_GET_PARAMS_EXT),
+ CMD(VIRTIO_VIDEO_CMD_SET_PARAMS_EXT),
+ };
+#undef CMD
+
+ if (cmd >= 0 && cmd < G_N_ELEMENTS(vg_cmd_str)) {
+ return vg_cmd_str[cmd];
+ } else {
+ return "unknown";
+ }
+}
+
+static void video_panic(VuDev *dev, const char *msg)
+{
+ g_critical("%s\n", msg);
+ exit(EXIT_FAILURE);
+}
+
+static uint64_t video_get_features(VuDev *dev)
+{
+ g_info("%s: replying", __func__);
+ return 0;
+}
+
+static void video_set_features(VuDev *dev, uint64_t features)
+{
+ if (features) {
+ g_autoptr(GString) s = g_string_new("Requested un-handled feature");
+ g_string_append_printf(s, " 0x%" PRIx64 "", features);
+ g_info("%s: %s", __func__, s->str);
+ }
+}
+
+/*
+ * The configuration of the device is static and set when we start the
+ * daemon.
+ */
+static int
+video_get_config(VuDev *dev, uint8_t *config, uint32_t len)
+{
+ VuVideo *v = container_of(dev, VuVideo, dev.parent);
+
+ g_return_val_if_fail(len <= sizeof(struct virtio_video_config), -1);
+ v->virtio_config.version = 0;
+ v->virtio_config.max_caps_length = MAX_CAPS_LEN;
+ v->virtio_config.max_resp_length = MAX_CAPS_LEN;
+
+ memcpy(config, &v->virtio_config, len);
+
+ g_debug("%s: config.max_caps_length = %d", __func__
+ , ((struct virtio_video_config *)config)->max_caps_length);
+ g_debug("%s: config.max_resp_length = %d", __func__
+ , ((struct virtio_video_config *)config)->max_resp_length);
+
+ return 0;
+}
+
+static int
+video_set_config(VuDev *dev, const uint8_t *data,
+ uint32_t offset, uint32_t size,
+ uint32_t flags)
+{
+ g_debug("%s: ", __func__);
+ /*
+ * set_config is required to set the F_CONFIG feature,
+ * but we can just ignore the call
+ */
+ return 0;
+}
+
+/*
+ * Handlers for individual control messages
+ */
+
+static void
+handle_set_params_cmd(struct VuVideo *v, struct vu_video_ctrl_command *vio_cmd)
+{
+ int ret = 0;
+ enum v4l2_buf_type buf_type;
+ struct virtio_video_set_params *cmd =
+ (struct virtio_video_set_params *) vio_cmd->cmd_buf;
+ struct stream *s;
+
+ g_debug("%s: type(0x%x) resource_type(%d) stream_id(%d) %s ",
+ __func__, cmd->hdr.type,
+ le32toh(cmd->params.resource_type), cmd->hdr.stream_id,
+ vio_queue_name(le32toh(cmd->params.queue_type)));
+ g_debug("%s: format=0x%x frame_width(%d) frame_height(%d)",
+ __func__, le32toh(cmd->params.format),
+ le32toh(cmd->params.frame_width),
+ le32toh(cmd->params.frame_height));
+ g_debug("%s: min_buffers(%d) max_buffers(%d)", __func__,
+ le32toh(cmd->params.min_buffers), le32toh(cmd->params.max_buffers));
+ g_debug("%s: frame_rate(%d) num_planes(%d)", __func__,
+ le32toh(cmd->params.frame_rate), le32toh(cmd->params.num_planes));
+ g_debug("%s: crop top=%d, left=%d, width=%d, height=%d", __func__,
+ le32toh(cmd->params.crop.left), le32toh(cmd->params.crop.top),
+ le32toh(cmd->params.crop.width), le32toh(cmd->params.crop.height));
+
+ s = find_stream(v, cmd->hdr.stream_id);
+ if (!s) {
+ g_critical("%s: stream_id(%d) not found", __func__, cmd->hdr.stream_id);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ g_mutex_lock(&s->mutex);
+
+ buf_type = get_v4l2_buf_type(le32toh(cmd->params.queue_type),
+ s->has_mplane);
+
+ ret = v4l2_video_set_format(s->fd, buf_type, &cmd->params);
+ if (ret < 0) {
+ g_error("%s: v4l2_video_set_format() failed", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+
+ if (V4L2_TYPE_IS_CAPTURE(buf_type)) {
+ /* decoder supports composing on CAPTURE */
+ struct v4l2_selection sel;
+ memset(&sel, 0, sizeof(struct v4l2_selection));
+
+ sel.r.left = le32toh(cmd->params.crop.left);
+ sel.r.top = le32toh(cmd->params.crop.top);
+ sel.r.width = le32toh(cmd->params.crop.width);
+ sel.r.height = le32toh(cmd->params.crop.height);
+
+ ret = v4l2_video_set_selection(s->fd, buf_type, &sel);
+ if (ret < 0) {
+ g_printerr("%s: v4l2_video_set_selection failed: %s (%d).\n"
+ , __func__, g_strerror(errno), errno);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+ }
+
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+
+out_unlock:
+ vio_cmd->finished = true;
+ send_ctrl_response_nodata(vio_cmd);
+ g_mutex_unlock(&s->mutex);
+ return;
+}
+
+static void
+handle_get_params_cmd(struct VuVideo *v, struct vu_video_ctrl_command *vio_cmd)
+{
+ int ret;
+ struct v4l2_format fmt;
+ struct v4l2_selection sel;
+ enum v4l2_buf_type buf_type;
+ struct virtio_video_get_params *cmd =
+ (struct virtio_video_get_params *) vio_cmd->cmd_buf;
+ struct virtio_video_get_params_resp getparams_reply;
+ struct stream *s;
+
+ g_debug("%s: type(0x%x) stream_id(%d) %s", __func__,
+ cmd->hdr.type, cmd->hdr.stream_id,
+ vio_queue_name(le32toh(cmd->queue_type)));
+
+ s = find_stream(v, cmd->hdr.stream_id);
+ if (!s) {
+ g_critical("%s: stream_id(%d) not found\n"
+ , __func__, cmd->hdr.stream_id);
+ getparams_reply.hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ g_mutex_lock(&s->mutex);
+
+ getparams_reply.hdr.stream_id = cmd->hdr.stream_id;
+ getparams_reply.params.queue_type = cmd->queue_type;
+
+ buf_type = get_v4l2_buf_type(cmd->queue_type, s->has_mplane);
+
+ ret = v4l2_video_get_format(s->fd, buf_type, &fmt);
+ if (ret < 0) {
+ g_printerr("v4l2_video_get_format failed\n");
+ getparams_reply.hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+
+ if (V4L2_TYPE_IS_CAPTURE(buf_type)) {
+ ret = v4l2_video_get_selection(s->fd, buf_type, &sel);
+ if (ret < 0) {
+ g_printerr("v4l2_video_get_selection failed\n");
+ getparams_reply.hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+ }
+
+ /* convert from v4l2 to virtio */
+ v4l2_to_virtio_video_params(v->v4l2_dev, &fmt, &sel,
+ &getparams_reply);
+
+ getparams_reply.hdr.type = VIRTIO_VIDEO_RESP_OK_GET_PARAMS;
+
+out_unlock:
+ vio_cmd->finished = true;
+ send_ctrl_response(vio_cmd, (uint8_t *)&getparams_reply,
+ sizeof(struct virtio_video_get_params_resp));
+ g_mutex_unlock(&s->mutex);
+}
+
+struct stream *find_stream(struct VuVideo *v, uint32_t stream_id)
+{
+ GList *l;
+ struct stream *s;
+
+ for (l = v->streams; l != NULL; l = l->next) {
+ s = (struct stream *)l->data;
+ if (s->stream_id == stream_id) {
+ return s;
+ }
+ }
+
+ return NULL;
+}
+
+int add_resource(struct stream *s, struct resource *r, uint32_t queue_type)
+{
+
+ if (!s || !r) {
+ return -EINVAL;
+ }
+
+ switch (queue_type) {
+ case VIRTIO_VIDEO_QUEUE_TYPE_INPUT:
+ s->inputq_resources = g_list_append(s->inputq_resources, r);
+ break;
+
+ case VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT:
+ s->outputq_resources = g_list_append(s->outputq_resources, r);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void free_resource_mem(struct resource *r)
+{
+
+ /*
+ * Frees the memory allocated for resource_queue_cmd
+ * not the memory allocated in resource_create
+ */
+
+ if (r->vio_q_cmd) {
+ g_free(r->vio_q_cmd->cmd_buf);
+ r->vio_q_cmd->cmd_buf = NULL;
+ free(r->vio_q_cmd);
+ r->vio_q_cmd = NULL;
+ }
+}
+
+void remove_all_resources(struct stream *s, uint32_t queue_type)
+{
+ GList **resource_list;
+ struct resource *r;
+
+ /* assumes stream mutex is held by caller */
+
+ switch (queue_type) {
+ case VIRTIO_VIDEO_QUEUE_TYPE_INPUT:
+ resource_list = &s->inputq_resources;
+ break;
+ case VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT:
+ resource_list = &s->outputq_resources;
+ break;
+ default:
+ g_critical("%s: Invalid virtio queue!", __func__);
+ return;
+ }
+
+ g_debug("%s: resource_list has %d elements", __func__
+ , g_list_length(*resource_list));
+
+ GList *l = *resource_list;
+ while (l != NULL) {
+ GList *next = l->next;
+ r = (struct resource *)l->data;
+ if (r) {
+ g_debug("%s: Removing resource_id(%d) resource=%p"
+ , __func__, r->vio_resource.resource_id, r);
+
+ /*
+ * Assumes that either QUEUE_CLEAR or normal dequeuing
+ * of buffers will have freed resource_queue cmd memory
+ */
+
+ /* free resource memory allocated in resource_create() */
+ g_free(r->iov);
+ g_free(r);
+ *resource_list = g_list_delete_link(*resource_list, l);
+ }
+ l = next;
+ }
+}
+
+struct resource *find_resource(struct stream *s, uint32_t resource_id,
+ uint32_t queue_type)
+{
+ GList *l;
+ struct resource *r;
+
+ switch (queue_type) {
+ case VIRTIO_VIDEO_QUEUE_TYPE_INPUT:
+ l = s->inputq_resources;
+ break;
+
+ case VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT:
+ l = s->outputq_resources;
+ break;
+ default:
+ g_error("%s: Invalid queue type!", __func__);
+ return NULL;
+ }
+
+ for (; l != NULL; l = l->next) {
+ r = (struct resource *)l->data;
+ if (r->vio_resource.resource_id == resource_id) {
+ return r;
+ }
+ }
+
+ return NULL;
+}
+
+struct resource *find_resource_by_v4l2index(struct stream *s,
+ enum v4l2_buf_type buf_type,
+ uint32_t v4l2_index)
+{
+ GList *l;
+ struct resource *r;
+
+ switch (buf_type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ l = s->outputq_resources;
+ break;
+
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ l = s->inputq_resources;
+ break;
+
+ default:
+ g_error("Unsupported buffer type\n");
+ return NULL;
+ }
+
+ for (; l != NULL; l = l->next) {
+ r = (struct resource *)l->data;
+ if (r->v4l2_index == v4l2_index) {
+ g_debug("%s: found Resource=%p streamid(%d) resourceid(%d) "
+ "numplanes(%d) planes_layout(0x%x) vio_q_cmd=%p", __func__,
+ r, r->stream_id, r->vio_resource.resource_id,
+ r->vio_resource.num_planes, r->vio_resource.planes_layout,
+ r->vio_q_cmd);
+ return r;
+ }
+ }
+ return NULL;
+}
+
+#define EVENT_WQ_IDX 1
+
+static void *stream_worker_thread(gpointer data)
+{
+ int ret;
+ struct stream *s = data;
+ VuVideo *v = s->video;
+ VugDev *vugdev = &v->dev;
+ VuDev *vudev = &vugdev->parent;
+ VuVirtq *vq = vu_get_queue(vudev, EVENT_WQ_IDX);
+ VuVirtqElement *elem;
+ size_t len;
+
+ struct v4l2_event ev;
+ struct virtio_video_event vio_event;
+
+ /* select vars */
+ fd_set efds, rfds, wfds;
+ bool have_event, have_read, have_write;
+ enum v4l2_buf_type buf_type;
+
+ fcntl(s->fd, F_SETFL, fcntl(s->fd, F_GETFL) | O_NONBLOCK);
+
+ while (true) {
+ int res;
+
+ g_mutex_lock(&s->mutex);
+
+ g_debug("Stream: id %d state %d", s->stream_id, s->stream_state);
+ /* wait for STREAMING or DESTROYING state */
+ while (s->stream_state != STREAM_DESTROYING &&
+ s->stream_state != STREAM_STREAMING &&
+ s->stream_state != STREAM_DRAINING)
+ g_cond_wait(&s->stream_cond, &s->mutex);
+
+ if (s->stream_state == STREAM_DESTROYING) {
+ g_debug("stream worker thread exiting!");
+ s->stream_state = STREAM_DESTROYED;
+ g_cond_signal(&s->stream_cond);
+ g_mutex_unlock(&s->mutex);
+ g_thread_exit(0);
+ }
+
+ g_mutex_unlock(&s->mutex);
+
+ FD_ZERO(&efds);
+ FD_SET(s->fd, &efds);
+ FD_ZERO(&rfds);
+ FD_SET(s->fd, &rfds);
+ FD_ZERO(&wfds);
+ FD_SET(s->fd, &wfds);
+
+ struct timeval tv = { 0 , 500000 };
+ res = select(s->fd + 1, &rfds, &wfds, &efds, &tv);
+ if (res < 0) {
+ g_printerr("%s:%d - select() failed: %s (%d)\n",
+ __func__, __LINE__, g_strerror(errno), errno);
+ break;
+ }
+
+ if (res == 0) {
+ g_debug("%s:%d - select() timeout", __func__, __LINE__);
+ continue;
+ }
+
+ have_event = FD_ISSET(s->fd, &efds);
+ have_read = FD_ISSET(s->fd, &rfds);
+ have_write = FD_ISSET(s->fd, &wfds);
+ /* read is capture queue, write is output queue */
+
+ g_debug("%s:%d have_event=%d, have_write=%d, have_read=%d\n",
+ __func__, __LINE__, FD_ISSET(s->fd, &efds),
+ FD_ISSET(s->fd, &wfds), FD_ISSET(s->fd, &rfds));
+
+ g_mutex_lock(&s->mutex);
+
+ if (have_event) {
+ g_debug("%s: have_event!", __func__);
+ res = ioctl(s->fd, VIDIOC_DQEVENT, &ev);
+ if (res < 0) {
+ g_printerr("%s:%d - VIDIOC_DQEVENT failed: %s (%d)\n",
+ __func__, __LINE__, g_strerror(errno), errno);
+ break;
+ }
+ v4l2_to_virtio_event(&ev, &vio_event);
+
+ /* get event workqueue */
+ elem = vu_queue_pop(vudev, vq, sizeof(struct VuVirtqElement));
+ if (!elem) {
+ g_debug("%s:%d\n", __func__, __LINE__);
+ break;
+ }
+
+ len = iov_from_buf_full(elem->in_sg,
+ elem->in_num, 0, (void *) &vio_event,
+ sizeof(struct virtio_video_event));
+
+ if (vio_event.event_type) {
+ vu_queue_push(vudev, vq, elem, len);
+ vu_queue_notify(vudev, vq);
+ }
+ }
+
+ if (have_read && s->capture_streaming) {
+ /* TODO assumes decoder */
+ buf_type = s->has_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
+ : V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ enum virtio_video_mem_type mem_type =
+ get_queue_mem_type(s, VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
+ enum v4l2_memory memory = get_v4l2_memory(mem_type);
+
+ ret = v4l2_dequeue_buffer(s->fd, buf_type, memory, s);
+ if (ret < 0) {
+ g_info("%s: v4l2_dequeue_buffer() failed CAPTURE ret(%d)",
+ __func__, ret);
+
+ if (ret == -EPIPE) {
+ g_debug("Dequeued last buffer, stop streaming.");
+ /* dequeued last buf, so stop streaming */
+ v4l2_streamoff(s, buf_type);
+ }
+ }
+ }
+
+ if (have_write && s->output_streaming) {
+ buf_type = s->has_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
+ : V4L2_BUF_TYPE_VIDEO_OUTPUT;
+
+ enum virtio_video_mem_type mem_type =
+ get_queue_mem_type(s, VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
+ enum v4l2_memory memory = get_v4l2_memory(mem_type);
+
+ ret = v4l2_dequeue_buffer(s->fd, buf_type, memory, s);
+ if (ret < 0) {
+ g_info("%s: v4l2_dequeue_buffer() failed OUTPUT ret(%d)",
+ __func__, ret);
+ }
+ }
+
+ g_mutex_unlock(&s->mutex);
+ }
+
+ return NULL;
+}
+
+void handle_queue_clear_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *vio_cmd)
+{
+ struct virtio_video_queue_clear *cmd =
+ (struct virtio_video_queue_clear *)vio_cmd->cmd_buf;
+ int ret = 0;
+ struct stream *s;
+ uint32_t stream_id = le32toh(cmd->hdr.stream_id);
+ enum virtio_video_queue_type queue_type = le32toh(cmd->queue_type);
+ GList *res_list = NULL;
+
+ g_debug("%s: stream_id(%d) %s\n", __func__, stream_id,
+ vio_queue_name(queue_type));
+
+ if (!v || !cmd) {
+ return;
+ }
+
+ s = find_stream(v, stream_id);
+ if (!s) {
+ g_critical("%s: stream_id(%d) not found", __func__, stream_id);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ g_mutex_lock(&s->mutex);
+
+ enum v4l2_buf_type buf_type =
+ get_v4l2_buf_type(le32toh(cmd->queue_type), s->has_mplane);
+
+ /*
+ * QUEUE_CLEAR behaviour from virtio-video spec
+ * Return already queued buffers back from the input or the output queue
+ * of the device. The device SHOULD return all of the buffers from the
+ * respective queue as soon as possible without pushing the buffers through
+ * the processing pipeline.
+ *
+ * From v4l2 PoV we issue a VIDIOC_STREAMOFF on the queue which will abort
+ * or finish any DMA in progress, unlocks any user pointer buffers locked
+ * in physical memory, and it removes all buffers from the incoming and
+ * outgoing queues.
+ */
+
+ /* issue streamoff */
+ ret = v4l2_streamoff(s, buf_type);
+ if (ret < 0) {
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+
+ /* iterate the queues resources list - and send a reply to each one */
+
+ /*
+ * If the processing was stopped due to VIRTIO_VIDEO_CMD_QUEUE_CLEAR,
+ * the device MUST respond with VIRTIO_VIDEO_RESP_OK_NODATA as a response
+ * type and VIRTIO_- VIDEO_BUFFER_FLAG_ERR in flags.
+ */
+
+ res_list = get_resource_list(s, queue_type);
+ g_list_foreach(res_list, (GFunc)send_qclear_res_reply, s);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+
+out_unlock:
+ vio_cmd->finished = true;
+ send_ctrl_response_nodata(vio_cmd);
+ g_mutex_unlock(&s->mutex);
+}
+
+GList *get_resource_list(struct stream *s, uint32_t queue_type)
+{
+ switch (queue_type) {
+ case VIRTIO_VIDEO_QUEUE_TYPE_INPUT:
+ return s->inputq_resources;
+ break;
+
+ case VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT:
+ return s->outputq_resources;
+ break;
+ default:
+ g_critical("%s: Unknown queue type!", __func__);
+ return NULL;
+ }
+}
+
+void send_ctrl_response(struct vu_video_ctrl_command *vio_cmd,
+ uint8_t *resp, size_t resp_len)
+{
+ size_t len;
+
+ virtio_video_ctrl_hdr_htole((struct virtio_video_cmd_hdr *)resp);
+
+ /* send virtio_video_resource_queue_resp */
+ len = iov_from_buf_full(vio_cmd->elem.in_sg,
+ vio_cmd->elem.in_num, 0, resp, resp_len);
+
+ if (len != resp_len) {
+ g_critical("%s: response size incorrect %zu vs %zu",
+ __func__, len, resp_len);
+ }
+
+ vu_queue_push(vio_cmd->dev, vio_cmd->vq, &vio_cmd->elem, len);
+ vu_queue_notify(vio_cmd->dev, vio_cmd->vq);
+
+ if (vio_cmd->finished) {
+ g_free(vio_cmd->cmd_buf);
+ free(vio_cmd);
+ }
+}
+
+void send_ctrl_response_nodata(struct vu_video_ctrl_command *vio_cmd)
+{
+ send_ctrl_response(vio_cmd, vio_cmd->cmd_buf,
+ sizeof(struct virtio_video_cmd_hdr));
+}
+
+void send_qclear_res_reply(gpointer data, gpointer user_data)
+{
+ struct resource *r = data;
+ if (!r->queued) {
+ /*
+ * only need to send replies for buffers that are
+ * inflight
+ */
+ return;
+ }
+
+ struct vu_video_ctrl_command *vio_cmd = r->vio_q_cmd;
+ struct virtio_video_queue_clear *cmd =
+ (struct virtio_video_queue_clear *) vio_cmd->cmd_buf;
+ struct virtio_video_resource_queue_resp resp;
+
+
+
+ resp.hdr.stream_id = cmd->hdr.stream_id;
+ resp.hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+ resp.flags = htole32(VIRTIO_VIDEO_BUFFER_FLAG_ERR);
+ resp.timestamp = htole64(r->vio_res_q.timestamp);
+
+ g_debug("%s: stream_id=%d type=0x%x flags=0x%x resource_id=%d t=%llx"
+ , __func__, resp.hdr.stream_id, resp.hdr.type, resp.flags,
+ r->vio_resource.resource_id, resp.timestamp);
+
+ vio_cmd->finished = true;
+ send_ctrl_response(vio_cmd, (uint8_t *) &resp,
+ sizeof(struct virtio_video_resource_queue_resp));
+}
+
+static int
+handle_resource_create_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *vio_cmd)
+{
+ int ret = 0, i;
+ uint32_t total_entries = 0;
+ uint32_t stream_id;
+ struct virtio_video_resource_create *cmd =
+ (struct virtio_video_resource_create *)vio_cmd->cmd_buf;
+ struct resource *res;
+ struct virtio_video_resource_create *r;
+ struct stream *s;
+ enum virtio_video_mem_type mem_type;
+
+ stream_id = cmd->hdr.stream_id;
+
+ s = find_stream(v, stream_id);
+ if (!s) {
+ g_critical("%s: stream_id(%d) not found", __func__, stream_id);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ return ret;
+ }
+
+ g_mutex_lock(&s->mutex);
+
+ if (le32toh(cmd->resource_id) == 0) {
+ g_critical("%s: resource id 0 is not allowed", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+
+ /* check resource id doesn't already exist */
+ res = find_resource(s, le32toh(cmd->resource_id), le32toh(cmd->queue_type));
+ if (res) {
+ g_critical("%s: resource_id:%d already exists"
+ , __func__, le32toh(cmd->resource_id));
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID;
+ goto out_unlock;
+ } else {
+ res = g_new0(struct resource, 1);
+ res->vio_resource.resource_id = le32toh(cmd->resource_id);
+ res->vio_resource.queue_type = le32toh(cmd->queue_type);
+ res->vio_resource.planes_layout = le32toh(cmd->planes_layout);
+
+ res->vio_resource.num_planes = le32toh(cmd->num_planes);
+ r = &res->vio_resource;
+
+ switch (le32toh(cmd->queue_type)) {
+ case VIRTIO_VIDEO_QUEUE_TYPE_INPUT:
+ res->v4l2_index = g_list_length(s->inputq_resources);
+ break;
+ case VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT:
+ res->v4l2_index = g_list_length(s->outputq_resources);
+ break;
+ default:
+ g_critical("%s: invalid queue_type(%s) resource_id(%d)",
+ __func__, vio_queue_name(r->queue_type),
+ le32toh(cmd->resource_id));
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID;
+ goto out_unlock;
+ }
+
+ ret = add_resource(s, res, le32toh(cmd->queue_type));
+ if (ret) {
+ g_critical("%s: resource_add id:%d failed",
+ __func__, le32toh(cmd->resource_id));
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID;
+ goto out_unlock;
+ }
+
+ g_debug("%s: resource=%p streamid(%d) resourceid(%d) numplanes(%d) "
+ "planes_layout(0x%x) %s",
+ __func__, res, res->stream_id, r->resource_id, r->num_planes,
+ r->planes_layout, vio_queue_name(r->queue_type));
+ }
+
+ if (r->planes_layout & VIRTIO_VIDEO_PLANES_LAYOUT_PER_PLANE) {
+ g_debug("%s: streamid(%d) resourceid(%d) planes_layout(0x%x)",
+ __func__, res->stream_id, r->resource_id, r->planes_layout);
+
+ for (i = 0; i < r->num_planes; i++) {
+ total_entries += le32toh(cmd->num_entries[i]);
+ g_debug("%s: streamid(%d) resourceid(%d) num_entries[%d]=%d",
+ __func__, res->stream_id, r->resource_id,
+ i, le32toh(cmd->num_entries[i]));
+ }
+ } else {
+ total_entries = 1;
+ }
+
+ /*
+ * virtio_video_resource_create is followed by either
+ * - struct virtio_video_mem_entry entries[]
+ * for VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES
+ * - struct virtio_video_object_entry entries[]
+ * for VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT
+ */
+
+ mem_type = get_queue_mem_type(s, r->queue_type);
+
+ switch (mem_type) {
+ case VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES:
+ {
+ struct virtio_video_mem_entry *ent;
+ ent = (void *)cmd + sizeof(struct virtio_video_resource_create);
+
+ res->iov = g_malloc0(sizeof(struct iovec) * total_entries);
+ for (i = 0; i < total_entries; i++) {
+ uint64_t len = le32toh(ent[i].length);
+ g_debug("%s: ent[%d] addr=0x%lx",
+ __func__, i, le64toh(ent[i].addr));
+
+ res->iov[i].iov_len = le32toh(ent[i].length);
+ res->iov[i].iov_base =
+ vu_gpa_to_va(&v->dev.parent, &len, le64toh(ent[i].addr));
+ g_debug("%s: [%d] iov_len = 0x%lx", __func__
+ , i, res->iov[i].iov_len);
+ g_debug("%s: [%d] iov_base = 0x%p", __func__
+ , i, res->iov[i].iov_base);
+ }
+ res->iov_count = total_entries;
+ } break;
+ case VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT:
+ g_critical("%s: VIRTIO_OBJECT not implemented!", __func__);
+ /* TODO implement VIRTIO_OBJECT support */
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+
+out_unlock:
+ /* send response */
+ vio_cmd->finished = true;
+ send_ctrl_response_nodata(vio_cmd);
+ g_mutex_unlock(&s->mutex);
+ return ret;
+}
+
+static int
+handle_resource_queue_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *vio_cmd)
+{
+ struct virtio_video_resource_queue *cmd =
+ (struct virtio_video_resource_queue *)vio_cmd->cmd_buf;
+ struct resource *res;
+ struct stream *s;
+ uint32_t stream_id;
+ int ret = 0;
+
+ g_debug("%s: type(0x%x) %s resource_id(%d)", __func__,
+ cmd->hdr.type, vio_queue_name(le32toh(cmd->queue_type)),
+ le32toh(cmd->resource_id));
+ g_debug("%s: num_data_sizes = %d", __func__, le32toh(cmd->num_data_sizes));
+ g_debug("%s: data_sizes[0] = %d", __func__, le32toh(cmd->data_sizes[0]));
+
+ stream_id = cmd->hdr.stream_id;
+
+ s = find_stream(v, stream_id);
+ if (!s) {
+ g_critical("%s: stream_id(%d) not found", __func__, stream_id);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ return ret;
+ }
+
+ g_mutex_lock(&s->mutex);
+
+ if (cmd->resource_id == 0) {
+ g_critical("%s: resource id 0 is not allowed", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID;
+ goto out_unlock;
+ }
+
+ if (le32toh(cmd->queue_type) == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) {
+ if (g_list_length(s->inputq_resources) && !s->output_bufcount) {
+ ret = video_resource_create(s, le32toh(cmd->queue_type),
+ g_list_length(s->inputq_resources));
+ if (ret < 0) {
+ g_critical("%s: output buffer allocation failed", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+ }
+ } else {
+ if (g_list_length(s->outputq_resources) && !s->capture_bufcount) {
+ ret = video_resource_create(s, le32toh(cmd->queue_type),
+ g_list_length(s->outputq_resources));
+ if (ret < 0) {
+ g_critical("%s: capture buffer allocation failed", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+ }
+ }
+
+ /* get resource object */
+ res = find_resource(s, le32toh(cmd->resource_id), le32toh(cmd->queue_type));
+ if (!res) {
+ g_critical("%s: resource_id:%d does not exist!"
+ , __func__, le32toh(cmd->resource_id));
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID;
+ goto out_unlock;
+ }
+
+ res->vio_res_q.timestamp = le64toh(cmd->timestamp);
+ res->vio_res_q.num_data_sizes = le32toh(cmd->num_data_sizes);
+ res->vio_res_q.queue_type = le32toh(cmd->queue_type);
+ res->vio_q_cmd = vio_cmd;
+
+ g_debug("%s: res=%p res->vio_q_cmd=0x%p", __func__, res, res->vio_q_cmd);
+
+ enum v4l2_buf_type buf_type = get_v4l2_buf_type(
+ cmd->queue_type, s->has_mplane);
+ enum virtio_video_mem_type mem_type =
+ get_queue_mem_type(s, cmd->queue_type);
+ enum v4l2_memory memory = get_v4l2_memory(mem_type);
+
+ ret = v4l2_queue_buffer(buf_type, memory, cmd, res, s, v->v4l2_dev);
+ if (ret < 0) {
+ g_critical("%s: v4l2_queue_buffer failed", __func__);
+ /* virtio error set by v4l2_queue_buffer */
+ goto out_unlock;
+ }
+
+ /*
+ * let the stream worker thread do the dequeueing of output and
+ * capture queue buffers and send the resource_queue replies
+ */
+
+ g_mutex_unlock(&s->mutex);
+ return ret;
+
+out_unlock:
+ /* send response */
+ vio_cmd->finished = true;
+ send_ctrl_response_nodata(vio_cmd);
+ g_mutex_unlock(&s->mutex);
+ return ret;
+}
+
+
+static void
+handle_resource_destroy_all_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *vio_cmd)
+{
+ struct virtio_video_resource_destroy_all *cmd =
+ (struct virtio_video_resource_destroy_all *)vio_cmd->cmd_buf;
+ enum v4l2_buf_type buf_type;
+ struct stream *s;
+ int ret = 0;
+
+ g_debug("%s: type(0x%x) %s stream_id(%d)", __func__,
+ cmd->hdr.type, vio_queue_name(le32toh(cmd->queue_type)),
+ cmd->hdr.stream_id);
+
+ s = find_stream(v, cmd->hdr.stream_id);
+ if (!s) {
+ g_critical("%s: stream_id(%d) not found", __func__, cmd->hdr.stream_id);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ g_mutex_lock(&s->mutex);
+
+ buf_type = get_v4l2_buf_type(le32toh(cmd->queue_type), s->has_mplane);
+ enum virtio_video_mem_type mem_type =
+ get_queue_mem_type(s, cmd->queue_type);
+
+ ret = video_free_buffers(s->fd, buf_type, get_v4l2_memory(mem_type));
+ if (ret) {
+ g_critical("%s: video_free_buffers() failed", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ remove_all_resources(s, le32toh(cmd->queue_type));
+
+ /* free resource objects from queue list */
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+
+out:
+ vio_cmd->finished = true;
+ send_ctrl_response_nodata(vio_cmd);
+ g_mutex_unlock(&s->mutex);
+}
+
+static void
+handle_stream_create_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *vio_cmd)
+{
+ int ret = 0;
+ struct stream *s;
+ uint32_t req_stream_id;
+ uint32_t coded_format;
+
+ struct virtio_video_stream_create *cmd =
+ (struct virtio_video_stream_create *)vio_cmd->cmd_buf;
+
+ g_debug("%s: type(0x%x) stream_id(%d) in_mem_type(0x%x) "
+ "out_mem_type(0x%x) coded_format(0x%x)",
+ __func__, cmd->hdr.type, cmd->hdr.stream_id,
+ le32toh(cmd->in_mem_type), le32toh(cmd->out_mem_type),
+ le32toh(cmd->coded_format));
+
+ req_stream_id = cmd->hdr.stream_id;
+ coded_format = le32toh(cmd->coded_format);
+
+ if ((le32toh(cmd->in_mem_type) == VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT) ||
+ (le32toh(cmd->out_mem_type) == VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT)) {
+ /* TODO implement VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT */
+ g_printerr("%s: MEM_TYPE_VIRTIO_OBJECT not supported yet", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ } else if (find_stream(v, req_stream_id)) {
+ g_debug("%s: Stream ID in use - ", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID;
+ } else {
+ s = g_new0(struct stream, 1);
+ /* copy but bswap */
+ s->vio_stream.in_mem_type = le32toh(cmd->in_mem_type);
+ s->vio_stream.out_mem_type = le32toh(cmd->out_mem_type);
+ s->vio_stream.coded_format = le32toh(cmd->coded_format);
+ strncpy((char *)&s->vio_stream.tag, (char *)cmd->tag,
+ sizeof(cmd->tag) - 1);
+ s->vio_stream.tag[sizeof(cmd->tag) - 1] = 0;
+ s->stream_id = req_stream_id;
+ s->video = v;
+ s->stream_state = STREAM_STOPPED;
+ s->has_mplane = v->v4l2_dev->has_mplane;
+ g_mutex_init(&s->mutex);
+ g_cond_init(&s->stream_cond);
+ v->streams = g_list_append(v->streams, s);
+
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+
+ /* set the requested coded format */
+ ret = v4l2_stream_create(v->v4l2_dev, coded_format, s);
+ if (ret < 0) {
+ g_printerr("%s: v4l2_stream_create() failed", __func__);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+
+ v->streams = g_list_remove(v->streams, s);
+ g_free(s);
+ }
+
+ /*
+ * create thread to handle
+ * - dequeing buffers from output & capture queues
+ * - sending resource replies for buffers
+ * - handling EOS and dynamic-resoltion events
+ */
+ s->worker_thread = g_thread_new("vio-video stream worker",
+ stream_worker_thread, s);
+ }
+
+ /* send response */
+ vio_cmd->finished = true;
+ send_ctrl_response_nodata(vio_cmd);
+}
+
+static void
+handle_stream_drain_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *vio_cmd)
+{
+ int ret;
+ struct stream *s;
+ uint32_t stream_id;
+ struct virtio_video_stream_drain *cmd =
+ (struct virtio_video_stream_drain *)vio_cmd->cmd_buf;
+
+ stream_id = cmd->hdr.stream_id;
+
+ g_debug("%s: stream_id(%d)", __func__, stream_id);
+
+ s = find_stream(v, stream_id);
+ if (!s) {
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID;
+ return;
+ }
+
+ g_debug("%s: Found stream=0x%p", __func__, s);
+
+ g_mutex_lock(&s->mutex);
+
+ ret = v4l2_issue_cmd(s->fd, V4L2_DEC_CMD_STOP, 0);
+ if (ret < 0) {
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER;
+ goto out_unlock;
+ }
+ s->stream_state = STREAM_DRAINING;
+ g_cond_signal(&s->stream_cond);
+
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+
+out_unlock:
+ vio_cmd->finished = true;
+ send_ctrl_response_nodata(vio_cmd);
+ g_mutex_unlock(&s->mutex);
+}
+
+static void
+handle_stream_destroy_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *vio_cmd)
+{
+ struct stream *s;
+ uint32_t stream_id;
+ struct virtio_video_stream_destroy *cmd =
+ (struct virtio_video_stream_destroy *)vio_cmd->cmd_buf;
+ enum v4l2_buf_type buftype;
+
+ if (!v || !vio_cmd) {
+ return;
+ }
+
+ stream_id = cmd->hdr.stream_id;
+
+ g_debug("%s: stream_id=(%d)", __func__, stream_id);
+
+ s = find_stream(v, stream_id);
+
+ if (!s) {
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID;
+ return;
+ }
+
+ if (s) {
+ g_debug("%s: Found stream=0x%p", __func__, s);
+
+ g_mutex_lock(&s->mutex);
+ /* TODO assumes decoder */
+ buftype = s->has_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
+ : V4L2_BUF_TYPE_VIDEO_OUTPUT;
+
+ video_streamoff(s, buftype);
+
+ /* signal worker thread */
+ s->stream_state = STREAM_DESTROYING;
+ g_cond_signal(&s->stream_cond);
+ g_mutex_unlock(&s->mutex);
+
+ /* wait for DESTROYED state */
+ g_mutex_lock(&s->mutex);
+ while (s->stream_state != STREAM_DESTROYED) {
+ g_cond_wait(&s->stream_cond, &s->mutex);
+ }
+
+ /* stream worker thread now exited */
+
+ enum virtio_video_mem_type mem_type = \
+ get_queue_mem_type(s, VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
+ /* deallocate the buffers */
+ video_free_buffers(s->fd, buftype, get_v4l2_memory(mem_type));
+ remove_all_resources(s, VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
+
+ buftype = s->has_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
+ V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ mem_type = get_queue_mem_type(s, VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
+ video_free_buffers(s->fd, buftype, get_v4l2_memory(mem_type));
+ remove_all_resources(s, VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
+
+ g_cond_clear(&s->stream_cond);
+
+ v4l2_close(s->fd);
+
+ v->streams = g_list_remove(v->streams, (gconstpointer) s);
+ cmd->hdr.type = VIRTIO_VIDEO_RESP_OK_NODATA;
+ }
+
+ /* send response */
+ vio_cmd->finished = true;
+ send_ctrl_response_nodata(vio_cmd);
+ g_mutex_unlock(&s->mutex);
+ g_mutex_clear(&s->mutex);
+ g_free(s);
+
+ return;
+}
+
+struct virtio_video_get_control_resp_level {
+ struct virtio_video_cmd_hdr hdr;
+ struct virtio_video_control_val_level level;
+};
+
+struct virtio_video_get_control_resp_profile {
+ struct virtio_video_cmd_hdr hdr;
+ struct virtio_video_control_val_profile profile;
+};
+
+struct virtio_video_get_control_resp_bitrate {
+ struct virtio_video_cmd_hdr hdr;
+ struct virtio_video_control_val_bitrate bitrate;
+};
+
+static int
+handle_query_control_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *cmd)
+{
+ int ret;
+ uint32_t v4l2_control;
+ int32_t value;
+
+ struct virtio_video_query_control_resp ctl_resp;
+
+ struct virtio_video_query_control *qcmd =
+ (struct virtio_video_query_control *)cmd->cmd_buf;
+
+ g_debug("%s: type(0x%x) stream_id(%d) control(0x%x)", __func__,
+ qcmd->hdr.type, qcmd->hdr.stream_id, le32toh(qcmd->control));
+
+ v4l2_control = virtio_video_control_to_v4l2(le32toh(qcmd->control));
+ if (!v4l2_control) {
+ goto out_err_unlock;
+ }
+
+
+ ctl_resp.hdr.stream_id = qcmd->hdr.stream_id;
+ ctl_resp.hdr.type = VIRTIO_VIDEO_RESP_OK_QUERY_CONTROL;
+
+ switch (le32toh(qcmd->control)) {
+ case VIRTIO_VIDEO_CONTROL_PROFILE:
+ struct virtio_video_query_control_resp_profile *ctl_resp_profile;
+ ctl_resp_profile = (void *)&ctl_resp +
+ sizeof(struct virtio_video_query_control_resp_profile);
+
+ g_debug("%s: VIRTIO_VIDEO_CONTROL_PROFILE", __func__);
+
+ ret = v4l2_ioctl_query_control(v->v4l2_dev->fd, v4l2_control, &value);
+ if (ret < 0) {
+ g_printerr("v4l2_ioctl_query_control() failed\n");
+ goto out_err_unlock;
+ }
+
+ ctl_resp_profile->num = htole32(value);
+
+ cmd->finished = true;
+ send_ctrl_response(cmd, (uint8_t *)&ctl_resp_profile,
+ sizeof(ctl_resp_profile));
+
+ break;
+
+ case VIRTIO_VIDEO_CONTROL_LEVEL:
+ struct virtio_video_query_control_resp_level *ctl_resp_level;
+ ctl_resp_level = (void *)&ctl_resp +
+ sizeof(struct virtio_video_query_control_resp_level);
+
+ g_debug("%s: VIRTIO_VIDEO_CONTROL_LEVEL", __func__);
+
+ ret = v4l2_ioctl_query_control(v->v4l2_dev->fd, v4l2_control, &value);
+ if (ret < 0) {
+ g_printerr("v4l2_ioctl_query_control() failed\n");
+ goto out_err_unlock;
+ }
+
+ ctl_resp_level->num = htole32(value);
+
+ cmd->finished = true;
+ send_ctrl_response(cmd, (uint8_t *)&ctl_resp_level,
+ sizeof(ctl_resp_profile));
+ break;
+
+ default:
+ g_critical("Unknown control requested!");
+ goto out_err_unlock;
+ break;
+ }
+
+ return 0;
+
+out_err_unlock:
+ ctl_resp.hdr.stream_id = qcmd->hdr.stream_id;
+ ctl_resp.hdr.type = VIRTIO_VIDEO_RESP_ERR_UNSUPPORTED_CONTROL;
+ cmd->finished = true;
+ send_ctrl_response(cmd, (uint8_t *)&ctl_resp,
+ sizeof(struct virtio_video_query_control_resp));
+ return -EINVAL;
+}
+
+static int
+handle_get_control_cmd(struct VuVideo *v, struct vu_video_ctrl_command *vio_cmd)
+{
+ int ret;
+ uint32_t v4l2_control;
+ int32_t value;
+
+ struct virtio_video_get_control_resp ctl_resp_err;
+ struct virtio_video_get_control_resp_level ctl_resp_level;
+ struct virtio_video_get_control_resp_profile ctl_resp_profile;
+ struct virtio_video_get_control_resp_bitrate ctl_resp_bitrate;
+
+ struct stream *s;
+
+ struct virtio_video_get_control *cmd =
+ (struct virtio_video_get_control *)vio_cmd->cmd_buf;
+
+ g_debug("%s: type(0x%x) stream_id(%d) control(0x%x)", __func__,
+ cmd->hdr.type, cmd->hdr.stream_id, le32toh(cmd->control));
+
+ s = find_stream(v, cmd->hdr.stream_id);
+ if (!s) {
+ g_critical("%s: stream_id(%d) not found", __func__, cmd->hdr.stream_id);
+ return -EINVAL;
+ }
+
+ g_mutex_lock(&s->mutex);
+
+ v4l2_control = virtio_video_control_to_v4l2(le32toh(cmd->control));
+ if (!v4l2_control) {
+ goto out_err_unlock;
+ }
+
+
+ switch (le32toh(cmd->control)) {
+ case VIRTIO_VIDEO_CONTROL_BITRATE:
+ g_debug("%s: VIRTIO_VIDEO_CONTROL_BITRATE", __func__);
+
+ ctl_resp_bitrate.hdr.stream_id = cmd->hdr.stream_id;
+ ctl_resp_bitrate.hdr.type = VIRTIO_VIDEO_RESP_OK_GET_PARAMS;
+
+ if (v->v4l2_dev->dev_type == STATEFUL_ENCODER) {
+ ret = v4l2_ioctl_get_control(s->fd, v4l2_control, &value);
+ if (ret < 0) {
+ g_printerr("v4l2_ioctl_get_control() failed\n");
+ goto out_err_unlock;
+ }
+ ctl_resp_bitrate.bitrate.bitrate = htole32(value);
+
+ } else {
+ g_debug("%s: CONTROL_BITRATE unsupported for decoders!", __func__);
+ goto out_err_unlock;
+ }
+
+ vio_cmd->finished = true;
+ send_ctrl_response(
+ vio_cmd, (uint8_t *)&ctl_resp_bitrate,
+ sizeof(struct virtio_video_get_control_resp_bitrate));
+ break;
+
+ case VIRTIO_VIDEO_CONTROL_PROFILE:
+ g_debug("%s: VIRTIO_VIDEO_CONTROL_PROFILE", __func__);
+
+ ctl_resp_profile.hdr.stream_id = cmd->hdr.stream_id;
+ ctl_resp_profile.hdr.type = VIRTIO_VIDEO_RESP_OK_GET_PARAMS;
+
+ ret = v4l2_ioctl_get_control(s->fd, v4l2_control, &value);
+ if (ret < 0) {
+ g_printerr("v4l2_ioctl_get_control() failed\n");
+ goto out_err_unlock;
+ }
+
+ ctl_resp_profile.profile.profile = htole32(value);
+
+ vio_cmd->finished = true;
+ send_ctrl_response(
+ vio_cmd, (uint8_t *)&ctl_resp_profile,
+ sizeof(struct virtio_video_get_control_resp_profile));
+
+ /*
+ * TODO need to determine "in use" codec to (h264/vp8/vp9) to map to
+ * v4l2 control for PROFILE?
+ */
+
+ break;
+
+ case VIRTIO_VIDEO_CONTROL_LEVEL:
+ g_debug("%s: VIRTIO_VIDEO_CONTROL_LEVEL", __func__);
+
+ ctl_resp_level.hdr.stream_id = cmd->hdr.stream_id;
+ ctl_resp_level.hdr.type = VIRTIO_VIDEO_RESP_OK_GET_PARAMS;
+
+ ret = v4l2_ioctl_get_control(s->fd, v4l2_control, &value);
+ if (ret < 0) {
+ g_printerr("v4l2_ioctl_get_control() failed\n");
+ goto out_err_unlock;
+ }
+
+ ctl_resp_level.level.level = htole32(value);
+
+ vio_cmd->finished = true;
+ send_ctrl_response(vio_cmd, (uint8_t *)&ctl_resp_level,
+ sizeof(struct virtio_video_get_control_resp_level));
+ break;
+
+ case VIRTIO_VIDEO_CONTROL_BITRATE_MODE:
+ case VIRTIO_VIDEO_CONTROL_BITRATE_PEAK:
+ case VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR:
+ g_info("Unsupported control requested");
+ goto out_err_unlock;
+ break;
+
+ default:
+ g_critical("Unknown control requested!");
+ goto out_err_unlock;
+ break;
+ }
+
+ g_mutex_unlock(&s->mutex);
+ return 0;
+
+out_err_unlock:
+ ctl_resp_err.hdr.stream_id = cmd->hdr.stream_id;
+ ctl_resp_err.hdr.type = VIRTIO_VIDEO_RESP_ERR_UNSUPPORTED_CONTROL;
+ vio_cmd->finished = true;
+ send_ctrl_response(vio_cmd, (uint8_t *)&ctl_resp_err,
+ sizeof(struct virtio_video_get_control_resp));
+ g_mutex_unlock(&s->mutex);
+ return -EINVAL;
+}
+
+static int
+handle_query_capability_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *cmd)
+{
+ GList *fmt_l;
+ int ret;
+ enum v4l2_buf_type buf_type;
+ struct virtio_video_query_capability *qcmd =
+ (struct virtio_video_query_capability *)cmd->cmd_buf;
+ GByteArray *querycapresp;
+
+ /* hdr bswapped already */
+ g_debug("%s: type(0x%x) stream_id(%d) %s", __func__,
+ qcmd->hdr.type, qcmd->hdr.stream_id,
+ vio_queue_name(le32toh(qcmd->queue_type)));
+
+ buf_type = get_v4l2_buf_type(le32toh(qcmd->queue_type),
+ v->v4l2_dev->has_mplane);
+
+ /* enumerate formats */
+ ret = video_enum_formats(v->v4l2_dev, buf_type, &fmt_l, false);
+ if (ret < 0) {
+ g_printerr("video_enum_formats failed");
+ return ret;
+ }
+
+ querycapresp = g_byte_array_new();
+ querycapresp = create_query_cap_resp(qcmd, &fmt_l, querycapresp);
+ cmd->finished = true;
+ send_ctrl_response(cmd, querycapresp->data, querycapresp->len);
+
+ video_free_formats(&fmt_l);
+ g_byte_array_free(querycapresp, true);
+
+ return 0;
+}
+
+static void
+vv_process_cmd(VuVideo *video, struct vu_video_ctrl_command *cmd)
+{
+ switch (cmd->cmd_hdr->type) {
+ case VIRTIO_VIDEO_CMD_QUERY_CAPABILITY:
+ handle_query_capability_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_STREAM_CREATE:
+ handle_stream_create_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_STREAM_DESTROY:
+ handle_stream_destroy_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_STREAM_DRAIN:
+ handle_stream_drain_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_RESOURCE_CREATE:
+ handle_resource_create_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_RESOURCE_QUEUE:
+ handle_resource_queue_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL:
+ handle_resource_destroy_all_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_QUEUE_CLEAR:
+ handle_queue_clear_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_GET_PARAMS_EXT:
+ handle_get_params_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_SET_PARAMS_EXT:
+ handle_set_params_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_QUERY_CONTROL:
+ handle_query_control_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_GET_CONTROL:
+ handle_get_control_cmd(video, cmd);
+ break;
+ case VIRTIO_VIDEO_CMD_SET_CONTROL:
+ g_error("**** VIRTIO_VIDEO_CMD_SET_CONTROL unimplemented!");
+ break;
+ default:
+ g_error("Unknown VIRTIO_VIDEO command!");
+ break;
+ }
+}
+
+/* for v3 virtio-video spec currently */
+static void
+video_handle_ctrl(VuDev *dev, int qidx)
+{
+ VuVirtq *vq = vu_get_queue(dev, qidx);
+ VuVideo *video = container_of(dev, VuVideo, dev.parent);
+ size_t cmd_len, len, offset = 0;
+
+ struct vu_video_ctrl_command *cmd;
+
+ for (;;) {
+
+ cmd = vu_queue_pop(dev, vq, sizeof(struct vu_video_ctrl_command));
+ if (!cmd) {
+ break;
+ }
+
+ cmd->vq = vq;
+ cmd->error = 0;
+ cmd->finished = false;
+ cmd->dev = dev;
+
+ cmd_len = iov_size(cmd->elem.out_sg, cmd->elem.out_num);
+ cmd->cmd_buf = g_malloc0(cmd_len);
+ len = iov_to_buf_full(cmd->elem.out_sg, cmd->elem.out_num,
+ offset, cmd->cmd_buf, cmd_len);
+
+ if (len != cmd_len) {
+ g_warning("%s: command size incorrect %zu vs %zu\n",
+ __func__, len, cmd_len);
+ }
+
+ /* header is first on every cmd struct */
+ cmd->cmd_hdr = (struct virtio_video_cmd_hdr *) cmd->cmd_buf;
+ /* bswap header */
+ virtio_video_ctrl_hdr_letoh(cmd->cmd_hdr);
+ g_debug("Received %s cmd", vv_cmd_to_string(cmd->cmd_hdr->type));
+ vv_process_cmd(video, cmd);
+ }
+}
+
+static void
+video_queue_set_started(VuDev *dev, int qidx, bool started)
+{
+ VuVirtq *vq = vu_get_queue(dev, qidx);
+
+ g_debug("queue started %d:%d\n", qidx, started);
+
+ switch (qidx) {
+ case 0:
+ vu_set_queue_handler(dev, vq, started ? video_handle_ctrl : NULL);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * video_process_msg: process messages of vhost-user interface
+ *
+ * Any that are not handled here are processed by the libvhost library
+ * itself.
+ */
+static int video_process_msg(VuDev *dev, VhostUserMsg *msg, int *do_reply)
+{
+ VuVideo *r = container_of(dev, VuVideo, dev.parent);
+
+ g_debug("%s: msg %d", __func__, msg->request);
+
+ switch (msg->request) {
+ case VHOST_USER_NONE:
+ g_main_loop_quit(r->loop);
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static const VuDevIface vuiface = {
+ .set_features = video_set_features,
+ .get_features = video_get_features,
+ .queue_set_started = video_queue_set_started,
+ .process_msg = video_process_msg,
+ .get_config = video_get_config,
+ .set_config = video_set_config,
+};
+
+static void video_destroy(VuVideo *v)
+{
+ vug_deinit(&v->dev);
+ if (socket_path) {
+ unlink(socket_path);
+ }
+
+ v4l2_backend_free(v->v4l2_dev);
+}
+
+/* Print vhost-user.json backend program capabilities */
+static void print_capabilities(void)
+{
+ printf("{\n");
+ printf(" \"type\": \"misc\"\n");
+ printf("}\n");
+}
+
+static gboolean hangup(gpointer user_data)
+{
+ GMainLoop *loop = (GMainLoop *) user_data;
+ g_info("%s: caught hangup/quit signal, quitting main loop", __func__);
+ g_main_loop_quit(loop);
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ GError *error = NULL;
+ GOptionContext *context;
+ g_autoptr(GSocket) socket = NULL;
+ VuVideo video = { };
+
+ context = g_option_context_new("vhost-user emulation of video device");
+ g_option_context_add_main_entries(context, options, "vhost-user-video");
+ if (!g_option_context_parse(context, &argc, &argv, &error)) {
+ g_printerr("option parsing failed: %s\n", error->message);
+ exit(1);
+ }
+
+ g_option_context_free(context);
+
+ if (print_cap) {
+ print_capabilities();
+ exit(0);
+ }
+
+ if (!socket_path && socket_fd < 0) {
+ g_printerr("Please specify either --fd or --socket-path\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (verbose || debug) {
+ g_log_set_handler(NULL, G_LOG_LEVEL_MASK, g_log_default_handler, NULL);
+ if (debug) {
+ g_setenv("G_MESSAGES_DEBUG", "all", true);
+ }
+ } else {
+ g_log_set_handler(NULL,
+ G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL
+ | G_LOG_LEVEL_ERROR,
+ g_log_default_handler, NULL);
+ }
+
+ /*
+ * Open the v4l2 device and enumerate supported formats.
+ * Use this to determine whether it is a stateful encoder/decoder.
+ */
+ if (!v4l2_path || !g_file_test(v4l2_path, G_FILE_TEST_EXISTS)) {
+ g_printerr("Please specify a valid --v4l2-device\n");
+ exit(EXIT_FAILURE);
+ } else {
+ video.v4l2_dev = v4l2_backend_init(v4l2_path);
+ if (!video.v4l2_dev) {
+ g_printerr("v4l2 backend init failed!\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /*
+ * Now create a vhost-user socket that we will receive messages
+ * on. Once we have our handler set up we can enter the glib main
+ * loop.
+ */
+ if (socket_path) {
+ g_autoptr(GSocketAddress) addr = g_unix_socket_address_new(socket_path);
+ g_autoptr(GSocket) bind_socket =
+ g_socket_new(G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT, &error);
+
+ if (!g_socket_bind(bind_socket, addr, false, &error)) {
+ g_printerr("Failed to bind to socket at %s (%s).\n",
+ socket_path, error->message);
+ exit(EXIT_FAILURE);
+ }
+ if (!g_socket_listen(bind_socket, &error)) {
+ g_printerr("Failed to listen on socket %s (%s).\n",
+ socket_path, error->message);
+ }
+ g_message("awaiting connection to %s", socket_path);
+ socket = g_socket_accept(bind_socket, NULL, &error);
+ if (!socket) {
+ g_printerr("Failed to accept on socket %s (%s).\n",
+ socket_path, error->message);
+ }
+ } else {
+ socket = g_socket_new_from_fd(socket_fd, &error);
+ if (!socket) {
+ g_printerr("Failed to connect to FD %d (%s).\n",
+ socket_fd, error->message);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /*
+ * Create the main loop first so all the various sources can be
+ * added. As well as catching signals we need to ensure vug_init
+ * can add it's GSource watches.
+ */
+
+ video.loop = g_main_loop_new(NULL, FALSE);
+ /* catch exit signals */
+ g_unix_signal_add(SIGHUP, hangup, video.loop);
+ g_unix_signal_add(SIGINT, hangup, video.loop);
+
+ if (!vug_init(&video.dev, VHOST_USER_VIDEO_MAX_QUEUES,
+ g_socket_get_fd(socket),
+ video_panic, &vuiface)) {
+ g_printerr("Failed to initialize libvhost-user-glib.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ g_message("entering main loop, awaiting messages");
+ g_main_loop_run(video.loop);
+ g_message("finished main loop, cleaning up");
+
+ g_main_loop_unref(video.loop);
+ video_destroy(&video);
+}
new file mode 100644
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * virtio-video helpers
+ *
+ * Copyright Red Hat, Inc. 2023
+ * Copyright Linaro 2021
+ * Copyright 2019 OpenSynergy GmbH.
+ *
+ * Authors:
+ * Peter Griffin <peter.griffin@linaro.org>
+ * Albert Esteve <aesteve@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <inttypes.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "standard-headers/linux/virtio_video.h"
+#include <linux/videodev2.h>
+#include "v4l2_backend.h"
+#include "virtio_video_helpers.h"
+
+struct virtio_video_convert_table {
+ uint32_t virtio_value;
+ uint32_t v4l2_value;
+};
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+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_NV12, V4L2_PIX_FMT_NV12 },
+ { VIRTIO_VIDEO_FORMAT_YUV420, V4L2_PIX_FMT_YUV420 },
+ { VIRTIO_VIDEO_FORMAT_YVU420, V4L2_PIX_FMT_YVU420 },
+ { 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 },
+ { VIRTIO_VIDEO_FORMAT_FWHT, V4L2_PIX_FMT_FWHT },
+ { 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;
+}
+
+/*
+ * TODO FIXME PROFILE and LEVEL seem wrong here as tied to H264 codec.
+ * V4L2_CID_MPEG_VIDEO_VP9_PROFILE
+ * e.g. https://elixir.bootlin.com/linux/v5.12.1/source/
+ * include/uapi/linux/v4l2-controls.h#L669
+ */
+static struct virtio_video_convert_table control_table[] = {
+ { VIRTIO_VIDEO_CONTROL_BITRATE, V4L2_CID_MPEG_VIDEO_BITRATE },
+ { VIRTIO_VIDEO_CONTROL_BITRATE_PEAK, V4L2_CID_MPEG_VIDEO_BITRATE_PEAK },
+ { VIRTIO_VIDEO_CONTROL_BITRATE_MODE, V4L2_CID_MPEG_VIDEO_BITRATE_MODE },
+ { VIRTIO_VIDEO_CONTROL_PROFILE, V4L2_CID_MPEG_VIDEO_H264_PROFILE },
+ { VIRTIO_VIDEO_CONTROL_LEVEL, V4L2_CID_MPEG_VIDEO_H264_LEVEL },
+ { VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME,
+ V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME },
+ { VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR,
+ V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR },
+ { 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;
+}
+
+/* new helper functions (not from Linux frontend driver) */
+
+const char *vio_queue_name(enum virtio_video_queue_type queue)
+{
+ if (queue == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) {
+ return "Queue Input";
+ }
+ if (queue == VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT) {
+ return "Queue Output";
+ }
+
+ return "Queue type unknown";
+}
+
+
+__le64 virtio_fmtdesc_generate_mask(GList **p_list)
+{
+ uint64_t mask = 0;
+ unsigned int bit = 0;
+ GList *l;
+
+ for (l = *p_list; l != NULL; l = l->next) {
+ mask |= (1 << bit);
+ bit++;
+ }
+ g_debug("%s: mask(0x%lx)\n", __func__, mask);
+
+ return mask;
+}
+
+/* vio_codedformat endian swapped by upper level */
+
+int v4l2_stream_create(struct v4l2_device *dev, uint32_t vio_codedformat,
+ struct stream *s)
+{
+ enum v4l2_buf_type buf_type;
+ uint32_t v4l2_pixformat;
+ int ret;
+
+ /* buf type for coded format depends on device type */
+ if (dev->dev_type & STATEFUL_DECODER) {
+ buf_type = dev->has_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
+ : V4L2_BUF_TYPE_VIDEO_OUTPUT;
+
+ } else if (dev->dev_type & STATEFUL_ENCODER) {
+ buf_type = dev->has_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
+ V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ } else {
+ g_critical("Unknown device type %d!", dev->dev_type);
+ return -EINVAL;
+ }
+
+ s->fd = v4l2_open(dev->devname);
+ if (s->fd < 0) {
+ g_printerr("Error opening device %s: %s (%d).\n", dev->devname,
+ g_strerror(errno), errno);
+ }
+
+ v4l2_pixformat = virtio_video_format_to_v4l2(vio_codedformat);
+ if (v4l2_pixformat == 0) {
+ g_error("%s: virtio to v4l2 format translation failed!", __func__);
+ ret = -EINVAL;
+ return ret;
+ }
+
+ /* set the requested coded format */
+ ret = v4l2_set_pixel_format(s->fd, buf_type, v4l2_pixformat);
+ if (ret < 0) {
+ g_printerr("%s: v4l2_video_set_pixel_format() failed", __func__);
+ }
+
+ return ret;
+}
+
+void v4l2_to_virtio_fmtdesc(struct v4l2_device *dev,
+ struct video_format *vid_fmt,
+ enum v4l2_buf_type type)
+{
+ struct v4l2_fmtdesc *v4l2_fmtdsc = &vid_fmt->fmt;
+ struct virtio_video_format_desc *virtio_fmtdesc = &vid_fmt->desc;
+ enum v4l2_buf_type buftype = V4L2_BUF_TYPE_PRIVATE;
+ int ret;
+
+ if (!vid_fmt) {
+ return;
+ }
+
+ virtio_fmtdesc->format =
+ htole32(virtio_video_v4l2_format_to_virtio(v4l2_fmtdsc->pixelformat));
+
+ /*
+ * To generate the mask we need to check the FORMAT is already set.
+ * before we enumerate the other queue to generate the mask
+ */
+
+ ret = v4l2_set_pixel_format(dev->fd, type, vid_fmt->fmt.pixelformat);
+ if (ret < 0) {
+ g_printerr("%s: v4l2_video_get_format() failed\n", __func__);
+ }
+
+ /* enumerate formats on the other queue now the format is set */
+ GList *vid_fmts_l = NULL;
+
+ if (V4L2_TYPE_IS_OUTPUT(type)) {
+ buftype = V4L2_TYPE_IS_MULTIPLANAR(type) ?
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
+ V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ }
+
+ if (V4L2_TYPE_IS_CAPTURE(type)) {
+ buftype = V4L2_TYPE_IS_MULTIPLANAR(type) ?
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE :
+ V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ }
+
+ ret = video_enum_formats(dev, buftype, &vid_fmts_l, true);
+
+ /*
+ * generate the capability mask. bitset represents the supported
+ * combinations of input and output formats.
+ */
+
+ virtio_fmtdesc->mask = htole64(virtio_fmtdesc_generate_mask(&vid_fmts_l));
+
+ virtio_fmtdesc->planes_layout =
+ htole32(VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER);
+
+ /* TODO need to set plane_align */
+ if ((!v4l2_fmtdsc->flags & V4L2_FMT_FLAG_COMPRESSED) &&
+ (le32toh(virtio_fmtdesc->planes_layout) &
+ VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER)) {
+ g_critical("%s: TODO need to set plane_align field", __func__);
+ }
+
+ virtio_fmtdesc->num_frames = htole32(g_list_length(vid_fmt->vid_fmt_frm_l));
+
+ video_free_formats(&vid_fmts_l);
+}
+
+void v4l2_to_virtio_video_params(struct v4l2_device *dev,
+ struct v4l2_format *fmt,
+ struct v4l2_selection *sel,
+ struct virtio_video_get_params_resp *resp)
+{
+ struct virtio_video_params *vid_params = &resp->params;
+ int i;
+
+ /* min/max_buffers default (taken from crosvm) */
+ vid_params->min_buffers = htole32(1);
+ vid_params->max_buffers = htole32(32);
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) {
+ vid_params->format =
+ virtio_video_v4l2_format_to_virtio(fmt->fmt.pix.pixelformat);
+ vid_params->frame_width = htole32(fmt->fmt.pix_mp.width);
+ vid_params->frame_height = htole32(fmt->fmt.pix_mp.height);
+
+ vid_params->num_planes = htole32(fmt->fmt.pix_mp.num_planes);
+
+ for (i = 0; i < fmt->fmt.pix_mp.num_planes; i++) {
+ vid_params->plane_formats[i].stride = \
+ htole32(fmt->fmt.pix_mp.plane_fmt[i].bytesperline);
+
+ vid_params->plane_formats[i].plane_size = \
+ htole32(fmt->fmt.pix_mp.plane_fmt[i].sizeimage);
+
+ g_debug(" ** Stride %u, buffer size %u\n",
+ fmt->fmt.pix_mp.plane_fmt[i].bytesperline,
+ fmt->fmt.pix_mp.plane_fmt[i].sizeimage);
+ }
+ } else if (V4L2_TYPE_IS_SINGLEPLANAR(fmt->type)) {
+ vid_params->format =
+ virtio_video_v4l2_format_to_virtio(fmt->fmt.pix.pixelformat);
+ vid_params->frame_width = htole32(fmt->fmt.pix.width);
+ vid_params->frame_height = htole32(fmt->fmt.pix.height);
+ vid_params->num_planes = htole32(1);
+
+ vid_params->plane_formats[0].stride = \
+ htole32(fmt->fmt.pix.bytesperline);
+
+ vid_params->plane_formats[0].plane_size = \
+ htole32(fmt->fmt.pix.sizeimage);
+ }
+
+ /* cropping rectangle */
+ if (V4L2_TYPE_IS_CAPTURE(fmt->type)) {
+ vid_params->crop.left = htole32(sel->r.left);
+ vid_params->crop.top = htole32(sel->r.top);
+ vid_params->crop.width = htole32(sel->r.width);
+ vid_params->crop.height = htole32(sel->r.height);
+
+ g_debug("%s: crop: left=(%d) top=(%d) width=(%d) height=(%d)",
+ __func__, sel->r.left, sel->r.top, sel->r.width, sel->r.height);
+ }
+
+ /* TODO frame_rate field for encoder */
+}
+
+void v4l2_to_virtio_event(struct v4l2_event *ev,
+ struct virtio_video_event *vio_ev)
+{
+ g_debug("%s: %ld.%06ld: event %u, pending %u",
+ __func__, ev->timestamp.tv_sec, ev->timestamp.tv_nsec / 1000,
+ ev->sequence, ev->pending);
+ /* Reset event type */
+ vio_ev->event_type = 0x0;
+ switch (ev->type) {
+ case V4L2_EVENT_VSYNC:
+ g_debug("vsync\n");
+ break;
+ case V4L2_EVENT_EOS:
+ g_debug("eos\n");
+ break;
+ case V4L2_EVENT_CTRL:
+ g_debug("eos\n");
+ break;
+ case V4L2_EVENT_FRAME_SYNC:
+ g_debug("frame_sync %d\n", ev->u.frame_sync.frame_sequence);
+ break;
+ case V4L2_EVENT_SOURCE_CHANGE:
+ g_debug("source_change!: pad/input=%d changes: %x\n"
+ , ev->id, ev->u.src_change.changes);
+
+ vio_ev->event_type =
+ htole32(VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED);
+ /* TODO need proper mapping from v4l2 streamid to virtio streamid */
+ vio_ev->stream_id = htole32(ev->id) + 1;
+ break;
+ case V4L2_EVENT_MOTION_DET:
+ if (ev->u.motion_det.flags & V4L2_EVENT_MD_FL_HAVE_FRAME_SEQ) {
+ g_debug("motion_det frame %d, regions 0x%x\n",
+ ev->u.motion_det.frame_sequence,
+ ev->u.motion_det.region_mask);
+ } else {
+ g_debug("motion_det regions 0x%x\n", ev->u.motion_det.region_mask);
+ }
+ break;
+ default:
+ if (ev->type >= V4L2_EVENT_PRIVATE_START) {
+ g_debug("unknown private event (%08x)\n", ev->type);
+ } else {
+ g_debug("unknown event (%08x)\n", ev->type);
+ }
+ break;
+ }
+}
new file mode 100644
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * virtio-video helpers
+ *
+ * Copyright Red Hat, Inc. 2023
+ * Copyright Linaro 2021
+ *
+ * Authors:
+ * Peter Griffin <peter.griffin@linaro.org>
+ * Albert Esteve <aesteve@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef VIRTIO_VIDEO_HELPERS_H
+#define VIRTIO_VIDEO_HELPERS_H
+
+#include <stdint.h>
+#include "standard-headers/linux/virtio_video.h"
+#include <linux/videodev2.h>
+#include "libvhost-user-glib.h"
+#include "libvhost-user.h"
+
+/*
+ * Structure to track internal state of VIDEO Device
+ */
+
+typedef struct VuVideo {
+ VugDev dev;
+ struct virtio_video_config virtio_config;
+ GMainLoop *loop;
+ struct v4l2_device *v4l2_dev;
+ GList *streams;
+} VuVideo;
+
+struct v4l2_device {
+ const gchar *devname;
+ unsigned int dev_type;
+ unsigned int capabilities;
+ int fd;
+ int epollfd;
+ int opened;
+ bool has_mplane;
+ bool sup_dyn_res_switching;
+};
+
+struct vu_video_ctrl_command {
+ VuVirtqElement elem;
+ VuVirtq *vq;
+ VuDev *dev;
+ struct virtio_video_cmd_hdr *cmd_hdr;
+ uint32_t error;
+ bool finished;
+ uint8_t *cmd_buf;
+};
+
+
+/*
+ * Structure to track internal state of a Stream
+ */
+
+struct stream {
+ struct virtio_video_stream_create vio_stream;
+ uint32_t stream_id;
+ GList *inputq_resources;
+ GList *outputq_resources;
+ VuVideo *video;
+ GThread *worker_thread;
+ uint32_t stream_state;
+ GMutex mutex;
+ GCond stream_cond;
+ bool output_streaming;
+ bool capture_streaming;
+ bool subscribed_events;
+ bool has_mplane;
+ int fd;
+ uint32_t output_bufcount;
+ uint32_t capture_bufcount;
+};
+
+#define STREAM_STOPPED 1
+#define STREAM_STREAMING 2
+#define STREAM_DRAINING 3
+#define STREAM_DESTROYING 4
+#define STREAM_DESTROYED 5
+
+/* Structure to track resources */
+
+struct resource {
+ uint32_t stream_id;
+ struct virtio_video_resource_create vio_resource;
+ struct virtio_video_resource_queue vio_res_q;
+ struct iovec *iov;
+ uint32_t iov_count;
+ uint32_t v4l2_index;
+ enum v4l2_buf_type type;
+ struct vu_video_ctrl_command *vio_q_cmd;
+ bool queued;
+};
+
+struct video_format_frame_rates {
+ struct virtio_video_format_range frame_rates;
+ struct v4l2_frmivalenum v4l_ival;
+};
+
+struct video_format_frame {
+ struct virtio_video_format_frame frame;
+ struct v4l2_frmsizeenum v4l_framesize;
+ GList *frm_rate_l;
+};
+
+struct video_format {
+ struct v4l2_fmtdesc fmt;
+ struct virtio_video_format_desc desc;
+ GList *vid_fmt_frm_l;
+};
+
+/* function prototypes */
+int v4l2_stream_create(struct v4l2_device *dev,
+ uint32_t vio_codedformat, struct stream *s);
+void v4l2_to_virtio_video_params(struct v4l2_device *dev,
+ struct v4l2_format *fmt,
+ struct v4l2_selection *sel,
+ struct virtio_video_get_params_resp *resp);
+
+void v4l2_to_virtio_fmtdesc(struct v4l2_device *dev,
+ struct video_format *vid_fmt,
+ enum v4l2_buf_type type);
+
+void v4l2_to_virtio_event(struct v4l2_event *ev,
+ struct virtio_video_event *vio_ev);
+
+struct resource *find_resource_by_v4l2index(struct stream *s,
+ enum v4l2_buf_type buf_type,
+ uint32_t v4l2_index);
+/*
+ * The following conversion helpers and tables taken from Linux
+ * frontend driver from opensynergy
+ */
+
+uint32_t virtio_video_level_to_v4l2(uint32_t level);
+uint32_t virtio_video_v4l2_level_to_virtio(uint32_t v4l2_level);
+uint32_t virtio_video_profile_to_v4l2(uint32_t profile);
+uint32_t virtio_video_v4l2_profile_to_virtio(uint32_t v4l2_profile);
+uint32_t virtio_video_format_to_v4l2(uint32_t format);
+uint32_t virtio_video_v4l2_format_to_virtio(uint32_t v4l2_format);
+uint32_t virtio_video_control_to_v4l2(uint32_t control);
+uint32_t virtio_video_v4l2_control_to_virtio(uint32_t v4l2_control);
+__le64 virtio_fmtdesc_generate_mask(GList **p_list);
+
+/* Helpers for logging */
+const char *vio_queue_name(enum virtio_video_queue_type queue);
+
+static inline void
+virtio_video_ctrl_hdr_letoh(struct virtio_video_cmd_hdr *hdr)
+{
+ hdr->type = le32toh(hdr->type);
+ hdr->stream_id = le32toh(hdr->stream_id);
+}
+
+static inline void
+virtio_video_ctrl_hdr_htole(struct virtio_video_cmd_hdr *hdr)
+{
+ hdr->type = htole32(hdr->type);
+ hdr->stream_id = htole32(hdr->stream_id);
+}
+#endif
new file mode 100644
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * vhost-user-video header
+ *
+ * Copyright Red Hat, Inc. 2023
+ * Copyright Linaro 2021
+ *
+ * Authors:
+ * Peter Griffin <peter.griffin@linaro.org>
+ * Albert Esteve <aesteve@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef VUVIDEO_H
+#define VUVIDEO_H
+
+#include "virtio_video_helpers.h"
+#include "v4l2_backend.h"
+#include "vuvideo.h"
+
+GList *get_resource_list(struct stream *s, uint32_t queue_type);
+void send_qclear_res_reply(gpointer data, gpointer user_data);
+
+struct stream *find_stream(struct VuVideo *v, uint32_t stream_id);
+int add_resource(struct stream *s, struct resource *r, uint32_t queue_type);
+int remove_resource(struct stream *s, struct resource *r, uint32_t queue_type);
+struct resource *find_resource(struct stream *s, uint32_t resource_id,
+ uint32_t queue_type);
+
+void send_ctrl_response(struct vu_video_ctrl_command *vio_cmd,
+ uint8_t *resp, size_t resp_len);
+
+void send_ctrl_response_nodata(struct vu_video_ctrl_command *vio_cmd);
+
+void free_resource_mem(struct resource *r);
+void remove_all_resources(struct stream *s, uint32_t queue_type);
+
+void handle_queue_clear_cmd(struct VuVideo *v,
+ struct vu_video_ctrl_command *vio_cmd);
+
+#endif