blob: 8731c9eb635e96d8f8a32d470e4016b7dc8cf628 [file] [log] [blame]
/*
V4L2 API compliance control ioctl tests.
Copyright (C) 2011 Hans Verkuil <hverkuil@xs4all.nl>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
*/
#include <map>
#include <set>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include "compiler.h"
#include "v4l2-compliance.h"
static int checkQCtrl(struct node *node, struct test_query_ext_ctrl &qctrl)
{
struct v4l2_querymenu qmenu;
__u32 fl = qctrl.flags;
__u32 rw_mask = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY;
int ret;
int i;
qctrl.menu_mask = 0;
if (check_string(qctrl.name, sizeof(qctrl.name)))
return fail("invalid name\n");
info("checking v4l2_query_ext_ctrl of control '%s' (0x%08x)\n", qctrl.name, qctrl.id);
if (qctrl.id & V4L2_CTRL_FLAG_NEXT_CTRL)
return fail("V4L2_CTRL_FLAG_NEXT_CTRL not cleared\n");
if (check_0(qctrl.reserved, sizeof(qctrl.reserved)))
return fail("non-zero reserved fields\n");
if (qctrl.type == V4L2_CTRL_TYPE_CTRL_CLASS) {
if ((qctrl.id & 0xffff) != 1)
return fail("invalid control ID for a control class\n");
} else if (qctrl.id < V4L2_CID_PRIVATE_BASE) {
if ((qctrl.id & 0xffff) < 0x900)
return fail("invalid control ID\n");
}
switch (qctrl.type) {
case V4L2_CTRL_TYPE_BOOLEAN:
if (qctrl.maximum > 1)
return fail("invalid boolean max value\n");
fallthrough;
case V4L2_CTRL_TYPE_MENU:
case V4L2_CTRL_TYPE_INTEGER_MENU:
if (qctrl.step != 1)
return fail("invalid step value %lld\n", qctrl.step);
if (qctrl.minimum < 0)
return fail("min < 0\n");
fallthrough;
case V4L2_CTRL_TYPE_INTEGER:
case V4L2_CTRL_TYPE_INTEGER64:
if (qctrl.default_value < qctrl.minimum ||
qctrl.default_value > qctrl.maximum)
return fail("def < min || def > max\n");
fallthrough;
case V4L2_CTRL_TYPE_STRING:
if (qctrl.minimum > qctrl.maximum)
return fail("min > max\n");
if (qctrl.step == 0)
return fail("step == 0\n");
if (static_cast<unsigned>(qctrl.step) > static_cast<unsigned>(qctrl.maximum - qctrl.minimum) &&
qctrl.maximum != qctrl.minimum)
return fail("step > max - min\n");
if ((qctrl.maximum - qctrl.minimum) % qctrl.step) {
// This really should be a fail, but there are so few
// drivers that do this right that I made it a warning
// for now.
warn("%s: (max - min) %% step != 0\n", qctrl.name);
}
break;
case V4L2_CTRL_TYPE_BITMASK:
if (qctrl.minimum)
return fail("minimum must be 0 for a bitmask control\n");
if (qctrl.step)
return fail("step must be 0 for a bitmask control\n");
if (!qctrl.maximum)
return fail("maximum must be non-zero for a bitmask control\n");
if (qctrl.default_value & ~qctrl.maximum)
return fail("default_value is out of range for a bitmask control\n");
break;
case V4L2_CTRL_TYPE_CTRL_CLASS:
case V4L2_CTRL_TYPE_BUTTON:
if (qctrl.minimum || qctrl.maximum || qctrl.step || qctrl.default_value)
return fail("non-zero min/max/step/def\n");
break;
default:
if (qctrl.type >= V4L2_CTRL_COMPOUND_TYPES)
break;
return fail("unknown control type\n");
}
if (qctrl.type == V4L2_CTRL_TYPE_STRING && qctrl.default_value)
return fail("non-zero default value for string\n");
switch (qctrl.type) {
case V4L2_CTRL_TYPE_BUTTON:
if ((fl & rw_mask) != V4L2_CTRL_FLAG_WRITE_ONLY)
return fail("button control not write only\n");
fallthrough;
case V4L2_CTRL_TYPE_BOOLEAN:
case V4L2_CTRL_TYPE_MENU:
case V4L2_CTRL_TYPE_INTEGER_MENU:
case V4L2_CTRL_TYPE_STRING:
case V4L2_CTRL_TYPE_BITMASK:
if (fl & V4L2_CTRL_FLAG_SLIDER)
return fail("slider makes only sense for integer controls\n");
fallthrough;
case V4L2_CTRL_TYPE_INTEGER64:
case V4L2_CTRL_TYPE_INTEGER:
if ((fl & rw_mask) == rw_mask)
return fail("can't read nor write this control\n");
break;
case V4L2_CTRL_TYPE_CTRL_CLASS:
if (fl != (V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY))
return fail("invalid flags for control class\n");
break;
}
if (fl & V4L2_CTRL_FLAG_GRABBED)
return fail("GRABBED flag set\n");
if (fl & V4L2_CTRL_FLAG_DISABLED)
return fail("DISABLED flag set\n");
if (qctrl.type != V4L2_CTRL_TYPE_MENU && qctrl.type != V4L2_CTRL_TYPE_INTEGER_MENU) {
memset(&qmenu, 0xff, sizeof(qmenu));
qmenu.id = qctrl.id;
qmenu.index = qctrl.minimum;
ret = doioctl(node, VIDIOC_QUERYMENU, &qmenu);
if (ret != EINVAL && ret != ENOTTY)
return fail("can do querymenu on a non-menu control\n");
return 0;
}
bool have_default_value = false;
for (i = 0; i <= qctrl.maximum + 1; i++) {
memset(&qmenu, 0xff, sizeof(qmenu));
qmenu.id = qctrl.id;
qmenu.index = i;
ret = doioctl(node, VIDIOC_QUERYMENU, &qmenu);
if (ret && ret != EINVAL)
return fail("invalid QUERYMENU return code (%d)\n", ret);
if (ret)
continue;
if (i < qctrl.minimum || i > qctrl.maximum)
return fail("can get menu for out-of-range index\n");
if (qmenu.index != static_cast<__u32>(i) || qmenu.id != qctrl.id)
return fail("id or index changed\n");
if (qctrl.type == V4L2_CTRL_TYPE_MENU &&
check_ustring(qmenu.name, sizeof(qmenu.name)))
return fail("invalid menu name\n");
if (qmenu.reserved)
return fail("reserved is non-zero\n");
if (i == qctrl.default_value)
have_default_value = true;
if (i < 64)
qctrl.menu_mask |= 1ULL << i;
}
if (qctrl.menu_mask == 0)
return fail("no menu items found\n");
fail_on_test(!have_default_value);
return 0;
}
int testQueryExtControls(struct node *node)
{
struct test_query_ext_ctrl qctrl;
__u32 id = 0;
int result = 0;
int ret;
__u32 which = 0;
bool found_ctrl_class = false;
unsigned user_controls = 0;
unsigned priv_user_controls = 0;
unsigned user_controls_check = 0;
unsigned priv_user_controls_check = 0;
unsigned class_count = 0;
for (;;) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id | V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
ret = doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qctrl);
if (ret == ENOTTY)
return ret;
if (ret && ret != EINVAL)
return fail("invalid query_ext_ctrl return code (%d)\n", ret);
if (ret)
break;
if (checkQCtrl(node, qctrl))
return fail("invalid control %08x\n", qctrl.id);
if (qctrl.id <= id)
return fail("id did not increase!\n");
id = qctrl.id;
if (id >= V4L2_CID_PRIVATE_BASE)
return fail("no V4L2_CID_PRIVATE_BASE allowed\n");
if (V4L2_CTRL_ID2WHICH(id) != which) {
if (which && !found_ctrl_class)
result = fail("missing control class for class %08x\n", which);
if (which && !class_count)
return fail("no controls in class %08x\n", which);
which = V4L2_CTRL_ID2WHICH(id);
found_ctrl_class = false;
class_count = 0;
}
if (qctrl.type == V4L2_CTRL_TYPE_CTRL_CLASS) {
found_ctrl_class = true;
} else {
class_count++;
}
if (which == V4L2_CTRL_CLASS_USER &&
(qctrl.type < V4L2_CTRL_COMPOUND_TYPES) &&
qctrl.type != V4L2_CTRL_TYPE_INTEGER64 &&
qctrl.type != V4L2_CTRL_TYPE_STRING &&
qctrl.type != V4L2_CTRL_TYPE_CTRL_CLASS) {
if (V4L2_CTRL_DRIVER_PRIV(id))
priv_user_controls_check++;
else if (id < V4L2_CID_LASTP1)
user_controls_check++;
}
if (V4L2_CTRL_DRIVER_PRIV(id)) {
node->priv_controls++;
if (qctrl.type >= V4L2_CTRL_COMPOUND_TYPES || qctrl.nr_of_dims)
node->priv_compound_controls++;
} else {
node->std_controls++;
if (qctrl.type >= V4L2_CTRL_COMPOUND_TYPES || qctrl.nr_of_dims)
node->std_compound_controls++;
}
node->controls[qctrl.id] = qctrl;
}
if (which && !found_ctrl_class &&
(priv_user_controls_check > node->priv_compound_controls ||
user_controls_check > node->std_compound_controls))
result = fail("missing control class for class %08x\n", which);
if (which && !class_count)
return fail("no controls in class %08x\n", which);
id = 0;
unsigned regular_ctrls = 0;
for (;;) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id | V4L2_CTRL_FLAG_NEXT_CTRL;
ret = doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qctrl);
if (ret)
break;
id = qctrl.id;
if (qctrl.type >= V4L2_CTRL_COMPOUND_TYPES || qctrl.nr_of_dims)
return fail("V4L2_CTRL_FLAG_NEXT_CTRL enumerates compound controls!\n");
regular_ctrls++;
}
unsigned num_compound_ctrls = node->std_compound_controls + node->priv_compound_controls;
unsigned num_regular_ctrls = node->std_controls + node->priv_controls - num_compound_ctrls;
if (regular_ctrls != num_regular_ctrls)
return fail("expected to find %d standard controls, got %d instead\n",
num_regular_ctrls, regular_ctrls);
id = 0;
unsigned compound_ctrls = 0;
for (;;) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id | V4L2_CTRL_FLAG_NEXT_COMPOUND;
ret = doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qctrl);
if (ret)
break;
id = qctrl.id;
if (qctrl.type < V4L2_CTRL_COMPOUND_TYPES && !qctrl.nr_of_dims)
return fail("V4L2_CTRL_FLAG_NEXT_COMPOUND enumerates regular controls!\n");
compound_ctrls++;
}
if (compound_ctrls != num_compound_ctrls)
return fail("expected to find %d compound controls, got %d instead\n",
num_compound_ctrls, compound_ctrls);
for (id = V4L2_CID_BASE; id < V4L2_CID_LASTP1; id++) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id;
ret = doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qctrl);
if (ret && ret != EINVAL)
return fail("invalid query_ext_ctrl return code (%d)\n", ret);
if (ret)
continue;
if (qctrl.id != id)
return fail("qctrl.id (%08x) != id (%08x)\n",
qctrl.id, id);
if (checkQCtrl(node, qctrl))
return fail("invalid control %08x\n", qctrl.id);
user_controls++;
}
for (id = V4L2_CID_PRIVATE_BASE; ; id++) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id;
ret = doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qctrl);
if (ret && ret != EINVAL)
return fail("invalid query_ext_ctrl return code (%d)\n", ret);
if (ret)
break;
if (qctrl.id != id)
return fail("qctrl.id (%08x) != id (%08x)\n",
qctrl.id, id);
if (checkQCtrl(node, qctrl))
return fail("invalid control %08x\n", qctrl.id);
priv_user_controls++;
}
if (priv_user_controls + user_controls && node->controls.empty())
return fail("does not support V4L2_CTRL_FLAG_NEXT_CTRL\n");
if (user_controls != user_controls_check)
return fail("expected %d user controls, got %d\n",
user_controls_check, user_controls);
if (priv_user_controls != priv_user_controls_check)
return fail("expected %d private controls, got %d\n",
priv_user_controls_check, priv_user_controls);
return result;
}
int testQueryControls(struct node *node)
{
struct v4l2_queryctrl qctrl;
unsigned controls = 0;
unsigned compound_controls = 0;
__u32 id = 0;
int ret;
unsigned num_compound_ctrls = node->std_compound_controls + node->priv_compound_controls;
unsigned num_regular_ctrls = node->std_controls + node->priv_controls - num_compound_ctrls;
for (;;) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id | V4L2_CTRL_FLAG_NEXT_CTRL;
ret = doioctl(node, VIDIOC_QUERYCTRL, &qctrl);
if (ret == ENOTTY)
return ret;
if (ret && ret != EINVAL)
return fail("invalid queryctrl return code (%d)\n", ret);
if (ret)
break;
id = qctrl.id;
fail_on_test(node->controls.find(qctrl.id) == node->controls.end());
fail_on_test(qctrl.step < 0);
controls++;
}
fail_on_test(controls != num_regular_ctrls);
id = 0;
for (;;) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id | V4L2_CTRL_FLAG_NEXT_COMPOUND;
ret = doioctl(node, VIDIOC_QUERYCTRL, &qctrl);
if (ret)
break;
id = qctrl.id;
fail_on_test(node->controls.find(qctrl.id) == node->controls.end());
fail_on_test(qctrl.step || qctrl.minimum || qctrl.maximum || qctrl.default_value);
compound_controls++;
}
fail_on_test(compound_controls != num_compound_ctrls);
for (id = V4L2_CID_BASE; id < V4L2_CID_LASTP1; id++) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id;
ret = doioctl(node, VIDIOC_QUERYCTRL, &qctrl);
if (ret && ret != EINVAL)
return fail("invalid queryctrl return code (%d)\n", ret);
if (ret)
continue;
if (qctrl.id != id)
return fail("qctrl.id (%08x) != id (%08x)\n",
qctrl.id, id);
fail_on_test(qctrl.step < 0);
}
for (id = V4L2_CID_PRIVATE_BASE; ; id++) {
memset(&qctrl, 0xff, sizeof(qctrl));
qctrl.id = id;
ret = doioctl(node, VIDIOC_QUERYCTRL, &qctrl);
if (ret && ret != EINVAL)
return fail("invalid queryctrl return code (%d)\n", ret);
if (ret)
break;
if (qctrl.id != id)
return fail("qctrl.id (%08x) != id (%08x)\n",
qctrl.id, id);
fail_on_test(qctrl.step < 0);
}
return 0;
}
static int checkSimpleCtrl(const struct v4l2_control &ctrl, const struct test_query_ext_ctrl &qctrl)
{
if (ctrl.id != qctrl.id)
return fail("control id mismatch\n");
switch (qctrl.type) {
case V4L2_CTRL_TYPE_INTEGER:
case V4L2_CTRL_TYPE_BOOLEAN:
case V4L2_CTRL_TYPE_MENU:
case V4L2_CTRL_TYPE_INTEGER_MENU:
if (ctrl.value < qctrl.minimum || ctrl.value > qctrl.maximum)
return fail("returned control value out of range\n");
if ((ctrl.value - qctrl.minimum) % qctrl.step) {
// This really should be a fail, but there are so few
// drivers that do this right that I made it a warning
// for now.
warn("%s: returned control value %d not a multiple of step\n",
qctrl.name, ctrl.value);
}
break;
case V4L2_CTRL_TYPE_BITMASK:
if (static_cast<__u32>(ctrl.value) & ~qctrl.maximum)
return fail("returned control value out of range\n");
break;
case V4L2_CTRL_TYPE_BUTTON:
break;
default:
return fail("this type should not allow g_ctrl\n");
}
return 0;
}
int testSimpleControls(struct node *node)
{
qctrl_map::iterator iter;
struct v4l2_control ctrl;
int ret;
int i;
for (iter = node->controls.begin(); iter != node->controls.end(); ++iter) {
test_query_ext_ctrl &qctrl = iter->second;
if (qctrl.type >= V4L2_CTRL_COMPOUND_TYPES)
continue;
if (is_vivid && V4L2_CTRL_ID2WHICH(qctrl.id) == V4L2_CTRL_CLASS_VIVID)
continue;
info("checking control '%s' (0x%08x)\n", qctrl.name, qctrl.id);
ctrl.id = qctrl.id;
if (qctrl.type == V4L2_CTRL_TYPE_INTEGER64 ||
qctrl.type == V4L2_CTRL_TYPE_STRING ||
qctrl.type == V4L2_CTRL_TYPE_CTRL_CLASS) {
ret = doioctl(node, VIDIOC_G_CTRL, &ctrl);
if (ret != EINVAL &&
!((qctrl.flags & V4L2_CTRL_FLAG_WRITE_ONLY) && ret == EACCES))
return fail("g_ctrl allowed for unsupported type\n");
ctrl.id = qctrl.id;
ctrl.value = 0;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (ret != EINVAL &&
!((qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) && ret == EACCES))
return fail("s_ctrl allowed for unsupported type\n");
continue;
}
// Get the current value
ret = doioctl(node, VIDIOC_G_CTRL, &ctrl);
if ((qctrl.flags & V4L2_CTRL_FLAG_WRITE_ONLY)) {
if (ret != EACCES)
return fail("g_ctrl did not check the write-only flag\n");
ctrl.id = qctrl.id;
ctrl.value = qctrl.default_value;
} else if (ret)
return fail("g_ctrl returned an error (%d)\n", ret);
else if (checkSimpleCtrl(ctrl, qctrl))
return fail("invalid control %08x\n", qctrl.id);
// Try to set the current value (or the default value for write only controls)
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) {
if (ret != EACCES)
return fail("s_ctrl did not check the read-only flag\n");
} else if (ret == EIO) {
warn("s_ctrl returned EIO\n");
ret = 0;
} else if (ret == EILSEQ) {
warn("s_ctrl returned EILSEQ\n");
ret = 0;
} else if (ret == EACCES && is_uvcvideo) {
ret = 0;
} else if (ret) {
return fail("s_ctrl returned an error (%d)\n", ret);
}
if (ret)
continue;
if (checkSimpleCtrl(ctrl, qctrl))
return fail("s_ctrl returned invalid control contents (%08x)\n", qctrl.id);
// Try to set value 'minimum - 1'
if (qctrl.minimum > -0x80000000LL && qctrl.minimum <= 0x7fffffffLL) {
ctrl.id = qctrl.id;
ctrl.value = qctrl.minimum - 1;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (ret && ret != EIO && ret != EILSEQ && ret != ERANGE &&
!(ret == EACCES && is_uvcvideo))
return fail("invalid minimum range check\n");
if (!ret && checkSimpleCtrl(ctrl, qctrl))
return fail("invalid control %08x\n", qctrl.id);
}
// Try to set value 'maximum + 1'
if (qctrl.maximum >= -0x80000000LL && qctrl.maximum < 0x7fffffffLL) {
ctrl.id = qctrl.id;
ctrl.value = qctrl.maximum + 1;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (ret && ret != EIO && ret != EILSEQ && ret != ERANGE &&
!(ret == EACCES && is_uvcvideo))
return fail("invalid maximum range check\n");
if (!ret && checkSimpleCtrl(ctrl, qctrl))
return fail("invalid control %08x\n", qctrl.id);
}
// Try to set non-step value
if (qctrl.step > 1 && qctrl.maximum > qctrl.minimum) {
ctrl.id = qctrl.id;
ctrl.value = qctrl.minimum + 1;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (ret == ERANGE)
warn("%s: returns ERANGE for in-range, but non-step-multiple value\n",
qctrl.name);
else if (ret && ret != EIO && ret != EILSEQ &&
!(ret == EACCES && is_uvcvideo))
return fail("returns error for in-range, but non-step-multiple value\n");
}
if (qctrl.type == V4L2_CTRL_TYPE_MENU || qctrl.type == V4L2_CTRL_TYPE_INTEGER_MENU) {
int max = qctrl.maximum;
/* Currently menu_mask is a 64-bit value, so we can't reliably
* test for menu item > 63. */
if (max > 63)
max = 63;
// check menu items
for (i = qctrl.minimum; i <= max; i++) {
bool valid = qctrl.menu_mask & (1ULL << i);
ctrl.id = qctrl.id;
ctrl.value = i;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (valid && ret == EACCES && is_uvcvideo)
continue;
if (valid && ret)
return fail("could not set valid menu item %d\n", i);
if (!valid && !ret)
return fail("could set invalid menu item %d\n", i);
if (ret && ret != EINVAL)
return fail("setting invalid menu item returned wrong error (%d)\n", ret);
}
} else {
// at least min, max and default values should work
ctrl.id = qctrl.id;
ctrl.value = qctrl.minimum;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (ret && ret != EIO && ret != EILSEQ &&
!(ret == EACCES && is_uvcvideo))
return fail("could not set minimum value\n");
ctrl.value = qctrl.maximum;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (ret && ret != EIO && ret != EILSEQ &&
!(ret == EACCES && is_uvcvideo))
return fail("could not set maximum value\n");
ctrl.value = qctrl.default_value;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (ret && ret != EIO && ret != EILSEQ &&
!(ret == EACCES && is_uvcvideo))
return fail("could not set default value\n");
}
}
ctrl.id = 0;
ret = doioctl(node, VIDIOC_G_CTRL, &ctrl);
if (ret != EINVAL && ret != ENOTTY)
return fail("g_ctrl accepted invalid control ID\n");
ctrl.id = 0;
ctrl.value = 0;
ret = doioctl(node, VIDIOC_S_CTRL, &ctrl);
if (ret != EINVAL && ret != ENOTTY)
return fail("s_ctrl accepted invalid control ID\n");
if (ret == ENOTTY && node->controls.empty())
return ENOTTY;
return 0;
}
static int checkExtendedCtrl(const struct v4l2_ext_control &ctrl, const struct test_query_ext_ctrl &qctrl)
{
int len;
if (ctrl.id != qctrl.id)
return fail("control id mismatch\n");
switch (qctrl.type) {
case V4L2_CTRL_TYPE_INTEGER:
case V4L2_CTRL_TYPE_INTEGER64:
case V4L2_CTRL_TYPE_BOOLEAN:
case V4L2_CTRL_TYPE_MENU:
case V4L2_CTRL_TYPE_INTEGER_MENU:
if (ctrl.value < qctrl.minimum || ctrl.value > qctrl.maximum)
return fail("returned control value out of range\n");
if ((ctrl.value - qctrl.minimum) % qctrl.step) {
// This really should be a fail, but there are so few
// drivers that do this right that I made it a warning
// for now.
warn("%s: returned control value %d not a multiple of step\n",
qctrl.name, ctrl.value);
}
break;
case V4L2_CTRL_TYPE_BITMASK:
if (static_cast<__u32>(ctrl.value) & ~qctrl.maximum)
return fail("returned control value out of range\n");
break;
case V4L2_CTRL_TYPE_BUTTON:
break;
case V4L2_CTRL_TYPE_STRING:
len = strnlen(ctrl.string, qctrl.maximum + 1);
if (len == qctrl.maximum + 1)
return fail("string too long\n");
if (len < qctrl.minimum)
return fail("string too short\n");
if ((len - qctrl.minimum) % qctrl.step)
return fail("string not a multiple of step\n");
break;
default:
break;
}
return 0;
}
int testExtendedControls(struct node *node)
{
qctrl_map::iterator iter;
struct v4l2_ext_controls ctrls;
std::vector<struct v4l2_ext_control> total_vec;
std::vector<struct v4l2_ext_control> class_vec;
struct v4l2_ext_control ctrl;
__u32 which = 0;
bool multiple_classes = false;
int ret;
memset(&ctrls, 0, sizeof(ctrls));
ret = doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls);
if (ret == ENOTTY && node->controls.empty())
return ret;
if (ret)
return fail("g_ext_ctrls does not support count == 0\n");
if (ctrls.which)
return fail("field which changed\n");
if (ctrls.count)
return fail("field count changed\n");
if (check_0(ctrls.reserved, sizeof(ctrls.reserved)))
return fail("reserved not zeroed\n");
memset(&ctrls, 0, sizeof(ctrls));
ret = doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls);
if (ret == ENOTTY && node->controls.empty())
return ret;
if (ret)
return fail("try_ext_ctrls does not support count == 0\n");
if (ctrls.which)
return fail("field which changed\n");
if (ctrls.count)
return fail("field count changed\n");
if (check_0(ctrls.reserved, sizeof(ctrls.reserved)))
return fail("reserved not zeroed\n");
for (iter = node->controls.begin(); iter != node->controls.end(); ++iter) {
test_query_ext_ctrl &qctrl = iter->second;
if (is_vivid && V4L2_CTRL_ID2WHICH(qctrl.id) == V4L2_CTRL_CLASS_VIVID)
continue;
info("checking extended control '%s' (0x%08x)\n", qctrl.name, qctrl.id);
ctrl.id = qctrl.id;
ctrl.size = 0;
ctrl.ptr = nullptr;
ctrl.reserved2[0] = 0;
ctrls.count = 1;
// Either should work, so try both semi-randomly
ctrls.which = (ctrl.id & 1) ? 0 : V4L2_CTRL_ID2WHICH(ctrl.id);
ctrls.controls = &ctrl;
// Get the current value
ret = doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls);
if ((qctrl.flags & V4L2_CTRL_FLAG_WRITE_ONLY)) {
if (ret != EACCES)
return fail("g_ext_ctrls did not check the write-only flag\n");
if (ctrls.error_idx != ctrls.count)
return fail("invalid error index write only control\n");
ctrl.id = qctrl.id;
ctrl.value = qctrl.default_value;
} else {
if (ret != ENOSPC && (qctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD))
return fail("did not check against size\n");
if (ret == ENOSPC && (qctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD)) {
if (qctrl.type == V4L2_CTRL_TYPE_STRING && ctrls.error_idx != 0)
return fail("invalid error index string control\n");
fail_on_test(ctrl.size != qctrl.elem_size * qctrl.elems);
ctrl.string = new char[ctrl.size];
ret = doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls);
}
if (ret == EIO) {
warn("g_ext_ctrls returned EIO\n");
ret = 0;
} else if (ret == EILSEQ) {
warn("g_ext_ctrls returned EILSEQ\n");
ret = 0;
}
if (ret)
return fail("g_ext_ctrls returned an error (%d)\n", ret);
if (checkExtendedCtrl(ctrl, qctrl))
return fail("invalid control %08x\n", qctrl.id);
}
// Try the current value (or the default value for write only controls)
ret = doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls);
if (qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) {
if (ret != EACCES)
return fail("try_ext_ctrls did not check the read-only flag\n");
if (ctrls.error_idx != 0)
return fail("invalid error index read only control\n");
} else if (ret) {
return fail("try_ext_ctrls returned an error (%d)\n", ret);
}
// Try to set the current value (or the default value for write only controls)
ret = doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls);
if (qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) {
if (ret != EACCES)
return fail("s_ext_ctrls did not check the read-only flag\n");
if (ctrls.error_idx != ctrls.count)
return fail("invalid error index\n");
} else {
if (ret == EIO) {
warn("s_ext_ctrls returned EIO\n");
ret = 0;
} else if (ret == EILSEQ) {
warn("s_ext_ctrls returned EILSEQ\n");
ret = 0;
} else if (ret == EACCES && is_uvcvideo) {
ret = 0;
}
if (ret)
return fail("s_ext_ctrls returned an error (%d)\n", ret);
if (checkExtendedCtrl(ctrl, qctrl))
return fail("s_ext_ctrls returned invalid control contents (%08x)\n", qctrl.id);
}
if (qctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD)
delete [] ctrl.string;
ctrl.string = nullptr;
}
ctrls.which = 0;
ctrls.count = 1;
ctrls.controls = &ctrl;
ctrl.id = 0;
ctrl.size = 0;
ret = doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls);
if (ret != EINVAL)
return fail("g_ext_ctrls accepted invalid control ID\n");
fail_on_test(ctrls.error_idx > ctrls.count);
if (ctrls.error_idx != ctrls.count)
warn("g_ext_ctrls(0) invalid error_idx %u\n", ctrls.error_idx);
ctrl.id = 0;
ctrl.size = 0;
ctrl.value = 0;
ret = doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls);
if (ret != EINVAL)
return fail("try_ext_ctrls accepted invalid control ID\n");
if (ctrls.error_idx != 0)
return fail("try_ext_ctrls(0) invalid error_idx %u\n", ctrls.error_idx);
ret = doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls);
if (ret != EINVAL)
return fail("s_ext_ctrls accepted invalid control ID\n");
if (ctrls.error_idx != ctrls.count)
return fail("s_ext_ctrls(0) invalid error_idx %u\n", ctrls.error_idx);
for (iter = node->controls.begin(); iter != node->controls.end(); ++iter) {
test_query_ext_ctrl &qctrl = iter->second;
struct v4l2_ext_control ctrl;
if (qctrl.flags & (V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY))
continue;
ctrl.id = qctrl.id;
ctrl.size = 0;
if (qctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
ctrl.size = qctrl.elems * qctrl.elem_size;
ctrl.string = new char[ctrl.size];
}
ctrl.reserved2[0] = 0;
if (!which)
which = V4L2_CTRL_ID2WHICH(ctrl.id);
else if (which != V4L2_CTRL_ID2WHICH(ctrl.id))
multiple_classes = true;
total_vec.push_back(ctrl);
}
if (total_vec.empty())
return 0;
ctrls.count = total_vec.size();
ctrls.controls = &total_vec[0];
ret = doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls);
if (ret)
return fail("could not get all controls\n");
ret = doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls);
if (ret)
return fail("could not try all controls\n");
ret = doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls);
if (ret == EIO) {
warn("s_ext_ctrls returned EIO\n");
ret = 0;
} else if (ret == EILSEQ) {
warn("s_ext_ctrls returned EILSEQ\n");
ret = 0;
} else if (ret == EACCES && is_uvcvideo) {
ret = 0;
}
if (ret)
return fail("could not set all controls\n");
if (!which)
return 0;
ctrls.which = which;
ret = doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls);
if (ret && !multiple_classes)
return fail("could not get all controls of a specific class\n");
if (ret != EINVAL && multiple_classes)
return fail("should get EINVAL when getting mixed-class controls\n");
if (multiple_classes && ctrls.error_idx != ctrls.count)
warn("error_idx should be equal to count\n");
ret = doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls);
if (ret && !multiple_classes)
return fail("could not try all controls of a specific class\n");
if (ret != EINVAL && multiple_classes)
return fail("should get EINVAL when trying mixed-class controls\n");
if (multiple_classes && ctrls.error_idx >= ctrls.count)
return fail("error_idx should be < count\n");
ret = doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls);
if (ret == EIO) {
warn("s_ext_ctrls returned EIO\n");
ret = 0;
} else if (ret == EILSEQ) {
warn("s_ext_ctrls returned EILSEQ\n");
ret = 0;
}
if (ret && !(ret == EACCES && is_uvcvideo) && !multiple_classes)
return fail("could not set all controls of a specific class\n");
if (ret != EINVAL && multiple_classes)
return fail("should get EINVAL when setting mixed-class controls\n");
if (multiple_classes && ctrls.error_idx != ctrls.count)
warn("error_idx should be equal to count\n");
ctrls.which = V4L2_CTRL_WHICH_DEF_VAL;
fail_on_test(!doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls));
fail_on_test(!doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls));
fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls));
return 0;
}
int testEvents(struct node *node)
{
qctrl_map::iterator iter;
for (iter = node->controls.begin(); iter != node->controls.end(); ++iter) {
test_query_ext_ctrl &qctrl = iter->second;
struct v4l2_event_subscription sub = { 0 };
struct v4l2_event ev;
struct timeval timeout = { 0, 100 };
fd_set set;
int ret;
info("checking control event '%s' (0x%08x)\n", qctrl.name, qctrl.id);
sub.type = V4L2_EVENT_CTRL;
sub.id = qctrl.id;
sub.flags = V4L2_EVENT_SUB_FL_SEND_INITIAL;
ret = doioctl(node, VIDIOC_SUBSCRIBE_EVENT, &sub);
if (ret)
return fail("subscribe event for control '%s' failed\n", qctrl.name);
//if (qctrl.type == V4L2_CTRL_TYPE_CTRL_CLASS)
FD_ZERO(&set);
FD_SET(node->g_fd(), &set);
ret = select(node->g_fd() + 1, nullptr, nullptr, &set, &timeout);
if (ret == 0) {
if (qctrl.type != V4L2_CTRL_TYPE_CTRL_CLASS)
return fail("failed to find event for control '%s'\n", qctrl.name);
} else if (qctrl.type == V4L2_CTRL_TYPE_CTRL_CLASS) {
return fail("found event for control class '%s'\n", qctrl.name);
}
if (ret) {
ret = doioctl(node, VIDIOC_DQEVENT, &ev);
if (ret)
return fail("couldn't get event for control '%s'\n", qctrl.name);
if (ev.type != V4L2_EVENT_CTRL || ev.id != qctrl.id)
return fail("dequeued wrong event\n");
fail_on_test(check_0(ev.reserved, sizeof(ev.reserved)));
}
ret = doioctl(node, VIDIOC_UNSUBSCRIBE_EVENT, &sub);
if (ret)
return fail("unsubscribe event for control '%s' failed\n", qctrl.name);
}
if (node->cur_io_caps & V4L2_IN_CAP_DV_TIMINGS) {
struct v4l2_event_subscription sub = { };
int id;
if (node->can_capture) {
fail_on_test(doioctl(node, VIDIOC_G_INPUT, &id));
if (node->is_video &&
node->controls.find(V4L2_CID_DV_RX_POWER_PRESENT) == node->controls.end())
warn("V4L2_CID_DV_RX_POWER_PRESENT not found for input %d\n", id);
} else {
fail_on_test(doioctl(node, VIDIOC_G_OUTPUT, &id));
if (node->is_video &&
node->controls.find(V4L2_CID_DV_TX_HOTPLUG) == node->controls.end())
warn("V4L2_CID_DV_TX_HOTPLUG not found for output %d\n", id);
if (node->is_video &&
node->controls.find(V4L2_CID_DV_TX_EDID_PRESENT) == node->controls.end())
warn("V4L2_CID_DV_TX_EDID_PRESENT not found for output %d\n", id);
}
if (node->can_capture) {
sub.type = V4L2_EVENT_SOURCE_CHANGE;
sub.id = id;
fail_on_test(doioctl(node, VIDIOC_SUBSCRIBE_EVENT, &sub));
fail_on_test(doioctl(node, VIDIOC_UNSUBSCRIBE_EVENT, &sub));
}
}
if (node->codec_mask & STATEFUL_ENCODER)
fail_on_test(node->controls.find(V4L2_CID_MIN_BUFFERS_FOR_OUTPUT) == node->controls.end());
struct v4l2_event_subscription sub = { 0 };
sub.type = V4L2_EVENT_EOS;
bool have_eos = !doioctl(node, VIDIOC_SUBSCRIBE_EVENT, &sub);
if (have_eos)
fail_on_test(doioctl(node, VIDIOC_UNSUBSCRIBE_EVENT, &sub));
else
fail_on_test(node->codec_mask & STATEFUL_ENCODER);
sub.type = V4L2_EVENT_SOURCE_CHANGE;
bool have_source_change = !doioctl(node, VIDIOC_SUBSCRIBE_EVENT, &sub);
if (have_source_change)
fail_on_test(doioctl(node, VIDIOC_UNSUBSCRIBE_EVENT, &sub));
switch (node->codec_mask) {
case STATEFUL_ENCODER:
fail_on_test(have_source_change || !have_eos);
break;
case STATEFUL_DECODER:
fail_on_test(!have_source_change || !have_eos);
break;
case STATELESS_ENCODER:
case STATELESS_DECODER:
fail_on_test(have_source_change || have_eos);
break;
default:
break;
}
if (node->can_output && !node->is_m2m)
fail_on_test(have_source_change);
if (node->controls.empty())
return ENOTTY;
return 0;
}
int testVividDisconnect(struct node *node)
{
// Test that disconnecting a device will wake up any processes
// that are using select or poll.
//
// This can only be tested with the vivid driver that enabled
// the DISCONNECT control.
pid_t pid = fork();
if (pid == 0) {
struct timeval tv = { 5, 0 };
fd_set fds;
FD_ZERO(&fds);
FD_SET(node->g_fd(), &fds);
int res = select(node->g_fd() + 1, nullptr, nullptr, &fds, &tv);
// No POLLPRI seen
if (res != 1)
exit(1);
// POLLPRI seen, but didn't wake up
if (!tv.tv_sec)
exit(2);
v4l2_event ev = {};
// Woke up on POLLPRI, but VIDIOC_DQEVENT didn't return
// the ENODEV error.
if (doioctl(node, VIDIOC_DQEVENT, &ev) != ENODEV)
exit(3);
exit(0);
}
v4l2_control ctrl = { VIVID_CID_DISCONNECT, 0 };
sleep(1);
fail_on_test(doioctl(node, VIDIOC_S_CTRL, &ctrl));
int wstatus;
fail_on_test(waitpid(pid, &wstatus, 0) != pid);
fail_on_test(!WIFEXITED(wstatus));
if (WEXITSTATUS(wstatus))
return fail("select child exited with status %d\n", WEXITSTATUS(wstatus));
node->reopen();
pid = fork();
if (pid == 0) {
struct epoll_event ep_ev;
int epollfd = epoll_create1(0);
if (epollfd < 0)
exit(1);
ep_ev.events = 0;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, node->g_fd(), &ep_ev))
exit(2);
ep_ev.events = EPOLLPRI;
ep_ev.data.fd = node->g_fd();
if (epoll_ctl(epollfd, EPOLL_CTL_MOD, node->g_fd(), &ep_ev))
exit(3);
int ret = epoll_wait(epollfd, &ep_ev, 1, 5000);
if (ret == 0)
exit(4);
if (ret < 0)
exit(5);
if (ret != 1)
exit(6);
if (!(ep_ev.events & EPOLLPRI))
exit(7);
if (!(ep_ev.events & EPOLLERR))
exit(8);
v4l2_event ev = {};
// Woke up on POLLPRI, but VIDIOC_DQEVENT didn't return
// the ENODEV error.
if (doioctl(node, VIDIOC_DQEVENT, &ev) != ENODEV)
exit(9);
exit(0);
}
sleep(1);
fail_on_test(doioctl(node, VIDIOC_S_CTRL, &ctrl));
fail_on_test(waitpid(pid, &wstatus, 0) != pid);
fail_on_test(!WIFEXITED(wstatus));
if (WEXITSTATUS(wstatus))
return fail("epoll child exited with status %d\n", WEXITSTATUS(wstatus));
return 0;
}
int testJpegComp(struct node *node)
{
struct v4l2_jpegcompression jc;
bool have_jpegcomp = false;
const unsigned all_markers =
V4L2_JPEG_MARKER_DHT | V4L2_JPEG_MARKER_DQT |
V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_COM |
V4L2_JPEG_MARKER_APP;
int ret;
memset(&jc, 0, sizeof(jc));
ret = doioctl(node, VIDIOC_G_JPEGCOMP, &jc);
if (ret != ENOTTY) {
warn("The VIDIOC_G_JPEGCOMP ioctl is deprecated!\n");
if (ret)
return fail("VIDIOC_G_JPEGCOMP gave an error\n");
have_jpegcomp = true;
if (jc.COM_len < 0 || jc.COM_len > static_cast<int>(sizeof(jc.COM_data)))
return fail("invalid COM_len value\n");
if (jc.APP_len < 0 || jc.APP_len > static_cast<int>(sizeof(jc.APP_data)))
return fail("invalid APP_len value\n");
if (jc.quality < 0 || jc.quality > 100)
warn("weird quality value: %d\n", jc.quality);
if (jc.APPn < 0 || jc.APPn > 15)
return fail("invalid APPn value (%d)\n", jc.APPn);
if (jc.jpeg_markers & ~all_markers)
return fail("invalid markers (%x)\n", jc.jpeg_markers);
}
ret = doioctl(node, VIDIOC_S_JPEGCOMP, &jc);
if (ret != ENOTTY) {
warn("The VIDIOC_S_JPEGCOMP ioctl is deprecated!\n");
if (ret && ret != EINVAL)
return fail("VIDIOC_S_JPEGCOMP gave an error\n");
have_jpegcomp = true;
}
return have_jpegcomp ? ret : ENOTTY;
}