Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx> --- drivers/media/video/uvc/uvc_ctrl.c | 104 ++++++++++++++++++++++++++++++++++++ drivers/media/video/uvc/uvc_v4l2.c | 51 ++++++++++++++++- drivers/media/video/uvc/uvcvideo.h | 9 +++ 3 files changed, 161 insertions(+), 3 deletions(-) diff --git a/drivers/media/video/uvc/uvc_ctrl.c b/drivers/media/video/uvc/uvc_ctrl.c index 1a2c1a3..b9486e5 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" @@ -1308,6 +1309,107 @@ 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 v4l2_event ev; + struct v4l2_subscribed_event *sev; + struct uvc_control *ctrl; + struct uvc_control_mapping *mapping; + + 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 *ctrl; + struct uvc_control_mapping *mapping; + 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 */ @@ -1652,6 +1754,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 dadf11f..1c577a3 100644 --- a/drivers/media/video/uvc/uvc_v4l2.c +++ b/drivers/media/video/uvc/uvc_v4l2.c @@ -25,6 +25,7 @@ #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> #include "uvcvideo.h" @@ -495,6 +496,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; @@ -521,6 +524,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; @@ -602,8 +607,10 @@ static long uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg) return ret; } ret = uvc_ctrl_commit(chain); - if (ret == 0) + if (ret == 0) { + uvc_ctrl_send_event(handle, &xctrl); ctrl->value = xctrl.value; + } break; } @@ -655,8 +662,14 @@ static long uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg) ctrls->error_idx = 0; - if (cmd == VIDIOC_S_EXT_CTRLS) + if (cmd == VIDIOC_S_EXT_CTRLS) { ret = uvc_ctrl_commit(chain); + if (ret == 0) { + ctrl = ctrls->controls; + for (i = 0; i < ctrls->count; ++ctrl, ++i) + uvc_ctrl_send_event(handle, ctrl); + } + } else ret = uvc_ctrl_rollback(chain); break; @@ -996,6 +1009,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: @@ -1058,10 +1091,22 @@ static unsigned int uvc_v4l2_poll(struct file *file, poll_table *wait) { struct uvc_fh *handle = file->private_data; struct uvc_streaming *stream = handle->stream; + unsigned long req_events = poll_requested_events(wait); + unsigned int res = 0; uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_poll\n"); - return uvc_queue_poll(&stream->queue, file, wait); + if (req_events & POLLPRI) { + if (v4l2_event_pending(&handle->vfh)) + res |= POLLPRI; + else + poll_wait(file, &handle->vfh.wait, wait); + } + + if (req_events & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)) + res |= uvc_queue_poll(&stream->queue, file, wait); + + return res; } #ifndef CONFIG_MMU diff --git a/drivers/media/video/uvc/uvcvideo.h b/drivers/media/video/uvc/uvcvideo.h index 4c1392e..cf3cbe4 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-fh.h> +#include <media/v4l2-event.h> /* -------------------------------------------------------------------------- * UVC constants @@ -151,6 +153,7 @@ struct uvc_control_info { struct uvc_control_mapping { struct list_head list; + struct list_head ev_subs; struct uvc_control_info *ctrl; @@ -455,6 +458,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; @@ -570,6 +574,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, @@ -597,6 +603,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.7 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html