Re: [RFC] virtio video driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Dmitry, Hans,

On Fri, Dec 6, 2019 at 1:33 AM Dmitry Sepp <Dmitry.Sepp@xxxxxxxxxxxxxxx> wrote:
>
> From: Kiran Pawar <kiran.pawar@xxxxxxxxxxxxxxx>
>
> This adds a Virtio based video driver for video streaming device that
> operates input and/or output data buffers to share video devices with
> several guests. The current implementation consist of V4L2 based video
> driver supporting video functions of decoder and encoder. This can be
> extended for other functions e.g. processor, capture and output.
> The device uses descriptor structures to advertise and negotiate stream
> formats and controls. This allows the driver to modify the processing
> logic of the device on a per stream basis.

Dmitry, just a note that some people may have email filters
specifically for "PATCH" in the subject line.

Hans, do you think you could have some time to take a look at this
regardless of that?

Best regards,
Tomasz

>
> Signed-off-by: Dmitry Sepp <Dmitry.Sepp@xxxxxxxxxxxxxxx>
> Signed-off-by: Kiran Pawar <Kiran.Pawar@xxxxxxxxxxxxxxx>
> Signed-off-by: Nikolay Martyanov <Nikolay.Martyanov@xxxxxxxxxxxxxxx>
> ---
>  drivers/media/Kconfig                      |    1 +
>  drivers/media/Makefile                     |    2 +
>  drivers/media/virtio/Kconfig               |   11 +
>  drivers/media/virtio/Makefile              |   12 +
>  drivers/media/virtio/virtio_video.h        |  372 +++++++
>  drivers/media/virtio/virtio_video_caps.c   |  618 +++++++++++
>  drivers/media/virtio/virtio_video_dec.c    |  947 +++++++++++++++++
>  drivers/media/virtio/virtio_video_dec.h    |   30 +
>  drivers/media/virtio/virtio_video_device.c |  747 +++++++++++++
>  drivers/media/virtio/virtio_video_driver.c |  278 +++++
>  drivers/media/virtio/virtio_video_enc.c    | 1124 ++++++++++++++++++++
>  drivers/media/virtio/virtio_video_enc.h    |   30 +
>  drivers/media/virtio/virtio_video_vq.c     |  950 +++++++++++++++++
>  include/uapi/linux/virtio_ids.h            |    2 +
>  include/uapi/linux/virtio_video.h          |  456 ++++++++
>  15 files changed, 5580 insertions(+)
>  create mode 100644 drivers/media/virtio/Kconfig
>  create mode 100644 drivers/media/virtio/Makefile
>  create mode 100644 drivers/media/virtio/virtio_video.h
>  create mode 100644 drivers/media/virtio/virtio_video_caps.c
>  create mode 100644 drivers/media/virtio/virtio_video_dec.c
>  create mode 100644 drivers/media/virtio/virtio_video_dec.h
>  create mode 100644 drivers/media/virtio/virtio_video_device.c
>  create mode 100644 drivers/media/virtio/virtio_video_driver.c
>  create mode 100644 drivers/media/virtio/virtio_video_enc.c
>  create mode 100644 drivers/media/virtio/virtio_video_enc.h
>  create mode 100644 drivers/media/virtio/virtio_video_vq.c
>  create mode 100644 include/uapi/linux/virtio_video.h
>
> diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
> index b36a41332867..cc95806e8f8b 100644
> --- a/drivers/media/Kconfig
> +++ b/drivers/media/Kconfig
> @@ -170,6 +170,7 @@ source "drivers/media/pci/Kconfig"
>  source "drivers/media/platform/Kconfig"
>  source "drivers/media/mmc/Kconfig"
>  source "drivers/media/radio/Kconfig"
> +source "drivers/media/virtio/Kconfig"
>
>  comment "Supported FireWire (IEEE 1394) Adapters"
>         depends on DVB_CORE && FIREWIRE
> diff --git a/drivers/media/Makefile b/drivers/media/Makefile
> index f215f0a89f9e..9517a6f724d1 100644
> --- a/drivers/media/Makefile
> +++ b/drivers/media/Makefile
> @@ -25,6 +25,8 @@ obj-y += rc/
>
>  obj-$(CONFIG_CEC_CORE) += cec/
>
> +obj-$(CONFIG_VIDEO_VIRTIO)  += virtio/
> +
>  #
>  # Finally, merge the drivers that require the core
>  #
> diff --git a/drivers/media/virtio/Kconfig b/drivers/media/virtio/Kconfig
> new file mode 100644
> index 000000000000..3289bcf233f0
> --- /dev/null
> +++ b/drivers/media/virtio/Kconfig
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +# Video driver for virtio
> +
> +config VIDEO_VIRTIO
> +       tristate "Virtio video V4L2 driver"
> +       depends on VIRTIO && VIDEO_DEV && VIDEO_V4L2
> +       select VIDEOBUF2_DMA_SG
> +       select V4L2_MEM2MEM_DEV
> +       help
> +          This is the virtual video driver for virtio.
> +          Say Y or M.
> diff --git a/drivers/media/virtio/Makefile b/drivers/media/virtio/Makefile
> new file mode 100644
> index 000000000000..6bc48fde57b4
> --- /dev/null
> +++ b/drivers/media/virtio/Makefile
> @@ -0,0 +1,12 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +
> +obj-$(CONFIG_VIDEO_VIRTIO) += virtio-video.o
> +
> +virtio-video-objs := \
> +    virtio_video_driver.o \
> +    virtio_video_vq.o \
> +    virtio_video_device.o \
> +    virtio_video_dec.o \
> +    virtio_video_enc.o \
> +    virtio_video_caps.o
> +
> diff --git a/drivers/media/virtio/virtio_video.h b/drivers/media/virtio/virtio_video.h
> new file mode 100644
> index 000000000000..23c77dc0cb93
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video.h
> @@ -0,0 +1,372 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/* Common header for virtio video driver.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef _VIRTIO_VIDEO_H
> +#define _VIRTIO_VIDEO_H
> +
> +#include <linux/virtio.h>
> +#include <linux/virtio_ids.h>
> +#include <linux/virtio_config.h>
> +#include <linux/virtio_video.h>
> +#include <linux/list.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-mem2mem.h>
> +#include <media/v4l2-ctrls.h>
> +
> +#define DRIVER_NAME "virtio-video"
> +
> +#ifndef VIRTIO_ID_VIDEO
> +#define VIRTIO_ID_VIDEO 29
> +#endif
> +
> +enum video_pin_type {
> +       VIDEO_PIN_TYPE_INPUT = 0,
> +       VIDEO_PIN_TYPE_OUTPUT,
> +};
> +
> +enum video_params_scope {
> +       VIDEO_PARAMS_SCOPE_STREAM = 0,
> +       VIDEO_PARAMS_SCOPE_DEVICE,
> +};
> +
> +#define MIN_BUFS_MIN 0
> +#define MIN_BUFS_MAX 32
> +#define MIN_BUFS_STEP 1
> +#define MIN_BUFS_DEF 1
> +
> +struct video_frame_rate {
> +       unsigned int min_rate;
> +       unsigned int max_rate;
> +       unsigned int step;
> +};
> +
> +struct video_frame_size {
> +       unsigned int min_width;
> +       unsigned int max_width;
> +       unsigned int step_width;
> +       unsigned int min_height;
> +       unsigned int max_height;
> +       unsigned int step_height;
> +       unsigned int num_rates;
> +       struct video_frame_rate *frame_rates;
> +};
> +
> +struct video_pix_format {
> +       uint32_t fourcc_format;
> +       unsigned int num_sizes;
> +       struct video_frame_size *frame_sizes;
> +};
> +
> +struct video_frame_format {
> +       int pin_type; /* VIRTIO_VIDEO_PIN_ */
> +       unsigned int num_formats;
> +       struct video_pix_format *pix_formats;
> +};
> +
> +struct video_control {
> +       uint32_t control_type;
> +       uint32_t step;
> +       uint64_t min;
> +       uint64_t max;
> +       uint64_t def;
> +};
> +
> +struct video_controls {
> +       unsigned int num_controls;
> +       struct video_control *control;
> +};
> +
> +struct video_capability {
> +       struct list_head caps_list_entry;
> +       int cap_type; /* VIRTIO_VIDEO_CAP_ */
> +       unsigned int cap_id;
> +       union {
> +               struct video_frame_format frame_format;
> +               struct video_controls controls;
> +       } u;
> +};
> +
> +struct virtio_video;
> +struct virtio_video_vbuffer;
> +
> +typedef void (*virtio_video_resp_cb)(struct virtio_video *vv,
> +                                    struct virtio_video_vbuffer *vbuf);
> +
> +struct virtio_video_vbuffer {
> +       char *buf;
> +       int size;
> +
> +       void *data_buf;
> +       uint32_t data_size;
> +
> +       char *resp_buf;
> +       int resp_size;
> +
> +       void *priv;
> +       virtio_video_resp_cb resp_cb;
> +
> +       struct list_head list;
> +};
> +
> +struct virtio_video_queue {
> +       struct virtqueue *vq;
> +       spinlock_t qlock;
> +       wait_queue_head_t ack_queue;
> +       struct work_struct dequeue_work;
> +};
> +
> +struct virtio_video {
> +       struct v4l2_device v4l2_dev;
> +       int instance;
> +
> +       struct virtio_device *vdev;
> +       struct virtio_video_queue ctrlq;
> +       struct virtio_video_queue eventq;
> +       wait_queue_head_t wq;
> +       bool vq_ready;
> +
> +       struct kmem_cache *vbufs;
> +
> +       struct idr resource_idr;
> +       spinlock_t resource_idr_lock;
> +       struct idr stream_idr;
> +       spinlock_t stream_idr_lock;
> +
> +       uint32_t num_devices;
> +       uint32_t funcs_size;
> +       bool got_funcs;
> +
> +       bool has_iommu;
> +       struct list_head devices_list;
> +
> +       int debug;
> +};
> +
> +struct video_plane_format {
> +       uint32_t channel;
> +       uint32_t plane_size;
> +       uint32_t stride;
> +       uint32_t padding;
> +};
> +
> +struct video_format_info {
> +       unsigned int frame_rate;
> +       unsigned int frame_width;
> +       unsigned int frame_height;
> +       unsigned int min_buffers;
> +       uint32_t fourcc_format;
> +       uint32_t num_planes;
> +       struct video_plane_format plane_format[VIRTIO_VIDEO_MAX_PLANES];
> +       bool is_updated;
> +};
> +
> +enum video_stream_state {
> +       STREAM_STATE_IDLE = 0,
> +       STREAM_STATE_INIT,
> +       STREAM_STATE_METADATA, /* specific to decoder */
> +       STREAM_STATE_RUNNING,
> +       STREAM_STATE_DRAIN,
> +       STREAM_STATE_STOPPED,
> +       STREAM_STATE_RESET, /* specific to encoder */
> +};
> +
> +struct virtio_video_device {
> +       struct virtio_video *vv;
> +       struct video_device video_dev;
> +       struct mutex video_dev_mutex;
> +
> +       struct v4l2_m2m_dev *m2m_dev;
> +
> +       struct workqueue_struct *workqueue;
> +
> +       struct list_head devices_list_entry;
> +       /* VIRTIO_VIDEO_FUNC_ */
> +       int type;
> +       unsigned int id;
> +       /* List of control capabilities */
> +       struct list_head ctrl_caps_list;
> +       /* List of frame formats capabilities */
> +       struct list_head fmt_caps_list;
> +
> +       /* The following 2 arrays contain pointers to pixel formats that are
> +        * stored in 'fmt_caps_list' (as a part of the 'video_frame_format'
> +        * structure). They are necessary to simplify indexing
> +        * through pixel formats in the implementation of ENUM_FMT callbacks
> +        */
> +
> +       /* Array of pointers to pixel formats of CAPTURE pin */
> +       unsigned int num_capture_formats;
> +       struct video_pix_format **capture_fmts;
> +
> +       /* Array of pointers to pixel formats of OUTPUT pin */
> +       unsigned int num_output_formats;
> +       struct video_pix_format **output_fmts;
> +
> +       struct video_format_info in_info;
> +       struct video_format_info out_info;
> +};
> +
> +int virtio_video_alloc_vbufs(struct virtio_video *vv);
> +void virtio_video_free_vbufs(struct virtio_video *vv);
> +int virtio_video_alloc_events(struct virtio_video *vv, size_t num);
> +
> +int virtio_video_devices_init(struct virtio_video *vv, void *funcs_buf);
> +void virtio_video_devices_deinit(struct virtio_video *vv);
> +
> +struct virtio_video_stream {
> +       uint32_t stream_id;
> +       enum video_stream_state state;
> +       struct video_device *video_dev;
> +       struct v4l2_fh fh;
> +       struct mutex vq_mutex;
> +       struct v4l2_ctrl_handler ctrl_handler;
> +       struct video_format_info in_info;
> +       struct video_format_info out_info;
> +       bool src_cleared;
> +       bool dst_cleared;
> +       bool mark_last_buffer_pending;
> +       bool check_drain_sequence_pending;
> +       struct work_struct work;
> +       struct video_frame_size *current_frame_size;
> +};
> +
> +struct virtio_video_buffer {
> +       struct v4l2_m2m_buffer v4l2_m2m_vb;
> +       uint32_t resource_id;
> +       bool detached;
> +       bool queued;
> +};
> +
> +static inline struct virtio_video_device *
> +to_virtio_vd(struct video_device *video_dev)
> +{
> +       return container_of(video_dev, struct virtio_video_device,
> +                        video_dev);
> +}
> +
> +static inline struct virtio_video_stream *file2stream(struct file *file)
> +{
> +       return container_of(file->private_data, struct virtio_video_stream, fh);
> +}
> +
> +static inline struct virtio_video_stream *ctrl2stream(struct v4l2_ctrl *ctrl)
> +{
> +       return container_of(ctrl->handler, struct virtio_video_stream,
> +                           ctrl_handler);
> +}
> +
> +static inline struct virtio_video_stream *work2stream(struct work_struct *work)
> +{
> +       return container_of(work, struct virtio_video_stream, work);
> +}
> +
> +static inline struct virtio_video_buffer *to_virtio_vb(struct vb2_buffer *vb)
> +{
> +       struct vb2_v4l2_buffer *v4l2_vb = to_vb2_v4l2_buffer(vb);
> +
> +       return container_of(v4l2_vb, struct virtio_video_buffer,
> +                           v4l2_m2m_vb.vb);
> +}
> +
> +void virtio_video_stream_id_get(struct virtio_video *vv,
> +                               struct virtio_video_stream *stream,
> +                               uint32_t *id);
> +void virtio_video_stream_id_put(struct virtio_video *vv, uint32_t id);
> +void virtio_video_resource_id_get(struct virtio_video *vv, uint32_t *id);
> +void virtio_video_resource_id_put(struct virtio_video *vv, uint32_t id);
> +
> +int virtio_video_req_stream_create(struct virtio_video *vv,
> +                                  uint32_t function_id, uint32_t stream_id,
> +                                  const char *name);
> +int virtio_video_req_stream_destroy(struct virtio_video *vv,
> +                                   uint32_t function_id, uint32_t stream_id);
> +int virtio_video_req_stream_start(struct virtio_video *vv,
> +                                 uint32_t function_id, uint32_t stream_id);
> +int virtio_video_req_stream_stop(struct virtio_video *vv,
> +                                uint32_t function_id,
> +                                struct virtio_video_stream *stream);
> +int virtio_video_req_stream_drain(struct virtio_video *vv,
> +                                 uint32_t function_id, uint32_t stream_id);
> +int virtio_video_req_resource_create(struct virtio_video *vv,
> +                                    uint32_t function_id, uint32_t stream_id,
> +                                    uint32_t resource_id);
> +int virtio_video_req_resource_destroy(struct virtio_video *vv,
> +                                     uint32_t function_id, uint32_t stream_id,
> +                                     uint32_t resource_id);
> +int virtio_video_req_resource_queue(struct virtio_video *vv,
> +                               uint32_t function_id, uint32_t stream_id,
> +                               struct virtio_video_buffer *virtio_vb,
> +                               uint32_t data_size[],
> +                               uint8_t num_data_size, bool is_in);
> +int virtio_video_req_queue_clear(struct virtio_video *vv, uint32_t function_id,
> +                                struct virtio_video_stream *stream,
> +                                bool is_in);
> +int
> +virtio_video_req_resource_attach_backing(struct virtio_video *vv,
> +                                        uint32_t function_id,
> +                                        uint32_t stream_id,
> +                                        uint32_t resource_id,
> +                                        struct virtio_video_mem_entry *ents,
> +                                        uint32_t nents);
> +int
> +virtio_video_req_resource_detach_backing(struct virtio_video *vv,
> +                                        uint32_t function_id,
> +                                        uint32_t stream_id,
> +                                        struct virtio_video_buffer *virtio_vb);
> +int virtio_video_req_funcs(struct virtio_video *vv, void *resp_buf,
> +                          size_t resp_size);
> +int virtio_video_req_set_params(struct virtio_video *vv, uint32_t function_id,
> +                                       struct video_format_info *format_info,
> +                                       enum video_pin_type pin_type,
> +                                       enum video_params_scope scope,
> +                                       struct virtio_video_stream *stream);
> +int virtio_video_req_get_params(struct virtio_video *vv, uint32_t function_id,
> +                                       enum video_pin_type pin_type,
> +                                       enum video_params_scope scope,
> +                                       struct virtio_video_stream *stream);
> +int virtio_video_req_set_control(struct virtio_video *vv,
> +                                uint32_t function_id, uint32_t stream_id,
> +                                uint32_t control, uint32_t val);
> +
> +void virtio_video_queue_res_chg_event(struct virtio_video_stream *stream);
> +void virtio_video_queue_eos_event(struct virtio_video_stream *stream);
> +void virtio_video_ctrl_ack(struct virtqueue *vq);
> +void virtio_video_event_ack(struct virtqueue *vq);
> +void virtio_video_dequeue_ctrl_func(struct work_struct *work);
> +void virtio_video_dequeue_event_func(struct work_struct *work);
> +void virtio_video_buf_done(struct virtio_video_buffer *virtio_vb,
> +                          uint32_t flags, uint64_t timestamp, uint32_t size);
> +void virtio_video_mark_drain_complete(struct virtio_video_stream *stream,
> +                                     struct vb2_v4l2_buffer *v4l2_vb);
> +
> +void virtio_video_free_caps_list(struct list_head *caps_list);
> +size_t virtio_video_parse_virtio_function(void *func_buf,
> +                                         struct virtio_video_device *vvd);
> +void virtio_video_clean_virtio_function(struct virtio_video_device *vvd);
> +
> +uint32_t virtio_video_format_to_v4l2(uint32_t pixel_format);
> +uint32_t virtio_video_v4l2_fourcc_to_virtio(uint32_t fourcc);
> +uint32_t virtio_video_control_to_v4l2(uint32_t control_type);
> +uint32_t virtio_video_profile_to_v4l2(uint32_t profile);
> +uint32_t virtio_video_level_to_v4l2(uint32_t level);
> +uint32_t virtio_video_v4l2_control_to_virtio(uint32_t v4l2_control);
> +uint32_t virtio_video_v4l2_profile_to_virtio(uint32_t v4l2_profile);
> +uint32_t virtio_video_v4l2_level_to_virtio(uint32_t v4l2_level);
> +
> +#endif /* _VIRTIO_VIDEO_H */
> diff --git a/drivers/media/virtio/virtio_video_caps.c b/drivers/media/virtio/virtio_video_caps.c
> new file mode 100644
> index 000000000000..19b025805570
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video_caps.c
> @@ -0,0 +1,618 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/* Driver for virtio video device.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-dma-sg.h>
> +
> +#include "virtio_video.h"
> +
> +static void pix_format_free(struct video_pix_format *pix_fmt)
> +{
> +       int size_idx = 0;
> +
> +       if (!pix_fmt)
> +               return;
> +
> +       for (size_idx = 0; size_idx < pix_fmt->num_sizes; size_idx++)
> +               kfree(pix_fmt->frame_sizes[size_idx].frame_rates);
> +       kfree(pix_fmt->frame_sizes);
> +}
> +
> +static void fmt_capability_free(struct video_frame_format *frame_fmt)
> +{
> +       int fmt_idx = 0;
> +
> +       if (!frame_fmt)
> +               return;
> +
> +       for (fmt_idx = 0; fmt_idx < frame_fmt->num_formats; fmt_idx++)
> +               pix_format_free(&frame_fmt->pix_formats[fmt_idx]);
> +       kfree(frame_fmt->pix_formats);
> +}
> +
> +static void ctrl_capability_free(struct video_controls *controls)
> +{
> +       if (!controls)
> +               return;
> +
> +       kfree(controls->control);
> +}
> +
> +static void capability_free(struct video_capability *cap)
> +{
> +       if (!cap)
> +               return;
> +
> +       switch (cap->cap_type) {
> +       case VIRTIO_VIDEO_CAP_CONTROL:
> +               ctrl_capability_free(&cap->u.controls);
> +               break;
> +       case VIRTIO_VIDEO_CAP_PIN_FORMATS:
> +               fmt_capability_free(&cap->u.frame_format);
> +               break;
> +       default:
> +               return;
> +       }
> +       kfree(cap);
> +}
> +
> +static size_t parse_virtio_frame_rate(struct video_frame_rate *frame_rate,
> +                                     void *frame_rate_buf,
> +                                     struct virtio_video_device *vvd)
> +{
> +       struct virtio_video *vv = NULL;
> +       struct virtio_video_frame_rate *virtio_frame_rate = NULL;
> +       size_t frame_rate_size = sizeof(struct virtio_video_frame_rate);
> +
> +       if (!frame_rate || !frame_rate_buf || !vvd)
> +               return 0;
> +
> +       vv = vvd->vv;
> +       virtio_frame_rate = frame_rate_buf;
> +
> +       if (le32_to_cpu(virtio_frame_rate->desc.type) !=
> +           VIRTIO_VIDEO_DESC_FRAME_RATE) {
> +               v4l2_err(&vv->v4l2_dev,
> +                        "failed to read frame rate descriptor\n");
> +               return 0;
> +       }
> +
> +       frame_rate->min_rate = le32_to_cpu(virtio_frame_rate->min_rate);
> +       frame_rate->max_rate = le32_to_cpu(virtio_frame_rate->max_rate);
> +       frame_rate->step = le32_to_cpu(virtio_frame_rate->step);
> +
> +       return frame_rate_size;
> +}
> +
> +static size_t parse_virtio_frame_size(struct video_frame_size *frame_size,
> +                                     void *frame_size_buf,
> +                                     struct virtio_video_device *vvd)
> +{
> +       struct virtio_video *vv = NULL;
> +       size_t frame_size_size = 0;
> +       struct virtio_video_frame_size *virtio_frame_size = NULL;
> +       int rate_idx = 0;
> +       size_t offset = sizeof(struct virtio_video_frame_size);
> +
> +       if (!frame_size || !frame_size_buf || !vvd)
> +               return 0;
> +
> +       vv = vvd->vv;
> +       virtio_frame_size = frame_size_buf;
> +
> +       if (le32_to_cpu(virtio_frame_size->desc.type) !=
> +           VIRTIO_VIDEO_DESC_FRAME_SIZE) {
> +               v4l2_err(&vv->v4l2_dev,
> +                        "failed to read frame size descriptor\n");
> +               return 0;
> +       }
> +
> +       frame_size->min_height = le32_to_cpu(virtio_frame_size->min_height);
> +       frame_size->max_height = le32_to_cpu(virtio_frame_size->max_height);
> +       frame_size->step_height = le32_to_cpu(virtio_frame_size->step_height);
> +       frame_size->min_width = le32_to_cpu(virtio_frame_size->min_width);
> +       frame_size->max_width = le32_to_cpu(virtio_frame_size->max_width);
> +       frame_size->step_width = le32_to_cpu(virtio_frame_size->step_width);
> +       frame_size->num_rates = le32_to_cpu(virtio_frame_size->num_rates);
> +
> +       frame_size->frame_rates = kcalloc(frame_size->num_rates,
> +                                         sizeof(struct video_frame_rate),
> +                                         GFP_KERNEL);
> +       if (!frame_size->frame_rates) {
> +               v4l2_err(&vv->v4l2_dev, "failed to alloc frame rates\n");
> +               return 0;
> +       }
> +
> +       for (rate_idx = 0; rate_idx < frame_size->num_rates; rate_idx++) {
> +               struct video_frame_rate *frame_rate =
> +                       &frame_size->frame_rates[rate_idx];
> +               size_t rate_size = 0;
> +
> +               rate_size = parse_virtio_frame_rate(frame_rate,
> +                                                   frame_size_buf + offset,
> +                                                   vvd);
> +               if (rate_size == 0) {
> +                       v4l2_err(&vv->v4l2_dev, "failed to parse frame rate\n");
> +                       kfree(frame_size->frame_rates);
> +                       return 0;
> +               }
> +               offset += rate_size;
> +       }
> +
> +       frame_size_size = offset;
> +
> +       return frame_size_size;
> +}
> +
> +static size_t parse_virtio_pix_fmt(struct video_pix_format *pix_fmt,
> +                                  void *pix_buf,
> +                                  struct virtio_video_device *vvd)
> +{
> +       struct virtio_video *vv = NULL;
> +       size_t pix_fmt_size = 0;
> +       struct virtio_video_pix_format *virtio_pix_fmt = NULL;
> +       int size_idx = 0;
> +       size_t offset = sizeof(struct virtio_video_pix_format);
> +
> +       if (!pix_fmt || !pix_buf || !vvd)
> +               return 0;
> +
> +       vv = vvd->vv;
> +       virtio_pix_fmt = pix_buf;
> +
> +       if (le32_to_cpu(virtio_pix_fmt->desc.type) !=
> +           VIRTIO_VIDEO_DESC_PIX_FORMAT) {
> +               v4l2_err(&vv->v4l2_dev, "failed to read pix fmt descriptor\n");
> +               return 0;
> +       }
> +
> +       pix_fmt->fourcc_format =
> +                       virtio_video_format_to_v4l2(
> +                               le32_to_cpu(virtio_pix_fmt->pixel_format));
> +       pix_fmt->num_sizes = le32_to_cpu(virtio_pix_fmt->num_sizes);
> +
> +       pix_fmt->frame_sizes = kcalloc(pix_fmt->num_sizes,
> +                                      sizeof(struct video_frame_size),
> +                                      GFP_KERNEL);
> +       if (!pix_fmt->frame_sizes) {
> +               v4l2_err(&vv->v4l2_dev, "failed to alloc frame sizes\n");
> +               return 0;
> +       }
> +
> +       for (size_idx = 0; size_idx < pix_fmt->num_sizes; size_idx++) {
> +               struct video_frame_size *frame_size = NULL;
> +               size_t size_size = 0;
> +
> +               frame_size = &pix_fmt->frame_sizes[size_idx];
> +               size_size = parse_virtio_frame_size(frame_size,
> +                                                   pix_buf + offset, vvd);
> +               if (size_size == 0) {
> +                       v4l2_err(&vv->v4l2_dev, "failed to parse frame size\n");
> +                       kfree(pix_fmt->frame_sizes);
> +                       return 0;
> +               }
> +               offset += size_size;
> +       }
> +
> +       pix_fmt_size = offset;
> +
> +       return pix_fmt_size;
> +}
> +
> +static size_t parse_virtio_fmts_cap(struct video_frame_format *frame_fmt,
> +                                   void *cap_buf,
> +                                   struct virtio_video_device *vvd)
> +{
> +       struct virtio_video *vv = NULL;
> +       size_t fmts_size = 0;
> +       struct virtio_video_frame_format *virtio_fmt = NULL;
> +       int fmt_idx = 0;
> +       size_t offset = sizeof(struct virtio_video_frame_format);
> +       struct virtio_video_capability dummy;
> +
> +       if (!frame_fmt || !cap_buf || !vvd)
> +               return 0;
> +
> +       vv = vvd->vv;
> +       virtio_fmt = cap_buf;
> +
> +       frame_fmt->pin_type = le32_to_cpu(virtio_fmt->pin_type);
> +       frame_fmt->num_formats = le32_to_cpu(virtio_fmt->num_formats);
> +
> +       frame_fmt->pix_formats = kcalloc(frame_fmt->num_formats,
> +                                        sizeof(struct video_pix_format),
> +                                        GFP_KERNEL);
> +       if (!frame_fmt->pix_formats) {
> +               v4l2_err(&vv->v4l2_dev, "failed to alloc pix formats\n");
> +               return 0;
> +       }
> +
> +       for (fmt_idx = 0; fmt_idx < frame_fmt->num_formats; fmt_idx++) {
> +               struct video_pix_format *pix_fmt = NULL;
> +               size_t fmt_size = 0;
> +
> +               pix_fmt = &frame_fmt->pix_formats[fmt_idx];
> +               fmt_size = parse_virtio_pix_fmt(pix_fmt, cap_buf + offset, vvd);
> +               if (fmt_size == 0) {
> +                       v4l2_err(&vv->v4l2_dev,
> +                                "failed to parse pixel format\n");
> +                       fmt_capability_free(frame_fmt);
> +                       return 0;
> +               }
> +               offset += fmt_size;
> +       }
> +
> +       switch (frame_fmt->pin_type) {
> +       case VIRTIO_VIDEO_PIN_INPUT:
> +               vvd->num_output_formats += frame_fmt->num_formats;
> +               break;
> +       case VIRTIO_VIDEO_PIN_OUTPUT:
> +               vvd->num_capture_formats += frame_fmt->num_formats;
> +               break;
> +       default:
> +               v4l2_err(&vv->v4l2_dev, "failed to parse a pin type\n");
> +               return 0;
> +       }
> +
> +       fmts_size = offset - sizeof(dummy.u);
> +
> +       return fmts_size;
> +}
> +
> +static size_t parse_virtio_ctrl(struct video_control *control,
> +                               void *control_buf,
> +                               struct virtio_video_device *vvd)
> +{
> +       struct virtio_video *vv = NULL;
> +       struct virtio_video_control *virtio_control = NULL;
> +       size_t control_size = sizeof(struct virtio_video_control);
> +
> +       if (!control || !control_buf || !vvd)
> +               return 0;
> +
> +       vv = vvd->vv;
> +       virtio_control = control_buf;
> +
> +       if (le32_to_cpu(virtio_control->desc.type) !=
> +           VIRTIO_VIDEO_DESC_CONTROL) {
> +               v4l2_err(&vv->v4l2_dev, "failed to read control descriptor\n");
> +               return 0;
> +       }
> +
> +       control->control_type =
> +               virtio_video_control_to_v4l2
> +               (le32_to_cpu(virtio_control->control_type));
> +
> +       switch (control->control_type) {
> +       case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
> +               control->min =
> +                       virtio_video_profile_to_v4l2
> +                       (le64_to_cpu(virtio_control->min));
> +               control->max =
> +                       virtio_video_profile_to_v4l2
> +                       (le64_to_cpu(virtio_control->max));
> +               control->step =
> +                       virtio_video_profile_to_v4l2
> +                       (le32_to_cpu(virtio_control->step));
> +               control->def =
> +                       virtio_video_profile_to_v4l2
> +                       (le64_to_cpu(virtio_control->def));
> +               break;
> +       case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
> +               control->min =
> +                       virtio_video_level_to_v4l2
> +                       (le64_to_cpu(virtio_control->min));
> +               control->max =
> +                       virtio_video_level_to_v4l2
> +                       (le64_to_cpu(virtio_control->max));
> +               control->step =
> +                       virtio_video_level_to_v4l2
> +                       (le32_to_cpu(virtio_control->step));
> +               control->def =
> +                       virtio_video_level_to_v4l2
> +                       (le64_to_cpu(virtio_control->def));
> +               break;
> +       default:
> +               control->min = le64_to_cpu(virtio_control->min);
> +               control->max = le64_to_cpu(virtio_control->max);
> +               control->step = le32_to_cpu(virtio_control->step);
> +               control->def = le64_to_cpu(virtio_control->def);
> +               break;
> +       }
> +
> +       return control_size;
> +}
> +
> +static size_t parse_virtio_ctrls_cap(struct video_controls *controls,
> +                                    void *cap_buf,
> +                                    struct virtio_video_device *vvd)
> +{
> +       struct virtio_video_controls *virtio_controls = NULL;
> +       struct virtio_video *vv = NULL;
> +       size_t ctrls_size = 0;
> +       int ctrl_idx = 0;
> +       size_t offset = sizeof(struct virtio_video_controls);
> +       struct virtio_video_capability dummy;
> +
> +       if (!controls || !cap_buf || !vvd)
> +               return 0;
> +
> +       vv = vvd->vv;
> +       virtio_controls = cap_buf;
> +
> +       controls->num_controls = le32_to_cpu(virtio_controls->num_controls);
> +       controls->control = kcalloc(controls->num_controls,
> +                                   sizeof(struct video_control),
> +                                   GFP_KERNEL);
> +       if (!controls->control) {
> +               v4l2_err(&vv->v4l2_dev, "failed to alloc controls\n");
> +               return 0;
> +       }
> +
> +       for (ctrl_idx = 0; ctrl_idx < controls->num_controls; ctrl_idx++) {
> +               struct video_control *ctrl = NULL;
> +               size_t ctrl_size = 0;
> +
> +               ctrl = &controls->control[ctrl_idx];
> +               ctrl_size = parse_virtio_ctrl(ctrl, cap_buf + offset, vvd);
> +               if (ctrl_size == 0) {
> +                       v4l2_err(&vv->v4l2_dev, "failed to parse control\n");
> +                       ctrl_capability_free(controls);
> +                       return 0;
> +               }
> +               offset += ctrl_size;
> +       }
> +
> +       ctrls_size = offset - sizeof(dummy.u);
> +
> +       return ctrls_size;
> +}
> +
> +static size_t parse_virtio_capability(struct video_capability *cap,
> +                                     void *cap_buf,
> +                                     struct virtio_video_device *vvd)
> +{
> +       struct virtio_video *vv = NULL;
> +       struct virtio_video_capability *virtio_cap = NULL;
> +       size_t offset = 0;
> +       size_t extra_size = 0;
> +       size_t cap_size = 0;
> +
> +       if (!cap || !cap_buf || !vvd)
> +               return 0;
> +
> +       vv = vvd->vv;
> +       virtio_cap = cap_buf;
> +       offset = sizeof(struct virtio_video_capability) - sizeof(virtio_cap->u);
> +
> +       if (le32_to_cpu(virtio_cap->desc.type) != VIRTIO_VIDEO_DESC_CAP) {
> +               v4l2_err(&vv->v4l2_dev,
> +                        "failed to read capability descriptor\n");
> +               return 0;
> +       }
> +
> +       cap->cap_id = le32_to_cpu(virtio_cap->cap_id);
> +       cap->cap_type = le32_to_cpu(virtio_cap->cap_type);
> +
> +       switch (cap->cap_type) {
> +       case VIRTIO_VIDEO_CAP_CONTROL:
> +               extra_size = parse_virtio_ctrls_cap(&cap->u.controls,
> +                                                  cap_buf + offset, vvd);
> +               if (extra_size == 0) {
> +                       v4l2_err(&vv->v4l2_dev, "failed to parse ctrl cap\n");
> +                       return 0;
> +               }
> +               break;
> +       case VIRTIO_VIDEO_CAP_PIN_FORMATS:
> +               extra_size = parse_virtio_fmts_cap(&cap->u.frame_format,
> +                                                  cap_buf + offset, vvd);
> +               if (extra_size == 0) {
> +                       v4l2_err(&vv->v4l2_dev, "failed to parse fmts cap\n");
> +                       return 0;
> +               }
> +               break;
> +       default:
> +               v4l2_err(&vv->v4l2_dev, "undefined capability type\n");
> +               return 0;
> +       }
> +
> +       if (extra_size < 0)
> +               extra_size = 0;
> +
> +       cap_size = sizeof(struct virtio_video_capability) + extra_size;
> +
> +       return cap_size;
> +
> +}
> +
> +void virtio_video_free_caps_list(struct list_head *caps_list)
> +{
> +       struct video_capability *cap = NULL;
> +       struct video_capability *tmp = NULL;
> +
> +       list_for_each_entry_safe(cap, tmp, caps_list, caps_list_entry) {
> +               list_del(&cap->caps_list_entry);
> +               capability_free(cap);
> +       }
> +
> +}
> +
> +static int virtio_video_copy_params(struct video_format_info *dst,
> +                                  const struct virtio_video_params *src)
> +{
> +       if (!dst || !src)
> +               return -EINVAL;
> +
> +       dst->fourcc_format =
> +                       virtio_video_format_to_v4l2(
> +                               le32_to_cpu(src->pixel_format));
> +       dst->frame_height = le32_to_cpu(src->frame_height);
> +       dst->frame_width = le32_to_cpu(src->frame_width);
> +       dst->frame_rate = le32_to_cpu(src->frame_rate);
> +       dst->min_buffers = le32_to_cpu(src->min_buffers);
> +       return 0;
> +}
> +
> +size_t virtio_video_parse_virtio_function(void *func_buf,
> +                                         struct virtio_video_device *vvd)
> +{
> +       struct virtio_video *vv = NULL;
> +       size_t func_size = 0;
> +       struct virtio_video_function *virtio_func = NULL;
> +       uint32_t num_caps = 0;
> +       int cap_idx = 0;
> +       size_t offset = sizeof(struct virtio_video_function);
> +       struct video_capability *cap = NULL;
> +       int capture_idx = 0;
> +       int output_idx = 0;
> +       int ret = 0;
> +
> +       if (!func_buf || !vvd)
> +               return 0;
> +
> +       vv = vvd->vv;
> +       virtio_func = func_buf;
> +
> +       if (le32_to_cpu(virtio_func->desc.type) != VIRTIO_VIDEO_DESC_FUNC) {
> +               v4l2_err(&vv->v4l2_dev, "failed to read function descriptor\n");
> +               return 0;
> +       }
> +
> +       vvd->id = le32_to_cpu(virtio_func->function_id);
> +       vvd->type = le32_to_cpu(virtio_func->function_type);
> +
> +       if (le32_to_cpu(virtio_func->in_params.desc.type) !=
> +           VIRTIO_VIDEO_DESC_PARAMS) {
> +               v4l2_err(&vv->v4l2_dev, "failed to read function params\n");
> +               return 0;
> +       }
> +
> +       ret = virtio_video_copy_params(&vvd->in_info,
> +                                        &virtio_func->in_params);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to read 'in' params\n");
> +               return 0;
> +       }
> +
> +       if (le32_to_cpu(virtio_func->out_params.desc.type) !=
> +           VIRTIO_VIDEO_DESC_PARAMS) {
> +               v4l2_err(&vv->v4l2_dev, "failed to read function params\n");
> +               return 0;
> +       }
> +
> +       ret = virtio_video_copy_params(&vvd->out_info,
> +                                        &virtio_func->out_params);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to read 'out' params\n");
> +               return 0;
> +       }
> +
> +       num_caps = le32_to_cpu(virtio_func->num_caps);
> +
> +       for (cap_idx = 0; cap_idx < num_caps; cap_idx++) {
> +               size_t cap_size = 0;
> +
> +               cap = kzalloc(sizeof(*cap), GFP_KERNEL);
> +
> +               if (!cap) {
> +                       virtio_video_free_caps_list(&vvd->ctrl_caps_list);
> +                       virtio_video_free_caps_list(&vvd->fmt_caps_list);
> +                       return 0;
> +               }
> +
> +               cap_size = parse_virtio_capability(cap, func_buf + offset, vvd);
> +               if (cap_size == 0) {
> +                       v4l2_err(&vv->v4l2_dev,
> +                                "failed to parse a capability\n");
> +                       virtio_video_free_caps_list(&vvd->ctrl_caps_list);
> +                       virtio_video_free_caps_list(&vvd->fmt_caps_list);
> +                       kfree(cap);
> +                       return 0;
> +               }
> +               offset += cap_size;
> +
> +               switch (cap->cap_type) {
> +               case VIRTIO_VIDEO_CAP_PIN_FORMATS:
> +                       list_add(&cap->caps_list_entry, &vvd->fmt_caps_list);
> +                       break;
> +               case VIRTIO_VIDEO_CAP_CONTROL:
> +                       list_add(&cap->caps_list_entry, &vvd->ctrl_caps_list);
> +                       break;
> +               default:
> +                       virtio_video_free_caps_list(&vvd->ctrl_caps_list);
> +                       virtio_video_free_caps_list(&vvd->fmt_caps_list);
> +                       capability_free(cap);
> +                       break;
> +               }
> +       }
> +
> +       vvd->capture_fmts = kcalloc(vvd->num_capture_formats,
> +                                   sizeof(struct video_capability *),
> +                                   GFP_KERNEL);
> +       vvd->output_fmts = kcalloc(vvd->num_output_formats,
> +                                  sizeof(struct video_capability *),
> +                                  GFP_KERNEL);
> +       if (!vvd->capture_fmts || !vvd->output_fmts) {
> +               v4l2_err(&vv->v4l2_dev, "failed to alloc capability arrays\n");
> +               virtio_video_free_caps_list(&vvd->ctrl_caps_list);
> +               virtio_video_free_caps_list(&vvd->fmt_caps_list);
> +       }
> +
> +       cap = NULL;
> +       list_for_each_entry(cap, &vvd->fmt_caps_list, caps_list_entry) {
> +               struct video_frame_format *frame_fmt = &cap->u.frame_format;
> +               int pix_idx = 0;
> +
> +               switch (le32_to_cpu(frame_fmt->pin_type)) {
> +               case VIRTIO_VIDEO_PIN_INPUT:
> +                       for (pix_idx = 0; pix_idx < frame_fmt->num_formats;
> +                            pix_idx++) {
> +                               vvd->output_fmts[pix_idx + output_idx] =
> +                                       &frame_fmt->pix_formats[pix_idx];
> +                       }
> +                       output_idx += pix_idx;
> +                       break;
> +               case VIRTIO_VIDEO_PIN_OUTPUT:
> +                       for (pix_idx = 0; pix_idx < frame_fmt->num_formats;
> +                            pix_idx++) {
> +                               vvd->capture_fmts[pix_idx + capture_idx] =
> +                                       &frame_fmt->pix_formats[pix_idx];
> +                       }
> +                       capture_idx += pix_idx;
> +                       break;
> +               default:
> +                       v4l2_err(&vv->v4l2_dev, "failed to parse a pin type\n");
> +                       return 0;
> +               }
> +       }
> +
> +       func_size = offset;
> +       return func_size;
> +}
> +
> +void virtio_video_clean_virtio_function(struct virtio_video_device *vvd)
> +{
> +       if (!vvd)
> +               return;
> +
> +       kfree(vvd->capture_fmts);
> +       kfree(vvd->output_fmts);
> +       virtio_video_free_caps_list(&vvd->ctrl_caps_list);
> +       virtio_video_free_caps_list(&vvd->fmt_caps_list);
> +}
> diff --git a/drivers/media/virtio/virtio_video_dec.c b/drivers/media/virtio/virtio_video_dec.c
> new file mode 100644
> index 000000000000..c2ad62229d21
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video_dec.c
> @@ -0,0 +1,947 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/* Decoder for virtio video device.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-dma-sg.h>
> +
> +#include "virtio_video.h"
> +
> +static 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 (*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 int virtio_video_buf_plane_init(uint32_t idx,
> +                                      uint32_t resource_id,
> +                                      struct virtio_video_device *vvd,
> +                                      struct virtio_video_stream *stream,
> +                                      struct vb2_buffer *vb)
> +{
> +       int ret;
> +       unsigned int i;
> +       struct virtio_video *vv = vvd->vv;
> +       struct scatterlist *sg;
> +       struct virtio_video_mem_entry *ents;
> +       struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, idx);
> +
> +       /* Freed when the request has been completed */
> +       ents = kcalloc(sgt->nents, sizeof(*ents), GFP_KERNEL);
> +       for_each_sg(sgt->sgl, sg, sgt->nents, i) {
> +               ents[i].addr = cpu_to_le64(vv->has_iommu
> +                                          ? sg_dma_address(sg)
> +                                          : sg_phys(sg));
> +               ents[i].length = cpu_to_le32(sg->length);
> +       }
> +
> +       v4l2_dbg(1, vv->debug, &vv->v4l2_dev, "mem entries:\n");
> +       if (vv->debug >= 1) {
> +               for (i = 0; i < sgt->nents; i++)
> +                       pr_debug("\t%03i: addr=%llx length=%u\n", i,
> +                                ents[i].addr, ents[i].length);
> +       }
> +
> +       ret = virtio_video_req_resource_attach_backing(vv, vvd->id,
> +                                                      stream->stream_id,
> +                                                      resource_id, ents,
> +                                                      sgt->nents);
> +       if (ret)
> +               kfree(ents);
> +
> +       return ret;
> +}
> +
> +static int virtio_video_buf_init(struct vb2_buffer *vb)
> +{
> +       int ret = 0;
> +       unsigned int i;
> +       uint32_t resource_id;
> +       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);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       virtio_video_resource_id_get(vv, &resource_id);
> +       ret = virtio_video_req_resource_create(vv, vvd->id, stream->stream_id,
> +                                              resource_id);
> +       if (ret)
> +               return ret;
> +
> +       for (i = 0; i < vb->num_planes; ++i) {
> +               ret = virtio_video_buf_plane_init(i,
> +                               resource_id, vvd, stream, vb);
> +               if (ret)
> +                       break;
> +       }
> +
> +       if (ret) {
> +               virtio_video_req_resource_destroy(vvd->vv, vvd->id,
> +                                                 stream->stream_id,
> +                                                 resource_id);
> +               virtio_video_resource_id_put(vvd->vv, resource_id);
> +               return ret;
> +       }
> +
> +       virtio_vb->queued = false;
> +       virtio_vb->detached = false;
> +       virtio_vb->resource_id = resource_id;
> +
> +       return 0;
> +}
> +
> +static void virtio_video_buf_cleanup(struct vb2_buffer *vb)
> +{
> +       int ret;
> +       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);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       ret = virtio_video_req_resource_detach_backing(vv, vvd->id,
> +                                                      stream->stream_id,
> +                                                      virtio_vb);
> +       if (ret)
> +               return;
> +
> +       ret = wait_event_timeout(vv->wq, virtio_vb->detached, 5 * HZ);
> +       if (ret == 0) {
> +               v4l2_err(&vv->v4l2_dev, "timed out waiting for detach\n");
> +               return;
> +       }
> +
> +       virtio_video_req_resource_destroy(vv, vvd->id, stream->stream_id,
> +                                         virtio_vb->resource_id);
> +       virtio_video_resource_id_put(vv, virtio_vb->resource_id);
> +}
> +
> +static void virtio_video_buf_queue(struct vb2_buffer *vb)
> +{
> +       int i, ret;
> +       struct vb2_buffer *src_buf;
> +       struct virtio_video_buffer *virtio_vb;
> +       uint32_t data_size[VB2_MAX_PLANES] = {0};
> +       struct vb2_v4l2_buffer *v4l2_vb = to_vb2_v4l2_buffer(vb);
> +       struct vb2_v4l2_buffer *src_vb;
> +       struct virtio_video_stream *stream = vb2_get_drv_priv(vb->vb2_queue);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       v4l2_m2m_buf_queue(stream->fh.m2m_ctx, v4l2_vb);
> +
> +       if ((stream->state != STREAM_STATE_INIT) ||
> +           !V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type))
> +               return;
> +
> +       src_vb = v4l2_m2m_next_src_buf(stream->fh.m2m_ctx);
> +       if (!src_vb) {
> +               v4l2_err(&vv->v4l2_dev, "no src buf during initialization\n");
> +               return;
> +       }
> +
> +       src_buf = &src_vb->vb2_buf;
> +       for (i = 0; i < src_buf->num_planes; ++i)
> +               data_size[i] = src_buf->planes[i].bytesused;
> +
> +       virtio_vb = to_virtio_vb(src_buf);
> +
> +       ret = virtio_video_req_resource_queue(vv, vvd->id, stream->stream_id,
> +                                             virtio_vb, data_size,
> +                                             src_buf->num_planes,
> +                                             true);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to queue an src buffer\n");
> +               return;
> +       }
> +
> +       virtio_vb->queued = true;
> +       stream->src_cleared = false;
> +       src_vb = v4l2_m2m_src_buf_remove(stream->fh.m2m_ctx);
> +}
> +
> +static int virtio_video_start_streaming(struct vb2_queue *vq,
> +                                       unsigned int count)
> +{
> +       int ret;
> +       struct virtio_video_stream *stream = vb2_get_drv_priv(vq);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       if ((V4L2_TYPE_IS_OUTPUT(vq->type) &&
> +           (stream->state == STREAM_STATE_INIT)) ||
> +           (stream->state == STREAM_STATE_STOPPED)) {
> +               ret = virtio_video_req_stream_start(vv, vvd->id,
> +                                                   stream->stream_id);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(vq->type) &&
> +           (stream->state >= STREAM_STATE_INIT))
> +               stream->state = STREAM_STATE_RUNNING;
> +
> +       return 0;
> +}
> +
> +static void virtio_video_stop_streaming(struct vb2_queue *vq)
> +{
> +       int ret;
> +       bool *cleared;
> +       bool is_output = V4L2_TYPE_IS_OUTPUT(vq->type);
> +       struct virtio_video_stream *stream = vb2_get_drv_priv(vq);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +       struct vb2_v4l2_buffer *v4l2_vb;
> +
> +       if (is_output)
> +               cleared = &stream->src_cleared;
> +       else
> +               cleared = &stream->dst_cleared;
> +
> +       ret = virtio_video_req_queue_clear(vv, vvd->id, stream, is_output);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to clear queue\n");
> +               return;
> +       }
> +
> +       ret = wait_event_timeout(vv->wq, *cleared, 5 * HZ);
> +       if (ret == 0) {
> +               v4l2_err(&vv->v4l2_dev, "timed out waiting for queue clear\n");
> +               return;
> +       }
> +
> +       for (;;) {
> +               if (is_output)
> +                       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);
> +       }
> +}
> +
> +static const struct vb2_ops virtio_video_qops = {
> +       .queue_setup     = virtio_video_queue_setup,
> +       .buf_init        = virtio_video_buf_init,
> +       .buf_cleanup     = virtio_video_buf_cleanup,
> +       .buf_queue       = virtio_video_buf_queue,
> +       .start_streaming = virtio_video_start_streaming,
> +       .stop_streaming  = virtio_video_stop_streaming,
> +       .wait_prepare    = vb2_ops_wait_prepare,
> +       .wait_finish     = vb2_ops_wait_finish,
> +};
> +
> +static int virtio_video_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       int ret = 0;
> +       struct virtio_video_stream *stream = ctrl2stream(ctrl);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +
> +       switch (ctrl->id) {
> +       case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE:
> +               if (stream->state >= STREAM_STATE_METADATA)
> +                       ctrl->val = vvd->out_info.min_buffers;
> +               else
> +                       ctrl->val = 0;
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +
> +       return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops virtio_video_ctrl_ops = {
> +       .g_volatile_ctrl        = virtio_video_g_volatile_ctrl,
> +};
> +
> +int virtio_video_init_dec_ctrls(struct virtio_video_stream *stream)
> +{
> +       struct v4l2_ctrl *ctrl;
> +
> +       v4l2_ctrl_handler_init(&stream->ctrl_handler, 1);
> +
> +       ctrl = v4l2_ctrl_new_std(&stream->ctrl_handler,
> +                               &virtio_video_ctrl_ops,
> +                               V4L2_CID_MIN_BUFFERS_FOR_CAPTURE,
> +                               MIN_BUFS_MIN, MIN_BUFS_MAX, MIN_BUFS_STEP,
> +                               MIN_BUFS_DEF);
> +       ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> +
> +       if (stream->ctrl_handler.error)
> +               return stream->ctrl_handler.error;
> +
> +       v4l2_ctrl_handler_setup(&stream->ctrl_handler);
> +
> +       return 0;
> +}
> +
> +int virtio_video_init_dec_queues(void *priv, struct vb2_queue *src_vq,
> +                                struct vb2_queue *dst_vq)
> +{
> +       int ret;
> +       struct virtio_video_stream *stream = priv;
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct device *dev = vvd->vv->v4l2_dev.dev;
> +
> +       src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +       src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> +       src_vq->drv_priv = stream;
> +       src_vq->buf_struct_size = sizeof(struct virtio_video_buffer);
> +       src_vq->ops = &virtio_video_qops;
> +       src_vq->mem_ops = &vb2_dma_sg_memops;
> +       src_vq->min_buffers_needed = vvd->in_info.min_buffers;
> +       src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +       src_vq->lock = &stream->vq_mutex;
> +       src_vq->dev = dev;
> +
> +       ret = vb2_queue_init(src_vq);
> +       if (ret)
> +               return ret;
> +
> +       dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +       dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> +       dst_vq->drv_priv = stream;
> +       dst_vq->buf_struct_size = sizeof(struct virtio_video_buffer);
> +       dst_vq->ops = &virtio_video_qops;
> +       dst_vq->mem_ops = &vb2_dma_sg_memops;
> +       dst_vq->min_buffers_needed = vvd->out_info.min_buffers;
> +       dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +       dst_vq->lock = &stream->vq_mutex;
> +       dst_vq->dev = dev;
> +
> +       return vb2_queue_init(dst_vq);
> +}
> +
> +static int virtio_video_querycap(struct file *file, void *fh,
> +                                struct v4l2_capability *cap)
> +{
> +       struct video_device *video_dev = video_devdata(file);
> +
> +       strncpy(cap->driver, DRIVER_NAME, sizeof(cap->driver));
> +       strncpy(cap->card, video_dev->name, sizeof(cap->card));
> +       snprintf(cap->bus_info, sizeof(cap->bus_info), "virtio:%s",
> +                video_dev->name);
> +
> +       cap->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
> +       cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
> +
> +       return 0;
> +}
> +
> +static int virtio_video_enum_fmt_vid_cap(struct file *file, void *fh,
> +                                        struct v4l2_fmtdesc *f)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +
> +       if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> +               return -EINVAL;
> +
> +       if (f->index >= vvd->num_capture_formats)
> +               return -EINVAL;
> +
> +       f->pixelformat = vvd->capture_fmts[f->index]->fourcc_format;
> +
> +       return 0;
> +}
> +
> +static struct video_pix_format *find_pix_format(struct video_pix_format **list,
> +                                               uint32_t fourcc, int num)
> +{
> +       int idx = 0;
> +
> +       for (idx = 0; idx < num; idx++) {
> +               if (list[idx]->fourcc_format == fourcc)
> +                       return list[idx];
> +       }
> +       return NULL;
> +}
> +
> +static int virtio_video_try_decoder_cmd(struct file *file, void *fh,
> +                                       struct v4l2_decoder_cmd *cmd)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = video_drvdata(file);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       if (stream->state == STREAM_STATE_DRAIN)
> +               return -EBUSY;
> +
> +       switch (cmd->cmd) {
> +       case V4L2_DEC_CMD_STOP:
> +       case V4L2_DEC_CMD_START:
> +               if (cmd->flags != 0) {
> +                       v4l2_err(&vv->v4l2_dev, "flags=%u are not supported",
> +                                cmd->flags);
> +                       return -EINVAL;
> +               }
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static int virtio_video_decoder_cmd(struct file *file, void *fh,
> +                                   struct v4l2_decoder_cmd *cmd)
> +{
> +       int ret;
> +       struct vb2_queue *src_vq, *dst_vq;
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = video_drvdata(file);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       ret = virtio_video_try_decoder_cmd(file, fh, cmd);
> +       if (ret < 0)
> +               return ret;
> +
> +       dst_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx,
> +                                V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
> +
> +       switch (cmd->cmd) {
> +       case V4L2_DEC_CMD_START:
> +               vb2_clear_last_buffer_dequeued(dst_vq);
> +               ret = virtio_video_req_stream_start(vv, vvd->id,
> +                                                   stream->stream_id);
> +               if (ret)
> +                       return ret;
> +               break;
> +       case V4L2_DEC_CMD_STOP:
> +               src_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx,
> +                                        V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
> +
> +               if (!vb2_is_streaming(src_vq)) {
> +                       v4l2_dbg(1, vv->debug,
> +                                &vv->v4l2_dev, "output is not streaming\n");
> +                       return 0;
> +               }
> +
> +               if (!vb2_is_streaming(dst_vq)) {
> +                       v4l2_dbg(1, vv->debug,
> +                                &vv->v4l2_dev, "capture is not streaming\n");
> +                       return 0;
> +               }
> +
> +               ret = virtio_video_req_stream_drain(vv, vvd->id,
> +                                                   stream->stream_id);
> +               if (ret) {
> +                       v4l2_err(&vv->v4l2_dev, "failed to drain stream\n");
> +                       return ret;
> +               }
> +               stream->state = STREAM_STATE_DRAIN;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static 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_frame_size *frame_size = NULL;
> +       int i = 0;
> +       bool fake_non_discrete = false;
> +       int idx = f->index;
> +       struct video_pix_format *fmt = NULL;
> +
> +       fmt = find_pix_format(vvd->output_fmts, f->pixel_format,
> +                             vvd->num_output_formats);
> +       if (fmt == NULL)
> +               fmt = find_pix_format(vvd->capture_fmts, f->pixel_format,
> +                                     vvd->num_capture_formats);
> +       if (fmt == NULL)
> +               return -EINVAL;
> +
> +       if (idx >= fmt->num_sizes)
> +               return -EINVAL;
> +
> +       /* If the index is 0 - it is the first call of ENUM_FRAMESIZES, that
> +        * defines a type of all the frame sizes.
> +        *
> +        * Indexes > 0 can be used later only in case of the type is discrete.
> +        * But, if there is at least one non-discrete type later in the array -
> +        * it may be misinterpreted as a discrete one.
> +        *
> +        * Hence, check, whether there is a non-discrete frame size, and if yes
> +        * - return the first of them.
> +        */
> +       if (!idx)
> +               for (i = 0; i < fmt->num_sizes; i++) {
> +                       frame_size = &fmt->frame_sizes[i];
> +                       if (frame_size->min_width != frame_size->max_width ||
> +                           frame_size->min_height != frame_size->max_height) {
> +                               idx = i;
> +                               fake_non_discrete = true;
> +                               break;
> +                       }
> +               }
> +
> +       /* Index > 0 can be used only for discrete frame sizes. Type of the
> +        * frame sizes is equal to type of the first frame size.
> +        */
> +       if (idx && !fake_non_discrete) {
> +               frame_size = &fmt->frame_sizes[0];
> +               if (frame_size->min_width != frame_size->max_width ||
> +                   frame_size->min_height != frame_size->max_height)
> +                       return -EINVAL;
> +       }
> +
> +       frame_size = &fmt->frame_sizes[idx];
> +
> +       if (frame_size->min_width == frame_size->max_width &&
> +          frame_size->min_height == frame_size->max_height) {
> +               f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> +               f->discrete.width = frame_size->min_width;
> +               f->discrete.height = frame_size->min_height;
> +       } else {
> +               if (idx && !fake_non_discrete)
> +                       return -EINVAL;
> +               f->stepwise.min_width = frame_size->min_width;
> +               f->stepwise.min_height = frame_size->min_height;
> +               f->stepwise.step_height = frame_size->step_height;
> +               f->stepwise.step_width = frame_size->step_width;
> +               f->stepwise.max_height = frame_size->max_height;
> +               f->stepwise.max_width = frame_size->max_width;
> +               if (frame_size->step_width == 1 &&
> +                   frame_size->min_height == 1) {
> +                       f->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
> +               } else {
> +                       f->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static bool in_stepped_interval(uint32_t int_start, uint32_t int_end,
> +                               uint32_t step, uint32_t point)
> +{
> +       if (point < int_start || point > int_end)
> +               return false;
> +
> +       if (step == 0 && int_start == int_end && int_start == point)
> +               return true;
> +
> +       if (step != 0 && (point - int_start) % step == 0)
> +               return true;
> +
> +       return false;
> +}
> +
> +static 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_frame_size *fsize = NULL;
> +       int fsize_idx = 0;
> +       int i = 0;
> +       bool fake_non_discrete = false;
> +       int idx = f->index;
> +       struct video_pix_format *fmt = NULL;
> +       struct video_frame_rate *frate = NULL;
> +
> +       fmt = find_pix_format(vvd->output_fmts, f->pixel_format,
> +                             vvd->num_output_formats);
> +       if (fmt == NULL)
> +               fmt = find_pix_format(vvd->capture_fmts, f->pixel_format,
> +                                     vvd->num_capture_formats);
> +       if (fmt == NULL)
> +               return -EINVAL;
> +
> +       for (fsize_idx = 0; fsize_idx <= fmt->num_sizes; fsize_idx++) {
> +               fsize = &fmt->frame_sizes[fsize_idx];
> +               if (in_stepped_interval(fsize->min_width, fsize->max_width,
> +                                       fsize->step_width, f->width) &&
> +                  in_stepped_interval(fsize->min_height, fsize->max_height,
> +                                       fsize->step_height, f->height))
> +                       break;
> +       }
> +
> +       if (fsize == NULL)
> +               return -EINVAL;
> +
> +       if (idx >= fsize->num_rates)
> +               return -EINVAL;
> +
> +       /* If the index is 0 - it is the first call of ENUM_FRAMEIVALS, that
> +        * defines a type of all the frame intervals.
> +        *
> +        * Indexes > 0 can be used later only in case of the type is discrete.
> +        * But, if there is at least one non-discrete type later in the array -
> +        * it may be misinterpreted as a discrete one.
> +        *
> +        * Hence, check, whether there is a non-discrete frame rate, and if yes
> +        * - return the first of them.
> +        */
> +       if (!idx)
> +               for (i = 0; i < fsize->num_rates; i++) {
> +                       frate = &fsize->frame_rates[i];
> +                       if (frate->min_rate != frate->max_rate) {
> +                               fake_non_discrete = true;
> +                               idx = i;
> +                               break;
> +                       }
> +               }
> +
> +       /* Index > 0 can be used only for discrete frame rates. Type of the
> +        * frame rate is equal to the type of the first frame size.
> +        */
> +       if (idx && !fake_non_discrete) {
> +               frate = &fsize->frame_rates[0];
> +               if (frate->max_rate != frate->min_rate)
> +                       return -EINVAL;
> +       }
> +
> +       frate = &fsize->frame_rates[idx];
> +       if (frate->max_rate == frate->min_rate) {
> +               f->type = V4L2_FRMIVAL_TYPE_DISCRETE;
> +               f->discrete.numerator = 1;
> +               f->discrete.denominator = frate->max_rate;
> +       } else {
> +               if (idx && !fake_non_discrete)
> +                       return -EINVAL;
> +               /* If A > B, then 1/A < 1/B, so max denominator = min_rate
> +                * and vise versa
> +                */
> +               f->stepwise.min.numerator = 1;
> +               f->stepwise.min.denominator = frate->max_rate;
> +               f->stepwise.max.numerator = 1;
> +               f->stepwise.max.denominator = frate->min_rate;
> +               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;
> +}
> +
> +static int virtio_video_enum_fmt_vid_out(struct file *file, void *fh,
> +                                        struct v4l2_fmtdesc *f)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +
> +       if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +               return -EINVAL;
> +
> +       if (f->index >= vvd->num_output_formats)
> +               return -EINVAL;
> +
> +       f->pixelformat = vvd->output_fmts[f->index]->fourcc_format;
> +
> +       return 0;
> +}
> +
> +static void fill_v4l2_format_from_info(struct video_format_info *info,
> +                                      struct v4l2_pix_format_mplane *pix_mp)
> +{
> +       int i;
> +
> +       pix_mp->width = info->frame_width;
> +       pix_mp->height = info->frame_height;
> +       pix_mp->field = V4L2_FIELD_NONE;
> +       pix_mp->colorspace = V4L2_COLORSPACE_REC709;
> +       pix_mp->xfer_func = 0;
> +       pix_mp->ycbcr_enc = 0;
> +       pix_mp->quantization = 0;
> +       memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved));
> +       memset(pix_mp->plane_fmt[0].reserved, 0,
> +              sizeof(pix_mp->plane_fmt[0].reserved));
> +
> +       pix_mp->num_planes = info->num_planes;
> +       pix_mp->pixelformat = info->fourcc_format;
> +
> +       for (i = 0; i < info->num_planes; i++) {
> +               pix_mp->plane_fmt[i].bytesperline =
> +                                        info->plane_format[i].stride;
> +               pix_mp->plane_fmt[i].sizeimage =
> +                                        info->plane_format[i].plane_size;
> +       }
> +}
> +
> +static int virtio_video_g_fmt(struct virtio_video_stream *stream,
> +                             struct v4l2_format *f)
> +{
> +       struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +       struct video_format_info *info;
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(f->type))
> +               info = &stream->out_info;
> +       else
> +               info = &stream->in_info;
> +
> +       fill_v4l2_format_from_info(info, pix_mp);
> +       return 0;
> +}
> +
> +static int virtio_video_g_fmt_vid_out(struct file *file, void *fh,
> +                                     struct v4l2_format *f)
> +{
> +       return virtio_video_g_fmt(file2stream(file), f);
> +}
> +
> +static int virtio_video_g_fmt_vid_cap(struct file *file, void *fh,
> +                                     struct v4l2_format *f)
> +{
> +       return virtio_video_g_fmt(file2stream(file), f);
> +}
> +
> +static inline bool within_range(uint32_t min, uint32_t val, uint32_t max)
> +{
> +       return ((val - min) <= (max - min));
> +}
> +
> +static inline bool needs_alignment(uint32_t val, uint32_t a)
> +{
> +       if (a == 0 || IS_ALIGNED(val, a))
> +               return false;
> +
> +       return true;
> +}
> +
> +static 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_pix_format *fmt = NULL;
> +       struct video_frame_size *frm_sz = NULL;
> +       bool found = false;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(f->type))
> +               fmt = find_pix_format(vvd->output_fmts, pix_mp->pixelformat,
> +                                        vvd->num_output_formats);
> +       else
> +               fmt = find_pix_format(vvd->capture_fmts, pix_mp->pixelformat,
> +                                        vvd->num_capture_formats);
> +
> +       if (!fmt) {
> +               if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> +                       fill_v4l2_format_from_info(&stream->out_info, pix_mp);
> +               else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +                       fill_v4l2_format_from_info(&stream->in_info, pix_mp);
> +               else
> +                       return -EINVAL;
> +               return 0;
> +       }
> +
> +       for (i = 0; i < fmt->num_sizes && !found; i++) {
> +               frm_sz = &fmt->frame_sizes[i];
> +               if (!within_range(frm_sz->min_width, pix_mp->width,
> +                                frm_sz->max_width))
> +                       continue;
> +
> +               if (!within_range(frm_sz->min_height, pix_mp->height,
> +                                 frm_sz->max_height))
> +                       continue;
> +
> +               idx = i;
> +               /*
> +                * Try to find a more suitable frame size. Go with the current
> +                * one otherwise.
> +                */
> +               if (needs_alignment(pix_mp->width, frm_sz->step_width))
> +                       continue;
> +
> +               if (needs_alignment(pix_mp->height, frm_sz->step_height))
> +                       continue;
> +
> +               found = true;
> +       }
> +
> +       if (!found) {
> +               frm_sz = &fmt->frame_sizes[idx];
> +               pix_mp->width = clamp(pix_mp->width, frm_sz->min_width,
> +                                     frm_sz->max_width);
> +               if (frm_sz->step_width != 0)
> +                       pix_mp->width = ALIGN(pix_mp->width,
> +                                             frm_sz->step_width);
> +
> +               pix_mp->height = clamp(pix_mp->height, frm_sz->min_height,
> +                                     frm_sz->max_height);
> +               if (frm_sz->step_height != 0)
> +                       pix_mp->height = ALIGN(pix_mp->height,
> +                                      frm_sz->step_height);
> +       }
> +
> +       return 0;
> +}
> +
> +static int virtio_video_s_fmt(struct virtio_video_stream *stream,
> +                             struct v4l2_format *f)
> +{
> +       int i, ret;
> +       struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +       struct video_format_info info;
> +       struct video_format_info *p_info;
> +       enum video_pin_type pin = VIDEO_PIN_TYPE_INPUT;
> +
> +       ret = virtio_video_try_fmt(stream, f);
> +       if (ret)
> +               return ret;
> +
> +       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;
> +       }
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(f->type))
> +               pin = VIDEO_PIN_TYPE_OUTPUT;
> +
> +       virtio_video_req_set_params(vv, vvd->id, &info, pin,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +
> +       virtio_video_req_get_params(vv, vvd->id, VIDEO_PIN_TYPE_INPUT,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +
> +       virtio_video_req_get_params(vv, vvd->id, VIDEO_PIN_TYPE_OUTPUT,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +
> +       if (V4L2_TYPE_IS_OUTPUT(f->type))
> +               p_info = &stream->in_info;
> +       else
> +               p_info = &stream->out_info;
> +
> +       fill_v4l2_format_from_info(p_info, pix_mp);
> +
> +       if (V4L2_TYPE_IS_OUTPUT(f->type)) {
> +               if (stream->state == STREAM_STATE_IDLE)
> +                       stream->state = STREAM_STATE_INIT;
> +       }
> +
> +       return 0;
> +}
> +
> +static int virtio_video_s_fmt_vid_cap(struct file *file, void *fh,
> +                                     struct v4l2_format *f)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +
> +       return virtio_video_s_fmt(stream, f);
> +}
> +
> +static int virtio_video_s_fmt_vid_out(struct file *file, void *fh,
> +                                     struct v4l2_format *f)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +
> +       return virtio_video_s_fmt(stream, f);
> +}
> +
> +static 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 -EINVAL;
> +       }
> +}
> +
> +static const struct v4l2_ioctl_ops virtio_video_device_dec_ioctl_ops = {
> +       .vidioc_querycap        = virtio_video_querycap,
> +
> +       .vidioc_enum_fmt_vid_cap = virtio_video_enum_fmt_vid_cap,
> +       .vidioc_g_fmt_vid_cap   = virtio_video_g_fmt_vid_cap,
> +       .vidioc_s_fmt_vid_cap   = virtio_video_s_fmt_vid_cap,
> +
> +       .vidioc_g_fmt_vid_cap_mplane    = virtio_video_g_fmt_vid_cap,
> +       .vidioc_s_fmt_vid_cap_mplane    = virtio_video_s_fmt_vid_cap,
> +
> +       .vidioc_enum_fmt_vid_out = virtio_video_enum_fmt_vid_out,
> +       .vidioc_g_fmt_vid_out   = virtio_video_g_fmt_vid_out,
> +       .vidioc_s_fmt_vid_out   = virtio_video_s_fmt_vid_out,
> +
> +       .vidioc_g_fmt_vid_out_mplane    = virtio_video_g_fmt_vid_out,
> +       .vidioc_s_fmt_vid_out_mplane    = virtio_video_s_fmt_vid_out,
> +
> +       .vidioc_try_decoder_cmd = virtio_video_try_decoder_cmd,
> +       .vidioc_decoder_cmd     = virtio_video_decoder_cmd,
> +       .vidioc_enum_frameintervals = virtio_video_enum_framemintervals,
> +       .vidioc_enum_framesizes = virtio_video_enum_framesizes,
> +
> +       .vidioc_reqbufs         = v4l2_m2m_ioctl_reqbufs,
> +       .vidioc_querybuf        = v4l2_m2m_ioctl_querybuf,
> +       .vidioc_qbuf            = v4l2_m2m_ioctl_qbuf,
> +       .vidioc_dqbuf           = v4l2_m2m_ioctl_dqbuf,
> +       .vidioc_prepare_buf     = v4l2_m2m_ioctl_prepare_buf,
> +       .vidioc_create_bufs     = v4l2_m2m_ioctl_create_bufs,
> +       .vidioc_expbuf          = v4l2_m2m_ioctl_expbuf,
> +
> +       .vidioc_streamon        = v4l2_m2m_ioctl_streamon,
> +       .vidioc_streamoff       = v4l2_m2m_ioctl_streamoff,
> +
> +       .vidioc_subscribe_event = virtio_video_subscribe_event,
> +       .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +int virtio_video_dec_init(struct virtio_video_device *vvd)
> +{
> +       struct video_device *vd = NULL;
> +
> +       if (!vvd)
> +               return -EINVAL;
> +
> +       vd = &vvd->video_dev;
> +       vd->ioctl_ops = &virtio_video_device_dec_ioctl_ops;
> +       return 0;
> +}
> diff --git a/drivers/media/virtio/virtio_video_dec.h b/drivers/media/virtio/virtio_video_dec.h
> new file mode 100644
> index 000000000000..5f67478ef9c5
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video_dec.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/* Decoder header for virtio video driver.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef _VIRTIO_VIDEO_DEC_H
> +#define _VIRTIO_VIDEO_DEC_H
> +
> +#include "virtio_video.h"
> +
> +int virtio_video_dec_init(struct virtio_video_device *vvd);
> +int virtio_video_init_dec_ctrls(struct virtio_video_stream *stream);
> +int virtio_video_init_dec_queues(void *priv, struct vb2_queue *src_vq,
> +                                struct vb2_queue *dst_vq);
> +
> +#endif /* _VIRTIO_VIDEO_DEC_H */
> diff --git a/drivers/media/virtio/virtio_video_device.c b/drivers/media/virtio/virtio_video_device.c
> new file mode 100644
> index 000000000000..d105a2fb1594
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video_device.c
> @@ -0,0 +1,747 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/* Driver for virtio video device.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-dma-sg.h>
> +
> +#include "virtio_video.h"
> +#include "virtio_video_dec.h"
> +#include "virtio_video_enc.h"
> +
> +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_mark_drain_complete(struct virtio_video_stream *stream,
> +                                     struct vb2_v4l2_buffer *v4l2_vb)
> +{
> +       struct vb2_buffer *vb2_buf;
> +
> +       v4l2_vb->flags |= V4L2_BUF_FLAG_LAST;
> +
> +       vb2_buf = &v4l2_vb->vb2_buf;
> +       vb2_buf->planes[0].bytesused = 0;
> +
> +       v4l2_m2m_buf_done(v4l2_vb, VB2_BUF_STATE_DONE);
> +       stream->state = STREAM_STATE_STOPPED;
> +}
> +
> +void virtio_video_buf_done(struct virtio_video_buffer *virtio_vb,
> +                          uint32_t flags, uint64_t timestamp, uint32_t size)
> +{
> +       int i, ret;
> +       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 virtio_video *vv = vvd->vv;
> +       struct video_format_info *p_info;
> +
> +       virtio_vb->queued = false;
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(vb2_queue->type) &&
> +           stream->check_drain_sequence_pending) {
> +               virtio_video_mark_drain_complete(stream, v4l2_vb);
> +               stream->check_drain_sequence_pending = false;
> +               return;
> +       }
> +
> +       if (flags & VIRTIO_VIDEO_BUFFER_F_ERR)
> +               done_state = VB2_BUF_STATE_ERROR;
> +
> +       if (flags & VIRTIO_VIDEO_BUFFER_IFRAME)
> +               v4l2_vb->flags |= V4L2_BUF_FLAG_KEYFRAME;
> +
> +       if (flags & VIRTIO_VIDEO_BUFFER_BFRAME)
> +               v4l2_vb->flags |= V4L2_BUF_FLAG_BFRAME;
> +
> +       if (flags & VIRTIO_VIDEO_BUFFER_PFRAME)
> +               v4l2_vb->flags |= V4L2_BUF_FLAG_PFRAME;
> +
> +       if (flags & VIRTIO_VIDEO_BUFFER_F_EOS) {
> +               v4l2_vb->flags |= V4L2_BUF_FLAG_LAST;
> +               ret = virtio_video_req_stream_stop(vv, vvd->id, stream);
> +               if (ret)
> +                       v4l2_err(&vv->v4l2_dev, "failed to stop stream\n");
> +               else
> +                       stream->state = STREAM_STATE_STOPPED;
> +               virtio_video_queue_eos_event(stream);
> +       }
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(vb2_queue->type)) {
> +               if (vvd->type == VIRTIO_VIDEO_FUNC_ENCODER) {
> +                       vb->planes[0].bytesused = size;
> +               } else if (vvd->type == VIRTIO_VIDEO_FUNC_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;
> +               }
> +
> +               vb->timestamp = timestamp;
> +       }
> +
> +       v4l2_m2m_buf_done(v4l2_vb, done_state);
> +}
> +
> +
> +static void virtio_video_worker(struct work_struct *work)
> +{
> +       unsigned int i;
> +       int ret;
> +       struct vb2_buffer *vb2_buf;
> +       struct vb2_v4l2_buffer *src_vb, *dst_vb;
> +       struct virtio_video_buffer *virtio_vb;
> +       struct virtio_video_stream *stream = work2stream(work);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct vb2_queue *src_vq =
> +               v4l2_m2m_get_vq(stream->fh.m2m_ctx,
> +                               V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
> +       struct vb2_queue *dst_vq =
> +               v4l2_m2m_get_vq(stream->fh.m2m_ctx,
> +                               V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
> +       struct virtio_video *vv = vvd->vv;
> +       uint32_t data_size[VB2_MAX_PLANES] = {0};
> +
> +
> +       mutex_lock(dst_vq->lock);
> +       for (;;) {
> +               dst_vb = v4l2_m2m_next_dst_buf(stream->fh.m2m_ctx);
> +               if (dst_vb == NULL)
> +                       break;
> +
> +               vb2_buf = &dst_vb->vb2_buf;
> +               virtio_vb = to_virtio_vb(vb2_buf);
> +
> +               for (i = 0; i < vb2_buf->num_planes; ++i)
> +                       data_size[i] = vb2_buf->planes[i].bytesused;
> +
> +               ret = virtio_video_req_resource_queue(vv, vvd->id,
> +                                                     stream->stream_id,
> +                                                     virtio_vb, data_size,
> +                                                     vb2_buf->num_planes,
> +                                                     false);
> +               if (ret) {
> +                       v4l2_info(&vv->v4l2_dev,
> +                                 "failed to queue a dst buffer\n");
> +                       v4l2_m2m_job_finish(vvd->m2m_dev, stream->fh.m2m_ctx);
> +                       mutex_unlock(dst_vq->lock);
> +                       return;
> +               }
> +
> +               virtio_vb->queued = true;
> +               stream->dst_cleared = false;
> +               dst_vb = v4l2_m2m_dst_buf_remove(stream->fh.m2m_ctx);
> +       }
> +       mutex_unlock(dst_vq->lock);
> +
> +       mutex_lock(src_vq->lock);
> +       for (;;) {
> +               if (stream->state == STREAM_STATE_DRAIN)
> +                       break;
> +
> +               src_vb = v4l2_m2m_next_src_buf(stream->fh.m2m_ctx);
> +               if (src_vb == NULL)
> +                       break;
> +
> +               vb2_buf = &src_vb->vb2_buf;
> +               virtio_vb = to_virtio_vb(vb2_buf);
> +
> +               for (i = 0; i < vb2_buf->num_planes; ++i)
> +                       data_size[i] = vb2_buf->planes[i].bytesused;
> +
> +               ret = virtio_video_req_resource_queue(vv, vvd->id,
> +                                                     stream->stream_id,
> +                                                     virtio_vb,
> +                                                         data_size,
> +                                                         vb2_buf->num_planes,
> +                                                     true);
> +               if (ret) {
> +                       v4l2_info(&vv->v4l2_dev,
> +                                 "failed to queue an src buffer\n");
> +                       v4l2_m2m_job_finish(vvd->m2m_dev, stream->fh.m2m_ctx);
> +                       mutex_unlock(src_vq->lock);
> +                       return;
> +               }
> +
> +               virtio_vb->queued = true;
> +               stream->src_cleared = false;
> +               src_vb = v4l2_m2m_src_buf_remove(stream->fh.m2m_ctx);
> +       }
> +       mutex_unlock(src_vq->lock);
> +
> +       v4l2_m2m_job_finish(vvd->m2m_dev, stream->fh.m2m_ctx);
> +}
> +
> +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_device *video_dev = video_devdata(file);
> +       struct virtio_video_device *vvd = video_drvdata(file);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       stream = kzalloc(sizeof(*stream), GFP_KERNEL);
> +       if (!stream)
> +               return -ENOMEM;
> +
> +       get_task_comm(name, current);
> +       virtio_video_stream_id_get(vv, stream, &stream_id);
> +       ret = virtio_video_req_stream_create(vv, vvd->id, stream_id, name);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to create stream\n");
> +               goto err_stream_create;
> +       }
> +
> +       stream->video_dev = video_dev;
> +       stream->stream_id = stream_id;
> +       stream->state = STREAM_STATE_IDLE;
> +       mutex_init(&stream->vq_mutex);
> +       INIT_WORK(&stream->work, virtio_video_worker);
> +       v4l2_fh_init(&stream->fh, video_dev);
> +       stream->fh.ctrl_handler = &stream->ctrl_handler;
> +
> +       if (vvd->type == VIRTIO_VIDEO_FUNC_DECODER) {
> +               stream->fh.m2m_ctx =
> +                       v4l2_m2m_ctx_init(vvd->m2m_dev, stream,
> +                                         &virtio_video_init_dec_queues);
> +       } else if (vvd->type == VIRTIO_VIDEO_FUNC_ENCODER) {
> +               stream->fh.m2m_ctx =
> +                       v4l2_m2m_ctx_init(vvd->m2m_dev, stream,
> +                                         &virtio_video_init_enc_queues);
> +       } else {
> +               v4l2_err(&vv->v4l2_dev, "unsupported device type\n");
> +               goto err_stream_create;
> +       }
> +
> +       v4l2_m2m_set_src_buffered(stream->fh.m2m_ctx, true);
> +       v4l2_m2m_set_dst_buffered(stream->fh.m2m_ctx, true);
> +       file->private_data = &stream->fh;
> +       v4l2_fh_add(&stream->fh);
> +
> +       if (vvd->type == VIRTIO_VIDEO_FUNC_DECODER)
> +               ret = virtio_video_init_dec_ctrls(stream);
> +       else if (vvd->type == VIRTIO_VIDEO_FUNC_ENCODER)
> +               ret = virtio_video_init_enc_ctrls(stream);
> +
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to init controls\n");
> +               goto err_init_ctrls;
> +       }
> +
> +       ret = virtio_video_req_get_params(vv, vvd->id, VIDEO_PIN_TYPE_INPUT,
> +                                         VIDEO_PARAMS_SCOPE_STREAM, stream);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to get stream in params\n");
> +               goto err_init_ctrls;
> +       }
> +
> +       ret = virtio_video_req_get_params(vv, vvd->id, VIDEO_PIN_TYPE_OUTPUT,
> +                                         VIDEO_PARAMS_SCOPE_STREAM, stream);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to get stream out params\n");
> +               goto err_init_ctrls;
> +       }
> +
> +       return 0;
> +
> +err_init_ctrls:
> +       v4l2_fh_del(&stream->fh);
> +       v4l2_fh_exit(&stream->fh);
> +       mutex_lock(video_dev->lock);
> +       v4l2_m2m_ctx_release(stream->fh.m2m_ctx);
> +       mutex_unlock(video_dev->lock);
> +err_stream_create:
> +       virtio_video_stream_id_put(vv, stream_id);
> +       kfree(stream);
> +
> +       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);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       v4l2_fh_del(&stream->fh);
> +       v4l2_fh_exit(&stream->fh);
> +       mutex_lock(video_dev->lock);
> +       v4l2_m2m_ctx_release(stream->fh.m2m_ctx);
> +       mutex_unlock(video_dev->lock);
> +
> +       virtio_video_req_stream_destroy(vv, vvd->id, stream->stream_id);
> +       virtio_video_stream_id_put(vv, stream->stream_id);
> +
> +       kfree(stream);
> +
> +       return 0;
> +}
> +
> +static const struct v4l2_file_operations virtio_video_device_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 void virtio_video_device_run(void *priv)
> +{
> +       struct virtio_video_stream *stream = priv;
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +
> +       queue_work(vvd->workqueue, &stream->work);
> +}
> +
> +static int virtio_video_device_job_ready(void *priv)
> +{
> +       struct virtio_video_stream *stream = priv;
> +
> +       if (stream->state == STREAM_STATE_STOPPED)
> +               return 0;
> +
> +       if (v4l2_m2m_num_src_bufs_ready(stream->fh.m2m_ctx) > 0 ||
> +           v4l2_m2m_num_dst_bufs_ready(stream->fh.m2m_ctx) > 0)
> +               return 1;
> +
> +       return 0;
> +}
> +
> +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_ready      = virtio_video_device_job_ready,
> +       .job_abort      = virtio_video_device_job_abort,
> +};
> +
> +uint32_t virtio_video_control_to_v4l2(uint32_t control_type)
> +{
> +       switch (control_type) {
> +       case VIRTIO_VIDEO_CONTROL_BITRATE:
> +               return V4L2_CID_MPEG_VIDEO_BITRATE;
> +       case VIRTIO_VIDEO_CONTROL_PROFILE:
> +               return V4L2_CID_MPEG_VIDEO_H264_PROFILE;
> +       case VIRTIO_VIDEO_CONTROL_LEVEL:
> +               return V4L2_CID_MPEG_VIDEO_H264_LEVEL;
> +       default:
> +               return 0;
> +       }
> +}
> +
> +uint32_t virtio_video_profile_to_v4l2(uint32_t profile)
> +{
> +       switch (profile) {
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_BASELINE:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_MAIN:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_MAIN;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_EXTENDED:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_10:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_422:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_10_INTRA:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10_INTRA;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_444_INTRA:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_INTRA;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_CAVLC_444_INTRA:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_CAVLC_444_INTRA;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH_INTRA:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH_INTRA;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH;
> +       case VIRTIO_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH:
> +               return V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH;
> +       default:
> +               return 0;
> +       }
> +}
> +
> +uint32_t virtio_video_level_to_v4l2(uint32_t level)
> +{
> +       switch (level) {
> +       case VIRTIO_MPEG_VIDEO_H264_LEVEL_1_0:
> +               return V4L2_MPEG_VIDEO_H264_LEVEL_1_0;
> +       case VIRTIO_MPEG_VIDEO_H264_LEVEL_2_0:
> +               return V4L2_MPEG_VIDEO_H264_LEVEL_2_0;
> +       case VIRTIO_MPEG_VIDEO_H264_LEVEL_4_0:
> +               return V4L2_MPEG_VIDEO_H264_LEVEL_4_0;
> +       case VIRTIO_MPEG_VIDEO_H264_LEVEL_4_1:
> +               return V4L2_MPEG_VIDEO_H264_LEVEL_4_1;
> +       case VIRTIO_MPEG_VIDEO_H264_LEVEL_4_2:
> +               return V4L2_MPEG_VIDEO_H264_LEVEL_4_2;
> +       case VIRTIO_MPEG_VIDEO_H264_LEVEL_5_0:
> +               return V4L2_MPEG_VIDEO_H264_LEVEL_5_0;
> +       case VIRTIO_MPEG_VIDEO_H264_LEVEL_5_1:
> +               return V4L2_MPEG_VIDEO_H264_LEVEL_5_1;
> +       default:
> +               return 0;
> +       }
> +}
> +
> +uint32_t virtio_video_v4l2_control_to_virtio(uint32_t v4l2_control)
> +{
> +       switch (v4l2_control) {
> +       case V4L2_CID_MPEG_VIDEO_BITRATE:
> +               return VIRTIO_VIDEO_CONTROL_BITRATE;
> +       case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
> +               return VIRTIO_VIDEO_CONTROL_PROFILE;
> +       case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
> +               return VIRTIO_VIDEO_CONTROL_LEVEL;
> +       default:
> +               return 0;
> +       }
> +}
> +
> +uint32_t virtio_video_v4l2_profile_to_virtio(uint32_t v4l2_profile)
> +{
> +       switch (v4l2_profile) {
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_BASELINE;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_MAIN;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_EXTENDED;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_10;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_422;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10_INTRA:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_10_INTRA;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_INTRA:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_444_INTRA;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_CAVLC_444_INTRA:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_CAVLC_444_INTRA;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH_INTRA:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH_INTRA;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH;
> +       case V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH;
> +       default:
> +               return VIRTIO_MPEG_VIDEO_H264_PROFILE_UNDEFINED;
> +       }
> +}
> +
> +uint32_t virtio_video_v4l2_level_to_virtio(uint32_t v4l2_level)
> +{
> +       switch (v4l2_level) {
> +       case V4L2_MPEG_VIDEO_H264_LEVEL_1_0:
> +               return VIRTIO_MPEG_VIDEO_H264_LEVEL_1_0;
> +       case V4L2_MPEG_VIDEO_H264_LEVEL_2_0:
> +               return VIRTIO_MPEG_VIDEO_H264_LEVEL_2_0;
> +       case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
> +               return VIRTIO_MPEG_VIDEO_H264_LEVEL_4_0;
> +       case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
> +               return VIRTIO_MPEG_VIDEO_H264_LEVEL_4_1;
> +       case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
> +               return VIRTIO_MPEG_VIDEO_H264_LEVEL_4_2;
> +       case V4L2_MPEG_VIDEO_H264_LEVEL_5_0:
> +               return VIRTIO_MPEG_VIDEO_H264_LEVEL_5_0;
> +       case V4L2_MPEG_VIDEO_H264_LEVEL_5_1:
> +               return VIRTIO_MPEG_VIDEO_H264_LEVEL_5_1;
> +       default:
> +               return VIRTIO_MPEG_VIDEO_H264_LEVEL_UNDEFINED;
> +       }
> +}
> +
> +uint32_t virtio_video_format_to_v4l2(uint32_t pixel_format)
> +{
> +
> +       switch (pixel_format) {
> +       case VIRTIO_VIDEO_PIX_FMT_H264:
> +               return V4L2_PIX_FMT_H264;
> +       case VIRTIO_VIDEO_PIX_FMT_H265:
> +               return V4L2_PIX_FMT_HEVC;
> +       case VIRTIO_VIDEO_PIX_FMT_MPEG4:
> +               return V4L2_PIX_FMT_MPEG4;
> +       case VIRTIO_VIDEO_PIX_FMT_MPEG2:
> +               return V4L2_PIX_FMT_MPEG2;
> +       case VIRTIO_VIDEO_PIX_FMT_NV12:
> +               return V4L2_PIX_FMT_NV12;
> +       case VIRTIO_VIDEO_PIX_FMT_I420:
> +               return V4L2_PIX_FMT_YUV420;
> +       default:
> +               return 0;
> +       }
> +}
> +
> +uint32_t virtio_video_v4l2_fourcc_to_virtio(uint32_t fourcc)
> +{
> +       switch (fourcc) {
> +       case V4L2_PIX_FMT_H264:
> +               return VIRTIO_VIDEO_PIX_FMT_H264;
> +       case V4L2_PIX_FMT_HEVC:
> +               return VIRTIO_VIDEO_PIX_FMT_H265;
> +       case V4L2_PIX_FMT_MPEG4:
> +               return VIRTIO_VIDEO_PIX_FMT_MPEG4;
> +       case V4L2_PIX_FMT_MPEG2:
> +               return VIRTIO_VIDEO_PIX_FMT_MPEG2;
> +       case V4L2_PIX_FMT_NV12:
> +               return VIRTIO_VIDEO_PIX_FMT_NV12;
> +       case V4L2_PIX_FMT_YUV420:
> +               return VIRTIO_VIDEO_PIX_FMT_I420;
> +       default:
> +               return VIRTIO_VIDEO_PIX_FMT_UNKNOWN;
> +       }
> +}
> +
> +static int virtio_video_device_init(struct virtio_video_device *vvd)
> +{
> +       int ret = 0;
> +       const char *device_name = NULL;
> +       struct video_device *vd = NULL;
> +       struct virtio_video *vv = NULL;
> +
> +       if (!vvd)
> +               return -EINVAL;
> +
> +       vd = &vvd->video_dev;
> +       vv = vvd->vv;
> +
> +       switch (vvd->type) {
> +       case VIRTIO_VIDEO_FUNC_ENCODER:
> +               device_name = "stateful-encoder";
> +               ret = virtio_video_enc_init(vvd);
> +               break;
> +       case VIRTIO_VIDEO_FUNC_DECODER:
> +               device_name = "stateful-decoder";
> +               ret = virtio_video_dec_init(vvd);
> +               break;
> +       case VIRTIO_VIDEO_FUNC_PROCESSOR:
> +       case VIRTIO_VIDEO_FUNC_CAPTURE:
> +       case VIRTIO_VIDEO_FUNC_OUTPUT:
> +       default:
> +               ret = -EINVAL;
> +               break;
> +       }
> +
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to init device type");
> +               return ret;
> +       }
> +
> +       ret = video_register_device(vd, VFL_TYPE_GRABBER, 0);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to register video device\n");
> +               return ret;
> +       }
> +
> +       vvd->workqueue = alloc_ordered_workqueue(vd->name,
> +                                                WQ_MEM_RECLAIM | WQ_FREEZABLE);
> +       if (!vvd->workqueue) {
> +               v4l2_err(&vv->v4l2_dev, "failed to create a workqueue");
> +               video_unregister_device(vd);
> +               return -ENOMEM;
> +       }
> +
> +       list_add(&vvd->devices_list_entry, &vv->devices_list);
> +       v4l2_info(&vv->v4l2_dev, "Device '%s' registered as /dev/video%d\n",
> +                 device_name, vd->num);
> +
> +       return 0;
> +}
> +
> +static void virtio_video_device_deinit(struct virtio_video_device *vvd)
> +{
> +       if (!vvd)
> +               return;
> +
> +       list_del(&vvd->devices_list_entry);
> +       flush_workqueue(vvd->workqueue);
> +       destroy_workqueue(vvd->workqueue);
> +       video_unregister_device(&vvd->video_dev);
> +}
> +
> +static struct virtio_video_device *
> +virtio_video_device_create(struct virtio_video *vv)
> +{
> +       struct device *dev = NULL;
> +       struct video_device *vd = NULL;
> +       struct v4l2_m2m_dev *m2m_dev = NULL;
> +       struct virtio_video_device *vvd = NULL;
> +
> +       if (!vv)
> +               return ERR_PTR(-EINVAL);
> +
> +       dev = &vv->vdev->dev;
> +
> +       vvd = devm_kzalloc(dev, sizeof(*vvd), GFP_KERNEL);
> +       if (!vvd)
> +               return ERR_PTR(-ENOMEM);
> +
> +       m2m_dev = v4l2_m2m_init(&virtio_video_device_m2m_ops);
> +       if (IS_ERR(m2m_dev)) {
> +               v4l2_err(&vv->v4l2_dev, "failed to init m2m device\n");
> +               goto err;
> +       }
> +
> +       vvd->vv = vv;
> +       vvd->m2m_dev = m2m_dev;
> +       mutex_init(&vvd->video_dev_mutex);
> +       vd = &vvd->video_dev;
> +       vd->lock = &vvd->video_dev_mutex;
> +       vd->v4l2_dev = &vv->v4l2_dev;
> +       vd->vfl_dir = VFL_DIR_M2M;
> +       vd->ioctl_ops = NULL;
> +       vd->fops = &virtio_video_device_fops;
> +       vd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
> +       vd->release = video_device_release_empty;
> +
> +       memset(vd->name, 0, sizeof(vd->name));
> +
> +       video_set_drvdata(vd, vvd);
> +
> +       INIT_LIST_HEAD(&vvd->ctrl_caps_list);
> +       INIT_LIST_HEAD(&vvd->fmt_caps_list);
> +
> +       vvd->num_output_formats = 0;
> +       vvd->num_capture_formats = 0;
> +
> +       return vvd;
> +
> +err:
> +       devm_kfree(dev, vvd);
> +
> +       return ERR_CAST(m2m_dev);
> +}
> +
> +void virtio_video_device_destroy(struct virtio_video_device *vvd)
> +{
> +       if (!vvd)
> +               return;
> +
> +       v4l2_m2m_release(vvd->m2m_dev);
> +       devm_kfree(&vvd->vv->vdev->dev, vvd);
> +}
> +
> +int virtio_video_devices_init(struct virtio_video *vv, void *funcs_buf)
> +{
> +       int ret = 0;
> +       int fun_idx = 0;
> +       size_t offset = 0;
> +
> +       if (!vv || !funcs_buf)
> +               return -EINVAL;
> +
> +       for (fun_idx = 0; fun_idx < vv->num_devices; fun_idx++) {
> +               struct virtio_video_device *vvd = NULL;
> +               size_t func_size = 0;
> +
> +               vvd = virtio_video_device_create(vv);
> +               if (IS_ERR(vvd)) {
> +                       v4l2_err(&vv->v4l2_dev,
> +                                "failed to create virtio video device\n");
> +                       ret = PTR_ERR(vvd);
> +                       goto failed;
> +               }
> +
> +               func_size = virtio_video_parse_virtio_function(funcs_buf +
> +                                                              offset, vvd);
> +               if (func_size == 0) {
> +                       v4l2_err(&vv->v4l2_dev, "failed to parse a function\n");
> +                       virtio_video_device_destroy(vvd);
> +                       ret = -EINVAL;
> +                       goto failed;
> +               }
> +               offset += func_size;
> +
> +               ret = virtio_video_device_init(vvd);
> +               if (ret != 0) {
> +                       v4l2_err(&vv->v4l2_dev,
> +                                "failed to init virtio video device\n");
> +                       virtio_video_clean_virtio_function(vvd);
> +                       virtio_video_device_destroy(vvd);
> +                       goto failed;
> +               }
> +       }
> +
> +       return 0;
> +
> +failed:
> +       virtio_video_devices_deinit(vv);
> +
> +       return ret;
> +}
> +
> +void virtio_video_devices_deinit(struct virtio_video *vv)
> +{
> +       struct virtio_video_device *vvd = NULL, *tmp = NULL;
> +
> +       list_for_each_entry_safe(vvd, tmp, &vv->devices_list,
> +                                devices_list_entry) {
> +               virtio_video_device_deinit(vvd);
> +               virtio_video_clean_virtio_function(vvd);
> +               virtio_video_device_destroy(vvd);
> +       }
> +}
> diff --git a/drivers/media/virtio/virtio_video_driver.c b/drivers/media/virtio/virtio_video_driver.c
> new file mode 100644
> index 000000000000..78e02faba7aa
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video_driver.c
> @@ -0,0 +1,278 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Driver for virtio video device.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/version.h>
> +#include <linux/dma-mapping.h>
> +
> +#include "virtio_video.h"
> +
> +static unsigned int debug;
> +module_param(debug, uint, 0644);
> +
> +static void virtio_video_init_vq(struct virtio_video_queue *vvq,
> +                                void (*work_func)(struct work_struct *work))
> +{
> +       spin_lock_init(&vvq->qlock);
> +       init_waitqueue_head(&vvq->ack_queue);
> +       INIT_WORK(&vvq->dequeue_work, work_func);
> +}
> +
> +static void *dma_phys_alloc(struct device *dev, size_t size,
> +                           dma_addr_t *dma_handle, gfp_t gfp,
> +                           unsigned long attrs)
> +{
> +       void *ret;
> +
> +       ret = (void *)__get_free_pages(gfp, get_order(size));
> +       if (ret)
> +               *dma_handle = virt_to_phys(ret) - PFN_PHYS(dev->dma_pfn_offset);
> +
> +       return ret;
> +}
> +
> +static void dma_phys_free(struct device *dev, size_t size,
> +                         void *cpu_addr, dma_addr_t dma_addr,
> +                         unsigned long attrs)
> +{
> +       free_pages((unsigned long)cpu_addr, get_order(size));
> +}
> +
> +static dma_addr_t dma_phys_map_page(struct device *dev, struct page *page,
> +                                   unsigned long offset, size_t size,
> +                                   enum dma_data_direction dir,
> +                                   unsigned long attrs)
> +{
> +       return page_to_phys(page) + offset - PFN_PHYS(dev->dma_pfn_offset);
> +}
> +
> +static int dma_phys_map_sg(struct device *dev, struct scatterlist *sgl,
> +                          int nents, enum dma_data_direction dir,
> +                          unsigned long attrs)
> +{
> +       int i;
> +       struct scatterlist *sg;
> +
> +       for_each_sg(sgl, sg, nents, i) {
> +               dma_addr_t offset = PFN_PHYS(dev->dma_pfn_offset);
> +               void *va;
> +
> +               BUG_ON(!sg_page(sg));
> +               va = sg_virt(sg);
> +               sg_dma_address(sg) = (dma_addr_t)virt_to_phys(va) - offset;
> +               sg_dma_len(sg) = sg->length;
> +       }
> +
> +       return nents;
> +}
> +
> +const struct dma_map_ops dma_phys_ops = {
> +       .alloc                  = dma_phys_alloc,
> +       .free                   = dma_phys_free,
> +       .map_page               = dma_phys_map_page,
> +       .map_sg                 = dma_phys_map_sg,
> +};
> +
> +static int virtio_video_init(struct virtio_video *vv)
> +{
> +       int ret = 0;
> +       void *resp_buf = NULL;
> +       void *funcs_buf = NULL;
> +       size_t total_resp_size = 0;
> +
> +       if (!vv)
> +               return -EINVAL;
> +
> +       total_resp_size = vv->funcs_size +
> +                         sizeof(struct virtio_video_get_functions);
> +       resp_buf = kzalloc(total_resp_size, GFP_KERNEL);
> +       if (IS_ERR(resp_buf))
> +               return -ENOMEM;
> +
> +       ret = virtio_video_req_funcs(vv, resp_buf, total_resp_size);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev,
> +                        "failed to get devices from the host\n");
> +               goto err;
> +       }
> +
> +       ret = wait_event_timeout(vv->wq, vv->got_funcs, 5 * HZ);
> +       if (ret == 0) {
> +               v4l2_err(&vv->v4l2_dev, "timed out waiting for get_funcs\n");
> +               ret = -EIO;
> +               goto err;
> +       }
> +
> +       funcs_buf = resp_buf + sizeof(struct virtio_video_ctrl_hdr);
> +       ret = virtio_video_devices_init(vv, funcs_buf);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to initialize devices\n");
> +               goto err;
> +       }
> +
> +       kfree(resp_buf);
> +       return 0;
> +
> +err:
> +       kfree(resp_buf);
> +       return ret;
> +};
> +
> +static int virtio_video_probe(struct virtio_device *vdev)
> +{
> +       int ret;
> +       struct virtio_video *vv;
> +       struct virtqueue *vqs[2];
> +       struct device *dev = &vdev->dev;
> +
> +       static const char * const names[] = { "control", "event" };
> +       static vq_callback_t *callbacks[] = {
> +               virtio_video_ctrl_ack,
> +               virtio_video_event_ack
> +       };
> +
> +       vv = devm_kzalloc(dev, sizeof(*vv), GFP_KERNEL);
> +       if (!vv)
> +               return -ENOMEM;
> +       vv->vdev = vdev;
> +       vv->debug = debug;
> +       vdev->priv = vv;
> +
> +       spin_lock_init(&vv->resource_idr_lock);
> +       idr_init(&vv->resource_idr);
> +       spin_lock_init(&vv->stream_idr_lock);
> +       idr_init(&vv->stream_idr);
> +
> +       init_waitqueue_head(&vv->wq);
> +
> +       vv->has_iommu = !virtio_has_iommu_quirk(vdev);
> +       if (!vv->has_iommu)
> +               set_dma_ops(dev, &dma_phys_ops);
> +
> +       dev_set_name(dev, DRIVER_NAME);
> +       ret = v4l2_device_register(dev, &vv->v4l2_dev);
> +       if (ret)
> +               goto err_v4l2_reg;
> +
> +       virtio_video_init_vq(&vv->ctrlq, virtio_video_dequeue_ctrl_func);
> +       virtio_video_init_vq(&vv->eventq, virtio_video_dequeue_event_func);
> +
> +       ret = virtio_find_vqs(vdev, 2, vqs, callbacks, names, NULL);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to find virt queues\n");
> +               goto err_vqs;
> +       }
> +
> +       vv->ctrlq.vq = vqs[0];
> +       vv->eventq.vq = vqs[1];
> +
> +       ret = virtio_video_alloc_vbufs(vv);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to alloc vbufs\n");
> +               goto err_vbufs;
> +       }
> +
> +       virtio_cread(vdev, struct virtio_video_config, total_functions_size,
> +                    &vv->funcs_size);
> +       if (!vv->funcs_size) {
> +               v4l2_err(&vv->v4l2_dev, "virtio_functions_size is zero\n");
> +               ret = -EINVAL;
> +               goto err_config;
> +       }
> +
> +       virtio_cread(vdev, struct virtio_video_config, num_functions,
> +                    &vv->num_devices);
> +       if (!vv->num_devices) {
> +               v4l2_err(&vv->v4l2_dev, "num_devices is zero\n");
> +               ret = -EINVAL;
> +               goto err_config;
> +       }
> +
> +       ret = virtio_video_alloc_events(vv, vv->eventq.vq->num_free);
> +       if (ret)
> +               goto err_events;
> +
> +       virtio_device_ready(vdev);
> +       vv->vq_ready = true;
> +       vv->got_funcs = false;
> +
> +       INIT_LIST_HEAD(&vv->devices_list);
> +
> +       ret = virtio_video_init(vv);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev,
> +                        "failed to init virtio video\n");
> +               goto err_init;
> +       }
> +
> +       return 0;
> +
> +err_init:
> +err_events:
> +err_config:
> +       virtio_video_free_vbufs(vv);
> +err_vbufs:
> +       vdev->config->del_vqs(vdev);
> +err_vqs:
> +       v4l2_device_unregister(&vv->v4l2_dev);
> +err_v4l2_reg:
> +       devm_kfree(&vdev->dev, vv);
> +
> +       return ret;
> +}
> +
> +static void virtio_video_remove(struct virtio_device *vdev)
> +{
> +       struct virtio_video *vv = vdev->priv;
> +
> +       virtio_video_devices_deinit(vv);
> +       virtio_video_free_vbufs(vv);
> +       vdev->config->del_vqs(vdev);
> +       v4l2_device_unregister(&vv->v4l2_dev);
> +       devm_kfree(&vdev->dev, vv);
> +}
> +
> +static struct virtio_device_id id_table[] = {
> +       { VIRTIO_ID_VIDEO, VIRTIO_DEV_ANY_ID },
> +       { 0 },
> +};
> +
> +static unsigned int features[] = {
> +       /* none */
> +};
> +
> +static struct virtio_driver virtio_video_driver = {
> +       .feature_table = features,
> +       .feature_table_size = ARRAY_SIZE(features),
> +       .driver.name = DRIVER_NAME,
> +       .driver.owner = THIS_MODULE,
> +       .id_table = id_table,
> +       .probe = virtio_video_probe,
> +       .remove = virtio_video_remove,
> +};
> +
> +module_virtio_driver(virtio_video_driver);
> +
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +MODULE_DESCRIPTION("virtio video driver");
> +MODULE_AUTHOR("Dmitry Morozov <dmitry.morozov@xxxxxxxxxxxxxxx>");
> +MODULE_AUTHOR("Kiran Pawar <kiran.pawar@xxxxxxxxxxxxxxx>");
> +MODULE_AUTHOR("Nikolay Martyanov <nikolay.martyanov@xxxxxxxxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/virtio/virtio_video_enc.c b/drivers/media/virtio/virtio_video_enc.c
> new file mode 100644
> index 000000000000..3fe6d0e52d4a
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video_enc.c
> @@ -0,0 +1,1124 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/* Encoder for virtio video device.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-dma-sg.h>
> +
> +#include "virtio_video.h"
> +
> +static 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 (*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 int virtio_video_buf_plane_init(uint32_t idx,
> +                                      uint32_t resource_id,
> +                                      struct virtio_video_device *vvd,
> +                                      struct virtio_video_stream *stream,
> +                                      struct vb2_buffer *vb)
> +{
> +       int ret;
> +       unsigned int i;
> +       struct virtio_video *vv = vvd->vv;
> +       struct scatterlist *sg;
> +       struct virtio_video_mem_entry *ents;
> +       struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, idx);
> +
> +       /* Freed when the request has been completed */
> +       ents = kcalloc(sgt->nents, sizeof(*ents), GFP_KERNEL);
> +       for_each_sg(sgt->sgl, sg, sgt->nents, i) {
> +               ents[i].addr = cpu_to_le64(vv->has_iommu
> +                                          ? sg_dma_address(sg)
> +                                          : sg_phys(sg));
> +               ents[i].length = cpu_to_le32(sg->length);
> +       }
> +
> +       v4l2_dbg(1, vv->debug, &vv->v4l2_dev, "mem entries:\n");
> +       if (vv->debug >= 1) {
> +               for (i = 0; i < sgt->nents; i++)
> +                       pr_debug("\t%03i: addr=%llx length=%u\n", i,
> +                                ents[i].addr, ents[i].length);
> +       }
> +
> +       ret = virtio_video_req_resource_attach_backing(vv, vvd->id,
> +                                                      stream->stream_id,
> +                                                      resource_id, ents,
> +                                                      sgt->nents);
> +       if (ret)
> +               kfree(ents);
> +
> +       return ret;
> +}
> +
> +static int virtio_video_buf_init(struct vb2_buffer *vb)
> +{
> +       int ret = 0;
> +       unsigned int i;
> +       uint32_t resource_id;
> +       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);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       virtio_video_resource_id_get(vv, &resource_id);
> +       ret = virtio_video_req_resource_create(vv, vvd->id, stream->stream_id,
> +                                              resource_id);
> +       if (ret)
> +               return ret;
> +
> +       for (i = 0; i < vb->num_planes; ++i) {
> +               ret = virtio_video_buf_plane_init(i,
> +                               resource_id, vvd, stream, vb);
> +               if (ret)
> +                       break;
> +       }
> +
> +       if (ret) {
> +               virtio_video_req_resource_destroy(vvd->vv, vvd->id,
> +                                                 stream->stream_id,
> +                                                 resource_id);
> +               virtio_video_resource_id_put(vvd->vv, resource_id);
> +               return ret;
> +       }
> +
> +       virtio_vb->queued = false;
> +       virtio_vb->detached = false;
> +       virtio_vb->resource_id = resource_id;
> +
> +       return 0;
> +}
> +
> +static void virtio_video_buf_cleanup(struct vb2_buffer *vb)
> +{
> +       int ret;
> +       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);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       ret = virtio_video_req_resource_detach_backing(vv, vvd->id,
> +                                                      stream->stream_id,
> +                                                      virtio_vb);
> +       if (ret)
> +               return;
> +
> +       ret = wait_event_timeout(vv->wq, virtio_vb->detached, 5 * HZ);
> +       if (ret == 0) {
> +               v4l2_err(&vv->v4l2_dev, "timed out waiting for detach\n");
> +               return;
> +       }
> +
> +       virtio_video_req_resource_destroy(vv, vvd->id, stream->stream_id,
> +                                         virtio_vb->resource_id);
> +       virtio_video_resource_id_put(vv, virtio_vb->resource_id);
> +}
> +
> +static void virtio_video_buf_queue(struct vb2_buffer *vb)
> +{
> +       struct vb2_v4l2_buffer *v4l2_vb = to_vb2_v4l2_buffer(vb);
> +       struct virtio_video_stream *stream = vb2_get_drv_priv(vb->vb2_queue);
> +
> +       v4l2_m2m_buf_queue(stream->fh.m2m_ctx, v4l2_vb);
> +
> +       v4l2_vb = v4l2_m2m_next_dst_buf(stream->fh.m2m_ctx);
> +       if (v4l2_vb && stream->mark_last_buffer_pending) {
> +               v4l2_vb = v4l2_m2m_dst_buf_remove(stream->fh.m2m_ctx);
> +               if (v4l2_vb)
> +                       virtio_video_mark_drain_complete(stream, v4l2_vb);
> +               stream->mark_last_buffer_pending = false;
> +       }
> +}
> +
> +static int virtio_video_start_streaming(struct vb2_queue *vq,
> +                                       unsigned int count)
> +{
> +       int ret;
> +       struct virtio_video_stream *stream = vb2_get_drv_priv(vq);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       if (stream->state == STREAM_STATE_INIT ||
> +           (!V4L2_TYPE_IS_OUTPUT(vq->type) &&
> +            stream->state == STREAM_STATE_RESET) ||
> +           (V4L2_TYPE_IS_OUTPUT(vq->type) &&
> +           stream->state == STREAM_STATE_STOPPED)) {
> +               ret = virtio_video_req_stream_start(vv, vvd->id,
> +                                                   stream->stream_id);
> +               if (ret)
> +                       return ret;
> +               stream->state = STREAM_STATE_RUNNING;
> +       }
> +
> +       return 0;
> +}
> +
> +static void virtio_video_stop_streaming(struct vb2_queue *vq)
> +{
> +       int ret;
> +       bool *cleared;
> +       bool is_output = V4L2_TYPE_IS_OUTPUT(vq->type);
> +       struct virtio_video_stream *stream = vb2_get_drv_priv(vq);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +       struct vb2_v4l2_buffer *v4l2_vb;
> +
> +       if (is_output)
> +               cleared = &stream->src_cleared;
> +       else
> +               cleared = &stream->dst_cleared;
> +
> +       if (is_output && stream->state == STREAM_STATE_DRAIN)
> +               stream->check_drain_sequence_pending = true;
> +
> +       ret = virtio_video_req_queue_clear(vv, vvd->id, stream, is_output);
> +       if (ret) {
> +               v4l2_err(&vv->v4l2_dev, "failed to clear queue\n");
> +               return;
> +       }
> +
> +       ret = wait_event_timeout(vv->wq, *cleared, 5 * HZ);
> +       if (ret == 0) {
> +               v4l2_err(&vv->v4l2_dev, "timed out waiting for queue clear\n");
> +               return;
> +       }
> +
> +       if (stream->check_drain_sequence_pending) {
> +               v4l2_vb = v4l2_m2m_dst_buf_remove(stream->fh.m2m_ctx);
> +               if (v4l2_vb)
> +                       virtio_video_mark_drain_complete(stream, v4l2_vb);
> +               else
> +                       stream->mark_last_buffer_pending = true;
> +       }
> +
> +       for (;;) {
> +               if (is_output)
> +                       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);
> +       }
> +
> +       if (is_output)
> +               stream->state = STREAM_STATE_STOPPED;
> +       else
> +               stream->state = STREAM_STATE_RESET;
> +}
> +
> +static const struct vb2_ops virtio_video_qops = {
> +       .queue_setup     = virtio_video_queue_setup,
> +       .buf_init        = virtio_video_buf_init,
> +       .buf_cleanup     = virtio_video_buf_cleanup,
> +       .buf_queue       = virtio_video_buf_queue,
> +       .start_streaming = virtio_video_start_streaming,
> +       .stop_streaming  = virtio_video_stop_streaming,
> +       .wait_prepare    = vb2_ops_wait_prepare,
> +       .wait_finish     = vb2_ops_wait_finish,
> +};
> +
> +static int virtio_video_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       int ret = 0, val;
> +       struct virtio_video_stream *stream = ctrl2stream(ctrl);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       switch (ctrl->id) {
> +       case V4L2_CID_MPEG_VIDEO_BITRATE:
> +               ret = virtio_video_req_set_control(vv, vvd->id,
> +                                                  stream->stream_id,
> +                                                  ctrl->id, ctrl->val);
> +               break;
> +       case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
> +               val = virtio_video_v4l2_level_to_virtio(ctrl->val);
> +               if (val == VIRTIO_MPEG_VIDEO_H264_LEVEL_UNDEFINED)
> +                       return -EINVAL;
> +               ret = virtio_video_req_set_control
> +                       (vv, vvd->id, stream->stream_id, ctrl->id, val);
> +               break;
> +       case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
> +               val = virtio_video_v4l2_profile_to_virtio(ctrl->val);
> +               if (val == VIRTIO_MPEG_VIDEO_H264_PROFILE_UNDEFINED)
> +                       return -EINVAL;
> +               ret = virtio_video_req_set_control
> +                       (vv, vvd->id, stream->stream_id, ctrl->id, val);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +               break;
> +       }
> +
> +       return ret;
> +}
> +
> +static int virtio_video_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       int ret = 0;
> +       struct virtio_video_stream *stream = ctrl2stream(ctrl);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +
> +       switch (ctrl->id) {
> +       case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT:
> +               if (stream->state >= STREAM_STATE_INIT) {
> +                       /* TODO: Get the real value from the device */
> +                       ctrl->val = vvd->in_info.min_buffers;
> +               } else {
> +                       ctrl->val = 0;
> +               }
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +
> +       return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops virtio_video_ctrl_ops = {
> +       .g_volatile_ctrl        = virtio_video_g_volatile_ctrl,
> +       .s_ctrl                 = virtio_video_s_ctrl,
> +};
> +
> +int virtio_video_init_enc_ctrls(struct virtio_video_stream *stream)
> +{
> +       struct v4l2_ctrl *ctrl;
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +       struct video_capability *cap = NULL;
> +
> +       v4l2_ctrl_handler_init(&stream->ctrl_handler, 1);
> +
> +       ctrl = v4l2_ctrl_new_std(&stream->ctrl_handler,
> +                               &virtio_video_ctrl_ops,
> +                               V4L2_CID_MIN_BUFFERS_FOR_OUTPUT,
> +                               MIN_BUFS_MIN, MIN_BUFS_MAX, MIN_BUFS_STEP,
> +                               MIN_BUFS_DEF);
> +       ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> +
> +       list_for_each_entry(cap, &vvd->ctrl_caps_list, caps_list_entry) {
> +               struct video_controls *video_controls = &cap->u.controls;
> +               struct video_control *control = NULL;
> +               int ctrl_idx = 0;
> +
> +               for (ctrl_idx = 0; ctrl_idx < video_controls->num_controls;
> +                    ctrl_idx++) {
> +                       control = &video_controls->control[ctrl_idx];
> +                       if (!control)
> +                               continue;
> +
> +                       switch (control->control_type) {
> +                       case V4L2_CID_MPEG_VIDEO_BITRATE:
> +                               v4l2_ctrl_new_std
> +                                       (&stream->ctrl_handler,
> +                                        &virtio_video_ctrl_ops,
> +                                        control->control_type,
> +                                        control->min, control->max,
> +                                        control->step, control->def);
> +                               break;
> +
> +                       case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
> +                       case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
> +                               v4l2_ctrl_new_std_menu
> +                                       (&stream->ctrl_handler,
> +                                        &virtio_video_ctrl_ops,
> +                                        control->control_type,
> +                                        control->max, 0, control->def);
> +                               break;
> +                       default:
> +                               v4l2_err(&vv->v4l2_dev,
> +                                        "unsupported control type\n");
> +                       }
> +               }
> +       }
> +
> +       if (stream->ctrl_handler.error)
> +               return stream->ctrl_handler.error;
> +
> +       v4l2_ctrl_handler_setup(&stream->ctrl_handler);
> +
> +       return 0;
> +}
> +
> +int virtio_video_init_enc_queues(void *priv, struct vb2_queue *src_vq,
> +                                struct vb2_queue *dst_vq)
> +{
> +       int ret;
> +       struct virtio_video_stream *stream = priv;
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct device *dev = vvd->vv->v4l2_dev.dev;
> +
> +       src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +       src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> +       src_vq->drv_priv = stream;
> +       src_vq->buf_struct_size = sizeof(struct virtio_video_buffer);
> +       src_vq->ops = &virtio_video_qops;
> +       src_vq->mem_ops = &vb2_dma_sg_memops;
> +       src_vq->min_buffers_needed = vvd->in_info.min_buffers;
> +       src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +       src_vq->lock = &stream->vq_mutex;
> +       src_vq->dev = dev;
> +
> +       ret = vb2_queue_init(src_vq);
> +       if (ret)
> +               return ret;
> +
> +       dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +       dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> +       dst_vq->drv_priv = stream;
> +       dst_vq->buf_struct_size = sizeof(struct virtio_video_buffer);
> +       dst_vq->ops = &virtio_video_qops;
> +       dst_vq->mem_ops = &vb2_dma_sg_memops;
> +       dst_vq->min_buffers_needed = vvd->out_info.min_buffers;
> +       dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +       dst_vq->lock = &stream->vq_mutex;
> +       dst_vq->dev = dev;
> +
> +       return vb2_queue_init(dst_vq);
> +}
> +
> +static int virtio_video_querycap(struct file *file, void *fh,
> +                                struct v4l2_capability *cap)
> +{
> +       struct video_device *video_dev = video_devdata(file);
> +
> +       strncpy(cap->driver, DRIVER_NAME, sizeof(cap->driver));
> +       strncpy(cap->card, video_dev->name, sizeof(cap->card));
> +       snprintf(cap->bus_info, sizeof(cap->bus_info), "virtio:%s",
> +                video_dev->name);
> +
> +       cap->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
> +       cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
> +
> +       return 0;
> +}
> +
> +static int virtio_video_enum_fmt_vid_cap(struct file *file, void *fh,
> +                                        struct v4l2_fmtdesc *f)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +
> +       if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> +               return -EINVAL;
> +
> +       if (f->index >= vvd->num_capture_formats)
> +               return -EINVAL;
> +
> +       f->pixelformat = vvd->capture_fmts[f->index]->fourcc_format;
> +
> +       return 0;
> +}
> +
> +static struct video_pix_format *find_pix_format(struct video_pix_format **list,
> +                                               uint32_t fourcc, int num)
> +{
> +       int idx = 0;
> +
> +       for (idx = 0; idx < num; idx++) {
> +               if (list[idx]->fourcc_format == fourcc)
> +                       return list[idx];
> +       }
> +       return NULL;
> +}
> +
> +static int virtio_video_try_encoder_cmd(struct file *file, void *fh,
> +                                       struct v4l2_encoder_cmd *cmd)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = video_drvdata(file);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       if (stream->state == STREAM_STATE_DRAIN)
> +               return -EBUSY;
> +
> +       switch (cmd->cmd) {
> +       case V4L2_ENC_CMD_STOP:
> +       case V4L2_ENC_CMD_START:
> +               if (cmd->flags != 0) {
> +                       v4l2_err(&vv->v4l2_dev, "flags=%u are not supported",
> +                                cmd->flags);
> +                       return -EINVAL;
> +               }
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static int virtio_video_encoder_cmd(struct file *file, void *fh,
> +                                   struct v4l2_encoder_cmd *cmd)
> +{
> +       int ret;
> +       struct vb2_queue *src_vq, *dst_vq;
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = video_drvdata(file);
> +       struct virtio_video *vv = vvd->vv;
> +
> +       ret = virtio_video_try_encoder_cmd(file, fh, cmd);
> +       if (ret < 0)
> +               return ret;
> +
> +       dst_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx,
> +                                V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
> +
> +       switch (cmd->cmd) {
> +       case V4L2_ENC_CMD_START:
> +               vb2_clear_last_buffer_dequeued(dst_vq);
> +               ret = virtio_video_req_stream_start(vv, vvd->id,
> +                                                   stream->stream_id);
> +               if (ret)
> +                       return ret;
> +               stream->state = STREAM_STATE_RUNNING;
> +               break;
> +       case V4L2_ENC_CMD_STOP:
> +               src_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx,
> +                                        V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
> +
> +               if (!vb2_is_streaming(src_vq)) {
> +                       v4l2_dbg(1, vv->debug,
> +                                &vv->v4l2_dev, "output is not streaming\n");
> +                       return 0;
> +               }
> +
> +               if (!vb2_is_streaming(dst_vq)) {
> +                       v4l2_dbg(1, vv->debug,
> +                                &vv->v4l2_dev, "capture is not streaming\n");
> +                       return 0;
> +               }
> +
> +               ret = virtio_video_req_stream_drain(vv, vvd->id,
> +                                                   stream->stream_id);
> +               if (ret) {
> +                       v4l2_err(&vv->v4l2_dev, "failed to drain stream\n");
> +                       return ret;
> +               }
> +
> +               stream->state = STREAM_STATE_DRAIN;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static 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_frame_size *frame_size = NULL;
> +       int i = 0;
> +       bool fake_non_discrete = false;
> +       int idx = f->index;
> +       struct video_pix_format *fmt = NULL;
> +
> +       fmt = find_pix_format(vvd->output_fmts, f->pixel_format,
> +                             vvd->num_output_formats);
> +       if (fmt == NULL)
> +               fmt = find_pix_format(vvd->capture_fmts, f->pixel_format,
> +                                     vvd->num_capture_formats);
> +       if (fmt == NULL)
> +               return -EINVAL;
> +
> +       if (idx >= fmt->num_sizes)
> +               return -EINVAL;
> +
> +       /* If the index is 0 - it is the first call of ENUM_FRAMESIZES, that
> +        * defines a type of all the frame sizes.
> +        *
> +        * Indexes > 0 can be used later only in case of the type is discrete.
> +        * But, if there is at least one non-discrete type later in the array -
> +        * it may be misinterpreted as a discrete one.
> +        *
> +        * Hence, check, whether there is a non-discrete frame size, and if yes
> +        * - return the first of them.
> +        */
> +       if (!idx)
> +               for (i = 0; i < fmt->num_sizes; i++) {
> +                       frame_size = &fmt->frame_sizes[i];
> +                       if (frame_size->min_width != frame_size->max_width ||
> +                           frame_size->min_height != frame_size->max_height) {
> +                               idx = i;
> +                               fake_non_discrete = true;
> +                               break;
> +                       }
> +               }
> +
> +       /* Index > 0 can be used only for discrete frame sizes. Type of the
> +        * frame sizes is equal to type of the first frame size.
> +        */
> +       if (idx && !fake_non_discrete) {
> +               frame_size = &fmt->frame_sizes[0];
> +               if (frame_size->min_width != frame_size->max_width ||
> +                   frame_size->min_height != frame_size->max_height)
> +                       return -EINVAL;
> +       }
> +
> +       frame_size = &fmt->frame_sizes[idx];
> +
> +       if (frame_size->min_width == frame_size->max_width &&
> +          frame_size->min_height == frame_size->max_height) {
> +               f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> +               f->discrete.width = frame_size->min_width;
> +               f->discrete.height = frame_size->min_height;
> +       } else {
> +               if (idx && !fake_non_discrete)
> +                       return -EINVAL;
> +               f->stepwise.min_width = frame_size->min_width;
> +               f->stepwise.min_height = frame_size->min_height;
> +               f->stepwise.step_height = frame_size->step_height;
> +               f->stepwise.step_width = frame_size->step_width;
> +               f->stepwise.max_height = frame_size->max_height;
> +               f->stepwise.max_width = frame_size->max_width;
> +               if (frame_size->step_width == 1 &&
> +                   frame_size->min_height == 1) {
> +                       f->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
> +               } else {
> +                       f->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static bool in_stepped_interval(uint32_t int_start, uint32_t int_end,
> +                               uint32_t step, uint32_t point)
> +{
> +       if (point < int_start || point > int_end)
> +               return false;
> +
> +       if (step == 0 && int_start == int_end && int_start == point)
> +               return true;
> +
> +       if (step != 0 && (point - int_start) % step == 0)
> +               return true;
> +
> +       return false;
> +}
> +
> +static 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_frame_size *fsize = NULL;
> +       int fsize_idx = 0;
> +       int i = 0;
> +       bool fake_non_discrete = false;
> +       int idx = f->index;
> +       struct video_pix_format *fmt = NULL;
> +       struct video_frame_rate *frate = NULL;
> +
> +       fmt = find_pix_format(vvd->output_fmts, f->pixel_format,
> +                             vvd->num_output_formats);
> +       if (fmt == NULL)
> +               fmt = find_pix_format(vvd->capture_fmts, f->pixel_format,
> +                                     vvd->num_capture_formats);
> +       if (fmt == NULL)
> +               return -EINVAL;
> +
> +       for (fsize_idx = 0; fsize_idx <= fmt->num_sizes; fsize_idx++) {
> +               fsize = &fmt->frame_sizes[fsize_idx];
> +               if (in_stepped_interval(fsize->min_width, fsize->max_width,
> +                                       fsize->step_width, f->width) &&
> +                  in_stepped_interval(fsize->min_height, fsize->max_height,
> +                                       fsize->step_height, f->height))
> +                       break;
> +       }
> +
> +       if (fsize == NULL)
> +               return -EINVAL;
> +
> +       if (idx >= fsize->num_rates)
> +               return -EINVAL;
> +
> +       /* If the index is 0 - it is the first call of ENUM_FRAMEIVALS, that
> +        * defines a type of all the frame intervals.
> +        *
> +        * Indexes > 0 can be used later only in case of the type is discrete.
> +        * But, if there is at least one non-discrete type later in the array -
> +        * it may be misinterpreted as a discrete one.
> +        *
> +        * Hence, check, whether there is a non-discrete frame rate, and if yes
> +        * - return the first of them.
> +        */
> +       if (!idx)
> +               for (i = 0; i < fsize->num_rates; i++) {
> +                       frate = &fsize->frame_rates[i];
> +                       if (frate->min_rate != frate->max_rate) {
> +                               fake_non_discrete = true;
> +                               idx = i;
> +                               break;
> +                       }
> +               }
> +
> +       /* Index > 0 can be used only for discrete frame rates. Type of the
> +        * frame rate is equal to the type of the first frame size.
> +        */
> +       if (idx && !fake_non_discrete) {
> +               frate = &fsize->frame_rates[0];
> +               if (frate->max_rate != frate->min_rate)
> +                       return -EINVAL;
> +       }
> +
> +       frate = &fsize->frame_rates[idx];
> +       if (frate->max_rate == frate->min_rate) {
> +               f->type = V4L2_FRMIVAL_TYPE_DISCRETE;
> +               f->discrete.numerator = 1;
> +               f->discrete.denominator = frate->max_rate;
> +       } else {
> +               if (idx && !fake_non_discrete)
> +                       return -EINVAL;
> +               /* If A > B, then 1/A < 1/B, so max denominator = min_rate
> +                * and vise versa
> +                */
> +               f->stepwise.min.numerator = 1;
> +               f->stepwise.min.denominator = frate->max_rate;
> +               f->stepwise.max.numerator = 1;
> +               f->stepwise.max.denominator = frate->min_rate;
> +               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;
> +}
> +
> +static int virtio_video_enum_fmt_vid_out(struct file *file, void *fh,
> +                                        struct v4l2_fmtdesc *f)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +
> +       if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +               return -EINVAL;
> +
> +       if (f->index >= vvd->num_output_formats)
> +               return -EINVAL;
> +
> +       f->pixelformat = vvd->output_fmts[f->index]->fourcc_format;
> +
> +       return 0;
> +}
> +
> +static void fill_v4l2_format_from_info(struct video_format_info *info,
> +                                      struct v4l2_pix_format_mplane *pix_mp)
> +{
> +       int i;
> +
> +       pix_mp->width = info->frame_width;
> +       pix_mp->height = info->frame_height;
> +       pix_mp->field = V4L2_FIELD_NONE;
> +       pix_mp->colorspace = V4L2_COLORSPACE_REC709;
> +       pix_mp->xfer_func = 0;
> +       pix_mp->ycbcr_enc = 0;
> +       pix_mp->quantization = 0;
> +       memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved));
> +       memset(pix_mp->plane_fmt[0].reserved, 0,
> +              sizeof(pix_mp->plane_fmt[0].reserved));
> +
> +       pix_mp->num_planes = info->num_planes;
> +       pix_mp->pixelformat = info->fourcc_format;
> +
> +       for (i = 0; i < info->num_planes; i++) {
> +               pix_mp->plane_fmt[i].bytesperline =
> +                                        info->plane_format[i].stride;
> +               pix_mp->plane_fmt[i].sizeimage =
> +                                        info->plane_format[i].plane_size;
> +       }
> +}
> +
> +static int virtio_video_g_fmt(struct virtio_video_stream *stream,
> +                             struct v4l2_format *f)
> +{
> +       struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +       struct video_format_info *info;
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(f->type))
> +               info = &stream->out_info;
> +       else
> +               info = &stream->in_info;
> +
> +       fill_v4l2_format_from_info(info, pix_mp);
> +       return 0;
> +}
> +
> +static int virtio_video_g_fmt_vid_out(struct file *file, void *fh,
> +                                     struct v4l2_format *f)
> +{
> +       return virtio_video_g_fmt(file2stream(file), f);
> +}
> +
> +static int virtio_video_g_fmt_vid_cap(struct file *file, void *fh,
> +                                     struct v4l2_format *f)
> +{
> +       return virtio_video_g_fmt(file2stream(file), f);
> +}
> +
> +static inline bool within_range(uint32_t min, uint32_t val, uint32_t max)
> +{
> +       return ((val - min) <= (max - min));
> +}
> +
> +static inline bool needs_alignment(uint32_t val, uint32_t a)
> +{
> +       if (a == 0 || IS_ALIGNED(val, a))
> +               return false;
> +
> +       return true;
> +}
> +
> +static 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_pix_format *fmt = NULL;
> +       struct video_frame_size *frm_sz = NULL;
> +       bool found = false;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(f->type))
> +               fmt = find_pix_format(vvd->output_fmts, pix_mp->pixelformat,
> +                                        vvd->num_output_formats);
> +       else
> +               fmt = find_pix_format(vvd->capture_fmts, pix_mp->pixelformat,
> +                                        vvd->num_capture_formats);
> +
> +       if (!fmt) {
> +               if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> +                       fill_v4l2_format_from_info(&stream->out_info, pix_mp);
> +               else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +                       fill_v4l2_format_from_info(&stream->in_info, pix_mp);
> +               else
> +                       return -EINVAL;
> +               return 0;
> +       }
> +
> +       for (i = 0; i < fmt->num_sizes && !found; i++) {
> +               frm_sz = &fmt->frame_sizes[i];
> +               if (!within_range(frm_sz->min_width, pix_mp->width,
> +                                frm_sz->max_width))
> +                       continue;
> +
> +               if (!within_range(frm_sz->min_height, pix_mp->height,
> +                                 frm_sz->max_height))
> +                       continue;
> +
> +               idx = i;
> +               /*
> +                * Try to find a more suitable frame size. Go with the current
> +                * one otherwise.
> +                */
> +               if (needs_alignment(pix_mp->width, frm_sz->step_width))
> +                       continue;
> +
> +               if (needs_alignment(pix_mp->height, frm_sz->step_height))
> +                       continue;
> +
> +               stream->current_frame_size = frm_sz;
> +               found = true;
> +       }
> +
> +       if (!found) {
> +               frm_sz = &fmt->frame_sizes[idx];
> +               pix_mp->width = clamp(pix_mp->width, frm_sz->min_width,
> +                                     frm_sz->max_width);
> +               if (frm_sz->step_width != 0)
> +                       pix_mp->width = ALIGN(pix_mp->width,
> +                                             frm_sz->step_width);
> +
> +               pix_mp->height = clamp(pix_mp->height, frm_sz->min_height,
> +                                     frm_sz->max_height);
> +               if (frm_sz->step_height != 0)
> +                       pix_mp->height = ALIGN(pix_mp->height,
> +                                      frm_sz->step_height);
> +               stream->current_frame_size = frm_sz;
> +       }
> +
> +       return 0;
> +}
> +
> +static int virtio_video_s_fmt(struct virtio_video_stream *stream,
> +                             struct v4l2_format *f)
> +{
> +       int i, ret;
> +       struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +       struct video_format_info info;
> +       struct video_format_info *p_info;
> +       enum video_pin_type pin = VIDEO_PIN_TYPE_INPUT;
> +
> +       ret = virtio_video_try_fmt(stream, f);
> +       if (ret)
> +               return ret;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(f->type)) {
> +               info.frame_rate = stream->in_info.frame_rate;
> +       } else {
> +               info.frame_rate = stream->out_info.frame_rate;
> +               pin = VIDEO_PIN_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_req_set_params(vv, vvd->id, &info, pin,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +       virtio_video_req_get_params(vv, vvd->id, VIDEO_PIN_TYPE_INPUT,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +       virtio_video_req_get_params(vv, vvd->id, VIDEO_PIN_TYPE_OUTPUT,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +
> +       if (V4L2_TYPE_IS_OUTPUT(f->type))
> +               p_info = &stream->in_info;
> +       else
> +               p_info = &stream->out_info;
> +
> +       fill_v4l2_format_from_info(p_info, pix_mp);
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(f->type)) {
> +               if (stream->state == STREAM_STATE_IDLE)
> +                       stream->state = STREAM_STATE_INIT;
> +       }
> +
> +       return 0;
> +}
> +
> +static int virtio_video_s_fmt_vid_cap(struct file *file, void *fh,
> +                                     struct v4l2_format *f)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +
> +       return virtio_video_s_fmt(stream, f);
> +}
> +
> +static int virtio_video_s_fmt_vid_out(struct file *file, void *fh,
> +                                     struct v4l2_format *f)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +
> +       return virtio_video_s_fmt(stream, f);
> +}
> +
> +static int virtio_video_try_framerate(struct virtio_video_stream *stream,
> +                                    unsigned int fps)
> +{
> +       int rate_idx;
> +       struct video_frame_size *frame_size = NULL;
> +
> +       if (stream->current_frame_size == NULL)
> +               return -EINVAL;
> +
> +       frame_size = stream->current_frame_size;
> +       for (rate_idx = 0; rate_idx < frame_size->num_rates; rate_idx++) {
> +               struct video_frame_rate *frame_rate =
> +                       &frame_size->frame_rates[rate_idx];
> +
> +               if (within_range(frame_rate->min_rate,
> +                                fps, frame_rate->max_rate))
> +                       return 0;
> +       }
> +       return -EINVAL;
> +}
> +
> +static void fill_timeperframe_from_info(struct video_format_info *info,
> +                                       struct v4l2_fract *timeperframe)
> +{
> +       timeperframe->numerator = info->frame_rate;
> +       timeperframe->denominator = 1;
> +}
> +
> +static int virtio_video_g_parm(struct file *file, void *priv,
> +                              struct v4l2_streamparm *a)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +       struct v4l2_outputparm *out = &a->parm.output;
> +       struct v4l2_fract *timeperframe = &out->timeperframe;
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(a->type)) {
> +               v4l2_err(&vv->v4l2_dev,
> +                        "Getting FPS is only possible for the output queue\n");
> +               return -EINVAL;
> +       }
> +
> +       out->capability = V4L2_CAP_TIMEPERFRAME;
> +       fill_timeperframe_from_info(&stream->in_info, timeperframe);
> +       return 0;
> +}
> +
> +static int virtio_video_s_parm(struct file *file, void *priv,
> +                              struct v4l2_streamparm *a)
> +{
> +       struct virtio_video_stream *stream = file2stream(file);
> +       struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
> +       struct virtio_video *vv = vvd->vv;
> +       struct v4l2_outputparm *out = &a->parm.output;
> +       struct v4l2_fract *timeperframe = &out->timeperframe;
> +       struct video_format_info info, *p_info;
> +       int i, ret;
> +       u64 frame_interval, frame_rate;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(a->type)) {
> +               frame_interval = timeperframe->numerator * (u64)USEC_PER_SEC;
> +               do_div(frame_interval, timeperframe->denominator);
> +               if (!frame_interval)
> +                       return -EINVAL;
> +
> +               frame_rate = (u64)USEC_PER_SEC;
> +               do_div(frame_rate, frame_interval);
> +       } else {
> +               v4l2_err(&vv->v4l2_dev,
> +                        "Setting FPS is only possible for the output queue\n");
> +               return -EINVAL;
> +       }
> +
> +       ret = virtio_video_try_framerate(stream, frame_rate);
> +       if (ret)
> +               return ret;
> +
> +       p_info = &stream->in_info;
> +       info.frame_rate = frame_rate;
> +       info.frame_width = p_info->frame_width;
> +       info.frame_height = p_info->frame_height;
> +       info.num_planes = p_info->num_planes;
> +       info.fourcc_format = p_info->fourcc_format;
> +
> +       for (i = 0; i < info.num_planes; i++) {
> +               info.plane_format[i].stride =
> +                       p_info->plane_format[i].stride;
> +               info.plane_format[i].plane_size =
> +                       p_info->plane_format[i].plane_size;
> +       }
> +
> +       virtio_video_req_set_params(vv, vvd->id, &info, VIDEO_PIN_TYPE_INPUT,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +       virtio_video_req_get_params(vv, vvd->id, VIDEO_PIN_TYPE_INPUT,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +       virtio_video_req_get_params(vv, vvd->id, VIDEO_PIN_TYPE_OUTPUT,
> +                                   VIDEO_PARAMS_SCOPE_STREAM, stream);
> +
> +       out->capability = V4L2_CAP_TIMEPERFRAME;
> +       fill_timeperframe_from_info(&stream->in_info, timeperframe);
> +       return 0;
> +}
> +
> +static 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 -EINVAL;
> +       }
> +}
> +
> +static const struct v4l2_ioctl_ops virtio_video_device_enc_ioctl_ops = {
> +       .vidioc_querycap        = virtio_video_querycap,
> +
> +       .vidioc_enum_fmt_vid_cap = virtio_video_enum_fmt_vid_cap,
> +       .vidioc_g_fmt_vid_cap   = virtio_video_g_fmt_vid_cap,
> +       .vidioc_s_fmt_vid_cap   = virtio_video_s_fmt_vid_cap,
> +
> +       .vidioc_g_fmt_vid_cap_mplane    = virtio_video_g_fmt_vid_cap,
> +       .vidioc_s_fmt_vid_cap_mplane    = virtio_video_s_fmt_vid_cap,
> +
> +       .vidioc_enum_fmt_vid_out = virtio_video_enum_fmt_vid_out,
> +       .vidioc_g_fmt_vid_out   = virtio_video_g_fmt_vid_out,
> +       .vidioc_s_fmt_vid_out   = virtio_video_s_fmt_vid_out,
> +
> +       .vidioc_g_fmt_vid_out_mplane    = virtio_video_g_fmt_vid_out,
> +       .vidioc_s_fmt_vid_out_mplane    = virtio_video_s_fmt_vid_out,
> +
> +       .vidioc_try_encoder_cmd = virtio_video_try_encoder_cmd,
> +       .vidioc_encoder_cmd     = virtio_video_encoder_cmd,
> +       .vidioc_enum_frameintervals = virtio_video_enum_framemintervals,
> +       .vidioc_enum_framesizes = virtio_video_enum_framesizes,
> +
> +       .vidioc_reqbufs         = v4l2_m2m_ioctl_reqbufs,
> +       .vidioc_querybuf        = v4l2_m2m_ioctl_querybuf,
> +       .vidioc_qbuf            = v4l2_m2m_ioctl_qbuf,
> +       .vidioc_dqbuf           = v4l2_m2m_ioctl_dqbuf,
> +       .vidioc_prepare_buf     = v4l2_m2m_ioctl_prepare_buf,
> +       .vidioc_create_bufs     = v4l2_m2m_ioctl_create_bufs,
> +       .vidioc_expbuf          = v4l2_m2m_ioctl_expbuf,
> +
> +       .vidioc_streamon        = v4l2_m2m_ioctl_streamon,
> +       .vidioc_streamoff       = v4l2_m2m_ioctl_streamoff,
> +
> +       .vidioc_s_parm          = virtio_video_s_parm,
> +       .vidioc_g_parm          = virtio_video_g_parm,
> +
> +       .vidioc_subscribe_event = virtio_video_subscribe_event,
> +       .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +int virtio_video_enc_init(struct virtio_video_device *vvd)
> +{
> +       struct video_device *vd = NULL;
> +
> +       if (!vvd)
> +               return -EINVAL;
> +
> +       vd = &vvd->video_dev;
> +       vd->ioctl_ops = &virtio_video_device_enc_ioctl_ops;
> +       return 0;
> +}
> diff --git a/drivers/media/virtio/virtio_video_enc.h b/drivers/media/virtio/virtio_video_enc.h
> new file mode 100644
> index 000000000000..28fb385f40a7
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video_enc.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/* Encoder header for virtio video driver.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef _VIRTIO_VIDEO_ENC_H
> +#define _VIRTIO_VIDEO_ENC_H
> +
> +#include "virtio_video.h"
> +
> +int virtio_video_enc_init(struct virtio_video_device *vvd);
> +int virtio_video_init_enc_ctrls(struct virtio_video_stream *stream);
> +int virtio_video_init_enc_queues(void *priv, struct vb2_queue *src_vq,
> +                                struct vb2_queue *dst_vq);
> +
> +#endif /* _VIRTIO_VIDEO_ENC_H */
> diff --git a/drivers/media/virtio/virtio_video_vq.c b/drivers/media/virtio/virtio_video_vq.c
> new file mode 100644
> index 000000000000..4ad72332112f
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_video_vq.c
> @@ -0,0 +1,950 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/* Driver for virtio video device.
> + *
> + * Copyright 2019 OpenSynergy GmbH.
> + *
> + * Based on drivers/gpu/drm/virtio/virtgpu_vq.c
> + * Copyright (C) 2015 Red Hat, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "virtio_video.h"
> +
> +#define MAX_INLINE_CMD_SIZE   298
> +#define MAX_INLINE_RESP_SIZE  298
> +#define VBUFFER_SIZE          (sizeof(struct virtio_video_vbuffer) \
> +                              + MAX_INLINE_CMD_SIZE               \
> +                              + MAX_INLINE_RESP_SIZE)
> +
> +void virtio_video_resource_id_get(struct virtio_video *vv, uint32_t *id)
> +{
> +       int handle;
> +
> +       idr_preload(GFP_KERNEL);
> +       spin_lock(&vv->resource_idr_lock);
> +       handle = idr_alloc(&vv->resource_idr, NULL, 1, 0, GFP_NOWAIT);
> +       spin_unlock(&vv->resource_idr_lock);
> +       idr_preload_end();
> +       *id = handle;
> +}
> +
> +void virtio_video_resource_id_put(struct virtio_video *vv, uint32_t id)
> +{
> +       spin_lock(&vv->resource_idr_lock);
> +       idr_remove(&vv->resource_idr, id);
> +       spin_unlock(&vv->resource_idr_lock);
> +}
> +
> +void virtio_video_stream_id_get(struct virtio_video *vv,
> +                               struct virtio_video_stream *stream,
> +                               uint32_t *id)
> +{
> +       int handle;
> +
> +       idr_preload(GFP_KERNEL);
> +       spin_lock(&vv->stream_idr_lock);
> +       handle = idr_alloc(&vv->stream_idr, stream, 1, 0, 0);
> +       spin_unlock(&vv->stream_idr_lock);
> +       idr_preload_end();
> +       *id = handle;
> +}
> +
> +void virtio_video_stream_id_put(struct virtio_video *vv, uint32_t id)
> +{
> +       spin_lock(&vv->stream_idr_lock);
> +       idr_remove(&vv->stream_idr, id);
> +       spin_unlock(&vv->stream_idr_lock);
> +}
> +
> +void virtio_video_ctrl_ack(struct virtqueue *vq)
> +{
> +       struct virtio_video *vv = vq->vdev->priv;
> +
> +       schedule_work(&vv->ctrlq.dequeue_work);
> +}
> +
> +void virtio_video_event_ack(struct virtqueue *vq)
> +{
> +       struct virtio_video *vv = vq->vdev->priv;
> +
> +       schedule_work(&vv->eventq.dequeue_work);
> +}
> +
> +static struct virtio_video_vbuffer *
> +virtio_video_get_vbuf(struct virtio_video *vv, int size,
> +                     int resp_size, void *resp_buf,
> +                     virtio_video_resp_cb resp_cb)
> +{
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       vbuf = kmem_cache_alloc(vv->vbufs, GFP_KERNEL);
> +       if (!vbuf)
> +               return ERR_PTR(-ENOMEM);
> +       memset(vbuf, 0, VBUFFER_SIZE);
> +
> +       BUG_ON(size > MAX_INLINE_CMD_SIZE);
> +       vbuf->buf = (void *)vbuf + sizeof(*vbuf);
> +       vbuf->size = size;
> +
> +       vbuf->resp_cb = resp_cb;
> +       vbuf->resp_size = resp_size;
> +       if (resp_size <= MAX_INLINE_RESP_SIZE && !resp_buf)
> +               vbuf->resp_buf = (void *)vbuf->buf + size;
> +       else
> +               vbuf->resp_buf = resp_buf;
> +       BUG_ON(!vbuf->resp_buf);
> +
> +       return vbuf;
> +}
> +
> +static void free_vbuf(struct virtio_video *vv,
> +                     struct virtio_video_vbuffer *vbuf)
> +{
> +       if (!vbuf->resp_cb &&
> +           vbuf->resp_size > MAX_INLINE_RESP_SIZE)
> +               kfree(vbuf->resp_buf);
> +       kfree(vbuf->data_buf);
> +       kmem_cache_free(vv->vbufs, vbuf);
> +}
> +
> +static void reclaim_vbufs(struct virtqueue *vq, struct list_head *reclaim_list)
> +{
> +       struct virtio_video_vbuffer *vbuf;
> +       unsigned int len;
> +       struct virtio_video *vv = vq->vdev->priv;
> +       int freed = 0;
> +
> +       while ((vbuf = virtqueue_get_buf(vq, &len))) {
> +               list_add_tail(&vbuf->list, reclaim_list);
> +               freed++;
> +       }
> +
> +       if (freed == 0)
> +               v4l2_dbg(1, vv->debug, &vv->v4l2_dev,
> +                        "zero vbufs reclaimed\n");
> +}
> +
> +static void detach_vbufs(struct virtqueue *vq, struct list_head *detach_list)
> +{
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       while ((vbuf = virtqueue_detach_unused_buf(vq)) != NULL)
> +               list_add_tail(&vbuf->list, detach_list);
> +}
> +
> +static void virtio_video_deatch_vbufs(struct virtio_video *vv)
> +{
> +       struct list_head detach_list;
> +       struct virtio_video_vbuffer *entry, *tmp;
> +
> +       INIT_LIST_HEAD(&detach_list);
> +
> +       detach_vbufs(vv->eventq.vq, &detach_list);
> +       detach_vbufs(vv->ctrlq.vq, &detach_list);
> +
> +       if (list_empty(&detach_list))
> +               return;
> +
> +       list_for_each_entry_safe(entry, tmp, &detach_list, list) {
> +               list_del(&entry->list);
> +               free_vbuf(vv, entry);
> +       }
> +}
> +
> +int virtio_video_alloc_vbufs(struct virtio_video *vv)
> +{
> +       vv->vbufs =
> +               kmem_cache_create("virtio-video-vbufs", VBUFFER_SIZE,
> +                                 __alignof__(struct virtio_video_vbuffer), 0,
> +                                 NULL);
> +       if (!vv->vbufs)
> +               return -ENOMEM;
> +
> +       return 0;
> +}
> +
> +void virtio_video_free_vbufs(struct virtio_video *vv)
> +{
> +       virtio_video_deatch_vbufs(vv);
> +       kmem_cache_destroy(vv->vbufs);
> +       vv->vbufs = NULL;
> +}
> +
> +static void *virtio_video_alloc_req(struct virtio_video *vv,
> +                                   struct virtio_video_vbuffer **vbuffer_p,
> +                                   int size)
> +{
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       vbuf = virtio_video_get_vbuf(vv, size,
> +                                    sizeof(struct virtio_video_ctrl_hdr),
> +                                    NULL, NULL);
> +       if (IS_ERR(vbuf)) {
> +               *vbuffer_p = NULL;
> +               return ERR_CAST(vbuf);
> +       }
> +       *vbuffer_p = vbuf;
> +
> +       return vbuf->buf;
> +}
> +
> +static void *
> +virtio_video_alloc_req_resp(struct virtio_video *vv,
> +                           virtio_video_resp_cb cb,
> +                           struct virtio_video_vbuffer **vbuffer_p,
> +                           int req_size, int resp_size,
> +                           void *resp_buf)
> +{
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       vbuf = virtio_video_get_vbuf(vv, req_size, resp_size, resp_buf, cb);
> +       if (IS_ERR(vbuf)) {
> +               *vbuffer_p = NULL;
> +               return ERR_CAST(vbuf);
> +       }
> +       *vbuffer_p = vbuf;
> +
> +       return vbuf->buf;
> +}
> +
> +void virtio_video_dequeue_ctrl_func(struct work_struct *work)
> +{
> +       struct virtio_video *vv =
> +               container_of(work, struct virtio_video,
> +                            ctrlq.dequeue_work);
> +       struct list_head reclaim_list;
> +       struct virtio_video_vbuffer *entry, *tmp;
> +       struct virtio_video_ctrl_hdr *resp;
> +
> +       INIT_LIST_HEAD(&reclaim_list);
> +       spin_lock(&vv->ctrlq.qlock);
> +       do {
> +               virtqueue_disable_cb(vv->ctrlq.vq);
> +               reclaim_vbufs(vv->ctrlq.vq, &reclaim_list);
> +
> +       } while (!virtqueue_enable_cb(vv->ctrlq.vq));
> +       spin_unlock(&vv->ctrlq.qlock);
> +
> +       list_for_each_entry_safe(entry, tmp, &reclaim_list, list) {
> +               resp = (struct virtio_video_ctrl_hdr *)entry->resp_buf;
> +               if (resp->type >= cpu_to_le32(VIRTIO_VIDEO_S_ERR_UNSPEC))
> +                       v4l2_dbg(1, vv->debug, &vv->v4l2_dev,
> +                                "response 0x%x\n", le32_to_cpu(resp->type));
> +               if (entry->resp_cb)
> +                       entry->resp_cb(vv, entry);
> +
> +               list_del(&entry->list);
> +               free_vbuf(vv, entry);
> +       }
> +       wake_up(&vv->ctrlq.ack_queue);
> +}
> +
> +void virtio_video_dequeue_event_func(struct work_struct *work)
> +{
> +       struct virtio_video *vv =
> +               container_of(work, struct virtio_video,
> +                            eventq.dequeue_work);
> +       struct list_head reclaim_list;
> +       struct virtio_video_vbuffer *entry, *tmp;
> +
> +       INIT_LIST_HEAD(&reclaim_list);
> +       spin_lock(&vv->eventq.qlock);
> +       do {
> +               virtqueue_disable_cb(vv->eventq.vq);
> +               reclaim_vbufs(vv->eventq.vq, &reclaim_list);
> +
> +       } while (!virtqueue_enable_cb(vv->eventq.vq));
> +       spin_unlock(&vv->eventq.qlock);
> +
> +       list_for_each_entry_safe(entry, tmp, &reclaim_list, list) {
> +               entry->resp_cb(vv, entry);
> +               list_del(&entry->list);
> +       }
> +       wake_up(&vv->eventq.ack_queue);
> +}
> +
> +static int
> +virtio_video_queue_ctrl_buffer_locked(struct virtio_video *vv,
> +                                     struct virtio_video_vbuffer *vbuf)
> +{
> +       struct virtqueue *vq = vv->ctrlq.vq;
> +       struct scatterlist *sgs[3], vreq, vout, vresp;
> +       int outcnt = 0, incnt = 0;
> +       int ret;
> +
> +       if (!vv->vq_ready)
> +               return -ENODEV;
> +
> +       sg_init_one(&vreq, vbuf->buf, vbuf->size);
> +       sgs[outcnt + incnt] = &vreq;
> +       outcnt++;
> +
> +       if (vbuf->data_size) {
> +               sg_init_one(&vout, vbuf->data_buf, vbuf->data_size);
> +               sgs[outcnt + incnt] = &vout;
> +               outcnt++;
> +       }
> +
> +       if (vbuf->resp_size) {
> +               sg_init_one(&vresp, vbuf->resp_buf, vbuf->resp_size);
> +               sgs[outcnt + incnt] = &vresp;
> +               incnt++;
> +       }
> +
> +retry:
> +       ret = virtqueue_add_sgs(vq, sgs, outcnt, incnt, vbuf, GFP_ATOMIC);
> +       if (ret == -ENOSPC) {
> +               spin_unlock(&vv->ctrlq.qlock);
> +               wait_event(vv->ctrlq.ack_queue, vq->num_free);
> +               spin_lock(&vv->ctrlq.qlock);
> +               goto retry;
> +       } else {
> +               virtqueue_kick(vq);
> +       }
> +
> +       return ret;
> +}
> +
> +static int virtio_video_queue_ctrl_buffer(struct virtio_video *vv,
> +                                         struct virtio_video_vbuffer *vbuf)
> +{
> +       int ret;
> +
> +       spin_lock(&vv->ctrlq.qlock);
> +       ret = virtio_video_queue_ctrl_buffer_locked(vv, vbuf);
> +       spin_unlock(&vv->ctrlq.qlock);
> +
> +       return ret;
> +}
> +
> +static int virtio_video_queue_event_buffer(struct virtio_video *vv,
> +                                          struct virtio_video_vbuffer *vbuf)
> +{
> +       int ret;
> +       struct scatterlist vresp;
> +       struct virtqueue *vq = vv->eventq.vq;
> +
> +       spin_lock(&vv->eventq.qlock);
> +       sg_init_one(&vresp, vbuf->resp_buf, vbuf->resp_size);
> +       ret = virtqueue_add_inbuf(vq, &vresp, 1, vbuf, GFP_ATOMIC);
> +       spin_unlock(&vv->eventq.qlock);
> +       if (ret)
> +               return ret;
> +
> +       virtqueue_kick(vq);
> +
> +       return 0;
> +}
> +
> +static void virtio_video_event_cb(struct virtio_video *vv,
> +                                 struct virtio_video_vbuffer *vbuf)
> +{
> +       int ret;
> +       struct virtio_video_stream *stream;
> +       struct virtio_video_event *event =
> +               (struct virtio_video_event *)vbuf->resp_buf;
> +
> +       stream = idr_find(&vv->stream_idr, event->stream_id);
> +       if (!stream) {
> +               v4l2_warn(&vv->v4l2_dev, "no stream %u found for event\n",
> +                         event->stream_id);
> +               return;
> +       }
> +
> +       switch (le32_to_cpu(event->event_type)) {
> +       case VIRTIO_VIDEO_EVENT_T_RESOLUTION_CHANGED:
> +               virtio_video_req_get_params(vv, event->function_id,
> +                                           VIDEO_PIN_TYPE_OUTPUT,
> +                                           VIDEO_PARAMS_SCOPE_STREAM, stream);
> +               virtio_video_queue_res_chg_event(stream);
> +               break;
> +       case VIRTIO_VIDEO_EVENT_T_CONFIGURED:
> +               if (stream->state == STREAM_STATE_INIT) {
> +                       stream->state = STREAM_STATE_METADATA;
> +                       wake_up(&vv->wq);
> +               }
> +               break;
> +       default:
> +               v4l2_warn(&vv->v4l2_dev, "failed to queue event buffer\n");
> +               break;
> +       }
> +
> +       memset(vbuf->resp_buf, 0, vbuf->resp_size);
> +       ret = virtio_video_queue_event_buffer(vv, vbuf);
> +       if (ret)
> +               v4l2_warn(&vv->v4l2_dev, "queue event buffer failed\n");
> +}
> +
> +int virtio_video_alloc_events(struct virtio_video *vv, size_t num)
> +{
> +       int ret;
> +       size_t i;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       for (i = 0; i < num; i++) {
> +               vbuf = virtio_video_get_vbuf(vv, 0,
> +                                            sizeof(struct virtio_video_event),
> +                                            NULL, virtio_video_event_cb);
> +               if (IS_ERR(vbuf))
> +                       return PTR_ERR(vbuf);
> +
> +               ret = virtio_video_queue_event_buffer(vv, vbuf);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +int virtio_video_req_stream_create(struct virtio_video *vv,
> +                                  uint32_t function_id, uint32_t stream_id,
> +                                  const char *name)
> +{
> +       struct virtio_video_stream_create *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_STREAM_CREATE);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       strncpy(req_p->debug_name, name, sizeof(req_p->debug_name) - 1);
> +       req_p->debug_name[sizeof(req_p->debug_name) - 1] = 0;
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +int virtio_video_req_stream_destroy(struct virtio_video *vv,
> +                                   uint32_t function_id, uint32_t stream_id)
> +{
> +       struct virtio_video_stream_destroy *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_STREAM_DESTROY);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +int virtio_video_req_stream_start(struct virtio_video *vv,
> +                                 uint32_t function_id, uint32_t stream_id)
> +{
> +       struct virtio_video_stream_start *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_STREAM_START);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +int virtio_video_req_stream_stop(struct virtio_video *vv,
> +                                uint32_t function_id,
> +                                struct virtio_video_stream *stream)
> +{
> +       struct virtio_video_stream_stop *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_STREAM_STOP);
> +       req_p->hdr.stream_id = cpu_to_le32(stream->stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +
> +       vbuf->priv = stream;
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +int virtio_video_req_stream_drain(struct virtio_video *vv,
> +                                 uint32_t function_id, uint32_t stream_id)
> +{
> +       struct virtio_video_stream_drain *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_STREAM_DRAIN);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +int virtio_video_req_resource_create(struct virtio_video *vv,
> +                                    uint32_t function_id, uint32_t stream_id,
> +                                    uint32_t resource_id)
> +{
> +       struct virtio_video_resource_create *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_RESOURCE_CREATE);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->resource_id = cpu_to_le32(resource_id);
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +int virtio_video_req_resource_destroy(struct virtio_video *vv,
> +                                     uint32_t function_id, uint32_t stream_id,
> +                                     uint32_t resource_id)
> +{
> +       struct virtio_video_resource_destroy *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_RESOURCE_DESTROY);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->resource_id = cpu_to_le32(resource_id);
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +static void
> +virtio_video_req_resource_queue_cb(struct virtio_video *vv,
> +                                  struct virtio_video_vbuffer *vbuf)
> +{
> +       uint32_t flags, bytesused;
> +       uint64_t timestamp;
> +       struct virtio_video_buffer *virtio_vb = vbuf->priv;
> +       struct virtio_video_resource_queue_resp *resp =
> +               (struct virtio_video_resource_queue_resp *)vbuf->resp_buf;
> +
> +       flags = le32_to_cpu(resp->flags);
> +       bytesused = le32_to_cpu(resp->size);
> +       timestamp = le64_to_cpu(resp->timestamp);
> +
> +       virtio_video_buf_done(virtio_vb, flags, timestamp, bytesused);
> +}
> +
> +int virtio_video_req_resource_queue(struct virtio_video *vv,
> +                                   uint32_t function_id, uint32_t stream_id,
> +                                   struct virtio_video_buffer *virtio_vb,
> +                                   uint32_t data_size[],
> +                                   uint8_t num_data_size, bool is_in)
> +{
> +       uint8_t i;
> +       struct virtio_video_resource_queue *req_p;
> +       struct virtio_video_resource_queue_resp *resp_p;
> +       struct virtio_video_vbuffer *vbuf;
> +       size_t resp_size = sizeof(struct virtio_video_resource_queue_resp);
> +
> +       req_p = virtio_video_alloc_req_resp(vv,
> +                                           &virtio_video_req_resource_queue_cb,
> +                                           &vbuf, sizeof(*req_p), resp_size,
> +                                           NULL);
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_RESOURCE_QUEUE);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->pin_type = cpu_to_le32(is_in ? VIRTIO_VIDEO_PIN_INPUT :
> +                                     VIRTIO_VIDEO_PIN_OUTPUT);
> +
> +       for (i = 0; i < num_data_size; ++i)
> +               req_p->data_size[i] = cpu_to_le32(data_size[i]);
> +
> +       req_p->resource_id = cpu_to_le32(virtio_vb->resource_id);
> +       req_p->nr_data_size = num_data_size;
> +       req_p->timestamp =
> +               cpu_to_le64(virtio_vb->v4l2_m2m_vb.vb.vb2_buf.timestamp);
> +
> +       resp_p = (struct virtio_video_resource_queue_resp *)vbuf->resp_buf;
> +       memset(resp_p, 0, sizeof(*resp_p));
> +
> +       vbuf->priv = virtio_vb;
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +int
> +virtio_video_req_resource_attach_backing(struct virtio_video *vv,
> +                                        uint32_t function_id,
> +                                        uint32_t stream_id,
> +                                        uint32_t resource_id,
> +                                        struct virtio_video_mem_entry *ents,
> +                                        uint32_t nents)
> +{
> +       struct virtio_video_resource_attach_backing *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_RESOURCE_ATTACH_BACKING);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->resource_id = cpu_to_le32(resource_id);
> +       req_p->nr_entries = cpu_to_le32(nents);
> +
> +       vbuf->data_buf = ents;
> +       vbuf->data_size = sizeof(*ents) * nents;
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +static void
> +virtio_video_req_detach_backing_cb(struct virtio_video *vv,
> +                                  struct virtio_video_vbuffer *vbuf)
> +{
> +       struct virtio_video_buffer *virtio_vb = vbuf->priv;
> +
> +       virtio_vb->detached = true;
> +       wake_up(&vv->wq);
> +}
> +
> +int
> +virtio_video_req_resource_detach_backing(struct virtio_video *vv,
> +                                        uint32_t function_id,
> +                                        uint32_t stream_id,
> +                                        struct virtio_video_buffer *virtio_vb)
> +{
> +       struct virtio_video_resource_detach_backing *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req_resp
> +               (vv, &virtio_video_req_detach_backing_cb, &vbuf, sizeof(*req_p),
> +                sizeof(struct virtio_video_ctrl_hdr), NULL);
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_RESOURCE_DETACH_BACKING);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->resource_id = cpu_to_le32(virtio_vb->resource_id);
> +       vbuf->priv = virtio_vb;
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +static void
> +virtio_video_req_queue_clear_cb(struct virtio_video *vv,
> +                               struct virtio_video_vbuffer *vbuf)
> +{
> +       struct virtio_video_stream *stream = vbuf->priv;
> +       struct virtio_video_queue_clear *req_p =
> +               (struct virtio_video_queue_clear *)vbuf->buf;
> +
> +       if (le32_to_cpu(req_p->pin_type) == VIRTIO_VIDEO_PIN_INPUT)
> +               stream->src_cleared = true;
> +       else
> +               stream->dst_cleared = true;
> +
> +       wake_up(&vv->wq);
> +}
> +
> +int virtio_video_req_queue_clear(struct virtio_video *vv, uint32_t function_id,
> +                                struct virtio_video_stream *stream,
> +                                bool is_in)
> +{
> +       struct virtio_video_queue_clear *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req_resp
> +               (vv, &virtio_video_req_queue_clear_cb, &vbuf, sizeof(*req_p),
> +                sizeof(struct virtio_video_ctrl_hdr), NULL);
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_QUEUE_CLEAR);
> +       req_p->hdr.stream_id = cpu_to_le32(stream->stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->pin_type = cpu_to_le32(is_in ? VIRTIO_VIDEO_PIN_INPUT :
> +                                     VIRTIO_VIDEO_PIN_OUTPUT);
> +
> +       vbuf->priv = stream;
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +static void
> +virtio_video_req_funcs_cb(struct virtio_video *vv,
> +                         struct virtio_video_vbuffer *vbuf)
> +{
> +       bool *got_resp_p = vbuf->priv;
> +       *got_resp_p = true;
> +       wake_up(&vv->wq);
> +}
> +
> +int virtio_video_req_funcs(struct virtio_video *vv, void *resp_buf,
> +                          size_t resp_size)
> +{
> +       struct virtio_video_get_functions *req_p = NULL;
> +       struct virtio_video_vbuffer *vbuf = NULL;
> +
> +       if (!vv || !resp_buf)
> +               return -1;
> +
> +       req_p = virtio_video_alloc_req_resp(vv, &virtio_video_req_funcs_cb,
> +                                           &vbuf, sizeof(*req_p), resp_size,
> +                                           resp_buf);
> +       if (IS_ERR(req_p))
> +               return -1;
> +
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_GET_FUNCS);
> +
> +       vbuf->priv = &vv->got_funcs;
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +static void
> +virtio_video_req_get_params_cb(struct virtio_video *vv,
> +                              struct virtio_video_vbuffer *vbuf)
> +{
> +       int i;
> +       struct virtio_video_get_params_resp *resp =
> +               (struct virtio_video_get_params_resp *)vbuf->resp_buf;
> +       struct virtio_video_params *params = &resp->params;
> +       struct virtio_video_stream *stream = vbuf->priv;
> +       enum virtio_video_pin_type pin_type;
> +       enum virtio_video_scope_type scope;
> +       struct video_format_info *format_info = NULL;
> +       struct virtio_video_device *vvd  = NULL;
> +
> +       pin_type = le32_to_cpu(params->pin_type);
> +       scope = le32_to_cpu(params->scope);
> +
> +       vvd = to_virtio_vd(stream->video_dev);
> +       if (!vvd) {
> +               v4l2_warn(&vv->v4l2_dev, "no video device found\n");
> +               return;
> +       }
> +
> +       if (scope == VIRTIO_VIDEO_SCOPE_STREAM) {
> +               if (pin_type == VIRTIO_VIDEO_PIN_INPUT)
> +                       format_info = &stream->in_info;
> +               else
> +                       format_info = &stream->out_info;
> +       } else {
> +               if (pin_type == VIRTIO_VIDEO_PIN_INPUT)
> +                       format_info = &vvd->in_info;
> +               else
> +                       format_info = &vvd->out_info;
> +       }
> +
> +       if (!format_info)
> +               return;
> +
> +       format_info->frame_rate = le32_to_cpu(params->frame_rate);
> +       format_info->frame_width = le32_to_cpu(params->frame_width);
> +       format_info->frame_height = le32_to_cpu(params->frame_height);
> +       format_info->min_buffers = le32_to_cpu(params->min_buffers);
> +       format_info->fourcc_format = virtio_video_format_to_v4l2(
> +                        le32_to_cpu(params->pixel_format));
> +
> +       format_info->num_planes = le32_to_cpu(params->num_planes);
> +       for (i = 0; i < le32_to_cpu(params->num_planes); i++) {
> +               struct virtio_video_plane_format *plane_formats =
> +                                                &params->plane_formats[i];
> +               struct video_plane_format *plane_format =
> +                                                &format_info->plane_format[i];
> +
> +               plane_format->channel = le32_to_cpu(plane_formats->channel);
> +               plane_format->plane_size =
> +                                le32_to_cpu(plane_formats->plane_size);
> +               plane_format->stride = le32_to_cpu(plane_formats->stride);
> +               plane_format->padding = le32_to_cpu(plane_formats->padding);
> +       }
> +
> +       format_info->is_updated = true;
> +       wake_up(&vv->wq);
> +}
> +
> +int virtio_video_req_get_params(struct virtio_video *vv, uint32_t function_id,
> +                               enum video_pin_type pin_type,
> +                               enum video_params_scope params_scope,
> +                               struct virtio_video_stream *stream)
> +{
> +       int ret;
> +       struct virtio_video_get_params *req_p = NULL;
> +       struct virtio_video_vbuffer *vbuf = NULL;
> +       struct virtio_video_get_params_resp *resp_p;
> +       struct video_format_info *format_info = NULL;
> +       size_t resp_size = sizeof(struct virtio_video_get_params_resp);
> +       struct virtio_video_device *vvd  = NULL;
> +
> +       if (!vv || !stream)
> +               return -1;
> +
> +       req_p = virtio_video_alloc_req_resp(vv,
> +                                       &virtio_video_req_get_params_cb,
> +                                       &vbuf, sizeof(*req_p), resp_size,
> +                                       NULL);
> +
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_GET_PARAMS);
> +       req_p->hdr.stream_id = cpu_to_le32(stream->stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->pin_type =
> +               cpu_to_le32(pin_type == VIDEO_PIN_TYPE_INPUT ?
> +                           VIRTIO_VIDEO_PIN_INPUT : VIRTIO_VIDEO_PIN_OUTPUT);
> +       req_p->scope =
> +               cpu_to_le32(params_scope == VIDEO_PARAMS_SCOPE_DEVICE ?
> +                           VIRTIO_VIDEO_SCOPE_GLOBAL :
> +                           VIRTIO_VIDEO_SCOPE_STREAM);
> +       resp_p = (struct virtio_video_get_params_resp *)vbuf->resp_buf;
> +       memset(resp_p, 0, sizeof(*resp_p));
> +
> +       if (req_p->scope == VIRTIO_VIDEO_SCOPE_STREAM) {
> +               if (req_p->pin_type == VIRTIO_VIDEO_PIN_INPUT)
> +                       format_info = &stream->in_info;
> +               else
> +                       format_info = &stream->out_info;
> +       } else {
> +               vvd = to_virtio_vd(stream->video_dev);
> +               if (!vvd) {
> +                       v4l2_warn(&vv->v4l2_dev, "no video device found\n");
> +                       return -1;
> +               }
> +               if (req_p->pin_type == VIRTIO_VIDEO_PIN_INPUT)
> +                       format_info = &vvd->in_info;
> +               else
> +                       format_info = &vvd->out_info;
> +       }
> +
> +       if (!format_info)
> +               return -1;
> +
> +       format_info->is_updated = false;
> +
> +       vbuf->priv = stream;
> +       ret = virtio_video_queue_ctrl_buffer(vv, vbuf);
> +       if (ret)
> +               return ret;
> +
> +       ret = wait_event_timeout(vv->wq,
> +                                format_info->is_updated, 5 * HZ);
> +       if (ret == 0) {
> +               v4l2_err(&vv->v4l2_dev, "timed out waiting for get_params\n");
> +               return -1;
> +       }
> +       return 0;
> +}
> +
> +int
> +virtio_video_req_set_params(struct virtio_video *vv, uint32_t function_id,
> +                           struct video_format_info *format_info,
> +                           enum video_pin_type pin_type,
> +                           enum video_params_scope params_scope,
> +                           struct virtio_video_stream *stream)
> +{
> +       int i;
> +       struct virtio_video_set_params *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_SET_PARAMS);
> +       req_p->hdr.stream_id = cpu_to_le32(stream->stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->params.pin_type =
> +               cpu_to_le32(pin_type == VIDEO_PIN_TYPE_INPUT ?
> +                           VIRTIO_VIDEO_PIN_INPUT : VIRTIO_VIDEO_PIN_OUTPUT);
> +       req_p->params.scope =
> +               cpu_to_le32(params_scope == VIDEO_PARAMS_SCOPE_DEVICE ?
> +                           VIRTIO_VIDEO_SCOPE_GLOBAL :
> +                           VIRTIO_VIDEO_SCOPE_STREAM);
> +       req_p->params.frame_rate = cpu_to_le32(format_info->frame_rate);
> +       req_p->params.frame_width = cpu_to_le32(format_info->frame_width);
> +       req_p->params.frame_height =
> +                                cpu_to_le32(format_info->frame_height);
> +       req_p->params.pixel_format = virtio_video_v4l2_fourcc_to_virtio(
> +                                cpu_to_le32(format_info->fourcc_format));
> +       req_p->params.min_buffers = cpu_to_le32(format_info->min_buffers);
> +       req_p->params.num_planes = cpu_to_le32(format_info->num_planes);
> +
> +       for (i = 0; i < format_info->num_planes; i++) {
> +               struct virtio_video_plane_format *plane_formats =
> +                       &req_p->params.plane_formats[i];
> +               struct video_plane_format *plane_format =
> +                       &format_info->plane_format[i];
> +               plane_formats->channel = cpu_to_le32(plane_format->channel);
> +               plane_formats->plane_size =
> +                                cpu_to_le32(plane_format->plane_size);
> +               plane_formats->stride = cpu_to_le32(plane_format->stride);
> +               plane_formats->padding = cpu_to_le32(plane_format->padding);
> +       }
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> +int virtio_video_req_set_control(struct virtio_video *vv,
> +                                uint32_t function_id, uint32_t stream_id,
> +                                uint32_t control, uint32_t val)
> +{
> +       struct virtio_video_set_control *req_p;
> +       struct virtio_video_vbuffer *vbuf;
> +
> +       req_p = virtio_video_alloc_req(vv, &vbuf, sizeof(*req_p));
> +       if (IS_ERR(req_p))
> +               return PTR_ERR(req_p);
> +       memset(req_p, 0, sizeof(*req_p));
> +
> +       req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_T_SET_CONTROL);
> +       req_p->hdr.stream_id = cpu_to_le32(stream_id);
> +       req_p->hdr.function_id = cpu_to_le32(function_id);
> +       req_p->type = virtio_video_v4l2_control_to_virtio(cpu_to_le32(control));
> +       req_p->val = cpu_to_le64(val);
> +
> +       return virtio_video_queue_ctrl_buffer(vv, vbuf);
> +}
> +
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index 585e07b27333..8be72c11ae22 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -47,4 +47,6 @@
>  #define VIRTIO_ID_FS           26 /* virtio filesystem */
>  #define VIRTIO_ID_PMEM         27 /* virtio pmem */
>
> +#define VIRTIO_ID_VIDEO        29 /* virtio video  */
> +
>  #endif /* _LINUX_VIRTIO_IDS_H */
> diff --git a/include/uapi/linux/virtio_video.h b/include/uapi/linux/virtio_video.h
> new file mode 100644
> index 000000000000..c8122f03bfeb
> --- /dev/null
> +++ b/include/uapi/linux/virtio_video.h
> @@ -0,0 +1,456 @@
> +/* SPDX-License-Identifier: BSD-3-Clause */
> +/*
> + * Virtio Video Device
> + *
> + * This header is BSD licensed so anyone can use the definitions
> + * to implement compatible drivers/servers:
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + *    notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + *    notice, this list of conditions and the following disclaimer in the
> + *    documentation and/or other materials provided with the distribution.
> + * 3. Neither the name of IBM nor the names of its contributors
> + *    may be used to endorse or promote products derived from this software
> + *    without specific prior written permission.
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
> + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL IBM OR
> + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
> + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
> + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
> + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
> + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> + * SUCH DAMAGE.
> + */
> +
> +#ifndef _UAPI_VIRTIO_VIDEO_H
> +#define        _UAPI_VIRTIO_VIDEO_H
> +
> +#include <linux/types.h>
> +#include <linux/virtio_config.h>
> +
> +/* Maximum number of planes associated with a resource. */
> +#define VIRTIO_VIDEO_MAX_PLANES 8
> +
> +/* Maximum size of get params response */
> +#define VIRTIO_VIDEO_MAXSIZE_GET_PARAMS 1024
> +
> +enum virtio_video_func_type {
> +       VIRTIO_VIDEO_FUNC_UNDEFINED = 0,
> +
> +       VIRTIO_VIDEO_FUNC_ENCODER = 0x0100,
> +       VIRTIO_VIDEO_FUNC_DECODER,
> +       VIRTIO_VIDEO_FUNC_PROCESSOR,
> +       VIRTIO_VIDEO_FUNC_CAPTURE,
> +       VIRTIO_VIDEO_FUNC_OUTPUT,
> +};
> +
> +enum virtio_video_ctrl_type {
> +       VIRTIO_VIDEO_CTRL_UNDEFINED = 0,
> +
> +       /* request */
> +       VIRTIO_VIDEO_T_GET_FUNCS = 0x0100,
> +       VIRTIO_VIDEO_T_STREAM_CREATE,
> +       VIRTIO_VIDEO_T_STREAM_DESTROY,
> +       VIRTIO_VIDEO_T_STREAM_START,
> +       VIRTIO_VIDEO_T_STREAM_STOP,
> +       VIRTIO_VIDEO_T_STREAM_DRAIN,
> +       VIRTIO_VIDEO_T_RESOURCE_CREATE,
> +       VIRTIO_VIDEO_T_RESOURCE_DESTROY,
> +       VIRTIO_VIDEO_T_RESOURCE_ATTACH_BACKING,
> +       VIRTIO_VIDEO_T_RESOURCE_DETACH_BACKING,
> +       VIRTIO_VIDEO_T_RESOURCE_QUEUE,
> +       VIRTIO_VIDEO_T_QUEUE_CLEAR,
> +       VIRTIO_VIDEO_T_SET_PARAMS,
> +       VIRTIO_VIDEO_T_GET_PARAMS,
> +       VIRTIO_VIDEO_T_SET_CONTROL,
> +
> +       /* response */
> +       VIRTIO_VIDEO_S_OK = 0x0200,
> +       VIRTIO_VIDEO_S_OK_RESOURCE_QUEUE,
> +       VIRTIO_VIDEO_S_OK_GET_PARAMS,
> +
> +       VIRTIO_VIDEO_S_ERR_UNSPEC = 0x0300,
> +       VIRTIO_VIDEO_S_ERR_OUT_OF_MEMORY,
> +       VIRTIO_VIDEO_S_ERR_INVALID_FUNCTION_ID,
> +       VIRTIO_VIDEO_S_ERR_INVALID_RESOURCE_ID,
> +       VIRTIO_VIDEO_S_ERR_INVALID_STREAM_ID,
> +       VIRTIO_VIDEO_S_ERR_INVALID_PARAMETER,
> +};
> +
> +enum virtio_video_event_type {
> +       VIRTIO_VIDEO_EVENT_T_UNDEFINED = 0,
> +
> +       VIRTIO_VIDEO_EVENT_T_RESOLUTION_CHANGED = 0x0100,
> +       VIRTIO_VIDEO_EVENT_T_CONFIGURED,
> +};
> +
> +enum virtio_video_buffer_flag {
> +       VIRTIO_VIDEO_BUFFER_F_ERR       = 0x0001,
> +       VIRTIO_VIDEO_BUFFER_F_EOS       = 0x0002,
> +       VIRTIO_VIDEO_BUFFER_IFRAME      = 0x0004,
> +       VIRTIO_VIDEO_BUFFER_PFRAME      = 0x0008,
> +       VIRTIO_VIDEO_BUFFER_BFRAME      = 0x0010,
> +};
> +
> +enum virtio_video_desc_type {
> +       VIRTIO_VIDEO_DESC_UNDEFINED = 0,
> +
> +       VIRTIO_VIDEO_DESC_FRAME_RATE = 0x0100,
> +       VIRTIO_VIDEO_DESC_FRAME_SIZE,
> +       VIRTIO_VIDEO_DESC_PIX_FORMAT,
> +       VIRTIO_VIDEO_DESC_PLANE_FORMAT,
> +       VIRTIO_VIDEO_DESC_CONTROL,
> +       VIRTIO_VIDEO_DESC_EXTRAS,
> +       VIRTIO_VIDEO_DESC_CAP,
> +       VIRTIO_VIDEO_DESC_FUNC,
> +       VIRTIO_VIDEO_DESC_PARAMS,
> +       VIRTIO_VIDEO_DESC_DEFAULTS,
> +};
> +
> +enum virtio_video_pin_type {
> +       VIRTIO_VIDEO_PIN_UNDEFINED = 0,
> +
> +       VIRTIO_VIDEO_PIN_INPUT = 0x0100,
> +       VIRTIO_VIDEO_PIN_OUTPUT,
> +};
> +
> +enum virtio_video_channel_type {
> +       VIRTIO_VIDEO_CHANNEL_UNDEFINED = 0,
> +
> +       VIRTIO_VIDEO_CHANNEL_Y = 0x0100,
> +       VIRTIO_VIDEO_CHANNEL_U,
> +       VIRTIO_VIDEO_CHANNEL_V,
> +       VIRTIO_VIDEO_CHANNEL_UV,
> +       VIRTIO_VIDEO_CHANNEL_VU,
> +       VIRTIO_VIDEO_CHANNEL_YUV,
> +       VIRTIO_VIDEO_CHANNEL_YVU,
> +       VIRTIO_VIDEO_CHANNEL_BGR,
> +       VIRTIO_VIDEO_CHANNEL_BGRX,
> +};
> +
> +enum virtio_video_control_type {
> +       VIRTIO_VIDEO_CONTROL_UNDEFINED = 0,
> +
> +       VIRTIO_VIDEO_CONTROL_BITRATE = 0x100,
> +       VIRTIO_VIDEO_CONTROL_PROFILE,
> +       VIRTIO_VIDEO_CONTROL_LEVEL,
> +};
> +
> +enum virtio_video_scope_type {
> +       VIRTIO_VIDEO_SCOPE_UNDEFINED = 0,
> +
> +       VIRTIO_VIDEO_SCOPE_GLOBAL = 0x0100,
> +       VIRTIO_VIDEO_SCOPE_STREAM,
> +};
> +
> +enum virtio_video_cap_type {
> +       VIRTIO_VIDEO_CAP_UNDEFINED = 0,
> +
> +       VIRTIO_VIDEO_CAP_PIN_FORMATS = 0x0100,
> +       VIRTIO_VIDEO_CAP_CONTROL,
> +};
> +
> +struct virtio_video_ctrl_hdr {
> +       __le32 type;
> +       __le32 stream_id;
> +       __le32 function_id;
> +       __u8 padding[4];
> +};
> +
> +struct virtio_video_desc {
> +       __le32 type; /* One of VIRTIO_VIDEO_DESC_* types */
> +       __le16 length;
> +       __u8 padding[2];
> +};
> +
> +struct virtio_video_frame_rate {
> +       struct virtio_video_desc desc;
> +       __le32 min_rate;
> +       __le32 max_rate;
> +       __le32 step;
> +       __u8 padding[4];
> +};
> +
> +struct virtio_video_frame_size {
> +       struct virtio_video_desc desc;
> +       __le32 min_width;
> +       __le32 max_width;
> +       __le32 step_width;
> +       __le32 min_height;
> +       __le32 max_height;
> +       __le32 step_height;
> +       __le32 num_rates;
> +       __u8 padding[4];
> +       /* Followed by struct virtio_video_frame_rate frame_rates[]; */
> +};
> +
> +struct virtio_video_pix_format {
> +       struct virtio_video_desc desc;
> +       __le32 pixel_format;
> +       __le32 num_sizes;
> +       /* Followed by struct virtio_video_frame_size frame_sizes[]; */
> +};
> +
> +struct virtio_video_frame_format {
> +       __le32 pin_type; /* One of VIRTIO_VIDEO_PIN_* types */
> +       __le32 num_formats;
> +       /* Followed by struct virtio_video_pix_format pix_formats[]; */
> +};
> +
> +struct virtio_video_extras {
> +       struct virtio_video_desc desc;
> +};
> +
> +struct virtio_video_plane_format {
> +       struct virtio_video_desc desc;
> +       __le32 channel; /* One of VIRTIO_VIDEO_CHANNEL_* types */
> +       __le32 plane_size;
> +       __le32 stride;
> +       __le32 padding;
> +};
> +
> +struct virtio_video_control {
> +       struct virtio_video_desc desc;
> +       __le32 control_type;  /* One of VIRTO_VIDEO_CONTROL_* types */
> +       __le32 step;
> +       __le64 min;
> +       __le64 max;
> +       __le64 def;
> +};
> +
> +struct virtio_video_controls {
> +       __le32 num_controls;
> +       __u8 padding[4];
> +       /* Followed by struct virtio_video_control control[]; */
> +};
> +
> +struct virtio_video_capability {
> +       struct virtio_video_desc desc;
> +       __le32 cap_type; /* One of VIRTIO_VIDEO_CAP_* types */
> +       __le32 cap_id;
> +       union {
> +               struct virtio_video_frame_format frame_format;
> +               struct virtio_video_controls controls;
> +       } u;
> +};
> +
> +struct virtio_video_params {
> +       struct virtio_video_desc desc;
> +       __le32 pin_type; /* One of VIRTIO_VIDEO_PIN_* types */
> +       __le32 scope; /* One of VIRTIO_VIDEO_SCOPE_* types */
> +       __le32 frame_rate;
> +       __le32 frame_width;
> +       __le32 frame_height;
> +       __le32 pixel_format;
> +       __le32 min_buffers;
> +       __le32 num_planes;
> +       struct virtio_video_plane_format plane_formats[VIRTIO_VIDEO_MAX_PLANES];
> +       struct virtio_video_extras extra;
> +
> +};
> +
> +struct virtio_video_function {
> +       struct virtio_video_desc desc;
> +       __le32 function_type; /* One of VIRTIO_VIDEO_FUNC_* types */
> +       __le32 function_id;
> +       struct virtio_video_params in_params;
> +       struct virtio_video_params out_params;
> +       __le32 num_caps;
> +       __u8 padding[4];
> +       /* Followed by struct virtio_video_capability video_caps[]; */
> +};
> +
> +struct virtio_video_config {
> +       __u32 num_functions;
> +       __u32 total_functions_size;
> +};
> +
> +struct virtio_video_mem_entry {
> +       __le64 addr;
> +       __le32 length;
> +       __u8 padding[4];
> +};
> +
> +struct virtio_video_event {
> +       __le32 event_type;
> +       __le32 function_id;
> +       __le32 stream_id;
> +       __u8 padding[4];
> +};
> +
> +/* VIRTIO_VIDEO_T_GET_FUNCS */
> +struct virtio_video_get_functions {
> +       struct virtio_video_ctrl_hdr hdr;
> +};
> +
> +/* VIRTIO_VIDEO_T_STREAM_CREATE */
> +struct virtio_video_stream_create {
> +       struct virtio_video_ctrl_hdr hdr;
> +       char debug_name[64];
> +};
> +
> +/* VIRTIO_VIDEO_T_STREAM_DESTROY */
> +struct virtio_video_stream_destroy {
> +       struct virtio_video_ctrl_hdr hdr;
> +};
> +
> +/* VIRTIO_VIDEO_T_STREAM_START */
> +struct virtio_video_stream_start {
> +       struct virtio_video_ctrl_hdr hdr;
> +};
> +
> +/* VIRTIO_VIDEO_T_STREAM_STOP */
> +struct virtio_video_stream_stop {
> +       struct virtio_video_ctrl_hdr hdr;
> +};
> +
> +/* VIRTIO_VIDEO_T_STREAM_DRAIN */
> +struct virtio_video_stream_drain {
> +       struct virtio_video_ctrl_hdr hdr;
> +};
> +
> +/* VIRTIO_VIDEO_T_RESOURCE_CREATE */
> +struct virtio_video_resource_create {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le32 resource_id;
> +       __u8 padding[4];
> +};
> +
> +/* VIRTIO_VIDEO_T_RESOURCE_DESTROY */
> +struct virtio_video_resource_destroy {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le32 resource_id;
> +       __u8 padding[4];
> +};
> +
> +/* VIRTIO_VIDEO_T_RESOURCE_ATTACH_BACKING */
> +struct virtio_video_resource_attach_backing {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le32 resource_id;
> +       __le32 nr_entries;
> +};
> +
> +/* VIRTIO_VIDEO_T_RESOURCE_DETACH_BACKING*/
> +struct virtio_video_resource_detach_backing {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le32 resource_id;
> +       __u8 padding[4];
> +};
> +
> +/* VIRTIO_VIDEO_T_RESOURCE_QUEUE */
> +struct virtio_video_resource_queue {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le64 timestamp;
> +       __le32 resource_id;
> +       __le32 pin_type;
> +       __le32 data_size[VIRTIO_VIDEO_MAX_PLANES];
> +       __u8 nr_data_size;
> +       __u8 padding[7];
> +};
> +
> +/* VIRTIO_VIDEO_QUEUE_CLEAR */
> +struct virtio_video_queue_clear {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le32 pin_type;
> +       __u8 padding[4];
> +};
> +
> +/* VIRTIO_VIDEO_T_SET_PARAMS */
> +struct virtio_video_set_params {
> +       struct virtio_video_ctrl_hdr hdr;
> +       struct virtio_video_params params;
> +};
> +
> +/* VIRTIO_VIDEO_T_GET_PARAMS */
> +struct virtio_video_get_params {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le32 pin_type; /* One of VIRTIO_VIDEO_PIN_* types */
> +       __le32 scope; /* One of VIRTIO_VIDEO_SCOPE_* types */
> +};
> +
> +struct virtio_video_resource_queue_resp {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le64 timestamp;
> +       __le32 flags; /* One of VIRTIO_VIDEO_BUFFER_* flags */
> +       __le32 size;  /* Encoded size */
> +};
> +
> +struct virtio_video_get_params_resp {
> +       struct virtio_video_ctrl_hdr hdr;
> +       struct virtio_video_params params;
> +};
> +
> +/* VIRTIO_VIDEO_T_SET_CONTROL */
> +struct virtio_video_set_control {
> +       struct virtio_video_ctrl_hdr hdr;
> +       __le64 val;
> +       __le32 type;  /* One of VIRTO_VIDEO_CONTROL_* types */
> +       __u8 padding[4];
> +};
> +
> +enum virtio_video_pix_format_type {
> +       VIRTIO_VIDEO_PIX_FMT_UNKNOWN = 0,
> +
> +       VIRTIO_VIDEO_PIX_FMT_H264 = 0x100,
> +       VIRTIO_VIDEO_PIX_FMT_NV12,
> +       VIRTIO_VIDEO_PIX_FMT_NV21,
> +       VIRTIO_VIDEO_PIX_FMT_I420,
> +       VIRTIO_VIDEO_PIX_FMT_I422,
> +       VIRTIO_VIDEO_PIX_FMT_XBGR,
> +       VIRTIO_VIDEO_PIX_FMT_H265,
> +       VIRTIO_VIDEO_PIX_FMT_MPEG4,
> +       VIRTIO_VIDEO_PIX_FMT_MPEG2,
> +};
> +
> +enum virtio_video_profile_type {
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_UNDEFINED = 0,
> +
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_BASELINE = 0x100,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_MAIN,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_EXTENDED,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_10,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_422,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_10_INTRA,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_HIGH_444_INTRA,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_CAVLC_444_INTRA,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH_INTRA,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH,
> +       VIRTIO_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH,
> +};
> +
> +enum virtio_video_level_type {
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_UNDEFINED = 0,
> +
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_1_0 = 0x100,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_1B,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_1_1,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_1_2,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_1_3,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_2_0,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_2_1,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_2_2,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_3_0,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_3_1,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_3_2,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_4_0,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_4_1,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_4_2,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_5_0,
> +       VIRTIO_MPEG_VIDEO_H264_LEVEL_5_1,
> +};
> +
> +#endif /* _UAPI_VIRTIO_VIDEO_H */
> --
> 2.24.0
>
>
> ________________________________________
> From: Dmitry Sepp
> Sent: Thursday, December 05, 2019 17:11
> To: linux-media@xxxxxxxxxxxxxxx
> Cc: virtio-dev@xxxxxxxxxxxxxxxxxxxx; kraxel@xxxxxxxxxx; tfiga@xxxxxxxxxxxx; keiichiw@xxxxxxxxxxxx; acourbot@xxxxxxxxxxxx; hverkuil@xxxxxxxxx; posciak@xxxxxxxxxxxx; marcheu@xxxxxxxxxxxx; stevensd@xxxxxxxxxxxx; dgreid@xxxxxxxxxxxx; daniel@xxxxxxxx; egranata@xxxxxxxxxx
> Subject: [RFC] virtio video driver
>
> Hello,
>
> My apologies for the long delay. The driver code is now available and provided
> as a follow-up to the discussion from this thread [1].
>
> The reference Linux kernel 5.4 driver implementation is located here:
> https://github.com/OpenSynergy/linux/tree/virtio-video-draft-v1
>
> The driver is implemented using the V4L2 API. It allocates a v4l2 device for
> each probed virtio device and then creates a video device for each function
> within the respective virtio device. The driver implements the stateful
> decoder interface [2] and the stateful encoder interface (WIP) [3].
>
> The DMA SG memory allocator tries to map buffers right away. As it is not
> always suitable, and some implementations might need just a physical address,
> we had to introduce a set of simple dma ops directly in the driver.
>
> The driver is in the RFC state and currently a bit ahead of the spec that was
> proposed in the discussion mentioned above. On the other hand, the driver
> unfortunately does not yet include changes proposed in the recent comments
> [4]. The driver currently supports encoder and decoder functions. Also, it
> does not fully pass the v4l2-compliance yet, it has been a bit out of the
> focus so far.
>
> Any feedback and contribution would be greatly appreciated.
>
> [1] https://markmail.org/message/gc6h25acct22niut
> [2] https://www.kernel.org/doc/html/v5.4/media/uapi/v4l/dev-decoder.html
> [3] https://hverkuil.home.xs4all.nl/codec-api/uapi/v4l/dev-encoder.html
> [4] https://markmail.org/message/yy67elx2adbivdsp
>
> Best regards,
> Dmitry.
>





[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux