v4l2-compliance: add streaming tests
authorHans Verkuil <hans.verkuil@cisco.com>
Tue, 11 Feb 2014 07:36:05 +0000 (08:36 +0100)
committerHans Verkuil <hans.verkuil@cisco.com>
Tue, 11 Feb 2014 07:36:05 +0000 (08:36 +0100)
Add tests for the streaming ioctls. If the -s flag is specified then the
streaming tests are enabled. These assume a valid input/output and/or
frequency. Options to set those are also added.

Currently only single plane video capture is supported. This is however
90% of all drivers, so this should be OK for now while work progresses
on other formats. Also the DMABUF streaming is not tested.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
utils/v4l2-compliance/v4l2-compliance.cpp
utils/v4l2-compliance/v4l2-compliance.h
utils/v4l2-compliance/v4l2-test-buffers.cpp

index d9fef7b..28af6c9 100644 (file)
    case is used to retrieve a setting. */
 enum Option {
        OptSetDevice = 'd',
+       OptSetFreq = 'f',
        OptHelp = 'h',
+       OptSetInput = 'i',
        OptNoWarnings = 'n',
+       OptSetOutput = 'o',
        OptSetRadioDevice = 'r',
-       OptTest = 't',
+       OptStreaming = 's',
        OptTrace = 'T',
        OptVerbose = 'v',
        OptSetVbiDevice = 'V',
@@ -68,6 +71,10 @@ bool wrapper;
 int kernel_version;
 unsigned warnings;
 
+static unsigned select_input;
+static unsigned select_output;
+static double select_freq;
+
 static struct option long_options[] = {
        {"device", required_argument, 0, OptSetDevice},
        {"radio-device", required_argument, 0, OptSetRadioDevice},
@@ -77,6 +84,10 @@ static struct option long_options[] = {
        {"no-warnings", no_argument, 0, OptNoWarnings},
        {"trace", no_argument, 0, OptTrace},
        {"wrapper", no_argument, 0, OptUseWrapper},
+       {"set-input", required_argument, 0, OptSetInput},
+       {"set-output", required_argument, 0, OptSetOutput},
+       {"set-freq", required_argument, 0, OptSetFreq},
+       {"streaming", no_argument, 0, OptStreaming},
        {0, 0, 0, 0}
 };
 
@@ -84,13 +95,18 @@ static void usage(void)
 {
        printf("Usage:\n");
        printf("Common options:\n");
-       printf("  -d, --device=<dev> use device <dev> as the video device\n");
-       printf("                     if <dev> starts with a digit, then /dev/video<dev> is used\n");
-       printf("  -r, --radio-device=<dev> use device <dev> as the radio device\n");
-       printf("                     if <dev> starts with a digit, then /dev/radio<dev> is used\n");
-       printf("  -V, --vbi-device=<dev> use device <dev> as the vbi device\n");
-       printf("                     if <dev> starts with a digit, then /dev/vbi<dev> is used\n");
-       printf("  -h, --help         display this help message\n");
+       printf("  -d, --device=<dev> use device <dev> as the video device.\n");
+       printf("                     if <dev> starts with a digit, then /dev/video<dev> is used.\n");
+       printf("  -r, --radio-device=<dev> use device <dev> as the radio device.\n");
+       printf("                     if <dev> starts with a digit, then /dev/radio<dev> is used.\n");
+       printf("  -V, --vbi-device=<dev> use device <dev> as the vbi device.\n");
+       printf("                     if <dev> starts with a digit, then /dev/vbi<dev> is used.\n");
+       printf("  -i, --set-input    select input for streaming tests (default is 0).\n");
+       printf("  -o, --set-output   select output for streaming tests (default is 0).\n");
+       printf("  -f, --set-freq     select frequency in MHz (kHz for radio) for streaming tests.\n");
+       printf("  -s, --streaming    enable the streaming tests. Requires a valid input/output and\n");
+       printf("                     frequency (when dealing with a tuner).\n");
+       printf("  -h, --help         display this help message.\n");
        printf("  -n, --no-warnings  turn off warning messages.\n");
        printf("  -T, --trace        trace all called ioctls.\n");
        printf("  -v, --verbose      turn on verbose reporting.\n");
@@ -445,6 +461,15 @@ int main(int argc, char **argv)
                                vbi_device = newdev;
                        }
                        break;
+               case OptSetInput:
+                       select_input = strtoul(optarg, NULL, 0);
+                       break;
+               case OptSetOutput:
+                       select_output = strtoul(optarg, NULL, 0);
+                       break;
+               case OptSetFreq:
+                       select_freq = strtod(optarg, NULL);
+                       break;
                case OptNoWarnings:
                        show_warnings = false;
                        break;
@@ -685,15 +710,46 @@ int main(int argc, char **argv)
 
        printf("Buffer ioctls:\n");
        printf("\ttest VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: %s\n", ok(testReqBufs(&node)));
-       //printf("\ttest read/write: %s\n", ok(testReadWrite(&node)));
+       if (options[OptStreaming]) {
+               if (options[OptSetInput])
+                       doioctl(&node, VIDIOC_S_INPUT, &select_input);
+               if (options[OptSetOutput])
+                       doioctl(&node, VIDIOC_S_OUTPUT, &select_output);
+               if (options[OptSetFreq]) {
+                       struct v4l2_frequency f = { 0 };
+                       unsigned freq_caps;
+
+                       if (node.caps & V4L2_CAP_MODULATOR) {
+                               struct v4l2_modulator m = { 0 };
+
+                               doioctl(&node, VIDIOC_G_MODULATOR, &m);
+                               freq_caps = m.capability;
+                               f.type = V4L2_TUNER_RADIO;
+                       } else {
+                               struct v4l2_tuner t = { 0 };
+
+                               doioctl(&node, VIDIOC_G_TUNER, &t);
+                               f.type = t.type;
+                               freq_caps = t.capability;
+                       }
+                       if (freq_caps & V4L2_TUNER_CAP_LOW)
+                               f.frequency = select_freq / 62.5;
+                       else
+                               f.frequency = select_freq / 62500;
+                       doioctl(&node, VIDIOC_S_FREQUENCY, &f);
+               }
+
+               printf("\ttest read/write: %s\n", ok(testReadWrite(&node)));
+               printf("\ttest MMAP: %s\n", ok(testMmap(&node)));
+               printf("\ttest USERPTR: %s\n", ok(testUserPtr(&node)));
+       }
        printf("\n");
 
        /* TODO:
 
           VIDIOC_CROPCAP, VIDIOC_G/S_CROP, VIDIOC_G/S_SELECTION
           VIDIOC_S_FBUF/OVERLAY
-          VIDIOC_QBUF/DQBUF/QUERYBUF/PREPARE_BUFS/EXPBUF
-          VIDIOC_STREAMON/OFF
+          VIDIOC_EXPBUF
           */
 
        /* Final test report */
index f9b5291..c53e4a0 100644 (file)
@@ -200,5 +200,7 @@ int testDecoder(struct node *node);
 // Buffer ioctl tests
 int testReqBufs(struct node *node);
 int testReadWrite(struct node *node);
+int testMmap(struct node *node);
+int testUserPtr(struct node *node);
 
 #endif
index b7e84cf..be7cb2b 100644 (file)
 #include <inttypes.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/mman.h>
 #include <fcntl.h>
 #include <ctype.h>
 #include <errno.h>
 #include <sys/ioctl.h>
 #include "v4l2-compliance.h"
 
+static inline void *test_mmap(void *start, size_t length, int prot, int flags,
+               int fd, int64_t offset)
+{
+       return wrapper ? v4l2_mmap(start, length, prot, flags, fd, offset) :
+               mmap(start, length, prot, flags, fd, offset);
+}
+
+static void *ptrs[VIDEO_MAX_FRAME];
+static struct v4l2_format cur_fmt;
+static int last_seq;
+static unsigned last_field;
+static unsigned field_nr;
+
+enum QueryBufMode {
+       Unqueued,
+       Prepared,
+       Queued,
+       Dequeued
+};
+
+static std::string num2s(unsigned num)
+{
+       char buf[10];
+
+       sprintf(buf, "%08x", num);
+       return buf;
+}
+
+static std::string field2s(unsigned val)
+{
+       switch (val) {
+       case V4L2_FIELD_ANY:
+               return "Any";
+       case V4L2_FIELD_NONE:
+               return "None";
+       case V4L2_FIELD_TOP:
+               return "Top";
+       case V4L2_FIELD_BOTTOM:
+               return "Bottom";
+       case V4L2_FIELD_INTERLACED:
+               return "Interlaced";
+       case V4L2_FIELD_SEQ_TB:
+               return "Sequential Top-Bottom";
+       case V4L2_FIELD_SEQ_BT:
+               return "Sequential Bottom-Top";
+       case V4L2_FIELD_ALTERNATE:
+               return "Alternating";
+       case V4L2_FIELD_INTERLACED_TB:
+               return "Interlaced Top-Bottom";
+       case V4L2_FIELD_INTERLACED_BT:
+               return "Interlaced Bottom-Top";
+       default:
+               return "Unknown (" + num2s(val) + ")";
+       }
+}
+
+static int checkQueryBuf(struct node *node, const struct v4l2_buffer &buf,
+               __u32 type, __u32 memory, unsigned index, enum QueryBufMode mode,
+               unsigned count)
+{
+       unsigned timestamp = buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MASK;
+       unsigned frame_types = 0;
+       unsigned buf_states = 0;
+
+       fail_on_test(buf.type != type);
+       fail_on_test(buf.memory == 0);
+       fail_on_test(buf.memory != memory);
+       fail_on_test(buf.index >= VIDEO_MAX_FRAME);
+       fail_on_test(buf.index != index);
+       fail_on_test(buf.reserved2 || buf.reserved);
+       fail_on_test(timestamp != V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC &&
+                    timestamp != V4L2_BUF_FLAG_TIMESTAMP_COPY);
+       if (!node->is_m2m)
+               fail_on_test(timestamp == V4L2_BUF_FLAG_TIMESTAMP_COPY);
+       if (buf.flags & V4L2_BUF_FLAG_KEYFRAME)
+               frame_types++;
+       if (buf.flags & V4L2_BUF_FLAG_PFRAME)
+               frame_types++;
+       if (buf.flags & V4L2_BUF_FLAG_BFRAME)
+               frame_types++;
+       fail_on_test(frame_types > 1);
+       fail_on_test(buf.flags & (V4L2_BUF_FLAG_NO_CACHE_INVALIDATE |
+                                 V4L2_BUF_FLAG_NO_CACHE_CLEAN));
+       if (buf.flags & V4L2_BUF_FLAG_QUEUED)
+               buf_states++;
+       if (buf.flags & V4L2_BUF_FLAG_DONE)
+               buf_states++;
+       if (buf.flags & V4L2_BUF_FLAG_ERROR)
+               buf_states++;
+       if (buf.flags & V4L2_BUF_FLAG_PREPARED)
+               buf_states++;
+       fail_on_test(buf_states > 1);
+       fail_on_test(buf.length == 0);
+       if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+               fail_on_test(buf.length > VIDEO_MAX_PLANES);
+               for (unsigned p = 0; p < buf.length; p++) {
+                       struct v4l2_plane *vp = buf.m.planes + p;
+
+                       fail_on_test(check_0(vp->reserved, sizeof(vp->reserved)));
+                       fail_on_test(vp->length == 0);
+               }
+       }
+
+       if (mode == Dequeued || mode == Prepared) {
+               fail_on_test(!(buf.flags & (V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_ERROR)));
+               if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+                       fail_on_test(buf.length <= VIDEO_MAX_PLANES);
+                       for (unsigned p = 0; p < buf.length; p++) {
+                               struct v4l2_plane *vp = buf.m.planes + p;
+
+                               if (buf.memory == V4L2_MEMORY_USERPTR)
+                                       fail_on_test((void *)vp->m.userptr != ptrs[buf.index]);
+                               fail_on_test(vp->data_offset + vp->bytesused > vp->length);
+                       }
+               } else {
+                       if (buf.memory == V4L2_MEMORY_USERPTR)
+                               fail_on_test((void *)buf.m.userptr != ptrs[buf.index]);
+               }
+       }
+
+       if (mode == Dequeued) {
+               fail_on_test(!buf.bytesused);
+               fail_on_test(buf.bytesused > buf.length);
+               fail_on_test(!buf.timestamp.tv_sec && !buf.timestamp.tv_usec);
+               fail_on_test(buf.field == V4L2_FIELD_ALTERNATE);
+               fail_on_test(buf.field == V4L2_FIELD_ANY);
+               if (cur_fmt.fmt.pix.field == V4L2_FIELD_ALTERNATE) {
+                       fail_on_test(buf.field != V4L2_FIELD_BOTTOM &&
+                                    buf.field != V4L2_FIELD_TOP);
+                       fail_on_test(buf.field == last_field);
+                       field_nr ^= 1;
+                       if (field_nr)
+                               fail_on_test((int)buf.sequence != last_seq);
+                       else
+                               fail_on_test((int)buf.sequence != last_seq + 1);
+               } else {
+                       fail_on_test(buf.field != cur_fmt.fmt.pix.field);
+                       fail_on_test((int)buf.sequence != last_seq + 1);
+               }
+               last_seq = (int)buf.sequence;
+               last_field = buf.field;
+
+               if (buf.flags & V4L2_BUF_FLAG_TIMECODE)
+                       warn("V4L2_BUF_FLAG_TIMECODE was used!\n");
+       } else {
+               fail_on_test(buf.sequence);
+               fail_on_test(buf.timestamp.tv_sec || buf.timestamp.tv_usec);
+               fail_on_test(buf.flags & V4L2_BUF_FLAG_TIMECODE);
+               fail_on_test(frame_types);
+               if (mode == Unqueued)
+                       fail_on_test(buf.flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_PREPARED |
+                                                 V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_ERROR));
+               else if (mode == Prepared)
+                       fail_on_test((buf.flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_PREPARED |
+                                                  V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_ERROR)) !=
+                                       V4L2_BUF_FLAG_PREPARED);
+               else
+                       fail_on_test(!(buf.flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_PREPARED)));
+       }
+       return 0;
+}
+
 static int testQueryBuf(struct node *node, unsigned type, unsigned count)
 {
+       struct v4l2_plane planes[VIDEO_MAX_PLANES];
        struct v4l2_buffer buf;
        int ret;
        unsigned i;
 
        memset(&buf, 0, sizeof(buf));
        buf.type = type;
+       if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
+               buf.m.planes = planes;
+               buf.length = VIDEO_MAX_PLANES;
+       }
        for (i = 0; i < count; i++) {
-               unsigned timestamp;
-
                buf.index = i;
                fail_on_test(doioctl(node, VIDIOC_QUERYBUF, &buf));
-               timestamp = buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MASK;
-               fail_on_test(buf.index != i);
-               fail_on_test(buf.type != type);
-               fail_on_test(buf.flags & (V4L2_BUF_FLAG_QUEUED |
-                                       V4L2_BUF_FLAG_DONE |
-                                       V4L2_BUF_FLAG_ERROR));
-               fail_on_test(timestamp != V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC &&
-                            timestamp != V4L2_BUF_FLAG_TIMESTAMP_COPY);
+               if (V4L2_TYPE_IS_MULTIPLANAR(type))
+                       fail_on_test(buf.m.planes != planes);
+               fail_on_test(checkQueryBuf(node, buf, type, buf.memory, i, Unqueued, 0));
        }
        buf.index = count;
        ret = doioctl(node, VIDIOC_QUERYBUF, &buf);
@@ -59,6 +220,7 @@ static int testQueryBuf(struct node *node, unsigned type, unsigned count)
        return 0;
 }
 
+
 int testReqBufs(struct node *node)
 {
        struct v4l2_requestbuffers bufs;
@@ -125,7 +287,7 @@ int testReqBufs(struct node *node)
                        fail_on_test(bufs.memory != V4L2_MEMORY_MMAP);
                        fail_on_test(bufs.type != i);
                        fail_on_test(doioctl(node, VIDIOC_REQBUFS, &bufs));
-                       testQueryBuf(node, i, bufs.count);
+                       fail_on_test(testQueryBuf(node, i, bufs.count));
                }
 
                if (userptr_valid) {
@@ -136,7 +298,7 @@ int testReqBufs(struct node *node)
                        fail_on_test(bufs.memory != V4L2_MEMORY_USERPTR);
                        fail_on_test(bufs.type != i);
                        fail_on_test(doioctl(node, VIDIOC_REQBUFS, &bufs));
-                       testQueryBuf(node, i, bufs.count);
+                       fail_on_test(testQueryBuf(node, i, bufs.count));
                }
 
                if (dmabuf_valid) {
@@ -147,7 +309,7 @@ int testReqBufs(struct node *node)
                        fail_on_test(bufs.memory != V4L2_MEMORY_DMABUF);
                        fail_on_test(bufs.type != i);
                        fail_on_test(doioctl(node, VIDIOC_REQBUFS, &bufs));
-                       testQueryBuf(node, i, bufs.count);
+                       fail_on_test(testQueryBuf(node, i, bufs.count));
                }
 
                if (can_rw) {
@@ -184,7 +346,7 @@ int testReqBufs(struct node *node)
                fail_on_test(cbufs.count == 0);
                fail_on_test(cbufs.memory != bufs.memory);
                fail_on_test(cbufs.format.type != i);
-               testQueryBuf(node, i, cbufs.count);
+               fail_on_test(testQueryBuf(node, i, cbufs.count));
                cbufs.count = 1;
                fail_on_test(doioctl(node, VIDIOC_CREATE_BUFS, &cbufs));
                if (!node->is_m2m) {
@@ -231,3 +393,272 @@ rw_exit:
        reopen(node);
        return 0;
 }
+
+static int setupMmap(struct node *node, struct v4l2_requestbuffers &bufs)
+{
+       for (unsigned i = 0; i < bufs.count; i++) {
+               struct v4l2_plane planes[VIDEO_MAX_PLANES];
+               struct v4l2_buffer buf;
+               int ret;
+
+               memset(&buf, 0, sizeof(buf));
+               buf.type = bufs.type;
+               buf.memory = bufs.memory;
+               buf.index = i;
+               if (V4L2_TYPE_IS_MULTIPLANAR(bufs.type)) {
+                       buf.m.planes = planes;
+                       buf.length = VIDEO_MAX_PLANES;
+               }
+               fail_on_test(doioctl(node, VIDIOC_QUERYBUF, &buf));
+               fail_on_test(checkQueryBuf(node, buf, bufs.type, bufs.memory, i, Unqueued, 0));
+               ptrs[i] = test_mmap(NULL, buf.length,
+                                 PROT_READ | PROT_WRITE, MAP_SHARED, node->fd, buf.m.offset);
+
+               fail_on_test(ptrs[i] == MAP_FAILED);
+
+               ret = doioctl(node, VIDIOC_PREPARE_BUF, &buf);
+               fail_on_test(ret && ret != ENOTTY);
+               if (ret == 0) {
+                       fail_on_test(doioctl(node, VIDIOC_QUERYBUF, &buf));
+                       fail_on_test(checkQueryBuf(node, buf, bufs.type, bufs.memory, i, Prepared, 0));
+               }
+
+               fail_on_test(doioctl(node, VIDIOC_QBUF, &buf));
+               fail_on_test(doioctl(node, VIDIOC_QUERYBUF, &buf));
+               fail_on_test(checkQueryBuf(node, buf, bufs.type, bufs.memory, i, Queued, 0));
+       }
+       return 0;
+}
+
+static int releaseMmap(struct node *node, struct v4l2_requestbuffers &bufs)
+{
+       for (unsigned i = 0; i < bufs.count; i++) {
+               struct v4l2_plane planes[VIDEO_MAX_PLANES];
+               struct v4l2_buffer buf;
+
+               memset(&buf, 0, sizeof(buf));
+               buf.type = bufs.type;
+               buf.memory = bufs.memory;
+               buf.index = i;
+               if (V4L2_TYPE_IS_MULTIPLANAR(bufs.type)) {
+                       buf.m.planes = planes;
+                       buf.length = VIDEO_MAX_PLANES;
+               }
+               fail_on_test(doioctl(node, VIDIOC_QUERYBUF, &buf));
+               munmap(ptrs[i], buf.length);
+       }
+       return 0;
+}
+
+static int captureBufs(struct node *node, const struct v4l2_requestbuffers &bufs,
+                      unsigned count, bool use_poll)
+{
+       int fd_flags = fcntl(node->fd, F_GETFL);
+       struct v4l2_buffer buf;
+       int ret;
+
+       if (use_poll)
+               fcntl(node->fd, F_SETFL, fd_flags | O_NONBLOCK);
+       for (;;) {
+               if (use_poll) {
+                       struct timeval tv = { 2, 0 };
+                       fd_set read_fds;
+
+                       FD_ZERO(&read_fds);
+                       FD_SET(node->fd, &read_fds);
+                       ret = select(node->fd + 1, &read_fds, NULL, NULL, &tv);
+                       fail_on_test(ret <= 0);
+                       fail_on_test(!FD_ISSET(node->fd, &read_fds));
+               }
+
+               buf.type = bufs.type;
+               buf.memory = bufs.memory;
+
+               ret = doioctl(node, VIDIOC_DQBUF, &buf);
+               if (ret == EAGAIN)
+                       continue;
+               if (show_info)
+                       printf("\t\tBuffer: %d Sequence: %d Field: %s Timestamp: %ld.%06lds\n",
+                               buf.index, buf.sequence, field2s(buf.field).c_str(),
+                               buf.timestamp.tv_sec, buf.timestamp.tv_usec);
+               fail_on_test(ret);
+               fail_on_test(checkQueryBuf(node, buf, bufs.type, bufs.memory, buf.index, Dequeued, 100 - count));
+               if (!show_info) {
+                       printf("\r\tFrame #%03d%s", 100 - count, use_poll ? " (polling)" : "");
+                       fflush(stdout);
+               }
+               fail_on_test(doioctl(node, VIDIOC_QBUF, &buf));
+               if (--count == 0)
+                       break;
+       }
+       if (use_poll)
+               fcntl(node->fd, F_SETFL, fd_flags);
+       if (!show_info)
+               printf("\r\t                       \r");
+       return 0;
+}
+
+int testMmap(struct node *node)
+{
+       struct v4l2_requestbuffers bufs;
+       struct v4l2_create_buffers cbufs;
+       struct v4l2_input input;
+       bool can_stream = node->caps & V4L2_CAP_STREAMING;
+       bool have_createbufs = true;
+       int ret;
+       
+       if (!(node->caps & V4L2_CAP_VIDEO_CAPTURE))
+               return 0;
+
+       memset(&input, 0, sizeof(input));
+       doioctl(node, VIDIOC_G_INPUT, &input.index);
+       doioctl(node, VIDIOC_ENUMINPUT, &input);
+
+       if (input.capabilities & V4L2_IN_CAP_STD) {
+               v4l2_std_id std;
+
+               doioctl(node, VIDIOC_QUERYSTD, &std);
+               if (std)
+                       doioctl(node, VIDIOC_S_STD, &std);
+       }
+
+       if (input.capabilities & V4L2_IN_CAP_DV_TIMINGS) {
+               struct v4l2_dv_timings t;
+
+               if (doioctl(node, VIDIOC_QUERY_DV_TIMINGS, &t) == 0)
+                       doioctl(node, VIDIOC_S_DV_TIMINGS, &t);
+       }
+
+       memset(&bufs, 0, sizeof(bufs));
+       bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       bufs.memory = V4L2_MEMORY_MMAP;
+       ret = doioctl(node, VIDIOC_REQBUFS, &bufs);
+       if (ret) {
+               fail_on_test(can_stream);
+               return ret;
+       }
+
+       fail_on_test(doioctl(node, VIDIOC_STREAMON, &bufs.type));
+       fail_on_test(doioctl(node, VIDIOC_STREAMOFF, &bufs.type));
+
+       cur_fmt.type = bufs.type;
+       doioctl(node, VIDIOC_G_FMT, &cur_fmt);
+       bufs.count = 1;
+       fail_on_test(doioctl(node, VIDIOC_REQBUFS, &bufs));
+       fail_on_test(doioctl(node, VIDIOC_STREAMOFF, &bufs.type));
+       last_seq = -1;
+       field_nr = 1;
+       last_field = cur_fmt.fmt.pix.field;
+
+       cbufs.format = cur_fmt;
+       cbufs.format.fmt.pix.height /= 2;
+       cbufs.format.fmt.pix.sizeimage /= 2;
+       cbufs.count = 1;
+       cbufs.memory = bufs.memory;
+       ret = doioctl(node, VIDIOC_CREATE_BUFS, &cbufs);
+       if (ret == ENOTTY)
+               have_createbufs = false;
+       else
+               fail_on_test(ret != EINVAL);
+       fail_on_test(testQueryBuf(node, cur_fmt.type, bufs.count));
+       if (have_createbufs) {
+               cbufs.format = cur_fmt;
+               cbufs.format.fmt.pix.sizeimage *= 2;
+               cbufs.count = 1;
+               cbufs.memory = bufs.memory;
+               fail_on_test(doioctl(node, VIDIOC_CREATE_BUFS, &cbufs));
+       }
+       fail_on_test(setupMmap(node, bufs));
+
+       fail_on_test(doioctl(node, VIDIOC_STREAMON, &bufs.type));
+       fail_on_test(doioctl(node, VIDIOC_STREAMON, &bufs.type));
+       fail_on_test(captureBufs(node, bufs, 100, false));
+       fail_on_test(captureBufs(node, bufs, 100, true));
+       fail_on_test(doioctl(node, VIDIOC_STREAMOFF, &bufs.type));
+       fail_on_test(doioctl(node, VIDIOC_STREAMOFF, &bufs.type));
+       fail_on_test(releaseMmap(node, bufs));
+       bufs.count = 0;
+       fail_on_test(doioctl(node, VIDIOC_REQBUFS, &bufs));
+       return 0;
+}
+
+static int setupUserPtr(struct node *node, struct v4l2_requestbuffers &bufs)
+{
+       for (unsigned i = 0; i < bufs.count; i++) {
+               struct v4l2_plane planes[VIDEO_MAX_PLANES];
+               struct v4l2_buffer buf;
+               int ret;
+
+               memset(&buf, 0, sizeof(buf));
+               buf.type = bufs.type;
+               buf.memory = bufs.memory;
+               buf.index = i;
+               if (V4L2_TYPE_IS_MULTIPLANAR(bufs.type)) {
+                       buf.m.planes = planes;
+                       buf.length = VIDEO_MAX_PLANES;
+               }
+               fail_on_test(doioctl(node, VIDIOC_QUERYBUF, &buf));
+               fail_on_test(checkQueryBuf(node, buf, bufs.type, bufs.memory, i, Unqueued, 0));
+               ptrs[i] = malloc(buf.length);
+               fail_on_test(ptrs[i] == NULL);
+               buf.m.userptr = (unsigned long)ptrs[i];
+
+               ret = doioctl(node, VIDIOC_PREPARE_BUF, &buf);
+               fail_on_test(ret && ret != ENOTTY);
+               if (ret == 0) {
+                       fail_on_test(doioctl(node, VIDIOC_QUERYBUF, &buf));
+                       fail_on_test(checkQueryBuf(node, buf, bufs.type, bufs.memory, i, Prepared, 0));
+               }
+
+               fail_on_test(doioctl(node, VIDIOC_QBUF, &buf));
+               fail_on_test(doioctl(node, VIDIOC_QUERYBUF, &buf));
+               fail_on_test(checkQueryBuf(node, buf, bufs.type, bufs.memory, i, Queued, 0));
+       }
+       return 0;
+}
+
+static int releaseUserPtr(struct node *node, struct v4l2_requestbuffers &bufs)
+{
+       for (unsigned i = 0; i < bufs.count; i++)
+               free(ptrs[i]);
+       return 0;
+}
+
+int testUserPtr(struct node *node)
+{
+       struct v4l2_requestbuffers bufs;
+       bool can_stream = node->caps & V4L2_CAP_STREAMING;
+       int ret;
+       
+       if (!(node->caps & V4L2_CAP_VIDEO_CAPTURE))
+               return 0;
+
+       memset(&bufs, 0, sizeof(bufs));
+       bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       bufs.memory = V4L2_MEMORY_USERPTR;
+       ret = doioctl(node, VIDIOC_REQBUFS, &bufs);
+       if (ret) {
+               fail_on_test(can_stream);
+               return ret;
+       }
+
+       bufs.count = 1;
+       fail_on_test(doioctl(node, VIDIOC_REQBUFS, &bufs));
+       fail_on_test(doioctl(node, VIDIOC_STREAMOFF, &bufs.type));
+       last_seq = -1;
+       field_nr = 1;
+       last_field = cur_fmt.fmt.pix.field;
+
+       fail_on_test(setupUserPtr(node, bufs));
+
+       fail_on_test(doioctl(node, VIDIOC_STREAMON, &bufs.type));
+       fail_on_test(doioctl(node, VIDIOC_STREAMON, &bufs.type));
+       fail_on_test(captureBufs(node, bufs, 100, false));
+       fail_on_test(captureBufs(node, bufs, 100, true));
+       fail_on_test(doioctl(node, VIDIOC_STREAMOFF, &bufs.type));
+       fail_on_test(doioctl(node, VIDIOC_STREAMOFF, &bufs.type));
+       fail_on_test(releaseUserPtr(node, bufs));
+       bufs.count = 0;
+       fail_on_test(doioctl(node, VIDIOC_REQBUFS, &bufs));
+       return 0;
+}