Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

V4L/DVB: v4l2: add core serialization lock

Drivers can optionally set a pointer to a mutex in struct video_device.
The core will use that to lock before calling open, read, write, unlocked_ioctl,
poll, mmap or release.

Updated the documentation as well and ensure that v4l2-event knows about the
lock: it will unlock it before doing a blocking wait on an event and relock it
afterwards.

Ensure that the 'video_is_registered' check is done when the lock is held:
a typical disconnect will take the lock as well before unregistering the
device nodes, so to prevent race conditions the video_is_registered check
should also be done with the lock held.

Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>

authored by

Hans Verkuil and committed by
Mauro Carvalho Chehab
ee6869af c29fcff3

+89 -19
+20
Documentation/video4linux/v4l2-framework.txt
··· 453 453 - ioctl_ops: if you use the v4l2_ioctl_ops to simplify ioctl maintenance 454 454 (highly recommended to use this and it might become compulsory in the 455 455 future!), then set this to your v4l2_ioctl_ops struct. 456 + - lock: leave to NULL if you want to do all the locking in the driver. 457 + Otherwise you give it a pointer to a struct mutex_lock and before any 458 + of the v4l2_file_operations is called this lock will be taken by the 459 + core and released afterwards. 456 460 - parent: you only set this if v4l2_device was registered with NULL as 457 461 the parent device struct. This only happens in cases where one hardware 458 462 device has multiple PCI devices that all share the same v4l2_device core. ··· 473 469 The v4l2_file_operations struct is a subset of file_operations. The main 474 470 difference is that the inode argument is omitted since it is never used. 475 471 472 + v4l2_file_operations and locking 473 + -------------------------------- 474 + 475 + You can set a pointer to a mutex_lock in struct video_device. Usually this 476 + will be either a top-level mutex or a mutex per device node. If you want 477 + finer-grained locking then you have to set it to NULL and do you own locking. 478 + 479 + If a lock is specified then all file operations will be serialized on that 480 + lock. If you use videobuf then you must pass the same lock to the videobuf 481 + queue initialize function: if videobuf has to wait for a frame to arrive, then 482 + it will temporarily unlock the lock and relock it afterwards. If your driver 483 + also waits in the code, then you should do the same to allow other processes 484 + to access the device node while the first process is waiting for something. 485 + 486 + The implementation of a hotplug disconnect should also take the lock before 487 + calling v4l2_device_disconnect and video_unregister_device. 476 488 477 489 video_device registration 478 490 -------------------------
+58 -18
drivers/media/video/v4l2-dev.c
··· 187 187 size_t sz, loff_t *off) 188 188 { 189 189 struct video_device *vdev = video_devdata(filp); 190 + int ret = -EIO; 190 191 191 192 if (!vdev->fops->read) 192 193 return -EINVAL; 193 - if (!video_is_registered(vdev)) 194 - return -EIO; 195 - return vdev->fops->read(filp, buf, sz, off); 194 + if (vdev->lock) 195 + mutex_lock(vdev->lock); 196 + if (video_is_registered(vdev)) 197 + ret = vdev->fops->read(filp, buf, sz, off); 198 + if (vdev->lock) 199 + mutex_unlock(vdev->lock); 200 + return ret; 196 201 } 197 202 198 203 static ssize_t v4l2_write(struct file *filp, const char __user *buf, 199 204 size_t sz, loff_t *off) 200 205 { 201 206 struct video_device *vdev = video_devdata(filp); 207 + int ret = -EIO; 202 208 203 209 if (!vdev->fops->write) 204 210 return -EINVAL; 205 - if (!video_is_registered(vdev)) 206 - return -EIO; 207 - return vdev->fops->write(filp, buf, sz, off); 211 + if (vdev->lock) 212 + mutex_lock(vdev->lock); 213 + if (video_is_registered(vdev)) 214 + ret = vdev->fops->write(filp, buf, sz, off); 215 + if (vdev->lock) 216 + mutex_unlock(vdev->lock); 217 + return ret; 208 218 } 209 219 210 220 static unsigned int v4l2_poll(struct file *filp, struct poll_table_struct *poll) 211 221 { 212 222 struct video_device *vdev = video_devdata(filp); 223 + int ret = DEFAULT_POLLMASK; 213 224 214 - if (!vdev->fops->poll || !video_is_registered(vdev)) 215 - return DEFAULT_POLLMASK; 216 - return vdev->fops->poll(filp, poll); 225 + if (!vdev->fops->poll) 226 + return ret; 227 + if (vdev->lock) 228 + mutex_lock(vdev->lock); 229 + if (video_is_registered(vdev)) 230 + ret = vdev->fops->poll(filp, poll); 231 + if (vdev->lock) 232 + mutex_unlock(vdev->lock); 233 + return ret; 217 234 } 218 235 219 236 static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ··· 241 224 if (!vdev->fops->ioctl) 242 225 return -ENOTTY; 243 226 if (vdev->fops->unlocked_ioctl) { 227 + if (vdev->lock) 228 + mutex_lock(vdev->lock); 244 229 ret = vdev->fops->unlocked_ioctl(filp, cmd, arg); 230 + if (vdev->lock) 231 + mutex_unlock(vdev->lock); 245 232 } else if (vdev->fops->ioctl) { 246 233 /* TODO: convert all drivers to unlocked_ioctl */ 247 234 lock_kernel(); ··· 260 239 static int v4l2_mmap(struct file *filp, struct vm_area_struct *vm) 261 240 { 262 241 struct video_device *vdev = video_devdata(filp); 242 + int ret = -ENODEV; 263 243 264 - if (!vdev->fops->mmap || !video_is_registered(vdev)) 265 - return -ENODEV; 266 - return vdev->fops->mmap(filp, vm); 244 + if (!vdev->fops->mmap) 245 + return ret; 246 + if (vdev->lock) 247 + mutex_lock(vdev->lock); 248 + if (video_is_registered(vdev)) 249 + ret = vdev->fops->mmap(filp, vm); 250 + if (vdev->lock) 251 + mutex_unlock(vdev->lock); 252 + return ret; 267 253 } 268 254 269 255 /* Override for the open function */ ··· 282 254 /* Check if the video device is available */ 283 255 mutex_lock(&videodev_lock); 284 256 vdev = video_devdata(filp); 285 - /* return ENODEV if the video device has been removed 286 - already or if it is not registered anymore. */ 287 - if (vdev == NULL || !video_is_registered(vdev)) { 257 + /* return ENODEV if the video device has already been removed. */ 258 + if (vdev == NULL) { 288 259 mutex_unlock(&videodev_lock); 289 260 return -ENODEV; 290 261 } 291 262 /* and increase the device refcount */ 292 263 video_get(vdev); 293 264 mutex_unlock(&videodev_lock); 294 - if (vdev->fops->open) 295 - ret = vdev->fops->open(filp); 265 + if (vdev->fops->open) { 266 + if (vdev->lock) 267 + mutex_lock(vdev->lock); 268 + if (video_is_registered(vdev)) 269 + ret = vdev->fops->open(filp); 270 + else 271 + ret = -ENODEV; 272 + if (vdev->lock) 273 + mutex_unlock(vdev->lock); 274 + } 296 275 297 276 /* decrease the refcount in case of an error */ 298 277 if (ret) ··· 313 278 struct video_device *vdev = video_devdata(filp); 314 279 int ret = 0; 315 280 316 - if (vdev->fops->release) 281 + if (vdev->fops->release) { 282 + if (vdev->lock) 283 + mutex_lock(vdev->lock); 317 284 vdev->fops->release(filp); 285 + if (vdev->lock) 286 + mutex_unlock(vdev->lock); 287 + } 318 288 319 289 /* decrease the refcount unconditionally since the release() 320 290 return value is ignored. */
+8 -1
drivers/media/video/v4l2-event.c
··· 134 134 if (nonblocking) 135 135 return __v4l2_event_dequeue(fh, event); 136 136 137 + /* Release the vdev lock while waiting */ 138 + if (fh->vdev->lock) 139 + mutex_unlock(fh->vdev->lock); 140 + 137 141 do { 138 142 ret = wait_event_interruptible(events->wait, 139 143 events->navailable != 0); 140 144 if (ret < 0) 141 - return ret; 145 + break; 142 146 143 147 ret = __v4l2_event_dequeue(fh, event); 144 148 } while (ret == -ENOENT); 149 + 150 + if (fh->vdev->lock) 151 + mutex_lock(fh->vdev->lock); 145 152 146 153 return ret; 147 154 }
+3
include/media/v4l2-dev.h
··· 94 94 95 95 /* ioctl callbacks */ 96 96 const struct v4l2_ioctl_ops *ioctl_ops; 97 + 98 + /* serialization lock */ 99 + struct mutex *lock; 97 100 }; 98 101 99 102 /* dev to video-device */