media: uvcvideo: Send a control event when a Control Change interrupt arrives
authorGuennadi Liakhovetski <guennadi.liakhovetski@intel.com>
Thu, 26 Jul 2018 08:17:53 +0000 (04:17 -0400)
committerMauro Carvalho Chehab <mchehab+samsung@kernel.org>
Fri, 27 Jul 2018 10:39:57 +0000 (06:39 -0400)
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>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
drivers/media/usb/uvc/uvc_ctrl.c
drivers/media/usb/uvc/uvc_status.c
drivers/media/usb/uvc/uvc_v4l2.c
drivers/media/usb/uvc/uvcvideo.h
include/uapi/linux/uvcvideo.h

index 12b5be6..c2ad102 100644 (file)
@@ -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>
 
@@ -971,12 +972,30 @@ static int uvc_ctrl_populate_cache(struct uvc_video_chain *chain,
        return 0;
 }
 
+static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping,
+                               const u8 *data)
+{
+       s32 value = mapping->get(mapping, UVC_GET_CUR, data);
+
+       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;
+                       }
+               }
+       }
+
+       return value;
+}
+
 static int __uvc_ctrl_get(struct uvc_video_chain *chain,
        struct uvc_control *ctrl, struct uvc_control_mapping *mapping,
        s32 *value)
 {
-       struct uvc_menu_info *menu;
-       unsigned int i;
        int ret;
 
        if ((ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) == 0)
@@ -993,18 +1012,8 @@ static int __uvc_ctrl_get(struct uvc_video_chain *chain,
                ctrl->loaded = 1;
        }
 
-       *value = mapping->get(mapping, UVC_GET_CUR,
-               uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
-
-       if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
-               menu = mapping->menu_info;
-               for (i = 0; i < mapping->menu_count; ++i, ++menu) {
-                       if (menu->value == *value) {
-                               *value = i;
-                               break;
-                       }
-               }
-       }
+       *value = __uvc_ctrl_get_value(mapping,
+                               uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
 
        return 0;
 }
@@ -1216,53 +1225,135 @@ static void uvc_ctrl_fill_event(struct uvc_video_chain *chain,
        ev->u.ctrl.default_value = v4l2_ctrl.default_value;
 }
 
-static void uvc_ctrl_send_event(struct uvc_fh *handle,
-       struct uvc_control *ctrl, struct uvc_control_mapping *mapping,
-       s32 value, u32 changes)
+/*
+ * Send control change events to all subscribers for the @ctrl control. By
+ * default the subscriber that generated the event, as identified by @handle,
+ * is not notified unless it has set the V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK flag.
+ * @handle can be NULL for asynchronous events related to auto-update controls,
+ * in which case all subscribers are notified.
+ */
+static void uvc_ctrl_send_event(struct uvc_video_chain *chain,
+       struct uvc_fh *handle, struct uvc_control *ctrl,
+       struct uvc_control_mapping *mapping, s32 value, u32 changes)
 {
+       struct v4l2_fh *originator = handle ? &handle->vfh : NULL;
        struct v4l2_subscribed_event *sev;
        struct v4l2_event ev;
 
        if (list_empty(&mapping->ev_subs))
                return;
 
-       uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping, value, changes);
+       uvc_ctrl_fill_event(chain, &ev, ctrl, mapping, value, changes);
 
        list_for_each_entry(sev, &mapping->ev_subs, node) {
-               if (sev->fh != &handle->vfh ||
+               if (sev->fh != originator ||
                    (sev->flags & V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK) ||
                    (changes & V4L2_EVENT_CTRL_CH_FLAGS))
                        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)
+/*
+ * Send control change events for the slave of the @master control identified
+ * by the V4L2 ID @slave_id. The @handle identifies the event subscriber that
+ * generated the event and may be NULL for auto-update events.
+ */
+static void uvc_ctrl_send_slave_event(struct uvc_video_chain *chain,
+       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;
 
-       /*
-        * We can skip sending an event for the slave if the slave
-        * is being modified in the same transaction.
-        */
-       for (i = 0; i < xctrls_count; i++) {
-               if (xctrls[i].id == slave_id)
-                       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)
+       if (__uvc_ctrl_get(chain, ctrl, mapping, &val) == 0)
                changes |= V4L2_EVENT_CTRL_CH_VALUE;
 
-       uvc_ctrl_send_event(handle, ctrl, mapping, val, changes);
+       uvc_ctrl_send_event(chain, 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_ctrl_work *w = &dev->async_ctrl;
+       struct uvc_video_chain *chain = w->chain;
+       struct uvc_control_mapping *mapping;
+       struct uvc_control *ctrl = w->ctrl;
+       struct uvc_fh *handle;
+       unsigned int i;
+       int ret;
+
+       mutex_lock(&chain->ctrl_mutex);
+
+       handle = ctrl->handle;
+       ctrl->handle = NULL;
+
+       list_for_each_entry(mapping, &ctrl->info.mappings, list) {
+               s32 value = __uvc_ctrl_get_value(mapping, w->data);
+
+               /*
+                * handle may be NULL here if the device sends auto-update
+                * events without a prior related control set from userspace.
+                */
+               for (i = 0; i < ARRAY_SIZE(mapping->slave_ids); ++i) {
+                       if (!mapping->slave_ids[i])
+                               break;
+
+                       uvc_ctrl_send_slave_event(chain, handle, ctrl,
+                                                 mapping->slave_ids[i]);
+               }
+
+               uvc_ctrl_send_event(chain, handle, ctrl, mapping, value,
+                                   V4L2_EVENT_CTRL_CH_VALUE);
+       }
+
+       mutex_unlock(&chain->ctrl_mutex);
+
+       /* Resubmit the URB. */
+       w->urb->interval = dev->int_ep->desc.bInterval;
+       ret = usb_submit_urb(w->urb, GFP_KERNEL);
+       if (ret < 0)
+               uvc_printk(KERN_ERR, "Failed to resubmit status URB (%d).\n",
+                          ret);
+}
+
+bool uvc_ctrl_status_event(struct urb *urb, struct uvc_video_chain *chain,
+                          struct uvc_control *ctrl, const u8 *data)
+{
+       struct uvc_device *dev = chain->dev;
+       struct uvc_ctrl_work *w = &dev->async_ctrl;
+
+       if (list_empty(&ctrl->info.mappings)) {
+               ctrl->handle = NULL;
+               return false;
+       }
+
+       w->data = data;
+       w->urb = urb;
+       w->chain = chain;
+       w->ctrl = ctrl;
+
+       schedule_work(&w->work);
+
+       return true;
+}
+
+static bool uvc_ctrl_xctrls_has_control(const struct v4l2_ext_control *xctrls,
+                                       unsigned int xctrls_count, u32 id)
+{
+       unsigned int i;
+
+       for (i = 0; i < xctrls_count; ++i) {
+               if (xctrls[i].id == id)
+                       return true;
+       }
+
+       return false;
 }
 
 static void uvc_ctrl_send_events(struct uvc_fh *handle,
@@ -1277,29 +1368,39 @@ 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])
+                       u32 slave_id = mapping->slave_ids[j];
+
+                       if (!slave_id)
                                break;
-                       uvc_ctrl_send_slave_event(handle, ctrl,
-                                                 mapping->slave_ids[j],
-                                                 xctrls, xctrls_count);
+
+                       /*
+                        * We can skip sending an event for the slave if the
+                        * slave is being modified in the same transaction.
+                        */
+                       if (uvc_ctrl_xctrls_has_control(xctrls, xctrls_count,
+                                                       slave_id))
+                               continue;
+
+                       uvc_ctrl_send_slave_event(handle->chain, handle, ctrl,
+                                                 slave_id);
                }
 
                /*
                 * If the master is being modified in the same transaction
                 * flags may change too.
                 */
-               if (mapping->master_id) {
-                       for (j = 0; j < xctrls_count; j++) {
-                               if (xctrls[j].id == mapping->master_id) {
-                                       changes |= V4L2_EVENT_CTRL_CH_FLAGS;
-                                       break;
-                               }
-                       }
-               }
+               if (mapping->master_id &&
+                   uvc_ctrl_xctrls_has_control(xctrls, xctrls_count,
+                                               mapping->master_id))
+                       changes |= V4L2_EVENT_CTRL_CH_FLAGS;
 
-               uvc_ctrl_send_event(handle, ctrl, mapping, xctrls[i].value,
-                                   changes);
+               uvc_ctrl_send_event(handle->chain, handle, ctrl, mapping,
+                                   xctrls[i].value, changes);
        }
 }
 
@@ -1472,9 +1573,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;
@@ -1581,6 +1683,9 @@ int uvc_ctrl_set(struct uvc_video_chain *chain,
        mapping->set(mapping, value,
                uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
 
+       if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
+               ctrl->handle = handle;
+
        ctrl->dirty = 1;
        ctrl->modified = 1;
        return 0;
@@ -1612,7 +1717,9 @@ static int uvc_ctrl_get_flags(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);
 
        kfree(data);
        return ret;
@@ -2173,6 +2280,8 @@ int uvc_ctrl_init_device(struct uvc_device *dev)
        struct uvc_entity *entity;
        unsigned int i;
 
+       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;
@@ -2241,6 +2350,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) {
index 7b71041..0722dc6 100644 (file)
@@ -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,97 @@ 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_find_ctrl(struct uvc_entity *entity,
+                                                     u8 selector)
 {
-       char *attrs[3] = { "value", "info", "failure" };
+       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;
+}
 
-       if (len < 6 || data[2] != 0 || data[4] > 2) {
+static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
+                                       const 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)
+                               continue;
+
+                       ctrl = uvc_event_entity_find_ctrl(entity,
+                                                         status->bSelector);
+                       if (ctrl)
+                               return ctrl;
+               }
+       }
+
+       return NULL;
+}
+
+static bool uvc_event_control(struct urb *urb,
+                             const struct uvc_control_status *status, int len)
+{
+       static const char *attrs[] = { "value", "info", "failure", "min", "max" };
+       struct uvc_device *dev = urb->context;
+       struct uvc_video_chain *chain;
+       struct uvc_control *ctrl;
+
+       if (len < 6 || status->bEvent != 0 ||
+           status->bAttribute >= ARRAY_SIZE(attrs)) {
                uvc_trace(UVC_TRACE_STATUS, "Invalid control status event "
                                "received.\n");
-               return;
+               return false;
        }
 
        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 false;
+
+       switch (status->bAttribute) {
+       case UVC_CTRL_VALUE_CHANGE:
+               return uvc_ctrl_status_event(urb, chain, ctrl, status->bValue);
+
+       case UVC_CTRL_INFO_CHANGE:
+       case UVC_CTRL_FAILURE_CHANGE:
+       case UVC_CTRL_MIN_CHANGE:
+       case UVC_CTRL_MAX_CHANGE:
+               break;
+       }
+
+       return false;
 }
 
 static void uvc_status_complete(struct urb *urb)
@@ -138,13 +221,23 @@ static void uvc_status_complete(struct urb *urb)
        len = urb->actual_length;
        if (len > 0) {
                switch (dev->status[0] & 0x0f) {
-               case UVC_STATUS_TYPE_CONTROL:
-                       uvc_event_control(dev, dev->status, len);
+               case UVC_STATUS_TYPE_CONTROL: {
+                       struct uvc_control_status *status =
+                               (struct uvc_control_status *)dev->status;
+
+                       if (uvc_event_control(urb, status, len))
+                               /* The URB will be resubmitted in work context. */
+                               return;
                        break;
+               }
 
-               case UVC_STATUS_TYPE_STREAMING:
-                       uvc_event_streaming(dev, dev->status, len);
+               case UVC_STATUS_TYPE_STREAMING: {
+                       struct uvc_streaming_status *status =
+                               (struct uvc_streaming_status *)dev->status;
+
+                       uvc_event_streaming(dev, status, len);
                        break;
+               }
 
                default:
                        uvc_trace(UVC_TRACE_STATUS, "Unknown status event "
index bd32914..18a7384 100644 (file)
@@ -994,7 +994,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;
@@ -1069,7 +1069,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;
index 6b955e0..e5f5d84 100644 (file)
@@ -12,6 +12,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>
@@ -259,6 +260,8 @@ struct uvc_control {
           initialized:1;
 
        u8 *uvc_data;
+
+       struct uvc_fh *handle;  /* File handle that last changed the control. */
 };
 
 struct uvc_format_desc {
@@ -603,6 +606,14 @@ struct uvc_device {
        u8 *status;
        struct input_dev *input;
        char input_phys[64];
+
+       struct uvc_ctrl_work {
+               struct work_struct work;
+               struct urb *urb;
+               struct uvc_video_chain *chain;
+               struct uvc_control *ctrl;
+               const void *data;
+       } async_ctrl;
 };
 
 enum uvc_handle_state {
@@ -756,6 +767,8 @@ int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
 int uvc_ctrl_init_device(struct uvc_device *dev);
 void uvc_ctrl_cleanup_device(struct uvc_device *dev);
 int uvc_ctrl_restore_values(struct uvc_device *dev);
+bool uvc_ctrl_status_event(struct urb *urb, struct uvc_video_chain *chain,
+                          struct uvc_control *ctrl, const u8 *data);
 
 int uvc_ctrl_begin(struct uvc_video_chain *chain);
 int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
@@ -773,7 +786,7 @@ static inline int uvc_ctrl_rollback(struct uvc_fh *handle)
 }
 
 int uvc_ctrl_get(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl);
-int uvc_ctrl_set(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl);
+int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl);
 
 int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
                      struct uvc_xu_control_query *xqry);
index 020714d..f80f05b 100644 (file)
@@ -28,6 +28,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 | \