diff mbox series

[v3,4/4] selftests: media_tests: Add a memory-to-memory concurrent stress test

Message ID 20180801215026.27809-5-ezequiel@collabora.com (mailing list archive)
State New
Headers show
Series Make sure .device_run is always called in non-atomic context | expand

Commit Message

Ezequiel Garcia Aug. 1, 2018, 9:50 p.m. UTC
Add a test for the memory-to-memory framework, to exercise the
scheduling of concurrent jobs, using multiple contexts.

This test needs to be run using the vim2m virtual driver,
and so needs no hardware.

While here, rework the media_tests suite in order to make it
useful for automatic tools. Those tests that need human intervention
are now separated from those that can run automatically, needing
only virtual drivers to work.

Signed-off-by: Ezequiel Garcia <ezequiel@collabora.com>
---
 .../testing/selftests/media_tests/.gitignore  |   1 +
 tools/testing/selftests/media_tests/Makefile  |   5 +-
 .../selftests/media_tests/m2m_job_test.c      | 287 ++++++++++++++++++
 .../selftests/media_tests/m2m_job_test.sh     |  32 ++
 4 files changed, 324 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/media_tests/m2m_job_test.c
 create mode 100755 tools/testing/selftests/media_tests/m2m_job_test.sh

Comments

Guillaume Tucker Aug. 3, 2018, 11:15 a.m. UTC | #1
Hi Ezequiel,

On 01/08/18 22:50, Ezequiel Garcia wrote:
> Add a test for the memory-to-memory framework, to exercise the
> scheduling of concurrent jobs, using multiple contexts.
> 
> This test needs to be run using the vim2m virtual driver,
> and so needs no hardware.
> 
> While here, rework the media_tests suite in order to make it
> useful for automatic tools. Those tests that need human intervention
> are now separated from those that can run automatically, needing
> only virtual drivers to work.
> 
> Signed-off-by: Ezequiel Garcia <ezequiel@collabora.com>
> ---
>   .../testing/selftests/media_tests/.gitignore  |   1 +
>   tools/testing/selftests/media_tests/Makefile  |   5 +-
>   .../selftests/media_tests/m2m_job_test.c      | 287 ++++++++++++++++++
>   .../selftests/media_tests/m2m_job_test.sh     |  32 ++
>   4 files changed, 324 insertions(+), 1 deletion(-)
>   create mode 100644 tools/testing/selftests/media_tests/m2m_job_test.c
>   create mode 100755 tools/testing/selftests/media_tests/m2m_job_test.sh
> 
> diff --git a/tools/testing/selftests/media_tests/.gitignore b/tools/testing/selftests/media_tests/.gitignore
> index 8745eba39012..71c6508348ce 100644
> --- a/tools/testing/selftests/media_tests/.gitignore
> +++ b/tools/testing/selftests/media_tests/.gitignore
> @@ -1,3 +1,4 @@
>   media_device_test
>   media_device_open
>   video_device_test
> +m2m_job_test
> diff --git a/tools/testing/selftests/media_tests/Makefile b/tools/testing/selftests/media_tests/Makefile
> index 60826d7d37d4..d25d4c3eb7d2 100644
> --- a/tools/testing/selftests/media_tests/Makefile
> +++ b/tools/testing/selftests/media_tests/Makefile
> @@ -1,6 +1,9 @@
>   # SPDX-License-Identifier: GPL-2.0
>   #
>   CFLAGS += -I../ -I../../../../usr/include/
> -TEST_GEN_PROGS := media_device_test media_device_open video_device_test
> +TEST_GEN_PROGS_EXTENDED := media_device_test media_device_open video_device_test m2m_job_test
> +TEST_PROGS := m2m_job_test.sh
>   
>   include ../lib.mk
> +
> +LDLIBS += -lpthread
> diff --git a/tools/testing/selftests/media_tests/m2m_job_test.c b/tools/testing/selftests/media_tests/m2m_job_test.c
> new file mode 100644
> index 000000000000..5800269567e6
> --- /dev/null
> +++ b/tools/testing/selftests/media_tests/m2m_job_test.c
> @@ -0,0 +1,287 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Copyright (c) Collabora, Ltd.

Add the year (2018), and authors (you).

> +
> +/*
> + * This file adds a test for the memory-to-memory
> + * framework.
> + *
> + * This test opens a user specified video device and then
> + * queues concurrent jobs. The jobs are totally dummy,
> + * its purpose is only to verify that each of the queued
> + * jobs is run, and is run only once.
> + *
> + * The vim2m driver is needed in order to run the test.
> + *
> + * Usage:
> + *      ./m2m-job-test -d /dev/videoX
> + */
> +
> +#include <assert.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/ioctl.h>
> +#include <sys/stat.h>
> +#include <time.h>
> +#include <pthread.h>
> +#include <poll.h>
> +
> +#include <linux/videodev2.h>
> +
> +#include "../kselftest.h"
> +
> +#define V4L2_CID_TRANS_TIME_MSEC        (V4L2_CID_USER_BASE + 0x1000)
> +#define V4L2_CID_TRANS_NUM_BUFS         (V4L2_CID_USER_BASE + 0x1001)
> +
> +#define MAX_TRANS_TIME_MSEC 500
> +#define MAX_COUNT 50
> +#define MAX_BUFFERS 5
> +#define W 10
> +#define H 10

I like short names, but W and H might be a little bit too short
esp for a macro.

> +#ifndef DEBUG
> +#define dprintf(fmt, arg...)			\
> +	do {					\
> +	} while (0)
> +#else
> +#define dprintf(fmt, arg...) printf(fmt, ## arg)
> +#endif
> +
> +static char video_device[256];
> +static int thread_count;
> +
> +struct context {
> +	int vfd;
> +	unsigned int width;
> +	unsigned int height;
> +	int buffers;
> +};
> +
> +static int req_src_buf(struct context *ctx, int buffers)
> +{
> +	struct v4l2_requestbuffers reqbuf;
> +	struct v4l2_buffer buf;
> +	int i, ret;
> +
> +	memset(&reqbuf, 0, sizeof(reqbuf));
> +	memset(&buf, 0, sizeof(buf));
> +
> +	reqbuf.count	= buffers;
> +	reqbuf.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT;
> +	reqbuf.memory	= V4L2_MEMORY_MMAP;
> +	ret = ioctl(ctx->vfd, VIDIOC_REQBUFS, &reqbuf);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < buffers; i++) {
> +		buf.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT;
> +		buf.memory	= V4L2_MEMORY_MMAP;
> +		buf.index	= i;
> +		ret = ioctl(ctx->vfd, VIDIOC_QUERYBUF, &buf);
> +		if (ret)
> +			return ret;
> +		buf.bytesused = W*H*2;

Shouldn't you be using ->width and ->height here rather than W
and H?

In fact, maybe these can actually be set as "static const
unsigned int WIDTH = 10;" in the main function rather than global
macros, since you're parring the context around at runtime.

> +		ret = ioctl(ctx->vfd, VIDIOC_QBUF, &buf);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int req_dst_buf(struct context *ctx, int buffers)
> +{
> +	struct v4l2_requestbuffers reqbuf;
> +	struct v4l2_buffer buf;
> +	int i, ret;
> +
> +	memset(&reqbuf, 0, sizeof(reqbuf));
> +	memset(&buf, 0, sizeof(buf));
> +
> +	reqbuf.count	= buffers;
> +	reqbuf.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	reqbuf.memory	= V4L2_MEMORY_MMAP;
> +
> +	ret = ioctl(ctx->vfd, VIDIOC_REQBUFS, &reqbuf);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < buffers; i++) {
> +		buf.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +		buf.memory	= V4L2_MEMORY_MMAP;
> +		buf.index	= i;
> +		ret = ioctl(ctx->vfd, VIDIOC_QUERYBUF, &buf);
> +		if (ret)
> +			return ret;
> +		ret = ioctl(ctx->vfd, VIDIOC_QBUF, &buf);
> +		if (ret)
> +			return ret;
> +	}
> +	return 0;
> +}
> +
> +static int streamon(struct context *ctx)
> +{
> +	enum v4l2_buf_type type;
> +	int ret;
> +
> +	type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
> +	ret = ioctl(ctx->vfd, VIDIOC_STREAMON, &type);
> +	if (ret)
> +		return ret;
> +
> +	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	ret = ioctl(ctx->vfd, VIDIOC_STREAMON, &type);
> +	if (ret)
> +		return ret;
> +
> +	return ret;
> +}
> +
> +static int dqbuf(struct context *ctx)
> +{
> +	struct v4l2_buffer buf;
> +	int i, ret, timeout;
> +
> +	struct pollfd fds[] = {
> +		{ .fd = ctx->vfd, .events = POLLIN },
> +	};
> +
> +	for (i = 0; i < ctx->buffers; i++) {
> +		timeout = (MAX_TRANS_TIME_MSEC + 10) * thread_count * 2;
> +		ret = poll(fds, 1, timeout);
> +		if (-1 == ret) {
> +			if (errno == EINTR)
> +				continue;
> +			return -1;
> +		}
> +
> +		if (ret == 0) {
> +			dprintf("%s: timeout on %p\n", __func__, ctx);
> +			return -1;
> +		}
> +
> +		dprintf("%s: event on %p\n", __func__, ctx);
> +
> +		memset(&buf, 0, sizeof(buf));
> +		buf.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT;
> +		buf.memory	= V4L2_MEMORY_MMAP;
> +		buf.index	= i;
> +		ret = ioctl(ctx->vfd, VIDIOC_DQBUF, &buf);
> +		if (ret) {
> +			dprintf("%s: VIDIOC_DQBUF failed %p\n", __func__, ctx);
> +			return ret;
> +		}
> +
> +		memset(&buf, 0, sizeof(buf));
> +		buf.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +		buf.memory	= V4L2_MEMORY_MMAP;
> +		buf.index	= i;
> +		ret = ioctl(ctx->vfd, VIDIOC_DQBUF, &buf);
> +		if (ret) {
> +			dprintf("%s: VIDIOC_DQBUF failed %p\n", __func__, ctx);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void *job(void *arg)
> +{
> +	struct context *ctx = (struct context *)arg;

The cast isn't actually required there, coming from a (void *).
If it's done on purpose for readability reasons then fine.

> +	dprintf("%s: %p running\n", __func__, ctx);
> +
> +	assert(streamon(ctx) == 0);
> +	assert(dqbuf(ctx) == 0);
> +	assert(dqbuf(ctx) != 0);
> +	close(ctx->vfd);
> +
> +	dprintf("%s: %p done\n", __func__, ctx);
> +	return NULL;
> +}
> +
> +static void init(struct context *ctx)
> +{
> +	struct v4l2_ext_controls ext_ctrls;
> +	struct v4l2_ext_control ctrls[2];
> +	struct v4l2_capability cap;
> +	int ret, buffers;
> +
> +	memset(ctx, 0, sizeof(*ctx));
> +
> +	ctx->vfd = open(video_device, O_RDWR | O_NONBLOCK, 0);
> +	ctx->width = W;
> +	ctx->height = H;
> +	assert(ctx->vfd >= 0);
> +
> +	ret = ioctl(ctx->vfd, VIDIOC_QUERYCAP, &cap);
> +	assert(ret == 0);
> +	assert(cap.device_caps & V4L2_CAP_VIDEO_M2M);
> +	if (strcmp((const char *)cap.driver, "vim2m") != 0)

This cast isn't needed either, passing non-const to const.

> +		ksft_exit_skip("Please run the test as root - Exiting.\n");
> +
> +	ctrls[0].id = V4L2_CID_TRANS_TIME_MSEC;
> +	ctrls[0].value = rand() % MAX_TRANS_TIME_MSEC + 10;

See what I wrote about random factors in tests below, regarding
the number of threads.  If you need a random series of values
here, using the pseudo-random function rand() should be fine but
it might be worth setting the seed with srand() first with a
fixed value to be sure it always produces the same series so the
test always does the same thing.

> +	ctrls[1].id =  V4L2_CID_TRANS_NUM_BUFS;
> +	ctrls[1].value = 1;
> +
> +	memset(&ext_ctrls, 0, sizeof(ext_ctrls));
> +	ext_ctrls.count = 2;
> +	ext_ctrls.controls = ctrls;
> +	ret = ioctl(ctx->vfd, VIDIOC_S_EXT_CTRLS, &ext_ctrls);
> +	assert(ret == 0);
> +
> +	buffers = rand() % MAX_BUFFERS + 1;
> +	assert(req_src_buf(ctx, buffers) == 0);
> +	assert(req_dst_buf(ctx, buffers) == 0);
> +	ctx->buffers = buffers;
> +}
> +
> +int main(int argc, char * const argv[])
> +{
> +	int i, opt;
> +
> +	if (argc < 2) {
> +		printf("Usage: %s [-d </dev/videoX>]\n", argv[0]);
> +		exit(-1);
> +	}
> +
> +	/* Process arguments */
> +	while ((opt = getopt(argc, argv, "d:")) != -1) {
> +		switch (opt) {
> +		case 'd':
> +			strncpy(video_device, optarg, sizeof(video_device) - 1);
> +			video_device[sizeof(video_device)-1] = '\0';
> +			break;
> +		default:
> +			printf("Usage: %s [-d </dev/videoX>]\n", argv[0]);
> +			exit(-1);
> +		}
> +	}
> +
> +	/* Generate random number of interations */

Did you mean iterations?

> +	srand((unsigned int) time(NULL));
> +	thread_count = rand() % MAX_COUNT + 1;

Having random factors in tests can lead to hard to reproduce
issues.  I think it would be wiser to set a reasonable default
value (maybe 50) and add a command line option to override it if
necessary.  For example, setting a very high value may push the
system to hit an unexpected limit, or if running automatically on
slow hardware a lower value may be used to reduce the time it
takes to run this test case.

> +	pthread_t in_thread[thread_count];
> +	struct context ctx[thread_count];
> +
> +	printf("Running %d threads\n", thread_count);
> +
> +	for (i = 0; i < thread_count; i++)
> +		init(&ctx[i]);
> +
> +	for (i = 0; i < thread_count; i++)
> +		pthread_create(&in_thread[i], NULL, job, &ctx[i]);
> +
> +	for (i = 0; i < thread_count; i++)
> +		pthread_join(in_thread[i], NULL);
> +
> +	return 0;
> +}
> diff --git a/tools/testing/selftests/media_tests/m2m_job_test.sh b/tools/testing/selftests/media_tests/m2m_job_test.sh
> new file mode 100755
> index 000000000000..59777a7ac7d8
> --- /dev/null
> +++ b/tools/testing/selftests/media_tests/m2m_job_test.sh
> @@ -0,0 +1,32 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +TCID="media_device"
> +
> +# Kselftest framework requirement - SKIP code is 4.
> +ksft_skip=4
> +
> +echo "-------------------"
> +echo "running media tests"
> +echo "-------------------"
> +
> +# Not needed, but does not hurt to have it
> +shopt -s nullglob
> +
> +v4l=/sys/class/video4linux
> +
> +if [ ! -d $v4l ]; then
> +        echo "$TCID : video4linux support not present"
> +        exit $ksft_skip
> +fi
> +
> +if [ -z `ls $v4l` ]; then
> +	echo "$TCID : no video4linux drivers loaded, vim2m is needed"
> +	exit $ksft_skip
> +fi
> +
> +for f in $v4l/*; do
> +	dev_node=/dev/`basename $f`
> +	if [ -c $dev_node ]; then
> +		./m2m_job_test -d $dev_node
> +	fi
> +done

Best wishes,
Guillaume
--
To unsubscribe from this list: send the line "unsubscribe linux-kselftest" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox series

Patch

diff --git a/tools/testing/selftests/media_tests/.gitignore b/tools/testing/selftests/media_tests/.gitignore
index 8745eba39012..71c6508348ce 100644
--- a/tools/testing/selftests/media_tests/.gitignore
+++ b/tools/testing/selftests/media_tests/.gitignore
@@ -1,3 +1,4 @@ 
 media_device_test
 media_device_open
 video_device_test
+m2m_job_test
diff --git a/tools/testing/selftests/media_tests/Makefile b/tools/testing/selftests/media_tests/Makefile
index 60826d7d37d4..d25d4c3eb7d2 100644
--- a/tools/testing/selftests/media_tests/Makefile
+++ b/tools/testing/selftests/media_tests/Makefile
@@ -1,6 +1,9 @@ 
 # SPDX-License-Identifier: GPL-2.0
 #
 CFLAGS += -I../ -I../../../../usr/include/
-TEST_GEN_PROGS := media_device_test media_device_open video_device_test
+TEST_GEN_PROGS_EXTENDED := media_device_test media_device_open video_device_test m2m_job_test
+TEST_PROGS := m2m_job_test.sh
 
 include ../lib.mk
+
+LDLIBS += -lpthread
diff --git a/tools/testing/selftests/media_tests/m2m_job_test.c b/tools/testing/selftests/media_tests/m2m_job_test.c
new file mode 100644
index 000000000000..5800269567e6
--- /dev/null
+++ b/tools/testing/selftests/media_tests/m2m_job_test.c
@@ -0,0 +1,287 @@ 
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) Collabora, Ltd.
+
+/*
+ * This file adds a test for the memory-to-memory
+ * framework.
+ *
+ * This test opens a user specified video device and then
+ * queues concurrent jobs. The jobs are totally dummy,
+ * its purpose is only to verify that each of the queued
+ * jobs is run, and is run only once.
+ *
+ * The vim2m driver is needed in order to run the test.
+ *
+ * Usage:
+ *      ./m2m-job-test -d /dev/videoX
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+
+#include <linux/videodev2.h>
+
+#include "../kselftest.h"
+
+#define V4L2_CID_TRANS_TIME_MSEC        (V4L2_CID_USER_BASE + 0x1000)
+#define V4L2_CID_TRANS_NUM_BUFS         (V4L2_CID_USER_BASE + 0x1001)
+
+#define MAX_TRANS_TIME_MSEC 500
+#define MAX_COUNT 50
+#define MAX_BUFFERS 5
+#define W 10
+#define H 10
+
+#ifndef DEBUG
+#define dprintf(fmt, arg...)			\
+	do {					\
+	} while (0)
+#else
+#define dprintf(fmt, arg...) printf(fmt, ## arg)
+#endif
+
+static char video_device[256];
+static int thread_count;
+
+struct context {
+	int vfd;
+	unsigned int width;
+	unsigned int height;
+	int buffers;
+};
+
+static int req_src_buf(struct context *ctx, int buffers)
+{
+	struct v4l2_requestbuffers reqbuf;
+	struct v4l2_buffer buf;
+	int i, ret;
+
+	memset(&reqbuf, 0, sizeof(reqbuf));
+	memset(&buf, 0, sizeof(buf));
+
+	reqbuf.count	= buffers;
+	reqbuf.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	reqbuf.memory	= V4L2_MEMORY_MMAP;
+	ret = ioctl(ctx->vfd, VIDIOC_REQBUFS, &reqbuf);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < buffers; i++) {
+		buf.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT;
+		buf.memory	= V4L2_MEMORY_MMAP;
+		buf.index	= i;
+		ret = ioctl(ctx->vfd, VIDIOC_QUERYBUF, &buf);
+		if (ret)
+			return ret;
+		buf.bytesused = W*H*2;
+		ret = ioctl(ctx->vfd, VIDIOC_QBUF, &buf);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int req_dst_buf(struct context *ctx, int buffers)
+{
+	struct v4l2_requestbuffers reqbuf;
+	struct v4l2_buffer buf;
+	int i, ret;
+
+	memset(&reqbuf, 0, sizeof(reqbuf));
+	memset(&buf, 0, sizeof(buf));
+
+	reqbuf.count	= buffers;
+	reqbuf.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	reqbuf.memory	= V4L2_MEMORY_MMAP;
+
+	ret = ioctl(ctx->vfd, VIDIOC_REQBUFS, &reqbuf);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < buffers; i++) {
+		buf.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		buf.memory	= V4L2_MEMORY_MMAP;
+		buf.index	= i;
+		ret = ioctl(ctx->vfd, VIDIOC_QUERYBUF, &buf);
+		if (ret)
+			return ret;
+		ret = ioctl(ctx->vfd, VIDIOC_QBUF, &buf);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int streamon(struct context *ctx)
+{
+	enum v4l2_buf_type type;
+	int ret;
+
+	type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	ret = ioctl(ctx->vfd, VIDIOC_STREAMON, &type);
+	if (ret)
+		return ret;
+
+	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	ret = ioctl(ctx->vfd, VIDIOC_STREAMON, &type);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+static int dqbuf(struct context *ctx)
+{
+	struct v4l2_buffer buf;
+	int i, ret, timeout;
+
+	struct pollfd fds[] = {
+		{ .fd = ctx->vfd, .events = POLLIN },
+	};
+
+	for (i = 0; i < ctx->buffers; i++) {
+		timeout = (MAX_TRANS_TIME_MSEC + 10) * thread_count * 2;
+		ret = poll(fds, 1, timeout);
+		if (-1 == ret) {
+			if (errno == EINTR)
+				continue;
+			return -1;
+		}
+
+		if (ret == 0) {
+			dprintf("%s: timeout on %p\n", __func__, ctx);
+			return -1;
+		}
+
+		dprintf("%s: event on %p\n", __func__, ctx);
+
+		memset(&buf, 0, sizeof(buf));
+		buf.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT;
+		buf.memory	= V4L2_MEMORY_MMAP;
+		buf.index	= i;
+		ret = ioctl(ctx->vfd, VIDIOC_DQBUF, &buf);
+		if (ret) {
+			dprintf("%s: VIDIOC_DQBUF failed %p\n", __func__, ctx);
+			return ret;
+		}
+
+		memset(&buf, 0, sizeof(buf));
+		buf.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		buf.memory	= V4L2_MEMORY_MMAP;
+		buf.index	= i;
+		ret = ioctl(ctx->vfd, VIDIOC_DQBUF, &buf);
+		if (ret) {
+			dprintf("%s: VIDIOC_DQBUF failed %p\n", __func__, ctx);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void *job(void *arg)
+{
+	struct context *ctx = (struct context *)arg;
+
+	dprintf("%s: %p running\n", __func__, ctx);
+
+	assert(streamon(ctx) == 0);
+	assert(dqbuf(ctx) == 0);
+	assert(dqbuf(ctx) != 0);
+	close(ctx->vfd);
+
+	dprintf("%s: %p done\n", __func__, ctx);
+	return NULL;
+}
+
+static void init(struct context *ctx)
+{
+	struct v4l2_ext_controls ext_ctrls;
+	struct v4l2_ext_control ctrls[2];
+	struct v4l2_capability cap;
+	int ret, buffers;
+
+	memset(ctx, 0, sizeof(*ctx));
+
+	ctx->vfd = open(video_device, O_RDWR | O_NONBLOCK, 0);
+	ctx->width = W;
+	ctx->height = H;
+	assert(ctx->vfd >= 0);
+
+	ret = ioctl(ctx->vfd, VIDIOC_QUERYCAP, &cap);
+	assert(ret == 0);
+	assert(cap.device_caps & V4L2_CAP_VIDEO_M2M);
+	if (strcmp((const char *)cap.driver, "vim2m") != 0)
+		ksft_exit_skip("Please run the test as root - Exiting.\n");
+
+	ctrls[0].id = V4L2_CID_TRANS_TIME_MSEC;
+	ctrls[0].value = rand() % MAX_TRANS_TIME_MSEC + 10;
+	ctrls[1].id =  V4L2_CID_TRANS_NUM_BUFS;
+	ctrls[1].value = 1;
+
+	memset(&ext_ctrls, 0, sizeof(ext_ctrls));
+	ext_ctrls.count = 2;
+	ext_ctrls.controls = ctrls;
+	ret = ioctl(ctx->vfd, VIDIOC_S_EXT_CTRLS, &ext_ctrls);
+	assert(ret == 0);
+
+	buffers = rand() % MAX_BUFFERS + 1;
+	assert(req_src_buf(ctx, buffers) == 0);
+	assert(req_dst_buf(ctx, buffers) == 0);
+	ctx->buffers = buffers;
+}
+
+int main(int argc, char * const argv[])
+{
+	int i, opt;
+
+	if (argc < 2) {
+		printf("Usage: %s [-d </dev/videoX>]\n", argv[0]);
+		exit(-1);
+	}
+
+	/* Process arguments */
+	while ((opt = getopt(argc, argv, "d:")) != -1) {
+		switch (opt) {
+		case 'd':
+			strncpy(video_device, optarg, sizeof(video_device) - 1);
+			video_device[sizeof(video_device)-1] = '\0';
+			break;
+		default:
+			printf("Usage: %s [-d </dev/videoX>]\n", argv[0]);
+			exit(-1);
+		}
+	}
+
+	/* Generate random number of interations */
+	srand((unsigned int) time(NULL));
+	thread_count = rand() % MAX_COUNT + 1;
+
+	pthread_t in_thread[thread_count];
+	struct context ctx[thread_count];
+
+	printf("Running %d threads\n", thread_count);
+
+	for (i = 0; i < thread_count; i++)
+		init(&ctx[i]);
+
+	for (i = 0; i < thread_count; i++)
+		pthread_create(&in_thread[i], NULL, job, &ctx[i]);
+
+	for (i = 0; i < thread_count; i++)
+		pthread_join(in_thread[i], NULL);
+
+	return 0;
+}
diff --git a/tools/testing/selftests/media_tests/m2m_job_test.sh b/tools/testing/selftests/media_tests/m2m_job_test.sh
new file mode 100755
index 000000000000..59777a7ac7d8
--- /dev/null
+++ b/tools/testing/selftests/media_tests/m2m_job_test.sh
@@ -0,0 +1,32 @@ 
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+TCID="media_device"
+
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+echo "-------------------"
+echo "running media tests"
+echo "-------------------"
+
+# Not needed, but does not hurt to have it
+shopt -s nullglob
+
+v4l=/sys/class/video4linux
+
+if [ ! -d $v4l ]; then
+        echo "$TCID : video4linux support not present"
+        exit $ksft_skip
+fi
+
+if [ -z `ls $v4l` ]; then
+	echo "$TCID : no video4linux drivers loaded, vim2m is needed"
+	exit $ksft_skip
+fi
+
+for f in $v4l/*; do
+	dev_node=/dev/`basename $f`
+	if [ -c $dev_node ]; then
+		./m2m_job_test -d $dev_node
+	fi
+done