Commit c37126dd authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[PATCH] -- hub/tt error recovery

This patch adds missing functionality to the transaction translator
support for USB 2.0 hubs:

    - moves the 'struct usb_tt' definition to "hub.h" from <linux/usb.h>
    - adds state to it as neeed for some control/bulk error recovery
    - teaches the hub driver how to use that state (via keventd)
    - adds a call letting HCDs trigger that recovery
parent 76ecfb4c
......@@ -149,6 +149,98 @@ static void hub_irq(struct urb *urb)
spin_unlock_irqrestore(&hub_event_lock, flags);
}
/* USB 2.0 spec Section 11.24.2.3 */
static inline int
hub_clear_tt_buffer (struct usb_device *hub, u16 devinfo, u16 tt)
{
return usb_control_msg (hub, usb_rcvctrlpipe (hub, 0),
HUB_CLEAR_TT_BUFFER, USB_DIR_IN | USB_RECIP_OTHER,
devinfo, tt, 0, 0, HZ);
}
/*
* enumeration blocks khubd for a long time. we use keventd instead, since
* long blocking there is the exception, not the rule. accordingly, HCDs
* 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 (void *arg)
{
struct usb_hub *hub = arg;
unsigned long flags;
spin_lock_irqsave (&hub->tt.lock, flags);
while (!list_empty (&hub->tt.clear_list)) {
struct list_head *temp;
struct usb_tt_clear *clear;
int status;
temp = hub->tt.clear_list.next;
clear = list_entry (temp, struct usb_tt_clear, clear_list);
list_del (&clear->clear_list);
/* drop lock so HCD can concurrently report other TT errors */
spin_unlock_irqrestore (&hub->tt.lock, flags);
status = hub_clear_tt_buffer (hub->dev,
clear->devinfo, clear->tt);
spin_lock_irqsave (&hub->tt.lock, flags);
if (status)
err ("usb-%s-%s clear tt %d (%04x) error %d",
hub->dev->bus->bus_name, hub->dev->devpath,
clear->tt, clear->devinfo, status);
kfree (clear);
}
spin_unlock_irqrestore (&hub->tt.lock, flags);
}
/**
* usb_hub_tt_clear_buffer - clear control/bulk TT state in high speed hub
* @dev: the device whose split transaction failed
* @pipe: identifies the endpoint of the failed 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
* a transaction translator. This is normally detected (and reported) from
* interrupt context.
*
* 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 *dev, int pipe)
{
struct usb_tt *tt = dev->tt;
unsigned long flags;
struct usb_tt_clear *clear;
/* we've got to cope with an arbitrary number of pending TT clears,
* since each TT has "at least two" buffers that can need it (and
* there can be many TTs per hub). even if they're uncommon.
*/
if ((clear = kmalloc (sizeof *clear, SLAB_ATOMIC)) == 0) {
err ("can't save CLEAR_TT_BUFFER state for hub at usb-%s-%s",
dev->bus->bus_name, tt->hub->devpath);
/* FIXME recover somehow ... RESET_TT? */
return;
}
/* info that CLEAR_TT_BUFFER needs */
clear->tt = tt->multi ? dev->ttport : 1;
clear->devinfo = usb_pipeendpoint (pipe);
clear->devinfo |= dev->devnum << 4;
clear->devinfo |= usb_pipecontrol (pipe)
? (USB_ENDPOINT_XFER_CONTROL << 11)
: (USB_ENDPOINT_XFER_BULK << 11);
if (usb_pipein (pipe))
clear->devinfo |= 1 << 15;
/* tell keventd to clear state for this TT */
spin_lock_irqsave (&tt->lock, flags);
list_add_tail (&clear->clear_list, &tt->clear_list);
schedule_task (&tt->kevent);
spin_unlock_irqrestore (&tt->lock, flags);
}
static void usb_hub_power_on(struct usb_hub *hub)
{
int i;
......@@ -231,6 +323,9 @@ static int usb_hub_configure(struct usb_hub *hub,
break;
}
spin_lock_init (&hub->tt.lock);
INIT_LIST_HEAD (&hub->tt.clear_list);
INIT_TQUEUE (&hub->tt.kevent, hub_tt_kevent, hub);
switch (dev->descriptor.bDeviceProtocol) {
case 0:
break;
......@@ -432,6 +527,10 @@ static void hub_disconnect(struct usb_device *dev, void *ptr)
down(&hub->khubd_sem); /* Wait for khubd to leave this hub alone. */
up(&hub->khubd_sem);
/* assuming we used keventd, it must quiesce too */
if (hub->tt.hub)
flush_scheduled_tasks ();
if (hub->urb) {
usb_unlink_urb(hub->urb);
usb_free_urb(hub->urb);
......
......@@ -136,6 +136,34 @@ struct usb_hub_descriptor {
struct usb_device;
/*
* As of USB 2.0, full/low speed devices are segregated into trees.
* One type grows from USB 1.1 host controllers (OHCI, UHCI etc).
* The other type grows from high speed hubs when they connect to
* full/low speed devices using "Transaction Translators" (TTs).
*
* TTs should only be known to the hub driver, and high speed bus
* drivers (only EHCI for now). They affect periodic scheduling and
* sometimes control/bulk error recovery.
*/
struct usb_tt {
struct usb_device *hub; /* upstream highspeed hub */
int multi; /* true means one TT per port */
/* for control/bulk error recovery (CLEAR_TT_BUFFER) */
spinlock_t lock;
struct list_head clear_list; /* of usb_tt_clear */
struct tq_struct kevent;
};
struct usb_tt_clear {
struct list_head clear_list;
unsigned tt;
u16 devinfo;
};
extern void usb_hub_tt_clear_buffer (struct usb_device *dev, int pipe);
struct usb_hub {
struct usb_device *dev; /* the "real" device */
struct urb *urb; /* for interrupt polling pipe */
......
......@@ -2743,9 +2743,8 @@ module_exit(usb_exit);
/*
* USB may be built into the kernel or be built as modules.
* If the USB core [and maybe a host controller driver] is built
* into the kernel, and other device drivers are built as modules,
* then these symbols need to be exported for the modules to use.
* These symbols are exported for device (or host controller)
* driver modules to use.
*/
EXPORT_SYMBOL(usb_ifnum_to_ifpos);
EXPORT_SYMBOL(usb_ifnum_to_if);
......@@ -2762,6 +2761,7 @@ EXPORT_SYMBOL(usb_deregister_dev);
EXPORT_SYMBOL(usb_alloc_dev);
EXPORT_SYMBOL(usb_free_dev);
EXPORT_SYMBOL(usb_hub_tt_clear_buffer);
EXPORT_SYMBOL(usb_find_interface_driver_for_ifnum);
EXPORT_SYMBOL(usb_driver_claim_interface);
......@@ -2799,6 +2799,5 @@ EXPORT_SYMBOL(usb_clear_halt);
EXPORT_SYMBOL(usb_set_configuration);
EXPORT_SYMBOL(usb_set_interface);
EXPORT_SYMBOL(usb_make_path);
EXPORT_SYMBOL(usb_devfs_handle);
MODULE_LICENSE("GPL");
......@@ -363,22 +363,6 @@ struct usb_bus {
extern int usb_root_hub_string(int id, int serial,
char *type, __u8 *data, int len);
/*
* As of USB 2.0, full/low speed devices are segregated into trees.
* One type grows from USB 1.1 host controllers (OHCI, UHCI etc).
* The other type grows from high speed hubs when they connect to
* full/low speed devices using "Transaction Translators" (TTs).
*
* TTs should only be known to the hub driver, and high speed bus
* drivers (only EHCI for now). They affect periodic scheduling and
* sometimes control/bulk error recovery.
*/
struct usb_tt {
struct usb_device *hub; /* upstream highspeed hub */
int multi; /* true means one TT per port */
};
/* -------------------------------------------------------------------------- */
/* This is arbitrary.
......@@ -387,6 +371,8 @@ struct usb_tt {
*/
#define USB_MAXCHILDREN (16)
struct usb_tt;
struct usb_device {
int devnum; /* Address on USB bus */
char devpath [16]; /* Use in messages: /port/port/... */
......@@ -1176,6 +1162,7 @@ extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate);
* appropriately.
*/
/* NOTE: these are not the standard USB_ENDPOINT_XFER_* values!! */
#define PIPE_ISOCHRONOUS 0
#define PIPE_INTERRUPT 1
#define PIPE_CONTROL 2
......
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