@@ -1814,6 +1814,7 @@ struct v4l2_querymenu {
#define V4L2_CTRL_FLAG_HAS_PAYLOAD 0x0100
#define V4L2_CTRL_FLAG_EXECUTE_ON_WRITE 0x0200
#define V4L2_CTRL_FLAG_MODIFY_LAYOUT 0x0400
+#define V4L2_CTRL_FLAG_DYNAMIC_ARRAY 0x0800
/* Query flags, to be ORed with the control ID */
#define V4L2_CTRL_FLAG_NEXT_CTRL 0x80000000
@@ -531,6 +531,7 @@ std::string ctrlflags2s(__u32 flags)
{ V4L2_CTRL_FLAG_HAS_PAYLOAD,"has-payload" },
{ V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, "execute-on-write" },
{ V4L2_CTRL_FLAG_MODIFY_LAYOUT, "modify-layout" },
+ { V4L2_CTRL_FLAG_DYNAMIC_ARRAY, "dynamic-array" },
{ 0, nullptr }
};
return flags2s(flags, def);
@@ -80,6 +80,10 @@ enum poll_mode {
#define VIVID_CID_QUEUE_ERROR (VIVID_CID_VIVID_BASE + 70)
#define VIVID_CID_REQ_VALIDATE_ERROR (VIVID_CID_VIVID_BASE + 72)
+#define VIVID_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000)
+#define VIVID_CID_RO_INTEGER (VIVID_CID_CUSTOM_BASE + 12)
+#define VIVID_CID_U32_DYN_ARRAY (VIVID_CID_CUSTOM_BASE + 13)
+
struct test_query_ext_ctrl: v4l2_query_ext_ctrl {
__u64 menu_mask;
};
@@ -1950,12 +1950,46 @@ int testRequests(struct node *node, bool test_streaming)
struct test_query_ext_ctrl valid_qctrl;
v4l2_ext_controls ctrls;
v4l2_ext_control ctrl;
+ v4l2_ext_control vivid_ro_ctrl = {
+ .id = VIVID_CID_RO_INTEGER,
+ };
+ v4l2_ext_controls vivid_ro_ctrls = {
+ .which = V4L2_CTRL_WHICH_REQUEST_VAL,
+ .count = 1,
+ .controls = &vivid_ro_ctrl,
+ };
+ // Note: the vivid dynamic array has range 10-90
+ // and the maximum number of elements is 100.
+ __u32 vivid_dyn_array[101] = {};
+ // Initialize with these values
+ static const __u32 vivid_dyn_array_init[16] = {
+ 6, 12, 18, 24, 30, 36, 42, 48,
+ 54, 60, 66, 72, 78, 84, 90, 96
+ };
+ // This is the clamped version to compare against
+ static const __u32 vivid_dyn_array_clamped[16] = {
+ 10, 12, 18, 24, 30, 36, 42, 48,
+ 54, 60, 66, 72, 78, 84, 90, 90
+ };
+ const unsigned elem_size = sizeof(vivid_dyn_array[0]);
+ v4l2_ext_control vivid_dyn_array_ctrl = {
+ .id = VIVID_CID_U32_DYN_ARRAY,
+ .size = elem_size,
+ .p_u32 = vivid_dyn_array,
+ };
+ v4l2_ext_controls vivid_dyn_array_ctrls = {
+ .which = V4L2_CTRL_WHICH_REQUEST_VAL,
+ .count = 1,
+ .controls = &vivid_dyn_array_ctrl,
+ };
bool have_controls;
int ret;
+ // If requests are supported, then there must be a media device
if (node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS)
fail_on_test(media_fd < 0);
+ // Check if the driver has controls that can be used to test requests
memset(&valid_qctrl, 0, sizeof(valid_qctrl));
memset(&ctrls, 0, sizeof(ctrls));
memset(&ctrl, 0, sizeof(ctrl));
@@ -1982,42 +2016,57 @@ int testRequests(struct node *node, bool test_streaming)
0 : ENOTTY;
}
+ // Test if V4L2_CTRL_WHICH_REQUEST_VAL is supported
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ret = doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls);
fail_on_test(ret != EINVAL && ret != EBADR && ret != ENOTTY);
have_controls = ret != ENOTTY;
if (media_fd < 0 || ret == EBADR) {
+ // Should not happen if requests are supported
fail_on_test(node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS);
return ENOTTY;
}
if (have_controls) {
ctrls.request_fd = 10;
+ // Test that querying controls with an invalid request_fd
+ // returns EINVAL
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) != EINVAL);
}
ret = doioctl_fd(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd);
if (ret == ENOTTY) {
+ // Should not happen if requests are supported
fail_on_test(node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS);
return ENOTTY;
}
+ // Check that a request was allocated with a valid fd
fail_on_test(ret);
- fhs.add(req_fd);
fail_on_test(req_fd < 0);
+ fhs.add(req_fd);
if (have_controls) {
ctrls.request_fd = req_fd;
+ // The request is in unqueued state, so this should return EACCES
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) != EACCES);
}
+ // You cannot queue a request that has no buffer
fail_on_test(doioctl_fd(req_fd, MEDIA_REQUEST_IOC_QUEUE, nullptr) != ENOENT);
+ // REINIT must work for an unqueued request
fail_on_test(doioctl_fd(req_fd, MEDIA_REQUEST_IOC_REINIT, nullptr));
+ // Close media_fd
fhs.del(media_fd);
+ // This should have no effect on req_fd
fail_on_test(doioctl_fd(req_fd, MEDIA_REQUEST_IOC_QUEUE, nullptr) != ENOENT);
fail_on_test(doioctl_fd(req_fd, MEDIA_REQUEST_IOC_REINIT, nullptr));
+ // Close req_fd
fhs.del(req_fd);
+ // G_EXT_CTRLS must now return EINVAL for req_fd since it no longer exists
if (have_controls)
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) != EINVAL);
+ // And the media request ioctls now must return EBADF
fail_on_test(doioctl_fd(req_fd, MEDIA_REQUEST_IOC_QUEUE, nullptr) != EBADF);
fail_on_test(doioctl_fd(req_fd, MEDIA_REQUEST_IOC_REINIT, nullptr) != EBADF);
+ // Open media_fd and alloc a request again
media_fd = fhs.add(mi_get_media_fd(node->g_fd(), node->bus_info));
fail_on_test(doioctl_fd(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd));
fhs.add(req_fd);
@@ -2026,29 +2075,40 @@ int testRequests(struct node *node, bool test_streaming)
if (have_controls) {
ctrl.value = valid_qctrl.minimum;
ctrls.which = 0;
+ // Set control without requests
fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
ctrl.value = valid_qctrl.maximum;
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ctrls.request_fd = req_fd;
+ // Set control for a request
fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
ctrl.value = valid_qctrl.minimum;
ctrls.request_fd = req_fd;
+ // But you cannot get the value of an unqueued request
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) != EACCES);
ctrls.which = 0;
+ // But you can without a request
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
fail_on_test(ctrl.value != valid_qctrl.minimum);
ctrls.request_fd = req_fd;
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ctrl.id = 1;
+ // Setting an invalid control in a request must fail
fail_on_test(!doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
+ // And also when trying to read an invalid control of a request
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) != EACCES);
}
ctrl.id = valid_qctrl.id;
+ // Close req_fd and media_fd and reopen device node
fhs.del(req_fd);
fhs.del(media_fd);
node->reopen();
int type = node->g_type();
+ // For m2m devices g_type() will return the capture type, so
+ // we need to invert it to get the output type.
+ // At the moment only the output type of an m2m device can use
+ // requests.
if (node->is_m2m)
type = v4l_type_invert(type);
if (v4l_type_is_vbi(type)) {
@@ -2059,6 +2119,8 @@ int testRequests(struct node *node, bool test_streaming)
}
if (!(node->valid_buftypes & (1 << type))) {
+ // If the type is not supported, then check that requests
+ // are also not supported.
fail_on_test(node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS);
return ENOTTY;
}
@@ -2067,46 +2129,60 @@ int testRequests(struct node *node, bool test_streaming)
buffer_info.clear();
cv4l_queue q(type, V4L2_MEMORY_MMAP);
+ // For m2m devices q is the output queue and m2m_q is the capture queue
cv4l_queue m2m_q(v4l_type_invert(type));
q.init(type, V4L2_MEMORY_MMAP);
- fail_on_test(q.reqbufs(node, 2));
+ fail_on_test(q.reqbufs(node, 15));
unsigned min_bufs = q.g_buffers();
- fail_on_test(q.reqbufs(node, min_bufs + 4));
+ fail_on_test(q.reqbufs(node, min_bufs + 6));
unsigned num_bufs = q.g_buffers();
+ // Create twice as many requests as there are buffers
unsigned num_requests = 2 * num_bufs;
last_seq.init();
media_fd = fhs.add(mi_get_media_fd(node->g_fd(), node->bus_info));
+ // Allocate the requests
for (unsigned i = 0; i < num_requests; i++) {
fail_on_test(doioctl_fd(media_fd, MEDIA_IOC_REQUEST_ALLOC, &buf_req_fds[i]));
fhs.add(buf_req_fds[i]);
fail_on_test(buf_req_fds[i] < 0);
+ // Check that empty requests can't be queued
fail_on_test(!doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_QUEUE, nullptr));
}
+ // close the media fd, should not be needed anymore
fhs.del(media_fd);
buffer buf(q);
fail_on_test(buf.querybuf(node, 0));
+ // Queue a buffer without using requests
ret = buf.qbuf(node);
+ // If this fails, then that can only happen if the queue
+ // requires requests. In that case EBADR is returned.
fail_on_test(ret && ret != EBADR);
fail_on_test(buf.querybuf(node, 1));
+ // Now try to queue the buffer to the request
buf.s_flags(V4L2_BUF_FLAG_REQUEST_FD);
buf.s_request_fd(buf_req_fds[1]);
+ // If requests are required, then this must now work
+ // If requests are optional, then this must now fail since the
+ // queue in is non-request mode.
if (ret == EBADR)
fail_on_test(buf.qbuf(node));
else
fail_on_test(!buf.qbuf(node));
+ // Reopen device node, clearing any pending requests
node->reopen();
q.init(type, V4L2_MEMORY_MMAP);
fail_on_test(q.reqbufs(node, num_bufs));
if (node->is_m2m) {
+ // Setup the capture queue
fail_on_test(m2m_q.reqbufs(node, 2));
fail_on_test(node->streamon(m2m_q.g_type()));
@@ -2115,6 +2191,9 @@ int testRequests(struct node *node, bool test_streaming)
fail_on_test(buf.querybuf(node, 0));
buf.s_flags(V4L2_BUF_FLAG_REQUEST_FD);
buf.s_request_fd(buf_req_fds[0]);
+ // Only the output queue can support requests,
+ // so if the capture queue also supports requests,
+ // then something is wrong.
fail_on_test(!buf.qbuf(node));
fail_on_test(node->streamoff(m2m_q.g_type()));
fail_on_test(m2m_q.reqbufs(node, 0));
@@ -2128,10 +2207,12 @@ int testRequests(struct node *node, bool test_streaming)
buffer buf(q);
fail_on_test(buf.querybuf(node, i));
+ // No request was set, so this should return 0
fail_on_test(buf.g_request_fd());
buf.s_flags(V4L2_BUF_FLAG_REQUEST_FD);
if (i == 0) {
buf.s_request_fd(-1);
+ // Can't queue to an invalid request fd
fail_on_test(!buf.qbuf(node));
buf.s_request_fd(0xdead);
fail_on_test(!buf.qbuf(node));
@@ -2140,25 +2221,34 @@ int testRequests(struct node *node, bool test_streaming)
if (v4l_type_is_video(buf.g_type()))
buf.s_field(V4L2_FIELD_ANY);
if (!(i & 1)) {
+ // VIDIOC_PREPARE_BUF is incompatible with requests
fail_on_test(buf.prepare_buf(node) != EINVAL);
buf.s_flags(0);
+ // Test vivid error injection
if (node->inject_error(VIVID_CID_BUF_PREPARE_ERROR))
fail_on_test(buf.prepare_buf(node) != EINVAL);
fail_on_test(buf.prepare_buf(node));
fail_on_test(buf.querybuf(node, i));
+ // Check that the buffer was prepared
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_PREPARED));
buf.s_flags(buf.g_flags() | V4L2_BUF_FLAG_REQUEST_FD);
buf.s_request_fd(buf_req_fds[i]);
}
+ // Queue the buffer to the request
int err = buf.qbuf(node);
if (!err) {
+ // If requests are not supported, this should fail
fail_on_test(!supports_requests);
+ // You can't queue the same buffer again
fail_on_test(!buf.qbuf(node));
} else {
+ // Can only fail if requests are not supported
fail_on_test(supports_requests);
+ // and should fail with EBADR in that case
fail_on_test(err != EBADR);
}
if (err) {
+ // Requests are not supported, so clean up and return
fail_on_test(node->streamoff(q.g_type()));
fail_on_test(q.reqbufs(node, 0));
if (node->is_m2m) {
@@ -2169,11 +2259,14 @@ int testRequests(struct node *node, bool test_streaming)
node->reopen();
return ENOTTY;
}
+ // Check flags and request fd
fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_DONE);
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_IN_REQUEST));
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD));
fail_on_test(buf.g_request_fd() < 0);
+ // Query the buffer again
fail_on_test(buf.querybuf(node, i));
+ // Check returned flags and request fd
fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_DONE);
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_IN_REQUEST));
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD));
@@ -2182,55 +2275,118 @@ int testRequests(struct node *node, bool test_streaming)
fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_PREPARED);
else
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_PREPARED));
+ // Check that you can't queue it again
buf.s_request_fd(buf_req_fds[i]);
fail_on_test(!buf.qbuf(node));
+ // Set a control in the request, except for every third request.
ctrl.value = (i & 1) ? valid_qctrl.maximum : valid_qctrl.minimum;
ctrls.request_fd = buf_req_fds[i];
- fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
+ if (i % 3 < 2)
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
+ if (is_vivid) {
+ // For vivid, check dynamic array support:
+ vivid_dyn_array_ctrls.request_fd = buf_req_fds[i];
+ vivid_dyn_array_ctrl.size = sizeof(vivid_dyn_array);
+ memset(vivid_dyn_array, 0xff, sizeof(vivid_dyn_array));
+ // vivid_dyn_array_ctrl.size is too large, must return ENOSPC
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS,
+ &vivid_dyn_array_ctrls) != ENOSPC);
+ // and size is set at 100 elems
+ fail_on_test(vivid_dyn_array_ctrl.size != 100 * elem_size);
+ // Check that the array is not overwritten
+ fail_on_test(vivid_dyn_array[0] != 0xffffffff);
+ if (i % 3 < 2) {
+ unsigned size = (2 + 2 * (i % 8)) * elem_size;
+
+ // Set proper size, varies per request
+ vivid_dyn_array_ctrl.size = size;
+ memcpy(vivid_dyn_array, vivid_dyn_array_init, size);
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS,
+ &vivid_dyn_array_ctrls));
+ // check that the size is as expected
+ fail_on_test(vivid_dyn_array_ctrl.size != size);
+ // and the array values are correctly clamped
+ fail_on_test(memcmp(vivid_dyn_array, vivid_dyn_array_clamped, size));
+ // and the end of the array is not overwritten
+ fail_on_test(vivid_dyn_array[size / elem_size] != 0xffffffff);
+ }
+ }
+ // Re-init the unqueued request
fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_REINIT, nullptr));
+ // Make sure that the buffer is no longer in a request
fail_on_test(buf.querybuf(node, i));
fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_IN_REQUEST);
fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD);
+ // Set the control again
ctrls.request_fd = buf_req_fds[i];
- fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
-
+ if (i % 3 < 2)
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
+ if (is_vivid && i % 3 < 2) {
+ // Set the dynamic array control again
+ vivid_dyn_array_ctrls.request_fd = buf_req_fds[i];
+ vivid_dyn_array_ctrl.size = (2 + 2 * (i % 8)) * elem_size;
+ memcpy(vivid_dyn_array, vivid_dyn_array_init,
+ sizeof(vivid_dyn_array_init));
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS,
+ &vivid_dyn_array_ctrls));
+ }
+
+ // After the re-init the buffer is no longer marked for
+ // a request. If a request has been queued before (hence
+ // the 'if (i)' check), then queuing the buffer without
+ // a request must fail since you can't mix the two streamining
+ // models.
if (i)
fail_on_test(!buf.qbuf(node));
buf.s_flags(buf.g_flags() | V4L2_BUF_FLAG_REQUEST_FD);
buf.s_request_fd(buf_req_fds[i]);
buf.s_field(V4L2_FIELD_ANY);
+ // Queue the buffer for the request
fail_on_test(buf.qbuf(node));
+ // Verify that drivers will replace FIELD_ANY for video output queues
if (v4l_type_is_video(buf.g_type()) && v4l_type_is_output(buf.g_type()))
fail_on_test(buf.g_field() == V4L2_FIELD_ANY);
+ // Query buffer and check that it is marked as being part of a request
fail_on_test(buf.querybuf(node, i));
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_IN_REQUEST));
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD));
+ // Use vivid to check buffer prepare or request validation error injection
if ((i & 1) && node->inject_error(i > num_bufs / 2 ?
VIVID_CID_BUF_PREPARE_ERROR :
VIVID_CID_REQ_VALIDATE_ERROR))
fail_on_test(doioctl_fd(buf_req_fds[i],
MEDIA_REQUEST_IOC_QUEUE, nullptr) != EINVAL);
+ // Queue the request
ret = doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_QUEUE, nullptr);
if (node->codec_mask & STATELESS_DECODER) {
+ // Stateless decoders might require that certain
+ // controls are present in the request. In that
+ // case they return ENOENT and we just stop testing
+ // since we don't know what those controls are.
fail_on_test(ret != ENOENT);
test_streaming = false;
break;
}
fail_on_test(ret);
fail_on_test(buf.querybuf(node, i));
+ // Check that the buffer is now queued up
fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_IN_REQUEST);
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD));
fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_QUEUED));
+ // Re-initing or requeuing the request is no longer possible
fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_REINIT, nullptr) != EBUSY);
fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_QUEUE, nullptr) != EBUSY);
if (i >= min_bufs) {
+ // Close some of the request fds to check that this
+ // is safe to do
close(buf_req_fds[i]);
buf_req_fds[i] = -1;
}
if (i == min_bufs - 1) {
+ // Check vivid STREAMON error injection
if (node->inject_error(VIVID_CID_START_STR_ERROR))
fail_on_test(!node->streamon(q.g_type()));
fail_on_test(node->streamon(q.g_type()));
@@ -2242,45 +2398,126 @@ int testRequests(struct node *node, bool test_streaming)
if (test_streaming) {
unsigned capture_count;
- fail_on_test(captureBufs(node, node, q, m2m_q, num_bufs,
+ // Continue streaming
+ // For m2m devices captureBufs() behaves a bit odd: you pass
+ // in the total number of output buffers that you want to
+ // stream, but since there are already q.g_buffers() output
+ // buffers queued up (see previous loop), the captureBufs()
+ // function will subtract that from frame_count, so it will
+ // only queue frame_count - q.g_buffers() output buffers.
+ // In order to ensure we captured at least
+ // min_bufs buffers we need to add min_bufs to the frame_count.
+ fail_on_test(captureBufs(node, node, q, m2m_q,
+ num_bufs + (node->is_m2m ? min_bufs : 0),
POLL_MODE_SELECT, capture_count));
}
fail_on_test(node->streamoff(q.g_type()));
+ // Note that requests min_bufs...2*min_bufs-1 close their filehandles,
+ // so here we just go through the first half of the requests.
for (unsigned i = 0; test_streaming && i < min_bufs; i++) {
buffer buf(q);
+ // Get the control
ctrls.request_fd = buf_req_fds[i];
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
- fail_on_test(ctrl.value != ((i & 1) ? valid_qctrl.maximum :
+ bool is_max = i & 1;
+ // Since the control was not set for every third request,
+ // the value will actually be that of the previous request.
+ if (i % 3 == 2)
+ is_max = !is_max;
+ // Check that the value is as expected
+ fail_on_test(ctrl.value != (is_max ? valid_qctrl.maximum :
valid_qctrl.minimum));
+ if (is_vivid) {
+ // vivid specific: check that the read-only control
+ // of the completed request has the expected value
+ // (sequence number & 0xff).
+ vivid_ro_ctrls.request_fd = buf_req_fds[i];
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_ro_ctrls));
+ if (node->is_video && !node->can_output)
+ fail_on_test(vivid_ro_ctrl.value != (int)i);
+
+ // Check that the dynamic control array is set as
+ // expected and with the correct values.
+ vivid_dyn_array_ctrls.request_fd = buf_req_fds[i];
+ memset(vivid_dyn_array, 0xff, sizeof(vivid_dyn_array));
+ vivid_dyn_array_ctrl.size = sizeof(vivid_dyn_array);
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_dyn_array_ctrls));
+ unsigned size = (2 + 2 * (i % 8)) * elem_size;
+ if (i % 3 == 2)
+ size = (2 + 2 * ((i - 1) % 8)) * elem_size;
+ fail_on_test(vivid_dyn_array_ctrl.size != size);
+ fail_on_test(memcmp(vivid_dyn_array, vivid_dyn_array_clamped,
+ vivid_dyn_array_ctrl.size));
+ fail_on_test(vivid_dyn_array[size / elem_size] != 0xffffffff);
+ }
fail_on_test(buf.querybuf(node, i));
+ // Check that all the buffers of the stopped stream are
+ // no longer marked as belonging to a request.
fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD);
fail_on_test(buf.g_request_fd());
struct pollfd pfd = {
buf_req_fds[i],
POLLPRI, 0
};
+ // Check that polling the request fd will immediately return,
+ // indicating that the request has completed.
fail_on_test(poll(&pfd, 1, 100) != 1);
+ // Requeuing the request must fail
fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_QUEUE, nullptr) != EBUSY);
+ // But reinit must succeed.
fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_REINIT, nullptr));
fail_on_test(buf.querybuf(node, i));
fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD);
fail_on_test(buf.g_request_fd());
- fhs.del(buf_req_fds[i]);
ctrls.request_fd = buf_req_fds[i];
+ // Check that getting controls from a reinited request fails
fail_on_test(!doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
+ // Close the request fd
+ fhs.del(buf_req_fds[i]);
+ buf_req_fds[i] = -1;
}
+ // Close any remaining open request fds
for (unsigned i = 0; i < num_requests; i++)
if (buf_req_fds[i] >= 0)
fhs.del(buf_req_fds[i]);
+ // Getting the current control value must work
ctrls.which = 0;
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
- if (test_streaming)
- fail_on_test(ctrl.value != (((num_bufs - 1) & 1) ? valid_qctrl.maximum :
+ // Check the final control value
+ if (test_streaming) {
+ bool is_max = (num_bufs - 1) & 1;
+ if ((num_bufs - 1) % 3 == 2)
+ is_max = !is_max;
+ fail_on_test(ctrl.value != (is_max ? valid_qctrl.maximum :
valid_qctrl.minimum));
-
+ if (is_vivid) {
+ // For vivid check the final read-only value
+ vivid_ro_ctrls.which = 0;
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_ro_ctrls));
+ if (node->is_video && !node->can_output)
+ fail_on_test(vivid_ro_ctrl.value != (int)(num_bufs - 1));
+
+ // and the final dynamic array value
+ v4l2_query_ext_ctrl q_dyn_array = {
+ .id = VIVID_CID_U32_DYN_ARRAY,
+ };
+ fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &q_dyn_array));
+ unsigned elems = 2 + 2 * ((num_bufs - 1) % 8);
+ if ((num_bufs - 1) % 3 == 2)
+ elems = 2 + 2 * ((num_bufs - 2) % 8);
+ fail_on_test(q_dyn_array.elems != elems);
+ vivid_dyn_array_ctrls.which = 0;
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_dyn_array_ctrls));
+ fail_on_test(vivid_dyn_array_ctrl.size != elems * elem_size);
+ fail_on_test(memcmp(vivid_dyn_array, vivid_dyn_array_clamped,
+ vivid_dyn_array_ctrl.size));
+ }
+ }
+
+ // Cleanup
fail_on_test(q.reqbufs(node, 0));
if (node->is_m2m) {
fail_on_test(node->streamoff(m2m_q.g_type()));
@@ -617,6 +617,128 @@ static int checkExtendedCtrl(const struct v4l2_ext_control &ctrl, const struct t
default:
break;
}
+ if (qctrl.flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY) {
+ fail_on_test(qctrl.nr_of_dims != 1);
+ unsigned tot_elems = qctrl.dims[0];
+ fail_on_test(qctrl.elems > tot_elems);
+ fail_on_test(!qctrl.elems);
+ }
+ return 0;
+}
+
+static int checkVividDynArray(struct node *node,
+ struct v4l2_ext_control &ctrl,
+ const struct test_query_ext_ctrl &qctrl)
+{
+ struct v4l2_query_ext_ctrl qextctrl = {};
+ unsigned max_elems = qctrl.dims[0];
+ unsigned max_size = qctrl.elem_size * max_elems;
+
+ delete [] ctrl.string;
+ // Allocate a buffer that's one element more than the max
+ ctrl.string = new char[max_size + qctrl.elem_size];
+ ctrl.size = qctrl.elem_size;
+ ctrl.p_u32[0] = (qctrl.minimum + qctrl.maximum) / 2;
+ // Set the last element + 1, must never be overwritten
+ ctrl.p_u32[max_elems] = 0xdeadbeef;
+
+ struct v4l2_ext_controls ctrls = {};
+ ctrls.count = 1;
+ ctrls.controls = &ctrl;
+ // Set the array to a single element
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
+
+ qextctrl.id = ctrl.id;
+ // Check that only one element is reported
+ fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl));
+ fail_on_test(qextctrl.elems != 1);
+
+ // If the size is less than elem_size, the ioctl must return -EFAULT
+ ctrl.size = 0;
+ fail_on_test(doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls) != EFAULT);
+ ctrl.size = qctrl.elem_size - 1;
+ fail_on_test(doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls) != EFAULT);
+ ctrl.size = max_size + qctrl.elem_size;
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
+ // Check that ctrl.size is reset to the current size of the array (1 element)
+ fail_on_test(ctrl.size != qctrl.elem_size);
+ ctrl.size = max_size + qctrl.elem_size;
+ // Attempting to set more than max_elems must result in -ENOSPC
+ fail_on_test(doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls) != ENOSPC);
+ fail_on_test(ctrl.size != max_size);
+ ctrl.size = max_size + qctrl.elem_size;
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls) != ENOSPC);
+ fail_on_test(ctrl.size != max_size);
+ fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl));
+ // Verify that the number of elements is still 1
+ fail_on_test(qextctrl.elems != 1);
+
+ ctrl.size = max_size;
+ for (unsigned i = 0; i < max_elems; i++)
+ ctrl.p_u32[i] = i;
+ // Try the max number of elements
+ fail_on_test(doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls));
+ // Check that the values are clamped
+ for (unsigned i = 0; i < max_elems; i++) {
+ unsigned j = i;
+ if (j < qctrl.minimum)
+ j = qctrl.minimum;
+ else if (j > qctrl.maximum)
+ j = qctrl.maximum;
+ fail_on_test(ctrl.p_u32[i] != j);
+ }
+ fail_on_test(ctrl.size != max_size);
+ fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl));
+ // Verify that the number of elements is still 1
+ fail_on_test(qextctrl.elems != 1);
+ memset(ctrl.string, 0xff, max_size);
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
+ // Check that there is still just one element returned.
+ fail_on_test(ctrl.size != qctrl.elem_size);
+ fail_on_test(ctrl.p_u32[0] != (qctrl.minimum + qctrl.maximum) / 2);
+
+ ctrl.size = max_size;
+ for (unsigned i = 0; i < max_elems; i++)
+ ctrl.p_u32[i] = i;
+ // Set the max number of elements
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
+ for (unsigned i = 0; i < max_elems; i++) {
+ unsigned j = i;
+ if (j < qctrl.minimum)
+ j = qctrl.minimum;
+ else if (j > qctrl.maximum)
+ j = qctrl.maximum;
+ fail_on_test(ctrl.p_u32[i] != j);
+ }
+ fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl));
+ // Check that it is updated
+ fail_on_test(qextctrl.elems != max_elems);
+ memset(ctrl.string, 0xff, max_size);
+ ctrl.size = qctrl.elem_size;
+ // Check that ENOSPC is returned if the size is too small
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) != ENOSPC);
+ // And updated to the actual required size
+ fail_on_test(ctrl.size != max_size);
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
+ for (unsigned i = 0; i < max_elems; i++) {
+ unsigned j = i;
+ if (j < qctrl.minimum)
+ j = qctrl.minimum;
+ else if (j > qctrl.maximum)
+ j = qctrl.maximum;
+ fail_on_test(ctrl.p_u32[i] != j);
+ }
+ ctrl.size = qctrl.elem_size;
+ ctrl.p_u32[0] = (qctrl.minimum + qctrl.maximum) / 2;
+ // Back to just one element
+ fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
+ fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl));
+ // Check this.
+ fail_on_test(qextctrl.elems != 1);
+
+ // Check that the end of the buffer isn't overwritten
+ fail_on_test(ctrl.p_u32[max_elems] != 0xdeadbeef);
+
return 0;
}
@@ -738,7 +860,11 @@ int testExtendedControls(struct node *node)
if (checkExtendedCtrl(ctrl, qctrl))
return fail("s_ext_ctrls returned invalid control contents (%08x)\n", qctrl.id);
}
- if (qctrl.type == V4L2_CTRL_TYPE_STRING)
+
+ if (is_vivid && ctrl.id == VIVID_CID_U32_DYN_ARRAY &&
+ checkVividDynArray(node, ctrl, qctrl))
+ return fail("dynamic array tests failed\n");
+ if (qctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD)
delete [] ctrl.string;
ctrl.string = nullptr;
}
@@ -390,6 +390,9 @@ static bool fill_subset(const struct v4l2_query_ext_ctrl &qc, ctrl_subset &subse
subset.size[d] = qc.dims[d];
}
+ if (qc.flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY)
+ subset.size[0] = qc.elems;
+
std::string s = name2var(qc.name);
if (ctrl_subsets.find(s) != ctrl_subsets.end()) {
@@ -489,6 +492,8 @@ static void print_value(int fd, const v4l2_query_ext_ctrl &qc, const v4l2_ext_co
memset(&subset, 0, sizeof(subset));
for (unsigned i = 0; i < qc.nr_of_dims; i++)
subset.size[i] = qc.dims[i];
+ if (qc.flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY)
+ subset.size[0] = qc.elems;
}
print_array(qc, ctrl, subset);
return;
@@ -629,6 +634,8 @@ static void print_qctrl(int fd, const v4l2_query_ext_ctrl &qc,
printf(" value=");
print_value(fd, qc, ctrl, false, false);
} else {
+ if (qc.flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY)
+ printf(" elems=%u", qc.elems);
printf(" dims=");
for (i = 0; i < qc.nr_of_dims; i++)
printf("[%u]", qc.dims[i]);
This patch adds support for dynamic array control tests, but it also improves testing of controls in requests in general. It also adds a lot of comments to testRequests() since that was getting hard to understand. Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> ---