aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2009-06-29 10:43:32 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2009-09-08 20:34:00 -0700
commitd34b435e14c18f29e15660e6fa2121297fb50018 (patch)
tree67ad0cbd8cfeee9b1b1b52f99e635d542d87a48f
parent3970742fce3383231a240f5bcd018c7f7106f5ea (diff)
USB: fix the clear_tt_buffer interface
commit cb88a1b887bb8908f6e00ce29e893ea52b074940 upstream. 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> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/core/hcd.h4
-rw-r--r--drivers/usb/core/hub.c40
-rw-r--r--drivers/usb/core/hub.h6
-rw-r--r--drivers/usb/host/ehci-q.c2
4 files changed, 35 insertions, 17 deletions
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
index e7d4479de41..798f362623a 100644
--- a/drivers/usb/core/hcd.h
+++ b/drivers/usb/core/hcd.h
@@ -224,6 +224,10 @@ struct hc_driver {
void (*relinquish_port)(struct usb_hcd *, int);
/* 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 *);
};
extern int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb);
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index be86ae3f408..2fc5b57458a 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -448,10 +448,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;
@@ -460,6 +460,7 @@ static void hub_tt_kevent (struct work_struct *work)
struct list_head *temp;
struct usb_tt_clear *clear;
struct usb_device *hdev = hub->hdev;
+ const struct hc_driver *drv;
int status;
temp = hub->tt.clear_list.next;
@@ -469,21 +470,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
@@ -493,8 +498,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;
@@ -506,7 +513,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 */
@@ -518,14 +525,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.
@@ -816,7 +828,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 */
@@ -933,7 +945,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;
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 2a116ce53c9..528c4111416 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -185,16 +185,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 */
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 1976b1b3778..68bf81e982d 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -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);
}
}