V4L/DVB (6538): em28xx: fix locking to allow accesses from 2 different threads at the same time

The attached patch modifies the em28xx driver so that there can be ioctls from
multiple different threads.

This is necessary for capture apps like MPlayer that use different threads for
capturing and channel tuning.

Now the locking is only done for the ioctls that change properties of the
device or access the i2c bus.

It also removes some locks that look unnecessary:

In em28xx_init_dev:
  the videodevice is not registered yet so nothing can access the hardware
 meanwhile, the device struct is not assigned to the interface yet so no race
 with disconnect is possible

In em28xx_release_resources:
  it gets only called when dev->lock is already held

Signed-off-by: Sascha Sommer <saschasommer@freenet.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
This commit is contained in:
Sascha Sommer 2007-11-03 21:22:38 -03:00 committed by Mauro Carvalho Chehab
parent ea4fd5679b
commit 5a80415bca
2 changed files with 81 additions and 74 deletions

View File

@ -126,8 +126,6 @@ static struct v4l2_queryctrl em28xx_qctrl[] = {
static struct usb_driver em28xx_usb_driver; static struct usb_driver em28xx_usb_driver;
static DEFINE_MUTEX(em28xx_sysfs_lock);
static DECLARE_RWSEM(em28xx_disconnect);
/********************* v4l2 interface ******************************************/ /********************* v4l2 interface ******************************************/
@ -253,22 +251,18 @@ static int em28xx_v4l2_open(struct inode *inode, struct file *filp)
em28xx_videodbg("open minor=%d type=%s users=%d\n", em28xx_videodbg("open minor=%d type=%s users=%d\n",
minor,v4l2_type_names[dev->type],dev->users); minor,v4l2_type_names[dev->type],dev->users);
if (!down_read_trylock(&em28xx_disconnect)) mutex_lock(&dev->lock);
return -ERESTARTSYS;
if (dev->users) { if (dev->users) {
em28xx_warn("this driver can be opened only once\n"); em28xx_warn("this driver can be opened only once\n");
up_read(&em28xx_disconnect); mutex_unlock(&dev->lock);
return -EBUSY; return -EBUSY;
} }
mutex_init(&dev->fileop_lock); /* to 1 == available */
spin_lock_init(&dev->queue_lock); spin_lock_init(&dev->queue_lock);
init_waitqueue_head(&dev->wait_frame); init_waitqueue_head(&dev->wait_frame);
init_waitqueue_head(&dev->wait_stream); init_waitqueue_head(&dev->wait_stream);
mutex_lock(&dev->lock);
if (dev->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { if (dev->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
em28xx_set_alternate(dev); em28xx_set_alternate(dev);
@ -306,7 +300,6 @@ static int em28xx_v4l2_open(struct inode *inode, struct file *filp)
err: err:
mutex_unlock(&dev->lock); mutex_unlock(&dev->lock);
up_read(&em28xx_disconnect);
return errCode; return errCode;
} }
@ -317,7 +310,6 @@ err:
*/ */
static void em28xx_release_resources(struct em28xx *dev) static void em28xx_release_resources(struct em28xx *dev)
{ {
mutex_lock(&em28xx_sysfs_lock);
/*FIXME: I2C IR should be disconnected */ /*FIXME: I2C IR should be disconnected */
@ -329,7 +321,6 @@ static void em28xx_release_resources(struct em28xx *dev)
video_unregister_device(dev->vbi_dev); video_unregister_device(dev->vbi_dev);
em28xx_i2c_unregister(dev); em28xx_i2c_unregister(dev);
usb_put_dev(dev->udev); usb_put_dev(dev->udev);
mutex_unlock(&em28xx_sysfs_lock);
/* Mark device as unused */ /* Mark device as unused */
@ -389,6 +380,8 @@ em28xx_v4l2_read(struct file *filp, char __user * buf, size_t count,
int ret = 0; int ret = 0;
struct em28xx *dev = filp->private_data; struct em28xx *dev = filp->private_data;
mutex_lock(&dev->lock);
if (dev->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { if (dev->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
em28xx_videodbg("V4l2_Buf_type_videocapture is set\n"); em28xx_videodbg("V4l2_Buf_type_videocapture is set\n");
} }
@ -396,47 +389,46 @@ em28xx_v4l2_read(struct file *filp, char __user * buf, size_t count,
em28xx_videodbg("V4L2_BUF_TYPE_VBI_CAPTURE is set\n"); em28xx_videodbg("V4L2_BUF_TYPE_VBI_CAPTURE is set\n");
em28xx_videodbg("not supported yet! ...\n"); em28xx_videodbg("not supported yet! ...\n");
if (copy_to_user(buf, "", 1)) { if (copy_to_user(buf, "", 1)) {
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EFAULT; return -EFAULT;
} }
mutex_unlock(&dev->lock);
return (1); return (1);
} }
if (dev->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) { if (dev->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
em28xx_videodbg("V4L2_BUF_TYPE_SLICED_VBI_CAPTURE is set\n"); em28xx_videodbg("V4L2_BUF_TYPE_SLICED_VBI_CAPTURE is set\n");
em28xx_videodbg("not supported yet! ...\n"); em28xx_videodbg("not supported yet! ...\n");
if (copy_to_user(buf, "", 1)) { if (copy_to_user(buf, "", 1)) {
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EFAULT; return -EFAULT;
} }
mutex_unlock(&dev->lock);
return (1); return (1);
} }
if (mutex_lock_interruptible(&dev->fileop_lock))
return -ERESTARTSYS;
if (dev->state & DEV_DISCONNECTED) { if (dev->state & DEV_DISCONNECTED) {
em28xx_videodbg("device not present\n"); em28xx_videodbg("device not present\n");
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -ENODEV; return -ENODEV;
} }
if (dev->state & DEV_MISCONFIGURED) { if (dev->state & DEV_MISCONFIGURED) {
em28xx_videodbg("device misconfigured; close and open it again\n"); em28xx_videodbg("device misconfigured; close and open it again\n");
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EIO; return -EIO;
} }
if (dev->io == IO_MMAP) { if (dev->io == IO_MMAP) {
em28xx_videodbg ("IO method is set to mmap; close and open" em28xx_videodbg ("IO method is set to mmap; close and open"
" the device again to choose the read method\n"); " the device again to choose the read method\n");
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EINVAL; return -EINVAL;
} }
if (dev->io == IO_NONE) { if (dev->io == IO_NONE) {
if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES)) { if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES)) {
em28xx_errdev("read failed, not enough memory\n"); em28xx_errdev("read failed, not enough memory\n");
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -ENOMEM; return -ENOMEM;
} }
dev->io = IO_READ; dev->io = IO_READ;
@ -445,13 +437,13 @@ em28xx_v4l2_read(struct file *filp, char __user * buf, size_t count,
} }
if (!count) { if (!count) {
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return 0; return 0;
} }
if (list_empty(&dev->outqueue)) { if (list_empty(&dev->outqueue)) {
if (filp->f_flags & O_NONBLOCK) { if (filp->f_flags & O_NONBLOCK) {
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EAGAIN; return -EAGAIN;
} }
ret = wait_event_interruptible ret = wait_event_interruptible
@ -459,11 +451,11 @@ em28xx_v4l2_read(struct file *filp, char __user * buf, size_t count,
(!list_empty(&dev->outqueue)) || (!list_empty(&dev->outqueue)) ||
(dev->state & DEV_DISCONNECTED)); (dev->state & DEV_DISCONNECTED));
if (ret) { if (ret) {
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return ret; return ret;
} }
if (dev->state & DEV_DISCONNECTED) { if (dev->state & DEV_DISCONNECTED) {
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -ENODEV; return -ENODEV;
} }
} }
@ -482,12 +474,12 @@ em28xx_v4l2_read(struct file *filp, char __user * buf, size_t count,
count = f->buf.length; count = f->buf.length;
if (copy_to_user(buf, f->bufmem, count)) { if (copy_to_user(buf, f->bufmem, count)) {
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EFAULT; return -EFAULT;
} }
*f_pos += count; *f_pos += count;
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return count; return count;
} }
@ -501,8 +493,7 @@ static unsigned int em28xx_v4l2_poll(struct file *filp, poll_table * wait)
unsigned int mask = 0; unsigned int mask = 0;
struct em28xx *dev = filp->private_data; struct em28xx *dev = filp->private_data;
if (mutex_lock_interruptible(&dev->fileop_lock)) mutex_lock(&dev->lock);
return POLLERR;
if (dev->state & DEV_DISCONNECTED) { if (dev->state & DEV_DISCONNECTED) {
em28xx_videodbg("device not present\n"); em28xx_videodbg("device not present\n");
@ -527,13 +518,13 @@ static unsigned int em28xx_v4l2_poll(struct file *filp, poll_table * wait)
if (!list_empty(&dev->outqueue)) if (!list_empty(&dev->outqueue))
mask |= POLLIN | POLLRDNORM; mask |= POLLIN | POLLRDNORM;
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return mask; return mask;
} }
} }
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return POLLERR; return POLLERR;
} }
@ -575,25 +566,24 @@ static int em28xx_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
struct em28xx *dev = filp->private_data; struct em28xx *dev = filp->private_data;
if (mutex_lock_interruptible(&dev->fileop_lock)) mutex_lock(&dev->lock);
return -ERESTARTSYS;
if (dev->state & DEV_DISCONNECTED) { if (dev->state & DEV_DISCONNECTED) {
em28xx_videodbg("mmap: device not present\n"); em28xx_videodbg("mmap: device not present\n");
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -ENODEV; return -ENODEV;
} }
if (dev->state & DEV_MISCONFIGURED) { if (dev->state & DEV_MISCONFIGURED) {
em28xx_videodbg ("mmap: Device is misconfigured; close and " em28xx_videodbg ("mmap: Device is misconfigured; close and "
"open it again\n"); "open it again\n");
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EIO; return -EIO;
} }
if (dev->io != IO_MMAP || !(vma->vm_flags & VM_WRITE) || if (dev->io != IO_MMAP || !(vma->vm_flags & VM_WRITE) ||
size != PAGE_ALIGN(dev->frame[0].buf.length)) { size != PAGE_ALIGN(dev->frame[0].buf.length)) {
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EINVAL; return -EINVAL;
} }
@ -603,7 +593,7 @@ static int em28xx_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
} }
if (i == dev->num_frames) { if (i == dev->num_frames) {
em28xx_videodbg("mmap: user supplied mapping address is out of range\n"); em28xx_videodbg("mmap: user supplied mapping address is out of range\n");
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EINVAL; return -EINVAL;
} }
@ -615,7 +605,7 @@ static int em28xx_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
while (size > 0) { /* size is page-aligned */ while (size > 0) { /* size is page-aligned */
if (vm_insert_page(vma, start, vmalloc_to_page(pos))) { if (vm_insert_page(vma, start, vmalloc_to_page(pos))) {
em28xx_videodbg("mmap: vm_insert_page failed\n"); em28xx_videodbg("mmap: vm_insert_page failed\n");
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return -EAGAIN; return -EAGAIN;
} }
start += PAGE_SIZE; start += PAGE_SIZE;
@ -627,7 +617,7 @@ static int em28xx_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
vma->vm_private_data = &dev->frame[i]; vma->vm_private_data = &dev->frame[i];
em28xx_vm_open(vma); em28xx_vm_open(vma);
mutex_unlock(&dev->fileop_lock); mutex_unlock(&dev->lock);
return 0; return 0;
} }
@ -1084,7 +1074,9 @@ static int em28xx_do_ioctl(struct inode *inode, struct file *filp,
} }
} }
} }
mutex_lock(&dev->lock);
em28xx_i2c_call_clients(dev,cmd,qc); em28xx_i2c_call_clients(dev,cmd,qc);
mutex_unlock(&dev->lock);
if (qc->type) if (qc->type)
return 0; return 0;
else else
@ -1098,7 +1090,9 @@ static int em28xx_do_ioctl(struct inode *inode, struct file *filp,
if (!dev->has_msp34xx) if (!dev->has_msp34xx)
retval=em28xx_get_ctrl(dev, ctrl); retval=em28xx_get_ctrl(dev, ctrl);
if (retval==-EINVAL) { if (retval==-EINVAL) {
mutex_lock(&dev->lock);
em28xx_i2c_call_clients(dev,cmd,arg); em28xx_i2c_call_clients(dev,cmd,arg);
mutex_unlock(&dev->lock);
return 0; return 0;
} else return retval; } else return retval;
} }
@ -1106,21 +1100,26 @@ static int em28xx_do_ioctl(struct inode *inode, struct file *filp,
{ {
struct v4l2_control *ctrl = arg; struct v4l2_control *ctrl = arg;
u8 i; u8 i;
mutex_lock(&dev->lock);
if (!dev->has_msp34xx){ if (!dev->has_msp34xx){
for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) { for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) {
if (ctrl->id == em28xx_qctrl[i].id) { if (ctrl->id == em28xx_qctrl[i].id) {
int retval=-EINVAL;
if (ctrl->value < if (ctrl->value <
em28xx_qctrl[i].minimum em28xx_qctrl[i].minimum
|| ctrl->value > || ctrl->value >
em28xx_qctrl[i].maximum) em28xx_qctrl[i].maximum)
return -ERANGE; return -ERANGE;
return em28xx_set_ctrl(dev, ctrl); retval = em28xx_set_ctrl(dev, ctrl);
mutex_unlock(&dev->lock);
return retval;
} }
} }
} }
em28xx_i2c_call_clients(dev,cmd,arg); em28xx_i2c_call_clients(dev,cmd,arg);
mutex_unlock(&dev->lock);
return 0; return 0;
} }
/* --- tuner ioctls ------------------------------------------ */ /* --- tuner ioctls ------------------------------------------ */
@ -1220,12 +1219,16 @@ static int em28xx_do_ioctl(struct inode *inode, struct file *filp,
|| dev->io != IO_MMAP) || dev->io != IO_MMAP)
return -EINVAL; return -EINVAL;
mutex_lock(&dev->lock);
if (dev->stream == STREAM_ON) { if (dev->stream == STREAM_ON) {
em28xx_videodbg ("VIDIOC_STREAMOFF: interrupting stream\n"); em28xx_videodbg ("VIDIOC_STREAMOFF: interrupting stream\n");
if ((ret = em28xx_stream_interrupt(dev))) if ((ret = em28xx_stream_interrupt(dev))){
mutex_unlock(&dev->lock);
return ret; return ret;
}
} }
em28xx_empty_framequeues(dev); em28xx_empty_framequeues(dev);
mutex_unlock(&dev->lock);
return 0; return 0;
} }
@ -1291,11 +1294,23 @@ static int em28xx_video_do_ioctl(struct inode *inode, struct file *filp,
return 0; return 0;
} }
case VIDIOC_G_FMT: case VIDIOC_G_FMT:
return em28xx_get_fmt(dev, (struct v4l2_format *) arg); {
int retval;
mutex_lock(&dev->lock);
retval = em28xx_get_fmt(dev, (struct v4l2_format *) arg);
mutex_unlock(&dev->lock);
return retval;
}
case VIDIOC_TRY_FMT: case VIDIOC_TRY_FMT:
case VIDIOC_S_FMT: case VIDIOC_S_FMT:
return em28xx_set_fmt(dev, cmd, (struct v4l2_format *)arg); {
int retval;
mutex_lock(&dev->lock);
retval = em28xx_set_fmt(dev, cmd, (struct v4l2_format *)arg);
mutex_unlock(&dev->lock);
return retval;
}
case VIDIOC_REQBUFS: case VIDIOC_REQBUFS:
{ {
@ -1320,10 +1335,13 @@ static int em28xx_video_do_ioctl(struct inode *inode, struct file *filp,
return -EINVAL; return -EINVAL;
} }
mutex_lock(&dev->lock);
if (dev->stream == STREAM_ON) { if (dev->stream == STREAM_ON) {
em28xx_videodbg("VIDIOC_REQBUFS: interrupting stream\n"); em28xx_videodbg("VIDIOC_REQBUFS: interrupting stream\n");
if ((ret = em28xx_stream_interrupt(dev))) if ((ret = em28xx_stream_interrupt(dev))){
mutex_unlock(&dev->lock);
return ret; return ret;
}
} }
em28xx_empty_framequeues(dev); em28xx_empty_framequeues(dev);
@ -1338,6 +1356,7 @@ static int em28xx_video_do_ioctl(struct inode *inode, struct file *filp,
em28xx_videodbg ("VIDIOC_REQBUFS: setting io method to mmap: num bufs %i\n", em28xx_videodbg ("VIDIOC_REQBUFS: setting io method to mmap: num bufs %i\n",
rb->count); rb->count);
dev->io = rb->count ? IO_MMAP : IO_NONE; dev->io = rb->count ? IO_MMAP : IO_NONE;
mutex_unlock(&dev->lock);
return 0; return 0;
} }
case VIDIOC_QUERYBUF: case VIDIOC_QUERYBUF:
@ -1439,26 +1458,19 @@ static int em28xx_v4l2_ioctl(struct inode *inode, struct file *filp,
int ret = 0; int ret = 0;
struct em28xx *dev = filp->private_data; struct em28xx *dev = filp->private_data;
if (mutex_lock_interruptible(&dev->fileop_lock))
return -ERESTARTSYS;
if (dev->state & DEV_DISCONNECTED) { if (dev->state & DEV_DISCONNECTED) {
em28xx_errdev("v4l2 ioctl: device not present\n"); em28xx_errdev("v4l2 ioctl: device not present\n");
mutex_unlock(&dev->fileop_lock);
return -ENODEV; return -ENODEV;
} }
if (dev->state & DEV_MISCONFIGURED) { if (dev->state & DEV_MISCONFIGURED) {
em28xx_errdev em28xx_errdev
("v4l2 ioctl: device is misconfigured; close and open it again\n"); ("v4l2 ioctl: device is misconfigured; close and open it again\n");
mutex_unlock(&dev->fileop_lock);
return -EIO; return -EIO;
} }
ret = video_usercopy(inode, filp, cmd, arg, em28xx_video_do_ioctl); ret = video_usercopy(inode, filp, cmd, arg, em28xx_video_do_ioctl);
mutex_unlock(&dev->fileop_lock);
return ret; return ret;
} }
@ -1519,8 +1531,6 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
return -ENOMEM; return -ENOMEM;
} }
mutex_lock(&dev->lock);
/* register i2c bus */ /* register i2c bus */
em28xx_i2c_register(dev); em28xx_i2c_register(dev);
@ -1530,8 +1540,6 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
/* configure the device */ /* configure the device */
em28xx_config_i2c(dev); em28xx_config_i2c(dev);
mutex_unlock(&dev->lock);
for (i = 0; i < TVNORMS; i++) for (i = 0; i < TVNORMS; i++)
if (em28xx_boards[dev->model].norm == tvnorms[i].mode) if (em28xx_boards[dev->model].norm == tvnorms[i].mode)
break; break;
@ -1599,8 +1607,18 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
list_add_tail(&dev->devlist,&em28xx_devlist); list_add_tail(&dev->devlist,&em28xx_devlist);
if (dev->has_msp34xx) {
/* Send a reset to other chips via gpio */
em28xx_write_regs_req(dev, 0x00, 0x08, "\xf7", 1);
msleep(3);
em28xx_write_regs_req(dev, 0x00, 0x08, "\xff", 1);
msleep(3);
}
video_mux(dev, 0);
/* register v4l2 device */ /* register v4l2 device */
mutex_lock(&dev->lock);
if ((retval = video_register_device(dev->vdev, VFL_TYPE_GRABBER, if ((retval = video_register_device(dev->vdev, VFL_TYPE_GRABBER,
video_nr[dev->devno]))) { video_nr[dev->devno]))) {
em28xx_errdev("unable to register video device (error=%i).\n", em28xx_errdev("unable to register video device (error=%i).\n",
@ -1627,18 +1645,6 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
printk("registered VBI\n"); printk("registered VBI\n");
} }
if (dev->has_msp34xx) {
/* Send a reset to other chips via gpio */
em28xx_write_regs_req(dev, 0x00, 0x08, "\xf7", 1);
msleep(3);
em28xx_write_regs_req(dev, 0x00, 0x08, "\xff", 1);
msleep(3);
}
video_mux(dev, 0);
mutex_unlock(&dev->lock);
em28xx_info("V4L2 device registered as /dev/video%d and /dev/vbi%d\n", em28xx_info("V4L2 device registered as /dev/video%d and /dev/vbi%d\n",
dev->vdev->minor-MINOR_VFL_TYPE_GRABBER_MIN, dev->vdev->minor-MINOR_VFL_TYPE_GRABBER_MIN,
dev->vbi_dev->minor-MINOR_VFL_TYPE_VBI_MIN); dev->vbi_dev->minor-MINOR_VFL_TYPE_VBI_MIN);
@ -1762,18 +1768,19 @@ static int em28xx_usb_probe(struct usb_interface *interface,
*/ */
static void em28xx_usb_disconnect(struct usb_interface *interface) static void em28xx_usb_disconnect(struct usb_interface *interface)
{ {
struct em28xx *dev = usb_get_intfdata(interface); struct em28xx *dev;
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL); usb_set_intfdata(interface, NULL);
if (!dev) if (!dev)
return; return;
down_write(&em28xx_disconnect);
mutex_lock(&dev->lock);
em28xx_info("disconnecting %s\n", dev->vdev->name); em28xx_info("disconnecting %s\n", dev->vdev->name);
/* wait until all current v4l2 io is finished then deallocate resources */
mutex_lock(&dev->lock);
wake_up_interruptible_all(&dev->open); wake_up_interruptible_all(&dev->open);
if (dev->users) { if (dev->users) {
@ -1792,6 +1799,7 @@ static void em28xx_usb_disconnect(struct usb_interface *interface)
em28xx_release_resources(dev); em28xx_release_resources(dev);
} }
mutex_unlock(&dev->lock); mutex_unlock(&dev->lock);
if (!dev->users) { if (!dev->users) {
@ -1799,7 +1807,6 @@ static void em28xx_usb_disconnect(struct usb_interface *interface)
kfree(dev); kfree(dev);
} }
up_write(&em28xx_disconnect);
} }
static struct usb_driver em28xx_usb_driver = { static struct usb_driver em28xx_usb_driver = {

View File

@ -265,7 +265,7 @@ struct em28xx {
enum em28xx_stream_state stream; enum em28xx_stream_state stream;
enum em28xx_io_method io; enum em28xx_io_method io;
/* locks */ /* locks */
struct mutex lock, fileop_lock; struct mutex lock;
spinlock_t queue_lock; spinlock_t queue_lock;
struct list_head inqueue, outqueue; struct list_head inqueue, outqueue;
wait_queue_head_t open, wait_frame, wait_stream; wait_queue_head_t open, wait_frame, wait_stream;