From 55b447bf79ad25591437d24b78caa9d0ae4fec82 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Tue, 29 Jul 2008 15:26:15 +0200 Subject: [PATCH] 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 Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/urb.c | 56 +++++++++++++++++++++++++++++++++++++----- 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 47111e88f79..a7945ab208c 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -522,6 +522,7 @@ int usb_unlink_urb(struct urb *urb) } EXPORT_SYMBOL_GPL(usb_unlink_urb); +static DEFINE_MUTEX(usb_reject_mutex); /** * usb_kill_urb - cancel a transfer request and wait for it to finish * @urb: pointer to URB describing a previously submitted request, @@ -544,24 +545,67 @@ EXPORT_SYMBOL_GPL(usb_unlink_urb); */ void usb_kill_urb(struct urb *urb) { - static DEFINE_MUTEX(reject_mutex); - might_sleep(); if (!(urb && urb->dev && urb->ep)) return; - mutex_lock(&reject_mutex); + mutex_lock(&usb_reject_mutex); ++urb->reject; - mutex_unlock(&reject_mutex); + mutex_unlock(&usb_reject_mutex); usb_hcd_unlink_urb(urb, -ENOENT); wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); - mutex_lock(&reject_mutex); + mutex_lock(&usb_reject_mutex); --urb->reject; - mutex_unlock(&reject_mutex); + mutex_unlock(&usb_reject_mutex); } EXPORT_SYMBOL_GPL(usb_kill_urb); +/** + * usb_poison_urb - reliably kill a transfer and prevent further use of an URB + * @urb: pointer to URB describing a previously submitted request, + * may be NULL + * + * This routine cancels an in-progress request. It is guaranteed that + * upon return all completion handlers will have finished and the URB + * will be totally idle and cannot be reused. These features make + * this an ideal way to stop I/O in a disconnect() callback. + * If the request has not already finished or been unlinked + * the completion handler will see urb->status == -ENOENT. + * + * After and while the routine runs, attempts to resubmit the URB will fail + * with error -EPERM. Thus even if the URB's completion handler always + * tries to resubmit, it will not succeed and the URB will become idle. + * + * This routine may not be used in an interrupt context (such as a bottom + * half or a completion handler), or when holding a spinlock, or in other + * situations where the caller can't schedule(). + */ +void usb_poison_urb(struct urb *urb) +{ + might_sleep(); + if (!(urb && urb->dev && urb->ep)) + return; + mutex_lock(&usb_reject_mutex); + ++urb->reject; + mutex_unlock(&usb_reject_mutex); + + usb_hcd_unlink_urb(urb, -ENOENT); + wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); +} +EXPORT_SYMBOL_GPL(usb_poison_urb); + +void usb_unpoison_urb(struct urb *urb) +{ + if (!urb) + return; + + mutex_lock(&usb_reject_mutex); + --urb->reject; + mutex_unlock(&usb_reject_mutex); +} +EXPORT_SYMBOL_GPL(usb_unpoison_urb); + /** * usb_kill_anchored_urbs - cancel transfer requests en masse * @anchor: anchor the requests are bound to diff --git a/include/linux/usb.h b/include/linux/usb.h index 94ac74aba6b..3371c91e7ff 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1459,6 +1459,8 @@ extern struct urb *usb_get_urb(struct urb *urb); extern int usb_submit_urb(struct urb *urb, gfp_t mem_flags); extern int usb_unlink_urb(struct urb *urb); extern void usb_kill_urb(struct urb *urb); +extern void usb_poison_urb(struct urb *urb); +extern void usb_unpoison_urb(struct urb *urb); extern void usb_kill_anchored_urbs(struct usb_anchor *anchor); extern void usb_unlink_anchored_urbs(struct usb_anchor *anchor); extern void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor); -- 2.20.1