Hi Hans, On Wed, Sep 12, 2018 at 02:32:52PM +0200, Hans Verkuil wrote: > On 09/12/18 12:00, Sakari Ailus wrote: > > Hi Hans, > > > > Thanks for the quick review. > > > > On Wed, Sep 12, 2018 at 11:27:35AM +0200, Hans Verkuil wrote: > >> On 09/12/18 10:52, Sakari Ailus wrote: > >>> The event subscriptions are added to the subscribed event list while > >>> holding a spinlock, but that lock is subsequently released while still > >>> accessing the subscription object. This makes it possible to unsubscribe > >>> the event --- and freeing the subscription object's memory --- while > >>> the subscription object is simultaneously accessed. > >> > >> Hmm, the (un)subscribe ioctls are serialized through the ioctl lock, > >> so this could only be a scenario with drivers that do not use this > >> lock. Off-hand the only driver I know that does this is uvc. Unfortunately, > >> that's a rather popular one. > > > > On video nodes, perhaps. But how about sub-device nodes? Generally drivers > > tend to do locking themselves, whether or not that is the best for most > > drivers. > > That's a whole different discussion :-) Not entirely: the sub-device drivers do expose the sub-device event interface to the user space and thus the bug affects those as well. > > I've never been convinced by this since 1) it's hard to prove correctness > if drivers handle locking and 2) I've never seen any proof that it actually > improves performance. > > Anyway, it's unrelated to this patch. Yes, I agree on that. > > > > >> > >>> > >>> Prevent this by adding a mutex to serialise the event subscription and > >>> unsubscription. This also gives a guarantee to the callback ops that the > >>> add op has returned before the del op is called. > >>> > >>> This change also results in making the elems field less special: > >>> subscriptions are only added to the event list once they are fully > >>> initialised. > >>> > >>> Signed-off-by: Sakari Ailus <sakari.ailus@xxxxxxxxxxxxxxx> > >>> --- > >>> Hi folks, > >>> > >>> I noticed this while working to add support for media events. This seems > >>> like material for the stable trees. > >> > >> I'd say 'no need for this' if it wasn't for uvc. > >> > >>> > >>> drivers/media/v4l2-core/v4l2-event.c | 35 ++++++++++++++++++----------------- > >>> drivers/media/v4l2-core/v4l2-fh.c | 2 ++ > >>> include/media/v4l2-fh.h | 4 ++++ > >>> 3 files changed, 24 insertions(+), 17 deletions(-) > >>> > >>> diff --git a/drivers/media/v4l2-core/v4l2-event.c b/drivers/media/v4l2-core/v4l2-event.c > >>> index 127fe6eb91d9..74161f79e4d3 100644 > >>> --- a/drivers/media/v4l2-core/v4l2-event.c > >>> +++ b/drivers/media/v4l2-core/v4l2-event.c > >>> @@ -115,14 +115,6 @@ static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *e > >>> if (sev == NULL) > >>> return; > >>> > >>> - /* > >>> - * If the event has been added to the fh->subscribed list, but its > >>> - * add op has not completed yet elems will be 0, treat this as > >>> - * not being subscribed. > >>> - */ > >>> - if (!sev->elems) > >>> - return; > >>> - > >>> /* Increase event sequence number on fh. */ > >>> fh->sequence++; > >>> > >>> @@ -208,6 +200,7 @@ int v4l2_event_subscribe(struct v4l2_fh *fh, > >>> struct v4l2_subscribed_event *sev, *found_ev; > >>> unsigned long flags; > >>> unsigned i; > >>> + int ret = 0; > >>> > >>> if (sub->type == V4L2_EVENT_ALL) > >>> return -EINVAL; > >>> @@ -225,31 +218,35 @@ int v4l2_event_subscribe(struct v4l2_fh *fh, > >>> sev->flags = sub->flags; > >>> sev->fh = fh; > >>> sev->ops = ops; > >>> + sev->elems = elems; > >>> + > >>> + mutex_lock(&fh->mutex); > >>> > >>> spin_lock_irqsave(&fh->vdev->fh_lock, flags); > >>> found_ev = v4l2_event_subscribed(fh, sub->type, sub->id); > >>> - if (!found_ev) > >>> - list_add(&sev->list, &fh->subscribed); > >>> spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); > >>> > >>> if (found_ev) { > >>> + /* Already listening */ > >>> kvfree(sev); > >>> - return 0; /* Already listening */ > >>> + goto out_unlock; > >>> } > >>> > >>> if (sev->ops && sev->ops->add) { > >>> - int ret = sev->ops->add(sev, elems); > >>> + ret = sev->ops->add(sev, elems); > >>> if (ret) { > >>> - sev->ops = NULL; > >>> - v4l2_event_unsubscribe(fh, sub); > >>> - return ret; > >>> + kvfree(sev); > >>> + goto out_unlock; > >>> } > >>> } > >>> > >>> /* Mark as ready for use */ > >>> - sev->elems = elems; > >>> + list_add(&sev->list, &fh->subscribed); > >>> > >>> - return 0; > >>> +out_unlock: > >>> + mutex_unlock(&fh->mutex); > >>> + > >>> + return ret; > >>> } > >>> EXPORT_SYMBOL_GPL(v4l2_event_subscribe); > >>> > >>> @@ -288,6 +285,8 @@ int v4l2_event_unsubscribe(struct v4l2_fh *fh, > >>> return 0; > >>> } > >>> > >>> + mutex_lock(&fh->mutex); > >>> + > >>> spin_lock_irqsave(&fh->vdev->fh_lock, flags); > >>> > >>> sev = v4l2_event_subscribed(fh, sub->type, sub->id); > >>> @@ -305,6 +304,8 @@ int v4l2_event_unsubscribe(struct v4l2_fh *fh, > >>> if (sev && sev->ops && sev->ops->del) > >>> sev->ops->del(sev); > >>> > >>> + mutex_unlock(&fh->mutex); > >>> + > >>> kvfree(sev); > >>> > >>> return 0; > >>> diff --git a/drivers/media/v4l2-core/v4l2-fh.c b/drivers/media/v4l2-core/v4l2-fh.c > >>> index 3895999bf880..b017dafde907 100644 > >>> --- a/drivers/media/v4l2-core/v4l2-fh.c > >>> +++ b/drivers/media/v4l2-core/v4l2-fh.c > >>> @@ -45,6 +45,7 @@ void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev) > >>> INIT_LIST_HEAD(&fh->available); > >>> INIT_LIST_HEAD(&fh->subscribed); > >>> fh->sequence = -1; > >>> + mutex_init(&fh->mutex); > >>> } > >>> EXPORT_SYMBOL_GPL(v4l2_fh_init); > >>> > >>> @@ -90,6 +91,7 @@ void v4l2_fh_exit(struct v4l2_fh *fh) > >>> return; > >>> v4l_disable_media_source(fh->vdev); > >>> v4l2_event_unsubscribe_all(fh); > >>> + mutex_destroy(&fh->mutex); > >>> fh->vdev = NULL; > >>> } > >>> EXPORT_SYMBOL_GPL(v4l2_fh_exit); > >>> diff --git a/include/media/v4l2-fh.h b/include/media/v4l2-fh.h > >>> index ea73fef8bdc0..1be45a5f6383 100644 > >>> --- a/include/media/v4l2-fh.h > >>> +++ b/include/media/v4l2-fh.h > >>> @@ -42,6 +42,9 @@ struct v4l2_ctrl_handler; > >>> * @available: list of events waiting to be dequeued > >>> * @navailable: number of available events at @available list > >>> * @sequence: event sequence number > >>> + * @mutex: hold event subscriptions during subscribing; > >>> + * guarantee that the add and del event callbacks are orderly called > >>> + * > >>> * @m2m_ctx: pointer to &struct v4l2_m2m_ctx > >>> */ > >>> struct v4l2_fh { > >>> @@ -56,6 +59,7 @@ struct v4l2_fh { > >>> struct list_head available; > >>> unsigned int navailable; > >>> u32 sequence; > >>> + struct mutex mutex; > >> > >> I don't like the name 'mutex'. Perhaps something more descriptive like: > >> 'subscribe_lock'? > >> > >>> > >>> #if IS_ENABLED(CONFIG_V4L2_MEM2MEM_DEV) > >>> struct v4l2_m2m_ctx *m2m_ctx; > >>> > >> > >> Overall I think this patch makes sense. The code is cleaner and easier to follow. > >> Just give 'mutex' a better name :-) > > > > How about "subscribe_mutex"? It's a mutex... "subscribe_lock" would use a > > similar convention elsewhere in V4L2 where mutexes are commonly called > > locks, so I'm certainly fine with that as well. > > > > Like Laurent I have a slight preference for using _lock. > > I prefer _slock for spinlocks, and _lock for mutexes. I'll use subscribe_lock. -- Sakari Ailus sakari.ailus@xxxxxxxxxxxxxxx