> Hi Hans,
>
> Thanks for the patchset! This looks really nice!
>
> Hans Verkuil wrote:
>> Whenever a control changes value an event is sent to anyone that
>> subscribed
>> to it.
>>
>> Signed-off-by: Hans Verkuil <[email protected]>
>> ---
>> 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);
>> + }
>
> No need for braces here.
>
>> +}
>> +
>> /* 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;
>
> This looks like a bugfix.
But it isn't :-)
v4l2_event_free didn't use fh->vdev in the past, but now it does so the
order had to be swapped.
>
>> }
>> 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;
>
> type is enum v4l2_ctrl_type in struct v4l2_ctrl and struct v4l2_queryctrl.
Yes, but enum's are frowned upon these days in the public API.
>
>> + 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;
>
> id is valid only for control related events. Shouldn't it be part of the
> control related structures instead, or another union for control related
> event types? E.g.
>
> struct {
> enum v4l2_ctrl_type id;
> union {
> struct v4l2_event_ctrl_ch_value ch_value;
> };
> } ctrl;
The problem with making this dependent of the type is that the core event
code would have to have a switch on the event type when it comes to
matching identifiers. By making it a core field it doesn't have to do
this. Also, this makes it available for use by private events as well.
It is important to keep the send-event core code as fast as possible since
it can be called from interrupt context.
So this is by design.
Regards,
Hans
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html