#define pr_fmt(fmt) "v4l2-ctrls: " fmt
#include <linux/export.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h>
#include "v4l2-ctrls-priv.h"
struct v4l2_ctrl_helper {
struct v4l2_ctrl_ref *mref;
struct v4l2_ctrl_ref *ref;
u32 next;
};
static int ptr_to_user(struct v4l2_ext_control *c,
struct v4l2_ctrl *ctrl,
union v4l2_ctrl_ptr ptr)
{
u32 len;
if (ctrl->is_ptr && !ctrl->is_string)
return copy_to_user(c->ptr, ptr.p_const, c->size) ?
-EFAULT : 0;
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;
}
static int cur_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{
return ptr_to_user(c, ctrl, ctrl->p_cur);
}
static int new_to_user(struct v4l2_ext_control *c,
struct v4l2_ctrl *ctrl)
{
return ptr_to_user(c, ctrl, ctrl->p_new);
}
static int req_to_user(struct v4l2_ext_control *c,
struct v4l2_ctrl_ref *ref)
{
return ptr_to_user(c, ref->ctrl, ref->p_req);
}
static int 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);
}
static int user_to_new(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{
int ret;
u32 size;
ctrl->is_new = 0;
if (ctrl->is_dyn_array &&
c->size > ctrl->p_array_alloc_elems * ctrl->elem_size) {
void *old = ctrl->p_array;
void *tmp = kvzalloc(2 * c->size, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
memcpy(tmp, ctrl->p_new.p, ctrl->elems * ctrl->elem_size);
memcpy(tmp + c->size, ctrl->p_cur.p, ctrl->elems * ctrl->elem_size);
ctrl->p_new.p = tmp;
ctrl->p_cur.p = tmp + c->size;
ctrl->p_array = tmp;
ctrl->p_array_alloc_elems = c->size / ctrl->elem_size;
kvfree(old);
}
if (ctrl->is_ptr && !ctrl->is_string) {
unsigned int elems = c->size / ctrl->elem_size;
if (copy_from_user(ctrl->p_new.p, c->ptr, c->size))
return -EFAULT;
ctrl->is_new = 1;
if (ctrl->is_dyn_array)
ctrl->new_elems = elems;
else if (ctrl->is_array)
ctrl->type_ops->init(ctrl, elems, ctrl->p_new);
return 0;
}
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 (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;
}
static int 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_REQUEST_VAL &&
V4L2_CTRL_ID2WHICH(id) != cs->which) {
dprintk(vdev,
"invalid which 0x%x or control id 0x%x\n",
cs->which, id);
return -EINVAL;
}
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->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) {
unsigned int max_size = ctrl->dims[0] * ctrl->elem_size;
unsigned int tot_size = ctrl->elem_size;
if (cs->which == V4L2_CTRL_WHICH_REQUEST_VAL)
tot_size *= ref->p_req_elems;
else
tot_size *= ctrl->elems;
c->size = ctrl->elem_size * (c->size / ctrl->elem_size);
if (get) {
if (c->size < tot_size) {
c->size = tot_size;
return -ENOSPC;
}
c->size = tot_size;
} else {
if (c->size > max_size) {
c->size = max_size;
return -ENOSPC;
}
if (!c->size)
return -EFAULT;
}
} else if (ctrl->is_ptr && !ctrl->is_string) {
unsigned int tot_size = ctrl->elems * ctrl->elem_size;
if (c->size < tot_size) {
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;
}
h->mref = ref;
h->next = 0;
}
if (!have_clusters)
return 0;
mutex_lock(hdl->lock);
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 (mref->helper) {
mref->helper->next = i;
h->mref = NULL;
}
mref->helper = h;
}
mutex_unlock(hdl->lock);
return 0;
}
static int class_check(struct v4l2_ctrl_handler *hdl, u32 which)
{
if (which == 0 || which == V4L2_CTRL_WHICH_DEF_VAL ||
which == V4L2_CTRL_WHICH_REQUEST_VAL)
return 0;
return find_ref_lock(hdl, which | 1) ? 0 : -EINVAL;
}
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_default = (cs->which == V4L2_CTRL_WHICH_DEF_VAL);
is_request = (cs->which == V4L2_CTRL_WHICH_REQUEST_VAL);
cs->error_idx = cs->count;
cs->which = V4L2_CTRL_ID2WHICH(cs->which);
if (!hdl)
return -EINVAL;
if (cs->count == 0)
return class_check(hdl, cs->which);
if (cs->count > ARRAY_SIZE(helper)) {
helpers = kvmalloc_array(cs->count, sizeof(helper[0]),
GFP_KERNEL);
if (!helpers)
return -ENOMEM;
}
ret = prepare_ext_ctrls(hdl, cs, helpers, vdev, true);
cs->error_idx = cs->count;
for (i = 0; !ret && i < cs->count; i++)
if (helpers[i].ref->ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY)
ret = -EACCES;
for (i = 0; !ret && i < cs->count; i++) {
struct v4l2_ctrl *master;
bool is_volatile = false;
u32 idx = i;
if (!helpers[i].mref)
continue;
master = helpers[i].mref->ctrl;
cs->error_idx = i;
v4l2_ctrl_lock(master);
if (!is_default && !is_request &&
((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;
}
do {
struct v4l2_ctrl_ref *ref = helpers[idx].ref;
if (is_default)
ret = def_to_user(cs->controls + idx, ref->ctrl);
else if (is_request && ref->p_req_array_enomem)
ret = -ENOMEM;
else if (is_request && ref->p_req_valid)
ret = req_to_user(cs->controls + idx, ref);
else if (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;
}
int v4l2_g_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct video_device *vdev,
struct media_device *mdev, struct v4l2_ext_controls *cs)
{
if (cs->which == V4L2_CTRL_WHICH_REQUEST_VAL)
return v4l2_g_ext_ctrls_request(hdl, vdev, mdev, cs);
return v4l2_g_ext_ctrls_common(hdl, cs, vdev);
}
EXPORT_SYMBOL(v4l2_g_ext_ctrls);
static int validate_new(const struct v4l2_ctrl *ctrl, union v4l2_ctrl_ptr p_new)
{
return ctrl->type_ops->validate(ctrl, p_new);
}
static int validate_ctrls(struct v4l2_ext_controls *cs,
struct v4l2_ctrl_helper *helpers,
struct video_device *vdev,
bool set)
{
unsigned int i;
int ret = 0;
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;
}
if (set && (ctrl->flags & V4L2_CTRL_FLAG_GRABBED)) {
dprintk(vdev,
"control id 0x%x is grabbed, cannot set\n",
ctrl->id);
return -EBUSY;
}
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;
}
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;
unsigned int i, j;
int ret;
cs->error_idx = cs->count;
if (cs->which == V4L2_CTRL_WHICH_DEF_VAL) {
dprintk(vdev, "%s: cannot change default value\n",
video_device_node_name(vdev));
return -EINVAL;
}
cs->which = V4L2_CTRL_ID2WHICH(cs->which);
if (!hdl) {
dprintk(vdev, "%s: invalid null control handler\n",
video_device_node_name(vdev));
return -EINVAL;
}
if (cs->count == 0)
return class_check(hdl, cs->which);
if (cs->count > ARRAY_SIZE(helper)) {
helpers = kvmalloc_array(cs->count, sizeof(helper[0]),
GFP_KERNEL);
if (!helpers)
return -ENOMEM;
}
ret = prepare_ext_ctrls(hdl, cs, helpers, vdev, false);
if (!ret)
ret = validate_ctrls(cs, helpers, vdev, set);
if (ret && set)
cs->error_idx = cs->count;
for (i = 0; !ret && i < cs->count; i++) {
struct v4l2_ctrl *master;
u32 idx = i;
if (!helpers[i].mref)
continue;
cs->error_idx = i;
master = helpers[i].mref->ctrl;
v4l2_ctrl_lock(master);
for (j = 0; j < master->ncontrols; j++)
if (master->cluster[j])
master->cluster[j]->is_new = 0;
if (master->is_auto && master->has_volatiles &&
!is_cur_manual(master)) {
s32 new_auto_val = master->manual_mode_value + 1;
u32 tmp_idx = idx;
do {
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 (new_auto_val == master->manual_mode_value)
update_from_auto_cluster(master);
}
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);
}
}
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;
}
static int try_set_ext_ctrls(struct v4l2_fh *fh,
struct v4l2_ctrl_handler *hdl,
struct video_device *vdev,
struct media_device *mdev,
struct v4l2_ext_controls *cs, bool set)
{
int ret;
if (cs->which == V4L2_CTRL_WHICH_REQUEST_VAL)
return try_set_ext_ctrls_request(fh, hdl, vdev, mdev, cs, set);
ret = try_set_ext_ctrls_common(fh, hdl, cs, vdev, set);
if (ret)
dprintk(vdev,
"%s: try_set_ext_ctrls_common failed (%d)\n",
video_device_node_name(vdev), ret);
return ret;
}
int v4l2_try_ext_ctrls(struct v4l2_ctrl_handler *hdl,
struct video_device *vdev,
struct media_device *mdev,
struct v4l2_ext_controls *cs)
{
return try_set_ext_ctrls(NULL, hdl, vdev, mdev, cs, false);
}
EXPORT_SYMBOL(v4l2_try_ext_ctrls);
int v4l2_s_ext_ctrls(struct v4l2_fh *fh,
struct v4l2_ctrl_handler *hdl,
struct video_device *vdev,
struct media_device *mdev,
struct v4l2_ext_controls *cs)
{
return try_set_ext_ctrls(fh, hdl, vdev, mdev, cs, true);
}
EXPORT_SYMBOL(v4l2_s_ext_ctrls);
static int get_ctrl(struct v4l2_ctrl *ctrl, struct v4l2_ext_control *c)
{
struct v4l2_ctrl *master = ctrl->cluster[0];
int ret = 0;
int i;
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);
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);
new_to_user(c, ctrl);
} else {
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);
control->value = c.value;
return ret;
}
EXPORT_SYMBOL(v4l2_g_ctrl);
static int set_ctrl(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, u32 ch_flags)
{
struct v4l2_ctrl *master = ctrl->cluster[0];
int ret;
int i;
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;
if (master->is_auto && master->has_volatiles && ctrl == master &&
!is_cur_manual(master) && ctrl->val == master->manual_mode_value)
update_from_auto_cluster(master);
ctrl->is_new = 1;
return try_or_set_cluster(fh, master, true, ch_flags);
}
static int set_ctrl_lock(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl,
struct v4l2_ext_control *c)
{
int ret;
v4l2_ctrl_lock(ctrl);
user_to_new(c, ctrl);
ret = set_ctrl(fh, ctrl, 0);
if (!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;
c.value = control->value;
ret = set_ctrl_lock(fh, ctrl, &c);
control->value = c.value;
return ret;
}
EXPORT_SYMBOL(v4l2_s_ctrl);
s32 v4l2_ctrl_g_ctrl(struct v4l2_ctrl *ctrl)
{
struct v4l2_ext_control c;
if (WARN_ON(!ctrl->is_int))
return 0;
c.value = 0;
get_ctrl(ctrl, &c);
return c.value;
}
EXPORT_SYMBOL(v4l2_ctrl_g_ctrl);
s64 v4l2_ctrl_g_ctrl_int64(struct v4l2_ctrl *ctrl)
{
struct v4l2_ext_control c;
if (WARN_ON(ctrl->is_ptr || ctrl->type != V4L2_CTRL_TYPE_INTEGER64))
return 0;
c.value64 = 0;
get_ctrl(ctrl, &c);
return c.value64;
}
EXPORT_SYMBOL(v4l2_ctrl_g_ctrl_int64);
int __v4l2_ctrl_s_ctrl(struct v4l2_ctrl *ctrl, s32 val)
{
lockdep_assert_held(ctrl->handler->lock);
if (WARN_ON(!ctrl->is_int))
return -EINVAL;
ctrl->val = val;
return set_ctrl(NULL, ctrl, 0);
}
EXPORT_SYMBOL(__v4l2_ctrl_s_ctrl);
int __v4l2_ctrl_s_ctrl_int64(struct v4l2_ctrl *ctrl, s64 val)
{
lockdep_assert_held(ctrl->handler->lock);
if (WARN_ON(ctrl->is_ptr || ctrl->type != V4L2_CTRL_TYPE_INTEGER64))
return -EINVAL;
*ctrl->p_new.p_s64 = val;
return set_ctrl(NULL, ctrl, 0);
}
EXPORT_SYMBOL(__v4l2_ctrl_s_ctrl_int64);
int __v4l2_ctrl_s_ctrl_string(struct v4l2_ctrl *ctrl, const char *s)
{
lockdep_assert_held(ctrl->handler->lock);
if (WARN_ON(ctrl->type != V4L2_CTRL_TYPE_STRING))
return -EINVAL;
strscpy(ctrl->p_new.p_char, s, ctrl->maximum + 1);
return set_ctrl(NULL, ctrl, 0);
}
EXPORT_SYMBOL(__v4l2_ctrl_s_ctrl_string);
int __v4l2_ctrl_s_ctrl_compound(struct v4l2_ctrl *ctrl,
enum v4l2_ctrl_type type, const void *p)
{
lockdep_assert_held(ctrl->handler->lock);
if (WARN_ON(ctrl->type != type))
return -EINVAL;
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);
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 (ctrl->type == V4L2_CTRL_TYPE_INTEGER64)
value_changed = *ctrl->p_new.p_s64 != *ctrl->p_cur.p_s64;
else
value_changed = *ctrl->p_new.p_s32 != *ctrl->p_cur.p_s32;
if (value_changed)
ret = set_ctrl(NULL, ctrl, V4L2_EVENT_CTRL_CH_RANGE);
else if (range_changed)
send_event(NULL, ctrl, V4L2_EVENT_CTRL_CH_RANGE);
return ret;
}
EXPORT_SYMBOL(__v4l2_ctrl_modify_range);
int __v4l2_ctrl_modify_dimensions(struct v4l2_ctrl *ctrl,
u32 dims[V4L2_CTRL_MAX_DIMS])
{
unsigned int elems = 1;
unsigned int i;
void *p_array;
lockdep_assert_held(ctrl->handler->lock);
if (!ctrl->is_array || ctrl->is_dyn_array)
return -EINVAL;
for (i = 0; i < ctrl->nr_of_dims; i++)
elems *= dims[i];
if (elems == 0)
return -EINVAL;
p_array = kvzalloc(2 * elems * ctrl->elem_size, GFP_KERNEL);
if (!p_array)
return -ENOMEM;
kvfree(ctrl->p_array);
ctrl->p_array_alloc_elems = elems;
ctrl->elems = elems;
ctrl->new_elems = elems;
ctrl->p_array = p_array;
ctrl->p_new.p = p_array;
ctrl->p_cur.p = p_array + elems * ctrl->elem_size;
for (i = 0; i < ctrl->nr_of_dims; i++)
ctrl->dims[i] = dims[i];
ctrl->type_ops->init(ctrl, 0, ctrl->p_cur);
cur_to_new(ctrl);
send_event(NULL, ctrl, V4L2_EVENT_CTRL_CH_VALUE |
V4L2_EVENT_CTRL_CH_DIMENSIONS);
return 0;
}
EXPORT_SYMBOL(__v4l2_ctrl_modify_dimensions);
int v4l2_query_ext_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_query_ext_ctrl *qc)
{
const unsigned int next_flags = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
u32 id = qc->id & V4L2_CTRL_ID_MASK;
struct v4l2_ctrl_ref *ref;
struct v4l2_ctrl *ctrl;
if (!hdl)
return -EINVAL;
mutex_lock(hdl->lock);
ref = find_ref(hdl, id);
if ((qc->id & next_flags) && !list_empty(&hdl->ctrl_refs)) {
bool is_compound;
unsigned int mask = 1;
bool match = false;
if ((qc->id & next_flags) == V4L2_CTRL_FLAG_NEXT_COMPOUND) {
match = true;
} else if ((qc->id & next_flags) == next_flags) {
mask = 0;
}
if (id >= node2id(hdl->ctrl_refs.prev)) {
ref = NULL;
} else if (ref) {
list_for_each_entry_continue(ref, &hdl->ctrl_refs, node) {
is_compound = ref->ctrl->is_array ||
ref->ctrl->type >= V4L2_CTRL_COMPOUND_TYPES;
if (id < ref->ctrl->id &&
(is_compound & mask) == match)
break;
}
if (&ref->node == &hdl->ctrl_refs)
ref = NULL;
} else {
list_for_each_entry(ref, &hdl->ctrl_refs, node) {
is_compound = ref->ctrl->is_array ||
ref->ctrl->type >= V4L2_CTRL_COMPOUND_TYPES;
if (id < ref->ctrl->id &&
(is_compound & mask) == match)
break;
}
if (&ref->node == &hdl->ctrl_refs)
ref = NULL;
}
}
mutex_unlock(hdl->lock);
if (!ref)
return -EINVAL;
ctrl = ref->ctrl;
memset(qc, 0, sizeof(*qc));
if (id >= V4L2_CID_PRIVATE_BASE)
qc->id = id;
else
qc->id = ctrl->id;
strscpy(qc->name, ctrl->name, sizeof(qc->name));
qc->flags = user_flags(ctrl);
qc->type = ctrl->type;
qc->elem_size = ctrl->elem_size;
qc->elems = ctrl->elems;
qc->nr_of_dims = ctrl->nr_of_dims;
memcpy(qc->dims, ctrl->dims, qc->nr_of_dims * sizeof(qc->dims[0]));
qc->minimum = ctrl->minimum;
qc->maximum = ctrl->maximum;
qc->default_value = ctrl->default_value;
if (ctrl->type == V4L2_CTRL_TYPE_MENU ||
ctrl->type == V4L2_CTRL_TYPE_INTEGER_MENU)
qc->step = 1;
else
qc->step = ctrl->step;
return 0;
}
EXPORT_SYMBOL(v4l2_query_ext_ctrl);
int v4l2_queryctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_queryctrl *qc)
{
struct v4l2_query_ext_ctrl qec = { qc->id };
int rc;
rc = v4l2_query_ext_ctrl(hdl, &qec);
if (rc)
return rc;
qc->id = qec.id;
qc->type = qec.type;
qc->flags = qec.flags;
strscpy(qc->name, qec.name, sizeof(qc->name));
switch (qc->type) {
case V4L2_CTRL_TYPE_INTEGER:
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:
qc->minimum = qec.minimum;
qc->maximum = qec.maximum;
qc->step = qec.step;
qc->default_value = qec.default_value;
break;
default:
qc->minimum = 0;
qc->maximum = 0;
qc->step = 0;
qc->default_value = 0;
break;
}
return 0;
}
EXPORT_SYMBOL(v4l2_queryctrl);
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;
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;
if (ctrl->menu_skip_mask & (1ULL << i))
return -EINVAL;
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);
int v4l2_ctrl_log_status(struct file *file, void *fh)
{
struct video_device *vfd = video_devdata(file);
struct v4l2_fh *vfh = file->private_data;
if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) && vfd->v4l2_dev)
v4l2_ctrl_handler_log_status(vfh->ctrl_handler,
vfd->v4l2_dev->name);
return 0;
}
EXPORT_SYMBOL(v4l2_ctrl_log_status);
int v4l2_ctrl_subdev_log_status(struct v4l2_subdev *sd)
{
v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name);
return 0;
}
EXPORT_SYMBOL(v4l2_ctrl_subdev_log_status);
static int v4l2_ctrl_add_event(struct v4l2_subscribed_event *sev,
unsigned int elems)
{
struct v4l2_ctrl *ctrl = v4l2_ctrl_find(sev->fh->ctrl_handler, sev->id);
if (!ctrl)
return -EINVAL;
v4l2_ctrl_lock(ctrl);
list_add_tail(&sev->node, &ctrl->ev_subs);
if (ctrl->type != V4L2_CTRL_TYPE_CTRL_CLASS &&
(sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL))
send_initial_event(sev->fh, ctrl);
v4l2_ctrl_unlock(ctrl);
return 0;
}
static void v4l2_ctrl_del_event(struct v4l2_subscribed_event *sev)
{
struct v4l2_ctrl *ctrl = v4l2_ctrl_find(sev->fh->ctrl_handler, sev->id);
if (!ctrl)
return;
v4l2_ctrl_lock(ctrl);
list_del(&sev->node);
v4l2_ctrl_unlock(ctrl);
}
void v4l2_ctrl_replace(struct v4l2_event *old, const struct v4l2_event *new)
{
u32 old_changes = old->u.ctrl.changes;
old->u.ctrl = new->u.ctrl;
old->u.ctrl.changes |= old_changes;
}
EXPORT_SYMBOL(v4l2_ctrl_replace);
void v4l2_ctrl_merge(const struct v4l2_event *old, struct v4l2_event *new)
{
new->u.ctrl.changes |= old->u.ctrl.changes;
}
EXPORT_SYMBOL(v4l2_ctrl_merge);
const struct v4l2_subscribed_event_ops v4l2_ctrl_sub_ev_ops = {
.add = v4l2_ctrl_add_event,
.del = v4l2_ctrl_del_event,
.replace = v4l2_ctrl_replace,
.merge = v4l2_ctrl_merge,
};
EXPORT_SYMBOL(v4l2_ctrl_sub_ev_ops);
int v4l2_ctrl_subscribe_event(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
if (sub->type == V4L2_EVENT_CTRL)
return v4l2_event_subscribe(fh, sub, 0, &v4l2_ctrl_sub_ev_ops);
return -EINVAL;
}
EXPORT_SYMBOL(v4l2_ctrl_subscribe_event);
int v4l2_ctrl_subdev_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
if (!sd->ctrl_handler)
return -EINVAL;
return v4l2_ctrl_subscribe_event(fh, sub);
}
EXPORT_SYMBOL(v4l2_ctrl_subdev_subscribe_event);
__poll_t v4l2_ctrl_poll(struct file *file, struct poll_table_struct *wait)
{
struct v4l2_fh *fh = file->private_data;
poll_wait(file, &fh->wait, wait);
if (v4l2_event_pending(fh))
return EPOLLPRI;
return 0;
}
EXPORT_SYMBOL