USB: kill URBs permanently
authorOliver Neukum <oliver@neukum.org>
Tue, 29 Jul 2008 13:26:15 +0000 (15:26 +0200)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 17 Oct 2008 21:40:51 +0000 (14:40 -0700)
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>
drivers/usb/core/urb.c
include/linux/usb.h

index 47111e88f791ad748956d4cbae7317145cfd8927..a7945ab208c2227a26e9c0f8d7f1a5a51fce94ec 100644 (file)
@@ -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
index 94ac74aba6b6644dcd70f5015082facd90824195..3371c91e7ff43aa5677a9aebf9eac0050544c048 100644 (file)
@@ -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);