/* Internal temporary helper struct, one for each v4l2_ext_control */ struct v4l2_ctrl_helper { /* Pointer to the control reference of the master control */ struct v4l2_ctrl_ref *mref; /* The control ref corresponding to the v4l2_ext_control ID field. */ struct v4l2_ctrl_ref *ref; /* * v4l2_ext_control index of the next control belonging to the * same cluster, or 0 if there isn't any.
*/
u32 next;
};
/* * Helper functions to copy control payload data from kernel space to * user space and vice versa.
*/
/* Helper function: copy the given control value back to the caller */ staticint ptr_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl, union v4l2_ctrl_ptr ptr)
{
u32 len;
switch (ctrl->type) { case V4L2_CTRL_TYPE_STRING:
len = strlen(ptr.p_char); if (c->size < len + 1) {
c->size = ctrl->elem_size; return -ENOSPC;
} return copy_to_user(c->string, ptr.p_char, len + 1) ?
-EFAULT : 0; case V4L2_CTRL_TYPE_INTEGER64:
c->value64 = *ptr.p_s64; break; default:
c->value = *ptr.p_s32; break;
} return 0;
}
/* Helper function: copy the current control value back to the caller */ staticint cur_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{ return ptr_to_user(c, ctrl, ctrl->p_cur);
}
/* Helper function: copy the new control value back to the caller */ staticint new_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{ return ptr_to_user(c, ctrl, ctrl->p_new);
}
/* Helper function: copy the request value back to the caller */ staticint req_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl_ref *ref)
{ return ptr_to_user(c, ref->ctrl, ref->p_req);
}
/* Helper function: copy the initial control value back to the caller */ staticint def_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{
ctrl->type_ops->init(ctrl, 0, ctrl->p_new);
return ptr_to_user(c, ctrl, ctrl->p_new);
}
/* Helper function: copy the minimum control value back to the caller */ staticint min_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{
ctrl->type_ops->minimum(ctrl, 0, ctrl->p_new);
return ptr_to_user(c, ctrl, ctrl->p_new);
}
/* Helper function: copy the maximum control value back to the caller */ staticint max_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{
ctrl->type_ops->maximum(ctrl, 0, ctrl->p_new);
return ptr_to_user(c, ctrl, ctrl->p_new);
}
/* Helper function: copy the caller-provider value as the new control value */ staticint user_to_new(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{ int ret;
u32 size;
switch (ctrl->type) { case V4L2_CTRL_TYPE_INTEGER64:
*ctrl->p_new.p_s64 = c->value64; break; case V4L2_CTRL_TYPE_STRING:
size = c->size; if (size == 0) return -ERANGE; if (size > ctrl->maximum + 1)
size = ctrl->maximum + 1;
ret = copy_from_user(ctrl->p_new.p_char, c->string, size) ? -EFAULT : 0; if (!ret) { char last = ctrl->p_new.p_char[size - 1];
ctrl->p_new.p_char[size - 1] = 0; /* * If the string was longer than ctrl->maximum, * then return an error.
*/ if (strlen(ctrl->p_new.p_char) == ctrl->maximum && last) return -ERANGE;
ctrl->is_new = 1;
} return ret; default:
*ctrl->p_new.p_s32 = c->value; break;
}
ctrl->is_new = 1; return 0;
}
/* * VIDIOC_G/TRY/S_EXT_CTRLS implementation
*/
/* * Some general notes on the atomic requirements of VIDIOC_G/TRY/S_EXT_CTRLS: * * It is not a fully atomic operation, just best-effort only. After all, if * multiple controls have to be set through multiple i2c writes (for example) * then some initial writes may succeed while others fail. Thus leaving the * system in an inconsistent state. The question is how much effort you are * willing to spend on trying to make something atomic that really isn't. * * From the point of view of an application the main requirement is that * when you call VIDIOC_S_EXT_CTRLS and some values are invalid then an * error should be returned without actually affecting any controls. * * If all the values are correct, then it is acceptable to just give up * in case of low-level errors. * * It is important though that the application can tell when only a partial * configuration was done. The way we do that is through the error_idx field * of struct v4l2_ext_controls: if that is equal to the count field then no * controls were affected. Otherwise all controls before that index were * successful in performing their 'get' or 'set' operation, the control at * the given index failed, and you don't know what happened with the controls * after the failed one. Since if they were part of a control cluster they * could have been successfully processed (if a cluster member was encountered * at index < error_idx), they could have failed (if a cluster member was at * error_idx), or they may not have been processed yet (if the first cluster * member appeared after error_idx). * * It is all fairly theoretical, though. In practice all you can do is to * bail out. If error_idx == count, then it is an application bug. If * error_idx < count then it is only an application bug if the error code was * EBUSY. That usually means that something started streaming just when you * tried to set the controls. In all other cases it is a driver/hardware * problem and all you can do is to retry or bail out. * * Note that these rules do not apply to VIDIOC_TRY_EXT_CTRLS: since that * never modifies controls the error_idx is just set to whatever control * has an invalid value.
*/
/* * Prepare for the extended g/s/try functions. * Find the controls in the control array and do some basic checks.
*/ staticint prepare_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *cs, struct v4l2_ctrl_helper *helpers, struct video_device *vdev, bool get)
{ struct v4l2_ctrl_helper *h; bool have_clusters = false;
u32 i;
for (i = 0, h = helpers; i < cs->count; i++, h++) { struct v4l2_ext_control *c = &cs->controls[i]; struct v4l2_ctrl_ref *ref; struct v4l2_ctrl *ctrl;
u32 id = c->id & V4L2_CTRL_ID_MASK;
cs->error_idx = i;
if (cs->which &&
(cs->which < V4L2_CTRL_WHICH_DEF_VAL ||
cs->which > V4L2_CTRL_WHICH_MAX_VAL) &&
V4L2_CTRL_ID2WHICH(id) != cs->which) {
dprintk(vdev, "invalid which 0x%x or control id 0x%x\n",
cs->which, id); return -EINVAL;
}
/* * Old-style private controls are not allowed for * extended controls.
*/ if (id >= V4L2_CID_PRIVATE_BASE) {
dprintk(vdev, "old-style private controls not allowed\n"); return -EINVAL;
}
ref = find_ref_lock(hdl, id); if (!ref) {
dprintk(vdev, "cannot find control id 0x%x\n", id); return -EINVAL;
}
h->ref = ref;
ctrl = ref->ctrl; if (ctrl->flags & V4L2_CTRL_FLAG_DISABLED) {
dprintk(vdev, "control id 0x%x is disabled\n", id); return -EINVAL;
}
if (!(ctrl->flags & V4L2_CTRL_FLAG_HAS_WHICH_MIN_MAX) &&
(cs->which == V4L2_CTRL_WHICH_MIN_VAL ||
cs->which == V4L2_CTRL_WHICH_MAX_VAL)) {
dprintk(vdev, "invalid which 0x%x or control id 0x%x\n",
cs->which, id); return -EINVAL;
}
if (ctrl->cluster[0]->ncontrols > 1)
have_clusters = true; if (ctrl->cluster[0] != ctrl)
ref = find_ref_lock(hdl, ctrl->cluster[0]->id); if (ctrl->is_dyn_array) { unsignedint max_size = ctrl->dims[0] * ctrl->elem_size; unsignedint tot_size = ctrl->elem_size;
if (c->size < tot_size) { /* * In the get case the application first * queries to obtain the size of the control.
*/ if (get) {
c->size = tot_size; return -ENOSPC;
}
dprintk(vdev, "pointer control id 0x%x size too small, %d bytes but %d bytes needed\n",
id, c->size, tot_size); return -EFAULT;
}
c->size = tot_size;
} /* Store the ref to the master control of the cluster */
h->mref = ref; /* * Initially set next to 0, meaning that there is no other * control in this helper array belonging to the same * cluster.
*/
h->next = 0;
}
/* * We are done if there were no controls that belong to a multi- * control cluster.
*/ if (!have_clusters) return 0;
/* * The code below figures out in O(n) time which controls in the list * belong to the same cluster.
*/
/* This has to be done with the handler lock taken. */
mutex_lock(hdl->lock);
/* First zero the helper field in the master control references */ for (i = 0; i < cs->count; i++)
helpers[i].mref->helper = NULL; for (i = 0, h = helpers; i < cs->count; i++, h++) { struct v4l2_ctrl_ref *mref = h->mref;
/* * If the mref->helper is set, then it points to an earlier * helper that belongs to the same cluster.
*/ if (mref->helper) { /* * Set the next field of mref->helper to the current * index: this means that the earlier helper now * points to the next helper in the same cluster.
*/
mref->helper->next = i; /* * mref should be set only for the first helper in the * cluster, clear the others.
*/
h->mref = NULL;
} /* Point the mref helper to the current helper struct. */
mref->helper = h;
}
mutex_unlock(hdl->lock); return 0;
}
/* * Handles the corner case where cs->count == 0. It checks whether the * specified control class exists. If that class ID is 0, then it checks * whether there are any controls at all.
*/ staticint class_check(struct v4l2_ctrl_handler *hdl, u32 which)
{ if (which == 0 || (which >= V4L2_CTRL_WHICH_DEF_VAL &&
which <= V4L2_CTRL_WHICH_MAX_VAL)) return 0; return find_ref_lock(hdl, which | 1) ? 0 : -EINVAL;
}
/* * Get extended controls. Allocates the helpers array if needed. * * Note that v4l2_g_ext_ctrls_common() with 'which' set to * V4L2_CTRL_WHICH_REQUEST_VAL is only called if the request was * completed, and in that case p_req_valid is true for all controls.
*/ int v4l2_g_ext_ctrls_common(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *cs, struct video_device *vdev)
{ struct v4l2_ctrl_helper helper[4]; struct v4l2_ctrl_helper *helpers = helper; int ret; int i, j; bool is_default, is_request, is_min, is_max;
/* * g_volatile_ctrl will update the new control values. * This makes no sense for V4L2_CTRL_WHICH_DEF_VAL, * V4L2_CTRL_WHICH_MIN_VAL, V4L2_CTRL_WHICH_MAX_VAL and * V4L2_CTRL_WHICH_REQUEST_VAL. In the case of requests * it is v4l2_ctrl_request_complete() that copies the * volatile controls at the time of request completion * to the request, so you don't want to do that again.
*/ if (!is_default && !is_request && !is_min && !is_max &&
((master->flags & V4L2_CTRL_FLAG_VOLATILE) ||
(master->has_volatiles && !is_cur_manual(master)))) { for (j = 0; j < master->ncontrols; j++)
cur_to_new(master->cluster[j]);
ret = call_op(master, g_volatile_ctrl);
is_volatile = true;
}
if (ret) {
v4l2_ctrl_unlock(master); break;
}
/* * Copy the default value (if is_default is true), the * request value (if is_request is true and p_req is valid), * the new volatile value (if is_volatile is true) or the * current value.
*/ do { struct v4l2_ctrl_ref *ref = helpers[idx].ref;
if (is_default)
ret = def_to_user(cs->controls + idx, ref->ctrl); elseif (is_request && ref->p_req_array_enomem)
ret = -ENOMEM; elseif (is_request && ref->p_req_valid)
ret = req_to_user(cs->controls + idx, ref); elseif (is_min)
ret = min_to_user(cs->controls + idx, ref->ctrl); elseif (is_max)
ret = max_to_user(cs->controls + idx, ref->ctrl); elseif (is_volatile)
ret = new_to_user(cs->controls + idx, ref->ctrl); else
ret = cur_to_user(cs->controls + idx, ref->ctrl);
idx = helpers[idx].next;
} while (!ret && idx);
v4l2_ctrl_unlock(master);
}
if (cs->count > ARRAY_SIZE(helper))
kvfree(helpers); return ret;
}
/* Validate a new control */ staticint validate_new(conststruct v4l2_ctrl *ctrl, union v4l2_ctrl_ptr p_new)
{ return ctrl->type_ops->validate(ctrl, p_new);
}
cs->error_idx = cs->count; for (i = 0; i < cs->count; i++) { struct v4l2_ctrl *ctrl = helpers[i].ref->ctrl; union v4l2_ctrl_ptr p_new;
cs->error_idx = i;
if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) {
dprintk(vdev, "control id 0x%x is read-only\n",
ctrl->id); return -EACCES;
} /* * This test is also done in try_set_control_cluster() which * is called in atomic context, so that has the final say, * but it makes sense to do an up-front check as well. Once * an error occurs in try_set_control_cluster() some other * controls may have been set already and we want to do a * best-effort to avoid that.
*/ if (set && (ctrl->flags & V4L2_CTRL_FLAG_GRABBED)) {
dprintk(vdev, "control id 0x%x is grabbed, cannot set\n",
ctrl->id); return -EBUSY;
} /* * Skip validation for now if the payload needs to be copied * from userspace into kernelspace. We'll validate those later.
*/ if (ctrl->is_ptr) continue; if (ctrl->type == V4L2_CTRL_TYPE_INTEGER64)
p_new.p_s64 = &cs->controls[i].value64; else
p_new.p_s32 = &cs->controls[i].value;
ret = validate_new(ctrl, p_new); if (ret) return ret;
} return 0;
}
/* Try or try-and-set controls */ int try_set_ext_ctrls_common(struct v4l2_fh *fh, struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *cs, struct video_device *vdev, bool set)
{ struct v4l2_ctrl_helper helper[4]; struct v4l2_ctrl_helper *helpers = helper; unsignedint i, j; int ret;
/* Reset the 'is_new' flags of the cluster */ for (j = 0; j < master->ncontrols; j++) if (master->cluster[j])
master->cluster[j]->is_new = 0;
/* * For volatile autoclusters that are currently in auto mode * we need to discover if it will be set to manual mode. * If so, then we have to copy the current volatile values * first since those will become the new manual values (which * may be overwritten by explicit new values from this set * of controls).
*/ if (master->is_auto && master->has_volatiles &&
!is_cur_manual(master)) { /* Pick an initial non-manual value */
s32 new_auto_val = master->manual_mode_value + 1;
u32 tmp_idx = idx;
do { /* * Check if the auto control is part of the * list, and remember the new value.
*/ if (helpers[tmp_idx].ref->ctrl == master)
new_auto_val = cs->controls[tmp_idx].value;
tmp_idx = helpers[tmp_idx].next;
} while (tmp_idx); /* * If the new value == the manual value, then copy * the current volatile values.
*/ if (new_auto_val == master->manual_mode_value)
update_from_auto_cluster(master);
}
/* * Copy the new caller-supplied control values. * user_to_new() sets 'is_new' to 1.
*/ do { struct v4l2_ctrl *ctrl = helpers[idx].ref->ctrl;
ret = user_to_new(cs->controls + idx, ctrl); if (!ret && ctrl->is_ptr) {
ret = validate_new(ctrl, ctrl->p_new); if (ret)
dprintk(vdev, "failed to validate control %s (%d)\n",
v4l2_ctrl_get_name(ctrl->id), ret);
}
idx = helpers[idx].next;
} while (!ret && idx);
if (!ret)
ret = try_or_set_cluster(fh, master,
!hdl->req_obj.req && set, 0); if (!ret && hdl->req_obj.req && set) { for (j = 0; j < master->ncontrols; j++) { struct v4l2_ctrl_ref *ref =
find_ref(hdl, master->cluster[j]->id);
new_to_req(ref);
}
}
/* Copy the new values back to userspace. */ if (!ret) {
idx = i; do {
ret = new_to_user(cs->controls + idx,
helpers[idx].ref->ctrl);
idx = helpers[idx].next;
} while (!ret && idx);
}
v4l2_ctrl_unlock(master);
}
if (cs->count > ARRAY_SIZE(helper))
kvfree(helpers); return ret;
}
/* Helper function to get a single control */ staticint get_ctrl(struct v4l2_ctrl *ctrl, struct v4l2_ext_control *c)
{ struct v4l2_ctrl *master = ctrl->cluster[0]; int ret = 0; int i;
/* Compound controls are not supported. The new_to_user() and * cur_to_user() calls below would need to be modified not to access * userspace memory when called from get_ctrl().
*/ if (!ctrl->is_int && ctrl->type != V4L2_CTRL_TYPE_INTEGER64) return -EINVAL;
if (ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY) return -EACCES;
v4l2_ctrl_lock(master); /* g_volatile_ctrl will update the current control values */ if (ctrl->flags & V4L2_CTRL_FLAG_VOLATILE) { for (i = 0; i < master->ncontrols; i++)
cur_to_new(master->cluster[i]);
ret = call_op(master, g_volatile_ctrl); if (!ret)
ret = new_to_user(c, ctrl);
} else {
ret = cur_to_user(c, ctrl);
}
v4l2_ctrl_unlock(master); return ret;
}
int v4l2_g_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *control)
{ struct v4l2_ctrl *ctrl = v4l2_ctrl_find(hdl, control->id); struct v4l2_ext_control c; int ret;
if (!ctrl || !ctrl->is_int) return -EINVAL;
ret = get_ctrl(ctrl, &c);
if (!ret)
control->value = c.value;
return ret;
}
EXPORT_SYMBOL(v4l2_g_ctrl);
/* Helper function for VIDIOC_S_CTRL compatibility */ staticint set_ctrl(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, u32 ch_flags)
{ struct v4l2_ctrl *master = ctrl->cluster[0]; int ret; int i;
/* Reset the 'is_new' flags of the cluster */ for (i = 0; i < master->ncontrols; i++) if (master->cluster[i])
master->cluster[i]->is_new = 0;
ret = validate_new(ctrl, ctrl->p_new); if (ret) return ret;
/* * For autoclusters with volatiles that are switched from auto to * manual mode we have to update the current volatile values since * those will become the initial manual values after such a switch.
*/ if (master->is_auto && master->has_volatiles && ctrl == master &&
!is_cur_manual(master) && ctrl->val == master->manual_mode_value)
update_from_auto_cluster(master);
/* Helper function for VIDIOC_S_CTRL compatibility */ staticint set_ctrl_lock(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, struct v4l2_ext_control *c)
{ int ret;
v4l2_ctrl_lock(ctrl);
ret = user_to_new(c, ctrl); if (!ret)
ret = set_ctrl(fh, ctrl, 0); if (!ret)
ret = cur_to_user(c, ctrl);
v4l2_ctrl_unlock(ctrl); return ret;
}
int v4l2_s_ctrl(struct v4l2_fh *fh, struct v4l2_ctrl_handler *hdl, struct v4l2_control *control)
{ struct v4l2_ctrl *ctrl = v4l2_ctrl_find(hdl, control->id); struct v4l2_ext_control c = { control->id }; int ret;
if (!ctrl || !ctrl->is_int) return -EINVAL;
if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) return -EACCES;
/* It's a driver bug if this happens. */ if (WARN_ON(ctrl->type != type)) return -EINVAL; /* Setting dynamic arrays is not (yet?) supported. */ if (WARN_ON(ctrl->is_dyn_array)) return -EINVAL;
memcpy(ctrl->p_new.p, p, ctrl->elems * ctrl->elem_size); return set_ctrl(NULL, ctrl, 0);
}
EXPORT_SYMBOL(__v4l2_ctrl_s_ctrl_compound);
/* * Modify the range of a control.
*/ int __v4l2_ctrl_modify_range(struct v4l2_ctrl *ctrl,
s64 min, s64 max, u64 step, s64 def)
{ bool value_changed; bool range_changed = false; int ret;
lockdep_assert_held(ctrl->handler->lock);
switch (ctrl->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: case V4L2_CTRL_TYPE_BITMASK: case V4L2_CTRL_TYPE_U8: case V4L2_CTRL_TYPE_U16: case V4L2_CTRL_TYPE_U32: if (ctrl->is_array) return -EINVAL;
ret = check_range(ctrl->type, min, max, step, def); if (ret) return ret; break; default: return -EINVAL;
} if (ctrl->minimum != min || ctrl->maximum != max ||
ctrl->step != step || ctrl->default_value != def) {
range_changed = true;
ctrl->minimum = min;
ctrl->maximum = max;
ctrl->step = step;
ctrl->default_value = def;
}
cur_to_new(ctrl); if (validate_new(ctrl, ctrl->p_new)) { if (ctrl->type == V4L2_CTRL_TYPE_INTEGER64)
*ctrl->p_new.p_s64 = def; else
*ctrl->p_new.p_s32 = def;
}
if ((qc->id & next_flags) && !list_empty(&hdl->ctrl_refs)) { bool is_compound; /* Match any control that is not hidden */ unsignedint mask = 1; bool match = false;
if ((qc->id & next_flags) == V4L2_CTRL_FLAG_NEXT_COMPOUND) { /* Match any hidden control */
match = true;
} elseif ((qc->id & next_flags) == next_flags) { /* Match any control, compound or not */
mask = 0;
}
/* Find the next control with ID > qc->id */
/* Did we reach the end of the control list? */ if (id >= node2id(hdl->ctrl_refs.prev)) {
ref = NULL; /* Yes, so there is no next control */
} elseif (ref) { struct v4l2_ctrl_ref *pos = ref;
/* * We found a control with the given ID, so just get * the next valid one in the list.
*/
ref = NULL;
list_for_each_entry_continue(pos, &hdl->ctrl_refs, node) {
is_compound = pos->ctrl->is_array ||
pos->ctrl->type >= V4L2_CTRL_COMPOUND_TYPES; if (id < pos->ctrl->id &&
(is_compound & mask) == match) {
ref = pos; break;
}
}
} else { struct v4l2_ctrl_ref *pos;
/* * No control with the given ID exists, so start * searching for the next largest ID. We know there * is one, otherwise the first 'if' above would have * been true.
*/
list_for_each_entry(pos, &hdl->ctrl_refs, node) {
is_compound = pos->ctrl->is_array ||
pos->ctrl->type >= V4L2_CTRL_COMPOUND_TYPES; if (id < pos->ctrl->id &&
(is_compound & mask) == match) {
ref = pos; break;
}
}
}
}
mutex_unlock(hdl->lock);
rc = v4l2_query_ext_ctrl(hdl, &qec); if (rc) return rc;
v4l2_query_ext_ctrl_to_v4l2_queryctrl(qc, &qec);
return 0;
}
EXPORT_SYMBOL(v4l2_queryctrl);
/* Implement VIDIOC_QUERYMENU */ int v4l2_querymenu(struct v4l2_ctrl_handler *hdl, struct v4l2_querymenu *qm)
{ struct v4l2_ctrl *ctrl;
u32 i = qm->index;
ctrl = v4l2_ctrl_find(hdl, qm->id); if (!ctrl) return -EINVAL;
qm->reserved = 0; /* Sanity checks */ switch (ctrl->type) { case V4L2_CTRL_TYPE_MENU: if (!ctrl->qmenu) return -EINVAL; break; case V4L2_CTRL_TYPE_INTEGER_MENU: if (!ctrl->qmenu_int) return -EINVAL; break; default: return -EINVAL;
}
if (i < ctrl->minimum || i > ctrl->maximum) return -EINVAL;
/* Use mask to see if this menu item should be skipped */ if (i < BITS_PER_LONG_LONG && (ctrl->menu_skip_mask & BIT_ULL(i))) return -EINVAL; /* Empty menu items should also be skipped */ if (ctrl->type == V4L2_CTRL_TYPE_MENU) { if (!ctrl->qmenu[i] || ctrl->qmenu[i][0] == '\0') return -EINVAL;
strscpy(qm->name, ctrl->qmenu[i], sizeof(qm->name));
} else {
qm->value = ctrl->qmenu_int[i];
} return 0;
}
EXPORT_SYMBOL(v4l2_querymenu);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.