Commit cb88a1b8 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: fix the clear_tt_buffer interface

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: default avatarAlan Stern <stern@rowland.harvard.edu>
Cc: stable <stable@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 87ea8c88
...@@ -227,6 +227,10 @@ struct hc_driver { ...@@ -227,6 +227,10 @@ struct hc_driver {
/* has a port been handed over to a companion? */ /* has a port been handed over to a companion? */
int (*port_handed_over)(struct usb_hcd *, int); 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 */ /* xHCI specific functions */
/* Called by usb_alloc_dev to alloc HC device structures */ /* Called by usb_alloc_dev to alloc HC device structures */
int (*alloc_dev)(struct usb_hcd *, struct usb_device *); int (*alloc_dev)(struct usb_hcd *, struct usb_device *);
......
...@@ -450,10 +450,10 @@ hub_clear_tt_buffer (struct usb_device *hdev, u16 devinfo, u16 tt) ...@@ -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 * talking to TTs must queue control transfers (not just bulk and iso), so
* both can talk to the same hub concurrently. * 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 = struct usb_hub *hub =
container_of(work, struct usb_hub, tt.kevent); container_of(work, struct usb_hub, tt.clear_work);
unsigned long flags; unsigned long flags;
int limit = 100; int limit = 100;
...@@ -462,6 +462,7 @@ static void hub_tt_kevent (struct work_struct *work) ...@@ -462,6 +462,7 @@ static void hub_tt_kevent (struct work_struct *work)
struct list_head *next; struct list_head *next;
struct usb_tt_clear *clear; struct usb_tt_clear *clear;
struct usb_device *hdev = hub->hdev; struct usb_device *hdev = hub->hdev;
const struct hc_driver *drv;
int status; int status;
next = hub->tt.clear_list.next; next = hub->tt.clear_list.next;
...@@ -471,21 +472,25 @@ static void hub_tt_kevent (struct work_struct *work) ...@@ -471,21 +472,25 @@ static void hub_tt_kevent (struct work_struct *work)
/* drop lock so HCD can concurrently report other TT errors */ /* drop lock so HCD can concurrently report other TT errors */
spin_unlock_irqrestore (&hub->tt.lock, flags); spin_unlock_irqrestore (&hub->tt.lock, flags);
status = hub_clear_tt_buffer (hdev, clear->devinfo, clear->tt); status = hub_clear_tt_buffer (hdev, clear->devinfo, clear->tt);
spin_lock_irqsave (&hub->tt.lock, flags);
if (status) if (status)
dev_err (&hdev->dev, dev_err (&hdev->dev,
"clear tt %d (%04x) error %d\n", "clear tt %d (%04x) error %d\n",
clear->tt, clear->devinfo, status); 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); kfree(clear);
spin_lock_irqsave(&hub->tt.lock, flags);
} }
spin_unlock_irqrestore (&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 * usb_hub_clear_tt_buffer - clear control/bulk TT state in high speed hub
* @udev: the device whose split transaction failed * @urb: an URB associated with the failed or incomplete split transaction
* @pipe: identifies the endpoint of the failed transaction
* *
* High speed HCDs use this to tell the hub driver that some split control or * 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 * 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) ...@@ -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) * It may not be possible for that hub to handle additional full (or low)
* speed transactions until that state is fully cleared out. * 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; struct usb_tt *tt = udev->tt;
unsigned long flags; unsigned long flags;
struct usb_tt_clear *clear; struct usb_tt_clear *clear;
...@@ -508,7 +515,7 @@ void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe) ...@@ -508,7 +515,7 @@ void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe)
if ((clear = kmalloc (sizeof *clear, GFP_ATOMIC)) == NULL) { if ((clear = kmalloc (sizeof *clear, GFP_ATOMIC)) == NULL) {
dev_err (&udev->dev, "can't save CLEAR_TT_BUFFER state\n"); dev_err (&udev->dev, "can't save CLEAR_TT_BUFFER state\n");
/* FIXME recover somehow ... RESET_TT? */ /* FIXME recover somehow ... RESET_TT? */
return; return -ENOMEM;
} }
/* info that CLEAR_TT_BUFFER needs */ /* info that CLEAR_TT_BUFFER needs */
...@@ -521,13 +528,18 @@ void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe) ...@@ -521,13 +528,18 @@ void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe)
if (usb_pipein (pipe)) if (usb_pipein (pipe))
clear->devinfo |= 1 << 15; 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 */ /* tell keventd to clear state for this TT */
spin_lock_irqsave (&tt->lock, flags); spin_lock_irqsave (&tt->lock, flags);
list_add_tail (&clear->clear_list, &tt->clear_list); list_add_tail (&clear->clear_list, &tt->clear_list);
schedule_work (&tt->kevent); schedule_work(&tt->clear_work);
spin_unlock_irqrestore (&tt->lock, flags); 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 /* If do_delay is false, return the number of milliseconds the caller
* needs to delay. * needs to delay.
...@@ -818,7 +830,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type) ...@@ -818,7 +830,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
if (hub->has_indicators) if (hub->has_indicators)
cancel_delayed_work_sync(&hub->leds); cancel_delayed_work_sync(&hub->leds);
if (hub->tt.hub) if (hub->tt.hub)
cancel_work_sync(&hub->tt.kevent); cancel_work_sync(&hub->tt.clear_work);
} }
/* caller has locked the hub device */ /* caller has locked the hub device */
...@@ -935,7 +947,7 @@ static int hub_configure(struct usb_hub *hub, ...@@ -935,7 +947,7 @@ static int hub_configure(struct usb_hub *hub,
spin_lock_init (&hub->tt.lock); spin_lock_init (&hub->tt.lock);
INIT_LIST_HEAD (&hub->tt.clear_list); 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) { switch (hdev->descriptor.bDeviceProtocol) {
case 0: case 0:
break; break;
......
...@@ -188,16 +188,18 @@ struct usb_tt { ...@@ -188,16 +188,18 @@ struct usb_tt {
/* for control/bulk error recovery (CLEAR_TT_BUFFER) */ /* for control/bulk error recovery (CLEAR_TT_BUFFER) */
spinlock_t lock; spinlock_t lock;
struct list_head clear_list; /* of usb_tt_clear */ struct list_head clear_list; /* of usb_tt_clear */
struct work_struct kevent; struct work_struct clear_work;
}; };
struct usb_tt_clear { struct usb_tt_clear {
struct list_head clear_list; struct list_head clear_list;
unsigned tt; unsigned tt;
u16 devinfo; 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 *); extern void usb_ep0_reinit(struct usb_device *);
#endif /* __LINUX_HUB_H */ #endif /* __LINUX_HUB_H */
...@@ -215,7 +215,7 @@ static int qtd_copy_status ( ...@@ -215,7 +215,7 @@ static int qtd_copy_status (
/* REVISIT ARC-derived cores don't clear the root /* REVISIT ARC-derived cores don't clear the root
* hub TT buffer in this way... * hub TT buffer in this way...
*/ */
usb_hub_tt_clear_buffer (urb->dev, urb->pipe); usb_hub_clear_tt_buffer(urb);
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment