aboutsummaryrefslogtreecommitdiffstats
path: root/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c
diff options
context:
space:
mode:
authorVasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>2021-11-22 12:38:06 +0100
committerVasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>2021-12-07 20:47:22 +0100
commit87b7cce6fb8a0afde96227423b3385d36e6fe0a1 (patch)
tree052311f8fd70ed9ed4efa0fc2f43bc2d5c452b85 /meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c
parent3c2b0cdba520c260a4342d9b968d9efb7892a643 (diff)
virtualization: Add virtio-video driver as external module.
This driver should conform WIP spec v3 [1] with some updates for spec v4 [2], and, some unspecified features such as VIRTIO_VIDEO_DEVICE_CAMERA. Imported from internal OpenSynergy's revision: bcc33b6b9e0156b381a70c54d2df02c57b63d270 Kernel was configured with necessary features for this driver: enable MEDIA_SUPPORT disable MEDIA_SUBDRV_AUTOSELECT enable MEDIA_PLATFORM_SUPPORT enable VIDEO_VIRTIO Keep driver as an external module to simplify future updates. [1]: https://lists.oasis-open.org/archives/virtio-dev/202002/msg00002.html [2]: https://lists.oasis-open.org/archives/virtio-dev/202006/msg00072.html Bug-AGL: SPEC-4148 Change-Id: Iea339194b22443f67b3e2ffddca84118357a2f15 Signed-off-by: Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>
Diffstat (limited to 'meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c')
-rw-r--r--meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c1282
1 files changed, 1282 insertions, 0 deletions
diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c
new file mode 100644
index 00000000..2e1f90ae
--- /dev/null
+++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_device.c
@@ -0,0 +1,1282 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Driver for virtio video device.
+ *
+ * Copyright 2020 OpenSynergy GmbH.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/version.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "virtio_video.h"
+
+enum video_stream_state virtio_video_state(struct virtio_video_stream *stream)
+{
+ return atomic_read(&stream->state);
+}
+
+void virtio_video_state_reset(struct virtio_video_stream *stream)
+{
+ atomic_set(&stream->state, STREAM_STATE_IDLE);
+}
+
+void virtio_video_state_update(struct virtio_video_stream *stream,
+ enum video_stream_state new_state)
+{
+ enum video_stream_state prev_state;
+
+ do {
+ prev_state = atomic_read(&stream->state);
+ if (prev_state == STREAM_STATE_ERROR)
+ return;
+ } while (atomic_cmpxchg(&stream->state, prev_state, new_state) !=
+ prev_state);
+}
+
+int virtio_video_pending_buf_list_empty(struct virtio_video_device *vvd)
+{
+ int ret = 0;
+
+ if (vvd->is_m2m_dev) {
+ v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n");
+ return -EPERM;
+ }
+
+ spin_lock(&vvd->pending_buf_list_lock);
+ if (list_empty(&vvd->pending_buf_list))
+ ret = 1;
+ spin_unlock(&vvd->pending_buf_list_lock);
+
+ return ret;
+}
+
+int virtio_video_pending_buf_list_pop(struct virtio_video_device *vvd,
+ struct virtio_video_buffer **virtio_vb)
+{
+ struct virtio_video_buffer *retbuf;
+
+ if (vvd->is_m2m_dev) {
+ v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n");
+ return -EPERM;
+ }
+
+ spin_lock(&vvd->pending_buf_list_lock);
+ if (list_empty(&vvd->pending_buf_list)) {
+ spin_unlock(&vvd->pending_buf_list_lock);
+ return -EAGAIN;
+ }
+
+ retbuf = list_first_entry(&vvd->pending_buf_list,
+ struct virtio_video_buffer, list);
+ spin_unlock(&vvd->pending_buf_list_lock);
+
+ *virtio_vb = retbuf;
+ return 0;
+}
+
+int virtio_video_pending_buf_list_add(struct virtio_video_device *vvd,
+ struct virtio_video_buffer *virtio_vb)
+{
+ if (vvd->is_m2m_dev) {
+ v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n");
+ return -EPERM;
+ }
+
+ spin_lock(&vvd->pending_buf_list_lock);
+ list_add_tail(&virtio_vb->list, &vvd->pending_buf_list);
+ spin_unlock(&vvd->pending_buf_list_lock);
+
+ return 0;
+}
+
+int virtio_video_pending_buf_list_del(struct virtio_video_device *vvd,
+ struct virtio_video_buffer *virtio_vb)
+{
+ struct virtio_video_buffer *vb, *vb_tmp;
+ int ret = -EINVAL;
+
+ if (vvd->is_m2m_dev) {
+ v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n");
+ return -EPERM;
+ }
+
+ spin_lock(&vvd->pending_buf_list_lock);
+ if (list_empty(&vvd->pending_buf_list)) {
+ spin_unlock(&vvd->pending_buf_list_lock);
+ return -EAGAIN;
+ }
+
+ list_for_each_entry_safe(vb, vb_tmp, &vvd->pending_buf_list, list) {
+ if (vb->resource_id == virtio_vb->resource_id) {
+ list_del(&vb->list);
+ ret = 0;
+ break;
+ }
+ }
+ spin_unlock(&vvd->pending_buf_list_lock);
+
+ return ret;
+}
+
+int virtio_video_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers,
+ unsigned int *num_planes, unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ int i;
+ struct virtio_video_stream *stream = vb2_get_drv_priv(vq);
+ struct video_format_info *p_info;
+
+ if (virtio_video_state(stream) == STREAM_STATE_ERROR)
+ return -EIO;
+
+ if (*num_planes)
+ return 0;
+
+ if (V4L2_TYPE_IS_OUTPUT(vq->type))
+ p_info = &stream->in_info;
+ else
+ p_info = &stream->out_info;
+
+ *num_planes = p_info->num_planes;
+
+ for (i = 0; i < p_info->num_planes; i++)
+ sizes[i] = p_info->plane_format[i].plane_size;
+
+ return 0;
+}
+
+static unsigned int
+build_virtio_video_sglist_contig(struct virtio_video_resource_sg_list *sgl,
+ struct vb2_buffer *vb, unsigned int plane)
+{
+ sgl->entries[0].addr = cpu_to_le64(vb2_dma_contig_plane_dma_addr(vb, plane));
+ sgl->entries[0].length = cpu_to_le32(vb->planes[plane].length);
+
+ sgl->num_entries = 1;
+
+ return VIRTIO_VIDEO_RESOURCE_SG_SIZE(1);
+}
+
+static unsigned int
+build_virtio_video_sglist(struct virtio_video_resource_sg_list *sgl,
+ struct vb2_buffer *vb, unsigned int plane,
+ bool has_iommu)
+{
+ int i;
+ struct scatterlist *sg;
+ struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, plane);
+
+ for_each_sg(sgt->sgl, sg, sgt->nents, i) {
+ sgl->entries[i].addr = cpu_to_le64(has_iommu
+ ? sg_dma_address(sg)
+ : sg_phys(sg));
+ sgl->entries[i].length = cpu_to_le32(sg->length);
+ }
+
+ sgl->num_entries = sgt->nents;
+
+ return VIRTIO_VIDEO_RESOURCE_SG_SIZE(sgt->nents);
+}
+
+int virtio_video_buf_init(struct vb2_buffer *vb)
+{
+ int ret = 0;
+ void *buf;
+ size_t buf_size = 0;
+ struct virtio_video_resource_sg_list *sg_list;
+ unsigned int i, offset = 0, resource_id, nents = 0;
+ struct vb2_queue *vq = vb->vb2_queue;
+ enum v4l2_buf_type queue_type = vq->type;
+ struct virtio_video_stream *stream = vb2_get_drv_priv(vq);
+ struct virtio_video_buffer *virtio_vb = to_virtio_vb(vb);
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+
+ if (vvd->supp_non_contig) {
+ for (i = 0; i < vb->num_planes; i++) {
+ nents = vb2_dma_sg_plane_desc(vb, i)->nents;
+ buf_size += VIRTIO_VIDEO_RESOURCE_SG_SIZE(nents);
+ }
+
+ buf = kcalloc(1, buf_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < vb->num_planes; i++) {
+ sg_list = buf + offset;
+ offset += build_virtio_video_sglist(sg_list, vb, i,
+ vvd->has_iommu);
+ }
+ } else {
+ buf_size = vb->num_planes * VIRTIO_VIDEO_RESOURCE_SG_SIZE(nents);
+
+ buf = kcalloc(1, buf_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < vb->num_planes; i++) {
+ sg_list = buf + offset;
+ offset += build_virtio_video_sglist_contig(sg_list,
+ vb, i);
+ }
+ }
+
+ virtio_video_resource_id_get(vvd, &resource_id);
+
+ ret = virtio_video_cmd_resource_attach(vvd, stream->stream_id,
+ resource_id,
+ to_virtio_queue_type(queue_type),
+ buf, buf_size);
+ if (ret) {
+ virtio_video_resource_id_put(vvd, resource_id);
+ kfree(buf);
+ return ret;
+ }
+
+ virtio_vb->queued = false;
+ virtio_vb->resource_id = resource_id;
+
+ return 0;
+}
+
+void virtio_video_buf_cleanup(struct vb2_buffer *vb)
+{
+ struct virtio_video_stream *stream = vb2_get_drv_priv(vb->vb2_queue);
+ struct virtio_video_buffer *virtio_vb = to_virtio_vb(vb);
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+
+ virtio_video_resource_id_put(vvd, virtio_vb->resource_id);
+}
+
+void virtio_video_buf_queue(struct vb2_buffer *vb)
+{
+ int i, ret;
+ struct virtio_video_buffer *virtio_vb;
+ uint32_t data_size[VB2_MAX_PLANES] = {0};
+ struct virtio_video_stream *stream = vb2_get_drv_priv(vb->vb2_queue);
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+
+ for (i = 0; i < vb->num_planes; ++i)
+ data_size[i] = vb->planes[i].bytesused;
+
+ virtio_vb = to_virtio_vb(vb);
+
+ if (!vvd->is_m2m_dev)
+ virtio_video_pending_buf_list_add(vvd, virtio_vb);
+
+ ret = virtio_video_cmd_resource_queue(vvd, stream->stream_id,
+ virtio_vb, data_size,
+ vb->num_planes,
+ to_virtio_queue_type(vb->type));
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to queue buffer\n");
+ return;
+ }
+
+ virtio_vb->queued = true;
+}
+
+int virtio_video_qbuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct virtio_video_stream *stream = file2stream(file);
+ struct virtio_video_device *vvd = video_drvdata(file);
+
+ if (virtio_video_state(stream) == STREAM_STATE_ERROR)
+ return -EIO;
+
+ if (vvd->is_m2m_dev)
+ return v4l2_m2m_ioctl_qbuf(file, priv, buf);
+
+ return vb2_ioctl_qbuf(file, priv, buf);
+}
+
+int virtio_video_dqbuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct virtio_video_stream *stream = file2stream(file);
+ struct virtio_video_device *vvd = video_drvdata(file);
+
+ if (virtio_video_state(stream) == STREAM_STATE_ERROR)
+ return -EIO;
+
+ if (vvd->is_m2m_dev)
+ return v4l2_m2m_ioctl_dqbuf(file, priv, buf);
+
+ return vb2_ioctl_dqbuf(file, priv, buf);
+}
+
+int virtio_video_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct video_device *video_dev = video_devdata(file);
+ struct virtio_video_device *vvd = video_drvdata(file);
+
+ if (strscpy(cap->driver, DRIVER_NAME, sizeof(cap->driver)) < 0)
+ v4l2_err(&vvd->v4l2_dev, "failed to copy driver name\n");
+ if (strscpy(cap->card, video_dev->name, sizeof(cap->card)) < 0)
+ v4l2_err(&vvd->v4l2_dev, "failed to copy card name\n");
+
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "virtio:%s",
+ video_dev->name);
+
+ cap->device_caps = video_dev->device_caps;
+ return 0;
+}
+
+int virtio_video_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *f)
+{
+ struct virtio_video_stream *stream = file2stream(file);
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+ struct video_format *fmt;
+ struct video_format_frame *frm;
+ struct virtio_video_format_frame *frame;
+ int idx = f->index;
+
+ fmt = virtio_video_find_video_format(&vvd->input_fmt_list,
+ f->pixel_format);
+ if (fmt == NULL)
+ fmt = virtio_video_find_video_format(&vvd->output_fmt_list,
+ f->pixel_format);
+ if (fmt == NULL)
+ return -EINVAL;
+
+ if (idx >= fmt->desc.num_frames)
+ return -EINVAL;
+
+ frm = &fmt->frames[idx];
+ frame = &frm->frame;
+
+ if (frame->width.min == frame->width.max &&
+ frame->height.min == frame->height.max) {
+ f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ f->discrete.width = frame->width.min;
+ f->discrete.height = frame->height.min;
+ return 0;
+ }
+
+ f->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
+ f->stepwise.min_width = frame->width.min;
+ f->stepwise.max_width = frame->width.max;
+ f->stepwise.min_height = frame->height.min;
+ f->stepwise.max_height = frame->height.max;
+ f->stepwise.step_width = frame->width.step;
+ f->stepwise.step_height = frame->height.step;
+ return 0;
+}
+
+static bool in_stepped_interval(struct virtio_video_format_range range,
+ uint32_t point)
+{
+ if (point < range.min || point > range.max)
+ return false;
+
+ if (range.step == 0 && range.min == range.max && range.min == point)
+ return true;
+
+ if (range.step != 0 && (point - range.min) % range.step == 0)
+ return true;
+
+ return false;
+}
+
+int virtio_video_enum_framemintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *f)
+{
+ struct virtio_video_stream *stream = file2stream(file);
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+ struct video_format *fmt;
+ struct video_format_frame *frm;
+ struct virtio_video_format_frame *frame;
+ struct virtio_video_format_range *frate;
+ int idx = f->index;
+ int f_idx;
+
+ fmt = virtio_video_find_video_format(&vvd->input_fmt_list,
+ f->pixel_format);
+ if (fmt == NULL)
+ fmt = virtio_video_find_video_format(&vvd->output_fmt_list,
+ f->pixel_format);
+ if (fmt == NULL)
+ return -EINVAL;
+
+ for (f_idx = 0; f_idx <= fmt->desc.num_frames; f_idx++) {
+ frm = &fmt->frames[f_idx];
+ frame = &frm->frame;
+ if (in_stepped_interval(frame->width, f->width) &&
+ in_stepped_interval(frame->height, f->height))
+ break;
+ }
+
+ if (frame == NULL || f->index >= frame->num_rates)
+ return -EINVAL;
+
+ frate = &frm->frame_rates[idx];
+ if (frate->max == frate->min) {
+ f->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ f->discrete.numerator = 1;
+ f->discrete.denominator = frate->max;
+ } else {
+ f->stepwise.min.numerator = 1;
+ f->stepwise.min.denominator = frate->max;
+ f->stepwise.max.numerator = 1;
+ f->stepwise.max.denominator = frate->min;
+ f->stepwise.step.numerator = 1;
+ f->stepwise.step.denominator = frate->step;
+ if (frate->step == 1)
+ f->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
+ else
+ f->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+ }
+ return 0;
+}
+
+int virtio_video_stream_get_params(struct virtio_video_device *vvd,
+ struct virtio_video_stream *stream)
+{
+ int ret;
+
+ if (vvd->is_m2m_dev) {
+ ret = virtio_video_cmd_get_params(vvd, stream,
+ VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev,
+ "failed to get stream in params\n");
+ goto err_get_parms;
+ }
+ }
+
+ ret = virtio_video_cmd_get_params(vvd, stream,
+ VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
+ if (ret)
+ v4l2_err(&vvd->v4l2_dev, "failed to get stream out params\n");
+
+err_get_parms:
+ return ret;
+}
+
+int virtio_video_stream_get_controls(struct virtio_video_device *vvd,
+ struct virtio_video_stream *stream)
+{
+ int ret;
+
+ ret = virtio_video_cmd_get_control(vvd, stream,
+ VIRTIO_VIDEO_CONTROL_PROFILE);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to get stream profile\n");
+ goto err_get_ctrl;
+ }
+
+ ret = virtio_video_cmd_get_control(vvd, stream,
+ VIRTIO_VIDEO_CONTROL_LEVEL);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to get stream level\n");
+ goto err_get_ctrl;
+ }
+
+ ret = virtio_video_cmd_get_control(vvd, stream,
+ VIRTIO_VIDEO_CONTROL_BITRATE);
+ if (ret)
+ v4l2_err(&vvd->v4l2_dev, "failed to get stream bitrate\n");
+
+err_get_ctrl:
+ return ret;
+}
+
+int virtio_video_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct video_format_info *info;
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ struct virtio_video_stream *stream = file2stream(file);
+
+ if (!V4L2_TYPE_IS_OUTPUT(f->type))
+ info = &stream->out_info;
+ else
+ info = &stream->in_info;
+
+ virtio_video_format_from_info(info, pix_mp);
+
+ return 0;
+}
+
+int virtio_video_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ int i, ret;
+ struct virtio_video_stream *stream = file2stream(file);
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+ struct video_format_info info;
+ struct video_format_info *p_info;
+ uint32_t queue;
+
+ ret = virtio_video_try_fmt(stream, f);
+ if (ret)
+ return ret;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type)) {
+ virtio_video_format_fill_default_info(&info, &stream->in_info);
+ queue = VIRTIO_VIDEO_QUEUE_TYPE_INPUT;
+ } else {
+ virtio_video_format_fill_default_info(&info, &stream->out_info);
+ queue = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT;
+ }
+
+ info.frame_width = pix_mp->width;
+ info.frame_height = pix_mp->height;
+ info.num_planes = pix_mp->num_planes;
+ info.fourcc_format = pix_mp->pixelformat;
+
+ for (i = 0; i < info.num_planes; i++) {
+ info.plane_format[i].stride =
+ pix_mp->plane_fmt[i].bytesperline;
+ info.plane_format[i].plane_size =
+ pix_mp->plane_fmt[i].sizeimage;
+ }
+
+ virtio_video_cmd_set_params(vvd, stream, &info, queue);
+ virtio_video_stream_get_params(vvd, stream);
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type))
+ p_info = &stream->in_info;
+ else
+ p_info = &stream->out_info;
+
+ virtio_video_format_from_info(p_info, pix_mp);
+
+ return 0;
+}
+
+int virtio_video_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *sel)
+{
+ struct video_format_info *info;
+ struct virtio_video_stream *stream = file2stream(file);
+ struct virtio_video_device *vvd = video_drvdata(file);
+
+ switch (vvd->type) {
+ case VIRTIO_VIDEO_DEVICE_ENCODER:
+ if (!V4L2_TYPE_IS_OUTPUT(sel->type))
+ return -EINVAL;
+ info = &stream->in_info;
+ break;
+ case VIRTIO_VIDEO_DEVICE_DECODER:
+ case VIRTIO_VIDEO_DEVICE_CAMERA:
+ if (V4L2_TYPE_IS_OUTPUT(sel->type))
+ return -EINVAL;
+ info = &stream->out_info;
+ break;
+ default:
+ v4l2_err(&vvd->v4l2_dev, "unsupported device type\n");
+ return -EINVAL;
+ }
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_COMPOSE:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ case V4L2_SEL_TGT_COMPOSE_PADDED:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ sel->r.width = info->frame_width;
+ sel->r.height = info->frame_height;
+ break;
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP:
+ sel->r.top = info->crop.top;
+ sel->r.left = info->crop.left;
+ sel->r.width = info->frame_width;
+ sel->r.height = info->frame_height;
+ break;
+ default:
+ v4l2_dbg(1, vvd->debug, &vvd->v4l2_dev,
+ "unsupported/invalid selection target: %d\n",
+ sel->target);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int virtio_video_try_fmt(struct virtio_video_stream *stream,
+ struct v4l2_format *f)
+{
+ int i, idx = 0;
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+ struct video_format *fmt;
+ bool found = false;
+ struct video_format_frame *frm;
+ struct virtio_video_format_frame *frame;
+
+ if (virtio_video_state(stream) == STREAM_STATE_ERROR)
+ return -EIO;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type))
+ fmt = virtio_video_find_video_format(&vvd->input_fmt_list,
+ pix_mp->pixelformat);
+ else
+ fmt = virtio_video_find_video_format(&vvd->output_fmt_list,
+ pix_mp->pixelformat);
+
+ if (!fmt) {
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ virtio_video_format_from_info(&stream->out_info,
+ pix_mp);
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ virtio_video_format_from_info(&stream->in_info,
+ pix_mp);
+ else
+ return -EINVAL;
+ return 0;
+ }
+
+ for (i = 0; i < fmt->desc.num_frames && !found; i++) {
+ frm = &fmt->frames[i];
+ frame = &frm->frame;
+ if (!within_range(frame->width.min, pix_mp->width,
+ frame->width.max))
+ continue;
+
+ if (!within_range(frame->height.min, pix_mp->height,
+ frame->height.max))
+ continue;
+ idx = i;
+ /*
+ * Try to find a more suitable frame size. Go with the current
+ * one otherwise.
+ */
+ if (needs_alignment(pix_mp->width, frame->width.step))
+ continue;
+
+ if (needs_alignment(pix_mp->height, frame->height.step))
+ continue;
+
+ stream->current_frame = frm;
+ found = true;
+ }
+
+ if (!found) {
+ frm = &fmt->frames[idx];
+ if (!frm)
+ return -EINVAL;
+
+ frame = &frm->frame;
+ pix_mp->width = clamp(pix_mp->width, frame->width.min,
+ frame->width.max);
+ if (frame->width.step != 0)
+ pix_mp->width = ALIGN(pix_mp->width, frame->width.step);
+
+ pix_mp->height = clamp(pix_mp->height, frame->height.min,
+ frame->height.max);
+ if (frame->height.step != 0)
+ pix_mp->height = ALIGN(pix_mp->height,
+ frame->height.step);
+ stream->current_frame = frm;
+ }
+
+ return 0;
+}
+
+static int virtio_video_queue_free(struct virtio_video_device *vvd,
+ struct virtio_video_stream *stream,
+ enum v4l2_buf_type type)
+{
+ int ret;
+ enum virtio_video_queue_type queue_type = to_virtio_queue_type(type);
+
+ ret = virtio_video_cmd_queue_detach_resources(vvd, stream, queue_type);
+ if (ret) {
+ v4l2_warn(&vvd->v4l2_dev,
+ "failed to destroy resources\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int virtio_video_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *rb)
+{
+ int ret;
+ struct virtio_video_stream *stream = file2stream(file);
+ struct v4l2_m2m_ctx *m2m_ctx = stream->fh.m2m_ctx;
+ struct virtio_video_device *vvd = video_drvdata(file);
+ struct video_device *vdev = video_devdata(file);
+ struct vb2_queue *vq;
+
+ if (vvd->is_m2m_dev)
+ vq = v4l2_m2m_get_vq(m2m_ctx, rb->type);
+ else
+ vq = vdev->queue;
+
+ if (rb->count == 0) {
+ ret = virtio_video_queue_free(vvd, stream, vq->type);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (vvd->is_m2m_dev)
+ return v4l2_m2m_reqbufs(file, m2m_ctx, rb);
+ else
+ return vb2_ioctl_reqbufs(file, priv, rb);
+}
+
+int virtio_video_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_SOURCE_CHANGE:
+ return v4l2_src_change_event_subscribe(fh, sub);
+ default:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ }
+}
+
+void virtio_video_queue_eos_event(struct virtio_video_stream *stream)
+{
+ static const struct v4l2_event eos_event = {
+ .type = V4L2_EVENT_EOS
+ };
+
+ v4l2_event_queue_fh(&stream->fh, &eos_event);
+}
+
+void virtio_video_queue_res_chg_event(struct virtio_video_stream *stream)
+{
+ static const struct v4l2_event ev_src_ch = {
+ .type = V4L2_EVENT_SOURCE_CHANGE,
+ .u.src_change.changes =
+ V4L2_EVENT_SRC_CH_RESOLUTION,
+ };
+
+ v4l2_event_queue_fh(&stream->fh, &ev_src_ch);
+}
+
+void virtio_video_handle_error(struct virtio_video_stream *stream)
+{
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+
+ if (vvd->is_m2m_dev)
+ virtio_video_queue_release_buffers
+ (stream, VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
+
+ virtio_video_queue_release_buffers
+ (stream, VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
+}
+
+int virtio_video_queue_release_buffers(struct virtio_video_stream *stream,
+ enum virtio_video_queue_type queue_type)
+{
+ int ret;
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+ struct vb2_v4l2_buffer *v4l2_vb;
+ struct virtio_video_buffer *vvb;
+
+ ret = virtio_video_cmd_queue_clear(vvd, stream, queue_type);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to clear queue\n");
+ return ret;
+ }
+
+ if (!vvd->is_m2m_dev) {
+ while (!virtio_video_pending_buf_list_pop(vvd, &vvb) && vvb) {
+ v4l2_vb = &vvb->v4l2_m2m_vb.vb;
+ v4l2_m2m_buf_done(v4l2_vb, VB2_BUF_STATE_ERROR);
+ }
+ return 0;
+ }
+
+ for (;;) {
+ if (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT)
+ v4l2_vb = v4l2_m2m_src_buf_remove(stream->fh.m2m_ctx);
+ else
+ v4l2_vb = v4l2_m2m_dst_buf_remove(stream->fh.m2m_ctx);
+ if (!v4l2_vb)
+ break;
+ v4l2_m2m_buf_done(v4l2_vb, VB2_BUF_STATE_ERROR);
+ }
+
+ return 0;
+}
+
+void virtio_video_buf_done(struct virtio_video_buffer *virtio_vb,
+ uint32_t flags, uint64_t timestamp,
+ uint32_t data_sizes[])
+{
+ int i;
+ enum vb2_buffer_state done_state = VB2_BUF_STATE_DONE;
+ struct vb2_v4l2_buffer *v4l2_vb = &virtio_vb->v4l2_m2m_vb.vb;
+ struct vb2_buffer *vb = &v4l2_vb->vb2_buf;
+ struct vb2_queue *vb2_queue = vb->vb2_queue;
+ struct virtio_video_stream *stream = vb2_get_drv_priv(vb2_queue);
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+ struct video_format_info *p_info;
+
+ virtio_vb->queued = false;
+
+ if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_ERR)
+ done_state = VB2_BUF_STATE_ERROR;
+
+ if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_KEY_FRAME)
+ v4l2_vb->flags |= V4L2_BUF_FLAG_KEYFRAME;
+
+ if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_BFRAME)
+ v4l2_vb->flags |= V4L2_BUF_FLAG_BFRAME;
+
+ if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_PFRAME)
+ v4l2_vb->flags |= V4L2_BUF_FLAG_PFRAME;
+
+ if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_EOS) {
+ v4l2_vb->flags |= V4L2_BUF_FLAG_LAST;
+ virtio_video_state_update(stream, STREAM_STATE_STOPPED);
+ virtio_video_queue_eos_event(stream);
+ }
+
+ if ((flags & VIRTIO_VIDEO_DEQUEUE_FLAG_ERR) ||
+ (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_EOS)) {
+ vb->planes[0].bytesused = 0;
+
+ if (!vvd->is_m2m_dev)
+ virtio_video_pending_buf_list_del(vvd, virtio_vb);
+
+ v4l2_m2m_buf_done(v4l2_vb, done_state);
+ return;
+ }
+
+ if (!V4L2_TYPE_IS_OUTPUT(vb2_queue->type)) {
+ switch (vvd->type) {
+ case VIRTIO_VIDEO_DEVICE_ENCODER:
+ for (i = 0; i < vb->num_planes; i++)
+ vb->planes[i].bytesused =
+ le32_to_cpu(data_sizes[i]);
+ break;
+ case VIRTIO_VIDEO_DEVICE_CAMERA:
+ case VIRTIO_VIDEO_DEVICE_DECODER:
+ p_info = &stream->out_info;
+ for (i = 0; i < p_info->num_planes; i++)
+ vb->planes[i].bytesused =
+ p_info->plane_format[i].plane_size;
+ break;
+ }
+
+ vb->timestamp = timestamp;
+ }
+
+ if (!vvd->is_m2m_dev)
+ virtio_video_pending_buf_list_del(vvd, virtio_vb);
+
+ v4l2_m2m_buf_done(v4l2_vb, done_state);
+}
+
+static int virtio_video_set_device_busy(struct virtio_video_device *vvd)
+{
+ struct video_device *vd = &vvd->video_dev;
+ int ret = 0;
+
+ /* Multiple open is allowed for m2m device */
+ if (vvd->is_m2m_dev)
+ return 0;
+
+ mutex_lock(vd->lock);
+
+ if (vvd->device_busy)
+ ret = -EBUSY;
+ else
+ vvd->device_busy = true;
+
+ mutex_unlock(vd->lock);
+
+ return ret;
+}
+
+static void virtio_video_clear_device_busy(struct virtio_video_device *vvd,
+ struct mutex *lock)
+{
+ /* Nothing to do for m2m device */
+ if (vvd->is_m2m_dev)
+ return;
+
+ if (lock)
+ mutex_lock(lock);
+
+ vvd->device_busy = false;
+
+ if (lock)
+ mutex_unlock(lock);
+}
+
+static int virtio_video_device_open(struct file *file)
+{
+ int ret;
+ uint32_t stream_id;
+ char name[TASK_COMM_LEN];
+ struct virtio_video_stream *stream;
+ struct video_format *default_fmt;
+ enum virtio_video_format format;
+ struct video_device *video_dev = video_devdata(file);
+ struct virtio_video_device *vvd = video_drvdata(file);
+
+ ret = virtio_video_set_device_busy(vvd);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "device already in use.\n");
+ return ret;
+ }
+
+ default_fmt = list_first_entry_or_null(vvd->ops->get_fmt_list(vvd),
+ struct video_format,
+ formats_list_entry);
+ if (!default_fmt) {
+ v4l2_err(&vvd->v4l2_dev, "device failed to start\n");
+ ret = -EIO;
+ goto err;
+ }
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ get_task_comm(name, current);
+ format = virtio_video_v4l2_format_to_virtio(default_fmt->desc.format);
+ virtio_video_stream_id_get(vvd, stream, &stream_id);
+ ret = virtio_video_cmd_stream_create(vvd, stream_id, format, name);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to create stream\n");
+ goto err_stream_create;
+ }
+
+ stream->video_dev = video_dev;
+ stream->stream_id = stream_id;
+
+ virtio_video_state_reset(stream);
+
+ ret = virtio_video_stream_get_params(vvd, stream);
+ if (ret)
+ goto err_stream_create;
+
+ if (format >= VIRTIO_VIDEO_FORMAT_CODED_MIN &&
+ format <= VIRTIO_VIDEO_FORMAT_CODED_MAX) {
+ ret = virtio_video_stream_get_controls(vvd, stream);
+ if (ret)
+ goto err_stream_create;
+ }
+
+ mutex_init(&stream->vq_mutex);
+ v4l2_fh_init(&stream->fh, video_dev);
+ stream->fh.ctrl_handler = &stream->ctrl_handler;
+
+ if (vvd->is_m2m_dev) {
+ stream->fh.m2m_ctx = v4l2_m2m_ctx_init(vvd->m2m_dev, stream,
+ vvd->ops->init_queues);
+ if (IS_ERR(stream->fh.m2m_ctx)) {
+ ret = PTR_ERR(stream->fh.m2m_ctx);
+ goto err_init_ctx;
+ }
+
+ v4l2_m2m_set_src_buffered(stream->fh.m2m_ctx, true);
+ v4l2_m2m_set_dst_buffered(stream->fh.m2m_ctx, true);
+ } else {
+ vvd->ops->init_queues(stream, NULL, &vvd->vb2_output_queue);
+ /* Video dev queue is required for vb2 ioctl wrappers */
+ video_dev->queue = &vvd->vb2_output_queue;
+ }
+
+ file->private_data = &stream->fh;
+ v4l2_fh_add(&stream->fh);
+
+ if (vvd->ops->init_ctrls) {
+ ret = vvd->ops->init_ctrls(stream);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to init controls\n");
+ goto err_init_ctrls;
+ }
+ }
+ return 0;
+
+err_init_ctrls:
+ v4l2_fh_del(&stream->fh);
+ mutex_lock(video_dev->lock);
+ if (vvd->is_m2m_dev)
+ v4l2_m2m_ctx_release(stream->fh.m2m_ctx);
+ mutex_unlock(video_dev->lock);
+err_init_ctx:
+ v4l2_fh_exit(&stream->fh);
+err_stream_create:
+ virtio_video_stream_id_put(vvd, stream_id);
+ kfree(stream);
+err:
+ virtio_video_clear_device_busy(vvd, video_dev->lock);
+ return ret;
+}
+
+static int virtio_video_device_release(struct file *file)
+{
+ struct virtio_video_stream *stream = file2stream(file);
+ struct video_device *video_dev = video_devdata(file);
+ struct virtio_video_device *vvd = video_drvdata(file);
+
+ mutex_lock(video_dev->lock);
+
+ v4l2_fh_del(&stream->fh);
+ if (vvd->is_m2m_dev) {
+ v4l2_m2m_ctx_release(stream->fh.m2m_ctx);
+ } else if (file->private_data == video_dev->queue->owner) {
+ vb2_queue_release(&vvd->vb2_output_queue);
+ video_dev->queue->owner = NULL;
+ }
+
+ v4l2_fh_exit(&stream->fh);
+
+ virtio_video_cmd_stream_destroy(vvd, stream->stream_id);
+ virtio_video_stream_id_put(vvd, stream->stream_id);
+
+ kfree(stream);
+
+ /* Mutex already locked here, passing NULL */
+ virtio_video_clear_device_busy(vvd, NULL);
+
+ mutex_unlock(video_dev->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations virtio_video_device_m2m_fops = {
+ .owner = THIS_MODULE,
+ .open = virtio_video_device_open,
+ .release = virtio_video_device_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+static const struct v4l2_file_operations virtio_video_device_fops = {
+ .owner = THIS_MODULE,
+ .open = virtio_video_device_open,
+ .release = virtio_video_device_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+static void virtio_video_device_run(void *priv)
+{
+
+}
+
+static void virtio_video_device_job_abort(void *priv)
+{
+ struct virtio_video_stream *stream = priv;
+ struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
+
+ v4l2_m2m_job_finish(vvd->m2m_dev, stream->fh.m2m_ctx);
+}
+
+static const struct v4l2_m2m_ops virtio_video_device_m2m_ops = {
+ .device_run = virtio_video_device_run,
+ .job_abort = virtio_video_device_job_abort,
+};
+
+static int virtio_video_device_register(struct virtio_video_device *vvd)
+{
+ int ret;
+ struct video_device *vd;
+
+ vd = &vvd->video_dev;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
+ ret = video_register_device(vd, VFL_TYPE_VIDEO, vvd->vid_dev_nr);
+#else
+ ret = video_register_device(vd, VFL_TYPE_GRABBER, vvd->vid_dev_nr);
+#endif
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to register video device\n");
+ return ret;
+ }
+
+ v4l2_info(&vvd->v4l2_dev, "Device '%s' registered as /dev/video%d\n",
+ vd->name, vd->num);
+
+ return 0;
+}
+
+static void virtio_video_device_unregister(struct virtio_video_device *vvd)
+{
+ video_unregister_device(&vvd->video_dev);
+}
+
+static int
+virtio_video_query_capability(struct virtio_video_device *vvd,
+ void *resp_buf,
+ enum virtio_video_queue_type queue_type)
+{
+ int ret;
+ int resp_size = vvd->max_caps_len;
+
+ ret = virtio_video_cmd_query_capability(vvd, resp_buf, resp_size,
+ queue_type);
+ if (ret)
+ v4l2_err(&vvd->v4l2_dev, "failed to query capability\n");
+
+ return ret;
+}
+
+int virtio_video_device_init(struct virtio_video_device *vvd)
+{
+ int ret;
+ int vfl_dir;
+ u32 dev_caps;
+ struct video_device *vd;
+ struct v4l2_m2m_dev *m2m_dev;
+ const struct v4l2_file_operations *fops;
+ void *input_resp_buf, *output_resp_buf;
+
+ output_resp_buf = kzalloc(vvd->max_caps_len, GFP_KERNEL);
+ if (!output_resp_buf)
+ return -ENOMEM;
+
+ ret = virtio_video_query_capability(vvd, output_resp_buf,
+ VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to get output caps\n");
+ goto err_output_cap;
+ }
+
+ if (vvd->is_m2m_dev) {
+ input_resp_buf = kzalloc(vvd->max_caps_len, GFP_KERNEL);
+ if (!input_resp_buf) {
+ ret = -ENOMEM;
+ goto err_input_buf;
+ }
+
+ ret = virtio_video_query_capability(vvd, input_resp_buf,
+ VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to get input caps\n");
+ goto err_input_cap;
+ }
+
+ m2m_dev = v4l2_m2m_init(&virtio_video_device_m2m_ops);
+ if (IS_ERR(m2m_dev)) {
+ v4l2_err(&vvd->v4l2_dev, "failed to init m2m device\n");
+ ret = PTR_ERR(m2m_dev);
+ goto err_m2m_dev;
+ }
+ vfl_dir = VFL_DIR_M2M;
+ fops = &virtio_video_device_m2m_fops;
+ dev_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+ } else {
+ input_resp_buf = NULL;
+ m2m_dev = NULL;
+ vfl_dir = VFL_DIR_RX;
+ fops = &virtio_video_device_fops;
+ dev_caps = V4L2_CAP_STREAMING;
+ if (vvd->is_mplane_cam)
+ dev_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE;
+ else
+ dev_caps |= V4L2_CAP_VIDEO_CAPTURE;
+ }
+
+ vvd->m2m_dev = m2m_dev;
+ mutex_init(&vvd->video_dev_mutex);
+ vd = &vvd->video_dev;
+ vd->lock = &vvd->video_dev_mutex;
+ vd->v4l2_dev = &vvd->v4l2_dev;
+ vd->vfl_dir = vfl_dir;
+ vd->ioctl_ops = NULL;
+ vd->fops = fops;
+ vd->device_caps = dev_caps;
+ vd->release = video_device_release_empty;
+
+ /* Use the selection API instead */
+ v4l2_disable_ioctl(vd, VIDIOC_CROPCAP);
+ v4l2_disable_ioctl(vd, VIDIOC_G_CROP);
+
+ video_set_drvdata(vd, vvd);
+
+ INIT_LIST_HEAD(&vvd->input_fmt_list);
+ INIT_LIST_HEAD(&vvd->output_fmt_list);
+ INIT_LIST_HEAD(&vvd->controls_fmt_list);
+ INIT_LIST_HEAD(&vvd->pending_buf_list);
+
+ vvd->num_output_fmts = 0;
+ vvd->num_input_fmts = 0;
+
+ switch (vvd->type) {
+ case VIRTIO_VIDEO_DEVICE_CAMERA:
+ virtio_video_cam_init(vvd);
+ break;
+ case VIRTIO_VIDEO_DEVICE_ENCODER:
+ virtio_video_enc_init(vvd);
+ break;
+ case VIRTIO_VIDEO_DEVICE_DECODER:
+ default:
+ virtio_video_dec_init(vvd);
+ break;
+ }
+
+ ret = virtio_video_parse_virtio_capabilities(vvd, input_resp_buf,
+ output_resp_buf);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to parse a function\n");
+ goto parse_cap_err;
+ }
+
+ ret = virtio_video_parse_virtio_control(vvd);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to query controls\n");
+ goto parse_ctrl_err;
+ }
+
+ ret = virtio_video_device_register(vvd);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev,
+ "failed to init virtio video device\n");
+ goto register_err;
+ }
+
+ goto out_cleanup;
+
+register_err:
+ virtio_video_clean_control(vvd);
+parse_ctrl_err:
+ virtio_video_clean_capability(vvd);
+parse_cap_err:
+ if (vvd->is_m2m_dev)
+ v4l2_m2m_release(vvd->m2m_dev);
+err_m2m_dev:
+err_input_cap:
+out_cleanup:
+ if (vvd->is_m2m_dev)
+ kfree(input_resp_buf);
+err_input_buf:
+err_output_cap:
+ kfree(output_resp_buf);
+
+ return ret;
+}
+
+void virtio_video_device_deinit(struct virtio_video_device *vvd)
+{
+ vvd->commandq.ready = false;
+ vvd->eventq.ready = false;
+
+ virtio_video_device_unregister(vvd);
+ if (vvd->is_m2m_dev)
+ v4l2_m2m_release(vvd->m2m_dev);
+ virtio_video_clean_control(vvd);
+ virtio_video_clean_capability(vvd);
+}