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@chromium.org>");
+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 */
