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.