Hi Laurent, On 16/11/17 12:32, Sakari Ailus wrote: > Hi Laurent, > > Thank you for the initiative to bring up and address the matter!
I concur - this looks like a good start towards managing the issue. One potential thing spotted on top of Sakari's review inline below, of course I suspect this was more of a prototype/consideration patch. > On Thu, Nov 16, 2017 at 02:33:48AM +0200, Laurent Pinchart wrote: >> Device unplug being asynchronous, it naturally races with operations >> performed by userspace through ioctls or other file operations on video >> device nodes. >> >> This leads to potential access to freed memory or to other resources >> during device access if unplug occurs during device access. To solve >> this, we need to wait until all device access completes when unplugging >> the device, and block all further access when the device is being >> unplugged. >> >> Three new functions are introduced. The video_device_enter() and >> video_device_exit() functions must be used to mark entry and exit from >> all code sections where the device can be accessed. The > > I wonder if it'd help splitting this patch into two: one that introduces > the mechanism and the other that uses it. Up to you. > > Nevertheless, it'd be better to have other system calls covered as well. > >> video_device_unplug() function is then used in the unplug handler to >> mark the device as being unplugged and wait for all access to complete. >> >> As an example mark the ioctl handler as a device access section. Other >> file operations need to be protected too, and blocking ioctls (such as >> VIDIOC_DQBUF) need to be handled as well. >> >> Signed-off-by: Laurent Pinchart <laurent.pinchart+rene...@ideasonboard.com> >> --- >> drivers/media/v4l2-core/v4l2-dev.c | 57 >> ++++++++++++++++++++++++++++++++++++++ >> include/media/v4l2-dev.h | 47 +++++++++++++++++++++++++++++++ >> 2 files changed, 104 insertions(+) >> >> diff --git a/drivers/media/v4l2-core/v4l2-dev.c >> b/drivers/media/v4l2-core/v4l2-dev.c >> index c647ba648805..c73c6d49e7cf 100644 >> --- a/drivers/media/v4l2-core/v4l2-dev.c >> +++ b/drivers/media/v4l2-core/v4l2-dev.c >> @@ -156,6 +156,52 @@ void video_device_release_empty(struct video_device >> *vdev) >> } >> EXPORT_SYMBOL(video_device_release_empty); >> >> +int video_device_enter(struct video_device *vdev) >> +{ >> + bool unplugged; >> + >> + spin_lock(&vdev->unplug_lock); >> + unplugged = vdev->unplugged; >> + if (!unplugged) >> + vdev->access_refcount++; >> + spin_unlock(&vdev->unplug_lock); >> + >> + return unplugged ? -ENODEV : 0; >> +} >> +EXPORT_SYMBOL_GPL(video_device_enter); >> + >> +void video_device_exit(struct video_device *vdev) >> +{ >> + bool wake_up; >> + >> + spin_lock(&vdev->unplug_lock); >> + WARN_ON(--vdev->access_refcount < 0); >> + wake_up = vdev->access_refcount == 0; >> + spin_unlock(&vdev->unplug_lock); >> + >> + if (wake_up) >> + wake_up(&vdev->unplug_wait); >> +} >> +EXPORT_SYMBOL_GPL(video_device_exit); > > Is there a need to export the two, i.e. wouldn't you only call them from > the framework, or the same module? > >> + >> +void video_device_unplug(struct video_device *vdev) >> +{ >> + bool unplug_blocked; >> + >> + spin_lock(&vdev->unplug_lock); >> + unplug_blocked = vdev->access_refcount > 0; >> + vdev->unplugged = true; > > Shouldn't this be set to false in video_register_device()? > >> + spin_unlock(&vdev->unplug_lock); >> + >> + if (!unplug_blocked) >> + return; > > Not necessary, wait_event_timeout() handles this already. > >> + >> + if (!wait_event_timeout(vdev->unplug_wait, !vdev->access_refcount, >> + msecs_to_jiffies(150000))) >> + WARN(1, "Timeout waiting for device access to complete\n"); > > Why a timeout? Does this get somehow less problematic over time? :-) > >> +} >> +EXPORT_SYMBOL_GPL(video_device_unplug); >> + >> static inline void video_get(struct video_device *vdev) >> { >> get_device(&vdev->dev); >> @@ -351,6 +397,10 @@ static long v4l2_ioctl(struct file *filp, unsigned int >> cmd, unsigned long arg) >> struct video_device *vdev = video_devdata(filp); >> int ret = -ENODEV; > > You could leave ret unassigned here. > >> >> + ret = video_device_enter(vdev); >> + if (ret < 0) >> + return ret; >> + >> if (vdev->fops->unlocked_ioctl) { >> struct mutex *lock = v4l2_ioctl_get_lock(vdev, cmd); >> >> @@ -358,11 +408,14 @@ static long v4l2_ioctl(struct file *filp, unsigned int >> cmd, unsigned long arg) >> return -ERESTARTSYS; It looks like that return -ERESTARTSYS might need a video_device_exit() too? >> if (video_is_registered(vdev)) >> ret = vdev->fops->unlocked_ioctl(filp, cmd, arg); >> + else >> + ret = -ENODEV; >> if (lock) >> mutex_unlock(lock); >> } else >> ret = -ENOTTY; >> >> + video_device_exit(vdev); >> return ret; >> } >> >> @@ -841,6 +894,10 @@ int __video_register_device(struct video_device *vdev, >> int type, int nr, >> if (WARN_ON(!vdev->v4l2_dev)) >> return -EINVAL; >> >> + /* unplug support */ >> + spin_lock_init(&vdev->unplug_lock); >> + init_waitqueue_head(&vdev->unplug_wait); >> + >> /* v4l2_fh support */ >> spin_lock_init(&vdev->fh_lock); >> INIT_LIST_HEAD(&vdev->fh_list); >> diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h >> index e657614521e3..365a94f91dc9 100644 >> --- a/include/media/v4l2-dev.h >> +++ b/include/media/v4l2-dev.h >> @@ -15,6 +15,7 @@ >> #include <linux/cdev.h> >> #include <linux/mutex.h> >> #include <linux/videodev2.h> >> +#include <linux/wait.h> >> >> #include <media/media-entity.h> >> >> @@ -178,6 +179,12 @@ struct v4l2_file_operations { >> * @pipe: &struct media_pipeline >> * @fops: pointer to &struct v4l2_file_operations for the video device >> * @device_caps: device capabilities as used in v4l2_capabilities >> + * @unplugged: when set the device has been unplugged and no device access >> + * section can be entered >> + * @access_refcount: number of device access section currently running for >> the >> + * device >> + * @unplug_lock: protects unplugged and access_refcount >> + * @unplug_wait: wait queue to wait for device access sections to complete >> * @dev: &struct device for the video device >> * @cdev: character device >> * @v4l2_dev: pointer to &struct v4l2_device parent >> @@ -221,6 +228,12 @@ struct video_device >> >> u32 device_caps; >> >> + /* unplug handling */ >> + bool unplugged; >> + int access_refcount; > > Could you use refcount_t instead, to avoid integer overflow issues? > >> + spinlock_t unplug_lock; >> + wait_queue_head_t unplug_wait; >> + >> /* sysfs */ >> struct device dev; >> struct cdev *cdev; >> @@ -506,4 +519,38 @@ static inline int video_is_registered(struct >> video_device *vdev) >> return test_bit(V4L2_FL_REGISTERED, &vdev->flags); >> } >> >> +/** >> + * video_device_enter - enter a device access section >> + * @vdev: the video device >> + * >> + * This function marks and protects the beginning of a section that should >> not >> + * be entered after the device has been unplugged. The section end is marked >> + * with a call to video_device_exit(). Calls to this function can be nested. >> + * >> + * Returns: >> + * 0 on success or a negative error code if the device has been unplugged. >> + */ >> +int video_device_enter(struct video_device *vdev); >> + >> +/** >> + * video_device_exit - exit a device access section >> + * @vdev: the video device >> + * >> + * This function marks the end of a section entered with >> video_device_enter(). >> + * It wakes up all tasks waiting on video_device_unplug() for device access >> + * sections to be exited. >> + */ >> +void video_device_exit(struct video_device *vdev); >> + >> +/** >> + * video_device_unplug - mark a device as unplugged >> + * @vdev: the video device >> + * >> + * Mark a device as unplugged, causing all subsequent calls to >> + * video_device_enter() to return an error. If a device access section is >> + * currently being executed the function waits until the section is exited >> as >> + * marked by a call to video_device_exit(). >> + */ >> +void video_device_unplug(struct video_device *vdev); >> + >> #endif /* _V4L2_DEV_H */ >> -- >> Regards, >> >> Laurent Pinchart >> >