Whenever a control changes value an event is sent to anyone that subscribed to it. Signed-off-by: Hans Verkuil <hans.verkuil@xxxxxxxxx> --- drivers/media/video/v4l2-ctrls.c | 59 ++++++++++++++++++ drivers/media/video/v4l2-event.c | 126 +++++++++++++++++++++++++++----------- drivers/media/video/v4l2-fh.c | 4 +- include/linux/videodev2.h | 17 +++++- include/media/v4l2-ctrls.h | 9 +++ include/media/v4l2-event.h | 2 + 6 files changed, 177 insertions(+), 40 deletions(-) diff --git a/drivers/media/video/v4l2-ctrls.c b/drivers/media/video/v4l2-ctrls.c index f75a1d4..163f412 100644 --- a/drivers/media/video/v4l2-ctrls.c +++ b/drivers/media/video/v4l2-ctrls.c @@ -23,6 +23,7 @@ #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> #include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> #include <media/v4l2-dev.h> /* Internal temporary helper struct, one for each v4l2_ext_control */ @@ -537,6 +538,16 @@ static bool type_is_int(const struct v4l2_ctrl *ctrl) } } +static void send_event(struct v4l2_ctrl *ctrl, struct v4l2_event *ev) +{ + struct v4l2_ctrl_fh *pos; + + ev->id = ctrl->id; + list_for_each_entry(pos, &ctrl->fhs, node) { + v4l2_event_queue_fh(pos->fh, ev); + } +} + /* Helper function: copy the current control value back to the caller */ static int cur_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl) @@ -626,20 +637,38 @@ static int new_to_user(struct v4l2_ext_control *c, /* Copy the new value to the current value. */ static void new_to_cur(struct v4l2_ctrl *ctrl) { + struct v4l2_event ev; + bool changed = false; + if (ctrl == NULL) return; switch (ctrl->type) { + case V4L2_CTRL_TYPE_BUTTON: + changed = true; + ev.u.ctrl_ch_value.value = 0; + break; case V4L2_CTRL_TYPE_STRING: /* strings are always 0-terminated */ + changed = strcmp(ctrl->string, ctrl->cur.string); strcpy(ctrl->cur.string, ctrl->string); + ev.u.ctrl_ch_value.value64 = 0; break; case V4L2_CTRL_TYPE_INTEGER64: + changed = ctrl->val64 != ctrl->cur.val64; ctrl->cur.val64 = ctrl->val64; + ev.u.ctrl_ch_value.value64 = ctrl->val64; break; default: + changed = ctrl->val != ctrl->cur.val; ctrl->cur.val = ctrl->val; + ev.u.ctrl_ch_value.value = ctrl->val; break; } + if (changed) { + ev.type = V4L2_EVENT_CTRL_CH_VALUE; + ev.u.ctrl_ch_value.type = ctrl->type; + send_event(ctrl, &ev); + } } /* Copy the current value to the new value */ @@ -784,6 +813,7 @@ void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) { struct v4l2_ctrl_ref *ref, *next_ref; struct v4l2_ctrl *ctrl, *next_ctrl; + struct v4l2_ctrl_fh *ctrl_fh, *next_ctrl_fh; if (hdl == NULL || hdl->buckets == NULL) return; @@ -797,6 +827,10 @@ void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) /* Free all controls owned by the handler */ list_for_each_entry_safe(ctrl, next_ctrl, &hdl->ctrls, node) { list_del(&ctrl->node); + list_for_each_entry_safe(ctrl_fh, next_ctrl_fh, &ctrl->fhs, node) { + list_del(&ctrl_fh->node); + kfree(ctrl_fh); + } kfree(ctrl); } kfree(hdl->buckets); @@ -1003,6 +1037,7 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl, } INIT_LIST_HEAD(&ctrl->node); + INIT_LIST_HEAD(&ctrl->fhs); ctrl->handler = hdl; ctrl->ops = ops; ctrl->id = id; @@ -1888,3 +1923,27 @@ int v4l2_ctrl_s_ctrl(struct v4l2_ctrl *ctrl, s32 val) return set_ctrl(ctrl, &val); } EXPORT_SYMBOL(v4l2_ctrl_s_ctrl); + +void v4l2_ctrl_add_fh(struct v4l2_ctrl *ctrl, struct v4l2_ctrl_fh *ctrl_fh) +{ + v4l2_ctrl_lock(ctrl); + list_add_tail(&ctrl_fh->node, &ctrl->fhs); + v4l2_ctrl_unlock(ctrl); +} +EXPORT_SYMBOL(v4l2_ctrl_add_fh); + +void v4l2_ctrl_del_fh(struct v4l2_ctrl *ctrl, struct v4l2_fh *fh) +{ + struct v4l2_ctrl_fh *pos; + + v4l2_ctrl_lock(ctrl); + list_for_each_entry(pos, &ctrl->fhs, node) { + if (pos->fh == fh) { + list_del(&pos->node); + kfree(pos); + break; + } + } + v4l2_ctrl_unlock(ctrl); +} +EXPORT_SYMBOL(v4l2_ctrl_del_fh); diff --git a/drivers/media/video/v4l2-event.c b/drivers/media/video/v4l2-event.c index 69fd343..c9251a5 100644 --- a/drivers/media/video/v4l2-event.c +++ b/drivers/media/video/v4l2-event.c @@ -25,10 +25,13 @@ #include <media/v4l2-dev.h> #include <media/v4l2-fh.h> #include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> #include <linux/sched.h> #include <linux/slab.h> +static void v4l2_event_unsubscribe_all(struct v4l2_fh *fh); + int v4l2_event_init(struct v4l2_fh *fh) { fh->events = kzalloc(sizeof(*fh->events), GFP_KERNEL); @@ -91,7 +94,7 @@ void v4l2_event_free(struct v4l2_fh *fh) list_kfree(&events->free, struct v4l2_kevent, list); list_kfree(&events->available, struct v4l2_kevent, list); - list_kfree(&events->subscribed, struct v4l2_subscribed_event, list); + v4l2_event_unsubscribe_all(fh); kfree(events); fh->events = NULL; @@ -154,9 +157,9 @@ int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event, } EXPORT_SYMBOL_GPL(v4l2_event_dequeue); -/* Caller must hold fh->event->lock! */ +/* Caller must hold fh->vdev->fh_lock! */ static struct v4l2_subscribed_event *v4l2_event_subscribed( - struct v4l2_fh *fh, u32 type) + struct v4l2_fh *fh, u32 type, u32 id) { struct v4l2_events *events = fh->events; struct v4l2_subscribed_event *sev; @@ -164,13 +167,46 @@ static struct v4l2_subscribed_event *v4l2_event_subscribed( assert_spin_locked(&fh->vdev->fh_lock); list_for_each_entry(sev, &events->subscribed, list) { - if (sev->type == type) + if (sev->type == type && sev->id == id) return sev; } return NULL; } +static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev, + const struct timespec *ts) +{ + struct v4l2_events *events = fh->events; + struct v4l2_subscribed_event *sev; + struct v4l2_kevent *kev; + + /* Are we subscribed? */ + sev = v4l2_event_subscribed(fh, ev->type, ev->id); + if (sev == NULL) + return; + + /* Increase event sequence number on fh. */ + events->sequence++; + + /* Do we have any free events? */ + if (list_empty(&events->free)) + return; + + /* Take one and fill it. */ + kev = list_first_entry(&events->free, struct v4l2_kevent, list); + kev->event.type = ev->type; + kev->event.u = ev->u; + kev->event.id = ev->id; + kev->event.timestamp = *ts; + kev->event.sequence = events->sequence; + list_move_tail(&kev->list, &events->available); + + events->navailable++; + + wake_up_all(&events->wait); +} + void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) { struct v4l2_fh *fh; @@ -182,37 +218,26 @@ void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) spin_lock_irqsave(&vdev->fh_lock, flags); list_for_each_entry(fh, &vdev->fh_list, list) { - struct v4l2_events *events = fh->events; - struct v4l2_kevent *kev; - - /* Are we subscribed? */ - if (!v4l2_event_subscribed(fh, ev->type)) - continue; - - /* Increase event sequence number on fh. */ - events->sequence++; - - /* Do we have any free events? */ - if (list_empty(&events->free)) - continue; - - /* Take one and fill it. */ - kev = list_first_entry(&events->free, struct v4l2_kevent, list); - kev->event.type = ev->type; - kev->event.u = ev->u; - kev->event.timestamp = timestamp; - kev->event.sequence = events->sequence; - list_move_tail(&kev->list, &events->available); - - events->navailable++; - - wake_up_all(&events->wait); + __v4l2_event_queue_fh(fh, ev, ×tamp); } spin_unlock_irqrestore(&vdev->fh_lock, flags); } EXPORT_SYMBOL_GPL(v4l2_event_queue); +void v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev) +{ + unsigned long flags; + struct timespec timestamp; + + ktime_get_ts(×tamp); + + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + __v4l2_event_queue_fh(fh, ev, ×tamp); + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); +} +EXPORT_SYMBOL_GPL(v4l2_event_queue_fh); + int v4l2_event_pending(struct v4l2_fh *fh) { return fh->events->navailable; @@ -223,7 +248,9 @@ int v4l2_event_subscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub) { struct v4l2_events *events = fh->events; - struct v4l2_subscribed_event *sev; + struct v4l2_subscribed_event *sev, *found_ev; + struct v4l2_ctrl *ctrl = NULL; + struct v4l2_ctrl_fh *ctrl_fh = NULL; unsigned long flags; if (fh->events == NULL) { @@ -231,15 +258,31 @@ int v4l2_event_subscribe(struct v4l2_fh *fh, return -ENOMEM; } + if (sub->type == V4L2_EVENT_CTRL_CH_VALUE) { + ctrl = v4l2_ctrl_find(fh->ctrl_handler, sub->id); + if (ctrl == NULL) + return -EINVAL; + } + sev = kmalloc(sizeof(*sev), GFP_KERNEL); if (!sev) return -ENOMEM; + if (ctrl) { + ctrl_fh = kzalloc(sizeof(*ctrl_fh), GFP_KERNEL); + if (!ctrl_fh) { + kfree(sev); + return -ENOMEM; + } + ctrl_fh->fh = fh; + } spin_lock_irqsave(&fh->vdev->fh_lock, flags); - if (v4l2_event_subscribed(fh, sub->type) == NULL) { + found_ev = v4l2_event_subscribed(fh, sub->type, sub->id); + if (!found_ev) { INIT_LIST_HEAD(&sev->list); sev->type = sub->type; + sev->id = sub->id; list_add(&sev->list, &events->subscribed); sev = NULL; @@ -247,6 +290,10 @@ int v4l2_event_subscribe(struct v4l2_fh *fh, spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + /* v4l2_ctrl_add_fh uses a mutex, so do this outside the spin lock */ + if (!found_ev && ctrl) + v4l2_ctrl_add_fh(ctrl, ctrl_fh); + kfree(sev); return 0; @@ -256,6 +303,7 @@ EXPORT_SYMBOL_GPL(v4l2_event_subscribe); static void v4l2_event_unsubscribe_all(struct v4l2_fh *fh) { struct v4l2_events *events = fh->events; + struct v4l2_event_subscription sub; struct v4l2_subscribed_event *sev; unsigned long flags; @@ -265,11 +313,13 @@ static void v4l2_event_unsubscribe_all(struct v4l2_fh *fh) spin_lock_irqsave(&fh->vdev->fh_lock, flags); if (!list_empty(&events->subscribed)) { sev = list_first_entry(&events->subscribed, - struct v4l2_subscribed_event, list); - list_del(&sev->list); + struct v4l2_subscribed_event, list); + sub.type = sev->type; + sub.id = sev->id; } spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); - kfree(sev); + if (sev) + v4l2_event_unsubscribe(fh, &sub); } while (sev); } @@ -286,11 +336,17 @@ int v4l2_event_unsubscribe(struct v4l2_fh *fh, spin_lock_irqsave(&fh->vdev->fh_lock, flags); - sev = v4l2_event_subscribed(fh, sub->type); + sev = v4l2_event_subscribed(fh, sub->type, sub->id); if (sev != NULL) list_del(&sev->list); spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + if (sev->type == V4L2_EVENT_CTRL_CH_VALUE) { + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(fh->ctrl_handler, sev->id); + + if (ctrl) + v4l2_ctrl_del_fh(ctrl, fh); + } kfree(sev); diff --git a/drivers/media/video/v4l2-fh.c b/drivers/media/video/v4l2-fh.c index 8635011..c6aef84 100644 --- a/drivers/media/video/v4l2-fh.c +++ b/drivers/media/video/v4l2-fh.c @@ -93,10 +93,8 @@ void v4l2_fh_exit(struct v4l2_fh *fh) { if (fh->vdev == NULL) return; - - fh->vdev = NULL; - v4l2_event_free(fh); + fh->vdev = NULL; } EXPORT_SYMBOL_GPL(v4l2_fh_exit); diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h index 92d2fdd..f7238c1 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -1787,6 +1787,7 @@ struct v4l2_streamparm { #define V4L2_EVENT_ALL 0 #define V4L2_EVENT_VSYNC 1 #define V4L2_EVENT_EOS 2 +#define V4L2_EVENT_CTRL_CH_VALUE 3 #define V4L2_EVENT_PRIVATE_START 0x08000000 /* Payload for V4L2_EVENT_VSYNC */ @@ -1795,21 +1796,33 @@ struct v4l2_event_vsync { __u8 field; } __attribute__ ((packed)); +/* Payload for V4L2_EVENT_CTRL_CH_VALUE */ +struct v4l2_event_ctrl_ch_value { + __u32 type; + union { + __s32 value; + __s64 value64; + }; +} __attribute__ ((packed)); + struct v4l2_event { __u32 type; union { struct v4l2_event_vsync vsync; + struct v4l2_event_ctrl_ch_value ctrl_ch_value; __u8 data[64]; } u; __u32 pending; __u32 sequence; struct timespec timestamp; - __u32 reserved[9]; + __u32 id; + __u32 reserved[8]; }; struct v4l2_event_subscription { __u32 type; - __u32 reserved[7]; + __u32 id; + __u32 reserved[6]; }; /* diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h index 97d0638..7ca45a5 100644 --- a/include/media/v4l2-ctrls.h +++ b/include/media/v4l2-ctrls.h @@ -30,6 +30,7 @@ struct v4l2_ctrl_handler; struct v4l2_ctrl; struct video_device; struct v4l2_subdev; +struct v4l2_fh; /** struct v4l2_ctrl_ops - The control operations that the driver has to provide. * @g_volatile_ctrl: Get a new value for this control. Generally only relevant @@ -97,6 +98,7 @@ struct v4l2_ctrl_ops { struct v4l2_ctrl { /* Administrative fields */ struct list_head node; + struct list_head fhs; struct v4l2_ctrl_handler *handler; struct v4l2_ctrl **cluster; unsigned ncontrols; @@ -168,6 +170,11 @@ struct v4l2_ctrl_handler { int error; }; +struct v4l2_ctrl_fh { + struct list_head node; + struct v4l2_fh *fh; +}; + /** struct v4l2_ctrl_config - Control configuration structure. * @ops: The control ops. * @id: The control ID. @@ -440,6 +447,8 @@ s32 v4l2_ctrl_g_ctrl(struct v4l2_ctrl *ctrl); */ int v4l2_ctrl_s_ctrl(struct v4l2_ctrl *ctrl, s32 val); +void v4l2_ctrl_add_fh(struct v4l2_ctrl *ctrl, struct v4l2_ctrl_fh *ctrl_fh); +void v4l2_ctrl_del_fh(struct v4l2_ctrl *ctrl, struct v4l2_fh *fh); /* Helpers for ioctl_ops. If hdl == NULL then they will all return -EINVAL. */ int v4l2_queryctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_queryctrl *qc); diff --git a/include/media/v4l2-event.h b/include/media/v4l2-event.h index 3b86177..45e9c1e 100644 --- a/include/media/v4l2-event.h +++ b/include/media/v4l2-event.h @@ -40,6 +40,7 @@ struct v4l2_kevent { struct v4l2_subscribed_event { struct list_head list; u32 type; + u32 id; }; struct v4l2_events { @@ -58,6 +59,7 @@ void v4l2_event_free(struct v4l2_fh *fh); int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event, int nonblocking); void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev); +void v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev); int v4l2_event_pending(struct v4l2_fh *fh); int v4l2_event_subscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub); -- 1.7.1 -- 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