[1/2,v6] uvcvideo: send a control event when a Control Change interrupt arrives
Commit Message
UVC defines a method of handling asynchronous controls, which sends a
USB packet over the interrupt pipe. This patch implements support for
such packets by sending a control event to the user. Since this can
involve USB traffic and, therefore, scheduling, this has to be done
in a work queue.
Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@intel.com>
---
drivers/media/usb/uvc/uvc_ctrl.c | 166 +++++++++++++++++++++++++++++++++----
drivers/media/usb/uvc/uvc_status.c | 111 ++++++++++++++++++++++---
drivers/media/usb/uvc/uvc_v4l2.c | 4 +-
drivers/media/usb/uvc/uvcvideo.h | 15 +++-
include/uapi/linux/uvcvideo.h | 2 +
5 files changed, 269 insertions(+), 29 deletions(-)
Comments
Sorry, forgot to mention a change from the previous version: now
autoupdate Control Change events are also delivered.
Thanks
Guennadi
On Wed, 13 Dec 2017, Guennadi Liakhovetski wrote:
> UVC defines a method of handling asynchronous controls, which sends a
> USB packet over the interrupt pipe. This patch implements support for
> such packets by sending a control event to the user. Since this can
> involve USB traffic and, therefore, scheduling, this has to be done
> in a work queue.
>
> Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@intel.com>
> ---
> drivers/media/usb/uvc/uvc_ctrl.c | 166 +++++++++++++++++++++++++++++++++----
> drivers/media/usb/uvc/uvc_status.c | 111 ++++++++++++++++++++++---
> drivers/media/usb/uvc/uvc_v4l2.c | 4 +-
> drivers/media/usb/uvc/uvcvideo.h | 15 +++-
> include/uapi/linux/uvcvideo.h | 2 +
> 5 files changed, 269 insertions(+), 29 deletions(-)
>
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index 20397ab..2a592c2 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -20,6 +20,7 @@
> #include <linux/videodev2.h>
> #include <linux/vmalloc.h>
> #include <linux/wait.h>
> +#include <linux/workqueue.h>
> #include <linux/atomic.h>
> #include <media/v4l2-ctrls.h>
>
> @@ -1222,30 +1223,134 @@ static void uvc_ctrl_send_event(struct uvc_fh *handle,
> {
> struct v4l2_subscribed_event *sev;
> struct v4l2_event ev;
> + bool autoupdate;
>
> if (list_empty(&mapping->ev_subs))
> return;
>
> + if (!handle) {
> + autoupdate = true;
> + sev = list_first_entry(&mapping->ev_subs,
> + struct v4l2_subscribed_event, node);
> + handle = container_of(sev->fh, struct uvc_fh, vfh);
> + } else {
> + autoupdate = false;
> + }
> +
> uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping, value, changes);
>
> list_for_each_entry(sev, &mapping->ev_subs, node) {
> if (sev->fh && (sev->fh != &handle->vfh ||
> (sev->flags & V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK) ||
> - (changes & V4L2_EVENT_CTRL_CH_FLAGS)))
> + (changes & V4L2_EVENT_CTRL_CH_FLAGS) || autoupdate))
> v4l2_event_queue_fh(sev->fh, &ev);
> }
> }
>
> -static void uvc_ctrl_send_slave_event(struct uvc_fh *handle,
> - struct uvc_control *master, u32 slave_id,
> - const struct v4l2_ext_control *xctrls, unsigned int xctrls_count)
> +static void __uvc_ctrl_send_slave_event(struct uvc_fh *handle,
> + struct uvc_control *master, u32 slave_id)
> {
> struct uvc_control_mapping *mapping = NULL;
> struct uvc_control *ctrl = NULL;
> u32 changes = V4L2_EVENT_CTRL_CH_FLAGS;
> - unsigned int i;
> s32 val = 0;
>
> + __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0);
> + if (ctrl == NULL)
> + return;
> +
> + if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0)
> + changes |= V4L2_EVENT_CTRL_CH_VALUE;
> +
> + uvc_ctrl_send_event(handle, ctrl, mapping, val, changes);
> +}
> +
> +static void uvc_ctrl_status_event_work(struct work_struct *work)
> +{
> + struct uvc_device *dev = container_of(work, struct uvc_device,
> + async_ctrl.work);
> + struct uvc_video_chain *chain;
> + struct uvc_ctrl_work *w = &dev->async_ctrl;
> + struct uvc_control_mapping *mapping;
> + struct uvc_control *ctrl;
> + struct uvc_fh *handle;
> + __u8 *data;
> + unsigned int i;
> +
> + spin_lock_irq(&w->lock);
> + data = w->data;
> + w->data = NULL;
> + chain = w->chain;
> + ctrl = w->ctrl;
> + handle = ctrl->handle;
> + ctrl->handle = NULL;
> + spin_unlock_irq(&w->lock);
> +
> + if (mutex_lock_interruptible(&chain->ctrl_mutex))
> + goto free;
> +
> + list_for_each_entry(mapping, &ctrl->info.mappings, list) {
> + s32 value = mapping->get(mapping, UVC_GET_CUR, data);
> +
> + for (i = 0; i < ARRAY_SIZE(mapping->slave_ids); ++i) {
> + if (!mapping->slave_ids[i])
> + break;
> +
> + __uvc_ctrl_send_slave_event(handle, ctrl,
> + mapping->slave_ids[i]);
> + }
> +
> + if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
> + struct uvc_menu_info *menu = mapping->menu_info;
> + unsigned int i;
> +
> + for (i = 0; i < mapping->menu_count; ++i, ++menu)
> + if (menu->value == value) {
> + value = i;
> + break;
> + }
> + }
> +
> + uvc_ctrl_send_event(handle, ctrl, mapping, value,
> + V4L2_EVENT_CTRL_CH_VALUE);
> + }
> +
> + mutex_unlock(&chain->ctrl_mutex);
> +
> +free:
> + kfree(data);
> +}
> +
> +void uvc_ctrl_status_event(struct uvc_video_chain *chain,
> + struct uvc_control *ctrl, __u8 *data, size_t len)
> +{
> + struct uvc_device *dev = chain->dev;
> + struct uvc_ctrl_work *w = &dev->async_ctrl;
> +
> + if (list_empty(&ctrl->info.mappings))
> + return;
> +
> + spin_lock(&w->lock);
> + if (w->data)
> + /* A previous event work hasn't run yet, we lose 1 event */
> + kfree(w->data);
> +
> + w->data = kmalloc(len, GFP_ATOMIC);
> + if (w->data) {
> + memcpy(w->data, data, len);
> + w->chain = chain;
> + w->ctrl = ctrl;
> + schedule_work(&w->work);
> + }
> + spin_unlock(&w->lock);
> +}
> +
> +static void uvc_ctrl_send_slave_event(struct uvc_fh *handle,
> + struct uvc_control *master, u32 slave_id,
> + const struct v4l2_ext_control *xctrls, unsigned int xctrls_count)
> +{
> + unsigned int i;
> +
> /*
> * We can skip sending an event for the slave if the slave
> * is being modified in the same transaction.
> @@ -1255,14 +1360,7 @@ static void uvc_ctrl_send_slave_event(struct uvc_fh *handle,
> return;
> }
>
> - __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0);
> - if (ctrl == NULL)
> - return;
> -
> - if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0)
> - changes |= V4L2_EVENT_CTRL_CH_VALUE;
> -
> - uvc_ctrl_send_event(handle, ctrl, mapping, val, changes);
> + __uvc_ctrl_send_slave_event(handle, master, slave_id);
> }
>
> static void uvc_ctrl_send_events(struct uvc_fh *handle,
> @@ -1277,6 +1375,10 @@ static void uvc_ctrl_send_events(struct uvc_fh *handle,
> for (i = 0; i < xctrls_count; ++i) {
> ctrl = uvc_find_control(handle->chain, xctrls[i].id, &mapping);
>
> + if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
> + /* Notification will be sent from an Interrupt event */
> + continue;
> +
> for (j = 0; j < ARRAY_SIZE(mapping->slave_ids); ++j) {
> if (!mapping->slave_ids[j])
> break;
> @@ -1472,9 +1574,10 @@ int uvc_ctrl_get(struct uvc_video_chain *chain,
> return __uvc_ctrl_get(chain, ctrl, mapping, &xctrl->value);
> }
>
> -int uvc_ctrl_set(struct uvc_video_chain *chain,
> +int uvc_ctrl_set(struct uvc_fh *handle,
> struct v4l2_ext_control *xctrl)
> {
> + struct uvc_video_chain *chain = handle->chain;
> struct uvc_control *ctrl;
> struct uvc_control_mapping *mapping;
> s32 value;
> @@ -1488,6 +1591,25 @@ int uvc_ctrl_set(struct uvc_video_chain *chain,
> return -EINVAL;
> if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR))
> return -EACCES;
> + if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) {
> + if (ctrl->handle)
> + /*
> + * We have already sent this control to the camera
> + * recently and are currently waiting for a completion
> + * notification. The camera might already have completed
> + * its processing and is ready to accept a new control
> + * or it's still busy processing. If we send a new
> + * instance of this control now, in the former case the
> + * camera will process this one too and we'll get
> + * completions for both, but we will only deliver an
> + * event for one of them back to the user. In the latter
> + * case the camera will reply with a STALL. It's easier
> + * and more reliable to return an error now and let the
> + * user retry.
> + */
> + return -EBUSY;
> + ctrl->handle = handle;
> + }
>
> /* Clamp out of range values. */
> switch (mapping->v4l2_type) {
> @@ -1676,7 +1798,9 @@ static int uvc_ctrl_fill_xu_info(struct uvc_device *dev,
> | (data[0] & UVC_CONTROL_CAP_SET ?
> UVC_CTRL_FLAG_SET_CUR : 0)
> | (data[0] & UVC_CONTROL_CAP_AUTOUPDATE ?
> - UVC_CTRL_FLAG_AUTO_UPDATE : 0);
> + UVC_CTRL_FLAG_AUTO_UPDATE : 0)
> + | (data[0] & UVC_CONTROL_CAP_ASYNCHRONOUS ?
> + UVC_CTRL_FLAG_ASYNCHRONOUS : 0);
>
> uvc_ctrl_fixup_xu_info(dev, ctrl, info);
>
> @@ -2131,6 +2255,13 @@ static void uvc_ctrl_init_ctrl(struct uvc_device *dev, struct uvc_control *ctrl)
> if (!ctrl->initialized)
> return;
>
> + /* Temporarily abuse DATA_CURRENT buffer to avoid 1 byte allocation */
> + if (!uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id,
> + dev->intfnum, info->selector,
> + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), 1) &&
> + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT)[0] & 0x10)
> + ctrl->info.flags |= UVC_CTRL_FLAG_ASYNCHRONOUS;
> +
> for (; mapping < mend; ++mapping) {
> if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
> ctrl->info.selector == mapping->selector)
> @@ -2146,6 +2277,9 @@ int uvc_ctrl_init_device(struct uvc_device *dev)
> struct uvc_entity *entity;
> unsigned int i;
>
> + spin_lock_init(&dev->async_ctrl.lock);
> + INIT_WORK(&dev->async_ctrl.work, uvc_ctrl_status_event_work);
> +
> /* Walk the entities list and instantiate controls */
> list_for_each_entry(entity, &dev->entities, list) {
> struct uvc_control *ctrl;
> @@ -2214,6 +2348,8 @@ void uvc_ctrl_cleanup_device(struct uvc_device *dev)
> struct uvc_entity *entity;
> unsigned int i;
>
> + cancel_work_sync(&dev->async_ctrl.work);
> +
> /* Free controls and control mappings for all entities. */
> list_for_each_entry(entity, &dev->entities, list) {
> for (i = 0; i < entity->ncontrols; ++i) {
> diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c
> index 1ef20e7..440a7be 100644
> --- a/drivers/media/usb/uvc/uvc_status.c
> +++ b/drivers/media/usb/uvc/uvc_status.c
> @@ -78,7 +78,24 @@ static void uvc_input_report_key(struct uvc_device *dev, unsigned int code,
> /* --------------------------------------------------------------------------
> * Status interrupt endpoint
> */
> -static void uvc_event_streaming(struct uvc_device *dev, __u8 *data, int len)
> +struct uvc_streaming_status {
> + __u8 bStatusType;
> + __u8 bOriginator;
> + __u8 bEvent;
> + __u8 bValue[];
> +} __packed;
> +
> +struct uvc_control_status {
> + __u8 bStatusType;
> + __u8 bOriginator;
> + __u8 bEvent;
> + __u8 bSelector;
> + __u8 bAttribute;
> + __u8 bValue[];
> +} __packed;
> +
> +static void uvc_event_streaming(struct uvc_device *dev,
> + struct uvc_streaming_status *status, int len)
> {
> if (len < 3) {
> uvc_trace(UVC_TRACE_STATUS, "Invalid streaming status event "
> @@ -86,31 +103,101 @@ static void uvc_event_streaming(struct uvc_device *dev, __u8 *data, int len)
> return;
> }
>
> - if (data[2] == 0) {
> + if (status->bEvent == 0) {
> if (len < 4)
> return;
> uvc_trace(UVC_TRACE_STATUS, "Button (intf %u) %s len %d\n",
> - data[1], data[3] ? "pressed" : "released", len);
> - uvc_input_report_key(dev, KEY_CAMERA, data[3]);
> + status->bOriginator,
> + status->bValue[0] ? "pressed" : "released", len);
> + uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]);
> } else {
> uvc_trace(UVC_TRACE_STATUS,
> "Stream %u error event %02x len %d.\n",
> - data[1], data[2], len);
> + status->bOriginator, status->bEvent, len);
> }
> }
>
> -static void uvc_event_control(struct uvc_device *dev, __u8 *data, int len)
> +#define UVC_CTRL_VALUE_CHANGE 0
> +#define UVC_CTRL_INFO_CHANGE 1
> +#define UVC_CTRL_FAILURE_CHANGE 2
> +#define UVC_CTRL_MIN_CHANGE 3
> +#define UVC_CTRL_MAX_CHANGE 4
> +
> +static struct uvc_control *uvc_event_entity_ctrl(struct uvc_entity *entity,
> + __u8 selector)
> +{
> + struct uvc_control *ctrl;
> + unsigned int i;
> +
> + for (i = 0, ctrl = entity->controls; i < entity->ncontrols; i++, ctrl++)
> + if (ctrl->info.selector == selector)
> + return ctrl;
> +
> + return NULL;
> +}
> +
> +static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
> + struct uvc_control_status *status,
> + struct uvc_video_chain **chain)
> +{
> + list_for_each_entry((*chain), &dev->chains, list) {
> + struct uvc_entity *entity;
> + struct uvc_control *ctrl;
> +
> + list_for_each_entry(entity, &(*chain)->entities, chain) {
> + if (entity->id == status->bOriginator) {
> + ctrl = uvc_event_entity_ctrl(entity,
> + status->bSelector);
> + /*
> + * Some buggy cameras send asynchronous Control
> + * Change events for control, other than the
> + * ones, that had been changed, even though the
> + * AutoUpdate flag isn't set for the control.
> + */
> + if (ctrl && (!ctrl->handle ||
> + ctrl->handle->chain == *chain))
> + return ctrl;
> + }
> + }
> + }
> +
> + return NULL;
> +}
> +
> +static void uvc_event_control(struct uvc_device *dev,
> + struct uvc_control_status *status, int len)
> {
> - char *attrs[3] = { "value", "info", "failure" };
> + struct uvc_video_chain *chain;
> + struct uvc_control *ctrl;
> + char *attrs[] = { "value", "info", "failure", "min", "max" };
>
> - if (len < 6 || data[2] != 0 || data[4] > 2) {
> + if (len < 6 || status->bEvent != 0 ||
> + status->bAttribute >= ARRAY_SIZE(attrs)) {
> uvc_trace(UVC_TRACE_STATUS, "Invalid control status event "
> "received.\n");
> return;
> }
>
> uvc_trace(UVC_TRACE_STATUS, "Control %u/%u %s change len %d.\n",
> - data[1], data[3], attrs[data[4]], len);
> + status->bOriginator, status->bSelector,
> + attrs[status->bAttribute], len);
> +
> + /* Find the control. */
> + ctrl = uvc_event_find_ctrl(dev, status, &chain);
> + if (!ctrl)
> + return;
> +
> + switch (status->bAttribute) {
> + case UVC_CTRL_VALUE_CHANGE:
> + uvc_ctrl_status_event(chain, ctrl, status->bValue, len -
> + offsetof(struct uvc_control_status, bValue));
> + break;
> + case UVC_CTRL_INFO_CHANGE:
> + case UVC_CTRL_FAILURE_CHANGE:
> + case UVC_CTRL_MIN_CHANGE:
> + case UVC_CTRL_MAX_CHANGE:
> + break;
> + }
> }
>
> static void uvc_status_complete(struct urb *urb)
> @@ -139,11 +226,13 @@ static void uvc_status_complete(struct urb *urb)
> if (len > 0) {
> switch (dev->status[0] & 0x0f) {
> case UVC_STATUS_TYPE_CONTROL:
> - uvc_event_control(dev, dev->status, len);
> + uvc_event_control(dev,
> + (struct uvc_control_status *)dev->status, len);
> break;
>
> case UVC_STATUS_TYPE_STREAMING:
> - uvc_event_streaming(dev, dev->status, len);
> + uvc_event_streaming(dev,
> + (struct uvc_streaming_status *)dev->status, len);
> break;
>
> default:
> diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
> index 5e03239..40863eb 100644
> --- a/drivers/media/usb/uvc/uvc_v4l2.c
> +++ b/drivers/media/usb/uvc/uvc_v4l2.c
> @@ -966,7 +966,7 @@ static int uvc_ioctl_s_ctrl(struct file *file, void *fh,
> if (ret < 0)
> return ret;
>
> - ret = uvc_ctrl_set(chain, &xctrl);
> + ret = uvc_ctrl_set(handle, &xctrl);
> if (ret < 0) {
> uvc_ctrl_rollback(handle);
> return ret;
> @@ -1041,7 +1041,7 @@ static int uvc_ioctl_s_try_ext_ctrls(struct uvc_fh *handle,
> return ret;
>
> for (i = 0; i < ctrls->count; ++ctrl, ++i) {
> - ret = uvc_ctrl_set(chain, ctrl);
> + ret = uvc_ctrl_set(handle, ctrl);
> if (ret < 0) {
> uvc_ctrl_rollback(handle);
> ctrls->error_idx = commit ? ctrls->count : i;
> diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
> index b65e86b..3fa089f 100644
> --- a/drivers/media/usb/uvc/uvcvideo.h
> +++ b/drivers/media/usb/uvc/uvcvideo.h
> @@ -11,6 +11,7 @@
> #include <linux/usb/video.h>
> #include <linux/uvcvideo.h>
> #include <linux/videodev2.h>
> +#include <linux/workqueue.h>
> #include <media/media-device.h>
> #include <media/v4l2-device.h>
> #include <media/v4l2-event.h>
> @@ -255,6 +256,8 @@ struct uvc_control {
> initialized:1;
>
> __u8 *uvc_data;
> +
> + struct uvc_fh *handle; /* Used for asynchronous event delivery */
> };
>
> struct uvc_format_desc {
> @@ -599,6 +602,14 @@ struct uvc_device {
> __u8 *status;
> struct input_dev *input;
> char input_phys[64];
> +
> + struct uvc_ctrl_work {
> + struct work_struct work;
> + struct uvc_video_chain *chain;
> + struct uvc_control *ctrl;
> + spinlock_t lock;
> + void *data;
> + } async_ctrl;
> };
>
> enum uvc_handle_state {
> @@ -754,6 +765,8 @@ extern int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
> extern int uvc_ctrl_init_device(struct uvc_device *dev);
> extern void uvc_ctrl_cleanup_device(struct uvc_device *dev);
> extern int uvc_ctrl_restore_values(struct uvc_device *dev);
> +extern void uvc_ctrl_status_event(struct uvc_video_chain *chain,
> + struct uvc_control *ctrl, __u8 *data, size_t len);
>
> extern int uvc_ctrl_begin(struct uvc_video_chain *chain);
> extern int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
> @@ -772,7 +785,7 @@ static inline int uvc_ctrl_rollback(struct uvc_fh *handle)
>
> extern int uvc_ctrl_get(struct uvc_video_chain *chain,
> struct v4l2_ext_control *xctrl);
> -extern int uvc_ctrl_set(struct uvc_video_chain *chain,
> +extern int uvc_ctrl_set(struct uvc_fh *handle,
> struct v4l2_ext_control *xctrl);
>
> extern int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
> diff --git a/include/uapi/linux/uvcvideo.h b/include/uapi/linux/uvcvideo.h
> index 8381ca7c..ee0a5ec 100644
> --- a/include/uapi/linux/uvcvideo.h
> +++ b/include/uapi/linux/uvcvideo.h
> @@ -27,6 +27,8 @@
> #define UVC_CTRL_FLAG_RESTORE (1 << 6)
> /* Control can be updated by the camera. */
> #define UVC_CTRL_FLAG_AUTO_UPDATE (1 << 7)
> +/* Control supports asynchronous reporting */
> +#define UVC_CTRL_FLAG_ASYNCHRONOUS (1 << 8)
>
> #define UVC_CTRL_FLAG_GET_RANGE \
> (UVC_CTRL_FLAG_GET_CUR | UVC_CTRL_FLAG_GET_MIN | \
> --
> 1.9.3
>
@@ -20,6 +20,7 @@
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
+#include <linux/workqueue.h>
#include <linux/atomic.h>
#include <media/v4l2-ctrls.h>
@@ -1222,30 +1223,134 @@ static void uvc_ctrl_send_event(struct uvc_fh *handle,
{
struct v4l2_subscribed_event *sev;
struct v4l2_event ev;
+ bool autoupdate;
if (list_empty(&mapping->ev_subs))
return;
+ if (!handle) {
+ autoupdate = true;
+ sev = list_first_entry(&mapping->ev_subs,
+ struct v4l2_subscribed_event, node);
+ handle = container_of(sev->fh, struct uvc_fh, vfh);
+ } else {
+ autoupdate = false;
+ }
+
uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping, value, changes);
list_for_each_entry(sev, &mapping->ev_subs, node) {
if (sev->fh && (sev->fh != &handle->vfh ||
(sev->flags & V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK) ||
- (changes & V4L2_EVENT_CTRL_CH_FLAGS)))
+ (changes & V4L2_EVENT_CTRL_CH_FLAGS) || autoupdate))
v4l2_event_queue_fh(sev->fh, &ev);
}
}
-static void uvc_ctrl_send_slave_event(struct uvc_fh *handle,
- struct uvc_control *master, u32 slave_id,
- const struct v4l2_ext_control *xctrls, unsigned int xctrls_count)
+static void __uvc_ctrl_send_slave_event(struct uvc_fh *handle,
+ struct uvc_control *master, u32 slave_id)
{
struct uvc_control_mapping *mapping = NULL;
struct uvc_control *ctrl = NULL;
u32 changes = V4L2_EVENT_CTRL_CH_FLAGS;
- unsigned int i;
s32 val = 0;
+ __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0);
+ if (ctrl == NULL)
+ return;
+
+ if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0)
+ changes |= V4L2_EVENT_CTRL_CH_VALUE;
+
+ uvc_ctrl_send_event(handle, ctrl, mapping, val, changes);
+}
+
+static void uvc_ctrl_status_event_work(struct work_struct *work)
+{
+ struct uvc_device *dev = container_of(work, struct uvc_device,
+ async_ctrl.work);
+ struct uvc_video_chain *chain;
+ struct uvc_ctrl_work *w = &dev->async_ctrl;
+ struct uvc_control_mapping *mapping;
+ struct uvc_control *ctrl;
+ struct uvc_fh *handle;
+ __u8 *data;
+ unsigned int i;
+
+ spin_lock_irq(&w->lock);
+ data = w->data;
+ w->data = NULL;
+ chain = w->chain;
+ ctrl = w->ctrl;
+ handle = ctrl->handle;
+ ctrl->handle = NULL;
+ spin_unlock_irq(&w->lock);
+
+ if (mutex_lock_interruptible(&chain->ctrl_mutex))
+ goto free;
+
+ list_for_each_entry(mapping, &ctrl->info.mappings, list) {
+ s32 value = mapping->get(mapping, UVC_GET_CUR, data);
+
+ for (i = 0; i < ARRAY_SIZE(mapping->slave_ids); ++i) {
+ if (!mapping->slave_ids[i])
+ break;
+
+ __uvc_ctrl_send_slave_event(handle, ctrl,
+ mapping->slave_ids[i]);
+ }
+
+ if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
+ struct uvc_menu_info *menu = mapping->menu_info;
+ unsigned int i;
+
+ for (i = 0; i < mapping->menu_count; ++i, ++menu)
+ if (menu->value == value) {
+ value = i;
+ break;
+ }
+ }
+
+ uvc_ctrl_send_event(handle, ctrl, mapping, value,
+ V4L2_EVENT_CTRL_CH_VALUE);
+ }
+
+ mutex_unlock(&chain->ctrl_mutex);
+
+free:
+ kfree(data);
+}
+
+void uvc_ctrl_status_event(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl, __u8 *data, size_t len)
+{
+ struct uvc_device *dev = chain->dev;
+ struct uvc_ctrl_work *w = &dev->async_ctrl;
+
+ if (list_empty(&ctrl->info.mappings))
+ return;
+
+ spin_lock(&w->lock);
+ if (w->data)
+ /* A previous event work hasn't run yet, we lose 1 event */
+ kfree(w->data);
+
+ w->data = kmalloc(len, GFP_ATOMIC);
+ if (w->data) {
+ memcpy(w->data, data, len);
+ w->chain = chain;
+ w->ctrl = ctrl;
+ schedule_work(&w->work);
+ }
+ spin_unlock(&w->lock);
+}
+
+static void uvc_ctrl_send_slave_event(struct uvc_fh *handle,
+ struct uvc_control *master, u32 slave_id,
+ const struct v4l2_ext_control *xctrls, unsigned int xctrls_count)
+{
+ unsigned int i;
+
/*
* We can skip sending an event for the slave if the slave
* is being modified in the same transaction.
@@ -1255,14 +1360,7 @@ static void uvc_ctrl_send_slave_event(struct uvc_fh *handle,
return;
}
- __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0);
- if (ctrl == NULL)
- return;
-
- if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0)
- changes |= V4L2_EVENT_CTRL_CH_VALUE;
-
- uvc_ctrl_send_event(handle, ctrl, mapping, val, changes);
+ __uvc_ctrl_send_slave_event(handle, master, slave_id);
}
static void uvc_ctrl_send_events(struct uvc_fh *handle,
@@ -1277,6 +1375,10 @@ static void uvc_ctrl_send_events(struct uvc_fh *handle,
for (i = 0; i < xctrls_count; ++i) {
ctrl = uvc_find_control(handle->chain, xctrls[i].id, &mapping);
+ if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
+ /* Notification will be sent from an Interrupt event */
+ continue;
+
for (j = 0; j < ARRAY_SIZE(mapping->slave_ids); ++j) {
if (!mapping->slave_ids[j])
break;
@@ -1472,9 +1574,10 @@ int uvc_ctrl_get(struct uvc_video_chain *chain,
return __uvc_ctrl_get(chain, ctrl, mapping, &xctrl->value);
}
-int uvc_ctrl_set(struct uvc_video_chain *chain,
+int uvc_ctrl_set(struct uvc_fh *handle,
struct v4l2_ext_control *xctrl)
{
+ struct uvc_video_chain *chain = handle->chain;
struct uvc_control *ctrl;
struct uvc_control_mapping *mapping;
s32 value;
@@ -1488,6 +1591,25 @@ int uvc_ctrl_set(struct uvc_video_chain *chain,
return -EINVAL;
if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR))
return -EACCES;
+ if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) {
+ if (ctrl->handle)
+ /*
+ * We have already sent this control to the camera
+ * recently and are currently waiting for a completion
+ * notification. The camera might already have completed
+ * its processing and is ready to accept a new control
+ * or it's still busy processing. If we send a new
+ * instance of this control now, in the former case the
+ * camera will process this one too and we'll get
+ * completions for both, but we will only deliver an
+ * event for one of them back to the user. In the latter
+ * case the camera will reply with a STALL. It's easier
+ * and more reliable to return an error now and let the
+ * user retry.
+ */
+ return -EBUSY;
+ ctrl->handle = handle;
+ }
/* Clamp out of range values. */
switch (mapping->v4l2_type) {
@@ -1676,7 +1798,9 @@ static int uvc_ctrl_fill_xu_info(struct uvc_device *dev,
| (data[0] & UVC_CONTROL_CAP_SET ?
UVC_CTRL_FLAG_SET_CUR : 0)
| (data[0] & UVC_CONTROL_CAP_AUTOUPDATE ?
- UVC_CTRL_FLAG_AUTO_UPDATE : 0);
+ UVC_CTRL_FLAG_AUTO_UPDATE : 0)
+ | (data[0] & UVC_CONTROL_CAP_ASYNCHRONOUS ?
+ UVC_CTRL_FLAG_ASYNCHRONOUS : 0);
uvc_ctrl_fixup_xu_info(dev, ctrl, info);
@@ -2131,6 +2255,13 @@ static void uvc_ctrl_init_ctrl(struct uvc_device *dev, struct uvc_control *ctrl)
if (!ctrl->initialized)
return;
+ /* Temporarily abuse DATA_CURRENT buffer to avoid 1 byte allocation */
+ if (!uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id,
+ dev->intfnum, info->selector,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), 1) &&
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT)[0] & 0x10)
+ ctrl->info.flags |= UVC_CTRL_FLAG_ASYNCHRONOUS;
+
for (; mapping < mend; ++mapping) {
if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
ctrl->info.selector == mapping->selector)
@@ -2146,6 +2277,9 @@ int uvc_ctrl_init_device(struct uvc_device *dev)
struct uvc_entity *entity;
unsigned int i;
+ spin_lock_init(&dev->async_ctrl.lock);
+ INIT_WORK(&dev->async_ctrl.work, uvc_ctrl_status_event_work);
+
/* Walk the entities list and instantiate controls */
list_for_each_entry(entity, &dev->entities, list) {
struct uvc_control *ctrl;
@@ -2214,6 +2348,8 @@ void uvc_ctrl_cleanup_device(struct uvc_device *dev)
struct uvc_entity *entity;
unsigned int i;
+ cancel_work_sync(&dev->async_ctrl.work);
+
/* Free controls and control mappings for all entities. */
list_for_each_entry(entity, &dev->entities, list) {
for (i = 0; i < entity->ncontrols; ++i) {
@@ -78,7 +78,24 @@ static void uvc_input_report_key(struct uvc_device *dev, unsigned int code,
/* --------------------------------------------------------------------------
* Status interrupt endpoint
*/
-static void uvc_event_streaming(struct uvc_device *dev, __u8 *data, int len)
+struct uvc_streaming_status {
+ __u8 bStatusType;
+ __u8 bOriginator;
+ __u8 bEvent;
+ __u8 bValue[];
+} __packed;
+
+struct uvc_control_status {
+ __u8 bStatusType;
+ __u8 bOriginator;
+ __u8 bEvent;
+ __u8 bSelector;
+ __u8 bAttribute;
+ __u8 bValue[];
+} __packed;
+
+static void uvc_event_streaming(struct uvc_device *dev,
+ struct uvc_streaming_status *status, int len)
{
if (len < 3) {
uvc_trace(UVC_TRACE_STATUS, "Invalid streaming status event "
@@ -86,31 +103,101 @@ static void uvc_event_streaming(struct uvc_device *dev, __u8 *data, int len)
return;
}
- if (data[2] == 0) {
+ if (status->bEvent == 0) {
if (len < 4)
return;
uvc_trace(UVC_TRACE_STATUS, "Button (intf %u) %s len %d\n",
- data[1], data[3] ? "pressed" : "released", len);
- uvc_input_report_key(dev, KEY_CAMERA, data[3]);
+ status->bOriginator,
+ status->bValue[0] ? "pressed" : "released", len);
+ uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]);
} else {
uvc_trace(UVC_TRACE_STATUS,
"Stream %u error event %02x len %d.\n",
- data[1], data[2], len);
+ status->bOriginator, status->bEvent, len);
}
}
-static void uvc_event_control(struct uvc_device *dev, __u8 *data, int len)
+#define UVC_CTRL_VALUE_CHANGE 0
+#define UVC_CTRL_INFO_CHANGE 1
+#define UVC_CTRL_FAILURE_CHANGE 2
+#define UVC_CTRL_MIN_CHANGE 3
+#define UVC_CTRL_MAX_CHANGE 4
+
+static struct uvc_control *uvc_event_entity_ctrl(struct uvc_entity *entity,
+ __u8 selector)
+{
+ struct uvc_control *ctrl;
+ unsigned int i;
+
+ for (i = 0, ctrl = entity->controls; i < entity->ncontrols; i++, ctrl++)
+ if (ctrl->info.selector == selector)
+ return ctrl;
+
+ return NULL;
+}
+
+static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
+ struct uvc_control_status *status,
+ struct uvc_video_chain **chain)
+{
+ list_for_each_entry((*chain), &dev->chains, list) {
+ struct uvc_entity *entity;
+ struct uvc_control *ctrl;
+
+ list_for_each_entry(entity, &(*chain)->entities, chain) {
+ if (entity->id == status->bOriginator) {
+ ctrl = uvc_event_entity_ctrl(entity,
+ status->bSelector);
+ /*
+ * Some buggy cameras send asynchronous Control
+ * Change events for control, other than the
+ * ones, that had been changed, even though the
+ * AutoUpdate flag isn't set for the control.
+ */
+ if (ctrl && (!ctrl->handle ||
+ ctrl->handle->chain == *chain))
+ return ctrl;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static void uvc_event_control(struct uvc_device *dev,
+ struct uvc_control_status *status, int len)
{
- char *attrs[3] = { "value", "info", "failure" };
+ struct uvc_video_chain *chain;
+ struct uvc_control *ctrl;
+ char *attrs[] = { "value", "info", "failure", "min", "max" };
- if (len < 6 || data[2] != 0 || data[4] > 2) {
+ if (len < 6 || status->bEvent != 0 ||
+ status->bAttribute >= ARRAY_SIZE(attrs)) {
uvc_trace(UVC_TRACE_STATUS, "Invalid control status event "
"received.\n");
return;
}
uvc_trace(UVC_TRACE_STATUS, "Control %u/%u %s change len %d.\n",
- data[1], data[3], attrs[data[4]], len);
+ status->bOriginator, status->bSelector,
+ attrs[status->bAttribute], len);
+
+ /* Find the control. */
+ ctrl = uvc_event_find_ctrl(dev, status, &chain);
+ if (!ctrl)
+ return;
+
+ switch (status->bAttribute) {
+ case UVC_CTRL_VALUE_CHANGE:
+ uvc_ctrl_status_event(chain, ctrl, status->bValue, len -
+ offsetof(struct uvc_control_status, bValue));
+ break;
+ case UVC_CTRL_INFO_CHANGE:
+ case UVC_CTRL_FAILURE_CHANGE:
+ case UVC_CTRL_MIN_CHANGE:
+ case UVC_CTRL_MAX_CHANGE:
+ break;
+ }
}
static void uvc_status_complete(struct urb *urb)
@@ -139,11 +226,13 @@ static void uvc_status_complete(struct urb *urb)
if (len > 0) {
switch (dev->status[0] & 0x0f) {
case UVC_STATUS_TYPE_CONTROL:
- uvc_event_control(dev, dev->status, len);
+ uvc_event_control(dev,
+ (struct uvc_control_status *)dev->status, len);
break;
case UVC_STATUS_TYPE_STREAMING:
- uvc_event_streaming(dev, dev->status, len);
+ uvc_event_streaming(dev,
+ (struct uvc_streaming_status *)dev->status, len);
break;
default:
@@ -966,7 +966,7 @@ static int uvc_ioctl_s_ctrl(struct file *file, void *fh,
if (ret < 0)
return ret;
- ret = uvc_ctrl_set(chain, &xctrl);
+ ret = uvc_ctrl_set(handle, &xctrl);
if (ret < 0) {
uvc_ctrl_rollback(handle);
return ret;
@@ -1041,7 +1041,7 @@ static int uvc_ioctl_s_try_ext_ctrls(struct uvc_fh *handle,
return ret;
for (i = 0; i < ctrls->count; ++ctrl, ++i) {
- ret = uvc_ctrl_set(chain, ctrl);
+ ret = uvc_ctrl_set(handle, ctrl);
if (ret < 0) {
uvc_ctrl_rollback(handle);
ctrls->error_idx = commit ? ctrls->count : i;
@@ -11,6 +11,7 @@
#include <linux/usb/video.h>
#include <linux/uvcvideo.h>
#include <linux/videodev2.h>
+#include <linux/workqueue.h>
#include <media/media-device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
@@ -255,6 +256,8 @@ struct uvc_control {
initialized:1;
__u8 *uvc_data;
+
+ struct uvc_fh *handle; /* Used for asynchronous event delivery */
};
struct uvc_format_desc {
@@ -599,6 +602,14 @@ struct uvc_device {
__u8 *status;
struct input_dev *input;
char input_phys[64];
+
+ struct uvc_ctrl_work {
+ struct work_struct work;
+ struct uvc_video_chain *chain;
+ struct uvc_control *ctrl;
+ spinlock_t lock;
+ void *data;
+ } async_ctrl;
};
enum uvc_handle_state {
@@ -754,6 +765,8 @@ extern int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
extern int uvc_ctrl_init_device(struct uvc_device *dev);
extern void uvc_ctrl_cleanup_device(struct uvc_device *dev);
extern int uvc_ctrl_restore_values(struct uvc_device *dev);
+extern void uvc_ctrl_status_event(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl, __u8 *data, size_t len);
extern int uvc_ctrl_begin(struct uvc_video_chain *chain);
extern int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
@@ -772,7 +785,7 @@ static inline int uvc_ctrl_rollback(struct uvc_fh *handle)
extern int uvc_ctrl_get(struct uvc_video_chain *chain,
struct v4l2_ext_control *xctrl);
-extern int uvc_ctrl_set(struct uvc_video_chain *chain,
+extern int uvc_ctrl_set(struct uvc_fh *handle,
struct v4l2_ext_control *xctrl);
extern int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
@@ -27,6 +27,8 @@
#define UVC_CTRL_FLAG_RESTORE (1 << 6)
/* Control can be updated by the camera. */
#define UVC_CTRL_FLAG_AUTO_UPDATE (1 << 7)
+/* Control supports asynchronous reporting */
+#define UVC_CTRL_FLAG_ASYNCHRONOUS (1 << 8)
#define UVC_CTRL_FLAG_GET_RANGE \
(UVC_CTRL_FLAG_GET_CUR | UVC_CTRL_FLAG_GET_MIN | \