@@ -1,6 +1,8 @@
# 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 := media_device_test media_device_open video_device_test m2m_job_test
include ../lib.mk
+
+LDLIBS += -lpthread
new file mode 100644
@@ -0,0 +1,283 @@
+// 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>
+
+#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 300
+#define MAX_BUFFERS 5
+#define W 100
+#define H 100
+
+#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;
+ struct pollfd fds[] = {
+ { .fd = ctx->vfd, .events = POLLIN },
+ };
+ int i, ret, tout;
+
+ tout = (MAX_TRANS_TIME_MSEC + 10) * thread_count * 2;
+ for (i = 0; i < ctx->buffers; i++) {
+ ret = poll(fds, 1, tout);
+ if (ret == -1) {
+ 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: running %p\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);
+ assert(strcmp((const char *)cap.driver, "vim2m") == 0);
+
+ 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;
+}
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. Signed-off-by: Ezequiel Garcia <ezequiel@collabora.com> --- tools/testing/selftests/media_tests/Makefile | 4 +- .../selftests/media_tests/m2m_job_test.c | 283 ++++++++++++++++++ 2 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/media_tests/m2m_job_test.c