Skip to content

Commit

Permalink
media: v4l2-ctrls: add support for dynamically allocated arrays.
Browse files Browse the repository at this point in the history
Implement support for dynamically allocated arrays.

Most of the changes concern keeping track of the number of elements
of the array and the number of elements allocated for the array and
reallocating memory if needed.

Acked-by: Benjamin Gaignard <benjamin.gaignard@collabora.com>
Acked-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Tested-by: Benjamin Gaignard <benjamin.gaignard@collabora.com>
Tested-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
  • Loading branch information
Hans Verkuil authored and Mauro Carvalho Chehab committed Jul 15, 2022
1 parent 64fe675 commit fb582cb
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 71 deletions.
103 changes: 78 additions & 25 deletions drivers/media/v4l2-core/v4l2-ctrls-api.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,62 +97,75 @@ static int def_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
return ptr_to_user(c, ctrl, ctrl->p_new);
}

/* Helper function: copy the caller-provider value to the given control value */
static int user_to_ptr(struct v4l2_ext_control *c,
struct v4l2_ctrl *ctrl,
union v4l2_ctrl_ptr ptr)
/* Helper function: copy the caller-provider value as the new control value */
static int user_to_new(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{
int ret;
u32 size;

ctrl->is_new = 1;
ctrl->is_new = 0;
if (ctrl->is_dyn_array &&
c->size > ctrl->p_dyn_alloc_elems * ctrl->elem_size) {
void *old = ctrl->p_dyn;
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_dyn = tmp;
ctrl->p_dyn_alloc_elems = c->size / ctrl->elem_size;
kvfree(old);
}

if (ctrl->is_ptr && !ctrl->is_string) {
unsigned int elems = c->size / ctrl->elem_size;
unsigned int idx;

ret = copy_from_user(ptr.p, c->ptr, c->size) ? -EFAULT : 0;
if (ret || !ctrl->is_array)
return ret;
for (idx = c->size / ctrl->elem_size; idx < ctrl->elems; idx++)
ctrl->type_ops->init(ctrl, idx, ptr);
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)
for (idx = elems; idx < ctrl->elems; idx++)
ctrl->type_ops->init(ctrl, idx, ctrl->p_new);
return 0;
}

switch (ctrl->type) {
case V4L2_CTRL_TYPE_INTEGER64:
*ptr.p_s64 = c->value64;
*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(ptr.p_char, c->string, size) ? -EFAULT : 0;
ret = copy_from_user(ctrl->p_new.p_char, c->string, size) ? -EFAULT : 0;
if (!ret) {
char last = ptr.p_char[size - 1];
char last = ctrl->p_new.p_char[size - 1];

ptr.p_char[size - 1] = 0;
ctrl->p_new.p_char[size - 1] = 0;
/*
* If the string was longer than ctrl->maximum,
* then return an error.
*/
if (strlen(ptr.p_char) == ctrl->maximum && last)
if (strlen(ctrl->p_new.p_char) == ctrl->maximum && last)
return -ERANGE;
}
return ret;
default:
*ptr.p_s32 = c->value;
*ctrl->p_new.p_s32 = c->value;
break;
}
ctrl->is_new = 1;
return 0;
}

/* Helper function: copy the caller-provider value as the new control value */
static int user_to_new(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
{
return user_to_ptr(c, ctrl, ctrl->p_new);
}

/*
* VIDIOC_G/TRY/S_EXT_CTRLS implementation
*/
Expand Down Expand Up @@ -254,7 +267,31 @@ static int prepare_ext_ctrls(struct v4l2_ctrl_handler *hdl,
have_clusters = true;
if (ctrl->cluster[0] != ctrl)
ref = find_ref_lock(hdl, ctrl->cluster[0]->id);
if (ctrl->is_ptr && !ctrl->is_string) {
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) {
Expand Down Expand Up @@ -346,7 +383,7 @@ static int class_check(struct v4l2_ctrl_handler *hdl, u32 which)
*
* 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 valid_p_req is true for all controls.
* 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,
Expand Down Expand Up @@ -430,7 +467,9 @@ int v4l2_g_ext_ctrls_common(struct v4l2_ctrl_handler *hdl,

if (is_default)
ret = def_to_user(cs->controls + idx, ref->ctrl);
else if (is_request && ref->valid_p_req)
else if (is_request && ref->p_req_dyn_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);
Expand All @@ -457,6 +496,17 @@ int v4l2_g_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct video_device *vdev,
}
EXPORT_SYMBOL(v4l2_g_ext_ctrls);

/* Validate a new control */
static int validate_new(const struct v4l2_ctrl *ctrl, union v4l2_ctrl_ptr p_new)
{
unsigned int idx;
int err = 0;

for (idx = 0; !err && idx < ctrl->new_elems; idx++)
err = ctrl->type_ops->validate(ctrl, idx, p_new);
return err;
}

/* Validate controls. */
static int validate_ctrls(struct v4l2_ext_controls *cs,
struct v4l2_ctrl_helper *helpers,
Expand Down Expand Up @@ -872,6 +922,9 @@ int __v4l2_ctrl_s_ctrl_compound(struct v4l2_ctrl *ctrl,
/* 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);
}
Expand Down
Loading

0 comments on commit fb582cb

Please sign in to comment.