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;
        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;
                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;
                }
                                                          &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;
                }
                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;
                }
        }
 
        /* All is done... */
-       pdev->iso_init = 1;
        PWC_DEBUG_OPEN("<< pwc_isoc_init()\n");
        return 0;
 }
        }
 }
 
+/* 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;
 
 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);
 }
 {
        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);
 }
 
                              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;
 }
 
 /***************************************************************************/
        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);
 }
 
        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);
                /* And cleanup any queued bufs!! */
                pwc_cleanup_queued_bufs(pdev);
        }
-leave:
-       mutex_unlock(&pdev->udevlock);
+
        return r;
 }
 
 {
        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,
        .buf_queue              = buffer_queue,
        .start_streaming        = start_streaming,
        .stop_streaming         = stop_streaming,
+       .wait_prepare           = wait_prepare,
+       .wait_finish            = wait_finish,
 };
 
 /***************************************************************************/
        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);
 
 
        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) {
        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)
 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);
 
        struct pwc_device *pdev = video_drvdata(file);
        int ret, pixelformat, compression = 0;
 
-       if (pwc_test_n_set_capt_file(pdev, file))
-               return -EBUSY;
-
        ret = pwc_vidioc_try_fmt(pdev, f);
        if (ret < 0)
                return ret;
 
-       pixelformat = f->fmt.pix.pixelformat;
+       if (mutex_lock_interruptible(&pdev->vb_queue_lock))
+               return -ERESTARTSYS;
 
-       mutex_lock(&pdev->udevlock);
-       if (!pdev->udev) {
-               ret = -ENODEV;
+       ret = pwc_test_n_set_capt_file(pdev, file);
+       if (ret)
                goto leave;
-       }
 
-       if (pdev->iso_init) {
+       if (pdev->vb_queue.streaming) {
                ret = -EBUSY;
                goto leave;
        }
 
+       pixelformat = f->fmt.pix.pixelformat;
+
        PWC_DEBUG_IOCTL("Trying to set format to: width=%d height=%d fps=%d "
                        "format=%c%c%c%c\n",
                        f->fmt.pix.width, f->fmt.pix.height, pdev->vframes,
 
        pwc_vidioc_fill_fmt(f, pdev->width, pdev->height, pdev->pixfmt);
 leave:
-       mutex_unlock(&pdev->udevlock);
+       mutex_unlock(&pdev->vb_queue_lock);
        return ret;
 }
 
 {
        struct pwc_device *pdev = video_drvdata(file);
 
-       if (!pdev->udev)
-               return -ENODEV;
-
        strcpy(cap->driver, PWC_NAME);
        strlcpy(cap->card, pdev->vdev.name, sizeof(cap->card));
        usb_make_path(pdev->udev, cap->bus_info, sizeof(cap->bus_info));
        return i ? -EINVAL : 0;
 }
 
-static int pwc_g_volatile_ctrl_unlocked(struct v4l2_ctrl *ctrl)
+static int pwc_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
 {
        struct pwc_device *pdev =
                container_of(ctrl->handler, struct pwc_device, ctrl_handler);
        int ret = 0;
 
-       if (!pdev->udev)
-               return -ENODEV;
-
        switch (ctrl->id) {
        case V4L2_CID_AUTO_WHITE_BALANCE:
                if (pdev->color_bal_valid &&
        return ret;
 }
 
-static int pwc_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
-{
-       struct pwc_device *pdev =
-               container_of(ctrl->handler, struct pwc_device, ctrl_handler);
-       int ret;
-
-       mutex_lock(&pdev->udevlock);
-       ret = pwc_g_volatile_ctrl_unlocked(ctrl);
-       mutex_unlock(&pdev->udevlock);
-       return ret;
-}
-
 static int pwc_set_awb(struct pwc_device *pdev)
 {
        int ret;
                if (pdev->auto_white_balance->val == awb_indoor ||
                    pdev->auto_white_balance->val == awb_outdoor ||
                    pdev->auto_white_balance->val == awb_fl)
-                       pwc_g_volatile_ctrl_unlocked(pdev->auto_white_balance);
+                       pwc_g_volatile_ctrl(pdev->auto_white_balance);
        }
        if (pdev->auto_white_balance->val != awb_manual)
                return 0;
                container_of(ctrl->handler, struct pwc_device, ctrl_handler);
        int ret = 0;
 
-       mutex_lock(&pdev->udevlock);
-
-       if (!pdev->udev) {
-               ret = -ENODEV;
-               goto leave;
-       }
-
        switch (ctrl->id) {
        case V4L2_CID_BRIGHTNESS:
                ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
        if (ret)
                PWC_ERROR("s_ctrl %s error %d\n", ctrl->name, ret);
 
-leave:
-       mutex_unlock(&pdev->udevlock);
        return ret;
 }
 
        if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
                return -EINVAL;
 
-       mutex_lock(&pdev->udevlock); /* To avoid race with s_fmt */
        PWC_DEBUG_IOCTL("ioctl(VIDIOC_G_FMT) return size %dx%d\n",
                        pdev->width, pdev->height);
        pwc_vidioc_fill_fmt(f, pdev->width, pdev->height, pdev->pixfmt);
-       mutex_unlock(&pdev->udevlock);
        return 0;
 }
 
                       struct v4l2_requestbuffers *rb)
 {
        struct pwc_device *pdev = video_drvdata(file);
+       int ret;
 
-       if (pwc_test_n_set_capt_file(pdev, file))
-               return -EBUSY;
+       if (mutex_lock_interruptible(&pdev->vb_queue_lock))
+               return -ERESTARTSYS;
 
-       return vb2_reqbufs(&pdev->vb_queue, rb);
+       ret = pwc_test_n_set_capt_file(pdev, file);
+       if (ret == 0)
+               ret = vb2_reqbufs(&pdev->vb_queue, rb);
+
+       mutex_unlock(&pdev->vb_queue_lock);
+       return ret;
 }
 
 static int pwc_querybuf(struct file *file, void *fh, struct v4l2_buffer *buf)
 {
        struct pwc_device *pdev = video_drvdata(file);
+       int ret;
 
-       return vb2_querybuf(&pdev->vb_queue, buf);
+       if (mutex_lock_interruptible(&pdev->vb_queue_lock))
+               return -ERESTARTSYS;
+
+       ret = pwc_test_n_set_capt_file(pdev, file);
+       if (ret == 0)
+               ret = vb2_querybuf(&pdev->vb_queue, buf);
+
+       mutex_unlock(&pdev->vb_queue_lock);
+       return ret;
 }
 
 static int pwc_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
 {
        struct pwc_device *pdev = video_drvdata(file);
+       int ret;
 
-       if (!pdev->udev)
-               return -ENODEV;
+       if (mutex_lock_interruptible(&pdev->vb_queue_lock))
+               return -ERESTARTSYS;
 
-       if (pdev->capt_file != file)
-               return -EBUSY;
+       ret = pwc_test_n_set_capt_file(pdev, file);
+       if (ret == 0)
+               ret = vb2_qbuf(&pdev->vb_queue, buf);
 
-       return vb2_qbuf(&pdev->vb_queue, buf);
+       mutex_unlock(&pdev->vb_queue_lock);
+       return ret;
 }
 
 static int pwc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
 {
        struct pwc_device *pdev = video_drvdata(file);
+       int ret;
 
-       if (!pdev->udev)
-               return -ENODEV;
+       if (mutex_lock_interruptible(&pdev->vb_queue_lock))
+               return -ERESTARTSYS;
 
-       if (pdev->capt_file != file)
-               return -EBUSY;
+       ret = pwc_test_n_set_capt_file(pdev, file);
+       if (ret == 0)
+               ret = vb2_dqbuf(&pdev->vb_queue, buf,
+                               file->f_flags & O_NONBLOCK);
 
-       return vb2_dqbuf(&pdev->vb_queue, buf, file->f_flags & O_NONBLOCK);
+       mutex_unlock(&pdev->vb_queue_lock);
+       return ret;
 }
 
 static int pwc_streamon(struct file *file, void *fh, enum v4l2_buf_type i)
 {
        struct pwc_device *pdev = video_drvdata(file);
+       int ret;
 
-       if (!pdev->udev)
-               return -ENODEV;
+       if (mutex_lock_interruptible(&pdev->vb_queue_lock))
+               return -ERESTARTSYS;
 
-       if (pdev->capt_file != file)
-               return -EBUSY;
+       ret = pwc_test_n_set_capt_file(pdev, file);
+       if (ret == 0)
+               ret = vb2_streamon(&pdev->vb_queue, i);
 
-       return vb2_streamon(&pdev->vb_queue, i);
+       mutex_unlock(&pdev->vb_queue_lock);
+       return ret;
 }
 
 static int pwc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i)
 {
        struct pwc_device *pdev = video_drvdata(file);
+       int ret;
 
-       if (!pdev->udev)
-               return -ENODEV;
+       if (mutex_lock_interruptible(&pdev->vb_queue_lock))
+               return -ERESTARTSYS;
 
-       if (pdev->capt_file != file)
-               return -EBUSY;
+       ret = pwc_test_n_set_capt_file(pdev, file);
+       if (ret == 0)
+               ret = vb2_streamoff(&pdev->vb_queue, i);
 
-       return vb2_streamoff(&pdev->vb_queue, i);
+       mutex_unlock(&pdev->vb_queue_lock);
+       return ret;
 }
 
 static int pwc_enum_framesizes(struct file *file, void *fh,
            parm->parm.capture.timeperframe.numerator == 0)
                return -EINVAL;
 
-       if (pwc_test_n_set_capt_file(pdev, file))
-               return -EBUSY;
-
        fps = parm->parm.capture.timeperframe.denominator /
              parm->parm.capture.timeperframe.numerator;
 
-       mutex_lock(&pdev->udevlock);
-       if (!pdev->udev) {
-               ret = -ENODEV;
+       if (mutex_lock_interruptible(&pdev->vb_queue_lock))
+               return -ERESTARTSYS;
+
+       ret = pwc_test_n_set_capt_file(pdev, file);
+       if (ret)
                goto leave;
-       }
 
-       if (pdev->iso_init) {
+       if (pdev->vb_queue.streaming) {
                ret = -EBUSY;
                goto leave;
        }
        pwc_g_parm(file, fh, parm);
 
 leave:
-       mutex_unlock(&pdev->udevlock);
+       mutex_unlock(&pdev->vb_queue_lock);
        return ret;
 }