Signed-off-by: Hans de Goede <hdego...@redhat.com>
---
 drivers/media/video/uvc/uvc_ctrl.c |  114 +++++++++++++++++++++++++++++++++++-
 drivers/media/video/uvc/uvc_v4l2.c |   30 +++++++++-
 drivers/media/video/uvc/uvcvideo.h |   21 +++++--
 3 files changed, 157 insertions(+), 8 deletions(-)

diff --git a/drivers/media/video/uvc/uvc_ctrl.c 
b/drivers/media/video/uvc/uvc_ctrl.c
index cea5e0a..96d05f7 100644
--- a/drivers/media/video/uvc/uvc_ctrl.c
+++ b/drivers/media/video/uvc/uvc_ctrl.c
@@ -21,6 +21,7 @@
 #include <linux/vmalloc.h>
 #include <linux/wait.h>
 #include <linux/atomic.h>
+#include <media/v4l2-ctrls.h>
 
 #include "uvcvideo.h"
 
@@ -1143,10 +1144,12 @@ static int uvc_ctrl_commit_entity(struct uvc_device 
*dev,
        return 0;
 }
 
-int __uvc_ctrl_commit(struct uvc_video_chain *chain, int rollback)
+int __uvc_ctrl_commit(struct uvc_video_chain *chain, int rollback,
+                     struct uvc_fh *handle,
+                     struct v4l2_ext_control *xctrls, int xctrls_count)
 {
        struct uvc_entity *entity;
-       int ret = 0;
+       int i, ret = 0;
 
        /* Find the control. */
        list_for_each_entry(entity, &chain->entities, chain) {
@@ -1155,6 +1158,9 @@ int __uvc_ctrl_commit(struct uvc_video_chain *chain, int 
rollback)
                        goto done;
        }
 
+       if (!rollback)
+               for (i = 0; i < xctrls_count; ++i)
+                       uvc_ctrl_send_event(handle, &xctrls[i]);
 done:
        mutex_unlock(&chain->ctrl_mutex);
        return ret;
@@ -1321,6 +1327,108 @@ int uvc_ctrl_set(struct uvc_video_chain *chain,
 }
 
 /* --------------------------------------------------------------------------
+ * Ctrl event handling
+ */
+
+static void uvc_ctrl_fill_event(struct uvc_video_chain *chain,
+       struct v4l2_event *ev,
+       struct uvc_control *ctrl,
+       struct uvc_control_mapping *mapping,
+       u32 value, u32 changes)
+{
+       struct v4l2_queryctrl v4l2_ctrl;
+
+       __uvc_query_v4l2_ctrl(chain, ctrl, mapping, &v4l2_ctrl);
+
+       memset(ev->reserved, 0, sizeof(ev->reserved));
+       ev->type = V4L2_EVENT_CTRL;
+       ev->id = v4l2_ctrl.id;
+       ev->u.ctrl.value = value;
+       ev->u.ctrl.changes = changes;
+       ev->u.ctrl.type = v4l2_ctrl.type;
+       ev->u.ctrl.flags = v4l2_ctrl.flags;
+       ev->u.ctrl.minimum = v4l2_ctrl.minimum;
+       ev->u.ctrl.maximum = v4l2_ctrl.maximum;
+       ev->u.ctrl.step = v4l2_ctrl.step;
+       ev->u.ctrl.default_value = v4l2_ctrl.default_value;
+}
+
+void uvc_ctrl_send_event(struct uvc_fh *handle,
+       struct v4l2_ext_control *xctrl)
+{
+       struct uvc_control *ctrl;
+       struct uvc_control_mapping *mapping;
+       struct v4l2_subscribed_event *sev;
+       struct v4l2_event ev;
+
+       ctrl = uvc_find_control(handle->chain, xctrl->id, &mapping);
+
+       if (list_empty(&mapping->ev_subs))
+               return;
+
+       uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping, xctrl->value,
+                           V4L2_EVENT_CTRL_CH_VALUE);
+
+       list_for_each_entry(sev, &mapping->ev_subs, node)
+               if (sev->fh &&
+                   (sev->fh != &handle->vfh ||
+                    (sev->flags & V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK)))
+                       v4l2_event_queue_fh(sev->fh, &ev);
+}
+
+static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev)
+{
+       struct uvc_fh *handle = container_of(sev->fh, struct uvc_fh, vfh);
+       struct uvc_control_mapping *mapping;
+       struct uvc_control *ctrl;
+       int ret;
+
+       ret = mutex_lock_interruptible(&handle->chain->ctrl_mutex);
+       if (ret < 0)
+               return -ERESTARTSYS;
+
+       ctrl = uvc_find_control(handle->chain, sev->id, &mapping);
+       if (ctrl == NULL) {
+               ret = -EINVAL;
+               goto done;
+       }
+
+       list_add_tail(&sev->node, &mapping->ev_subs);
+       if (sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL) {
+               struct v4l2_event ev;
+               struct v4l2_ext_control xctrl = { .value = 0 };
+               u32 changes = V4L2_EVENT_CTRL_CH_FLAGS;
+
+               if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &xctrl) == 0)
+                       changes |= V4L2_EVENT_CTRL_CH_VALUE;
+
+               uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping,
+                                   xctrl.value, changes);
+               v4l2_event_queue_fh(sev->fh, &ev);
+       }
+
+done:
+       mutex_unlock(&handle->chain->ctrl_mutex);
+       return ret;
+}
+
+static void uvc_ctrl_del_event(struct v4l2_subscribed_event *sev)
+{
+       struct uvc_fh *handle = container_of(sev->fh, struct uvc_fh, vfh);
+
+       mutex_lock(&handle->chain->ctrl_mutex);
+       list_del(&sev->node);
+       mutex_unlock(&handle->chain->ctrl_mutex);
+}
+
+const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops = {
+       .add = uvc_ctrl_add_event,
+       .del = uvc_ctrl_del_event,
+       .replace = v4l2_ctrl_replace,
+       .merge = v4l2_ctrl_merge,
+};
+
+/* --------------------------------------------------------------------------
  * Dynamic controls
  */
 
@@ -1665,6 +1773,8 @@ static int __uvc_ctrl_add_mapping(struct uvc_device *dev,
        if (map == NULL)
                return -ENOMEM;
 
+       INIT_LIST_HEAD(&map->ev_subs);
+
        size = sizeof(*mapping->menu_info) * mapping->menu_count;
        map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
        if (map->menu_info == NULL) {
diff --git a/drivers/media/video/uvc/uvc_v4l2.c 
b/drivers/media/video/uvc/uvc_v4l2.c
index ff2cddd..2561ad8 100644
--- a/drivers/media/video/uvc/uvc_v4l2.c
+++ b/drivers/media/video/uvc/uvc_v4l2.c
@@ -25,6 +25,7 @@
 #include <linux/atomic.h>
 
 #include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
 #include <media/v4l2-ioctl.h>
 
 #include "uvcvideo.h"
@@ -505,6 +506,8 @@ static int uvc_v4l2_open(struct file *file)
                }
        }
 
+       v4l2_fh_init(&handle->vfh, stream->vdev);
+       v4l2_fh_add(&handle->vfh);
        handle->chain = stream->chain;
        handle->stream = stream;
        handle->state = UVC_HANDLE_PASSIVE;
@@ -528,6 +531,8 @@ static int uvc_v4l2_release(struct file *file)
 
        /* Release the file handle. */
        uvc_dismiss_privileges(handle);
+       v4l2_fh_del(&handle->vfh);
+       v4l2_fh_exit(&handle->vfh);
        kfree(handle);
        file->private_data = NULL;
 
@@ -608,7 +613,7 @@ static long uvc_v4l2_do_ioctl(struct file *file, unsigned 
int cmd, void *arg)
                        uvc_ctrl_rollback(chain);
                        return ret;
                }
-               ret = uvc_ctrl_commit(chain);
+               ret = uvc_ctrl_commit(chain, handle, &xctrl, 1);
                if (ret == 0)
                        ctrl->value = xctrl.value;
                break;
@@ -663,7 +668,8 @@ static long uvc_v4l2_do_ioctl(struct file *file, unsigned 
int cmd, void *arg)
                ctrls->error_idx = 0;
 
                if (cmd == VIDIOC_S_EXT_CTRLS)
-                       ret = uvc_ctrl_commit(chain);
+                       ret = uvc_ctrl_commit(chain, handle,
+                                             ctrls->controls, ctrls->count);
                else
                        ret = uvc_ctrl_rollback(chain);
                break;
@@ -990,6 +996,26 @@ static long uvc_v4l2_do_ioctl(struct file *file, unsigned 
int cmd, void *arg)
                return uvc_video_enable(stream, 0);
        }
 
+       case VIDIOC_SUBSCRIBE_EVENT:
+       {
+               struct v4l2_event_subscription *sub = arg;
+
+               switch (sub->type) {
+               case V4L2_EVENT_CTRL:
+                       return v4l2_event_subscribe(&handle->vfh, sub, 0,
+                                                   &uvc_ctrl_sub_ev_ops);
+               default:
+                       return -EINVAL;
+               }
+       }
+
+       case VIDIOC_UNSUBSCRIBE_EVENT:
+               return v4l2_event_unsubscribe(&handle->vfh, arg);
+
+       case VIDIOC_DQEVENT:
+               return v4l2_event_dequeue(&handle->vfh, arg,
+                                         file->f_flags & O_NONBLOCK);
+
        /* Analog video standards make no sense for digital cameras. */
        case VIDIOC_ENUMSTD:
        case VIDIOC_QUERYSTD:
diff --git a/drivers/media/video/uvc/uvcvideo.h 
b/drivers/media/video/uvc/uvcvideo.h
index 67f88d8..3a89519 100644
--- a/drivers/media/video/uvc/uvcvideo.h
+++ b/drivers/media/video/uvc/uvcvideo.h
@@ -13,6 +13,8 @@
 #include <linux/videodev2.h>
 #include <media/media-device.h>
 #include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
 #include <media/videobuf2-core.h>
 
 /* --------------------------------------------------------------------------
@@ -153,6 +155,7 @@ struct uvc_control_info {
 
 struct uvc_control_mapping {
        struct list_head list;
+       struct list_head ev_subs;
 
        struct uvc_control_info *ctrl;
 
@@ -524,6 +527,7 @@ enum uvc_handle_state {
 };
 
 struct uvc_fh {
+       struct v4l2_fh vfh;
        struct uvc_video_chain *chain;
        struct uvc_streaming *stream;
        enum uvc_handle_state state;
@@ -643,6 +647,8 @@ extern int uvc_status_suspend(struct uvc_device *dev);
 extern int uvc_status_resume(struct uvc_device *dev);
 
 /* Controls */
+extern const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops;
+
 extern int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
                struct v4l2_queryctrl *v4l2_ctrl);
 extern int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
@@ -655,14 +661,18 @@ extern void uvc_ctrl_cleanup_device(struct uvc_device 
*dev);
 extern int uvc_ctrl_resume_device(struct uvc_device *dev);
 
 extern int uvc_ctrl_begin(struct uvc_video_chain *chain);
-extern int __uvc_ctrl_commit(struct uvc_video_chain *chain, int rollback);
-static inline int uvc_ctrl_commit(struct uvc_video_chain *chain)
+extern int __uvc_ctrl_commit(struct uvc_video_chain *chain, int rollback,
+                       struct uvc_fh *handle,
+                       struct v4l2_ext_control *xctrls, int xctrls_count);
+static inline int uvc_ctrl_commit(struct uvc_video_chain *chain,
+                       struct uvc_fh *handle,
+                       struct v4l2_ext_control *xctrls, int xctrls_count)
 {
-       return __uvc_ctrl_commit(chain, 0);
+       return __uvc_ctrl_commit(chain, 0, handle, xctrls, xctrls_count);
 }
 static inline int uvc_ctrl_rollback(struct uvc_video_chain *chain)
 {
-       return __uvc_ctrl_commit(chain, 1);
+       return __uvc_ctrl_commit(chain, 1, NULL, NULL, 0);
 }
 
 extern int uvc_ctrl_get(struct uvc_video_chain *chain,
@@ -670,6 +680,9 @@ extern int uvc_ctrl_get(struct uvc_video_chain *chain,
 extern int uvc_ctrl_set(struct uvc_video_chain *chain,
                struct v4l2_ext_control *xctrl);
 
+extern void uvc_ctrl_send_event(struct uvc_fh *handle,
+               struct v4l2_ext_control *xctrl);
+
 extern int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
                struct uvc_xu_control_query *xqry);
 
-- 
1.7.9.3

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to