lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20210810220552.298140-3-daniel.almeida@collabora.com>
Date:   Tue, 10 Aug 2021 19:05:52 -0300
From:   daniel.almeida@...labora.com
To:     stevecho@...gle.com, shawnku@...gle.com, tzungbi@...gle.com,
        mcasas@...gle.com, nhebert@...gle.com, abodenha@...gle.com,
        randy.wu@...iatek.com, yunfei.dong@...iatek.com,
        gustavo.padovan@...labora.com, andrzej.pietrasiewicz@...labora.com,
        enric.balletbo@...labora.com, ezequiel@...labora.com,
        nicolas.dufresne@...labora.com, tomeu.vizoso@...labora.com,
        nick.milner@...labora.com, xiaoyong.lu@...iatek.com,
        mchehab@...nel.org, hverkuil-cisco@...all.nl
Cc:     Daniel Almeida <daniel.almeida@...labora.com>,
        linux-media@...r.kernel.org, linux-kernel@...r.kernel.org,
        kernel@...labora.com
Subject: [RFC PATCH 2/2] media: vivpu: add virtual VPU driver

From: Daniel Almeida <daniel.almeida@...labora.com>

Add a virtual VPU driver to aid userspace in testing stateless uAPI
implementations for which no hardware is currently available.

A userspace implementation can use vivpu to run a decoding loop even
when no hardware is available or when the kernel uAPI for the codec
has not been upstreamed yet. This can reveal bugs at an early stage.

Also makes it possible to work on the kernel uAPI for a codec and
a corresponding userspace implementation at the same time.

Signed-off-by: Daniel Almeida <daniel.almeida@...labora.com>
---
 drivers/media/test-drivers/Kconfig            |   1 +
 drivers/media/test-drivers/Makefile           |   1 +
 drivers/media/test-drivers/vivpu/Kconfig      |  16 +
 drivers/media/test-drivers/vivpu/Makefile     |   4 +
 drivers/media/test-drivers/vivpu/vivpu-core.c | 418 ++++++++++++
 drivers/media/test-drivers/vivpu/vivpu-dec.c  | 491 ++++++++++++++
 drivers/media/test-drivers/vivpu/vivpu-dec.h  |  61 ++
 .../media/test-drivers/vivpu/vivpu-video.c    | 599 ++++++++++++++++++
 .../media/test-drivers/vivpu/vivpu-video.h    |  46 ++
 drivers/media/test-drivers/vivpu/vivpu.h      | 119 ++++
 10 files changed, 1756 insertions(+)
 create mode 100644 drivers/media/test-drivers/vivpu/Kconfig
 create mode 100644 drivers/media/test-drivers/vivpu/Makefile
 create mode 100644 drivers/media/test-drivers/vivpu/vivpu-core.c
 create mode 100644 drivers/media/test-drivers/vivpu/vivpu-dec.c
 create mode 100644 drivers/media/test-drivers/vivpu/vivpu-dec.h
 create mode 100644 drivers/media/test-drivers/vivpu/vivpu-video.c
 create mode 100644 drivers/media/test-drivers/vivpu/vivpu-video.h
 create mode 100644 drivers/media/test-drivers/vivpu/vivpu.h

diff --git a/drivers/media/test-drivers/Kconfig b/drivers/media/test-drivers/Kconfig
index e27d6602545d..b426cef7fc88 100644
--- a/drivers/media/test-drivers/Kconfig
+++ b/drivers/media/test-drivers/Kconfig
@@ -22,6 +22,7 @@ config VIDEO_VIM2M
 	  framework.
 
 source "drivers/media/test-drivers/vicodec/Kconfig"
+source "drivers/media/test-drivers/vivpu/Kconfig"
 
 endif #V4L_TEST_DRIVERS
 
diff --git a/drivers/media/test-drivers/Makefile b/drivers/media/test-drivers/Makefile
index 9f0e4ebb2efe..a4fadccc4b95 100644
--- a/drivers/media/test-drivers/Makefile
+++ b/drivers/media/test-drivers/Makefile
@@ -7,4 +7,5 @@ obj-$(CONFIG_VIDEO_VIMC)		+= vimc/
 obj-$(CONFIG_VIDEO_VIVID)		+= vivid/
 obj-$(CONFIG_VIDEO_VIM2M)		+= vim2m.o
 obj-$(CONFIG_VIDEO_VICODEC)		+= vicodec/
+obj-$(CONFIG_VIDEO_VIVPU)		+= vivpu/
 obj-$(CONFIG_DVB_VIDTV)			+= vidtv/
diff --git a/drivers/media/test-drivers/vivpu/Kconfig b/drivers/media/test-drivers/vivpu/Kconfig
new file mode 100644
index 000000000000..1e6267418d19
--- /dev/null
+++ b/drivers/media/test-drivers/vivpu/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_VIVPU
+	tristate "Virtual VPU Driver (vivpu)"
+	depends on VIDEO_DEV && VIDEO_V4L2
+	select VIDEOBUF2_VMALLOC
+	select V4L2_MEM2MEM_DEV
+	select MEDIA_CONTROLLER
+	select MEDIA_CONTROLLER_REQUEST_API
+	help
+	  A virtual stateless VPU example device for uAPI development purposes.
+
+	  A userspace implementation can use vivpu to run a decoding loop even
+	  when no hardware is available or when the kernel uAPI for the codec
+	  has not been upstreamed yet. This can reveal bugs at an early stage
+
+	  When in doubt, say N.
diff --git a/drivers/media/test-drivers/vivpu/Makefile b/drivers/media/test-drivers/vivpu/Makefile
new file mode 100644
index 000000000000..d20a1dbbd9e5
--- /dev/null
+++ b/drivers/media/test-drivers/vivpu/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+vivpu-y := vivpu-core.o vivpu-video.o vivpu-dec.o
+
+obj-$(CONFIG_VIDEO_VIVPU) += vivpu.o
diff --git a/drivers/media/test-drivers/vivpu/vivpu-core.c b/drivers/media/test-drivers/vivpu/vivpu-core.c
new file mode 100644
index 000000000000..1eb1ce33bab1
--- /dev/null
+++ b/drivers/media/test-drivers/vivpu/vivpu-core.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * A virtual stateless VPU example device for uAPI development purposes.
+ *
+ * A userspace implementation can use vivpu to run a decoding loop even
+ * when no hardware is available or when the kernel uAPI for the codec
+ * has not been upstreamed yet. This can reveal bugs at an early stage.
+ *
+ * Copyright (c) Collabora, Ltd.
+ *
+ * Based on the vim2m driver, that is:
+ *
+ * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
+ * Pawel Osciak, <pawel@...iak.com>
+ * Marek Szyprowski, <m.szyprowski@...sung.com>
+ *
+ * Based on the vicodec driver, that is:
+ *
+ * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@...e-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@...tlin.com>
+ * Copyright (C) 2018 Bootlin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/font.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+
+#include "vivpu.h"
+#include "vivpu-dec.h"
+#include "vivpu-video.h"
+
+unsigned int vivpu_debug;
+module_param(vivpu_debug, uint, 0644);
+MODULE_PARM_DESC(vivpu_debug, " activates debug info");
+
+const unsigned int vivpu_src_default_w = 640;
+const unsigned int vivpu_src_default_h = 480;
+const unsigned int vivpu_src_default_depth = 8;
+
+unsigned int vivpu_transtime;
+module_param(vivpu_transtime, uint, 0644);
+MODULE_PARM_DESC(vivpu_transtime, " simulated process time.");
+
+struct v4l2_ctrl *vivpu_find_control(struct vivpu_ctx *ctx, u32 id)
+{
+	unsigned int i;
+
+	for (i = 0; ctx->ctrls[i]; i++)
+		if (ctx->ctrls[i]->id == id)
+			return ctx->ctrls[i];
+
+	return NULL;
+}
+
+void *vivpu_find_control_data(struct vivpu_ctx *ctx, u32 id)
+{
+	struct v4l2_ctrl *ctrl;
+
+	ctrl = vivpu_find_control(ctx, id);
+	if (ctrl)
+		return ctrl->p_cur.p;
+
+	return NULL;
+}
+
+u32 vivpu_control_num_elems(struct vivpu_ctx *ctx, u32 id)
+{
+	struct v4l2_ctrl *ctrl;
+
+	ctrl = vivpu_find_control(ctx, id);
+	if (ctrl)
+		return ctrl->elems;
+
+	return 0;
+}
+
+static void vivpu_device_release(struct video_device *vdev)
+{
+	struct vivpu_dev *dev = container_of(vdev, struct vivpu_dev, vfd);
+
+	v4l2_device_unregister(&dev->v4l2_dev);
+	v4l2_m2m_release(dev->m2m_dev);
+	media_device_cleanup(&dev->mdev);
+	kfree(dev);
+}
+
+static const struct vivpu_control vivpu_controls[] = {
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_FRAME_HEADER,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_SEQUENCE,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_TILE_GROUP,
+		.cfg.dims = { V4L2_AV1_MAX_TILE_COUNT },
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY,
+		.cfg.dims = { V4L2_AV1_MAX_TILE_COUNT },
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_TILE_LIST,
+		.cfg.dims = { V4L2_AV1_MAX_TILE_COUNT },
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_TILE_LIST_ENTRY,
+		.cfg.dims = { V4L2_AV1_MAX_TILE_COUNT },
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_PROFILE,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_LEVEL,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_AV1_OPERATING_MODE,
+	},
+};
+
+#define VIVPU_CONTROLS_COUNT	ARRAY_SIZE(vivpu_controls)
+
+static int vivpu_init_ctrls(struct vivpu_ctx *ctx)
+{
+	struct vivpu_dev *dev = ctx->dev;
+	struct v4l2_ctrl_handler *hdl = &ctx->hdl;
+	struct v4l2_ctrl *ctrl;
+	unsigned int ctrl_size;
+	unsigned int i;
+
+	v4l2_ctrl_handler_init(hdl, VIVPU_CONTROLS_COUNT);
+	if (hdl->error) {
+		v4l2_err(&dev->v4l2_dev,
+			 "Failed to initialize control handler\n");
+		return hdl->error;
+	}
+
+	ctrl_size = sizeof(ctrl) * VIVPU_CONTROLS_COUNT + 1;
+
+	ctx->ctrls = kzalloc(ctrl_size, GFP_KERNEL);
+	if (!ctx->ctrls)
+		return -ENOMEM;
+
+	for (i = 0; i < VIVPU_CONTROLS_COUNT; i++) {
+		ctrl = v4l2_ctrl_new_custom(hdl, &vivpu_controls[i].cfg,
+					    NULL);
+		if (hdl->error) {
+			v4l2_err(&dev->v4l2_dev,
+				 "Failed to create new custom control, errno: %d\n",
+				 hdl->error);
+
+			return hdl->error;
+		}
+
+		ctx->ctrls[i] = ctrl;
+	}
+
+	ctx->fh.ctrl_handler = hdl;
+	v4l2_ctrl_handler_setup(hdl);
+
+	return 0;
+}
+
+static void vivpu_free_ctrls(struct vivpu_ctx *ctx)
+{
+	kfree(ctx->ctrls);
+	v4l2_ctrl_handler_free(&ctx->hdl);
+}
+
+static int vivpu_open(struct file *file)
+{
+	struct vivpu_dev *dev = video_drvdata(file);
+	struct vivpu_ctx *ctx = NULL;
+	int rc = 0;
+
+	if (mutex_lock_interruptible(&dev->dev_mutex))
+		return -ERESTARTSYS;
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx) {
+		rc = -ENOMEM;
+		goto unlock;
+	}
+
+	v4l2_fh_init(&ctx->fh, video_devdata(file));
+	file->private_data = &ctx->fh;
+	ctx->dev = dev;
+
+	rc = vivpu_init_ctrls(ctx);
+	if (rc)
+		goto free_ctx;
+
+	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &vivpu_queue_init);
+
+	mutex_init(&ctx->vb_mutex);
+
+	if (IS_ERR(ctx->fh.m2m_ctx)) {
+		rc = PTR_ERR(ctx->fh.m2m_ctx);
+		goto free_hdl;
+	}
+
+	vivpu_set_default_format(ctx);
+
+	v4l2_fh_add(&ctx->fh);
+
+	dprintk(dev, "Created instance: %p, m2m_ctx: %p\n",
+		ctx, ctx->fh.m2m_ctx);
+
+	mutex_unlock(&dev->dev_mutex);
+	return rc;
+
+free_hdl:
+	vivpu_free_ctrls(ctx);
+	v4l2_fh_exit(&ctx->fh);
+free_ctx:
+	kfree(ctx);
+unlock:
+	mutex_unlock(&dev->dev_mutex);
+	return rc;
+}
+
+static int vivpu_release(struct file *file)
+{
+	struct vivpu_dev *dev = video_drvdata(file);
+	struct vivpu_ctx *ctx = vivpu_file_to_ctx(file);
+
+	dprintk(dev, "Releasing instance %p\n", ctx);
+
+	v4l2_fh_del(&ctx->fh);
+	v4l2_fh_exit(&ctx->fh);
+	vivpu_free_ctrls(ctx);
+	mutex_lock(&dev->dev_mutex);
+	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+	mutex_unlock(&dev->dev_mutex);
+	kfree(ctx);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations vivpu_fops = {
+	.owner		= THIS_MODULE,
+	.open		= vivpu_open,
+	.release	= vivpu_release,
+	.poll		= v4l2_m2m_fop_poll,
+	.unlocked_ioctl	= video_ioctl2,
+	.mmap		= v4l2_m2m_fop_mmap,
+};
+
+static const struct video_device vivpu_videodev = {
+	.name		= VIVPU_NAME,
+	.vfl_dir	= VFL_DIR_M2M,
+	.fops		= &vivpu_fops,
+	.ioctl_ops	= &vivpu_ioctl_ops,
+	.minor		= -1,
+	.release	= vivpu_device_release,
+	.device_caps	= V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING,
+};
+
+static const struct v4l2_m2m_ops vivpu_m2m_ops = {
+	.device_run	= vivpu_device_run,
+};
+
+static const struct media_device_ops vivpu_m2m_media_ops = {
+	.req_validate	= vivpu_request_validate,
+	.req_queue	= v4l2_m2m_request_queue,
+};
+
+static int vivpu_probe(struct platform_device *pdev)
+{
+	struct vivpu_dev *dev;
+	struct video_device *vfd;
+	int ret;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+	if (ret)
+		goto error_vivpu_dev;
+
+	mutex_init(&dev->dev_mutex);
+
+	dev->vfd = vivpu_videodev;
+	vfd = &dev->vfd;
+	vfd->lock = &dev->dev_mutex;
+	vfd->v4l2_dev = &dev->v4l2_dev;
+
+	video_set_drvdata(vfd, dev);
+	v4l2_info(&dev->v4l2_dev,
+		  "Device registered as /dev/video%d\n", vfd->num);
+
+	platform_set_drvdata(pdev, dev);
+
+	dev->m2m_dev = v4l2_m2m_init(&vivpu_m2m_ops);
+	if (IS_ERR(dev->m2m_dev)) {
+		v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n");
+		ret = PTR_ERR(dev->m2m_dev);
+		dev->m2m_dev = NULL;
+		goto error_dev;
+	}
+
+	dev->mdev.dev = &pdev->dev;
+	strscpy(dev->mdev.model, "vivpu", sizeof(dev->mdev.model));
+	strscpy(dev->mdev.bus_info, "platform:vivpu",
+		sizeof(dev->mdev.bus_info));
+	media_device_init(&dev->mdev);
+	dev->mdev.ops = &vivpu_m2m_media_ops;
+	dev->v4l2_dev.mdev = &dev->mdev;
+
+	ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+		goto error_m2m;
+	}
+
+	ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd,
+						 MEDIA_ENT_F_PROC_VIDEO_DECODER);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n");
+		goto error_v4l2;
+	}
+
+	ret = media_device_register(&dev->mdev);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev, "Failed to register mem2mem media device\n");
+		goto error_m2m_mc;
+	}
+
+	return 0;
+
+error_m2m_mc:
+	v4l2_m2m_unregister_media_controller(dev->m2m_dev);
+error_v4l2:
+	video_unregister_device(&dev->vfd);
+	/* vivpu_device_release called by video_unregister_device to release various objects */
+	return ret;
+error_m2m:
+	v4l2_m2m_release(dev->m2m_dev);
+error_dev:
+	v4l2_device_unregister(&dev->v4l2_dev);
+error_vivpu_dev:
+	kfree(dev);
+
+	return ret;
+}
+
+static int vivpu_remove(struct platform_device *pdev)
+{
+	struct vivpu_dev *dev = platform_get_drvdata(pdev);
+
+	v4l2_info(&dev->v4l2_dev, "Removing " VIVPU_NAME);
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	if (media_devnode_is_registered(dev->mdev.devnode)) {
+		media_device_unregister(&dev->mdev);
+		v4l2_m2m_unregister_media_controller(dev->m2m_dev);
+	}
+#endif
+	video_unregister_device(&dev->vfd);
+
+	return 0;
+}
+
+static struct platform_driver vivpu_pdrv = {
+	.probe		= vivpu_probe,
+	.remove		= vivpu_remove,
+	.driver		= {
+		.name	= VIVPU_NAME,
+	},
+};
+
+static void vivpu_dev_release(struct device *dev) {}
+
+static struct platform_device vivpu_pdev = {
+	.name		= VIVPU_NAME,
+	.dev.release	= vivpu_dev_release,
+};
+
+static void __exit vivpu_exit(void)
+{
+	platform_driver_unregister(&vivpu_pdrv);
+	platform_device_unregister(&vivpu_pdev);
+}
+
+static int __init vivpu_init(void)
+{
+	int ret;
+
+	ret = platform_device_register(&vivpu_pdev);
+	if (ret)
+		return ret;
+
+	ret = platform_driver_register(&vivpu_pdrv);
+	if (ret)
+		platform_device_unregister(&vivpu_pdev);
+
+	return ret;
+}
+
+MODULE_DESCRIPTION("Virtual VPU device");
+MODULE_AUTHOR("Daniel Almeida <daniel.almeida@...labora.com>");
+MODULE_LICENSE("GPL v2");
+
+module_init(vivpu_init);
+module_exit(vivpu_exit);
diff --git a/drivers/media/test-drivers/vivpu/vivpu-dec.c b/drivers/media/test-drivers/vivpu/vivpu-dec.c
new file mode 100644
index 000000000000..f928768aff77
--- /dev/null
+++ b/drivers/media/test-drivers/vivpu/vivpu-dec.c
@@ -0,0 +1,491 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * A virtual stateless VPU example device for uAPI development purposes.
+ *
+ * A userspace implementation can use vivpu to run a decoding loop even
+ * when no hardware is available or when the kernel uAPI for the codec
+ * has not been upstreamed yet. This can reveal bugs at an early stage.
+ *
+ * Copyright (c) Collabora, Ltd.
+ *
+ * Based on the vim2m driver, that is:
+ *
+ * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
+ * Pawel Osciak, <pawel@...iak.com>
+ * Marek Szyprowski, <m.szyprowski@...sung.com>
+ *
+ * Based on the vicodec driver, that is:
+ *
+ * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@...e-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@...tlin.com>
+ * Copyright (C) 2018 Bootlin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version
+ */
+
+#include "vivpu.h"
+#include "vivpu-dec.h"
+
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/tpg/v4l2-tpg.h>
+
+static void
+vivpu_av1_check_reference_frames(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	u32 i;
+	int idx;
+	const struct v4l2_ctrl_av1_frame_header *f;
+	struct vb2_queue *capture_queue;
+
+	f = run->av1.frame_header;
+	capture_queue = &ctx->fh.m2m_ctx->cap_q_ctx.q;
+
+	/*
+	 * For every reference frame timestamp, make sure we can actually find
+	 * the buffer in the CAPTURE queue.
+	 */
+	for (i = 0; i < V4L2_AV1_NUM_REF_FRAMES; i++) {
+		idx = vb2_find_timestamp(capture_queue, f->reference_frame_ts[i], 0);
+		if (idx < 0)
+			v4l2_err(&ctx->dev->v4l2_dev,
+				 "no capture buffer found for reference_frame_ts[%d] %llu",
+				 i, f->reference_frame_ts[i]);
+		else
+			dprintk(ctx->dev,
+				"found capture buffer %d for reference_frame_ts[%d] %llu\n",
+				idx, i, f->reference_frame_ts[i]);
+	}
+}
+
+static void vivpu_dump_av1_seq(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_ctrl_av1_sequence *seq = run->av1.sequence;
+
+	dprintk(ctx->dev, "AV1 Sequence\n");
+	dprintk(ctx->dev, "flags %d\n", seq->flags);
+	dprintk(ctx->dev, "profile %d\n", seq->seq_profile);
+	dprintk(ctx->dev, "order_hint_bits %d\n", seq->order_hint_bits);
+	dprintk(ctx->dev, "bit_depth %d\n", seq->bit_depth);
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_tile_group(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_ctrl_av1_tile_group *tg;
+	u32 n;
+	u32 i;
+
+	n = vivpu_control_num_elems(ctx, V4L2_CID_STATELESS_AV1_TILE_GROUP);
+	for (i = 0; i < n; i++) {
+		tg = &run->av1.tile_group[i];
+		dprintk(ctx->dev, "AV1 Tile Group\n");
+		dprintk(ctx->dev, "flags %d\n", tg->flags);
+		dprintk(ctx->dev, "tg_start %d\n", tg->tg_start);
+		dprintk(ctx->dev, "tg_end %d\n", tg->tg_end);
+		dprintk(ctx->dev, "\n");
+	}
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_tile_group_entry(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_ctrl_av1_tile_group_entry *tge;
+	u32 n;
+	u32 i;
+
+	n = vivpu_control_num_elems(ctx, V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY);
+	for (i = 0; i < n; i++) {
+		tge = &run->av1.tg_entries[i];
+		dprintk(ctx->dev, "AV1 Tile Group Entry\n");
+		dprintk(ctx->dev, "tile_offset %d\n", tge->tile_offset);
+		dprintk(ctx->dev, "tile_size %d\n", tge->tile_size);
+		dprintk(ctx->dev, "tile_row %d\n", tge->tile_row);
+		dprintk(ctx->dev, "tile_col %d\n", tge->tile_col);
+
+		dprintk(ctx->dev, "\n");
+	}
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_tile_list(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_ctrl_av1_tile_list *tl;
+	u32 n;
+	u32 i;
+
+	n = vivpu_control_num_elems(ctx, V4L2_CID_STATELESS_AV1_TILE_LIST);
+	for (i = 0; i < n; i++) {
+		tl = &run->av1.tile_list[i];
+		dprintk(ctx->dev, "AV1 Tile List\n");
+		dprintk(ctx->dev, "output_frame_width_in_tiles_minus_1 %d\n",
+			tl->output_frame_width_in_tiles_minus_1);
+		dprintk(ctx->dev, "output_frame_height_in_tiles_minus_1 %d\n",
+			tl->output_frame_height_in_tiles_minus_1);
+		dprintk(ctx->dev, "tile_count_minus_1 %d\n",
+			tl->tile_count_minus_1);
+		dprintk(ctx->dev, "\n");
+	}
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_tile_list_entry(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_ctrl_av1_tile_list_entry *tle;
+	u32 n;
+	u32 i;
+
+	n = vivpu_control_num_elems(ctx, V4L2_CID_STATELESS_AV1_TILE_LIST_ENTRY);
+	for (i = 0; i < n; i++) {
+		tle = &run->av1.tl_entries[i];
+		dprintk(ctx->dev, "AV1 Tile List Entry\n");
+		dprintk(ctx->dev, "anchor_frame_idx %d\n", tle->anchor_frame_idx);
+		dprintk(ctx->dev, "anchor_tile_row %d\n", tle->anchor_tile_row);
+		dprintk(ctx->dev, "anchor_tile_col %d\n", tle->anchor_tile_col);
+		dprintk(ctx->dev, "tile_data_size_minus_1 %d\n",
+			tle->tile_data_size_minus_1);
+		dprintk(ctx->dev, "\n");
+	}
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_quantization(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_av1_quantization *q = &run->av1.frame_header->quantization;
+
+	dprintk(ctx->dev, "AV1 Quantization\n");
+	dprintk(ctx->dev, "flags %d\n", q->flags);
+	dprintk(ctx->dev, "base_q_idx %d\n", q->base_q_idx);
+	dprintk(ctx->dev, "delta_q_y_dc %d\n", q->delta_q_y_dc);
+	dprintk(ctx->dev, "delta_q_u_dc %d\n", q->delta_q_u_dc);
+	dprintk(ctx->dev, "delta_q_u_ac %d\n", q->delta_q_u_ac);
+	dprintk(ctx->dev, "delta_q_v_dc %d\n", q->delta_q_v_dc);
+	dprintk(ctx->dev, "delta_q_v_ac %d\n", q->delta_q_v_ac);
+	dprintk(ctx->dev, "qm_y %d\n", q->qm_y);
+	dprintk(ctx->dev, "qm_u %d\n", q->qm_u);
+	dprintk(ctx->dev, "qm_v %d\n", q->qm_v);
+	dprintk(ctx->dev, "delta_q_res %d\n", q->delta_q_res);
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_segmentation(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_av1_segmentation *s = &run->av1.frame_header->segmentation;
+	u32 i;
+	u32 j;
+
+	dprintk(ctx->dev, "AV1 Segmentation\n");
+	dprintk(ctx->dev, "flags %d\n", s->flags);
+
+	for (i = 0; i < ARRAY_SIZE(s->feature_enabled); i++)
+		dprintk(ctx->dev,
+			"feature_enabled[%d] %d\n",
+			i, s->feature_enabled[i]);
+
+	for (i = 0; i < V4L2_AV1_MAX_SEGMENTS; i++)
+		for (j = 0; j < V4L2_AV1_SEG_LVL_MAX; j++)
+			dprintk(ctx->dev,
+				"feature_data[%d][%d] %d\n",
+				i, j, s->feature_data[i][j]);
+
+	dprintk(ctx->dev, "last_active_seg_id %d\n", s->last_active_seg_id);
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_loop_filter(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_av1_loop_filter *lf = &run->av1.frame_header->loop_filter;
+	u32 i;
+
+	dprintk(ctx->dev, "AV1 Loop Filter\n");
+	dprintk(ctx->dev, "flags %d\n", lf->flags);
+
+	for (i = 0; i < ARRAY_SIZE(lf->level); i++)
+		dprintk(ctx->dev, "level[%d] %d\n", i, lf->level[i]);
+
+	dprintk(ctx->dev, "sharpness %d\n", lf->sharpness);
+
+	for (i = 0; i < ARRAY_SIZE(lf->ref_deltas); i++)
+		dprintk(ctx->dev, "ref_deltas[%d] %d\n", i, lf->ref_deltas[i]);
+
+	for (i = 0; i < ARRAY_SIZE(lf->mode_deltas); i++)
+		dprintk(ctx->dev, "mode_deltas[%d], %d\n", i, lf->mode_deltas[i]);
+
+	dprintk(ctx->dev, "delta_lf_res %d\n", lf->delta_lf_res);
+	dprintk(ctx->dev, "delta_lf_multi %d\n", lf->delta_lf_multi);
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_loop_restoration(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_av1_loop_restoration *lr;
+	u32 i;
+
+	lr = &run->av1.frame_header->loop_restoration;
+	dprintk(ctx->dev, "AV1 Loop Restoration\n");
+
+	for (i = 0; i < ARRAY_SIZE(lr->frame_restoration_type); i++)
+		dprintk(ctx->dev, "frame_restoration_type[%d] %d\n", i,
+			lr->frame_restoration_type[i]);
+
+	dprintk(ctx->dev, "lr_unit_shift %d\n", lr->lr_unit_shift);
+	dprintk(ctx->dev, "lr_uv_shift %d\n", lr->lr_uv_shift);
+
+	for (i = 0; i < ARRAY_SIZE(lr->loop_restoration_size); i++)
+		dprintk(ctx->dev, "loop_restoration_size[%d] %d\n",
+			i, lr->loop_restoration_size[i]);
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_cdef(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_av1_cdef *cdef = &run->av1.frame_header->cdef;
+	u32 i;
+
+	dprintk(ctx->dev, "AV1 CDEF\n");
+	dprintk(ctx->dev, "damping_minus_3 %d\n", cdef->damping_minus_3);
+	dprintk(ctx->dev, "bits %d\n", cdef->bits);
+
+	for (i = 0; i < ARRAY_SIZE(cdef->y_pri_strength); i++)
+		dprintk(ctx->dev,
+			"y_pri_strength[%d] %d\n", i, cdef->y_pri_strength[i]);
+	for (i = 0; i < ARRAY_SIZE(cdef->y_sec_strength); i++)
+		dprintk(ctx->dev,
+			"y_sec_strength[%d] %d\n", i, cdef->y_sec_strength[i]);
+	for (i = 0; i < ARRAY_SIZE(cdef->uv_pri_strength); i++)
+		dprintk(ctx->dev,
+			"uv_pri_strength[%d] %d\n", i, cdef->uv_pri_strength[i]);
+	for (i = 0; i < ARRAY_SIZE(cdef->uv_sec_strength); i++)
+		dprintk(ctx->dev,
+			"uv_sec_strength[%d] %d\n", i, cdef->uv_sec_strength[i]);
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_global_motion(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_av1_global_motion *gm;
+	u32 i;
+	u32 j;
+
+	gm = &run->av1.frame_header->global_motion;
+
+	dprintk(ctx->dev, "AV1 Global Motion\n");
+
+	for (i = 0; i < ARRAY_SIZE(gm->flags); i++)
+		dprintk(ctx->dev, "flags[%d] %d\n", i, gm->flags[i]);
+	for (i = 0; i < ARRAY_SIZE(gm->type); i++)
+		dprintk(ctx->dev, "type[%d] %d\n", i, gm->type[i]);
+
+	for (i = 0; i < V4L2_AV1_TOTAL_REFS_PER_FRAME; i++)
+		for (j = 0; j < 6; j++)
+			dprintk(ctx->dev, "params[%d][%d] %d\n",
+				i, j, gm->type[i]);
+
+	dprintk(ctx->dev, "invalid %d\n", gm->invalid);
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_film_grain(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_av1_film_grain *fg;
+	u32 i;
+
+	fg = &run->av1.frame_header->film_grain;
+
+	dprintk(ctx->dev, "AV1 Film Grain\n");
+	dprintk(ctx->dev, "flags %d\n", fg->flags);
+	dprintk(ctx->dev, "grain_seed %d\n", fg->grain_seed);
+	dprintk(ctx->dev, "film_grain_params_ref_idx %d\n",
+		fg->film_grain_params_ref_idx);
+	dprintk(ctx->dev, "num_y_points %d\n", fg->num_y_points);
+
+	for (i = 0; i < ARRAY_SIZE(fg->point_y_value); i++)
+		dprintk(ctx->dev, "point_y_value[%d] %d\n",
+			i, fg->point_y_value[i]);
+
+	for (i = 0; i < ARRAY_SIZE(fg->point_y_scaling); i++)
+		dprintk(ctx->dev, "point_y_scaling[%d] %d\n",
+			i, fg->point_y_scaling[i]);
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void
+vivpu_dump_av1_tile_info(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_av1_tile_info *ti;
+	u32 i;
+
+	ti = &run->av1.frame_header->tile_info;
+
+	dprintk(ctx->dev, "AV1 Tile Info\n");
+
+	dprintk(ctx->dev, "flags %d\n", ti->flags);
+
+	for (i = 0; i < ARRAY_SIZE(ti->mi_col_starts); i++)
+		dprintk(ctx->dev, "mi_col_starts[%d] %d\n",
+			i, ti->mi_col_starts[i]);
+
+	for (i = 0; i < ARRAY_SIZE(ti->mi_row_starts); i++)
+		dprintk(ctx->dev, "mi_row_starts[%d] %d\n",
+			i, ti->mi_row_starts[i]);
+
+	for (i = 0; i < ARRAY_SIZE(ti->width_in_sbs_minus_1); i++)
+		dprintk(ctx->dev, "width_in_sbs_minus_1[%d] %d\n",
+			i, ti->width_in_sbs_minus_1[i]);
+
+	for (i = 0; i < ARRAY_SIZE(ti->height_in_sbs_minus_1); i++)
+		dprintk(ctx->dev, "height_in_sbs_minus_1[%d] %d\n",
+			i, ti->height_in_sbs_minus_1[i]);
+
+	dprintk(ctx->dev, "tile_size_bytes %d\n", ti->tile_size_bytes);
+	dprintk(ctx->dev, "context_update_tile_id %d\n", ti->context_update_tile_id);
+	dprintk(ctx->dev, "tile_cols %d\n", ti->tile_cols);
+	dprintk(ctx->dev, "tile_rows %d\n", ti->tile_rows);
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void vivpu_dump_av1_frame(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	const struct v4l2_ctrl_av1_frame_header *f = run->av1.frame_header;
+	u32 i;
+
+	dprintk(ctx->dev, "AV1 Frame Header\n");
+	dprintk(ctx->dev, "flags %d\n", f->flags);
+	dprintk(ctx->dev, "frame_type %d\n", f->frame_type);
+	dprintk(ctx->dev, "order_hint %d\n", f->order_hint);
+	dprintk(ctx->dev, "superres_denom %d\n", f->superres_denom);
+	dprintk(ctx->dev, "upscaled_width %d\n", f->upscaled_width);
+	dprintk(ctx->dev, "interpolation_filter %d\n", f->interpolation_filter);
+	dprintk(ctx->dev, "tx_mode %d\n", f->tx_mode);
+	dprintk(ctx->dev, "frame_width_minus_1 %d\n", f->frame_width_minus_1);
+	dprintk(ctx->dev, "frame_height_minus_1 %d\n", f->frame_height_minus_1);
+	dprintk(ctx->dev, "render_width_minus_1 %d\n", f->render_width_minus_1);
+	dprintk(ctx->dev, "render_height_minus_1 %d\n", f->render_height_minus_1);
+	dprintk(ctx->dev, "current_frame_id %d\n", f->current_frame_id);
+	dprintk(ctx->dev, "primary_ref_frame %d\n", f->primary_ref_frame);
+
+	for (i = 0; i < V4L2_AV1_MAX_OPERATING_POINTS; i++) {
+		dprintk(ctx->dev, "buffer_removal_time[%d] %d\n",
+			i, f->buffer_removal_time[i]);
+	}
+
+	dprintk(ctx->dev, "refresh_frame_flags %d\n", f->refresh_frame_flags);
+	dprintk(ctx->dev, "last_frame_idx %d\n", f->last_frame_idx);
+	dprintk(ctx->dev, "gold_frame_idx %d\n", f->gold_frame_idx);
+
+	for (i = 0; i < ARRAY_SIZE(f->reference_frame_ts); i++)
+		dprintk(ctx->dev, "reference_frame_ts[%d] %llu\n", i,
+			f->reference_frame_ts[i]);
+
+	vivpu_dump_av1_tile_info(ctx, run);
+	vivpu_dump_av1_quantization(ctx, run);
+	vivpu_dump_av1_segmentation(ctx, run);
+	vivpu_dump_av1_loop_filter(ctx, run);
+	vivpu_dump_av1_cdef(ctx, run);
+	vivpu_dump_av1_loop_restoration(ctx, run);
+	vivpu_dump_av1_global_motion(ctx, run);
+	vivpu_dump_av1_film_grain(ctx, run);
+
+	for (i = 0; i < ARRAY_SIZE(f->skip_mode_frame); i++)
+		dprintk(ctx->dev, "skip_mode_frame[%d] %d\n",
+			i, f->skip_mode_frame[i]);
+
+	dprintk(ctx->dev, "\n");
+}
+
+static void vivpu_dump_av1_ctrls(struct vivpu_ctx *ctx, struct vivpu_run *run)
+{
+	vivpu_dump_av1_seq(ctx, run);
+	vivpu_dump_av1_frame(ctx, run);
+	vivpu_dump_av1_tile_group(ctx, run);
+	vivpu_dump_av1_tile_group_entry(ctx, run);
+	vivpu_dump_av1_tile_list(ctx, run);
+	vivpu_dump_av1_tile_list_entry(ctx, run);
+}
+
+void vivpu_device_run(void *priv)
+{
+	struct vivpu_ctx *ctx = priv;
+	struct vivpu_run run = {};
+	struct media_request *src_req;
+
+	run.src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+	run.dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+	/* Apply request(s) controls if needed. */
+	src_req = run.src->vb2_buf.req_obj.req;
+
+	if (src_req)
+		v4l2_ctrl_request_setup(src_req, &ctx->hdl);
+
+	switch (ctx->current_codec) {
+	case VIVPU_CODEC_AV1:
+		run.av1.sequence =
+			vivpu_find_control_data(ctx, V4L2_CID_STATELESS_AV1_SEQUENCE);
+		run.av1.frame_header =
+			vivpu_find_control_data(ctx, V4L2_CID_STATELESS_AV1_FRAME_HEADER);
+		run.av1.tile_group =
+			vivpu_find_control_data(ctx, V4L2_CID_STATELESS_AV1_TILE_GROUP);
+		run.av1.tg_entries =
+			vivpu_find_control_data(ctx, V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY);
+		run.av1.tile_list =
+			vivpu_find_control_data(ctx, V4L2_CID_STATELESS_AV1_TILE_LIST);
+		run.av1.tl_entries =
+			vivpu_find_control_data(ctx, V4L2_CID_STATELESS_AV1_TILE_LIST_ENTRY);
+
+		vivpu_dump_av1_ctrls(ctx, &run);
+		vivpu_av1_check_reference_frames(ctx, &run);
+		break;
+	default:
+		break;
+	}
+
+	v4l2_m2m_buf_copy_metadata(run.src, run.dst, true);
+	run.dst->sequence = ctx->q_data[V4L2_M2M_DST].sequence++;
+	run.src->sequence = ctx->q_data[V4L2_M2M_SRC].sequence++;
+	run.dst->field = ctx->dst_fmt.fmt.pix.field;
+
+	dprintk(ctx->dev, "Got src buffer %p, sequence %d, timestamp %llu\n",
+		run.src, run.src->sequence, run.src->vb2_buf.timestamp);
+
+	dprintk(ctx->dev, "Got dst buffer %p, sequence %d, timestamp %llu\n",
+		run.dst, run.dst->sequence, run.dst->vb2_buf.timestamp);
+
+	/* Complete request(s) controls if needed. */
+	if (src_req)
+		v4l2_ctrl_request_complete(src_req, &ctx->hdl);
+
+	if (vivpu_transtime)
+		usleep_range(vivpu_transtime, vivpu_transtime * 2);
+
+	v4l2_m2m_buf_done_and_job_finish(ctx->dev->m2m_dev,
+					 ctx->fh.m2m_ctx, VB2_BUF_STATE_DONE);
+}
diff --git a/drivers/media/test-drivers/vivpu/vivpu-dec.h b/drivers/media/test-drivers/vivpu/vivpu-dec.h
new file mode 100644
index 000000000000..4a3ca5952e43
--- /dev/null
+++ b/drivers/media/test-drivers/vivpu/vivpu-dec.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * A virtual stateless VPU example device for uAPI development purposes.
+ *
+ * A userspace implementation can use vivpu to run a decoding loop even
+ * when no hardware is available or when the kernel uAPI for the codec
+ * has not been upstreamed yet. This can reveal bugs at an early stage.
+ *
+ * Copyright (c) Collabora, Ltd.
+ *
+ * Based on the vim2m driver, that is:
+ *
+ * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
+ * Pawel Osciak, <pawel@...iak.com>
+ * Marek Szyprowski, <m.szyprowski@...sung.com>
+ *
+ * Based on the vicodec driver, that is:
+ *
+ * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@...e-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@...tlin.com>
+ * Copyright (C) 2018 Bootlin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version
+ */
+
+#ifndef _VIVPU_DEC_H_
+#define _VIVPU_DEC_H_
+
+#include "vivpu.h"
+
+struct vivpu_av1_run {
+	const struct v4l2_ctrl_av1_sequence *sequence;
+	const struct v4l2_ctrl_av1_frame_header *frame_header;
+	const struct v4l2_ctrl_av1_tile_group *tile_group;
+	const struct v4l2_ctrl_av1_tile_group_entry *tg_entries;
+	const struct v4l2_ctrl_av1_tile_list *tile_list;
+	const struct v4l2_ctrl_av1_tile_list_entry *tl_entries;
+};
+
+struct vivpu_run {
+	struct vb2_v4l2_buffer	*src;
+	struct vb2_v4l2_buffer	*dst;
+
+	union {
+		struct vivpu_av1_run	av1;
+	};
+};
+
+int vivpu_dec_start(struct vivpu_ctx *ctx);
+int vivpu_dec_stop(struct vivpu_ctx *ctx);
+int vivpu_job_ready(void *priv);
+void vivpu_device_run(void *priv);
+
+#endif /* _VIVPU_DEC_H_ */
diff --git a/drivers/media/test-drivers/vivpu/vivpu-video.c b/drivers/media/test-drivers/vivpu/vivpu-video.c
new file mode 100644
index 000000000000..a3018b0a4da3
--- /dev/null
+++ b/drivers/media/test-drivers/vivpu/vivpu-video.c
@@ -0,0 +1,599 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * A virtual stateless VPU example device for uAPI development purposes.
+ *
+ * A userspace implementation can use vivpu to run a decoding loop even
+ * when no hardware is available or when the kernel uAPI for the codec
+ * has not been upstreamed yet. This can reveal bugs at an early stage.
+ *
+ * Copyright (c) Collabora, Ltd.
+ *
+ * Based on the vim2m driver, that is:
+ *
+ * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
+ * Pawel Osciak, <pawel@...iak.com>
+ * Marek Szyprowski, <m.szyprowski@...sung.com>
+ *
+ * Based on the vicodec driver, that is:
+ *
+ * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * Based on the cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@...e-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@...tlin.com>
+ * Copyright (C) 2018 Bootlin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version
+ */
+
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "vivpu-video.h"
+#include "vivpu.h"
+
+static const u32 vivpu_decoded_formats[] = {
+	V4L2_PIX_FMT_NV12,
+};
+
+static const struct vivpu_coded_format_desc coded_formats[] = {
+	{
+	.pixelformat = V4L2_PIX_FMT_AV1_FRAME,
+	/* simulated frame sizes for AV1 */
+	.frmsize = {
+		.min_width = 48,
+		.max_width = 4096,
+		.step_width = 16,
+		.min_height = 48,
+		.max_height = 2304,
+		.step_height = 16,
+	},
+	.num_decoded_fmts = ARRAY_SIZE(vivpu_decoded_formats),
+	/* simulate that the AV1 coded format decodes to raw NV12 */
+	.decoded_fmts = vivpu_decoded_formats,
+	}
+};
+
+static const struct vivpu_coded_format_desc*
+vivpu_find_coded_fmt_desc(u32 fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(coded_formats); i++) {
+		if (coded_formats[i].pixelformat == fourcc)
+			return &coded_formats[i];
+	}
+
+	return NULL;
+}
+
+void vivpu_set_default_format(struct vivpu_ctx *ctx)
+{
+	struct v4l2_format src_fmt = {
+		.fmt.pix = {
+			.width = vivpu_src_default_w,
+			.height = vivpu_src_default_h,
+			/* Zero bytes per line for encoded source. */
+			.bytesperline = 0,
+			/* Choose some minimum size since this can't be 0 */
+			.sizeimage = SZ_1K,
+		},
+	};
+
+	ctx->coded_format_desc = &coded_formats[0];
+	ctx->src_fmt = src_fmt;
+
+	v4l2_fill_pixfmt_mp(&ctx->dst_fmt.fmt.pix_mp,
+			    V4L2_PIX_FMT_NV12,
+			    vivpu_src_default_w, vivpu_src_default_h);
+
+	/* Always apply the frmsize constraint of the coded end. */
+	v4l2_apply_frmsize_constraints(&ctx->dst_fmt.fmt.pix.width,
+				       &ctx->dst_fmt.fmt.pix.height,
+				       &ctx->coded_format_desc->frmsize);
+
+	ctx->src_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	ctx->dst_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+}
+
+static const char *q_name(enum v4l2_buf_type type)
+{
+	switch (type) {
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+		return "Output";
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		return "Capture";
+	default:
+		return "Invalid";
+	}
+}
+
+static struct vivpu_q_data *get_q_data(struct vivpu_ctx *ctx,
+				       enum v4l2_buf_type type)
+{
+	switch (type) {
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+		return &ctx->q_data[V4L2_M2M_SRC];
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+		return &ctx->q_data[V4L2_M2M_DST];
+	default:
+		break;
+	}
+	return NULL;
+}
+
+static int vivpu_querycap(struct file *file, void *priv,
+			  struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, VIVPU_NAME, sizeof(cap->driver));
+	strscpy(cap->card, VIVPU_NAME, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info),
+		 "platform:%s", VIVPU_NAME);
+
+	return 0;
+}
+
+static int vivpu_enum_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_fmtdesc *f)
+{
+	struct vivpu_ctx *ctx = vivpu_file_to_ctx(file);
+
+	if (f->index >= ctx->coded_format_desc->num_decoded_fmts)
+		return -EINVAL;
+
+	f->pixelformat = ctx->coded_format_desc->decoded_fmts[f->index];
+	return 0;
+}
+
+static int vivpu_enum_fmt_vid_out(struct file *file, void *priv,
+				  struct v4l2_fmtdesc *f)
+{
+	if (f->index >= ARRAY_SIZE(coded_formats))
+		return -EINVAL;
+
+	f->pixelformat = coded_formats[f->index].pixelformat;
+	return 0;
+}
+
+static int vivpu_g_fmt_vid_cap(struct file *file, void *priv,
+			       struct v4l2_format *f)
+{
+	struct vivpu_ctx *ctx = vivpu_file_to_ctx(file);
+	*f = ctx->dst_fmt;
+
+	return 0;
+}
+
+static int vivpu_g_fmt_vid_out(struct file *file, void *priv,
+			       struct v4l2_format *f)
+{
+	struct vivpu_ctx *ctx = vivpu_file_to_ctx(file);
+
+	*f = ctx->src_fmt;
+	return 0;
+}
+
+static int vivpu_try_fmt_vid_cap(struct file *file, void *priv,
+				 struct v4l2_format *f)
+{
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	struct vivpu_ctx *ctx = vivpu_file_to_ctx(file);
+	const struct vivpu_coded_format_desc *coded_desc;
+	unsigned int i;
+
+	coded_desc = ctx->coded_format_desc;
+	if (WARN_ON(!coded_desc))
+		return -EINVAL;
+
+	for (i = 0; i < coded_desc->num_decoded_fmts; i++) {
+		if (coded_desc->decoded_fmts[i] == pix_mp->pixelformat)
+			break;
+	}
+
+	if (i == coded_desc->num_decoded_fmts)
+		return -EINVAL;
+
+	v4l2_apply_frmsize_constraints(&pix_mp->width,
+				       &pix_mp->height,
+				       &coded_desc->frmsize);
+
+	pix_mp->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int vivpu_try_fmt_vid_out(struct file *file, void *priv,
+				 struct v4l2_format *f)
+{
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	const struct vivpu_coded_format_desc *coded_desc;
+
+	coded_desc = vivpu_find_coded_fmt_desc(pix_mp->pixelformat);
+	if (!coded_desc)
+		return -EINVAL;
+
+	/* apply the (simulated) hw constraints */
+	v4l2_apply_frmsize_constraints(&pix_mp->width,
+				       &pix_mp->height,
+				       &coded_desc->frmsize);
+
+	pix_mp->field = V4L2_FIELD_NONE;
+	/* All coded formats are considered single planar for now. */
+	pix_mp->num_planes = 1;
+
+	return 0;
+}
+
+static int vivpu_s_fmt_vid_out(struct file *file, void *priv,
+			       struct v4l2_format *f)
+{
+	struct vivpu_ctx *ctx = vivpu_file_to_ctx(file);
+	struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx;
+	struct v4l2_format *cap_fmt = &ctx->dst_fmt;
+	const struct vivpu_coded_format_desc *desc;
+	struct vb2_queue *peer_vq;
+	int ret;
+
+	peer_vq = v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+	if (vb2_is_busy(peer_vq))
+		return -EBUSY;
+
+	dprintk(ctx->dev,
+		"Current OUTPUT queue format: width %d, height %d, pixfmt %d\n",
+		ctx->src_fmt.fmt.pix_mp.width, ctx->src_fmt.fmt.pix_mp.height,
+		ctx->src_fmt.fmt.pix_mp.pixelformat);
+
+	dprintk(ctx->dev,
+		"Current CAPTURE queue format: width %d, height %d, pixfmt %d\n",
+		ctx->dst_fmt.fmt.pix_mp.width, ctx->dst_fmt.fmt.pix_mp.height,
+		ctx->dst_fmt.fmt.pix_mp.pixelformat);
+
+	ret = vivpu_try_fmt_vid_out(file, priv, f);
+	if (ret) {
+		dprintk(ctx->dev,
+			"Unsupported format for the OUTPUT queue: %d\n",
+			f->fmt.pix_mp.pixelformat);
+		return ret;
+	}
+
+	desc = vivpu_find_coded_fmt_desc(f->fmt.pix_mp.pixelformat);
+	if (!desc) {
+		dprintk(ctx->dev,
+			"Unsupported format for the OUTPUT queue: %d\n",
+			f->fmt.pix_mp.pixelformat);
+		return -EINVAL;
+	}
+
+	ctx->coded_format_desc = desc;
+
+	ctx->src_fmt = *f;
+
+	v4l2_fill_pixfmt_mp(&ctx->dst_fmt.fmt.pix_mp,
+			    ctx->coded_format_desc->decoded_fmts[0],
+			    ctx->src_fmt.fmt.pix_mp.width,
+			    ctx->src_fmt.fmt.pix_mp.height);
+	cap_fmt->fmt.pix_mp.colorspace = f->fmt.pix_mp.colorspace;
+	cap_fmt->fmt.pix_mp.xfer_func = f->fmt.pix_mp.xfer_func;
+	cap_fmt->fmt.pix_mp.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc;
+	cap_fmt->fmt.pix_mp.quantization = f->fmt.pix_mp.quantization;
+
+	dprintk(ctx->dev,
+		"Current OUTPUT queue format: width %d, height %d, pixfmt %d\n",
+		ctx->src_fmt.fmt.pix_mp.width, ctx->src_fmt.fmt.pix_mp.height,
+		ctx->src_fmt.fmt.pix_mp.pixelformat);
+
+	dprintk(ctx->dev,
+		"Current CAPTURE queue format: width %d, height %d, pixfmt %d\n",
+		ctx->dst_fmt.fmt.pix_mp.width, ctx->dst_fmt.fmt.pix_mp.height,
+		ctx->dst_fmt.fmt.pix_mp.pixelformat);
+
+	return 0;
+}
+
+static int vivpu_s_fmt_vid_cap(struct file *file, void *priv,
+			       struct v4l2_format *f)
+{
+	struct vivpu_ctx *ctx = vivpu_file_to_ctx(file);
+	int ret;
+
+	dprintk(ctx->dev,
+		"Current CAPTURE queue format: width %d, height %d, pixfmt %d\n",
+		ctx->dst_fmt.fmt.pix_mp.width, ctx->dst_fmt.fmt.pix_mp.height,
+		ctx->dst_fmt.fmt.pix_mp.pixelformat);
+
+	ret = vivpu_try_fmt_vid_cap(file, priv, f);
+	if (ret)
+		return ret;
+
+	ctx->dst_fmt = *f;
+
+	dprintk(ctx->dev,
+		"Current CAPTURE queue format: width %d, height %d, pixfmt %d\n",
+		ctx->dst_fmt.fmt.pix_mp.width, ctx->dst_fmt.fmt.pix_mp.height,
+		ctx->dst_fmt.fmt.pix_mp.pixelformat);
+
+	return 0;
+}
+
+static int vivpu_enum_framesizes(struct file *file, void *priv,
+				 struct v4l2_frmsizeenum *fsize)
+{
+	const struct vivpu_coded_format_desc *fmt;
+	struct vivpu_ctx *ctx = vivpu_file_to_ctx(file);
+
+	if (fsize->index != 0)
+		return -EINVAL;
+
+	fmt = vivpu_find_coded_fmt_desc(fsize->pixel_format);
+	if (!fmt) {
+		dprintk(ctx->dev,
+			"Unsupported format for the OUTPUT queue: %d\n",
+			fsize->pixel_format);
+
+		return -EINVAL;
+	}
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise = fmt->frmsize;
+	return 0;
+}
+
+const struct v4l2_ioctl_ops vivpu_ioctl_ops = {
+	.vidioc_querycap		= vivpu_querycap,
+	.vidioc_enum_framesizes		= vivpu_enum_framesizes,
+
+	.vidioc_enum_fmt_vid_cap	= vivpu_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap		= vivpu_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap		= vivpu_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		= vivpu_s_fmt_vid_cap,
+
+	.vidioc_enum_fmt_vid_out	= vivpu_enum_fmt_vid_out,
+	.vidioc_g_fmt_vid_out		= vivpu_g_fmt_vid_out,
+	.vidioc_try_fmt_vid_out		= vivpu_try_fmt_vid_out,
+	.vidioc_s_fmt_vid_out		= vivpu_s_fmt_vid_out,
+
+	.vidioc_reqbufs			= v4l2_m2m_ioctl_reqbufs,
+	.vidioc_querybuf		= v4l2_m2m_ioctl_querybuf,
+	.vidioc_qbuf			= v4l2_m2m_ioctl_qbuf,
+	.vidioc_dqbuf			= v4l2_m2m_ioctl_dqbuf,
+	.vidioc_prepare_buf		= v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_create_bufs		= v4l2_m2m_ioctl_create_bufs,
+	.vidioc_expbuf			= v4l2_m2m_ioctl_expbuf,
+
+	.vidioc_streamon		= v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff		= v4l2_m2m_ioctl_streamoff,
+
+	.vidioc_try_decoder_cmd		= v4l2_m2m_ioctl_stateless_try_decoder_cmd,
+	.vidioc_decoder_cmd		= v4l2_m2m_ioctl_stateless_decoder_cmd,
+
+	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+};
+
+static int vivpu_queue_setup(struct vb2_queue *vq,
+			     unsigned int *nbuffers,
+			     unsigned int *nplanes,
+			     unsigned int sizes[],
+			     struct device *alloc_devs[])
+{
+	struct vivpu_ctx *ctx = vb2_get_drv_priv(vq);
+	struct v4l2_pix_format *pix_fmt;
+
+	if (V4L2_TYPE_IS_OUTPUT(vq->type))
+		pix_fmt = &ctx->src_fmt.fmt.pix;
+	else
+		pix_fmt = &ctx->dst_fmt.fmt.pix;
+
+	if (*nplanes) {
+		if (sizes[0] < pix_fmt->sizeimage) {
+			v4l2_err(&ctx->dev->v4l2_dev, "sizes[0] is %d, sizeimage is %d\n",
+				 sizes[0], pix_fmt->sizeimage);
+			return -EINVAL;
+		}
+	} else {
+		sizes[0] = pix_fmt->sizeimage;
+		*nplanes = 1;
+	}
+
+	dprintk(ctx->dev, "%s: get %d buffer(s) of size %d each.\n",
+		q_name(vq->type), *nbuffers, sizes[0]);
+
+	return 0;
+}
+
+static void vivpu_queue_cleanup(struct vb2_queue *vq, u32 state)
+{
+	struct vivpu_ctx *ctx = vb2_get_drv_priv(vq);
+	struct vb2_v4l2_buffer *vbuf;
+
+	dprintk(ctx->dev, "Cleaning up queues\n");
+	for (;;) {
+		if (V4L2_TYPE_IS_OUTPUT(vq->type))
+			vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+		else
+			vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+		if (!vbuf)
+			break;
+
+		v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req,
+					   &ctx->hdl);
+		dprintk(ctx->dev, "Marked request %p as complete\n",
+			vbuf->vb2_buf.req_obj.req);
+
+		v4l2_m2m_buf_done(vbuf, state);
+		dprintk(ctx->dev,
+			"Marked buffer %llu as done, state is %d\n",
+			vbuf->vb2_buf.timestamp,
+			state);
+	}
+}
+
+static int vivpu_buf_out_validate(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+	vbuf->field = V4L2_FIELD_NONE;
+	return 0;
+}
+
+static int vivpu_buf_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_queue *vq = vb->vb2_queue;
+	struct vivpu_ctx *ctx = vb2_get_drv_priv(vq);
+	u32 plane_sz = vb2_plane_size(vb, 0);
+	struct v4l2_pix_format *pix_fmt;
+
+	if (V4L2_TYPE_IS_OUTPUT(vq->type))
+		pix_fmt = &ctx->src_fmt.fmt.pix;
+	else
+		pix_fmt = &ctx->dst_fmt.fmt.pix;
+
+	if (plane_sz < pix_fmt->sizeimage) {
+		v4l2_err(&ctx->dev->v4l2_dev, "plane[0] size is %d, sizeimage is %d\n",
+			 plane_sz, pix_fmt->sizeimage);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, pix_fmt->sizeimage);
+
+	return 0;
+}
+
+static int vivpu_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct vivpu_ctx *ctx = vb2_get_drv_priv(vq);
+	struct vivpu_q_data *q_data = get_q_data(ctx, vq->type);
+	int ret = 0;
+
+	if (!q_data)
+		return -EINVAL;
+
+	q_data->sequence = 0;
+
+	switch (ctx->src_fmt.fmt.pix.pixelformat) {
+	case V4L2_PIX_FMT_AV1_FRAME:
+		dprintk(ctx->dev, "Pixfmt is AV1F\n");
+		ctx->current_codec = VIVPU_CODEC_AV1;
+		break;
+	default:
+		v4l2_err(&ctx->dev->v4l2_dev, "Unsupported src format %d\n",
+			 ctx->src_fmt.fmt.pix.pixelformat);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	return 0;
+
+err:
+	vivpu_queue_cleanup(vq, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void vivpu_stop_streaming(struct vb2_queue *vq)
+{
+	struct vivpu_ctx *ctx = vb2_get_drv_priv(vq);
+
+	dprintk(ctx->dev, "Stop streaming\n");
+	vivpu_queue_cleanup(vq, VB2_BUF_STATE_ERROR);
+}
+
+static void vivpu_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vivpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static void vivpu_buf_request_complete(struct vb2_buffer *vb)
+{
+	struct vivpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl);
+}
+
+const struct vb2_ops vivpu_qops = {
+	.queue_setup          = vivpu_queue_setup,
+	.buf_out_validate     = vivpu_buf_out_validate,
+	.buf_prepare          = vivpu_buf_prepare,
+	.buf_queue            = vivpu_buf_queue,
+	.start_streaming      = vivpu_start_streaming,
+	.stop_streaming       = vivpu_stop_streaming,
+	.wait_prepare         = vb2_ops_wait_prepare,
+	.wait_finish          = vb2_ops_wait_finish,
+	.buf_request_complete = vivpu_buf_request_complete,
+};
+
+int vivpu_queue_init(void *priv, struct vb2_queue *src_vq,
+		     struct vb2_queue *dst_vq)
+{
+	struct vivpu_ctx *ctx = priv;
+	int ret;
+
+	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+	src_vq->drv_priv = ctx;
+	src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+	src_vq->ops = &vivpu_qops;
+	src_vq->mem_ops = &vb2_vmalloc_memops;
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->lock = &ctx->vb_mutex;
+	src_vq->supports_requests = true;
+
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+	dst_vq->drv_priv = ctx;
+	dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+	dst_vq->ops = &vivpu_qops;
+	dst_vq->mem_ops = &vb2_vmalloc_memops;
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->lock = &ctx->vb_mutex;
+
+	return vb2_queue_init(dst_vq);
+}
+
+int vivpu_request_validate(struct media_request *req)
+{
+	struct media_request_object *obj;
+	struct vivpu_ctx *ctx = NULL;
+	unsigned int count;
+
+	list_for_each_entry(obj, &req->objects, list) {
+		struct vb2_buffer *vb;
+
+		if (vb2_request_object_is_buffer(obj)) {
+			vb = container_of(obj, struct vb2_buffer, req_obj);
+			ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+			break;
+		}
+	}
+
+	if (!ctx)
+		return -ENOENT;
+
+	count = vb2_request_buffer_cnt(req);
+	if (!count) {
+		v4l2_err(&ctx->dev->v4l2_dev,
+			 "No buffer was provided with the request\n");
+		return -ENOENT;
+	} else if (count > 1) {
+		v4l2_err(&ctx->dev->v4l2_dev,
+			 "More than one buffer was provided with the request\n");
+		return -EINVAL;
+	}
+
+	return vb2_request_validate(req);
+}
diff --git a/drivers/media/test-drivers/vivpu/vivpu-video.h b/drivers/media/test-drivers/vivpu/vivpu-video.h
new file mode 100644
index 000000000000..6cf8c1570887
--- /dev/null
+++ b/drivers/media/test-drivers/vivpu/vivpu-video.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * A virtual stateless VPU example device for uAPI development purposes.
+ *
+ * A userspace implementation can use vivpu to run a decoding loop even
+ * when no hardware is available or when the kernel uAPI for the codec
+ * has not been upstreamed yet. This can reveal bugs at an early stage.
+ *
+ * Copyright (c) Collabora, Ltd.
+ *
+ * Based on the vim2m driver, that is:
+ *
+ * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
+ * Pawel Osciak, <pawel@...iak.com>
+ * Marek Szyprowski, <m.szyprowski@...sung.com>
+ *
+ * Based on the vicodec driver, that is:
+ *
+ * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@...e-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@...tlin.com>
+ * Copyright (C) 2018 Bootlin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version
+ */
+
+#ifndef _VIVPU_VIDEO_H_
+#define _VIVPU_VIDEO_H_
+#include <media/v4l2-mem2mem.h>
+
+#include "vivpu.h"
+
+extern const struct v4l2_ioctl_ops vivpu_ioctl_ops;
+int vivpu_queue_init(void *priv, struct vb2_queue *src_vq,
+		     struct vb2_queue *dst_vq);
+
+void vivpu_set_default_format(struct vivpu_ctx *ctx);
+int vivpu_request_validate(struct media_request *req);
+
+#endif /* _VIVPU_VIDEO_H_ */
diff --git a/drivers/media/test-drivers/vivpu/vivpu.h b/drivers/media/test-drivers/vivpu/vivpu.h
new file mode 100644
index 000000000000..89b993c460c1
--- /dev/null
+++ b/drivers/media/test-drivers/vivpu/vivpu.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * A virtual stateless VPU example device for uAPI development purposes.
+ *
+ * A userspace implementation can use vivpu to run a decoding loop even
+ * when no hardware is available or when the kernel uAPI for the codec
+ * has not been upstreamed yet. This can reveal bugs at an early stage.
+ *
+ * Copyright (c) Collabora, Ltd.
+ *
+ * Based on the vim2m driver, that is:
+ *
+ * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
+ * Pawel Osciak, <pawel@...iak.com>
+ * Marek Szyprowski, <m.szyprowski@...sung.com>
+ *
+ * Based on the vicodec driver, that is:
+ *
+ * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@...e-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@...tlin.com>
+ * Copyright (C) 2018 Bootlin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version
+ */
+
+#ifndef _VIVPU_H_
+#define _VIVPU_H_
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/tpg/v4l2-tpg.h>
+
+#define VIVPU_NAME		"vivpu"
+#define VIVPU_M2M_NQUEUES	2
+
+extern const unsigned int vivpu_src_default_w;
+extern const unsigned int vivpu_src_default_h;
+extern const unsigned int vivpu_src_default_depth;
+extern unsigned int vivpu_transtime;
+
+struct vivpu_coded_format_desc {
+	u32 pixelformat;
+	struct v4l2_frmsize_stepwise frmsize;
+	unsigned int num_decoded_fmts;
+	const u32 *decoded_fmts;
+};
+
+enum {
+	V4L2_M2M_SRC = 0,
+	V4L2_M2M_DST = 1,
+};
+
+extern unsigned int vivpu_debug;
+#define dprintk(dev, fmt, arg...) \
+	v4l2_dbg(1, vivpu_debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg)
+
+struct vivpu_q_data {
+	unsigned int		sequence;
+};
+
+struct vivpu_dev {
+	struct v4l2_device	v4l2_dev;
+	struct video_device	vfd;
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device	mdev;
+#endif
+
+	struct mutex		dev_mutex;
+
+	struct v4l2_m2m_dev	*m2m_dev;
+};
+
+enum vivpu_codec {
+	VIVPU_CODEC_AV1,
+};
+
+struct vivpu_ctx {
+	struct v4l2_fh		fh;
+	struct vivpu_dev	*dev;
+	struct v4l2_ctrl_handler hdl;
+	struct v4l2_ctrl	**ctrls;
+
+	struct mutex		vb_mutex;
+
+	struct vivpu_q_data	q_data[VIVPU_M2M_NQUEUES];
+	enum   vivpu_codec	current_codec;
+
+	const struct vivpu_coded_format_desc *coded_format_desc;
+
+	struct v4l2_format	src_fmt;
+	struct v4l2_format	dst_fmt;
+};
+
+struct vivpu_control {
+	struct v4l2_ctrl_config cfg;
+};
+
+static inline struct vivpu_ctx *vivpu_file_to_ctx(struct file *file)
+{
+	return container_of(file->private_data, struct vivpu_ctx, fh);
+}
+
+static inline struct vivpu_ctx *vivpu_v4l2fh_to_ctx(struct v4l2_fh *v4l2_fh)
+{
+	return container_of(v4l2_fh, struct vivpu_ctx, fh);
+}
+
+void *vivpu_find_control_data(struct vivpu_ctx *ctx, u32 id);
+struct v4l2_ctrl *vivpu_find_control(struct vivpu_ctx *ctx, u32 id);
+u32 vivpu_control_num_elems(struct vivpu_ctx *ctx, u32 id);
+
+#endif /* _VIVPU_H_ */
-- 
2.32.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ