USB: fix the clear_tt_buffer interface
authorAlan Stern <stern@rowland.harvard.edu>
Mon, 29 Jun 2009 14:43:32 +0000 (10:43 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Sun, 12 Jul 2009 22:16:38 +0000 (15:16 -0700)
This patch (as1255) updates the interface for calling
usb_hub_clear_tt_buffer().  Even the name of the function is changed!

When an async URB (i.e., Control or Bulk) going through a high-speed
hub to a non-high-speed device is cancelled or fails, the hub's
Transaction Translator buffer may be left busy still trying to
complete the transaction.  The buffer has to be cleared; that's what
usb_hub_clear_tt_buffer() does.

It isn't safe to send any more URBs to the same endpoint until the TT
buffer is fully clear.  Therefore the HCD needs to be told when the
Clear-TT-Buffer request has finished.  This patch adds a callback
method to struct hc_driver for that purpose, and makes the hub driver
invoke the callback at the proper time.

The patch also changes a couple of names; "hub_tt_kevent" and
"tt.kevent" now look rather antiquated.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Cc: stable <stable@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/hcd.h
drivers/usb/core/hub.c
drivers/usb/core/hub.h
drivers/usb/host/ehci-q.c

index d397ecfd5b178d881f97c72e51989bfccbe2a064..ec5c67ea07b745a95b92e8db88349abadc1bbca1 100644 (file)
@@ -227,6 +227,10 @@ struct hc_driver {
                /* has a port been handed over to a companion? */
        int     (*port_handed_over)(struct usb_hcd *, int);
 
+               /* CLEAR_TT_BUFFER completion callback */
+       void    (*clear_tt_buffer_complete)(struct usb_hcd *,
+                               struct usb_host_endpoint *);
+
        /* xHCI specific functions */
                /* Called by usb_alloc_dev to alloc HC device structures */
        int     (*alloc_dev)(struct usb_hcd *, struct usb_device *);
index 2af3b4f0605405dd723b1ccd4f853f7f85dc1848..71f86c60d83c3962cf8b7eabe1d709d2d87b7a76 100644 (file)
@@ -450,10 +450,10 @@ hub_clear_tt_buffer (struct usb_device *hdev, u16 devinfo, u16 tt)
  * talking to TTs must queue control transfers (not just bulk and iso), so
  * both can talk to the same hub concurrently.
  */
-static void hub_tt_kevent (struct work_struct *work)
+static void hub_tt_work(struct work_struct *work)
 {
        struct usb_hub          *hub =
-               container_of(work, struct usb_hub, tt.kevent);
+               container_of(work, struct usb_hub, tt.clear_work);
        unsigned long           flags;
        int                     limit = 100;
 
@@ -462,6 +462,7 @@ static void hub_tt_kevent (struct work_struct *work)
                struct list_head        *next;
                struct usb_tt_clear     *clear;
                struct usb_device       *hdev = hub->hdev;
+               const struct hc_driver  *drv;
                int                     status;
 
                next = hub->tt.clear_list.next;
@@ -471,21 +472,25 @@ static void hub_tt_kevent (struct work_struct *work)
                /* drop lock so HCD can concurrently report other TT errors */
                spin_unlock_irqrestore (&hub->tt.lock, flags);
                status = hub_clear_tt_buffer (hdev, clear->devinfo, clear->tt);
-               spin_lock_irqsave (&hub->tt.lock, flags);
-
                if (status)
                        dev_err (&hdev->dev,
                                "clear tt %d (%04x) error %d\n",
                                clear->tt, clear->devinfo, status);
+
+               /* Tell the HCD, even if the operation failed */
+               drv = clear->hcd->driver;
+               if (drv->clear_tt_buffer_complete)
+                       (drv->clear_tt_buffer_complete)(clear->hcd, clear->ep);
+
                kfree(clear);
+               spin_lock_irqsave(&hub->tt.lock, flags);
        }
        spin_unlock_irqrestore (&hub->tt.lock, flags);
 }
 
 /**
- * usb_hub_tt_clear_buffer - clear control/bulk TT state in high speed hub
- * @udev: the device whose split transaction failed
- * @pipe: identifies the endpoint of the failed transaction
+ * usb_hub_clear_tt_buffer - clear control/bulk TT state in high speed hub
+ * @urb: an URB associated with the failed or incomplete split transaction
  *
  * High speed HCDs use this to tell the hub driver that some split control or
  * bulk transaction failed in a way that requires clearing internal state of
@@ -495,8 +500,10 @@ static void hub_tt_kevent (struct work_struct *work)
  * It may not be possible for that hub to handle additional full (or low)
  * speed transactions until that state is fully cleared out.
  */
-void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe)
+int usb_hub_clear_tt_buffer(struct urb *urb)
 {
+       struct usb_device       *udev = urb->dev;
+       int                     pipe = urb->pipe;
        struct usb_tt           *tt = udev->tt;
        unsigned long           flags;
        struct usb_tt_clear     *clear;
@@ -508,7 +515,7 @@ void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe)
        if ((clear = kmalloc (sizeof *clear, GFP_ATOMIC)) == NULL) {
                dev_err (&udev->dev, "can't save CLEAR_TT_BUFFER state\n");
                /* FIXME recover somehow ... RESET_TT? */
-               return;
+               return -ENOMEM;
        }
 
        /* info that CLEAR_TT_BUFFER needs */
@@ -520,14 +527,19 @@ void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe)
                        : (USB_ENDPOINT_XFER_BULK << 11);
        if (usb_pipein (pipe))
                clear->devinfo |= 1 << 15;
-       
+
+       /* info for completion callback */
+       clear->hcd = bus_to_hcd(udev->bus);
+       clear->ep = urb->ep;
+
        /* tell keventd to clear state for this TT */
        spin_lock_irqsave (&tt->lock, flags);
        list_add_tail (&clear->clear_list, &tt->clear_list);
-       schedule_work (&tt->kevent);
+       schedule_work(&tt->clear_work);
        spin_unlock_irqrestore (&tt->lock, flags);
+       return 0;
 }
-EXPORT_SYMBOL_GPL(usb_hub_tt_clear_buffer);
+EXPORT_SYMBOL_GPL(usb_hub_clear_tt_buffer);
 
 /* If do_delay is false, return the number of milliseconds the caller
  * needs to delay.
@@ -818,7 +830,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
        if (hub->has_indicators)
                cancel_delayed_work_sync(&hub->leds);
        if (hub->tt.hub)
-               cancel_work_sync(&hub->tt.kevent);
+               cancel_work_sync(&hub->tt.clear_work);
 }
 
 /* caller has locked the hub device */
@@ -935,7 +947,7 @@ static int hub_configure(struct usb_hub *hub,
 
        spin_lock_init (&hub->tt.lock);
        INIT_LIST_HEAD (&hub->tt.clear_list);
-       INIT_WORK (&hub->tt.kevent, hub_tt_kevent);
+       INIT_WORK(&hub->tt.clear_work, hub_tt_work);
        switch (hdev->descriptor.bDeviceProtocol) {
                case 0:
                        break;
index 889c0f32a40b729431f2dab51d13aead39f5f3e8..de8081f065edda76ae95a415b98c6db86a0d9a57 100644 (file)
@@ -188,16 +188,18 @@ struct usb_tt {
        /* for control/bulk error recovery (CLEAR_TT_BUFFER) */
        spinlock_t              lock;
        struct list_head        clear_list;     /* of usb_tt_clear */
-       struct work_struct                      kevent;
+       struct work_struct      clear_work;
 };
 
 struct usb_tt_clear {
        struct list_head        clear_list;
        unsigned                tt;
        u16                     devinfo;
+       struct usb_hcd          *hcd;
+       struct usb_host_endpoint        *ep;
 };
 
-extern void usb_hub_tt_clear_buffer(struct usb_device *dev, int pipe);
+extern int usb_hub_clear_tt_buffer(struct urb *urb);
 extern void usb_ep0_reinit(struct usb_device *);
 
 #endif /* __LINUX_HUB_H */
index 1976b1b3778cd3de2d7946733fbda37edb6a7108..68bf81e982d239fec2f2a38dc6b20d6129edde16 100644 (file)
@@ -215,7 +215,7 @@ static int qtd_copy_status (
                        /* REVISIT ARC-derived cores don't clear the root
                         * hub TT buffer in this way...
                         */
-                       usb_hub_tt_clear_buffer (urb->dev, urb->pipe);
+                       usb_hub_clear_tt_buffer(urb);
                }
        }