Skip to content

Commit

Permalink
[media] pwc: Fix locking
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Hans de Goede authored and Mauro Carvalho Chehab committed May 14, 2012
1 parent a67e172 commit ceede9f
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 153 deletions.
190 changes: 121 additions & 69 deletions drivers/media/video/pwc/pwc-if.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ static void pwc_isoc_handler(struct urb *urb)
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;
Expand All @@ -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;
Expand Down Expand Up @@ -418,7 +416,6 @@ static int pwc_isoc_init(struct pwc_device *pdev)
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;
}
Expand All @@ -435,7 +432,6 @@ static int pwc_isoc_init(struct pwc_device *pdev)
&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;
}
Expand All @@ -455,21 +451,18 @@ static int pwc_isoc_init(struct pwc_device *pdev)
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;
}
PWC_DEBUG_MEMORY("URB 0x%p submitted.\n", pdev->urbs[i]);
}

/* All is done... */
pdev->iso_init = 1;
PWC_DEBUG_OPEN("<< pwc_isoc_init()\n");
return 0;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -573,25 +564,21 @@ 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)
{
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);
}
Expand All @@ -600,55 +587,107 @@ 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);
}

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;
}

/***************************************************************************/
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
Expand All @@ -756,28 +794,37 @@ 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;
}

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,
Expand All @@ -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,
};

/***************************************************************************/
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Loading

0 comments on commit ceede9f

Please sign in to comment.