diff options
author | Oliver Neukum <oliver@neukum.org> | 2007-09-26 09:19:01 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@infradead.org> | 2007-10-09 21:14:49 -0400 |
commit | 0b67f5c568c545cb36f88e9f418af2df1cc58589 (patch) | |
tree | 71fddc45a7e9ea0d5b58c06577e155824b4d7a09 /drivers/media/video/pwc/pwc-if.c | |
parent | 23869e236846657415654e8f5fbda9faec8d19e4 (diff) |
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: Oliver Neukum <oneukum@suse.de>
CC: Luc Saillard <luc@saillard.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
Diffstat (limited to 'drivers/media/video/pwc/pwc-if.c')
-rw-r--r-- | drivers/media/video/pwc/pwc-if.c | 96 |
1 files changed, 72 insertions, 24 deletions
diff --git a/drivers/media/video/pwc/pwc-if.c b/drivers/media/video/pwc/pwc-if.c index 1088ebf5744f..0ff5718bf9b9 100644 --- a/drivers/media/video/pwc/pwc-if.c +++ b/drivers/media/video/pwc/pwc-if.c | |||
@@ -907,31 +907,49 @@ int pwc_isoc_init(struct pwc_device *pdev) | |||
907 | return 0; | 907 | return 0; |
908 | } | 908 | } |
909 | 909 | ||
910 | void pwc_isoc_cleanup(struct pwc_device *pdev) | 910 | static void pwc_iso_stop(struct pwc_device *pdev) |
911 | { | 911 | { |
912 | int i; | 912 | int i; |
913 | 913 | ||
914 | PWC_DEBUG_OPEN(">> pwc_isoc_cleanup()\n"); | ||
915 | if (pdev == NULL) | ||
916 | return; | ||
917 | if (pdev->iso_init == 0) | ||
918 | return; | ||
919 | |||
920 | /* Unlinking ISOC buffers one by one */ | 914 | /* Unlinking ISOC buffers one by one */ |
921 | for (i = 0; i < MAX_ISO_BUFS; i++) { | 915 | for (i = 0; i < MAX_ISO_BUFS; i++) { |
922 | struct urb *urb; | 916 | struct urb *urb; |
923 | 917 | ||
924 | urb = pdev->sbuf[i].urb; | 918 | urb = pdev->sbuf[i].urb; |
925 | if (urb != 0) { | 919 | if (urb != 0) { |
926 | if (pdev->iso_init) { | 920 | PWC_DEBUG_MEMORY("Unlinking URB %p\n", urb); |
927 | PWC_DEBUG_MEMORY("Unlinking URB %p\n", urb); | 921 | usb_kill_urb(urb); |
928 | usb_kill_urb(urb); | 922 | } |
929 | } | 923 | } |
924 | } | ||
925 | |||
926 | static void pwc_iso_free(struct pwc_device *pdev) | ||
927 | { | ||
928 | int i; | ||
929 | |||
930 | /* Freeing ISOC buffers one by one */ | ||
931 | for (i = 0; i < MAX_ISO_BUFS; i++) { | ||
932 | struct urb *urb; | ||
933 | |||
934 | urb = pdev->sbuf[i].urb; | ||
935 | if (urb != 0) { | ||
930 | PWC_DEBUG_MEMORY("Freeing URB\n"); | 936 | PWC_DEBUG_MEMORY("Freeing URB\n"); |
931 | usb_free_urb(urb); | 937 | usb_free_urb(urb); |
932 | pdev->sbuf[i].urb = NULL; | 938 | pdev->sbuf[i].urb = NULL; |
933 | } | 939 | } |
934 | } | 940 | } |
941 | } | ||
942 | |||
943 | void pwc_isoc_cleanup(struct pwc_device *pdev) | ||
944 | { | ||
945 | PWC_DEBUG_OPEN(">> pwc_isoc_cleanup()\n"); | ||
946 | if (pdev == NULL) | ||
947 | return; | ||
948 | if (pdev->iso_init == 0) | ||
949 | return; | ||
950 | |||
951 | pwc_iso_stop(pdev); | ||
952 | pwc_iso_free(pdev); | ||
935 | 953 | ||
936 | /* Stop camera, but only if we are sure the camera is still there (unplug | 954 | /* Stop camera, but only if we are sure the camera is still there (unplug |
937 | is signalled by EPIPE) | 955 | is signalled by EPIPE) |
@@ -1211,6 +1229,7 @@ static int pwc_video_close(struct inode *inode, struct file *file) | |||
1211 | 1229 | ||
1212 | PWC_DEBUG_OPEN(">> video_close called(vdev = 0x%p).\n", vdev); | 1230 | PWC_DEBUG_OPEN(">> video_close called(vdev = 0x%p).\n", vdev); |
1213 | 1231 | ||
1232 | lock_kernel(); | ||
1214 | pdev = (struct pwc_device *)vdev->priv; | 1233 | pdev = (struct pwc_device *)vdev->priv; |
1215 | if (pdev->vopen == 0) | 1234 | if (pdev->vopen == 0) |
1216 | PWC_DEBUG_MODULE("video_close() called on closed device?\n"); | 1235 | 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 | pwc_isoc_cleanup(pdev); | 1249 | pwc_isoc_cleanup(pdev); |
1231 | pwc_free_buffers(pdev); | 1250 | pwc_free_buffers(pdev); |
1232 | 1251 | ||
1233 | lock_kernel(); | ||
1234 | /* Turn off LEDS and power down camera, but only when not unplugged */ | 1252 | /* Turn off LEDS and power down camera, but only when not unplugged */ |
1235 | if (!pdev->unplugged) { | 1253 | if (!pdev->unplugged) { |
1236 | /* Turn LEDs off */ | 1254 | /* Turn LEDs off */ |
@@ -1276,7 +1294,7 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, | |||
1276 | struct pwc_device *pdev; | 1294 | struct pwc_device *pdev; |
1277 | int noblock = file->f_flags & O_NONBLOCK; | 1295 | int noblock = file->f_flags & O_NONBLOCK; |
1278 | DECLARE_WAITQUEUE(wait, current); | 1296 | DECLARE_WAITQUEUE(wait, current); |
1279 | int bytes_to_read; | 1297 | int bytes_to_read, rv = 0; |
1280 | void *image_buffer_addr; | 1298 | void *image_buffer_addr; |
1281 | 1299 | ||
1282 | PWC_DEBUG_READ("pwc_video_read(vdev=0x%p, buf=%p, count=%zd) called.\n", | 1300 | 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 | pdev = vdev->priv; | 1304 | pdev = vdev->priv; |
1287 | if (pdev == NULL) | 1305 | if (pdev == NULL) |
1288 | return -EFAULT; | 1306 | return -EFAULT; |
1289 | if (pdev->error_status) | 1307 | |
1290 | return -pdev->error_status; /* Something happened, report what. */ | 1308 | mutex_lock(&pdev->modlock); |
1309 | if (pdev->error_status) { | ||
1310 | rv = -pdev->error_status; /* Something happened, report what. */ | ||
1311 | goto err_out; | ||
1312 | } | ||
1291 | 1313 | ||
1292 | /* In case we're doing partial reads, we don't have to wait for a frame */ | 1314 | /* In case we're doing partial reads, we don't have to wait for a frame */ |
1293 | if (pdev->image_read_pos == 0) { | 1315 | if (pdev->image_read_pos == 0) { |
@@ -1298,17 +1320,20 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, | |||
1298 | if (pdev->error_status) { | 1320 | if (pdev->error_status) { |
1299 | remove_wait_queue(&pdev->frameq, &wait); | 1321 | remove_wait_queue(&pdev->frameq, &wait); |
1300 | set_current_state(TASK_RUNNING); | 1322 | set_current_state(TASK_RUNNING); |
1301 | return -pdev->error_status ; | 1323 | rv = -pdev->error_status ; |
1324 | goto err_out; | ||
1302 | } | 1325 | } |
1303 | if (noblock) { | 1326 | if (noblock) { |
1304 | remove_wait_queue(&pdev->frameq, &wait); | 1327 | remove_wait_queue(&pdev->frameq, &wait); |
1305 | set_current_state(TASK_RUNNING); | 1328 | set_current_state(TASK_RUNNING); |
1306 | return -EWOULDBLOCK; | 1329 | rv = -EWOULDBLOCK; |
1330 | goto err_out; | ||
1307 | } | 1331 | } |
1308 | if (signal_pending(current)) { | 1332 | if (signal_pending(current)) { |
1309 | remove_wait_queue(&pdev->frameq, &wait); | 1333 | remove_wait_queue(&pdev->frameq, &wait); |
1310 | set_current_state(TASK_RUNNING); | 1334 | set_current_state(TASK_RUNNING); |
1311 | return -ERESTARTSYS; | 1335 | rv = -ERESTARTSYS; |
1336 | goto err_out; | ||
1312 | } | 1337 | } |
1313 | schedule(); | 1338 | schedule(); |
1314 | set_current_state(TASK_INTERRUPTIBLE); | 1339 | set_current_state(TASK_INTERRUPTIBLE); |
@@ -1317,8 +1342,10 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf, | |||
1317 | set_current_state(TASK_RUNNING); | 1342 | set_current_state(TASK_RUNNING); |
1318 | 1343 | ||
1319 | /* Decompress and release frame */ | 1344 | /* Decompress and release frame */ |
1320 | if (pwc_handle_frame(pdev)) | 1345 | if (pwc_handle_frame(pdev)) { |
1321 | return -EFAULT; | 1346 | rv = -EFAULT; |
1347 | goto err_out; | ||
1348 | } | ||
1322 | } | 1349 | } |
1323 | 1350 | ||
1324 | PWC_DEBUG_READ("Copying data to user space.\n"); | 1351 | 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 | image_buffer_addr = pdev->image_data; | 1360 | image_buffer_addr = pdev->image_data; |
1334 | image_buffer_addr += pdev->images[pdev->fill_image].offset; | 1361 | image_buffer_addr += pdev->images[pdev->fill_image].offset; |
1335 | image_buffer_addr += pdev->image_read_pos; | 1362 | image_buffer_addr += pdev->image_read_pos; |
1336 | if (copy_to_user(buf, image_buffer_addr, count)) | 1363 | if (copy_to_user(buf, image_buffer_addr, count)) { |
1337 | return -EFAULT; | 1364 | rv = -EFAULT; |
1365 | goto err_out; | ||
1366 | } | ||
1338 | pdev->image_read_pos += count; | 1367 | pdev->image_read_pos += count; |
1339 | if (pdev->image_read_pos >= bytes_to_read) { /* All data has been read */ | 1368 | if (pdev->image_read_pos >= bytes_to_read) { /* All data has been read */ |
1340 | pdev->image_read_pos = 0; | 1369 | pdev->image_read_pos = 0; |
1341 | pwc_next_image(pdev); | 1370 | pwc_next_image(pdev); |
1342 | } | 1371 | } |
1372 | mutex_unlock(&pdev->modlock); | ||
1343 | return count; | 1373 | return count; |
1374 | err_out: | ||
1375 | mutex_unlock(&pdev->modlock); | ||
1376 | return rv; | ||
1344 | } | 1377 | } |
1345 | 1378 | ||
1346 | static unsigned int pwc_video_poll(struct file *file, poll_table *wait) | 1379 | 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 | static int pwc_video_ioctl(struct inode *inode, struct file *file, | 1399 | static int pwc_video_ioctl(struct inode *inode, struct file *file, |
1367 | unsigned int cmd, unsigned long arg) | 1400 | unsigned int cmd, unsigned long arg) |
1368 | { | 1401 | { |
1369 | return video_usercopy(inode, file, cmd, arg, pwc_video_do_ioctl); | 1402 | struct video_device *vdev = file->private_data; |
1403 | struct pwc_device *pdev; | ||
1404 | int r = -ENODEV; | ||
1405 | |||
1406 | if (!vdev) | ||
1407 | goto out; | ||
1408 | pdev = vdev->priv; | ||
1409 | |||
1410 | mutex_lock(&pdev->modlock); | ||
1411 | if (!pdev->unplugged) | ||
1412 | r = video_usercopy(inode, file, cmd, arg, pwc_video_do_ioctl); | ||
1413 | mutex_unlock(&pdev->modlock); | ||
1414 | out: | ||
1415 | return r; | ||
1370 | } | 1416 | } |
1371 | 1417 | ||
1372 | static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma) | 1418 | 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 | wake_up_interruptible(&pdev->frameq); | 1855 | wake_up_interruptible(&pdev->frameq); |
1810 | /* Wait until device is closed */ | 1856 | /* Wait until device is closed */ |
1811 | if(pdev->vopen) { | 1857 | if(pdev->vopen) { |
1858 | mutex_lock(&pdev->modlock); | ||
1812 | pdev->unplugged = 1; | 1859 | pdev->unplugged = 1; |
1860 | mutex_unlock(&pdev->modlock); | ||
1861 | pwc_iso_stop(pdev); | ||
1813 | } else { | 1862 | } else { |
1814 | /* Device is closed, so we can safely unregister it */ | 1863 | /* Device is closed, so we can safely unregister it */ |
1815 | PWC_DEBUG_PROBE("Unregistering video device in disconnect().\n"); | 1864 | PWC_DEBUG_PROBE("Unregistering video device in disconnect().\n"); |
@@ -1827,7 +1876,6 @@ disconnect_out: | |||
1827 | unlock_kernel(); | 1876 | unlock_kernel(); |
1828 | } | 1877 | } |
1829 | 1878 | ||
1830 | |||
1831 | /* *grunt* We have to do atoi ourselves :-( */ | 1879 | /* *grunt* We have to do atoi ourselves :-( */ |
1832 | static int pwc_atoi(const char *s) | 1880 | static int pwc_atoi(const char *s) |
1833 | { | 1881 | { |