diff options
author | Oliver Neukum <oliver@neukum.org> | 2008-07-29 09:26:15 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-10-17 17:40:51 -0400 |
commit | 55b447bf79ad25591437d24b78caa9d0ae4fec82 (patch) | |
tree | f8b84be61cf96a993664f35f572d8ddf6e618d39 | |
parent | 49b707b90c7f7260beb8691fc5d99d71a5549ec0 (diff) |
USB: kill URBs permanently
looking at usb_kill_urb() it seems to me that it is unnecessarily lenient.
In the use case of disconnect() you never want to use the URB again
(for the same device) But leaving urb->reject elevated will make it easier
to avoid races between read/write and disconnect.
Signed-off-by: Oliver Neukum <oneukum@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | drivers/usb/core/urb.c | 56 | ||||
-rw-r--r-- | include/linux/usb.h | 2 |
2 files changed, 52 insertions, 6 deletions
diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 47111e88f791..a7945ab208c2 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c | |||
@@ -522,6 +522,7 @@ int usb_unlink_urb(struct urb *urb) | |||
522 | } | 522 | } |
523 | EXPORT_SYMBOL_GPL(usb_unlink_urb); | 523 | EXPORT_SYMBOL_GPL(usb_unlink_urb); |
524 | 524 | ||
525 | static DEFINE_MUTEX(usb_reject_mutex); | ||
525 | /** | 526 | /** |
526 | * usb_kill_urb - cancel a transfer request and wait for it to finish | 527 | * usb_kill_urb - cancel a transfer request and wait for it to finish |
527 | * @urb: pointer to URB describing a previously submitted request, | 528 | * @urb: pointer to URB describing a previously submitted request, |
@@ -544,25 +545,68 @@ EXPORT_SYMBOL_GPL(usb_unlink_urb); | |||
544 | */ | 545 | */ |
545 | void usb_kill_urb(struct urb *urb) | 546 | void usb_kill_urb(struct urb *urb) |
546 | { | 547 | { |
547 | static DEFINE_MUTEX(reject_mutex); | ||
548 | |||
549 | might_sleep(); | 548 | might_sleep(); |
550 | if (!(urb && urb->dev && urb->ep)) | 549 | if (!(urb && urb->dev && urb->ep)) |
551 | return; | 550 | return; |
552 | mutex_lock(&reject_mutex); | 551 | mutex_lock(&usb_reject_mutex); |
553 | ++urb->reject; | 552 | ++urb->reject; |
554 | mutex_unlock(&reject_mutex); | 553 | mutex_unlock(&usb_reject_mutex); |
555 | 554 | ||
556 | usb_hcd_unlink_urb(urb, -ENOENT); | 555 | usb_hcd_unlink_urb(urb, -ENOENT); |
557 | wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); | 556 | wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); |
558 | 557 | ||
559 | mutex_lock(&reject_mutex); | 558 | mutex_lock(&usb_reject_mutex); |
560 | --urb->reject; | 559 | --urb->reject; |
561 | mutex_unlock(&reject_mutex); | 560 | mutex_unlock(&usb_reject_mutex); |
562 | } | 561 | } |
563 | EXPORT_SYMBOL_GPL(usb_kill_urb); | 562 | EXPORT_SYMBOL_GPL(usb_kill_urb); |
564 | 563 | ||
565 | /** | 564 | /** |
565 | * usb_poison_urb - reliably kill a transfer and prevent further use of an URB | ||
566 | * @urb: pointer to URB describing a previously submitted request, | ||
567 | * may be NULL | ||
568 | * | ||
569 | * This routine cancels an in-progress request. It is guaranteed that | ||
570 | * upon return all completion handlers will have finished and the URB | ||
571 | * will be totally idle and cannot be reused. These features make | ||
572 | * this an ideal way to stop I/O in a disconnect() callback. | ||
573 | * If the request has not already finished or been unlinked | ||
574 | * the completion handler will see urb->status == -ENOENT. | ||
575 | * | ||
576 | * After and while the routine runs, attempts to resubmit the URB will fail | ||
577 | * with error -EPERM. Thus even if the URB's completion handler always | ||
578 | * tries to resubmit, it will not succeed and the URB will become idle. | ||
579 | * | ||
580 | * This routine may not be used in an interrupt context (such as a bottom | ||
581 | * half or a completion handler), or when holding a spinlock, or in other | ||
582 | * situations where the caller can't schedule(). | ||
583 | */ | ||
584 | void usb_poison_urb(struct urb *urb) | ||
585 | { | ||
586 | might_sleep(); | ||
587 | if (!(urb && urb->dev && urb->ep)) | ||
588 | return; | ||
589 | mutex_lock(&usb_reject_mutex); | ||
590 | ++urb->reject; | ||
591 | mutex_unlock(&usb_reject_mutex); | ||
592 | |||
593 | usb_hcd_unlink_urb(urb, -ENOENT); | ||
594 | wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); | ||
595 | } | ||
596 | EXPORT_SYMBOL_GPL(usb_poison_urb); | ||
597 | |||
598 | void usb_unpoison_urb(struct urb *urb) | ||
599 | { | ||
600 | if (!urb) | ||
601 | return; | ||
602 | |||
603 | mutex_lock(&usb_reject_mutex); | ||
604 | --urb->reject; | ||
605 | mutex_unlock(&usb_reject_mutex); | ||
606 | } | ||
607 | EXPORT_SYMBOL_GPL(usb_unpoison_urb); | ||
608 | |||
609 | /** | ||
566 | * usb_kill_anchored_urbs - cancel transfer requests en masse | 610 | * usb_kill_anchored_urbs - cancel transfer requests en masse |
567 | * @anchor: anchor the requests are bound to | 611 | * @anchor: anchor the requests are bound to |
568 | * | 612 | * |
diff --git a/include/linux/usb.h b/include/linux/usb.h index 94ac74aba6b6..3371c91e7ff4 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h | |||
@@ -1459,6 +1459,8 @@ extern struct urb *usb_get_urb(struct urb *urb); | |||
1459 | extern int usb_submit_urb(struct urb *urb, gfp_t mem_flags); | 1459 | extern int usb_submit_urb(struct urb *urb, gfp_t mem_flags); |
1460 | extern int usb_unlink_urb(struct urb *urb); | 1460 | extern int usb_unlink_urb(struct urb *urb); |
1461 | extern void usb_kill_urb(struct urb *urb); | 1461 | extern void usb_kill_urb(struct urb *urb); |
1462 | extern void usb_poison_urb(struct urb *urb); | ||
1463 | extern void usb_unpoison_urb(struct urb *urb); | ||
1462 | extern void usb_kill_anchored_urbs(struct usb_anchor *anchor); | 1464 | extern void usb_kill_anchored_urbs(struct usb_anchor *anchor); |
1463 | extern void usb_unlink_anchored_urbs(struct usb_anchor *anchor); | 1465 | extern void usb_unlink_anchored_urbs(struct usb_anchor *anchor); |
1464 | extern void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor); | 1466 | extern void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor); |