Hans Verkuil wrote:
> Hi Sakari,
> 
> Once again, thanks for all the work you're doing on this! Very much 
> appreciated!

You're welcome. I think I want to see this finished as much as you do.
:-) And many thanks for the review to you!

> As you requested, I've reviewed this code with special attention to 
> refcounting
> and locking.
>
> In general, locking is hard, so the fewer locks there are, the easier it is
> to get locking right and to maintain/understand the code. 
> 
> Currently you have a lock in v4l2_fhs (at the video_device level) and a lock 
> and
> a refcount in v4l2_fh.
> 
> In my opinion this causes more complexity than is needed. I think a single 
> lock
> in v4l2_fhs would suffice. This does mean that struct v4l2_fh needs a pointer
> to video_device (which is a good idea anyway).
> 
> The only place where this locks more than is strictly necessary is the dequeue
> function. On the other hand, the lock that is taken there is very short and
> I prefer simplicity over unproven performance increases. I actually suspect
> that all the complexity might introduce slower performance than slightly
> sub-optimal locking. I have rewritten the queue and dequeue event functions
> below to what I think should be done there. As you can see, they are now a lot
> easier to understand and a lot shorter.

Could be true. Usually the number of file handles keeping a device open
is rather small, as is the number of subscriptions. My idea there was to
keep the time during which interrupts were disabled small.

> Regards,
> 
>       Hans
> 
...
>> +void v4l2_event_exit(struct v4l2_fh *fh)
>> +{
>> +    struct v4l2_events *events = fh->events;
>> +
>> +    if (!events)
>> +            return;
>> +
>> +    while (!list_empty(&events->free)) {
>> +            struct v4l2_kevent *kev;
>> +
>> +            kev = list_first_entry(&events->free,
>> +                                   struct v4l2_kevent, list);
>> +            list_del(&kev->list);
>> +
>> +            kfree(kev);
>> +    }
> 
> This can be done cleaner via a static inline function to delete an event
> list.

Made a macro out of that. All three can be freed that way.

...

>> +int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event)
>> +{
>> +    struct v4l2_events *events = fh->events;
>> +    struct v4l2_kevent *kev;
>> +    unsigned long flags;
>> +
>> +    spin_lock_irqsave(&fh->lock, flags);
>> +
>> +    if (list_empty(&events->available)) {
>> +            spin_unlock_irqrestore(&fh->lock, flags);
>> +            return -ENOENT;
>> +    }
>> +
>> +    kev = list_first_entry(&events->available, struct v4l2_kevent, list);
>> +    list_del(&kev->list);
>> +
>> +    kev->event.count = !list_empty(&events->available);
>> +
>> +    spin_unlock_irqrestore(&fh->lock, flags);
>> +
>> +    *event = kev->event;
>> +
>> +    spin_lock_irqsave(&fh->lock, flags);
>> +    list_add(&kev->list, &events->free);
>> +    spin_unlock_irqrestore(&fh->lock, flags);
>> +
>> +    return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(v4l2_event_dequeue);
> 
> int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event)
> {
>       struct v4l2_events *events = fh->events;
>       struct v4l2_kevent *kev;
>       unsigned long flags;
> 
>       spin_lock_irqsave(&fh->vdev->fhs.lock, flags);
> 
>       if (list_empty(&events->available)) {
>               spin_unlock_irqrestore(&fh->vdev->fhs.lock, flags);
>               return -ENOENT;
>       }
> 
>       kev = list_first_entry(&events->available, struct v4l2_kevent, list);
>       list_move(&kev->list, &events->free);
> 
>       kev->event.count = !list_empty(&events->available);
> 
>       *event = kev->event;
> 
>       spin_unlock_irqrestore(&fh->vdev->fhs.lock, flags);
> 
>       return 0;
> }
> 
> One possible optimization might be to do this (as you did in the original 
> code):
> 
>       kev = list_first_entry(&events->available, struct v4l2_kevent, list);
>       list_del(&kev->list);
>       kev->event.count = !list_empty(&events->available);
>       spin_unlock_irqrestore(&fh->vdev->fhs.lock, flags);
> 
>       *event = kev->event;
> 
>       spin_lock_irqsave(&fh->vdev->fhs.lock, flags);
>       list_add(&kev->list, &events->free);
>       spin_unlock_irqrestore(&fh->vdev->fhs.lock, flags);
> 
> However, this would need some testing first to see what the performance
> trade-off is between unlocking and locking vs. copying 120 bytes.

Probably makes no sense to drop the lock for that duration. I now keep
it all the time in the updated patch.

...

>> +void v4l2_event_queue(struct video_device *vdev, struct v4l2_event *ev)
>> +{
>> +    struct v4l2_fh *fh;
>> +    unsigned long flags;
>> +    struct v4l2_fh *put_me = NULL;
>> +
>> +    spin_lock_irqsave(&vdev->fhs.lock, flags);
>> +
>> +    list_for_each_entry(fh, &vdev->fhs.list, list) {
>> +            struct v4l2_events *events = fh->events;
>> +            struct v4l2_kevent *kev;
>> +
>> +            /* Is it subscribed? */
>> +            if (!v4l2_event_subscribed(fh, ev->type))
>> +                    continue;
>> +
>> +            /* Can we get the file handle? */
>> +            if (v4l2_fh_get(vdev, fh))
>> +                    continue;
>> +
>> +            /* List lock no longer required. */
>> +            spin_unlock_irqrestore(&vdev->fhs.lock, flags);
>> +
>> +            /* Put earlier v4l2_fh. */
>> +            if (put_me) {
>> +                    v4l2_fh_put(vdev, put_me);
>> +                    put_me = NULL;
>> +            }
>> +            put_me = fh;
>> +
>> +            /* Do we have any free events? */
>> +            spin_lock_irqsave(&fh->lock, flags);
>> +            if (list_empty(&events->free)) {
>> +                    spin_unlock_irqrestore(&fh->lock, flags);
>> +                    spin_lock_irqsave(&vdev->fhs.lock, flags);
>> +                    continue;
>> +            }
>> +
>> +            /* Take one and fill it. */
>> +            kev = list_first_entry(&events->free, struct v4l2_kevent, list);
>> +            list_del(&kev->list);
>> +            spin_unlock_irqrestore(&fh->lock, flags);
>> +
>> +            kev->event = *ev;
>> +
>> +            /* And add to the available list. */
>> +            spin_lock_irqsave(&fh->lock, flags);
>> +            list_add_tail(&kev->list, &events->available);
>> +            spin_unlock_irqrestore(&fh->lock, flags);
>> +
>> +            wake_up_all(&events->wait);
>> +
>> +            spin_lock_irqsave(&vdev->fhs.lock, flags);
>> +    }
>> +
>> +    spin_unlock_irqrestore(&vdev->fhs.lock, flags);
>> +
>> +    /* Put final v4l2_fh if exists. */
>> +    if (put_me)
>> +            v4l2_fh_put(vdev, put_me);
>> +}
>> +EXPORT_SYMBOL_GPL(v4l2_event_queue);
> 
> void v4l2_event_queue(struct video_device *vdev, struct v4l2_event *ev)
> {
>       struct v4l2_fh *fh;
>       unsigned long flags;
> 
>       spin_lock_irqsave(&vdev->fhs.lock, flags);
> 
>       list_for_each_entry(fh, &vdev->fhs.list, list) {
>               struct v4l2_events *events = fh->events;
>               struct v4l2_kevent *kev;
> 
>               /* Do we have any free events and are we subscribed? */
>               if (list_empty(&events->free) ||
>                   !__v4l2_event_subscribed(fh, ev->type))
>                       continue;
> 
>               /* Take one and fill it. */
>               kev = list_first_entry(&events->free, struct v4l2_kevent, list);
>               kev->event = *ev;
>               list_move_tail(&kev->list, &events->available);
> 
>               wake_up_all(&events->wait);
>       }
> 
>       spin_unlock_irqrestore(&vdev->fhs.lock, flags);
> }

Fixed.

-- 
Sakari Ailus
sakari.ai...@maxwell.research.nokia.com
--
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