Re: [RFC PATCH] media: pci: Add VirtIO video decoder driver

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

 



Hi everyone,

Gentle ping.

-Keiichi

On Thu, Oct 31, 2019 at 4:11 PM Keiichi Watanabe <keiichiw@xxxxxxxxxxxx> wrote:
>
> This patch adds a VirtIO video decoder driver, which provides a
> functionality of hardware accelerated video decoding in virtual
> machines. This implementation complies with the draft of the
> virtio-vdec specification we recently proposed in virtio-dev ML [1,2].
> PDF version of the draft is available in [3].
>
> This driver provides V4L2 stateful video decoder interface [4].
>
> We tested this driver on a VM in ChromeOS (Google Pixelbook). The
> guest kernel's version is 4.19 and host's version is 4.4. We used
> crosvm [5] as a VMM here. Patches for the host-side virtio-vdec device
> are shown in [6].
> Note that the device doesn't communicate with the host's hardware
> directly. Instead, it uses Chrome's video decoder APIs provided in
> ChromeOS.
>
> As this is still under development, we have following limitations:
> * Dynamic resolution change is not supported.
> * It assumes that both OUTPUT/CAPTURE buffers are allocated as
>   DMA-bufs via virtio-gpu and passed as resource handles exported by
>   [7]. We're discussing a better approach in the threads of [1,7].
> * It assumes that the user pass an integer via vb2_plane's data_offset
>   to use it as |timestamp| in virtio-vdec. So, we needed a local
>   patch [8].
> * Several TODO comments are still remaining.
>
> Any feedback will be really appreciated. Thanks in advance!
>
> [1]: https://markmail.org/message/f2lxoyxltenqcq4t (virtio-dev)
> [2]: https://patchwork.kernel.org/patch/11151995/ (LKML)
> [3]: https://drive.google.com/corp/drive/u/1/folders/1hed-mTVI7dj0M_iab4DTfx5kPMLoeX8R
> [4]: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dev-decoder.html
> [5]: https://chromium.googlesource.com/chromiumos/platform/crosvm/
> [6]: https://chromium-review.googlesource.com/q/hashtag:%22virtio-vdec-device%22
> [7]: https://patchwork.freedesktop.org/patch/330305/
> [8]: https://crrev.com/c/1772842/6
>
> Signed-off-by: Keiichi Watanabe <keiichiw@xxxxxxxxxxxx>
> ---
>  drivers/media/pci/Kconfig                   |    2 +
>  drivers/media/pci/Makefile                  |    1 +
>  drivers/media/pci/virtio-vdec/Kconfig       |   11 +
>  drivers/media/pci/virtio-vdec/Makefile      |    2 +
>  drivers/media/pci/virtio-vdec/virtio_vdec.c | 2240 +++++++++++++++++++
>  include/uapi/linux/virtio_ids.h             |    1 +
>  include/uapi/linux/virtio_vdec.h            |  224 ++
>  7 files changed, 2481 insertions(+)
>  create mode 100644 drivers/media/pci/virtio-vdec/Kconfig
>  create mode 100644 drivers/media/pci/virtio-vdec/Makefile
>  create mode 100644 drivers/media/pci/virtio-vdec/virtio_vdec.c
>  create mode 100644 include/uapi/linux/virtio_vdec.h
>
> diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
> index dcb3719f440e..b70d68f7cae1 100644
> --- a/drivers/media/pci/Kconfig
> +++ b/drivers/media/pci/Kconfig
> @@ -9,6 +9,8 @@ menuconfig MEDIA_PCI_SUPPORT
>
>  if MEDIA_PCI_SUPPORT
>
> +source "drivers/media/pci/virtio-vdec/Kconfig"
> +
>  if MEDIA_CAMERA_SUPPORT
>         comment "Media capture support"
>  source "drivers/media/pci/meye/Kconfig"
> diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
> index 984fa247096d..f24b32ddb627 100644
> --- a/drivers/media/pci/Makefile
> +++ b/drivers/media/pci/Makefile
> @@ -33,3 +33,4 @@ obj-$(CONFIG_STA2X11_VIP) += sta2x11/
>  obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/
>  obj-$(CONFIG_VIDEO_COBALT) += cobalt/
>  obj-$(CONFIG_VIDEO_TW5864) += tw5864/
> +obj-$(CONFIG_VIRTIO_VDEC) += virtio-vdec/
> diff --git a/drivers/media/pci/virtio-vdec/Kconfig b/drivers/media/pci/virtio-vdec/Kconfig
> new file mode 100644
> index 000000000000..6cfef7fc256f
> --- /dev/null
> +++ b/drivers/media/pci/virtio-vdec/Kconfig
> @@ -0,0 +1,11 @@
> +config VIRTIO_VDEC
> +       bool "Virtio video decoder"
> +       depends on VIRTIO_PCI && VIDEO_DEV && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
> +       select VIDEOBUF2_VMALLOC
> +       select VIDEOBUF2_DMA_SG
> +       select VIDEOBUF2_V4L2
> +       select V4L2_MEM2MEM_DEV
> +       help
> +          This driver supports video decoding using host-side devices.
> +
> +          If unsure, say 'N'.
> diff --git a/drivers/media/pci/virtio-vdec/Makefile b/drivers/media/pci/virtio-vdec/Makefile
> new file mode 100644
> index 000000000000..972c0c200f13
> --- /dev/null
> +++ b/drivers/media/pci/virtio-vdec/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0
> +obj-$(CONFIG_VIRTIO_VDEC) += virtio_vdec.o
> diff --git a/drivers/media/pci/virtio-vdec/virtio_vdec.c b/drivers/media/pci/virtio-vdec/virtio_vdec.c
> new file mode 100644
> index 000000000000..f93c5c19845a
> --- /dev/null
> +++ b/drivers/media/pci/virtio-vdec/virtio_vdec.c
> @@ -0,0 +1,2240 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * VirtIO Video Decoder
> + *
> + * Copyright 2019 Google LLC.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/dma-buf.h>
> +#include <linux/fs.h>
> +#include <linux/module.h>
> +#include <linux/net.h>
> +#include <linux/pci_ids.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/videodev2.h>
> +#include <linux/virtio_config.h>
> +#include <linux/virtio_ids.h>
> +#include <linux/virtio_pci.h>
> +#include <linux/virtio_ring.h>
> +#include <linux/virtio_vdec.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mem2mem.h>
> +#include <media/videobuf2-dma-sg.h>
> +#include <media/videobuf2-vmalloc.h>
> +#define VIRTIO_VDEC_NAME "virtio_vdec"
> +
> +#define DST_QUEUE_OFF_BASE (1 << 30)
> +
> +#define vdec_debug(virt_vdev, fmt, args...)                                    \
> +       dev_dbg(&virt_vdev->vid_dev.dev, "%s:%d " fmt, __func__, __LINE__,     \
> +               ##args)
> +
> +#define vdec_err(virt_vdev, fmt, args...)                                      \
> +       dev_err(&virt_vdev->vid_dev.dev, "%s:%d " fmt, __func__, __LINE__,     \
> +               ##args)
> +
> +/**
> + * struct virtvdec_dev - VirtIO video decode driver data.
> + *
> + * @virt_dev:          Reference to a virtio device struct.
> + * @vid_dev:           Video device struct.
> + * @v4l2_dev:          V4L2 device struct.
> + * @lock:              Lock for the video device and vb2 queues.
> + * @src_fmts:          Array of supported bitstream formats.
> + * @dst_fmts:          Array of supported frame formats.
> + * @vq_in:             virtqueue for inputs (i.e. host requests).
> + * @vq_in_lock:        Lock for @vq_in.
> + * @vq_in_work:        Delayed work for host requests.
> + * @vq_out:            virtqueue for outputs (i.e. guest requests).
> + * @vq_out_lock:       Lock for @vq_out.
> + * @vq_out_work:       Delayed work for guest requests.
> + * @out_waitq:         Wait queue for guest requests.
> + * @subscriptions:     List head of all subscriptions for host request.
> + * @contexts:          List head of all active contexts.
> + * @num_ctxs:          Number of contexts created so far.
> + */
> +struct virtvdec_dev {
> +       struct virtio_device *virt_dev;
> +       struct video_device vid_dev;
> +       struct v4l2_device v4l2_dev;
> +
> +       struct mutex lock;
> +
> +       struct virtio_vdec_format_desc src_fmts[VIRTIO_VDEC_NUM_FORMATS];
> +       struct virtio_vdec_format_desc dst_fmts[VIRTIO_VDEC_NUM_FORMATS];
> +
> +       struct virtqueue *vq_in;
> +       struct mutex vq_in_lock;
> +       struct work_struct vq_in_work;
> +
> +       struct virtqueue *vq_out;
> +       struct mutex vq_out_lock;
> +       struct work_struct vq_out_work;
> +       wait_queue_head_t out_waitq;
> +
> +       struct list_head subscriptions;
> +       struct list_head contexts;
> +
> +       int num_ctxs;
> +};
> +
> +/**
> + * enum virtvdec_state - State of V4L2 stateful video decoder.
> + */
> +enum virtvdec_state {
> +       VIRTVDEC_STATE_INITIALIZING = 0,
> +       VIRTVDEC_STATE_CAPTURE_SETUP,
> +       VIRTVDEC_STATE_STOPPED,
> +       VIRTVDEC_STATE_DECODING,
> +       VIRTVDEC_STATE_DRAINING,
> +       VIRTVDEC_STATE_DRAINED,
> +       VIRTVDEC_STATE_SEEK,
> +       /* TODO(keiichiw): Dynamic resolution change is unsupported */
> +       /* VIRTVDEC_STATE_DRC, */
> +};
> +
> +/**
> + * struct virtvdec_ctx - Context (instance) private data.
> + *
> + * @list:              List head to attach to the driver data.
> + * @id:                Instance ID.
> + * @fh:                V4L2 file handler,
> + * @dev:               Driver data to which the context belongs
> + * @hdl:               Control handler.
> + * @state:             Decoder state.
> + * @ctrl_handler:      Control handler used to register controls.
> + * @src_fmt:           V4L2 pixel format of active source format.
> + * @vdec_src_fmt:      virtio-vdec pixel format of active source format.
> + * @dst_fmt:           V4L2 pixel format of active destination format.
> + * @vdec_dst_fmt:      virtio-vdec pixel format of active destination format.
> + * @crop:              Crop rectangle.
> + * @min_capbufs:       Minimum number of CAPTURE buffers.
> + * @max_capbufs:       Maximum number of CAPTURE buffers.
> + * @src_vbq:           Videobuf2 source queue.
> + * @src_queue:         Internal source buffer queue.
> + * @dst_vbq:           Videobuf2 destination queue.
> + * @dst_queue:         Internal destination buffer queue.
> + * @has_host_session:  Whether it has corresponding host-side session.
> + * @outbuf_handles:    List of registered resource handles for OUTPUT buffers.
> + * @capbuf_handles:    List of registered resource handles for CAPTURE buffers.
> + * @outbuf_handle_map: Mapping from an index to a resource handle for OUTPUT
> + *                     buffers.
> + * @capbuf_handle_map: Mapping from an index to a resource handle for CAPTURE
> + *                     buffers.
> + * @outbuf_vb2_map:    Mapping from an index to a vb2 buffer for OUTPUT
> + *                     buffers.
> + * @capbuf_vb2_map:    Mapping from an index to a vb2 buffer for CAPTURE
> + *                     buffers.
> + * @stored_capbuf:     Videobuf2 destination buffer used to notify DRAINED
> + *                     by specifying V4L2_BUF_FLAG_LAST;
> + * @waiting_drained:   Whether it's waiting for a DRAINED event coming from the
> + *                     host.
> + */
> +struct virtvdec_ctx {
> +       struct list_head list;
> +       int id;
> +       struct v4l2_fh fh;
> +       struct virtvdec_dev *dev;
> +       struct v4l2_ctrl_handler hdl;
> +       enum virtvdec_state state;
> +       struct v4l2_ctrl_handler ctrl_handler;
> +
> +       struct v4l2_pix_format_mplane src_fmt;
> +       const struct virtio_vdec_format_desc *vdec_src_fmt;
> +       struct v4l2_pix_format_mplane dst_fmt;
> +       struct virtio_vdec_format_desc *vdec_dst_fmt;
> +
> +       struct v4l2_crop crop;
> +       u32 min_capbufs;
> +       u32 max_capbufs;
> +
> +       struct vb2_queue src_vbq;
> +       struct list_head src_queue;
> +       struct vb2_queue dst_vbq;
> +       struct list_head dst_queue;
> +       bool has_host_session;
> +
> +       struct list_head outbuf_handles;
> +       struct list_head capbuf_handles;
> +
> +#define VIRTIO_VDEC_MAX_BUFFER_COUNT 32
> +       u32 outbuf_handle_map[VIRTIO_VDEC_MAX_BUFFER_COUNT];
> +       u32 capbuf_handle_map[VIRTIO_VDEC_MAX_BUFFER_COUNT];
> +       struct vb2_buffer *outbuf_vb2_map[VIRTIO_VDEC_MAX_BUFFER_COUNT];
> +       struct vb2_buffer *capbuf_vb2_map[VIRTIO_VDEC_MAX_BUFFER_COUNT];
> +
> +       /*
> +        * Store one CAPTURE buffer to notify DRAINED to the user.
> +        *
> +        * @stored_capbuf won't be used for decoded frames.
> +        * Instead, when VIRTIO_VDEC_HOST_REQ_DRAINED arrives, vb2_buffer_done
> +        * is called for this buffer with V4L2_BUF_FLAG_LAST.
> +        *
> +        * Note: the value of VIRTIO_VDEC_DRAINED_NOTIFICATION_BUFFER_INDEX must
> +        * be 0 since there is no guarantee how many buffers the client will
> +        * enqueue.
> +        */
> +#define VIRTIO_VDEC_DRAINED_NOTIFICATION_BUFFER_INDEX 0
> +       struct vb2_buffer *stored_capbuf;
> +       bool waiting_drained;
> +};
> +
> +struct virtvdec_buf {
> +       struct vb2_v4l2_buffer vb;
> +       struct list_head list;
> +};
> +
> +static inline struct virtvdec_buf *
> +virtvdec_vb2_to_virtvdec_buf(const struct vb2_buffer *buf)
> +{
> +       return container_of(to_vb2_v4l2_buffer(buf), struct virtvdec_buf, vb);
> +}
> +
> +static struct virtvdec_ctx *virtvdec_get_context(struct virtvdec_dev *dev,
> +                                                int id)
> +{
> +       struct virtvdec_ctx *ctx, *next;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       list_for_each_entry_safe(ctx, next, &dev->contexts, list)
> +               if (ctx->id == id)
> +                       return ctx;
> +
> +       return NULL;
> +}
> +
> +static void virtvdec_del_context(struct virtvdec_dev *dev, const int id)
> +{
> +       struct virtvdec_ctx *ctx, *next;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       list_for_each_entry_safe(ctx, next, &dev->contexts, list) {
> +               if (ctx->id == id) {
> +                       list_del_init(&ctx->list);
> +                       mutex_unlock(&dev->lock);
> +                       return;
> +               }
> +       }
> +}
> +
> +static int virtvdec_initialize_id(struct virtvdec_ctx *ctx)
> +{
> +       struct virtvdec_dev *dev = ctx->dev;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       ctx->id = dev->num_ctxs;
> +       dev->num_ctxs += 1;
> +       list_add_tail(&ctx->list, &dev->contexts);
> +
> +       return 0;
> +}
> +
> +static struct virtvdec_ctx *virtvdec_fh_to_ctx(const struct v4l2_fh *fh)
> +{
> +       return container_of(fh, struct virtvdec_ctx, fh);
> +}
> +
> +/**
> + * struct vdec_host_req_subscription - Used to specify a host request to be
> + * waited for.
> + *
> + * @list:              List head to attach to the driver data.
> + * @done:              Completion for the notification to the a subscriber
> + * @type:              type of a host request waited for.
> + * @instance_id:       instance_id of a host request waited for.
> + * @result:            result of the reported host request
> + */
> +struct vdec_host_req_subscription {
> +       struct list_head list;
> +       struct completion done;
> +       u32 type;
> +       u32 instance_id;
> +       int result;
> +};
> +
> +#define DEFINE_SUBSCRIPTION(name, req, id)                                     \
> +       struct vdec_host_req_subscription name = {                             \
> +               .type = VIRTIO_VDEC_HOST_REQ_##req,                            \
> +               .instance_id = id,                                             \
> +       }
> +
> +#define DEFINE_SUBSCRIPTION_QUERY(name) DEFINE_SUBSCRIPTION(name, QUERY, 0)
> +#define DEFINE_SUBSCRIPTION_OPEN(name, id) DEFINE_SUBSCRIPTION(name, OPEN, id)
> +#define DEFINE_SUBSCRIPTION_CLOSE(name, id) DEFINE_SUBSCRIPTION(name, CLOSE, id)
> +#define DEFINE_SUBSCRIPTION_SET_BUFFER_COUNT(name, id)                         \
> +       DEFINE_SUBSCRIPTION(name, SET_BUFFER_COUNT, id)
> +#define DEFINE_SUBSCRIPTION_FLUSHED(name, id)                                  \
> +       DEFINE_SUBSCRIPTION(name, FLUSHED, id)
> +
> +static bool virtvdec_match_host_req_subscription(
> +       const struct virtio_vdec_host_req *req,
> +       const struct vdec_host_req_subscription *sub)
> +{
> +       if (req->type != sub->type)
> +               return false;
> +       switch (req->type) {
> +       case VIRTIO_VDEC_HOST_REQ_QUERY:
> +               /* No need to check instance_id */
> +               return true;
> +       case VIRTIO_VDEC_HOST_REQ_OPEN:
> +       case VIRTIO_VDEC_HOST_REQ_CLOSE:
> +       case VIRTIO_VDEC_HOST_REQ_SET_BUFFER_COUNT:
> +       case VIRTIO_VDEC_HOST_REQ_FLUSHED:
> +               return req->instance_id == sub->instance_id;
> +       default:
> +               return false;
> +       }
> +}
> +
> +/*
> + * Utilities for vdec_host_req_subscription. *
> + */
> +static void virtvdec_host_req_subscribe(struct virtvdec_dev *dev,
> +                                       struct vdec_host_req_subscription *sub)
> +{
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       init_completion(&sub->done);
> +
> +       list_add_tail(&sub->list, &dev->subscriptions);
> +}
> +
> +static void virtvdec_host_req_wait(struct virtvdec_dev *dev,
> +                                  struct vdec_host_req_subscription *sub)
> +{
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       mutex_unlock(&dev->lock);
> +       wait_for_completion(&sub->done);
> +       mutex_lock(&dev->lock);
> +}
> +
> +/**
> + * Virtqueue operations
> + */
> +static int
> +virtvdec_process_host_req_query(struct virtvdec_dev *dev,
> +                               const struct virtio_vdec_host_req *req)
> +{
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       if (req->result != VIRTIO_VDEC_HOST_REQ_RESULT_SUCCESS) {
> +               vdec_err(dev, "failed to query to the host : %d", req->result);
> +               return req->result;
> +       }
> +
> +       memcpy(dev->src_fmts, &req->query.bitstream_formats,
> +              sizeof(dev->src_fmts));
> +       memcpy(dev->dst_fmts, &req->query.frame_formats, sizeof(dev->dst_fmts));
> +
> +       return 0;
> +}
> +
> +static void virtvdec_set_capture_format(struct virtvdec_ctx *ctx,
> +                                       struct v4l2_pix_format_mplane *pix_mp)
> +{
> +       int i;
> +
> +       for (i = 0; i < pix_mp->num_planes; ++i) {
> +               pix_mp->plane_fmt[i].bytesperline = pix_mp->width;
> +               pix_mp->plane_fmt[i].sizeimage =
> +                       pix_mp->plane_fmt[i].bytesperline * pix_mp->height;
> +
> +               /*
> +                * NV12 has chroma planes subsampled by 2.
> +                *
> +                * TODO(keiichiw): This computation can be wrong for some
> +                * formats like YUV420.
> +                * We might be able to a helper for this computation.
> +                * cf. https://lkml.org/lkml/2019/4/17/158
> +                */
> +               if (i != 0)
> +                       pix_mp->plane_fmt[i].sizeimage /= 2;
> +       }
> +
> +       ctx->dst_fmt = *pix_mp;
> +}
> +
> +static int
> +virtvdec_process_host_req_stream_info(struct virtvdec_dev *dev,
> +                                     const struct virtio_vdec_host_req *req)
> +{
> +       struct virtvdec_ctx *ctx;
> +       const struct virtio_vdec_host_req_stream_info *info = &req->stream_info;
> +       struct v4l2_pix_format_mplane pix_mp = {};
> +       static const struct v4l2_event event = {
> +               .type = V4L2_EVENT_SOURCE_CHANGE,
> +               .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION
> +       };
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       ctx = virtvdec_get_context(dev, req->instance_id);
> +       if (!ctx) {
> +               vdec_err(
> +                       dev,
> +                       "invalid instance_id passed from the host for stream_info: %d",
> +                       req->instance_id);
> +               return -EINVAL;
> +       }
> +
> +       /* Store stream information */
> +       /* Only support MPLANE */
> +       pix_mp = ctx->dst_fmt;
> +       pix_mp.pixelformat = info->raw_fourcc;
> +       pix_mp.width = info->fb_width;
> +       pix_mp.height = info->fb_height;
> +
> +       /* num_planes */
> +       /* TODO(keiichiw): Support other pixel formats. */
> +       if (pix_mp.pixelformat == V4L2_PIX_FMT_NV12)
> +               pix_mp.num_planes = 2;
> +       else {
> +               vdec_err(dev, "unknown pixel format: %d", pix_mp.pixelformat);
> +               return -EINVAL;
> +       }
> +
> +       virtvdec_set_capture_format(ctx, &pix_mp);
> +
> +       ctx->min_capbufs = info->min_frame_buffer_count;
> +       ctx->max_capbufs = info->max_frame_buffer_count;
> +       ctx->crop.c.left = info->crop.left;
> +       ctx->crop.c.top = info->crop.top;
> +       ctx->crop.c.width = info->crop.width;
> +       ctx->crop.c.height = info->crop.height;
> +
> +       if (ctx->state == VIRTVDEC_STATE_INITIALIZING)
> +               ctx->state = VIRTVDEC_STATE_CAPTURE_SETUP;
> +
> +       v4l2_event_queue_fh(&ctx->fh, &event);
> +
> +       return 0;
> +}
> +
> +static void virtvdec_vb2_buffer_done(struct virtvdec_ctx *ctx,
> +                                    bool output_buffer, int buffer_id,
> +                                    u64 timestamp)
> +{
> +       struct vb2_buffer *vb;
> +
> +       WARN_ON(!mutex_is_locked(&ctx->dev->lock));
> +
> +       if (buffer_id < 0)
> +               return;
> +
> +       if (buffer_id >= VIRTIO_VDEC_MAX_BUFFER_COUNT) {
> +               vdec_err(ctx->dev, "invalid buffer_id for %s buffer: %d",
> +                        output_buffer ? "OUTPUT" : "CAPTURE", buffer_id);
> +               return;
> +       }
> +
> +       if (output_buffer) {
> +               vb = ctx->outbuf_vb2_map[buffer_id];
> +               ctx->outbuf_vb2_map[buffer_id] = NULL;
> +       } else {
> +               vb = ctx->capbuf_vb2_map[buffer_id];
> +               ctx->capbuf_vb2_map[buffer_id] = NULL;
> +       }
> +
> +       if (!vb) {
> +               vdec_err(ctx->dev, "empty %s buffer: %d",
> +                        output_buffer ? "OUTPUT" : "CAPTURE", buffer_id);
> +               return;
> +       }
> +
> +       vb->timestamp = timestamp * NSEC_PER_SEC;
> +       vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
> +}
> +
> +static int virtvdec_lookup_index_from_handle(const struct virtvdec_ctx *ctx,
> +                                            bool output_buffer, int handle)
> +{
> +       const u32 *handle_map;
> +       int i;
> +
> +       WARN_ON(!mutex_is_locked(&ctx->dev->lock));
> +
> +       if (output_buffer)
> +               handle_map = ctx->outbuf_handle_map;
> +       else
> +               handle_map = ctx->capbuf_handle_map;
> +
> +       for (i = 0; i < VIRTIO_VDEC_MAX_BUFFER_COUNT; ++i)
> +               if (handle_map[i] == handle)
> +                       return i;
> +
> +       vdec_err(ctx->dev, "invalid handle for %s: %d",
> +                output_buffer ? "OUTPUT" : "CAPTURE", handle);
> +
> +       return -1;
> +}
> +
> +static int
> +virtvdec_process_host_req_buffer(struct virtvdec_dev *dev,
> +                                const struct virtio_vdec_host_req *req,
> +                                bool output_buffer)
> +{
> +       struct virtvdec_ctx *ctx;
> +       const struct list_head *queue;
> +       struct virtvdec_buf *cur, *next;
> +       u32 index;
> +       u64 timestamp;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       ctx = virtvdec_get_context(dev, req->instance_id);
> +       if (!ctx) {
> +               vdec_err(dev, "invalid instance_id: %d", req->instance_id);
> +               return -EINVAL;
> +       }
> +
> +       if (output_buffer) {
> +               index = virtvdec_lookup_index_from_handle(
> +                       ctx, true, req->bitstream_buffer.handle);
> +               timestamp = 0; /* Don't need timestamp for OUTPUT buffer*/
> +               queue = &ctx->src_queue;
> +       } else {
> +               index = virtvdec_lookup_index_from_handle(
> +                       ctx, false, req->frame_buffer.handles[0]);
> +               timestamp = req->frame_buffer.cookie;
> +               queue = &ctx->dst_queue;
> +       }
> +
> +       list_for_each_entry_safe(cur, next, queue, list) {
> +               if (cur->vb.vb2_buf.index == index) {
> +                       list_del_init(&cur->list);
> +                       break;
> +               }
> +       }
> +
> +       virtvdec_vb2_buffer_done(ctx, output_buffer, index, timestamp);
> +
> +       return 0;
> +}
> +
> +static int
> +virtvdec_process_host_req_drained(struct virtvdec_dev *dev,
> +                                 const struct virtio_vdec_host_req *req)
> +{
> +       struct virtvdec_ctx *ctx;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       ctx = virtvdec_get_context(dev, req->instance_id);
> +       if (!ctx) {
> +               vdec_err(dev, "DRAINED notified for invalid instance_id: %d",
> +                        req->instance_id);
> +               return -EINVAL;
> +       }
> +
> +       if (!ctx->waiting_drained)
> +               return 0;
> +       ctx->waiting_drained = false;
> +
> +       if (!ctx->stored_capbuf) {
> +               vdec_err(dev, "stored_capbuf is not stored: instance=%d",
> +                        req->instance_id);
> +               return -EINVAL;
> +       }
> +
> +       to_vb2_v4l2_buffer(ctx->stored_capbuf)->flags |= V4L2_BUF_FLAG_LAST;
> +       ctx->stored_capbuf->planes[0].bytesused = 0;
> +       vb2_buffer_done(ctx->stored_capbuf, VB2_BUF_STATE_DONE);
> +
> +       list_del(&virtvdec_vb2_to_virtvdec_buf(ctx->stored_capbuf)->list);
> +       ctx->stored_capbuf = NULL;
> +
> +       ctx->state = VIRTVDEC_STATE_DRAINED;
> +
> +       return 0;
> +}
> +
> +/* Report a fatal error to the user */
> +static void virtvdec_signal_error(struct virtvdec_ctx *ctx)
> +{
> +       if (!ctx)
> +               return;
> +
> +       vb2_queue_error(&ctx->src_vbq);
> +       vb2_queue_error(&ctx->dst_vbq);
> +}
> +
> +static int virtvdec_process_host_req(struct virtvdec_dev *dev,
> +                                    const struct virtio_vdec_host_req *req)
> +{
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       /* Handle an error from the host */
> +       if (req->result) {
> +               struct virtvdec_ctx *ctx;
> +
> +               ctx = virtvdec_get_context(dev, req->instance_id);
> +               if (ctx) {
> +                       vdec_err(
> +                               dev,
> +                               "error notified from the host for instance %d: %d",
> +                               req->instance_id, req->type);
> +                       virtvdec_signal_error(ctx);
> +               } else {
> +                       /*
> +                        * When the host notifies an error for an invalid
> +                        * instance, signal fatal error to all instances,
> +                        * as it means something bad happened.
> +                        */
> +                       struct virtvdec_ctx *next;
> +
> +                       vdec_err(dev, "error notified from the host: %d",
> +                                req->type);
> +                       list_for_each_entry_safe(ctx, next, &dev->contexts,
> +                                                 list) {
> +                               virtvdec_signal_error(ctx);
> +                       }
> +               }
> +
> +               return -EIO;
> +       }
> +
> +       switch (req->type) {
> +       case VIRTIO_VDEC_HOST_REQ_QUERY:
> +               return virtvdec_process_host_req_query(dev, req);
> +       case VIRTIO_VDEC_HOST_REQ_STREAM_INFO:
> +               return virtvdec_process_host_req_stream_info(dev, req);
> +       case VIRTIO_VDEC_HOST_REQ_BITSTREAM_BUFFER:
> +               return virtvdec_process_host_req_buffer(dev, req,
> +                                                       true /* OUTPUT */);
> +       case VIRTIO_VDEC_HOST_REQ_FRAME_BUFFER:
> +               return virtvdec_process_host_req_buffer(dev, req,
> +                                                       false /* CAPTURE */);
> +       case VIRTIO_VDEC_HOST_REQ_DRAINED:
> +               return virtvdec_process_host_req_drained(dev, req);
> +       case VIRTIO_VDEC_HOST_REQ_OPEN:
> +       case VIRTIO_VDEC_HOST_REQ_CLOSE:
> +       case VIRTIO_VDEC_HOST_REQ_SET_BUFFER_COUNT:
> +       case VIRTIO_VDEC_HOST_REQ_REGISTER_BUFFER:
> +       case VIRTIO_VDEC_HOST_REQ_FLUSHED:
> +               /* Do nothing */
> +               return 0;
> +       default:
> +               vdec_err(dev, "got an unknown host request from the host: %d",
> +                        req->type);
> +               return -EINVAL;
> +       }
> +}
> +
> +/**
> + * virtvdec_check_subscription - Call complete() if the incoming request is
> + * subscribed.
> + */
> +static void virtvdec_check_subscription(struct virtvdec_dev *dev,
> +                                       const struct virtio_vdec_host_req *req,
> +                                       int result)
> +{
> +       struct vdec_host_req_subscription *sub, *next;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       list_for_each_entry_safe(sub, next, &dev->subscriptions, list) {
> +               if (virtvdec_match_host_req_subscription(req, sub)) {
> +                       sub->result = result;
> +                       complete(&sub->done);
> +                       list_del(&sub->list);
> +                       break;
> +               }
> +       }
> +}
> +
> +/**
> + * virtvdec_vq_add_inbuf - expose a buffer for virtio_vdec_host_req to the host
> + */
> +static int virtvdec_vq_add_inbuf(struct virtqueue *vq,
> +                                struct virtio_vdec_host_req *buffer)
> +{
> +       struct scatterlist sg[1];
> +
> +       sg_init_one(sg, buffer, sizeof(*buffer));
> +       return virtqueue_add_inbuf(vq, sg, ARRAY_SIZE(sg), buffer, GFP_KERNEL);
> +}
> +
> +/**
> + * virtvdec_vq_in_work_handler - process incoming host requests and send
> + * notifications to subscribers.
> + */
> +static void virtvdec_vq_in_work_handler(struct work_struct *work)
> +{
> +       struct virtvdec_dev *dev;
> +       u32 len;
> +       struct virtio_vdec_host_req *req;
> +       bool kick_vq = false;
> +
> +       dev = container_of(work, struct virtvdec_dev, vq_in_work);
> +
> +       mutex_lock(&dev->lock);
> +       mutex_lock(&dev->vq_in_lock);
> +       while ((req = virtqueue_get_buf(dev->vq_in, &len)) != NULL) {
> +               int ret;
> +
> +               mutex_unlock(&dev->vq_in_lock);
> +               ret = virtvdec_process_host_req(dev, req);
> +               virtvdec_check_subscription(dev, req, ret);
> +               mutex_lock(&dev->vq_in_lock);
> +               /* Reuse the inqueue entry */
> +               WARN_ON(virtvdec_vq_add_inbuf(dev->vq_in, req) < 0);
> +               kick_vq = true;
> +       }
> +       if (kick_vq)
> +               virtqueue_kick(dev->vq_in);
> +
> +       mutex_unlock(&dev->vq_in_lock);
> +       mutex_unlock(&dev->lock);
> +}
> +
> +static void virtvdec_vq_in_cb(struct virtqueue *vq)
> +{
> +       struct virtvdec_dev *dev = vq->vdev->priv;
> +
> +       schedule_work(&dev->vq_in_work);
> +}
> +
> +static void virtvdec_vq_out_work_handler(struct work_struct *work)
> +{
> +       struct virtvdec_dev *dev =
> +               container_of(work, struct virtvdec_dev, vq_out_work);
> +       struct virtio_vdec_guest_req *req;
> +       u32 len;
> +       bool wake_waitq = false;
> +
> +       mutex_lock(&dev->vq_out_lock);
> +       while ((req = virtqueue_get_buf(dev->vq_out, &len)) != NULL) {
> +               wake_waitq = true;
> +               kfree(req);
> +       }
> +       mutex_unlock(&dev->vq_out_lock);
> +
> +       if (wake_waitq)
> +               wake_up_interruptible_all(&dev->out_waitq);
> +}
> +
> +static void virtvdec_vq_out_cb(struct virtqueue *vq)
> +{
> +       struct virtvdec_dev *dev = vq->vdev->priv;
> +
> +       /*
> +        * TODO(keiichiw): Optimization idea
> +        * - Invoke wake_up_interruptible_all(&dev->out_waitq) here.
> +        * - Call virtqueue_get_buf() in virtvdec_send_request()
> +        */
> +
> +       schedule_work(&dev->vq_out_work);
> +}
> +
> +/**
> + * virtvdec_init_inqueue - expose virtqueue entries to the host as many as
> + * possible
> + */
> +static int virtvdec_init_inqueue(struct virtvdec_dev *dev)
> +{
> +       struct virtio_vdec_host_req *buffer;
> +       int ret;
> +
> +       mutex_lock(&dev->vq_in_lock);
> +
> +       while (dev->vq_in->num_free > 0) {
> +               buffer = devm_kzalloc(&dev->virt_dev->dev, sizeof(*buffer),
> +                                     GFP_KERNEL);
> +               if (!buffer) {
> +                       ret = -ENOMEM;
> +                       goto clear_queue;
> +               }
> +               ret = virtvdec_vq_add_inbuf(dev->vq_in, buffer);
> +               if (ret) {
> +                       vdec_err(dev, "failed to give inbuf to host: %d", ret);
> +                       goto clear_queue;
> +               }
> +       }
> +
> +       mutex_unlock(&dev->vq_in_lock);
> +       return 0;
> +
> +clear_queue:
> +       while ((buffer = virtqueue_detach_unused_buf(dev->vq_in)))
> +               kfree(buffer);
> +
> +       mutex_unlock(&dev->vq_in_lock);
> +       return ret;
> +}
> +
> +/**
> + * virtvdec_send_request - enqueue a request to the virtqueue.
> + * @dev: the virtio-vdec device
> + * @req: the request to be enqueued
> + *
> + * If it fails, frees @req and returns a non-zero value.
> + */
> +static int virtvdec_send_request(struct virtvdec_dev *dev,
> +                                struct virtio_vdec_guest_req *req)
> +{
> +       struct scatterlist sg[1];
> +       int ret;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       mutex_lock(&dev->vq_out_lock);
> +
> +       sg_init_one(sg, req, sizeof(*req));
> +       while ((ret = virtqueue_add_outbuf(dev->vq_out, sg, ARRAY_SIZE(sg), req,
> +                                          GFP_KERNEL)) == -ENOSPC) {
> +               mutex_unlock(&dev->vq_out_lock);
> +               if (!wait_event_timeout(
> +                           dev->out_waitq,
> +                           atomic_read((atomic_t *)&dev->vq_out->num_free),
> +                           HZ)) {
> +                       vdec_err(dev, "failed to send Virtqueue req: -EBUSY");
> +                       return -EBUSY;
> +               }
> +               mutex_lock(&dev->vq_out_lock);
> +       }
> +
> +       if (ret) {
> +               vdec_err(dev, "failed to send virtqueue req: %d", ret);
> +               goto error;
> +       }
> +
> +       virtqueue_kick(dev->vq_out);
> +       mutex_unlock(&dev->vq_out_lock);
> +       return 0;
> +
> +error:
> +       kfree(req);
> +       mutex_unlock(&dev->vq_out_lock);
> +       return ret;
> +}
> +
> +/**
> + * virtvdec_allocate_guest_req - allocate and initialize virtio_vdec_guest_
> + * req struct.
> + *
> + * Returns NULL if it fails to allocate the memory.
> + */
> +static struct virtio_vdec_guest_req *
> +virtvdec_allocate_guest_req(int type, int instance_id)
> +{
> +       struct virtio_vdec_guest_req *req;
> +
> +       req = kzalloc(sizeof(*req), GFP_KERNEL);
> +       if (!req)
> +               return NULL;
> +
> +       req->type = type;
> +       req->instance_id = instance_id;
> +
> +       return req;
> +}
> +
> +static int virtvdec_send_request_query(struct virtvdec_dev *dev)
> +{
> +       struct virtio_vdec_guest_req *req;
> +
> +       req = virtvdec_allocate_guest_req(VIRTIO_VDEC_GUEST_REQ_QUERY, 0);
> +       if (req == NULL)
> +               return -ENOMEM;
> +       return virtvdec_send_request(dev, req);
> +}
> +
> +static int virtvdec_send_request_open(const struct virtvdec_ctx *ctx)
> +{
> +       struct virtio_vdec_guest_req *req;
> +
> +       req = virtvdec_allocate_guest_req(VIRTIO_VDEC_GUEST_REQ_OPEN, ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       req->open.coded_fourcc = ctx->src_fmt.pixelformat;
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +static int virtvdec_send_request_close(const struct virtvdec_ctx *ctx)
> +{
> +       struct virtio_vdec_guest_req *req;
> +
> +       req = virtvdec_allocate_guest_req(VIRTIO_VDEC_GUEST_REQ_CLOSE, ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +static int
> +virtvdec_send_request_set_buffer_count(const struct virtvdec_ctx *ctx,
> +                                      int buffer_type, int buffer_count)
> +{
> +       struct virtio_vdec_guest_req *req;
> +       struct virtio_vdec_guest_req_set_buffer_count *req_set_bc;
> +
> +       req = virtvdec_allocate_guest_req(
> +               VIRTIO_VDEC_GUEST_REQ_SET_BUFFER_COUNT, ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       req_set_bc = &req->set_buffer_count;
> +       req_set_bc->type = buffer_type;
> +       req_set_bc->buffer_count = buffer_count;
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +static int virtvdec_send_request_register_buffer(const struct virtvdec_ctx *ctx,
> +                                                const int buf_type,
> +                                                struct vb2_buffer *vb,
> +                                                const u32 handle)
> +{
> +       struct virtio_vdec_guest_req *req;
> +       struct virtio_vdec_guest_req_register_buffer *req_buf;
> +       int i;
> +
> +       req = virtvdec_allocate_guest_req(VIRTIO_VDEC_GUEST_REQ_REGISTER_BUFFER,
> +                                         ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       req_buf = &req->register_buffer;
> +       req_buf->type = buf_type;
> +
> +       req_buf->num_planes = vb->num_planes;
> +       for (i = 0; i < vb->num_planes; ++i) {
> +               req_buf->planes[i].handle = handle;
> +               req_buf->planes[i].length = vb->planes[i].length;
> +
> +               /*
> +                * Always use 0 for offset here.
> +                * The offsets passed from the client are used in
> +                * OUTPUT/CAPTURE requests
> +                */
> +               req_buf->planes[i].offset = 0;
> +       }
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +static int virtvdec_send_request_ack_stream_info(const struct virtvdec_ctx *ctx)
> +{
> +       struct virtio_vdec_guest_req *req;
> +
> +       req = virtvdec_allocate_guest_req(VIRTIO_VDEC_GUEST_REQ_ACK_STREAM_INFO,
> +                                         ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +static int
> +virtvdec_send_request_bitstream_buffer(const struct virtvdec_ctx *ctx,
> +                                      const struct vb2_buffer *vb)
> +{
> +       struct virtio_vdec_guest_req *req;
> +       struct virtio_vdec_guest_req_bitstream_buffer *bb;
> +
> +       req = virtvdec_allocate_guest_req(
> +               VIRTIO_VDEC_GUEST_REQ_BITSTREAM_BUFFER, ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       bb = &req->bitstream_buffer;
> +       bb->handle = ctx->outbuf_handle_map[vb->index];
> +       bb->offset = vb->planes[0].data_offset;
> +       bb->length = vb->planes[0].bytesused;
> +       bb->cookie = vb->timestamp / NSEC_PER_SEC;
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +static int virtvdec_send_request_frame_buffer(const struct virtvdec_ctx *ctx,
> +                                             const struct vb2_buffer *vb)
> +{
> +       const struct v4l2_pix_format_mplane *dst_fmt = &ctx->dst_fmt;
> +       struct virtio_vdec_guest_req *req;
> +       struct virtio_vdec_guest_req_frame_buffer *fb;
> +       int handle;
> +       int i;
> +
> +       WARN_ON(!mutex_is_locked(&ctx->dev->lock));
> +
> +       req = virtvdec_allocate_guest_req(VIRTIO_VDEC_GUEST_REQ_FRAME_BUFFER,
> +                                         ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       fb = &req->frame_buffer;
> +       handle = ctx->capbuf_handle_map[vb->index];
> +
> +       for (i = 0; i < dst_fmt->num_planes; ++i) {
> +               fb->planes[i].handle = handle;
> +               fb->planes[i].offset = vb->planes[i].data_offset;
> +               fb->planes[i].stride = dst_fmt->width;
> +       }
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +static int virtvdec_send_request_drain(const struct virtvdec_ctx *ctx)
> +{
> +       struct virtio_vdec_guest_req *req;
> +
> +       req = virtvdec_allocate_guest_req(VIRTIO_VDEC_GUEST_REQ_DRAIN, ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +static int virtvdec_send_request_flush(const struct virtvdec_ctx *ctx)
> +{
> +       struct virtio_vdec_guest_req *req;
> +
> +       req = virtvdec_allocate_guest_req(VIRTIO_VDEC_GUEST_REQ_FLUSH, ctx->id);
> +       if (req == NULL)
> +               return -ENOMEM;
> +
> +       return virtvdec_send_request(ctx->dev, req);
> +}
> +
> +/*
> + * Video decoding controls
> + */
> +
> +static int virtvdec_vidioc_querycap(struct file *file, void *priv,
> +                                   struct v4l2_capability *cap)
> +{
> +       strlcpy(cap->driver, VIRTIO_VDEC_NAME, sizeof(cap->driver));
> +       strlcpy(cap->card, "virtio video decoder", sizeof(cap->card));
> +       snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI: virtio-vdec");
> +       return 0;
> +}
> +
> +static const struct virtio_vdec_format_desc *
> +virtvdec_find_src_format(const struct virtvdec_dev *dev, u32 fourcc)
> +{
> +       int i;
> +
> +       for (i = 0; i < VIRTIO_VDEC_NUM_FORMATS; ++i)
> +               if (dev->src_fmts[i].mask != 0 &&
> +                   dev->src_fmts[i].fourcc == fourcc)
> +                       return &dev->src_fmts[i];
> +
> +       return NULL;
> +}
> +
> +static int virtvdec_vidioc_enum_framesizes(struct file *file, void *prov,
> +                                          struct v4l2_frmsizeenum *fsize)
> +{
> +       const struct virtvdec_dev *dev = video_drvdata(file);
> +       const struct virtio_vdec_format_desc *fmt;
> +
> +       if (fsize->index != 0) {
> +               vdec_err(dev, "invalid frame size index (expected 0, got %d)",
> +                        fsize->index);
> +               return -EINVAL;
> +       }
> +       fmt = virtvdec_find_src_format(dev, fsize->pixel_format);
> +       if (!fmt) {
> +               vdec_err(dev, "unsupported bitstream format (%08x)",
> +                        fsize->pixel_format);
> +               return -EINVAL;
> +       }
> +       fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +       fsize->stepwise.min_width = fmt->width.min;
> +       fsize->stepwise.max_width = fmt->width.max;
> +       fsize->stepwise.step_width = fmt->width.step;
> +       fsize->stepwise.min_height = fmt->height.min;
> +       fsize->stepwise.max_height = fmt->height.max;
> +       fsize->stepwise.step_height = fmt->height.step;
> +
> +       return 0;
> +}
> +
> +static int virtvdec_vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *f,
> +                                   bool bitstream_format)
> +{
> +       const struct virtvdec_dev *dev = video_drvdata(file);
> +       const struct virtio_vdec_format_desc *formats;
> +       const struct virtio_vdec_format_desc *fmt;
> +       int fmt_cnt;
> +       int i;
> +
> +       if (bitstream_format)
> +               formats = dev->src_fmts;
> +       else
> +               formats = dev->dst_fmts;
> +
> +       fmt_cnt = 0;
> +       for (i = 0; i < VIRTIO_VDEC_NUM_FORMATS; ++i) {
> +               if (!formats[i].mask)
> +                       continue;
> +               if (fmt_cnt == f->index) {
> +                       fmt = &formats[i];
> +                       f->pixelformat = fmt->fourcc;
> +                       f->flags = 0;
> +                       if (bitstream_format)
> +                               f->flags |= V4L2_FMT_FLAG_COMPRESSED;
> +                       return 0;
> +               }
> +               fmt_cnt++;
> +       }
> +       return -EINVAL;
> +}
> +
> +static int virtvdec_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
> +                                           struct v4l2_fmtdesc *f)
> +{
> +       return virtvdec_vidioc_enum_fmt(file, f, false);
> +}
> +
> +static int virtvdec_vidioc_enum_fmt_vid_out(struct file *file, void *priv,
> +                                           struct v4l2_fmtdesc *f)
> +{
> +       return virtvdec_vidioc_enum_fmt(file, f, true);
> +}
> +
> +static int virtvdec_vidioc_fmt_vid_cap_mplane(struct file *file, void *priv,
> +                                             struct v4l2_format *f)
> +{
> +       const struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +
> +       if (ctx->state == VIRTVDEC_STATE_INITIALIZING)
> +               return -EINVAL;
> +
> +       f->fmt.pix_mp = ctx->dst_fmt;
> +
> +       return 0;
> +}
> +
> +static int virtvdec_vidioc_s_fmt_vid_cap_mplane(struct file *file, void *priv,
> +                                               struct v4l2_format *f)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +
> +       virtvdec_set_capture_format(ctx, pix_mp);
> +
> +       return 0;
> +}
> +
> +static int virtvdec_vidioc_g_fmt_vid_out_mplane(struct file *file, void *priv,
> +                                               struct v4l2_format *f)
> +{
> +       const struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +
> +       f->type = ctx->vdec_src_fmt->fourcc;
> +       f->fmt.pix_mp = ctx->src_fmt;
> +
> +       return 0;
> +}
> +
> +static int virtvdec_vidioc_try_fmt_vid_out_mplane(struct file *file, void *priv,
> +                                                 struct v4l2_format *f)
> +{
> +       const struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       const struct virtio_vdec_format_desc *fmt;
> +       const struct v4l2_pix_format_mplane *pix_mp;
> +
> +       pix_mp = &f->fmt.pix_mp;
> +
> +       fmt = virtvdec_find_src_format(ctx->dev, pix_mp->pixelformat);
> +       if (!fmt) {
> +               vdec_err(ctx->dev, "failed to try output format");
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static int virtvdec_vidioc_s_fmt_vid_out_mplane(struct file *file, void *priv,
> +                                               struct v4l2_format *f)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       const struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +       int ret;
> +
> +       if (ctx->state != VIRTVDEC_STATE_INITIALIZING) {
> +               vdec_err(ctx->dev,
> +                        "dynamic resolution change is not supported");
> +               return -EINVAL;
> +       }
> +
> +       ret = virtvdec_vidioc_try_fmt_vid_out_mplane(file, priv, f);
> +       if (ret)
> +               return ret;
> +       ctx->vdec_src_fmt =
> +               virtvdec_find_src_format(ctx->dev, pix_mp->pixelformat);
> +       ctx->src_fmt = *pix_mp;
> +
> +       return 0;
> +}
> +
> +static int virtvdec_vidioc_g_selection(struct file *file, void *priv,
> +                                      struct v4l2_selection *s)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +
> +       if (V4L2_TYPE_IS_OUTPUT(s->type))
> +               return -EINVAL;
> +
> +       /* TODO(keiichiw): Should we check s->target? */
> +
> +       s->r.width = ctx->crop.c.width;
> +       s->r.height = ctx->crop.c.height;
> +       s->r.top = ctx->crop.c.top;
> +       s->r.left = ctx->crop.c.left;
> +
> +       return 0;
> +}
> +
> +static int virtvdec_send_request_open_sync(const struct virtvdec_ctx *ctx)
> +{
> +       DEFINE_SUBSCRIPTION_OPEN(sub, ctx->id);
> +       int ret;
> +
> +       virtvdec_host_req_subscribe(ctx->dev, &sub);
> +       ret = virtvdec_send_request_open(ctx);
> +       if (ret) {
> +               vdec_err(ctx->dev, "failed to send open request");
> +               return ret;
> +       }
> +       virtvdec_host_req_wait(ctx->dev, &sub);
> +
> +       return sub.result;
> +}
> +
> +static int virtvdec_open_session(struct virtvdec_ctx *ctx)
> +{
> +       struct virtvdec_dev *dev = ctx->dev;
> +       int ret;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       ret = virtvdec_initialize_id(ctx);
> +       if (ret)
> +               return ret;
> +
> +       ret = virtvdec_send_request_open_sync(ctx);
> +       if (!ret)
> +               ctx->has_host_session = true;
> +
> +       return ret;
> +}
> +
> +static int virtvdec_close_session(struct virtvdec_ctx *ctx)
> +{
> +       DEFINE_SUBSCRIPTION_CLOSE(sub, ctx->id);
> +       int ret;
> +
> +       WARN_ON(!mutex_is_locked(&ctx->dev->lock));
> +
> +       /*
> +        * TODO(keiichiw): we might need to:
> +        * - re-initialize fields in ctx
> +        * - free all allocated memories
> +        **/
> +
> +       if (!ctx->has_host_session)
> +               return 0;
> +
> +       virtvdec_host_req_subscribe(ctx->dev, &sub);
> +       ret = virtvdec_send_request_close(ctx);
> +       if (ret)
> +               return ret;
> +
> +       virtvdec_host_req_wait(ctx->dev, &sub);
> +       ctx->has_host_session = false;
> +
> +       return sub.result;
> +}
> +
> +static int virtvdec_vidioc_reqbufs(struct file *file, void *priv,
> +                                  struct v4l2_requestbuffers *rb)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       struct vb2_queue *vbq;
> +       int buffer_type;
> +       int ret;
> +
> +       WARN_ON(!mutex_is_locked(&ctx->dev->lock));
> +
> +       if (V4L2_TYPE_IS_OUTPUT(rb->type)) {
> +               buffer_type = VIRTIO_VDEC_GUEST_REQ_BUFFER_TYPE_BITSTREAM;
> +               vbq = &ctx->src_vbq;
> +       } else {
> +               buffer_type = VIRTIO_VDEC_GUEST_REQ_BUFFER_TYPE_FRAME;
> +               vbq = &ctx->dst_vbq;
> +
> +               if (rb->count > 0 && rb->count < ctx->min_capbufs)
> +                       rb->count = ctx->min_capbufs;
> +
> +               /* TODO(keiichiw): Check max_capbufs? */
> +       }
> +
> +       if (rb->count == 0) {
> +               ctx->waiting_drained = false;
> +
> +               if (ctx->state != VIRTVDEC_STATE_INITIALIZING) {
> +                       WARN_ON(!ctx->has_host_session);
> +
> +                       ret = virtvdec_close_session(ctx);
> +                       if (ret) {
> +                               vdec_err(
> +                                       ctx->dev,
> +                                       "failed to close host-side session : %d",
> +                                       ret);
> +                               return ret;
> +                       }
> +               }
> +
> +               ctx->state = VIRTVDEC_STATE_INITIALIZING;
> +
> +               return vb2_reqbufs(vbq, rb);
> +       }
> +
> +       if (!ctx->has_host_session) {
> +               ret = virtvdec_open_session(ctx);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       if (V4L2_TYPE_IS_OUTPUT(rb->type) ||
> +           ctx->state == VIRTVDEC_STATE_CAPTURE_SETUP) {
> +               DEFINE_SUBSCRIPTION_SET_BUFFER_COUNT(sub, ctx->id);
> +               int buf_count;
> +
> +               virtvdec_host_req_subscribe(ctx->dev, &sub);
> +
> +               /*
> +                * For CAPTURE, save one buffer for
> +                * VIRTIO_VDEC_DRAINED_NOTIFICATION_BUFFER_INDEX
> +                */
> +               if (V4L2_TYPE_IS_OUTPUT(rb->type))
> +                       buf_count = rb->count;
> +               else
> +                       buf_count = rb->count - 1;
> +
> +               ret = virtvdec_send_request_set_buffer_count(ctx, buffer_type,
> +                                                            buf_count);
> +               if (ret)
> +                       return ret;
> +               virtvdec_host_req_wait(ctx->dev, &sub);
> +               if (sub.result)
> +                       return sub.result;
> +       }
> +
> +       return vb2_reqbufs(vbq, rb);
> +}
> +
> +static int virtvdec_vidioc_querybuf(struct file *file, void *priv,
> +                                   struct v4l2_buffer *buf)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       int ret;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(buf->type)) {
> +               ret = vb2_querybuf(&ctx->src_vbq, buf);
> +       } else {
> +               ret = vb2_querybuf(&ctx->dst_vbq, buf);
> +               buf->m.planes[0].m.mem_offset += DST_QUEUE_OFF_BASE;
> +               buf->m.offset += DST_QUEUE_OFF_BASE;
> +       }
> +       if (ret)
> +               vdec_err(ctx->dev, "QUERYBUF for type %d failed", buf->type);
> +       return ret;
> +}
> +
> +static int virtvdec_vidioc_qbuf(struct file *file, void *priv,
> +                               struct v4l2_buffer *buf)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +
> +       if (V4L2_TYPE_IS_OUTPUT(buf->type))
> +               return vb2_qbuf(&ctx->src_vbq, ctx->dev->vid_dev.v4l2_dev->mdev,
> +                               buf);
> +       else
> +               return vb2_qbuf(&ctx->dst_vbq, ctx->dev->vid_dev.v4l2_dev->mdev,
> +                               buf);
> +}
> +
> +static int virtvdec_vidioc_dqbuf(struct file *file, void *priv,
> +                                struct v4l2_buffer *buf)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       int ret;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(buf->type))
> +               ret = vb2_dqbuf(&ctx->src_vbq, buf, file->f_flags & O_NONBLOCK);
> +       else
> +               ret = vb2_dqbuf(&ctx->dst_vbq, buf, file->f_flags & O_NONBLOCK);
> +
> +       if (ret)
> +               return ret;
> +
> +       if (!V4L2_TYPE_IS_OUTPUT(buf->type) &&
> +           !(buf->flags & V4L2_BUF_FLAG_LAST)) {
> +               int i;
> +
> +               for (i = 0; i < ctx->dst_fmt.num_planes; ++i)
> +                       buf->m.planes[i].bytesused =
> +                               ctx->dst_fmt.plane_fmt[i].sizeimage;
> +       }
> +
> +       return 0;
> +}
> +
> +static int virtvdec_vidioc_expbuf(struct file *file, void *priv,
> +                                 struct v4l2_exportbuffer *eb)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       int ret;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(eb->type))
> +               ret = vb2_expbuf(&ctx->src_vbq, eb);
> +       else
> +               ret = vb2_expbuf(&ctx->dst_vbq, eb);
> +
> +       if (ret)
> +               vdec_err(ctx->dev, "EXPBUF for type %d failed", eb->type);
> +       return ret;
> +}
> +
> +static int virtvdec_vidioc_streamon(struct file *file, void *priv,
> +                                   enum v4l2_buf_type type)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       int ret;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(type))
> +               ret = vb2_streamon(&ctx->src_vbq, type);
> +       else {
> +               switch (ctx->state) {
> +               case VIRTVDEC_STATE_STOPPED:
> +                       ctx->state = VIRTVDEC_STATE_DECODING;
> +                       break;
> +               case VIRTVDEC_STATE_CAPTURE_SETUP:
> +                       ctx->state = VIRTVDEC_STATE_DECODING;
> +                       ret = virtvdec_send_request_ack_stream_info(ctx);
> +                       if (ret) {
> +                               vdec_err(ctx->dev,
> +                                        "failed to send AckStreamInfo");
> +                               return ret;
> +                       }
> +                       break;
> +               default:
> +                       break;
> +               }
> +               ret = vb2_streamon(&ctx->dst_vbq, type);
> +       }
> +       if (ret)
> +               vdec_err(ctx->dev, "STREAMON for type %d failed", type);
> +       return ret;
> +}
> +
> +static int virtvdec_vidioc_streamoff(struct file *file, void *priv,
> +                                    enum v4l2_buf_type type)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       int ret;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(type))
> +               ret = vb2_streamoff(&ctx->src_vbq, type);
> +       else
> +               ret = vb2_streamoff(&ctx->dst_vbq, type);
> +
> +       if (ret)
> +               vdec_err(ctx->dev, "STREAMOFF for type %d failed", type);
> +       return ret;
> +}
> +
> +static int
> +virtvdec_vidioc_subscribe_event(struct v4l2_fh *fh,
> +                               const struct v4l2_event_subscription *sub)
> +{
> +       switch (sub->type) {
> +       case V4L2_EVENT_EOS:
> +               return v4l2_event_subscribe(fh, sub, 2, NULL);
> +       case V4L2_EVENT_SOURCE_CHANGE:
> +               return v4l2_src_change_event_subscribe(fh, sub);
> +       default:
> +               return -EINVAL;
> +       }
> +}
> +
> +static int virtvdec_vidioc_try_decoder_cmd(struct file *file, void *priv,
> +                                          struct v4l2_decoder_cmd *cmd)
> +{
> +       switch (cmd->cmd) {
> +       case V4L2_DEC_CMD_START:
> +       case V4L2_DEC_CMD_STOP:
> +               return 0;
> +       default:
> +               return -EINVAL;
> +       }
> +}
> +
> +static int virtvdec_vidioc_decoder_cmd(struct file *file, void *priv,
> +                                      struct v4l2_decoder_cmd *cmd)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(priv);
> +       int ret;
> +
> +       ret = virtvdec_vidioc_try_decoder_cmd(file, priv, cmd);
> +       if (ret)
> +               return ret;
> +
> +       switch (cmd->cmd) {
> +       case V4L2_DEC_CMD_START:
> +               ctx->state = VIRTVDEC_STATE_DECODING;
> +               /* TODO(keiichiw): Do something else? */
> +
> +               return 0;
> +       case V4L2_DEC_CMD_STOP:
> +               ctx->state = VIRTVDEC_STATE_DRAINING;
> +
> +               if (!vb2_is_streaming(&ctx->src_vbq))
> +                       vdec_debug(ctx->dev,
> +                                  "Output stream is off. No need to drain.");
> +
> +               if (!vb2_is_streaming(&ctx->dst_vbq))
> +                       vdec_debug(ctx->dev,
> +                                  "Capture stream is off. No need to drain.");
> +
> +               ctx->waiting_drained = true;
> +               ret = virtvdec_send_request_drain(ctx);
> +               if (ret)
> +                       vdec_err(ctx->dev, "failed to send drain request: %d",
> +                                ret);
> +               return ret;
> +       default:
> +               return -EINVAL;
> +       }
> +}
> +
> +static const struct v4l2_ioctl_ops virtvdec_ioctl_dec_ops = {
> +       .vidioc_querycap = virtvdec_vidioc_querycap,
> +       .vidioc_enum_framesizes = virtvdec_vidioc_enum_framesizes,
> +       .vidioc_enum_fmt_vid_cap = virtvdec_vidioc_enum_fmt_vid_cap,
> +       .vidioc_enum_fmt_vid_out = virtvdec_vidioc_enum_fmt_vid_out,
> +       .vidioc_g_fmt_vid_cap_mplane = virtvdec_vidioc_fmt_vid_cap_mplane,
> +       .vidioc_g_fmt_vid_out_mplane = virtvdec_vidioc_g_fmt_vid_out_mplane,
> +       .vidioc_try_fmt_vid_cap_mplane = virtvdec_vidioc_fmt_vid_cap_mplane,
> +       .vidioc_try_fmt_vid_out_mplane = virtvdec_vidioc_try_fmt_vid_out_mplane,
> +       .vidioc_s_fmt_vid_cap_mplane = virtvdec_vidioc_s_fmt_vid_cap_mplane,
> +       .vidioc_s_fmt_vid_out_mplane = virtvdec_vidioc_s_fmt_vid_out_mplane,
> +       .vidioc_g_selection = virtvdec_vidioc_g_selection,
> +       .vidioc_reqbufs = virtvdec_vidioc_reqbufs,
> +       .vidioc_querybuf = virtvdec_vidioc_querybuf,
> +       .vidioc_qbuf = virtvdec_vidioc_qbuf,
> +       .vidioc_dqbuf = virtvdec_vidioc_dqbuf,
> +       .vidioc_expbuf = virtvdec_vidioc_expbuf,
> +       .vidioc_streamon = virtvdec_vidioc_streamon,
> +       .vidioc_streamoff = virtvdec_vidioc_streamoff,
> +       .vidioc_subscribe_event = virtvdec_vidioc_subscribe_event,
> +       .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +
> +       .vidioc_decoder_cmd = virtvdec_vidioc_decoder_cmd,
> +       .vidioc_try_decoder_cmd = virtvdec_vidioc_try_decoder_cmd,
> +};
> +
> +/*
> + * Device operations
> + */
> +static int virtvdec_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers,
> +                               unsigned int *num_planes, unsigned int sizes[],
> +                               struct device *alloc_devs[])
> +{
> +       const struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(vq->drv_priv);
> +       const struct v4l2_pix_format_mplane *fmt;
> +       int i;
> +
> +       switch (vq->type) {
> +       case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
> +               fmt = &ctx->src_fmt;
> +               break;
> +       case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
> +               fmt = &ctx->dst_fmt;
> +               break;
> +       default:
> +               vdec_err(ctx->dev, "unsupported buffer type: %d", vq->type);
> +               return -EINVAL;
> +       }
> +
> +       if (*num_buffers < 1)
> +               *num_buffers = 1;
> +
> +       if (*num_buffers > VIDEO_MAX_FRAME)
> +               *num_buffers = VIDEO_MAX_FRAME;
> +
> +       *num_planes = fmt->num_planes;
> +
> +       for (i = 0; i < *num_planes; ++i)
> +               sizes[i] = fmt->plane_fmt[i].sizeimage;
> +
> +       return 0;
> +}
> +
> +/**
> + * virtvdec_get_dma_buf_handle - get a virtgpu's resource handle from vb2_buffer
> + *
> + * This function relies on https://patchwork.kernel.org/patch/11142679/
> + */
> +static int virtvdec_get_dma_buf_handle(struct vb2_buffer *vb, u32 *handle)
> +{
> +       struct sg_table *sgt;
> +       int ret = 0;
> +
> +       sgt = vb2_dma_sg_plane_desc(vb, 0);
> +       if (IS_ERR(sgt))
> +               return PTR_ERR(sgt);
> +
> +       if (sgt->nents != 1)
> +               return -EINVAL;
> +
> +       *handle = sg_dma_address(sgt->sgl);
> +
> +       return ret;
> +}
> +
> +struct virtvdec_registered_handle {
> +       u32 handle;
> +       struct list_head list;
> +};
> +
> +/**
> + * virtvdec_handle_registered - check if a virtgpu handle is already registered
> + * to the host
> + */
> +static bool
> +virtvdec_handle_registered(const struct list_head *registered_handles,
> +                          u32 handle)
> +{
> +       struct virtvdec_registered_handle *cur, *next;
> +
> +       list_for_each_entry_safe(cur, next, registered_handles, list) {
> +               if (cur->handle == handle)
> +                       return true;
> +       }
> +
> +       return false;
> +}
> +
> +static int virtvdec_buf_prepare(struct vb2_buffer *vb)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(vb->vb2_queue->drv_priv);
> +       struct list_head *registered_handles;
> +       u32 *handle_map;
> +       u32 virtio_buf_type;
> +       int ret;
> +       u32 handle = 0;
> +
> +       WARN_ON(!mutex_is_locked(&ctx->dev->lock));
> +
> +       ret = virtvdec_get_dma_buf_handle(vb, &handle);
> +       if (ret) {
> +               vdec_err(
> +                       ctx->dev,
> +                       "failed to get DMA-buf handle for OUTPUT buffer %d: %d",
> +                       vb->index, ret);
> +               return ret;
> +       }
> +
> +       if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
> +               registered_handles = &ctx->outbuf_handles;
> +               handle_map = ctx->outbuf_handle_map;
> +               virtio_buf_type = VIRTIO_VDEC_GUEST_REQ_BUFFER_TYPE_BITSTREAM;
> +       } else {
> +               registered_handles = &ctx->capbuf_handles;
> +               handle_map = ctx->capbuf_handle_map;
> +               virtio_buf_type = VIRTIO_VDEC_GUEST_REQ_BUFFER_TYPE_FRAME;
> +       }
> +
> +       /* Register a resource handle to the host if it's unused so far */
> +       if (!virtvdec_handle_registered(registered_handles, handle)) {
> +               struct virtvdec_registered_handle *registered_handle;
> +
> +               ret = virtvdec_send_request_register_buffer(
> +                       ctx, virtio_buf_type, vb, handle);
> +               if (ret) {
> +                       vdec_err(ctx->dev,
> +                                "failed to register buffer: %d/%d %d", ctx->id,
> +                                vb->index, handle);
> +                       return ret;
> +               }
> +
> +               /* Remember that |handle| is registered */
> +               registered_handle =
> +                       devm_kzalloc(&ctx->dev->virt_dev->dev,
> +                                    sizeof(*registered_handle), GFP_KERNEL);
> +               registered_handle->handle = handle;
> +               list_add_tail(&registered_handle->list, registered_handles);
> +       }
> +
> +       /* Update the mapping from a index to resource handle */
> +       if (handle != handle_map[vb->index]) {
> +               int i;
> +
> +               /* Invalidate obsolete entries in the table */
> +               for (i = 0; i < VIRTIO_VDEC_MAX_BUFFER_COUNT; ++i)
> +                       if (handle_map[i] == handle)
> +                               handle_map[i] = 0;
> +
> +               handle_map[vb->index] = handle;
> +       }
> +
> +       return 0;
> +}
> +
> +static void virtvdec_buf_queue_core(struct vb2_buffer *vb)
> +{
> +       struct vb2_queue *vq = vb->vb2_queue;
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(vq->drv_priv);
> +       struct virtvdec_buf *buf = virtvdec_vb2_to_virtvdec_buf(vb);
> +       int ret;
> +
> +       WARN_ON(!mutex_is_locked(&ctx->dev->lock));
> +
> +       if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
> +               list_add_tail(&buf->list, &ctx->src_queue);
> +
> +               ret = virtvdec_send_request_bitstream_buffer(ctx, vb);
> +
> +               if (ret)
> +                       vdec_err(ctx->dev,
> +                                "failed to send request_bitstream_buffer: %d",
> +                                ret);
> +       } else {
> +               list_add_tail(&buf->list, &ctx->dst_queue);
> +
> +               if (vb->index ==
> +                   VIRTIO_VDEC_DRAINED_NOTIFICATION_BUFFER_INDEX) {
> +                       ctx->stored_capbuf = vb;
> +                       return;
> +               }
> +
> +               ret = virtvdec_send_request_frame_buffer(ctx, vb);
> +               if (ret)
> +                       vdec_err(ctx->dev,
> +                                "failed to send request_frame_buffer");
> +       }
> +}
> +
> +static void virtvdec_buf_queue(struct vb2_buffer *vb)
> +{
> +       struct vb2_queue *vq = vb->vb2_queue;
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(vq->drv_priv);
> +
> +       WARN_ON(!mutex_is_locked(&ctx->dev->lock));
> +
> +       virtvdec_buf_queue_core(vb);
> +       switch (vq->type) {
> +       case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
> +               ctx->capbuf_vb2_map[vb->index] = vb;
> +               return;
> +       case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
> +               ctx->outbuf_vb2_map[vb->index] = vb;
> +               return;
> +       default:
> +               vdec_err(ctx->dev, "unsupported buffer type: %d", vq->type);
> +       }
> +}
> +
> +static int virtvdec_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(vq->drv_priv);
> +
> +       if (ctx->state == VIRTVDEC_STATE_CAPTURE_SETUP)
> +               ctx->state = VIRTVDEC_STATE_DECODING;
> +       return 0;
> +}
> +
> +/**
> + * virtvdec_stop_streaming
> + *
> + * TODO(keiichiw):
> + * Though the user can call stop_streaming for each buffer queue
> + * separately (OUTPUT/CAPTURE), VIRTIO_VDEC_GUEST_REQ_DRAIN affects both
> + * queues. So, we might want to update the protocol. Otherwise, we need
> + * to re-enqueue some requeusts for the other buffer.
> + */
> +static void virtvdec_stop_streaming(struct vb2_queue *vq)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(vq->drv_priv);
> +       struct list_head *registered_handles;
> +       LIST_HEAD(queue);
> +       DEFINE_SUBSCRIPTION_FLUSHED(sub, ctx->id);
> +       int i, ret;
> +       u32 *handle_map;
> +
> +       if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
> +               if (ctx->state != VIRTVDEC_STATE_STOPPED) {
> +                       vdec_err(ctx->dev,
> +                                "stop_streaming for %s at state %d isn't supported",
> +                                V4L2_TYPE_IS_OUTPUT(vq->type) ? "OUTPUT" : "CAPTURE",
> +                                ctx->state);
> +                       return;
> +               }
> +
> +               list_splice_init(&ctx->src_queue, &queue);
> +               registered_handles = &ctx->outbuf_handles;
> +               handle_map = ctx->outbuf_handle_map;
> +       } else {
> +               list_splice_init(&ctx->dst_queue, &queue);
> +               registered_handles = &ctx->capbuf_handles;
> +               handle_map = ctx->capbuf_handle_map;
> +       }
> +
> +       /* Empty registered buffers */
> +       for (i = 0; i < VIRTIO_VDEC_MAX_BUFFER_COUNT; ++i)
> +               handle_map[i] = 0;
> +
> +       while (!list_empty(registered_handles)) {
> +               struct virtvdec_registered_handle *rh;
> +
> +               rh = list_first_entry(registered_handles,
> +                                     struct virtvdec_registered_handle, list);
> +               list_del(&rh->list);
> +       }
> +
> +       /* Send FLUSH request and wait for the response. */
> +       /* TODO(keiichiw): If no buffer, this FLUSH request can be skipped. */
> +       virtvdec_host_req_subscribe(ctx->dev, &sub);
> +       ret = virtvdec_send_request_flush(ctx);
> +       /* TODO(keiichiw): Should the driver signal an error? */
> +       if (ret)
> +               vdec_err(ctx->dev, "failed to send FLUSH request: %d", ret);
> +
> +       virtvdec_host_req_wait(ctx->dev, &sub);
> +
> +       while (!list_empty(&queue)) {
> +               struct virtvdec_buf *b =
> +                       list_first_entry(&queue, struct virtvdec_buf, list);
> +
> +               for (i = 0; i < b->vb.vb2_buf.num_planes; ++i)
> +                       vb2_set_plane_payload(&b->vb.vb2_buf, i, 0);
> +               vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +               list_del(&b->list);
> +       }
> +
> +       if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
> +               if (ctx->state == VIRTVDEC_STATE_STOPPED)
> +                       ctx->state = VIRTVDEC_STATE_SEEK;
> +       } else {
> +               ctx->state = VIRTVDEC_STATE_STOPPED;
> +       }
> +}
> +
> +static const struct vb2_ops virtvdec_qops = {
> +       .queue_setup = virtvdec_queue_setup,
> +       .buf_prepare = virtvdec_buf_prepare,
> +       .buf_queue = virtvdec_buf_queue,
> +       .start_streaming = virtvdec_start_streaming,
> +       .stop_streaming = virtvdec_stop_streaming,
> +       .wait_prepare = vb2_ops_wait_prepare,
> +       .wait_finish = vb2_ops_wait_finish,
> +};
> +
> +static int virtvdec_init_vb2_queue(struct vb2_queue *q,
> +                                  struct virtvdec_ctx *ctx,
> +                                  enum v4l2_buf_type type)
> +{
> +       q->type = type;
> +       q->io_modes = VB2_DMABUF;
> +       q->lock = &ctx->dev->lock;
> +       q->ops = &virtvdec_qops;
> +       q->mem_ops = &vb2_dma_sg_memops;
> +       q->drv_priv = &ctx->fh;
> +       q->buf_struct_size = sizeof(struct virtvdec_buf);
> +       q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +       q->dev = &ctx->dev->virt_dev->dev;
> +       return vb2_queue_init(q);
> +}
> +
> +static void
> +virtvdec_init_v4l2_fmt(const struct virtio_vdec_format_desc *vdec_fmt,
> +                      struct v4l2_pix_format_mplane *v4l2_fmt)
> +{
> +       memset(v4l2_fmt, 0, sizeof(*v4l2_fmt));
> +
> +       v4l2_fmt->pixelformat = vdec_fmt->fourcc;
> +       /* TODO(keiichiw): Is it okay to use max? */
> +       v4l2_fmt->width = vdec_fmt->width.max;
> +       v4l2_fmt->height = vdec_fmt->height.max;
> +}
> +
> +static struct virtio_vdec_format_desc *
> +virtvdec_get_default_format(struct virtvdec_dev *dev,
> +                           const bool bitstream_format)
> +{
> +       int i;
> +       struct virtio_vdec_format_desc *formats;
> +
> +       if (bitstream_format)
> +               formats = dev->src_fmts;
> +       else
> +               formats = dev->dst_fmts;
> +       for (i = 0; i < VIRTIO_VDEC_NUM_FORMATS; ++i) {
> +               if (formats[i].mask)
> +                       return &formats[i];
> +       }
> +
> +       return NULL;
> +}
> +
> +static int virtvdec_init_ctx(struct virtvdec_dev *dev, struct virtvdec_ctx *ctx)
> +{
> +       int ret;
> +
> +       ctx->state = VIRTVDEC_STATE_INITIALIZING;
> +       /* Initialize src/dst formats */
> +       ctx->vdec_src_fmt = virtvdec_get_default_format(dev, true);
> +       virtvdec_init_v4l2_fmt(ctx->vdec_src_fmt, &ctx->src_fmt);
> +       ctx->vdec_dst_fmt = virtvdec_get_default_format(dev, false);
> +       virtvdec_init_v4l2_fmt(ctx->vdec_dst_fmt, &ctx->dst_fmt);
> +       ctx->has_host_session = false;
> +       ret = virtvdec_init_vb2_queue(&ctx->src_vbq, ctx,
> +                                     V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
> +       if (ret) {
> +               vdec_err(dev, "failed to initialize OUTPUT buffer");
> +               return ret;
> +       }
> +       ret = virtvdec_init_vb2_queue(&ctx->dst_vbq, ctx,
> +                                     V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
> +       if (ret) {
> +               vdec_err(dev, "failed to initialize CAPTURE buffer");
> +               return ret;
> +       }
> +
> +       INIT_LIST_HEAD(&ctx->src_queue);
> +       INIT_LIST_HEAD(&ctx->dst_queue);
> +       INIT_LIST_HEAD(&ctx->outbuf_handles);
> +       INIT_LIST_HEAD(&ctx->capbuf_handles);
> +
> +       return 0;
> +}
> +
> +struct virtvdec_ctrl {
> +       u32 id;
> +       s32 min;
> +       s32 max;
> +       s32 step;
> +       s32 def;
> +};
> +
> +static struct virtvdec_ctrl virtvdec_dec_ctrls[] = {
> +       {
> +               .id = V4L2_CID_MIN_BUFFERS_FOR_CAPTURE,
> +               .min = 1,
> +               .max = 32,
> +               .step = 1,
> +               .def = 8,
> +       },
> +};
> +
> +static int virtvdec_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       return 0;
> +}
> +
> +static const struct v4l2_ctrl_ops virtvdec_ctrl_ops = {
> +       .g_volatile_ctrl = virtvdec_g_volatile_ctrl,
> +};
> +
> +static int virtvdec_init_ctrl_handler(struct virtvdec_ctx *ctx)
> +{
> +       struct v4l2_ctrl_handler *hdl = &ctx->ctrl_handler;
> +       int num_ctrls;
> +       int i;
> +       int ret;
> +
> +       num_ctrls = ARRAY_SIZE(virtvdec_dec_ctrls);
> +       ret = v4l2_ctrl_handler_init(hdl, num_ctrls);
> +       if (ret)
> +               return ret;
> +
> +       for (i = 0; i < num_ctrls; ++i) {
> +               struct virtvdec_ctrl *ctrl = &virtvdec_dec_ctrls[i];
> +
> +               v4l2_ctrl_new_std(hdl, &virtvdec_ctrl_ops, ctrl->id, ctrl->min,
> +                                 ctrl->max, ctrl->step, ctrl->def);
> +       }
> +
> +       ret = hdl->error;
> +       if (ret) {
> +               v4l2_ctrl_handler_free(hdl);
> +               return ret;
> +       }
> +
> +       ctx->fh.ctrl_handler = hdl;
> +       return v4l2_ctrl_handler_setup(hdl);
> +}
> +
> +static int virtvdec_open(struct file *file)
> +{
> +       struct virtvdec_dev *dev = video_drvdata(file);
> +       struct virtvdec_ctx *ctx = NULL;
> +       int ret;
> +
> +       /* Initialize ctx */
> +       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +       if (!ctx)
> +               return -ENOMEM;
> +       v4l2_fh_init(&ctx->fh, video_devdata(file));
> +       file->private_data = &ctx->fh;
> +       v4l2_fh_add(&ctx->fh);
> +
> +       ctx->dev = dev;
> +
> +       /*  Control_handler */
> +       ret = virtvdec_init_ctrl_handler(ctx);
> +       if (ret) {
> +               vdec_err(dev, "failed to initialize control handler");
> +               goto error;
> +       }
> +
> +       ret = virtvdec_init_ctx(dev, ctx);
> +       if (ret) {
> +               vdec_err(dev, "failed to initialize ctx");
> +               goto error;
> +       }
> +       return 0;
> +
> +error:
> +       kfree(ctx);
> +       return ret;
> +}
> +
> +static int virtvdec_release(struct file *file)
> +{
> +       struct virtvdec_dev *dev = video_drvdata(file);
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(file->private_data);
> +       int ret;
> +
> +       mutex_lock(&dev->lock);
> +
> +       ret = virtvdec_close_session(ctx);
> +
> +       v4l2_ctrl_handler_free(&ctx->ctrl_handler);
> +       virtvdec_del_context(dev, ctx->id);
> +       kfree(ctx);
> +
> +       mutex_unlock(&dev->lock);
> +       return ret;
> +}
> +
> +static __poll_t virtvdec_poll(struct file *file, poll_table *wait)
> +{
> +       struct virtvdec_ctx *ctx = virtvdec_fh_to_ctx(file->private_data);
> +       struct vb2_queue *src_q, *dst_q;
> +       struct vb2_buffer *src_vb = NULL, *dst_vb = NULL;
> +       unsigned int rc = 0;
> +       unsigned long flags;
> +
> +       src_q = &ctx->src_vbq;
> +       dst_q = &ctx->dst_vbq;
> +
> +       /*
> +        * There has to be at least one buffer queued on each queued_list, which
> +        * means either in driver already or waiting for driver to claim it
> +        * and start processing.
> +        */
> +       if ((!vb2_is_streaming(src_q) || list_empty(&src_q->queued_list)) &&
> +           (!vb2_is_streaming(dst_q) || list_empty(&dst_q->queued_list))) {
> +               return POLLERR;
> +       }
> +
> +       poll_wait(file, &ctx->fh.wait, wait);
> +       poll_wait(file, &src_q->done_wq, wait);
> +       poll_wait(file, &dst_q->done_wq, wait);
> +
> +       if (v4l2_event_pending(&ctx->fh))
> +               rc |= POLLPRI;
> +
> +       spin_lock_irqsave(&src_q->done_lock, flags);
> +
> +       if (!list_empty(&src_q->done_list))
> +               src_vb = list_first_entry(&src_q->done_list, struct vb2_buffer,
> +                                         done_entry);
> +
> +       if (src_vb && (src_vb->state == VB2_BUF_STATE_DONE ||
> +                      src_vb->state == VB2_BUF_STATE_ERROR))
> +               rc |= POLLOUT | POLLWRNORM;
> +
> +       spin_unlock_irqrestore(&src_q->done_lock, flags);
> +
> +       spin_lock_irqsave(&dst_q->done_lock, flags);
> +
> +       if (!list_empty(&dst_q->done_list))
> +               dst_vb = list_first_entry(&dst_q->done_list, struct vb2_buffer,
> +                                         done_entry);
> +
> +       if (dst_vb && (dst_vb->state == VB2_BUF_STATE_DONE ||
> +                      dst_vb->state == VB2_BUF_STATE_ERROR))
> +               rc |= POLLIN | POLLRDNORM;
> +
> +       spin_unlock_irqrestore(&dst_q->done_lock, flags);
> +
> +       return rc;
> +}
> +
> +static const struct v4l2_file_operations virtvdec_fops = {
> +       .owner = THIS_MODULE,
> +       .open = virtvdec_open,
> +       .release = virtvdec_release,
> +       .unlocked_ioctl = video_ioctl2,
> +       .poll = virtvdec_poll,
> +};
> +
> +static int virtvdec_fill_supported_format(struct virtvdec_dev *dev)
> +{
> +       DEFINE_SUBSCRIPTION_QUERY(sub);
> +       int ret;
> +
> +       WARN_ON(!mutex_is_locked(&dev->lock));
> +
> +       virtvdec_host_req_subscribe(dev, &sub);
> +       ret = virtvdec_send_request_query(dev);
> +       if (ret)
> +               return ret;
> +       virtvdec_host_req_wait(dev, &sub);
> +       return sub.result;
> +}
> +
> +static int virtvdec_init_virtqueues(struct virtio_device *vdev,
> +                                   struct virtvdec_dev *dev)
> +{
> +       vq_callback_t *callbacks[] = { virtvdec_vq_in_cb, virtvdec_vq_out_cb };
> +       static const char * const names[] = { "in", "out" };
> +       struct virtqueue *vqs[2];
> +       int ret;
> +
> +       /* Initialize virtqueues */
> +       ret = virtio_find_vqs(vdev, 2, vqs, callbacks, names, NULL);
> +       if (ret) {
> +               dev_err(&vdev->dev, "failed to find virtio vdec queues: %d",
> +                       ret);
> +               return ret;
> +       }
> +       dev->vq_in = vqs[0];
> +       dev->vq_out = vqs[1];
> +
> +       mutex_init(&dev->vq_in_lock);
> +       mutex_init(&dev->vq_out_lock);
> +
> +       INIT_WORK(&dev->vq_in_work, virtvdec_vq_in_work_handler);
> +       INIT_WORK(&dev->vq_out_work, virtvdec_vq_out_work_handler);
> +
> +       init_waitqueue_head(&dev->out_waitq);
> +
> +       return 0;
> +}
> +
> +static int virtvdec_probe(struct virtio_device *virt_dev)
> +{
> +       int ret;
> +       struct virtvdec_dev *dev;
> +       struct video_device *vid_dev;
> +
> +       dev = devm_kzalloc(&virt_dev->dev, sizeof(*dev), GFP_KERNEL);
> +       if (!dev)
> +               return -ENOMEM;
> +       virt_dev->priv = dev;
> +       dev->virt_dev = virt_dev;
> +       /* Initialize counters */
> +       dev->num_ctxs = 1;
> +
> +       mutex_init(&dev->lock);
> +       vid_dev = &dev->vid_dev;
> +       strlcpy(vid_dev->name, "virtio-vdec", sizeof(vid_dev->name));
> +       vid_dev->v4l2_dev = &dev->v4l2_dev;
> +       vid_dev->fops = &virtvdec_fops;
> +       vid_dev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
> +       vid_dev->vfl_dir = VFL_DIR_M2M;
> +       vid_dev->ioctl_ops = &virtvdec_ioctl_dec_ops;
> +       vid_dev->lock = &dev->lock;
> +       vid_dev->release = video_device_release_empty;
> +       video_set_drvdata(vid_dev, dev);
> +
> +       /*  Initialize Virtio device */
> +       ret = virtvdec_init_virtqueues(virt_dev, dev);
> +       if (ret)
> +               return ret;
> +
> +       INIT_LIST_HEAD(&dev->subscriptions);
> +       INIT_LIST_HEAD(&dev->contexts);
> +
> +       ret = virtvdec_init_inqueue(dev);
> +       if (ret) {
> +               dev_err(&virt_dev->dev, "failed to fill in virtqueue: %d", ret);
> +               return ret;
> +       }
> +
> +       virtio_device_ready(virt_dev);
> +       virtqueue_kick(dev->vq_in);
> +
> +       mutex_lock(&dev->lock);
> +       ret = virtvdec_fill_supported_format(dev);
> +       mutex_unlock(&dev->lock);
> +       if (ret)
> +               return ret;
> +
> +       dma_coerce_mask_and_coherent(&virt_dev->dev, DMA_BIT_MASK(64));
> +
> +       ret = v4l2_device_register(&virt_dev->dev, &dev->v4l2_dev);
> +       if (ret) {
> +               dev_err(&virt_dev->dev, "failed registering V4L2 device");
> +               return ret;
> +       }
> +
> +       ret = video_register_device(vid_dev, VFL_TYPE_GRABBER, 0);
> +       if (ret) {
> +               dev_err(&virt_dev->dev, "failed registering video device: %d",
> +                       ret);
> +               goto unregister_v4l2_device;
> +       }
> +
> +       v4l2_info(&dev->v4l2_dev, "Device registered as /dev/video%d",
> +                 vid_dev->num);
> +
> +       return 0;
> +
> +unregister_v4l2_device:
> +       v4l2_device_unregister(&dev->v4l2_dev);
> +       mutex_destroy(&dev->lock);
> +       return ret;
> +}
> +
> +static void virtvdec_remove(struct virtio_device *virt_dev)
> +{
> +       /* TODO(keiichiw): implement */
> +}
> +
> +static struct virtio_device_id id_table[] = {
> +       { VIRTIO_ID_VDEC, VIRTIO_DEV_ANY_ID },
> +       { 0 },
> +};
> +
> +static struct virtio_driver virtvdec_driver = {
> +       .driver.name = KBUILD_MODNAME,
> +       .driver.owner = THIS_MODULE,
> +       .id_table = id_table,
> +       .probe = virtvdec_probe,
> +       .remove = virtvdec_remove,
> +};
> +
> +module_virtio_driver(virtvdec_driver);
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +MODULE_DESCRIPTION("VirtIO video decoder driver");
> +MODULE_AUTHOR("Keiichi Watanabe <keiichiw@xxxxxxxxxxxx>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index 585e07b27333..c0d941e10a1e 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -46,5 +46,6 @@
>  #define VIRTIO_ID_IOMMU        23 /* virtio IOMMU */
>  #define VIRTIO_ID_FS           26 /* virtio filesystem */
>  #define VIRTIO_ID_PMEM         27 /* virtio pmem */
> +#define VIRTIO_ID_VDEC         40 /* virtio video decoder */
>
>  #endif /* _LINUX_VIRTIO_IDS_H */
> diff --git a/include/uapi/linux/virtio_vdec.h b/include/uapi/linux/virtio_vdec.h
> new file mode 100644
> index 000000000000..70dfc2662b79
> --- /dev/null
> +++ b/include/uapi/linux/virtio_vdec.h
> @@ -0,0 +1,224 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * VirtIO Video Decoder
> + *
> + * Copyright 2019 Google LLC.
> + */
> +
> +#ifndef _LINUX_VIRTIO_VDEC_H
> +#define _LINUX_VIRTIO_VDEC_H
> +#include <linux/types.h>
> +
> +#define VIRTIO_VDEC_VQ_OUT 0
> +#define VIRTIO_VDEC_VQ_IN 1
> +#define VIRTIO_VDEC_QUEUE_COUNT 2
> +#define VIRTIO_VDEC_NUM_FORMATS 32
> +
> +/* Same with VIDEO_MAX_PLANES in videodev2.h */
> +#define VIRTIO_VDEC_MAX_PLANES 8
> +
> +/*
> + * Query
> + */
> +struct virtio_vdec_range {
> +       __le32 min;
> +       __le32 max;
> +       __le32 step;
> +};
> +
> +struct virtio_vdec_format_desc {
> +       __le32 fourcc;
> +       /*
> +        * For bitstream formats: Bit (1 << N) corresponds to frame_formats[N].
> +        * For frame formats: Bit (1 << N) corresponds to bitstream_formats[N].
> +        * entry is invalid if 0
> +        */
> +       __le32 mask;
> +       /*
> +        * For bitstream formats: Stream coded resolution.
> +        * For frame formats:   Frame buffer resolution.
> +        */
> +       struct virtio_vdec_range width;
> +       struct virtio_vdec_range height;
> +};
> +
> +struct virtio_vdec_host_req_query {
> +       struct virtio_vdec_format_desc
> +               bitstream_formats[VIRTIO_VDEC_NUM_FORMATS];
> +       struct virtio_vdec_format_desc frame_formats[VIRTIO_VDEC_NUM_FORMATS];
> +};
> +
> +/*
> + * Open
> + */
> +struct virtio_vdec_guest_req_open {
> +       __le32 coded_fourcc;
> +};
> +
> +/*
> + * Set Buffer Count
> + */
> +
> +enum virtio_vdec_buffer_type {
> +       VIRTIO_VDEC_GUEST_REQ_BUFFER_TYPE_BITSTREAM = 0,
> +       VIRTIO_VDEC_GUEST_REQ_BUFFER_TYPE_FRAME = 1,
> +};
> +
> +struct virtio_vdec_guest_req_set_buffer_count {
> +       __le32 type;
> +       __le32 buffer_count;
> +};
> +
> +struct virtio_vdec_host_req_set_buffer_count {
> +       __le32 type;
> +       __le32 buffer_count;
> +};
> +
> +/*
> + * Register Buffer
> + */
> +
> +struct virtio_vdec_guest_req_register_buffer {
> +       __le32 type;
> +       __le32 num_planes;
> +       struct virtio_vdec_plane {
> +               __le32 handle;
> +               __le32 offset;
> +               __le32 length;
> +       } planes[VIRTIO_VDEC_MAX_PLANES];
> +};
> +
> +struct virtio_vdec_host_req_register_buffer {
> +       __le32 type;
> +       __le32 handles[VIRTIO_VDEC_MAX_PLANES];
> +};
> +
> +/*
> + * Decode
> + */
> +
> +struct virtio_vdec_guest_req_frame_buffer {
> +       struct virtio_vdec_frame_buffer_plane {
> +               __le32 handle;
> +               __le32 offset;
> +               __le32 stride;
> +               __le32 length; /* length of plane data */
> +       } planes[VIRTIO_VDEC_MAX_PLANES];
> +};
> +
> +struct virtio_vdec_guest_req_bitstream_buffer {
> +       __le32 handle;
> +       __le32 offset;
> +       __le32 length; /* length of valid data */
> +       __le64 cookie;
> +};
> +
> +/*
> + * Stream info
> + */
> +
> +struct virtio_vdec_host_req_stream_info {
> +       __le32 raw_fourcc;
> +       __le32 fb_width;
> +       __le32 fb_height;
> +       __le32 min_frame_buffer_count;
> +       __le32 max_frame_buffer_count;
> +       struct virtio_vdec_host_req_stream_crop {
> +               __le32 left;
> +               __le32 top;
> +               __le32 width;
> +               __le32 height;
> +       } crop;
> +};
> +
> +struct virtio_vdec_host_req_frame_buffer {
> +       __le32 handles[VIRTIO_VDEC_MAX_PLANES];
> +       __le64 cookie;
> +};
> +
> +struct virtio_vdec_host_req_bitstream_buffer {
> +       __le32 handle;
> +};
> +
> +/*
> + * Guest/Host Requests
> + */
> +
> +enum virtio_vdec_guest_req_type {
> +       VIRTIO_VDEC_GUEST_REQ_UNDEFINED = 0,
> +
> +       /* Global */
> +       VIRTIO_VDEC_GUEST_REQ_QUERY = 0x0100,
> +
> +       /* Per instance */
> +       VIRTIO_VDEC_GUEST_REQ_OPEN = 0x0200,
> +       VIRTIO_VDEC_GUEST_REQ_SET_BUFFER_COUNT,
> +       VIRTIO_VDEC_GUEST_REQ_REGISTER_BUFFER,
> +       VIRTIO_VDEC_GUEST_REQ_BITSTREAM_BUFFER,
> +       VIRTIO_VDEC_GUEST_REQ_ACK_STREAM_INFO,
> +       VIRTIO_VDEC_GUEST_REQ_FRAME_BUFFER,
> +       VIRTIO_VDEC_GUEST_REQ_DRAIN,
> +       VIRTIO_VDEC_GUEST_REQ_FLUSH,
> +       VIRTIO_VDEC_GUEST_REQ_CLOSE,
> +};
> +
> +struct virtio_vdec_guest_req {
> +       __le32 type;
> +       __le32 instance_id;
> +       __le32 reserved;
> +       union {
> +               struct virtio_vdec_guest_req_open open;
> +               struct virtio_vdec_guest_req_set_buffer_count set_buffer_count;
> +               struct virtio_vdec_guest_req_register_buffer register_buffer;
> +               struct virtio_vdec_guest_req_bitstream_buffer bitstream_buffer;
> +               struct virtio_vdec_guest_req_frame_buffer frame_buffer;
> +       };
> +};
> +
> +enum virtio_vdec_host_req_type {
> +       VIRTIO_VDEC_HOST_REQ_UNDEFINED = 0,
> +
> +       /* Global */
> +       VIRTIO_VDEC_HOST_REQ_QUERY = 0x0100,
> +
> +       /* Per instance */
> +       VIRTIO_VDEC_HOST_REQ_OPEN = 0x0200,
> +       VIRTIO_VDEC_HOST_REQ_SET_BUFFER_COUNT,
> +       VIRTIO_VDEC_HOST_REQ_REGISTER_BUFFER,
> +       VIRTIO_VDEC_HOST_REQ_BITSTREAM_BUFFER,
> +       VIRTIO_VDEC_HOST_REQ_STREAM_INFO,
> +       VIRTIO_VDEC_HOST_REQ_FRAME_BUFFER,
> +       VIRTIO_VDEC_HOST_REQ_DRAINED,
> +       VIRTIO_VDEC_HOST_REQ_FLUSHED,
> +       VIRTIO_VDEC_HOST_REQ_CLOSE,
> +       VIRTIO_VDEC_HOST_REQ_EOS,
> +
> +       /* Global Error response */
> +       VIRTIO_VDEC_HOST_REQ_NOTIFY_GLOBAL_ERROR = 0x1100,
> +};
> +
> +enum virtio_vdec_host_req_result {
> +       /* Success */
> +       VIRTIO_VDEC_HOST_REQ_RESULT_SUCCESS = 0,
> +
> +       /* Error */
> +       VIRTIO_VDEC_HOST_REQ_RESULT_ERROR_UNSPEC = 0x1000,
> +       VIRTIO_VDEC_HOST_REQ_RESULT_ERROR_INVALID_REQUEST,
> +       VIRTIO_VDEC_HOST_REQ_RESULT_ERROR_INVALID_INSTANCE_ID,
> +};
> +
> +struct virtio_vdec_host_req {
> +       __le32 type; /* VIRTIO_VDEC_HOST_REQ_* */
> +       __le32 result; /* VIRTIO_VDEC_HOST_REQ_RESULT_ */
> +       __le32 instance_id;
> +       __le32 reserved; /* for 64-bit alignment */
> +       union {
> +               struct virtio_vdec_host_req_query query;
> +               struct virtio_vdec_host_req_set_buffer_count set_buffer_count;
> +               struct virtio_vdec_host_req_register_buffer register_buffer;
> +               struct virtio_vdec_host_req_bitstream_buffer bitstream_buffer;
> +               struct virtio_vdec_host_req_stream_info stream_info;
> +               struct virtio_vdec_host_req_frame_buffer frame_buffer;
> +       };
> +};
> +#endif /* _LINUX_VIRTIO_VDEC_H */
> --
> 2.24.0.rc0.303.g954a862665-goog
>




[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