diff options
author | Hans Verkuil <hverkuil@xs4all.nl> | 2010-09-26 07:47:38 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2010-10-20 23:06:14 -0400 |
commit | ee6869afc922a9849979e49bb3bbcad794872fcb (patch) | |
tree | 2266050d01da694d04b533a6509873888327108b | |
parent | c29fcff3daafbf46d64a543c1950bbd206ad8c1c (diff) |
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>
-rw-r--r-- | Documentation/video4linux/v4l2-framework.txt | 20 | ||||
-rw-r--r-- | drivers/media/video/v4l2-dev.c | 76 | ||||
-rw-r--r-- | drivers/media/video/v4l2-event.c | 9 | ||||
-rw-r--r-- | include/media/v4l2-dev.h | 3 |
4 files changed, 89 insertions, 19 deletions
diff --git a/Documentation/video4linux/v4l2-framework.txt b/Documentation/video4linux/v4l2-framework.txt index 9b1d81c26b7d..a128e012a45c 100644 --- a/Documentation/video4linux/v4l2-framework.txt +++ b/Documentation/video4linux/v4l2-framework.txt | |||
@@ -453,6 +453,10 @@ You should also set these fields: | |||
453 | - ioctl_ops: if you use the v4l2_ioctl_ops to simplify ioctl maintenance | 453 | - ioctl_ops: if you use the v4l2_ioctl_ops to simplify ioctl maintenance |
454 | (highly recommended to use this and it might become compulsory in the | 454 | (highly recommended to use this and it might become compulsory in the |
455 | future!), then set this to your v4l2_ioctl_ops struct. | 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 | - parent: you only set this if v4l2_device was registered with NULL as | 460 | - parent: you only set this if v4l2_device was registered with NULL as |
457 | the parent device struct. This only happens in cases where one hardware | 461 | the parent device struct. This only happens in cases where one hardware |
458 | device has multiple PCI devices that all share the same v4l2_device core. | 462 | device has multiple PCI devices that all share the same v4l2_device core. |
@@ -469,6 +473,22 @@ If you use v4l2_ioctl_ops, then you should set either .unlocked_ioctl or | |||
469 | The v4l2_file_operations struct is a subset of file_operations. The main | 473 | The v4l2_file_operations struct is a subset of file_operations. The main |
470 | difference is that the inode argument is omitted since it is never used. | 474 | difference is that the inode argument is omitted since it is never used. |
471 | 475 | ||
476 | v4l2_file_operations and locking | ||
477 | -------------------------------- | ||
478 | |||
479 | You can set a pointer to a mutex_lock in struct video_device. Usually this | ||
480 | will be either a top-level mutex or a mutex per device node. If you want | ||
481 | finer-grained locking then you have to set it to NULL and do you own locking. | ||
482 | |||
483 | If a lock is specified then all file operations will be serialized on that | ||
484 | lock. If you use videobuf then you must pass the same lock to the videobuf | ||
485 | queue initialize function: if videobuf has to wait for a frame to arrive, then | ||
486 | it will temporarily unlock the lock and relock it afterwards. If your driver | ||
487 | also waits in the code, then you should do the same to allow other processes | ||
488 | to access the device node while the first process is waiting for something. | ||
489 | |||
490 | The implementation of a hotplug disconnect should also take the lock before | ||
491 | calling v4l2_device_disconnect and video_unregister_device. | ||
472 | 492 | ||
473 | video_device registration | 493 | video_device registration |
474 | ------------------------- | 494 | ------------------------- |
diff --git a/drivers/media/video/v4l2-dev.c b/drivers/media/video/v4l2-dev.c index 5a54eabd4c42..a7702e3d149c 100644 --- a/drivers/media/video/v4l2-dev.c +++ b/drivers/media/video/v4l2-dev.c | |||
@@ -187,33 +187,50 @@ static ssize_t v4l2_read(struct file *filp, char __user *buf, | |||
187 | size_t sz, loff_t *off) | 187 | size_t sz, loff_t *off) |
188 | { | 188 | { |
189 | struct video_device *vdev = video_devdata(filp); | 189 | struct video_device *vdev = video_devdata(filp); |
190 | int ret = -EIO; | ||
190 | 191 | ||
191 | if (!vdev->fops->read) | 192 | if (!vdev->fops->read) |
192 | return -EINVAL; | 193 | return -EINVAL; |
193 | if (!video_is_registered(vdev)) | 194 | if (vdev->lock) |
194 | return -EIO; | 195 | mutex_lock(vdev->lock); |
195 | return vdev->fops->read(filp, buf, sz, off); | 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 | static ssize_t v4l2_write(struct file *filp, const char __user *buf, | 203 | static ssize_t v4l2_write(struct file *filp, const char __user *buf, |
199 | size_t sz, loff_t *off) | 204 | size_t sz, loff_t *off) |
200 | { | 205 | { |
201 | struct video_device *vdev = video_devdata(filp); | 206 | struct video_device *vdev = video_devdata(filp); |
207 | int ret = -EIO; | ||
202 | 208 | ||
203 | if (!vdev->fops->write) | 209 | if (!vdev->fops->write) |
204 | return -EINVAL; | 210 | return -EINVAL; |
205 | if (!video_is_registered(vdev)) | 211 | if (vdev->lock) |
206 | return -EIO; | 212 | mutex_lock(vdev->lock); |
207 | return vdev->fops->write(filp, buf, sz, off); | 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 | static unsigned int v4l2_poll(struct file *filp, struct poll_table_struct *poll) | 220 | static unsigned int v4l2_poll(struct file *filp, struct poll_table_struct *poll) |
211 | { | 221 | { |
212 | struct video_device *vdev = video_devdata(filp); | 222 | struct video_device *vdev = video_devdata(filp); |
223 | int ret = DEFAULT_POLLMASK; | ||
213 | 224 | ||
214 | if (!vdev->fops->poll || !video_is_registered(vdev)) | 225 | if (!vdev->fops->poll) |
215 | return DEFAULT_POLLMASK; | 226 | return ret; |
216 | return vdev->fops->poll(filp, poll); | 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 | static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | 236 | static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
@@ -224,7 +241,11 @@ static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | |||
224 | if (!vdev->fops->ioctl) | 241 | if (!vdev->fops->ioctl) |
225 | return -ENOTTY; | 242 | return -ENOTTY; |
226 | if (vdev->fops->unlocked_ioctl) { | 243 | if (vdev->fops->unlocked_ioctl) { |
244 | if (vdev->lock) | ||
245 | mutex_lock(vdev->lock); | ||
227 | ret = vdev->fops->unlocked_ioctl(filp, cmd, arg); | 246 | ret = vdev->fops->unlocked_ioctl(filp, cmd, arg); |
247 | if (vdev->lock) | ||
248 | mutex_unlock(vdev->lock); | ||
228 | } else if (vdev->fops->ioctl) { | 249 | } else if (vdev->fops->ioctl) { |
229 | /* TODO: convert all drivers to unlocked_ioctl */ | 250 | /* TODO: convert all drivers to unlocked_ioctl */ |
230 | lock_kernel(); | 251 | lock_kernel(); |
@@ -239,10 +260,17 @@ static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | |||
239 | static int v4l2_mmap(struct file *filp, struct vm_area_struct *vm) | 260 | static int v4l2_mmap(struct file *filp, struct vm_area_struct *vm) |
240 | { | 261 | { |
241 | struct video_device *vdev = video_devdata(filp); | 262 | struct video_device *vdev = video_devdata(filp); |
263 | int ret = -ENODEV; | ||
242 | 264 | ||
243 | if (!vdev->fops->mmap || !video_is_registered(vdev)) | 265 | if (!vdev->fops->mmap) |
244 | return -ENODEV; | 266 | return ret; |
245 | return vdev->fops->mmap(filp, vm); | 267 | if (vdev->lock) |
268 | mutex_lock(vdev->lock); | ||
269 | if (video_is_registered(vdev)) | ||
270 | ret = vdev->fops->mmap(filp, vm); | ||
271 | if (vdev->lock) | ||
272 | mutex_unlock(vdev->lock); | ||
273 | return ret; | ||
246 | } | 274 | } |
247 | 275 | ||
248 | /* Override for the open function */ | 276 | /* Override for the open function */ |
@@ -254,17 +282,24 @@ static int v4l2_open(struct inode *inode, struct file *filp) | |||
254 | /* Check if the video device is available */ | 282 | /* Check if the video device is available */ |
255 | mutex_lock(&videodev_lock); | 283 | mutex_lock(&videodev_lock); |
256 | vdev = video_devdata(filp); | 284 | vdev = video_devdata(filp); |
257 | /* return ENODEV if the video device has been removed | 285 | /* return ENODEV if the video device has already been removed. */ |
258 | already or if it is not registered anymore. */ | 286 | if (vdev == NULL) { |
259 | if (vdev == NULL || !video_is_registered(vdev)) { | ||
260 | mutex_unlock(&videodev_lock); | 287 | mutex_unlock(&videodev_lock); |
261 | return -ENODEV; | 288 | return -ENODEV; |
262 | } | 289 | } |
263 | /* and increase the device refcount */ | 290 | /* and increase the device refcount */ |
264 | video_get(vdev); | 291 | video_get(vdev); |
265 | mutex_unlock(&videodev_lock); | 292 | mutex_unlock(&videodev_lock); |
266 | if (vdev->fops->open) | 293 | if (vdev->fops->open) { |
267 | ret = vdev->fops->open(filp); | 294 | if (vdev->lock) |
295 | mutex_lock(vdev->lock); | ||
296 | if (video_is_registered(vdev)) | ||
297 | ret = vdev->fops->open(filp); | ||
298 | else | ||
299 | ret = -ENODEV; | ||
300 | if (vdev->lock) | ||
301 | mutex_unlock(vdev->lock); | ||
302 | } | ||
268 | 303 | ||
269 | /* decrease the refcount in case of an error */ | 304 | /* decrease the refcount in case of an error */ |
270 | if (ret) | 305 | if (ret) |
@@ -278,8 +313,13 @@ static int v4l2_release(struct inode *inode, struct file *filp) | |||
278 | struct video_device *vdev = video_devdata(filp); | 313 | struct video_device *vdev = video_devdata(filp); |
279 | int ret = 0; | 314 | int ret = 0; |
280 | 315 | ||
281 | if (vdev->fops->release) | 316 | if (vdev->fops->release) { |
317 | if (vdev->lock) | ||
318 | mutex_lock(vdev->lock); | ||
282 | vdev->fops->release(filp); | 319 | vdev->fops->release(filp); |
320 | if (vdev->lock) | ||
321 | mutex_unlock(vdev->lock); | ||
322 | } | ||
283 | 323 | ||
284 | /* decrease the refcount unconditionally since the release() | 324 | /* decrease the refcount unconditionally since the release() |
285 | return value is ignored. */ | 325 | return value is ignored. */ |
diff --git a/drivers/media/video/v4l2-event.c b/drivers/media/video/v4l2-event.c index de74ce07b5e2..69fd343d4774 100644 --- a/drivers/media/video/v4l2-event.c +++ b/drivers/media/video/v4l2-event.c | |||
@@ -134,15 +134,22 @@ int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event, | |||
134 | if (nonblocking) | 134 | if (nonblocking) |
135 | return __v4l2_event_dequeue(fh, event); | 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 | do { | 141 | do { |
138 | ret = wait_event_interruptible(events->wait, | 142 | ret = wait_event_interruptible(events->wait, |
139 | events->navailable != 0); | 143 | events->navailable != 0); |
140 | if (ret < 0) | 144 | if (ret < 0) |
141 | return ret; | 145 | break; |
142 | 146 | ||
143 | ret = __v4l2_event_dequeue(fh, event); | 147 | ret = __v4l2_event_dequeue(fh, event); |
144 | } while (ret == -ENOENT); | 148 | } while (ret == -ENOENT); |
145 | 149 | ||
150 | if (fh->vdev->lock) | ||
151 | mutex_lock(fh->vdev->lock); | ||
152 | |||
146 | return ret; | 153 | return ret; |
147 | } | 154 | } |
148 | EXPORT_SYMBOL_GPL(v4l2_event_dequeue); | 155 | EXPORT_SYMBOL_GPL(v4l2_event_dequeue); |
diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h index ba236ff35c8a..15802a067a12 100644 --- a/include/media/v4l2-dev.h +++ b/include/media/v4l2-dev.h | |||
@@ -94,6 +94,9 @@ struct video_device | |||
94 | 94 | ||
95 | /* ioctl callbacks */ | 95 | /* ioctl callbacks */ |
96 | const struct v4l2_ioctl_ops *ioctl_ops; | 96 | const struct v4l2_ioctl_ops *ioctl_ops; |
97 | |||
98 | /* serialization lock */ | ||
99 | struct mutex *lock; | ||
97 | }; | 100 | }; |
98 | 101 | ||
99 | /* dev to video-device */ | 102 | /* dev to video-device */ |