summaryrefslogtreecommitdiff
path: root/drivers/media/usb/uvc/uvc_ctrl.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/usb/uvc/uvc_ctrl.c')
-rw-r--r--drivers/media/usb/uvc/uvc_ctrl.c194
1 files changed, 142 insertions, 52 deletions
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index cbf19aa1d823..efe609d70877 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -1483,14 +1483,28 @@ static u32 uvc_get_ctrl_bitmap(struct uvc_control *ctrl,
return ~0;
}
+/*
+ * Maximum retry count to avoid spurious errors with controls. Increasing this
+ * value does no seem to produce better results in the tested hardware.
+ */
+#define MAX_QUERY_RETRIES 2
+
static int __uvc_queryctrl_boundaries(struct uvc_video_chain *chain,
struct uvc_control *ctrl,
struct uvc_control_mapping *mapping,
struct v4l2_query_ext_ctrl *v4l2_ctrl)
{
if (!ctrl->cached) {
- int ret = uvc_ctrl_populate_cache(chain, ctrl);
- if (ret < 0)
+ unsigned int retries;
+ int ret;
+
+ for (retries = 0; retries < MAX_QUERY_RETRIES; retries++) {
+ ret = uvc_ctrl_populate_cache(chain, ctrl);
+ if (ret != -EIO)
+ break;
+ }
+
+ if (ret)
return ret;
}
@@ -1567,6 +1581,7 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
{
struct uvc_control_mapping *master_map = NULL;
struct uvc_control *master_ctrl = NULL;
+ int ret;
memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl));
v4l2_ctrl->id = mapping->id;
@@ -1587,18 +1602,31 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
__uvc_find_control(ctrl->entity, mapping->master_id,
&master_map, &master_ctrl, 0, 0);
if (master_ctrl && (master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) {
+ unsigned int retries;
s32 val;
int ret;
if (WARN_ON(uvc_ctrl_mapping_is_compound(master_map)))
return -EIO;
- ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val);
- if (ret < 0)
- return ret;
+ for (retries = 0; retries < MAX_QUERY_RETRIES; retries++) {
+ ret = __uvc_ctrl_get(chain, master_ctrl, master_map,
+ &val);
+ if (!ret)
+ break;
+ if (ret < 0 && ret != -EIO)
+ return ret;
+ }
- if (val != mapping->master_manual)
- v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE;
+ if (ret == -EIO) {
+ dev_warn_ratelimited(&chain->dev->udev->dev,
+ "UVC non compliance: Error %d querying master control %x (%s)\n",
+ ret, master_map->id,
+ uvc_map_get_name(master_map));
+ } else {
+ if (val != mapping->master_manual)
+ v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE;
+ }
}
v4l2_ctrl->elem_size = uvc_mapping_v4l2_size(mapping);
@@ -1613,7 +1641,18 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
return 0;
}
- return __uvc_queryctrl_boundaries(chain, ctrl, mapping, v4l2_ctrl);
+ ret = __uvc_queryctrl_boundaries(chain, ctrl, mapping, v4l2_ctrl);
+ if (ret && !mapping->disabled) {
+ dev_warn(&chain->dev->udev->dev,
+ "UVC non compliance: permanently disabling control %x (%s), due to error %d\n",
+ mapping->id, uvc_map_get_name(mapping), ret);
+ mapping->disabled = true;
+ }
+
+ if (mapping->disabled)
+ v4l2_ctrl->flags |= V4L2_CTRL_FLAG_DISABLED;
+
+ return 0;
}
int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
@@ -1812,38 +1851,49 @@ static void uvc_ctrl_send_slave_event(struct uvc_video_chain *chain,
uvc_ctrl_send_event(chain, handle, ctrl, mapping, val, changes);
}
-static void uvc_ctrl_set_handle(struct uvc_fh *handle, struct uvc_control *ctrl,
- struct uvc_fh *new_handle)
+static int uvc_ctrl_set_handle(struct uvc_control *ctrl, struct uvc_fh *handle)
{
- lockdep_assert_held(&handle->chain->ctrl_mutex);
+ int ret;
- if (new_handle) {
- if (ctrl->handle)
- dev_warn_ratelimited(&handle->stream->dev->udev->dev,
- "UVC non compliance: Setting an async control with a pending operation.");
+ lockdep_assert_held(&handle->chain->ctrl_mutex);
- if (new_handle == ctrl->handle)
- return;
+ if (ctrl->handle) {
+ dev_warn_ratelimited(&handle->stream->dev->udev->dev,
+ "UVC non compliance: Setting an async control with a pending operation.");
- if (ctrl->handle) {
- WARN_ON(!ctrl->handle->pending_async_ctrls);
- if (ctrl->handle->pending_async_ctrls)
- ctrl->handle->pending_async_ctrls--;
- }
+ if (ctrl->handle == handle)
+ return 0;
- ctrl->handle = new_handle;
- handle->pending_async_ctrls++;
- return;
+ WARN_ON(!ctrl->handle->pending_async_ctrls);
+ if (ctrl->handle->pending_async_ctrls)
+ ctrl->handle->pending_async_ctrls--;
+ ctrl->handle = handle;
+ ctrl->handle->pending_async_ctrls++;
+ return 0;
}
- /* Cannot clear the handle for a control not owned by us.*/
- if (WARN_ON(ctrl->handle != handle))
- return;
+ ret = uvc_pm_get(handle->chain->dev);
+ if (ret)
+ return ret;
+
+ ctrl->handle = handle;
+ ctrl->handle->pending_async_ctrls++;
+ return 0;
+}
+
+static int uvc_ctrl_clear_handle(struct uvc_control *ctrl)
+{
+ lockdep_assert_held(&ctrl->handle->chain->ctrl_mutex);
+ if (WARN_ON(!ctrl->handle->pending_async_ctrls)) {
+ ctrl->handle = NULL;
+ return -EINVAL;
+ }
+
+ ctrl->handle->pending_async_ctrls--;
+ uvc_pm_put(ctrl->handle->chain->dev);
ctrl->handle = NULL;
- if (WARN_ON(!handle->pending_async_ctrls))
- return;
- handle->pending_async_ctrls--;
+ return 0;
}
void uvc_ctrl_status_event(struct uvc_video_chain *chain,
@@ -1860,7 +1910,7 @@ void uvc_ctrl_status_event(struct uvc_video_chain *chain,
handle = ctrl->handle;
if (handle)
- uvc_ctrl_set_handle(handle, ctrl, NULL);
+ uvc_ctrl_clear_handle(ctrl);
list_for_each_entry(mapping, &ctrl->info.mappings, list) {
s32 value;
@@ -1943,7 +1993,9 @@ static bool uvc_ctrl_xctrls_has_control(const struct v4l2_ext_control *xctrls,
}
static void uvc_ctrl_send_events(struct uvc_fh *handle,
- const struct v4l2_ext_control *xctrls, unsigned int xctrls_count)
+ struct uvc_entity *entity,
+ const struct v4l2_ext_control *xctrls,
+ unsigned int xctrls_count)
{
struct uvc_control_mapping *mapping;
struct uvc_control *ctrl;
@@ -1955,6 +2007,9 @@ static void uvc_ctrl_send_events(struct uvc_fh *handle,
s32 value;
ctrl = uvc_find_control(handle->chain, xctrls[i].id, &mapping);
+ if (ctrl->entity != entity)
+ continue;
+
if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
/* Notification will be sent from an Interrupt event. */
continue;
@@ -2017,18 +2072,24 @@ static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems)
goto done;
}
- list_add_tail(&sev->node, &mapping->ev_subs);
if (sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL) {
struct v4l2_event ev;
u32 changes = V4L2_EVENT_CTRL_CH_FLAGS;
s32 val = 0;
+ ret = uvc_pm_get(handle->chain->dev);
+ if (ret)
+ goto done;
+
if (uvc_ctrl_mapping_is_compound(mapping) ||
__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0)
changes |= V4L2_EVENT_CTRL_CH_VALUE;
uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping, val,
changes);
+
+ uvc_pm_put(handle->chain->dev);
+
/*
* Mark the queue as active, allowing this initial event to be
* accepted.
@@ -2037,6 +2098,8 @@ static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems)
v4l2_event_queue_fh(sev->fh, &ev);
}
+ list_add_tail(&sev->node, &mapping->ev_subs);
+
done:
mutex_unlock(&handle->chain->ctrl_mutex);
return ret;
@@ -2090,15 +2153,20 @@ int uvc_ctrl_begin(struct uvc_video_chain *chain)
return mutex_lock_interruptible(&chain->ctrl_mutex) ? -ERESTARTSYS : 0;
}
+/*
+ * Returns the number of uvc controls that have been correctly set, or a
+ * negative number if there has been an error.
+ */
static int uvc_ctrl_commit_entity(struct uvc_device *dev,
struct uvc_fh *handle,
struct uvc_entity *entity,
int rollback,
struct uvc_control **err_ctrl)
{
+ unsigned int processed_ctrls = 0;
struct uvc_control *ctrl;
unsigned int i;
- int ret;
+ int ret = 0;
if (entity == NULL)
return 0;
@@ -2127,8 +2195,9 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev,
dev->intfnum, ctrl->info.selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info.size);
- else
- ret = 0;
+
+ if (!ret)
+ processed_ctrls++;
if (rollback || ret < 0)
memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
@@ -2137,18 +2206,25 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev,
ctrl->dirty = 0;
- if (ret < 0) {
+ if (!rollback && handle && !ret &&
+ ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
+ ret = uvc_ctrl_set_handle(ctrl, handle);
+
+ if (ret < 0 && !rollback) {
if (err_ctrl)
*err_ctrl = ctrl;
- return ret;
+ /*
+ * If we fail to set a control, we need to rollback
+ * the next ones.
+ */
+ rollback = 1;
}
-
- if (!rollback && handle &&
- ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
- uvc_ctrl_set_handle(handle, ctrl, handle);
}
- return 0;
+ if (ret)
+ return ret;
+
+ return processed_ctrls;
}
static int uvc_ctrl_find_ctrl_idx(struct uvc_entity *entity,
@@ -2178,7 +2254,8 @@ int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
struct uvc_video_chain *chain = handle->chain;
struct uvc_control *err_ctrl;
struct uvc_entity *entity;
- int ret = 0;
+ int ret_out = 0;
+ int ret;
/* Find the control. */
list_for_each_entry(entity, &chain->entities, chain) {
@@ -2189,15 +2266,23 @@ int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
ctrls->error_idx =
uvc_ctrl_find_ctrl_idx(entity, ctrls,
err_ctrl);
- goto done;
+ /*
+ * When we fail to commit an entity, we need to
+ * restore the UVC_CTRL_DATA_BACKUP for all the
+ * controls in the other entities, otherwise our cache
+ * and the hardware will be out of sync.
+ */
+ rollback = 1;
+
+ ret_out = ret;
+ } else if (ret > 0 && !rollback) {
+ uvc_ctrl_send_events(handle, entity,
+ ctrls->controls, ctrls->count);
}
}
- if (!rollback)
- uvc_ctrl_send_events(handle, ctrls->controls, ctrls->count);
-done:
mutex_unlock(&chain->ctrl_mutex);
- return ret;
+ return ret_out;
}
static int uvc_mapping_get_xctrl_compound(struct uvc_video_chain *chain,
@@ -3222,6 +3307,7 @@ int uvc_ctrl_init_device(struct uvc_device *dev)
void uvc_ctrl_cleanup_fh(struct uvc_fh *handle)
{
struct uvc_entity *entity;
+ int i;
guard(mutex)(&handle->chain->ctrl_mutex);
@@ -3232,11 +3318,15 @@ void uvc_ctrl_cleanup_fh(struct uvc_fh *handle)
for (unsigned int i = 0; i < entity->ncontrols; ++i) {
if (entity->controls[i].handle != handle)
continue;
- uvc_ctrl_set_handle(handle, &entity->controls[i], NULL);
+ uvc_ctrl_clear_handle(&entity->controls[i]);
}
}
- WARN_ON(handle->pending_async_ctrls);
+ if (!WARN_ON(handle->pending_async_ctrls))
+ return;
+
+ for (i = 0; i < handle->pending_async_ctrls; i++)
+ uvc_pm_put(handle->stream->dev);
}
/*