diff options
author | Oliver Neukum <oliver@neukum.org> | 2008-12-16 04:55:15 -0500 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2009-01-03 19:00:53 -0500 |
commit | 079034073faf974973baa0256b029451f6e768ad (patch) | |
tree | 4f6c083dcf3585e28b7540d7358e3f89bdbc9b0c | |
parent | 42859e0bd21daba9974757fcfe4a4dde265fe28d (diff) |
HID: hiddev cleanup -- handle all error conditions properly
This is a cleanup of hiddev and fixes the following issues:
- thread safety by locking in read & ioctl, introducing a per device mutex
- race between ioctl and disconnect, introducing a flag and locking
in form of a per low level device mutex
- race between open and other methods, making sure only successfully
opened devices are put on the list, changing order of events
- range checking both upper and lower limits of the minor range
- make sure further calls to open fail for unplugged devices even if
the device still has opened files
- error checking for low level open
- possible loss of wakeup events, using standard waiting macros
- race in initialisation by moving registration after full initialisation
Signed-off-by: Oliver Neukum <oneukum@suse.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r-- | drivers/hid/usbhid/hiddev.c | 135 |
1 files changed, 99 insertions, 36 deletions
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 83e851a5ed30..6a98f9f572b0 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c | |||
@@ -49,6 +49,7 @@ | |||
49 | struct hiddev { | 49 | struct hiddev { |
50 | int exist; | 50 | int exist; |
51 | int open; | 51 | int open; |
52 | struct mutex existancelock; | ||
52 | wait_queue_head_t wait; | 53 | wait_queue_head_t wait; |
53 | struct hid_device *hid; | 54 | struct hid_device *hid; |
54 | struct list_head list; | 55 | struct list_head list; |
@@ -63,6 +64,7 @@ struct hiddev_list { | |||
63 | struct fasync_struct *fasync; | 64 | struct fasync_struct *fasync; |
64 | struct hiddev *hiddev; | 65 | struct hiddev *hiddev; |
65 | struct list_head node; | 66 | struct list_head node; |
67 | struct mutex thread_lock; | ||
66 | }; | 68 | }; |
67 | 69 | ||
68 | static struct hiddev *hiddev_table[HIDDEV_MINORS]; | 70 | static struct hiddev *hiddev_table[HIDDEV_MINORS]; |
@@ -264,29 +266,48 @@ static int hiddev_release(struct inode * inode, struct file * file) | |||
264 | static int hiddev_open(struct inode *inode, struct file *file) | 266 | static int hiddev_open(struct inode *inode, struct file *file) |
265 | { | 267 | { |
266 | struct hiddev_list *list; | 268 | struct hiddev_list *list; |
267 | unsigned long flags; | 269 | int res; |
268 | 270 | ||
269 | int i = iminor(inode) - HIDDEV_MINOR_BASE; | 271 | int i = iminor(inode) - HIDDEV_MINOR_BASE; |
270 | 272 | ||
271 | if (i >= HIDDEV_MINORS || !hiddev_table[i]) | 273 | if (i >= HIDDEV_MINORS || i < 0 || !hiddev_table[i]) |
272 | return -ENODEV; | 274 | return -ENODEV; |
273 | 275 | ||
274 | if (!(list = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL))) | 276 | if (!(list = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL))) |
275 | return -ENOMEM; | 277 | return -ENOMEM; |
278 | mutex_init(&list->thread_lock); | ||
276 | 279 | ||
277 | list->hiddev = hiddev_table[i]; | 280 | list->hiddev = hiddev_table[i]; |
278 | 281 | ||
279 | spin_lock_irqsave(&list->hiddev->list_lock, flags); | ||
280 | list_add_tail(&list->node, &hiddev_table[i]->list); | ||
281 | spin_unlock_irqrestore(&list->hiddev->list_lock, flags); | ||
282 | 282 | ||
283 | file->private_data = list; | 283 | file->private_data = list; |
284 | 284 | ||
285 | if (!list->hiddev->open++) | 285 | /* |
286 | if (list->hiddev->exist) | 286 | * no need for locking because the USB major number |
287 | usbhid_open(hiddev_table[i]->hid); | 287 | * is shared which usbcore guards against disconnect |
288 | */ | ||
289 | if (list->hiddev->exist) { | ||
290 | if (!list->hiddev->open++) { | ||
291 | res = usbhid_open(hiddev_table[i]->hid); | ||
292 | if (res < 0) { | ||
293 | res = -EIO; | ||
294 | goto bail; | ||
295 | } | ||
296 | } | ||
297 | } else { | ||
298 | res = -ENODEV; | ||
299 | goto bail; | ||
300 | } | ||
301 | |||
302 | spin_lock_irq(&list->hiddev->list_lock); | ||
303 | list_add_tail(&list->node, &hiddev_table[i]->list); | ||
304 | spin_unlock_irq(&list->hiddev->list_lock); | ||
288 | 305 | ||
289 | return 0; | 306 | return 0; |
307 | bail: | ||
308 | file->private_data = NULL; | ||
309 | kfree(list->hiddev); | ||
310 | return res; | ||
290 | } | 311 | } |
291 | 312 | ||
292 | /* | 313 | /* |
@@ -305,7 +326,7 @@ static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t coun | |||
305 | DECLARE_WAITQUEUE(wait, current); | 326 | DECLARE_WAITQUEUE(wait, current); |
306 | struct hiddev_list *list = file->private_data; | 327 | struct hiddev_list *list = file->private_data; |
307 | int event_size; | 328 | int event_size; |
308 | int retval = 0; | 329 | int retval; |
309 | 330 | ||
310 | event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ? | 331 | event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ? |
311 | sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event); | 332 | sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event); |
@@ -313,10 +334,14 @@ static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t coun | |||
313 | if (count < event_size) | 334 | if (count < event_size) |
314 | return 0; | 335 | return 0; |
315 | 336 | ||
337 | /* lock against other threads */ | ||
338 | retval = mutex_lock_interruptible(&list->thread_lock); | ||
339 | if (retval) | ||
340 | return -ERESTARTSYS; | ||
341 | |||
316 | while (retval == 0) { | 342 | while (retval == 0) { |
317 | if (list->head == list->tail) { | 343 | if (list->head == list->tail) { |
318 | add_wait_queue(&list->hiddev->wait, &wait); | 344 | prepare_to_wait(&list->hiddev->wait, &wait, TASK_INTERRUPTIBLE); |
319 | set_current_state(TASK_INTERRUPTIBLE); | ||
320 | 345 | ||
321 | while (list->head == list->tail) { | 346 | while (list->head == list->tail) { |
322 | if (file->f_flags & O_NONBLOCK) { | 347 | if (file->f_flags & O_NONBLOCK) { |
@@ -332,35 +357,45 @@ static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t coun | |||
332 | break; | 357 | break; |
333 | } | 358 | } |
334 | 359 | ||
360 | /* let O_NONBLOCK tasks run */ | ||
361 | mutex_unlock(&list->thread_lock); | ||
335 | schedule(); | 362 | schedule(); |
363 | if (mutex_lock_interruptible(&list->thread_lock)) | ||
364 | return -EINTR; | ||
336 | set_current_state(TASK_INTERRUPTIBLE); | 365 | set_current_state(TASK_INTERRUPTIBLE); |
337 | } | 366 | } |
367 | finish_wait(&list->hiddev->wait, &wait); | ||
338 | 368 | ||
339 | set_current_state(TASK_RUNNING); | ||
340 | remove_wait_queue(&list->hiddev->wait, &wait); | ||
341 | } | 369 | } |
342 | 370 | ||
343 | if (retval) | 371 | if (retval) { |
372 | mutex_unlock(&list->thread_lock); | ||
344 | return retval; | 373 | return retval; |
374 | } | ||
345 | 375 | ||
346 | 376 | ||
347 | while (list->head != list->tail && | 377 | while (list->head != list->tail && |
348 | retval + event_size <= count) { | 378 | retval + event_size <= count) { |
349 | if ((list->flags & HIDDEV_FLAG_UREF) == 0) { | 379 | if ((list->flags & HIDDEV_FLAG_UREF) == 0) { |
350 | if (list->buffer[list->tail].field_index != | 380 | if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE) { |
351 | HID_FIELD_INDEX_NONE) { | ||
352 | struct hiddev_event event; | 381 | struct hiddev_event event; |
382 | |||
353 | event.hid = list->buffer[list->tail].usage_code; | 383 | event.hid = list->buffer[list->tail].usage_code; |
354 | event.value = list->buffer[list->tail].value; | 384 | event.value = list->buffer[list->tail].value; |
355 | if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event))) | 385 | if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event))) { |
386 | mutex_unlock(&list->thread_lock); | ||
356 | return -EFAULT; | 387 | return -EFAULT; |
388 | } | ||
357 | retval += sizeof(struct hiddev_event); | 389 | retval += sizeof(struct hiddev_event); |
358 | } | 390 | } |
359 | } else { | 391 | } else { |
360 | if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE || | 392 | if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE || |
361 | (list->flags & HIDDEV_FLAG_REPORT) != 0) { | 393 | (list->flags & HIDDEV_FLAG_REPORT) != 0) { |
362 | if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref))) | 394 | |
395 | if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref))) { | ||
396 | mutex_unlock(&list->thread_lock); | ||
363 | return -EFAULT; | 397 | return -EFAULT; |
398 | } | ||
364 | retval += sizeof(struct hiddev_usage_ref); | 399 | retval += sizeof(struct hiddev_usage_ref); |
365 | } | 400 | } |
366 | } | 401 | } |
@@ -368,6 +403,7 @@ static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t coun | |||
368 | } | 403 | } |
369 | 404 | ||
370 | } | 405 | } |
406 | mutex_unlock(&list->thread_lock); | ||
371 | 407 | ||
372 | return retval; | 408 | return retval; |
373 | } | 409 | } |
@@ -555,7 +591,7 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||
555 | struct hid_field *field; | 591 | struct hid_field *field; |
556 | struct usbhid_device *usbhid = hid->driver_data; | 592 | struct usbhid_device *usbhid = hid->driver_data; |
557 | void __user *user_arg = (void __user *)arg; | 593 | void __user *user_arg = (void __user *)arg; |
558 | int i; | 594 | int i, r; |
559 | 595 | ||
560 | /* Called without BKL by compat methods so no BKL taken */ | 596 | /* Called without BKL by compat methods so no BKL taken */ |
561 | 597 | ||
@@ -619,10 +655,22 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||
619 | } | 655 | } |
620 | 656 | ||
621 | case HIDIOCGSTRING: | 657 | case HIDIOCGSTRING: |
622 | return hiddev_ioctl_string(hiddev, cmd, user_arg); | 658 | mutex_lock(&hiddev->existancelock); |
659 | if (!hiddev->exist) | ||
660 | r = hiddev_ioctl_string(hiddev, cmd, user_arg); | ||
661 | else | ||
662 | r = -ENODEV; | ||
663 | mutex_unlock(&hiddev->existancelock); | ||
664 | return r; | ||
623 | 665 | ||
624 | case HIDIOCINITREPORT: | 666 | case HIDIOCINITREPORT: |
667 | mutex_lock(&hiddev->existancelock); | ||
668 | if (!hiddev->exist) { | ||
669 | mutex_unlock(&hiddev->existancelock); | ||
670 | return -ENODEV; | ||
671 | } | ||
625 | usbhid_init_reports(hid); | 672 | usbhid_init_reports(hid); |
673 | mutex_unlock(&hiddev->existancelock); | ||
626 | 674 | ||
627 | return 0; | 675 | return 0; |
628 | 676 | ||
@@ -636,8 +684,12 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||
636 | if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) | 684 | if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) |
637 | return -EINVAL; | 685 | return -EINVAL; |
638 | 686 | ||
639 | usbhid_submit_report(hid, report, USB_DIR_IN); | 687 | mutex_lock(&hiddev->existancelock); |
640 | usbhid_wait_io(hid); | 688 | if (hiddev->exist) { |
689 | usbhid_submit_report(hid, report, USB_DIR_IN); | ||
690 | usbhid_wait_io(hid); | ||
691 | } | ||
692 | mutex_unlock(&hiddev->existancelock); | ||
641 | 693 | ||
642 | return 0; | 694 | return 0; |
643 | 695 | ||
@@ -651,8 +703,12 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||
651 | if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) | 703 | if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) |
652 | return -EINVAL; | 704 | return -EINVAL; |
653 | 705 | ||
654 | usbhid_submit_report(hid, report, USB_DIR_OUT); | 706 | mutex_lock(&hiddev->existancelock); |
655 | usbhid_wait_io(hid); | 707 | if (hiddev->exist) { |
708 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
709 | usbhid_wait_io(hid); | ||
710 | } | ||
711 | mutex_unlock(&hiddev->existancelock); | ||
656 | 712 | ||
657 | return 0; | 713 | return 0; |
658 | 714 | ||
@@ -710,7 +766,13 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||
710 | case HIDIOCGUSAGES: | 766 | case HIDIOCGUSAGES: |
711 | case HIDIOCSUSAGES: | 767 | case HIDIOCSUSAGES: |
712 | case HIDIOCGCOLLECTIONINDEX: | 768 | case HIDIOCGCOLLECTIONINDEX: |
713 | return hiddev_ioctl_usage(hiddev, cmd, user_arg); | 769 | mutex_lock(&hiddev->existancelock); |
770 | if (hiddev->exist) | ||
771 | r = hiddev_ioctl_usage(hiddev, cmd, user_arg); | ||
772 | else | ||
773 | r = -ENODEV; | ||
774 | mutex_unlock(&hiddev->existancelock); | ||
775 | return r; | ||
714 | 776 | ||
715 | case HIDIOCGCOLLECTIONINFO: | 777 | case HIDIOCGCOLLECTIONINFO: |
716 | if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) | 778 | if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) |
@@ -808,23 +870,22 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) | |||
808 | if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL))) | 870 | if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL))) |
809 | return -1; | 871 | return -1; |
810 | 872 | ||
811 | retval = usb_register_dev(usbhid->intf, &hiddev_class); | ||
812 | if (retval) { | ||
813 | err_hid("Not able to get a minor for this device."); | ||
814 | kfree(hiddev); | ||
815 | return -1; | ||
816 | } | ||
817 | |||
818 | init_waitqueue_head(&hiddev->wait); | 873 | init_waitqueue_head(&hiddev->wait); |
819 | INIT_LIST_HEAD(&hiddev->list); | 874 | INIT_LIST_HEAD(&hiddev->list); |
820 | spin_lock_init(&hiddev->list_lock); | 875 | spin_lock_init(&hiddev->list_lock); |
876 | mutex_init(&hiddev->existancelock); | ||
821 | hiddev->hid = hid; | 877 | hiddev->hid = hid; |
822 | hiddev->exist = 1; | 878 | hiddev->exist = 1; |
823 | 879 | ||
824 | hid->minor = usbhid->intf->minor; | 880 | retval = usb_register_dev(usbhid->intf, &hiddev_class); |
825 | hid->hiddev = hiddev; | 881 | if (retval) { |
826 | 882 | err_hid("Not able to get a minor for this device."); | |
827 | hiddev_table[usbhid->intf->minor - HIDDEV_MINOR_BASE] = hiddev; | 883 | kfree(hiddev); |
884 | return -1; | ||
885 | } else { | ||
886 | hid->minor = usbhid->intf->minor; | ||
887 | hiddev_table[usbhid->intf->minor - HIDDEV_MINOR_BASE] = hiddev; | ||
888 | } | ||
828 | 889 | ||
829 | return 0; | 890 | return 0; |
830 | } | 891 | } |
@@ -839,7 +900,9 @@ void hiddev_disconnect(struct hid_device *hid) | |||
839 | struct hiddev *hiddev = hid->hiddev; | 900 | struct hiddev *hiddev = hid->hiddev; |
840 | struct usbhid_device *usbhid = hid->driver_data; | 901 | struct usbhid_device *usbhid = hid->driver_data; |
841 | 902 | ||
903 | mutex_lock(&hiddev->existancelock); | ||
842 | hiddev->exist = 0; | 904 | hiddev->exist = 0; |
905 | mutex_unlock(&hiddev->existancelock); | ||
843 | 906 | ||
844 | hiddev_table[hiddev->hid->minor - HIDDEV_MINOR_BASE] = NULL; | 907 | hiddev_table[hiddev->hid->minor - HIDDEV_MINOR_BASE] = NULL; |
845 | usb_deregister_dev(usbhid->intf, &hiddev_class); | 908 | usb_deregister_dev(usbhid->intf, &hiddev_class); |