]> git.neil.brown.name Git - history.git/commitdiff
[PATCH] -- hub/tt error recovery
authorDavid Brownell <david-b@pacbell.net>
Sat, 11 May 2002 14:16:35 +0000 (07:16 -0700)
committerGreg Kroah-Hartman <greg@kroah.com>
Sat, 11 May 2002 14:16:35 +0000 (07:16 -0700)
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

drivers/usb/core/hub.c
drivers/usb/core/hub.h
drivers/usb/core/usb.c
include/linux/usb.h

index 3d7efd659d75325573bec4c86d2e0908a4c0b1c8..ff77c843a8d9c5e5d27915e7d23320206bc5efb4 100644 (file)
@@ -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);
index f0f41b7a91300f993d485cd0a731775eb5b6d4f5..eafcb86a2dc465588184957584e1b918ad015563 100644 (file)
@@ -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 */
index 6d8e1bd02d8a079a1b2f6122cae9b73e3c6a8226..be4737a42201c2416ea42dea1ca6af4fb4307db9 100644 (file)
@@ -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");
index 78ff81cdc4b4a99457d770bde79ebc2622ccf8ca..6a898a21a83dd256a2d2f995400938da897848e3 100644 (file)
@@ -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