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: Alan Stern <stern@rowland.harvard.edu> Cc: stable <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
87ea8c8879
commit
cb88a1b887
|
@ -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 *);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue