@@ -1836,6 +1836,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);
@@ -82,6 +82,7 @@ enum poll_mode {
#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;
@@ -1958,6 +1958,30 @@ int testRequests(struct node *node, bool test_streaming)
.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;
@@ -2260,6 +2284,34 @@ int testRequests(struct node *node, bool test_streaming)
ctrls.request_fd = buf_req_fds[i];
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));
@@ -2272,6 +2324,15 @@ int testRequests(struct node *node, bool test_streaming)
ctrls.request_fd = buf_req_fds[i];
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
@@ -2376,6 +2437,20 @@ int testRequests(struct node *node, bool test_streaming)
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_ro_ctrls));
if (node->is_video && !node->can_output)
warn_once_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
@@ -2424,6 +2499,21 @@ int testRequests(struct node *node, bool test_streaming)
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_ro_ctrls));
if (node->is_video && !node->can_output)
warn_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));
}
}
@@ -617,6 +617,150 @@ 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);
+ }
+ // Check that the end of the buffer isn't overwritten
+ fail_on_test(ctrl.p_u32[max_elems] != 0xdeadbeef);
+
+ ctrl.size = qctrl.elem_size;
+ ctrls.which = V4L2_CTRL_WHICH_DEF_VAL;
+ // 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++)
+ fail_on_test(ctrl.p_u32[i] != 50);
+ // Check that the end of the buffer isn't overwritten
+ fail_on_test(ctrl.p_u32[max_elems] != 0xdeadbeef);
+
+ ctrls.which = 0;
+ 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);
+
+ ctrl.size = max_size;
+ ctrls.which = V4L2_CTRL_WHICH_DEF_VAL;
+ memset(ctrl.string, 0xff, max_size);
+ // And updated to the actual required size
+ fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
+ fail_on_test(ctrl.size != qctrl.elem_size);
+ fail_on_test(ctrl.p_u32[0] != 50);
+ fail_on_test(ctrl.p_u32[1] != 0xffffffff);
+
return 0;
}
@@ -739,6 +883,9 @@ int testExtendedControls(struct node *node)
return fail("s_ext_ctrls returned invalid control contents (%08x)\n", qctrl.id);
}
+ 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;
@@ -647,6 +652,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]);
Add support for dynamic array controls. Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> --- include/linux/videodev2.h | 1 + utils/common/v4l2-info.cpp | 1 + utils/v4l2-compliance/v4l2-compliance.h | 1 + utils/v4l2-compliance/v4l2-test-buffers.cpp | 90 ++++++++++++ utils/v4l2-compliance/v4l2-test-controls.cpp | 147 +++++++++++++++++++ utils/v4l2-ctl/v4l2-ctl-common.cpp | 7 + 6 files changed, 247 insertions(+)