diff options
author | Hans de Goede <hdegoede@redhat.com> | 2012-05-09 04:43:12 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-05-14 09:21:59 -0300 |
commit | ceede9fa8939e40ad0ddb4ad1355f45c6f1d3478 (patch) | |
tree | 201155f368f69a4875b523f899aed80226fe8822 /drivers/media/video/pwc/pwc-if.c | |
parent | a67e17221429c0322e543ae4bf0b25259c3416a5 (diff) | |
download | linux-stable-ceede9fa8939e40ad0ddb4ad1355f45c6f1d3478.tar.gz linux-stable-ceede9fa8939e40ad0ddb4ad1355f45c6f1d3478.tar.bz2 linux-stable-ceede9fa8939e40ad0ddb4ad1355f45c6f1d3478.zip |
[media] pwc: Fix locking
My last locking rework for pwc mistakenly assumed that videbuf2 does its
own locking, but it does not! This patch fixes the missing locking by
moving over the the video_device lock, and introducing a separate lock
for the videobuf2_queue.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/video/pwc/pwc-if.c')
-rw-r--r-- | drivers/media/video/pwc/pwc-if.c | 190 |
1 files changed, 121 insertions, 69 deletions
diff --git a/drivers/media/video/pwc/pwc-if.c b/drivers/media/video/pwc/pwc-if.c index f3370a87cbc0..998e809765a7 100644 --- a/drivers/media/video/pwc/pwc-if.c +++ b/drivers/media/video/pwc/pwc-if.c @@ -357,6 +357,7 @@ handler_end: PWC_ERROR("Error (%d) re-submitting urb in pwc_isoc_handler.\n", i); } +/* Both v4l2_lock and vb_queue_lock should be locked when calling this */ static int pwc_isoc_init(struct pwc_device *pdev) { struct usb_device *udev; @@ -366,9 +367,6 @@ static int pwc_isoc_init(struct pwc_device *pdev) struct usb_host_interface *idesc = NULL; int compression = 0; /* 0..3 = uncompressed..high */ - if (pdev->iso_init) - return 0; - pdev->vsync = 0; pdev->vlast_packet_size = 0; pdev->fill_buf = NULL; @@ -418,7 +416,6 @@ retry: urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL); if (urb == NULL) { PWC_ERROR("Failed to allocate urb %d\n", i); - pdev->iso_init = 1; pwc_isoc_cleanup(pdev); return -ENOMEM; } @@ -435,7 +432,6 @@ retry: &urb->transfer_dma); if (urb->transfer_buffer == NULL) { PWC_ERROR("Failed to allocate urb buffer %d\n", i); - pdev->iso_init = 1; pwc_isoc_cleanup(pdev); return -ENOMEM; } @@ -455,13 +451,11 @@ retry: ret = usb_submit_urb(pdev->urbs[i], GFP_KERNEL); if (ret == -ENOSPC && compression < 3) { compression++; - pdev->iso_init = 1; pwc_isoc_cleanup(pdev); goto retry; } if (ret) { PWC_ERROR("isoc_init() submit_urb %d failed with error %d\n", i, ret); - pdev->iso_init = 1; pwc_isoc_cleanup(pdev); return ret; } @@ -469,7 +463,6 @@ retry: } /* All is done... */ - pdev->iso_init = 1; PWC_DEBUG_OPEN("<< pwc_isoc_init()\n"); return 0; } @@ -507,21 +500,19 @@ static void pwc_iso_free(struct pwc_device *pdev) } } +/* Both v4l2_lock and vb_queue_lock should be locked when calling this */ static void pwc_isoc_cleanup(struct pwc_device *pdev) { PWC_DEBUG_OPEN(">> pwc_isoc_cleanup()\n"); - if (pdev->iso_init == 0) - return; - pwc_iso_stop(pdev); pwc_iso_free(pdev); usb_set_interface(pdev->udev, 0, 0); - pdev->iso_init = 0; PWC_DEBUG_OPEN("<< pwc_isoc_cleanup()\n"); } +/* Must be called with vb_queue_lock hold */ static void pwc_cleanup_queued_bufs(struct pwc_device *pdev) { unsigned long flags = 0; @@ -573,18 +564,13 @@ static const char *pwc_sensor_type_to_string(unsigned int sensor_type) int pwc_test_n_set_capt_file(struct pwc_device *pdev, struct file *file) { - int r = 0; - - mutex_lock(&pdev->capt_file_lock); if (pdev->capt_file != NULL && - pdev->capt_file != file) { - r = -EBUSY; - goto leave; - } + pdev->capt_file != file) + return -EBUSY; + pdev->capt_file = file; -leave: - mutex_unlock(&pdev->capt_file_lock); - return r; + + return 0; } static void pwc_video_release(struct v4l2_device *v) @@ -592,6 +578,7 @@ static void pwc_video_release(struct v4l2_device *v) struct pwc_device *pdev = container_of(v, struct pwc_device, v4l2_dev); v4l2_ctrl_handler_free(&pdev->ctrl_handler); + v4l2_device_unregister(&pdev->v4l2_dev); kfree(pdev->ctrl_buf); kfree(pdev); } @@ -600,10 +587,25 @@ static int pwc_video_close(struct file *file) { struct pwc_device *pdev = video_drvdata(file); + /* + * If we're still streaming vb2_queue_release will call stream_stop + * so we must take both the v4l2_lock and the vb_queue_lock. + */ + if (mutex_lock_interruptible(&pdev->v4l2_lock)) + return -ERESTARTSYS; + if (mutex_lock_interruptible(&pdev->vb_queue_lock)) { + mutex_unlock(&pdev->v4l2_lock); + return -ERESTARTSYS; + } + if (pdev->capt_file == file) { vb2_queue_release(&pdev->vb_queue); pdev->capt_file = NULL; } + + mutex_unlock(&pdev->vb_queue_lock); + mutex_unlock(&pdev->v4l2_lock); + return v4l2_fh_release(file); } @@ -611,44 +613,81 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct pwc_device *pdev = video_drvdata(file); + int lock_v4l2 = 0; + ssize_t ret; - if (!pdev->udev) - return -ENODEV; + if (mutex_lock_interruptible(&pdev->vb_queue_lock)) + return -ERESTARTSYS; - if (pwc_test_n_set_capt_file(pdev, file)) - return -EBUSY; + ret = pwc_test_n_set_capt_file(pdev, file); + if (ret) + goto out; + + /* stream_start will get called so we must take the v4l2_lock */ + if (pdev->vb_queue.fileio == NULL) + lock_v4l2 = 1; - return vb2_read(&pdev->vb_queue, buf, count, ppos, - file->f_flags & O_NONBLOCK); + /* Use try_lock, since we're taking the locks in the *wrong* order! */ + if (lock_v4l2 && !mutex_trylock(&pdev->v4l2_lock)) { + ret = -ERESTARTSYS; + goto out; + } + ret = vb2_read(&pdev->vb_queue, buf, count, ppos, + file->f_flags & O_NONBLOCK); + if (lock_v4l2) + mutex_unlock(&pdev->v4l2_lock); +out: + mutex_unlock(&pdev->vb_queue_lock); + return ret; } static unsigned int pwc_video_poll(struct file *file, poll_table *wait) { struct pwc_device *pdev = video_drvdata(file); + struct vb2_queue *q = &pdev->vb_queue; unsigned long req_events = poll_requested_events(wait); + unsigned int ret = POLL_ERR; + int lock_v4l2 = 0; - if (!pdev->udev) + if (mutex_lock_interruptible(&pdev->vb_queue_lock)) return POLL_ERR; + /* Will this start fileio and thus call start_stream? */ if ((req_events & (POLLIN | POLLRDNORM)) && - pdev->vb_queue.num_buffers == 0 && - !pdev->iso_init) { - /* This poll will start a read stream, check capt_file */ + q->num_buffers == 0 && !q->streaming && q->fileio == NULL) { if (pwc_test_n_set_capt_file(pdev, file)) - return POLL_ERR; + goto out; + lock_v4l2 = 1; } - return vb2_poll(&pdev->vb_queue, file, wait); + /* Use try_lock, since we're taking the locks in the *wrong* order! */ + if (lock_v4l2 && !mutex_trylock(&pdev->v4l2_lock)) + goto out; + ret = vb2_poll(&pdev->vb_queue, file, wait); + if (lock_v4l2) + mutex_unlock(&pdev->v4l2_lock); + +out: + if (!pdev->udev) + ret |= POLLHUP; + mutex_unlock(&pdev->vb_queue_lock); + return ret; } static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma) { struct pwc_device *pdev = video_drvdata(file); + int ret; - if (pdev->capt_file != file) - return -EBUSY; + if (mutex_lock_interruptible(&pdev->vb_queue_lock)) + return -ERESTARTSYS; + + ret = pwc_test_n_set_capt_file(pdev, file); + if (ret == 0) + ret = vb2_mmap(&pdev->vb_queue, vma); - return vb2_mmap(&pdev->vb_queue, vma); + mutex_unlock(&pdev->vb_queue_lock); + return ret; } /***************************************************************************/ @@ -724,12 +763,14 @@ static void buffer_queue(struct vb2_buffer *vb) struct pwc_frame_buf *buf = container_of(vb, struct pwc_frame_buf, vb); unsigned long flags = 0; - spin_lock_irqsave(&pdev->queued_bufs_lock, flags); /* Check the device has not disconnected between prep and queuing */ - if (pdev->udev) - list_add_tail(&buf->list, &pdev->queued_bufs); - else + if (!pdev->udev) { vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + return; + } + + spin_lock_irqsave(&pdev->queued_bufs_lock, flags); + list_add_tail(&buf->list, &pdev->queued_bufs); spin_unlock_irqrestore(&pdev->queued_bufs_lock, flags); } @@ -738,11 +779,8 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) struct pwc_device *pdev = vb2_get_drv_priv(vq); int r; - mutex_lock(&pdev->udevlock); - if (!pdev->udev) { - r = -ENODEV; - goto leave; - } + if (!pdev->udev) + return -ENODEV; /* Turn on camera and set LEDS on */ pwc_camera_power(pdev, 1); @@ -756,8 +794,7 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) /* And cleanup any queued bufs!! */ pwc_cleanup_queued_bufs(pdev); } -leave: - mutex_unlock(&pdev->udevlock); + return r; } @@ -765,19 +802,29 @@ static int stop_streaming(struct vb2_queue *vq) { struct pwc_device *pdev = vb2_get_drv_priv(vq); - mutex_lock(&pdev->udevlock); if (pdev->udev) { pwc_set_leds(pdev, 0, 0); pwc_camera_power(pdev, 0); pwc_isoc_cleanup(pdev); } - mutex_unlock(&pdev->udevlock); pwc_cleanup_queued_bufs(pdev); return 0; } +static void wait_prepare(struct vb2_queue *vq) +{ + struct pwc_device *pdev = vb2_get_drv_priv(vq); + mutex_unlock(&pdev->vb_queue_lock); +} + +static void wait_finish(struct vb2_queue *vq) +{ + struct pwc_device *pdev = vb2_get_drv_priv(vq); + mutex_lock(&pdev->vb_queue_lock); +} + static struct vb2_ops pwc_vb_queue_ops = { .queue_setup = queue_setup, .buf_init = buffer_init, @@ -787,6 +834,8 @@ static struct vb2_ops pwc_vb_queue_ops = { .buf_queue = buffer_queue, .start_streaming = start_streaming, .stop_streaming = stop_streaming, + .wait_prepare = wait_prepare, + .wait_finish = wait_finish, }; /***************************************************************************/ @@ -1066,8 +1115,8 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id pdev->features = features; pwc_construct(pdev); /* set min/max sizes correct */ - mutex_init(&pdev->capt_file_lock); - mutex_init(&pdev->udevlock); + mutex_init(&pdev->v4l2_lock); + mutex_init(&pdev->vb_queue_lock); spin_lock_init(&pdev->queued_bufs_lock); INIT_LIST_HEAD(&pdev->queued_bufs); @@ -1139,6 +1188,16 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id pdev->v4l2_dev.ctrl_handler = &pdev->ctrl_handler; pdev->vdev.v4l2_dev = &pdev->v4l2_dev; + pdev->vdev.lock = &pdev->v4l2_lock; + + /* + * Don't take v4l2_lock for these ioctls. This improves latency if + * v4l2_lock is taken for a long time, e.g. when changing a control + * value, and a new frame is ready to be dequeued. + */ + v4l2_dont_use_lock(&pdev->vdev, VIDIOC_DQBUF); + v4l2_dont_use_lock(&pdev->vdev, VIDIOC_QBUF); + v4l2_dont_use_lock(&pdev->vdev, VIDIOC_QUERYBUF); rc = video_register_device(&pdev->vdev, VFL_TYPE_GRABBER, -1); if (rc < 0) { @@ -1194,16 +1253,20 @@ static void usb_pwc_disconnect(struct usb_interface *intf) struct v4l2_device *v = usb_get_intfdata(intf); struct pwc_device *pdev = container_of(v, struct pwc_device, v4l2_dev); - mutex_lock(&pdev->udevlock); + mutex_lock(&pdev->v4l2_lock); + + mutex_lock(&pdev->vb_queue_lock); /* No need to keep the urbs around after disconnection */ - pwc_isoc_cleanup(pdev); + if (pdev->vb_queue.streaming) + pwc_isoc_cleanup(pdev); pdev->udev = NULL; - mutex_unlock(&pdev->udevlock); - pwc_cleanup_queued_bufs(pdev); + mutex_unlock(&pdev->vb_queue_lock); + v4l2_device_disconnect(&pdev->v4l2_dev); video_unregister_device(&pdev->vdev); - v4l2_device_unregister(&pdev->v4l2_dev); + + mutex_unlock(&pdev->v4l2_lock); #ifdef CONFIG_USB_PWC_INPUT_EVDEV if (pdev->button_dev) @@ -1238,15 +1301,4 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("pwcx"); MODULE_VERSION( PWC_VERSION ); -static int __init usb_pwc_init(void) -{ - return usb_register(&pwc_driver); -} - -static void __exit usb_pwc_exit(void) -{ - usb_deregister(&pwc_driver); -} - -module_init(usb_pwc_init); -module_exit(usb_pwc_exit); +module_usb_driver(pwc_driver); |