Commit 0b67f5c5 authored by Oliver Neukum's avatar Oliver Neukum Committed by Mauro Carvalho Chehab

V4L/DVB (6237): Oops in pwc v4l driver

The pwc driver is defficient in locking, which can trigger an oops
when disconnecting.
Signed-off-by: default avatarOliver Neukum <oneukum@suse.de>
CC: Luc Saillard <luc@saillard.org>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@infradead.org>
parent 23869e23
...@@ -907,31 +907,49 @@ int pwc_isoc_init(struct pwc_device *pdev) ...@@ -907,31 +907,49 @@ int pwc_isoc_init(struct pwc_device *pdev)
return 0; return 0;
} }
void pwc_isoc_cleanup(struct pwc_device *pdev) static void pwc_iso_stop(struct pwc_device *pdev)
{ {
int i; int i;
PWC_DEBUG_OPEN(">> pwc_isoc_cleanup()\n");
if (pdev == NULL)
return;
if (pdev->iso_init == 0)
return;
/* Unlinking ISOC buffers one by one */ /* Unlinking ISOC buffers one by one */
for (i = 0; i < MAX_ISO_BUFS; i++) { for (i = 0; i < MAX_ISO_BUFS; i++) {
struct urb *urb; struct urb *urb;
urb = pdev->sbuf[i].urb; urb = pdev->sbuf[i].urb;
if (urb != 0) { if (urb != 0) {
if (pdev->iso_init) { PWC_DEBUG_MEMORY("Unlinking URB %p\n", urb);
PWC_DEBUG_MEMORY("Unlinking URB %p\n", urb); usb_kill_urb(urb);
usb_kill_urb(urb); }
} }
}
static void pwc_iso_free(struct pwc_device *pdev)
{
int i;
/* Freeing ISOC buffers one by one */
for (i = 0; i < MAX_ISO_BUFS; i++) {
struct urb *urb;
urb = pdev->sbuf[i].urb;
if (urb != 0) {
PWC_DEBUG_MEMORY("Freeing URB\n"); PWC_DEBUG_MEMORY("Freeing URB\n");
usb_free_urb(urb); usb_free_urb(urb);
pdev->sbuf[i].urb = NULL; pdev->sbuf[i].urb = NULL;
} }
} }
}
void pwc_isoc_cleanup(struct pwc_device *pdev)
{
PWC_DEBUG_OPEN(">> pwc_isoc_cleanup()\n");
if (pdev == NULL)
return;
if (pdev->iso_init == 0)
return;
pwc_iso_stop(pdev);
pwc_iso_free(pdev);
/* Stop camera, but only if we are sure the camera is still there (unplug /* Stop camera, but only if we are sure the camera is still there (unplug
is signalled by EPIPE) is signalled by EPIPE)
...@@ -1211,6 +1229,7 @@ static int pwc_video_close(struct inode *inode, struct file *file) ...@@ -1211,6 +1229,7 @@ static int pwc_video_close(struct inode *inode, struct file *file)
PWC_DEBUG_OPEN(">> video_close called(vdev = 0x%p).\n", vdev); PWC_DEBUG_OPEN(">> video_close called(vdev = 0x%p).\n", vdev);
lock_kernel();
pdev = (struct pwc_device *)vdev->priv; pdev = (struct pwc_device *)vdev->priv;
if (pdev->vopen == 0) if (pdev->vopen == 0)
PWC_DEBUG_MODULE("video_close() called on closed device?\n"); PWC_DEBUG_MODULE("video_close() called on closed device?\n");
...@@ -1230,7 +1249,6 @@ static int pwc_video_close(struct inode *inode, struct file *file) ...@@ -1230,7 +1249,6 @@ static int pwc_video_close(struct inode *inode, struct file *file)
pwc_isoc_cleanup(pdev); pwc_isoc_cleanup(pdev);
pwc_free_buffers(pdev); pwc_free_buffers(pdev);
lock_kernel();
/* Turn off LEDS and power down camera, but only when not unplugged */ /* Turn off LEDS and power down camera, but only when not unplugged */
if (!pdev->unplugged) { if (!pdev->unplugged) {
/* Turn LEDs off */ /* Turn LEDs off */
...@@ -1276,7 +1294,7 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, ...@@ -1276,7 +1294,7 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf,
struct pwc_device *pdev; struct pwc_device *pdev;
int noblock = file->f_flags & O_NONBLOCK; int noblock = file->f_flags & O_NONBLOCK;
DECLARE_WAITQUEUE(wait, current); DECLARE_WAITQUEUE(wait, current);
int bytes_to_read; int bytes_to_read, rv = 0;
void *image_buffer_addr; void *image_buffer_addr;
PWC_DEBUG_READ("pwc_video_read(vdev=0x%p, buf=%p, count=%zd) called.\n", PWC_DEBUG_READ("pwc_video_read(vdev=0x%p, buf=%p, count=%zd) called.\n",
...@@ -1286,8 +1304,12 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, ...@@ -1286,8 +1304,12 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf,
pdev = vdev->priv; pdev = vdev->priv;
if (pdev == NULL) if (pdev == NULL)
return -EFAULT; return -EFAULT;
if (pdev->error_status)
return -pdev->error_status; /* Something happened, report what. */ mutex_lock(&pdev->modlock);
if (pdev->error_status) {
rv = -pdev->error_status; /* Something happened, report what. */
goto err_out;
}
/* In case we're doing partial reads, we don't have to wait for a frame */ /* In case we're doing partial reads, we don't have to wait for a frame */
if (pdev->image_read_pos == 0) { if (pdev->image_read_pos == 0) {
...@@ -1298,17 +1320,20 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, ...@@ -1298,17 +1320,20 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf,
if (pdev->error_status) { if (pdev->error_status) {
remove_wait_queue(&pdev->frameq, &wait); remove_wait_queue(&pdev->frameq, &wait);
set_current_state(TASK_RUNNING); set_current_state(TASK_RUNNING);
return -pdev->error_status ; rv = -pdev->error_status ;
goto err_out;
} }
if (noblock) { if (noblock) {
remove_wait_queue(&pdev->frameq, &wait); remove_wait_queue(&pdev->frameq, &wait);
set_current_state(TASK_RUNNING); set_current_state(TASK_RUNNING);
return -EWOULDBLOCK; rv = -EWOULDBLOCK;
goto err_out;
} }
if (signal_pending(current)) { if (signal_pending(current)) {
remove_wait_queue(&pdev->frameq, &wait); remove_wait_queue(&pdev->frameq, &wait);
set_current_state(TASK_RUNNING); set_current_state(TASK_RUNNING);
return -ERESTARTSYS; rv = -ERESTARTSYS;
goto err_out;
} }
schedule(); schedule();
set_current_state(TASK_INTERRUPTIBLE); set_current_state(TASK_INTERRUPTIBLE);
...@@ -1317,8 +1342,10 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, ...@@ -1317,8 +1342,10 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf,
set_current_state(TASK_RUNNING); set_current_state(TASK_RUNNING);
/* Decompress and release frame */ /* Decompress and release frame */
if (pwc_handle_frame(pdev)) if (pwc_handle_frame(pdev)) {
return -EFAULT; rv = -EFAULT;
goto err_out;
}
} }
PWC_DEBUG_READ("Copying data to user space.\n"); PWC_DEBUG_READ("Copying data to user space.\n");
...@@ -1333,14 +1360,20 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, ...@@ -1333,14 +1360,20 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf,
image_buffer_addr = pdev->image_data; image_buffer_addr = pdev->image_data;
image_buffer_addr += pdev->images[pdev->fill_image].offset; image_buffer_addr += pdev->images[pdev->fill_image].offset;
image_buffer_addr += pdev->image_read_pos; image_buffer_addr += pdev->image_read_pos;
if (copy_to_user(buf, image_buffer_addr, count)) if (copy_to_user(buf, image_buffer_addr, count)) {
return -EFAULT; rv = -EFAULT;
goto err_out;
}
pdev->image_read_pos += count; pdev->image_read_pos += count;
if (pdev->image_read_pos >= bytes_to_read) { /* All data has been read */ if (pdev->image_read_pos >= bytes_to_read) { /* All data has been read */
pdev->image_read_pos = 0; pdev->image_read_pos = 0;
pwc_next_image(pdev); pwc_next_image(pdev);
} }
mutex_unlock(&pdev->modlock);
return count; return count;
err_out:
mutex_unlock(&pdev->modlock);
return rv;
} }
static unsigned int pwc_video_poll(struct file *file, poll_table *wait) static unsigned int pwc_video_poll(struct file *file, poll_table *wait)
...@@ -1366,7 +1399,20 @@ static unsigned int pwc_video_poll(struct file *file, poll_table *wait) ...@@ -1366,7 +1399,20 @@ static unsigned int pwc_video_poll(struct file *file, poll_table *wait)
static int pwc_video_ioctl(struct inode *inode, struct file *file, static int pwc_video_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg) unsigned int cmd, unsigned long arg)
{ {
return video_usercopy(inode, file, cmd, arg, pwc_video_do_ioctl); struct video_device *vdev = file->private_data;
struct pwc_device *pdev;
int r = -ENODEV;
if (!vdev)
goto out;
pdev = vdev->priv;
mutex_lock(&pdev->modlock);
if (!pdev->unplugged)
r = video_usercopy(inode, file, cmd, arg, pwc_video_do_ioctl);
mutex_unlock(&pdev->modlock);
out:
return r;
} }
static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma) static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma)
...@@ -1809,7 +1855,10 @@ static void usb_pwc_disconnect(struct usb_interface *intf) ...@@ -1809,7 +1855,10 @@ static void usb_pwc_disconnect(struct usb_interface *intf)
wake_up_interruptible(&pdev->frameq); wake_up_interruptible(&pdev->frameq);
/* Wait until device is closed */ /* Wait until device is closed */
if(pdev->vopen) { if(pdev->vopen) {
mutex_lock(&pdev->modlock);
pdev->unplugged = 1; pdev->unplugged = 1;
mutex_unlock(&pdev->modlock);
pwc_iso_stop(pdev);
} else { } else {
/* Device is closed, so we can safely unregister it */ /* Device is closed, so we can safely unregister it */
PWC_DEBUG_PROBE("Unregistering video device in disconnect().\n"); PWC_DEBUG_PROBE("Unregistering video device in disconnect().\n");
...@@ -1827,7 +1876,6 @@ static void usb_pwc_disconnect(struct usb_interface *intf) ...@@ -1827,7 +1876,6 @@ static void usb_pwc_disconnect(struct usb_interface *intf)
unlock_kernel(); unlock_kernel();
} }
/* *grunt* We have to do atoi ourselves :-( */ /* *grunt* We have to do atoi ourselves :-( */
static int pwc_atoi(const char *s) static int pwc_atoi(const char *s)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment