blob: 7d098f287fd95916d2e38347f6f340921745df44 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* V4L2 controls framework Request API implementation.
*
* Copyright (C) 2018-2021 Hans Verkuil <hverkuil-cisco@xs4all.nl>
*/
#define pr_fmt(fmt) "v4l2-ctrls: " fmt
#include <linux/export.h>
#include <linux/slab.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-ioctl.h>
#include "v4l2-ctrls-priv.h"
/* Initialize the request-related fields in a control handler */
void v4l2_ctrl_handler_init_request(struct v4l2_ctrl_handler *hdl)
{
INIT_LIST_HEAD(&hdl->requests);
INIT_LIST_HEAD(&hdl->requests_queued);
hdl->request_is_queued = false;
media_request_object_init(&hdl->req_obj);
}
/* Free the request-related fields in a control handler */
void v4l2_ctrl_handler_free_request(struct v4l2_ctrl_handler *hdl)
{
struct v4l2_ctrl_handler *req, *next_req;
/*
* Do nothing if this isn't the main handler or the main
* handler is not used in any request.
*
* The main handler can be identified by having a NULL ops pointer in
* the request object.
*/
if (hdl->req_obj.ops || list_empty(&hdl->requests))
return;
/*
* If the main handler is freed and it is used by handler objects in
* outstanding requests, then unbind and put those objects before
* freeing the main handler.
*/
list_for_each_entry_safe(req, next_req, &hdl->requests, requests) {
media_request_object_unbind(&req->req_obj);
media_request_object_put(&req->req_obj);
}
}
static int v4l2_ctrl_request_clone(struct v4l2_ctrl_handler *hdl,
const struct v4l2_ctrl_handler *from)
{
struct v4l2_ctrl_ref *ref;
int err = 0;
if (WARN_ON(!hdl || hdl == from))
return -EINVAL;
if (hdl->error)
return hdl->error;
WARN_ON(hdl->lock != &hdl->_lock);
mutex_lock(from->lock);
list_for_each_entry(ref, &from->ctrl_refs, node) {
struct v4l2_ctrl *ctrl = ref->ctrl;
struct v4l2_ctrl_ref *new_ref;
/* Skip refs inherited from other devices */
if (ref->from_other_dev)
continue;
err = handler_new_ref(hdl, ctrl, &new_ref, false, true);
if (err)
break;
}
mutex_unlock(from->lock);
return err;
}
static void v4l2_ctrl_request_queue(struct media_request_object *obj)
{
struct v4l2_ctrl_handler *hdl =
container_of(obj, struct v4l2_ctrl_handler, req_obj);
struct v4l2_ctrl_handler *main_hdl = obj->priv;
mutex_lock(main_hdl->lock);
list_add_tail(&hdl->requests_queued, &main_hdl->requests_queued);
hdl->request_is_queued = true;
mutex_unlock(main_hdl->lock);
}
static void v4l2_ctrl_request_unbind(struct media_request_object *obj)
{
struct v4l2_ctrl_handler *hdl =
container_of(obj, struct v4l2_ctrl_handler, req_obj);
struct v4l2_ctrl_handler *main_hdl = obj->priv;
mutex_lock(main_hdl->lock);
list_del_init(&hdl->requests);
if (hdl->request_is_queued) {
list_del_init(&hdl->requests_queued);
hdl->request_is_queued = false;
}
mutex_unlock(main_hdl->lock);
}
static void v4l2_ctrl_request_release(struct media_request_object *obj)
{
struct v4l2_ctrl_handler *hdl =
container_of(obj, struct v4l2_ctrl_handler, req_obj);
v4l2_ctrl_handler_free(hdl);
kfree(hdl);
}
static const struct media_request_object_ops req_ops = {
.queue = v4l2_ctrl_request_queue,
.unbind = v4l2_ctrl_request_unbind,
.release = v4l2_ctrl_request_release,
};
struct v4l2_ctrl_handler *v4l2_ctrl_request_hdl_find(struct media_request *req,
struct v4l2_ctrl_handler *parent)
{
struct media_request_object *obj;
if (WARN_ON(req->state != MEDIA_REQUEST_STATE_VALIDATING &&
req->state != MEDIA_REQUEST_STATE_QUEUED))
return NULL;
obj = media_request_object_find(req, &req_ops, parent);
if (obj)
return container_of(obj, struct v4l2_ctrl_handler, req_obj);
return NULL;
}
EXPORT_SYMBOL_GPL(v4l2_ctrl_request_hdl_find);
struct v4l2_ctrl *
v4l2_ctrl_request_hdl_ctrl_find(struct v4l2_ctrl_handler *hdl, u32 id)
{
struct v4l2_ctrl_ref *ref = find_ref_lock(hdl, id);
return (ref && ref->valid_p_req) ? ref->ctrl : NULL;
}
EXPORT_SYMBOL_GPL(v4l2_ctrl_request_hdl_ctrl_find);
static int v4l2_ctrl_request_bind(struct media_request *req,
struct v4l2_ctrl_handler *hdl,
struct v4l2_ctrl_handler *from)
{
int ret;
ret = v4l2_ctrl_request_clone(hdl, from);
if (!ret) {
ret = media_request_object_bind(req, &req_ops,
from, false, &hdl->req_obj);
if (!ret) {
mutex_lock(from->lock);
list_add_tail(&hdl->requests, &from->requests);
mutex_unlock(from->lock);
}
}
return ret;
}
static struct media_request_object *
v4l2_ctrls_find_req_obj(struct v4l2_ctrl_handler *hdl,
struct media_request *req, bool set)
{
struct media_request_object *obj;
struct v4l2_ctrl_handler *new_hdl;
int ret;
if (IS_ERR(req))
return ERR_CAST(req);
if (set && WARN_ON(req->state != MEDIA_REQUEST_STATE_UPDATING))
return ERR_PTR(-EBUSY);
obj = media_request_object_find(req, &req_ops, hdl);
if (obj)
return obj;
/*
* If there are no controls in this completed request,
* then that can only happen if:
*
* 1) no controls were present in the queued request, and
* 2) v4l2_ctrl_request_complete() could not allocate a
* control handler object to store the completed state in.
*
* So return ENOMEM to indicate that there was an out-of-memory
* error.
*/
if (!set)
return ERR_PTR(-ENOMEM);
new_hdl = kzalloc(sizeof(*new_hdl), GFP_KERNEL);
if (!new_hdl)
return ERR_PTR(-ENOMEM);
obj = &new_hdl->req_obj;
ret = v4l2_ctrl_handler_init(new_hdl, (hdl->nr_of_buckets - 1) * 8);
if (!ret)
ret = v4l2_ctrl_request_bind(req, new_hdl, hdl);
if (ret) {
v4l2_ctrl_handler_free(new_hdl);
kfree(new_hdl);
return ERR_PTR(ret);
}
media_request_object_get(obj);
return obj;
}
int v4l2_g_ext_ctrls_request(struct v4l2_ctrl_handler *hdl, struct video_device *vdev,
struct media_device *mdev, struct v4l2_ext_controls *cs)
{
struct media_request_object *obj = NULL;
struct media_request *req = NULL;
int ret;
if (!mdev || cs->request_fd < 0)
return -EINVAL;
req = media_request_get_by_fd(mdev, cs->request_fd);
if (IS_ERR(req))
return PTR_ERR(req);
if (req->state != MEDIA_REQUEST_STATE_COMPLETE) {
media_request_put(req);
return -EACCES;
}
ret = media_request_lock_for_access(req);
if (ret) {
media_request_put(req);
return ret;
}
obj = v4l2_ctrls_find_req_obj(hdl, req, false);
if (IS_ERR(obj)) {
media_request_unlock_for_access(req);
media_request_put(req);
return PTR_ERR(obj);
}
hdl = container_of(obj, struct v4l2_ctrl_handler,
req_obj);
ret = v4l2_g_ext_ctrls_common(hdl, cs, vdev);
media_request_unlock_for_access(req);
media_request_object_put(obj);
media_request_put(req);
return ret;
}
int try_set_ext_ctrls_request(struct v4l2_fh *fh,
struct v4l2_ctrl_handler *hdl,
struct video_device *vdev,
struct media_device *mdev,
struct v4l2_ext_controls *cs, bool set)
{
struct media_request_object *obj = NULL;
struct media_request *req = NULL;
int ret;
if (!mdev) {
dprintk(vdev, "%s: missing media device\n",
video_device_node_name(vdev));
return -EINVAL;
}
if (cs->request_fd < 0) {
dprintk(vdev, "%s: invalid request fd %d\n",
video_device_node_name(vdev), cs->request_fd);
return -EINVAL;
}
req = media_request_get_by_fd(mdev, cs->request_fd);
if (IS_ERR(req)) {
dprintk(vdev, "%s: cannot find request fd %d\n",
video_device_node_name(vdev), cs->request_fd);
return PTR_ERR(req);
}
ret = media_request_lock_for_update(req);
if (ret) {
dprintk(vdev, "%s: cannot lock request fd %d\n",
video_device_node_name(vdev), cs->request_fd);
media_request_put(req);
return ret;
}
obj = v4l2_ctrls_find_req_obj(hdl, req, set);
if (IS_ERR(obj)) {
dprintk(vdev,
"%s: cannot find request object for request fd %d\n",
video_device_node_name(vdev),
cs->request_fd);
media_request_unlock_for_update(req);
media_request_put(req);
return PTR_ERR(obj);
}
hdl = container_of(obj, struct v4l2_ctrl_handler,
req_obj);
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);
media_request_unlock_for_update(req);
media_request_object_put(obj);
media_request_put(req);
return ret;
}
void v4l2_ctrl_request_complete(struct media_request *req,
struct v4l2_ctrl_handler *main_hdl)
{
struct media_request_object *obj;
struct v4l2_ctrl_handler *hdl;
struct v4l2_ctrl_ref *ref;
if (!req || !main_hdl)
return;
/*
* Note that it is valid if nothing was found. It means
* that this request doesn't have any controls and so just
* wants to leave the controls unchanged.
*/
obj = media_request_object_find(req, &req_ops, main_hdl);
if (!obj) {
int ret;
/* Create a new request so the driver can return controls */
hdl = kzalloc(sizeof(*hdl), GFP_KERNEL);
if (!hdl)
return;
ret = v4l2_ctrl_handler_init(hdl, (main_hdl->nr_of_buckets - 1) * 8);
if (!ret)
ret = v4l2_ctrl_request_bind(req, hdl, main_hdl);
if (ret) {
v4l2_ctrl_handler_free(hdl);
kfree(hdl);
return;
}
hdl->request_is_queued = true;
obj = media_request_object_find(req, &req_ops, main_hdl);
}
hdl = container_of(obj, struct v4l2_ctrl_handler, req_obj);
list_for_each_entry(ref, &hdl->ctrl_refs, node) {
struct v4l2_ctrl *ctrl = ref->ctrl;
struct v4l2_ctrl *master = ctrl->cluster[0];
unsigned int i;
if (ctrl->flags & V4L2_CTRL_FLAG_VOLATILE) {
v4l2_ctrl_lock(master);
/* g_volatile_ctrl will update the current control values */
for (i = 0; i < master->ncontrols; i++)
cur_to_new(master->cluster[i]);
call_op(master, g_volatile_ctrl);
new_to_req(ref);
v4l2_ctrl_unlock(master);
continue;
}
if (ref->valid_p_req)
continue;
/* Copy the current control value into the request */
v4l2_ctrl_lock(ctrl);
cur_to_req(ref);
v4l2_ctrl_unlock(ctrl);
}
mutex_lock(main_hdl->lock);
WARN_ON(!hdl->request_is_queued);
list_del_init(&hdl->requests_queued);
hdl->request_is_queued = false;
mutex_unlock(main_hdl->lock);
media_request_object_complete(obj);
media_request_object_put(obj);
}
EXPORT_SYMBOL(v4l2_ctrl_request_complete);
int v4l2_ctrl_request_setup(struct media_request *req,
struct v4l2_ctrl_handler *main_hdl)
{
struct media_request_object *obj;
struct v4l2_ctrl_handler *hdl;
struct v4l2_ctrl_ref *ref;
int ret = 0;
if (!req || !main_hdl)
return 0;
if (WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED))
return -EBUSY;
/*
* Note that it is valid if nothing was found. It means
* that this request doesn't have any controls and so just
* wants to leave the controls unchanged.
*/
obj = media_request_object_find(req, &req_ops, main_hdl);
if (!obj)
return 0;
if (obj->completed) {
media_request_object_put(obj);
return -EBUSY;
}
hdl = container_of(obj, struct v4l2_ctrl_handler, req_obj);
list_for_each_entry(ref, &hdl->ctrl_refs, node)
ref->req_done = false;
list_for_each_entry(ref, &hdl->ctrl_refs, node) {
struct v4l2_ctrl *ctrl = ref->ctrl;
struct v4l2_ctrl *master = ctrl->cluster[0];
bool have_new_data = false;
int i;
/*
* Skip if this control was already handled by a cluster.
* Skip button controls and read-only controls.
*/
if (ref->req_done || (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY))
continue;
v4l2_ctrl_lock(master);
for (i = 0; i < master->ncontrols; i++) {
if (master->cluster[i]) {
struct v4l2_ctrl_ref *r =
find_ref(hdl, master->cluster[i]->id);
if (r->valid_p_req) {
have_new_data = true;
break;
}
}
}
if (!have_new_data) {
v4l2_ctrl_unlock(master);
continue;
}
for (i = 0; i < master->ncontrols; i++) {
if (master->cluster[i]) {
struct v4l2_ctrl_ref *r =
find_ref(hdl, master->cluster[i]->id);
req_to_new(r);
master->cluster[i]->is_new = 1;
r->req_done = true;
}
}
/*
* 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)) {
s32 new_auto_val = *master->p_new.p_s32;
/*
* 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);
}
ret = try_or_set_cluster(NULL, master, true, 0);
v4l2_ctrl_unlock(master);
if (ret)
break;
}
media_request_object_put(obj);
return ret;
}
EXPORT_SYMBOL(v4l2_ctrl_request_setup);