[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH 8/8] tools/vhost-user-video: Add initial vhost-user-video vmm
From: |
Alex Bennée |
Subject: |
Re: [PATCH 8/8] tools/vhost-user-video: Add initial vhost-user-video vmm |
Date: |
Tue, 11 Jan 2022 16:38:52 +0000 |
User-agent: |
mu4e 1.7.5; emacs 28.0.91 |
Peter Griffin <peter.griffin@linaro.org> writes:
> This vmm translates from virtio-video v3 protocol and writes
> to a v4l2 mem2mem stateful decoder/encoder device [1]. v3 was
> chosen as that is what the virtio-video Linux frontend driver
> implements.
>
> This allows for testing with the v4l2 vicodec test codec [2]
> module in the Linux kernel, and is intended to also be used
> with Arm SoCs that implement a v4l2 stateful decoder/encoder
> drivers.
>
> The advantage of developing & testing with vicodec is that
> is allows quick development on a purely virtual setup with
> qemu and a host Linux kernel. Also it allows ci systems like
> lkft, kernelci to easily test the virtio interface.
>
> Currently conversion from virtio-video to v4l2 stateless m2m
> codec driver or VAAPI drivers is consiered out ot scope as
> is emulation of a decoder device using a something like ffmpeg.
> Although this could be added in the future.
>
> Note some virtio & v4l2 helpers were based off virtio-video
> Linux frontend driver and yavta utility, both GPL v2.
>
> Example host commands
> modprobe vicodec
> vhost-user-video --v4l2-device=/dev/video3 -v --socket-path=video.sock
>
> Run Qemu with
> -device vhost-user-video-pci,chardev=video,id=video
>
> Guest decoder
> v4l2-ctl -d0 -x width=640,height=480 -v width=640,height=480,pixelformat=YU12
> --stream-mmap --stream-out-mmap --stream-from jelly_640_480-420P.fwht
> --stream-to out-jelly-640-480.YU12
>
> [1] https://www.kernel.org/doc/html/latest/userspace-api/media/
> v4l/dev-decoder.html
>
> [2] https://lwn.net/Articles/760650/
>
> Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
> ---
> tools/vhost-user-video/50-qemu-rpmb.json.in | 5 +
> tools/vhost-user-video/main.c | 1680 ++++++++++++++++
> tools/vhost-user-video/meson.build | 10 +
> tools/vhost-user-video/v4l2_backend.c | 1777 +++++++++++++++++
> tools/vhost-user-video/v4l2_backend.h | 99 +
> tools/vhost-user-video/virtio_video_helpers.c | 462 +++++
> tools/vhost-user-video/virtio_video_helpers.h | 166 ++
> tools/vhost-user-video/vuvideo.h | 43 +
> 8 files changed, 4242 insertions(+)
> create mode 100644 tools/vhost-user-video/50-qemu-rpmb.json.in
> create mode 100644 tools/vhost-user-video/main.c
> create mode 100644 tools/vhost-user-video/meson.build
> create mode 100644 tools/vhost-user-video/v4l2_backend.c
> create mode 100644 tools/vhost-user-video/v4l2_backend.h
> create mode 100644 tools/vhost-user-video/virtio_video_helpers.c
> create mode 100644 tools/vhost-user-video/virtio_video_helpers.h
> create mode 100644 tools/vhost-user-video/vuvideo.h
>
> diff --git a/tools/vhost-user-video/50-qemu-rpmb.json.in
> b/tools/vhost-user-video/50-qemu-rpmb.json.in
> new file mode 100644
> index 0000000000..2b033cda56
> --- /dev/null
> +++ b/tools/vhost-user-video/50-qemu-rpmb.json.in
> @@ -0,0 +1,5 @@
> +{
> + "description": "QEMU vhost-user-rpmb",
> + "type": "block",
> + "binary": "@libexecdir@/vhost-user-rpmb"
> +}
I'm spotting a copy and paste error here (filename, description and binary).
> diff --git a/tools/vhost-user-video/main.c b/tools/vhost-user-video/main.c
> new file mode 100644
> index 0000000000..a944efadb6
> --- /dev/null
> +++ b/tools/vhost-user-video/main.c
> @@ -0,0 +1,1680 @@
> +/*
> + * VIRTIO Video Emulation via vhost-user
> + *
> + * Copyright (c) 2021 Linaro Ltd
> + *
> + * 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,
> +};
> +
> +/* taken from util/iov.c */
> +size_t video_iov_size(const struct iovec *iov, const unsigned int iov_cnt)
> +{
> + size_t len;
> + unsigned int i;
> +
> + len = 0;
> + for (i = 0; i < iov_cnt; i++) {
> + len += iov[i].iov_len;
> + }
> + return len;
> +}
> +
> +static size_t video_iov_to_buf(const struct iovec *iov,
> + const unsigned int iov_cnt,
> + size_t offset, void *buf, size_t bytes)
> +{
> + size_t done;
> + unsigned int i;
> + for (i = 0, done = 0; (offset || done < bytes) && i < iov_cnt; i++) {
> + if (offset < iov[i].iov_len) {
> + size_t len = MIN(iov[i].iov_len - offset, bytes - done);
> + memcpy(buf + done, iov[i].iov_base + offset, len);
> + done += len;
> + offset = 0;
> + } else {
> + offset -= iov[i].iov_len;
> + }
> + }
> + assert(offset == 0);
> + return done;
> +}
> +
> +static size_t video_iov_from_buf(const struct iovec *iov, unsigned int
> iov_cnt,
> + size_t offset, const void *buf, size_t
> bytes)
> +{
> + size_t done;
> + unsigned int i;
> + for (i = 0, done = 0; (offset || done < bytes) && i < iov_cnt; i++) {
> + if (offset < iov[i].iov_len) {
> + size_t len = MIN(iov[i].iov_len - offset, bytes - done);
> + memcpy(iov[i].iov_base + offset, buf + done, len);
> + done += len;
> + offset = 0;
> + } else {
> + offset -= iov[i].iov_len;
> + }
> + }
> + assert(offset == 0);
> + return done;
> +}
> +
> +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__);
> + /* ignore */
> + 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(x%x) stream_id(%d) %s ", __func__,
> + cmd->hdr.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;
> + goto out;
> + }
I think you can just return from here as no clean-up is required.
> +
> + g_mutex_lock(&s->mutex);
It is possible to use the g_autofree stuff to simplify this but as we
are avoiding bringing in QEMU code we can't use WITH_QEMU_LOCK_GUARD :-/
> +
> + 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 (is_capture_queue(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);
> +out:
> + 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;
> + goto out;
> + }
> +
> + 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 (is_capture_queue(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);
> +out:
> + return;
> +}
> +
> +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__);
> + }
> +
> + for (; l != NULL; l = l->next) {
> + r = (struct resource *)l->data;
> + if (r->vio_resource.resource_id == resource_id) {
> + return r;
> + }
> + }
Given the iteration here would it be worth tracking the struct resource
in a GArray rather than chasing pointers in a linked list?
> +
> + return NULL;
> +}
> +
> +struct resource *find_resource_by_v4l2index(struct stream *s,
> + enum v4l2_buf_type buf_type,
> + uint32_t v4l2_index)
> +{
> + GList *l = NULL;
> + 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");
> + }
> +
> + 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);
> +
> + /* 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 errno(%s)\n", __func__,
> + __LINE__, g_strerror(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: errno(%s)\n",
> + __func__, __LINE__, g_strerror(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 = video_iov_from_buf(elem->in_sg,
> + elem->in_num, 0, (void *)
> &vio_event,
> + sizeof(struct virtio_video_event));
> +
> + vu_queue_push(vudev, vq, elem, len);
> + vu_queue_notify(vudev, vq);
> + }
> +
> + if (have_read && s->capture_streaming == true) {
> + /* TODO assumes decoder */
> + buf_type = s->has_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
> + : V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +
> + ret = v4l2_dequeue_buffer(s->fd, buf_type, s);
> + if (ret < 0) {
> + g_info("%s: v4l2_dequeue_buffer() failed CAPTURE ret(%d)"
> + , __func__, ret);
> +
> + if (errno == EPIPE) {
> + /* dequeued last buf, so stop streaming */
> + ioctl_streamoff(s, buf_type);
> + }
> + }
> + }
> +
> + if (have_write && s->output_streaming == true) {
> + buf_type = s->has_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
> + : V4L2_BUF_TYPE_VIDEO_OUTPUT;
> +
> + ret = v4l2_dequeue_buffer(s->fd, buf_type, 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);
> +
> + 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;
> + goto out;
> + }
> +
> + 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 = ioctl_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.
> + */
> +
> + g_list_foreach(get_resource_list(s, queue_type),
> + (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);
> +out:
> + return;
> +}
> +
> +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 = video_iov_from_buf(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;
> + 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;
> +
> + /*
> + * only need to send replies for buffers that are
> + * inflight
> + */
> +
> + if (r->queued) {
> +
> + 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));
> + }
> + return;
> +}
> +
> +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 virtio_video_mem_entry *mem;
> + 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;
> + goto out;
> + }
> +
> + 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;
> +
> + 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
> + */
> +
> + if (r->queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) {
> + mem_type = s->vio_stream.in_mem_type;
> + } else {
> + mem_type = s->vio_stream.out_mem_type;
> + }
> + /*
> + * 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
> + */
> +
> + if (mem_type == VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES) {
> + mem = (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(mem[i].length);
> + g_debug("%s: mem[%d] addr=0x%lx", __func__
> + , i, le64toh(mem[i].addr));
> +
> + res->iov[i].iov_len = le32toh(mem[i].length);
> + res->iov[i].iov_base =
> + vu_gpa_to_va(&v->dev.parent, &len, le64toh(mem[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;
> +
> + } else if (mem_type == 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;
> + }
> +
> + /* check underlying driver supports GUEST_PAGES */
> + enum v4l2_buf_type buf_type =
> + get_v4l2_buf_type(r->queue_type, s->has_mplane);
> +
> + ret = v4l2_resource_create(s, buf_type, mem_type, res);
> + if (ret < 0) {
> + 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);
> +out:
> + 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;
> + goto out;
> + }
> +
> + 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;
> + }
> +
> + /* 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);
> +
> +
> + ret = v4l2_queue_buffer(s->fd, buf_type, 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);
> +out:
> + 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);
> +
> + ret = v4l2_free_buffers(s->fd, buf_type);
> + if (ret) {
> + g_critical("%s: v4l2_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;
> + goto out;
> + }
> +
> + if (!find_stream(v, req_stream_id)) {
> + 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;
> + } else {
> + g_debug("%s: Stream ID in use - ", __func__);
> + cmd->hdr.type = VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID;
> + goto out;
> + }
Couldn't you avoid the goto by folding this in a level:
if (le32toh....) {
...
} else if (find_stream()) {
...
} else {
.. fall through case ..
v4l_stream_create...
g_thread_new
}
send_ctrl_response()
I know gotos are the kernel style but we can at least try to avoid them ;-)
I've run out of steam here (3000 lines is a lot for one patch)... I'll
do another pass on the next revision.
--
Alex Bennée
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- Re: [PATCH 8/8] tools/vhost-user-video: Add initial vhost-user-video vmm,
Alex Bennée <=