Author: hselasky
Date: Mon Jun  6 21:45:09 2011
New Revision: 222786
URL: http://svn.freebsd.org/changeset/base/222786

Log:
  Improve enumeration of Low- and Full-speed devices connected through a
  High-speed USB HUB by resetting the transaction translator (TT)
  before trying re-enumeration. Also when clear-stall fails multiple times
  try a re-enumeration.
  
  Suggested by: Trevor Blackwell
  MFC after:    14 days

Modified:
  head/sys/dev/usb/usb_device.h
  head/sys/dev/usb/usb_freebsd.h
  head/sys/dev/usb/usb_generic.c
  head/sys/dev/usb/usb_hub.c
  head/sys/dev/usb/usb_request.c
  head/sys/dev/usb/usb_request.h
  head/sys/dev/usb/usbdi.h

Modified: head/sys/dev/usb/usb_device.h
==============================================================================
--- head/sys/dev/usb/usb_device.h       Mon Jun  6 21:41:10 2011        
(r222785)
+++ head/sys/dev/usb/usb_device.h       Mon Jun  6 21:45:09 2011        
(r222786)
@@ -187,6 +187,8 @@ struct usb_device {
        struct usb_host_endpoint *linux_endpoint_end;
        uint16_t devnum;
 #endif
+
+       uint32_t clear_stall_errors;    /* number of clear-stall failures */
 };
 
 /* globals */

Modified: head/sys/dev/usb/usb_freebsd.h
==============================================================================
--- head/sys/dev/usb/usb_freebsd.h      Mon Jun  6 21:41:10 2011        
(r222785)
+++ head/sys/dev/usb/usb_freebsd.h      Mon Jun  6 21:45:09 2011        
(r222786)
@@ -66,6 +66,7 @@
 
 #define        USB_HUB_MAX_DEPTH       5
 #define        USB_EP0_BUFSIZE         1024    /* bytes */
+#define        USB_CS_RESET_LIMIT      20      /* failures = 20 * 50 ms = 1sec 
*/
 
 typedef uint32_t usb_timeout_t;                /* milliseconds */
 typedef uint32_t usb_frlength_t;       /* bytes */

Modified: head/sys/dev/usb/usb_generic.c
==============================================================================
--- head/sys/dev/usb/usb_generic.c      Mon Jun  6 21:41:10 2011        
(r222785)
+++ head/sys/dev/usb/usb_generic.c      Mon Jun  6 21:45:09 2011        
(r222786)
@@ -966,10 +966,8 @@ ugen_re_enumerate(struct usb_fifo *f)
                /* ignore any errors */
                DPRINTFN(6, "no FIFOs\n");
        }
-       if (udev->re_enumerate_wait == 0) {
-               udev->re_enumerate_wait = 1;
-               usb_needs_explore(udev->bus, 0);
-       }
+       /* start re-enumeration of device */
+       usbd_start_re_enumerate(udev);
        return (0);
 }
 

Modified: head/sys/dev/usb/usb_hub.c
==============================================================================
--- head/sys/dev/usb/usb_hub.c  Mon Jun  6 21:41:10 2011        (r222785)
+++ head/sys/dev/usb/usb_hub.c  Mon Jun  6 21:45:09 2011        (r222786)
@@ -242,9 +242,14 @@ uhub_explore_sub(struct uhub_softc *sc, 
        if (child->flags.usb_mode == USB_MODE_HOST) {
                usbd_enum_lock(child);
                if (child->re_enumerate_wait) {
-                       err = usbd_set_config_index(child, USB_UNCONFIG_INDEX);
-                       if (err == 0)
-                               err = usbd_req_re_enumerate(child, NULL);
+                       err = usbd_set_config_index(child,
+                           USB_UNCONFIG_INDEX);
+                       if (err != 0) {
+                               DPRINTF("Unconfigure failed: "
+                                   "%s: Ignored.\n",
+                                   usbd_errstr(err));
+                       }
+                       err = usbd_req_re_enumerate(child, NULL);
                        if (err == 0)
                                err = usbd_set_config_index(child, 0);
                        if (err == 0) {
@@ -2471,3 +2476,19 @@ usbd_filter_power_mode(struct usb_device
        /* use fixed power mode given by hardware driver */
        return (temp);
 }
+
+/*------------------------------------------------------------------------*
+ *     usbd_start_re_enumerate
+ *
+ * This function starts re-enumeration of the given USB device. This
+ * function does not need to be called BUS-locked. This function does
+ * not wait until the re-enumeration is completed.
+ *------------------------------------------------------------------------*/
+void
+usbd_start_re_enumerate(struct usb_device *udev)
+{
+       if (udev->re_enumerate_wait == 0) {
+               udev->re_enumerate_wait = 1;
+               usb_needs_explore(udev->bus, 0);
+       }
+}

Modified: head/sys/dev/usb/usb_request.c
==============================================================================
--- head/sys/dev/usb/usb_request.c      Mon Jun  6 21:41:10 2011        
(r222785)
+++ head/sys/dev/usb/usb_request.c      Mon Jun  6 21:45:09 2011        
(r222786)
@@ -238,6 +238,10 @@ usb_do_clear_stall_callback(struct usb_x
 
        switch (USB_GET_STATE(xfer)) {
        case USB_ST_TRANSFERRED:
+
+               /* reset error counter */
+               udev->clear_stall_errors = 0;
+
                if (ep == NULL)
                        goto tr_setup;          /* device was unconfigured */
                if (ep->edesc &&
@@ -289,8 +293,23 @@ tr_setup:
                goto tr_setup;
 
        default:
-               if (xfer->error == USB_ERR_CANCELLED) {
+               if (error == USB_ERR_CANCELLED)
                        break;
+
+               DPRINTF("Clear stall failed.\n");
+               if (udev->clear_stall_errors == USB_CS_RESET_LIMIT)
+                       goto tr_setup;
+
+               if (error == USB_ERR_TIMEOUT) {
+                       udev->clear_stall_errors = USB_CS_RESET_LIMIT;
+                       DPRINTF("Trying to re-enumerate.\n");
+                       usbd_start_re_enumerate(udev);
+               } else {
+                       udev->clear_stall_errors++;
+                       if (udev->clear_stall_errors == USB_CS_RESET_LIMIT) {
+                               DPRINTF("Trying to re-enumerate.\n");
+                               usbd_start_re_enumerate(udev);
+                       }
                }
                goto tr_setup;
        }
@@ -1936,6 +1955,23 @@ usbd_req_re_enumerate(struct usb_device 
                return (USB_ERR_INVAL);
        }
 retry:
+       /*
+        * Try to reset the High Speed parent HUB of a LOW- or FULL-
+        * speed device, if any.
+        */
+       if (udev->parent_hs_hub != NULL &&
+           udev->speed != USB_SPEED_HIGH) {
+               DPRINTF("Trying to reset parent High Speed TT.\n");
+               err = usbd_req_reset_tt(udev->parent_hs_hub, NULL,
+                   udev->hs_port_no);
+               if (err) {
+                       DPRINTF("Resetting parent High "
+                           "Speed TT failed (%s).\n",
+                           usbd_errstr(err));
+               }
+       }
+
+       /* Try to reset the parent HUB port. */
        err = usbd_req_reset_port(parent_hub, mtx, udev->port_no);
        if (err) {
                DPRINTFN(0, "addr=%d, port reset failed, %s\n", 
@@ -2033,3 +2069,65 @@ usbd_req_set_device_feature(struct usb_d
        USETW(req.wLength, 0);
        return (usbd_do_request(udev, mtx, &req, 0));
 }
+
+/*------------------------------------------------------------------------*
+ *     usbd_req_reset_tt
+ *
+ * Returns:
+ *    0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_reset_tt(struct usb_device *udev, struct mtx *mtx,
+    uint8_t port)
+{
+       struct usb_device_request req;
+
+       /* For single TT HUBs the port should be 1 */
+
+       if (udev->ddesc.bDeviceClass == UDCLASS_HUB &&
+           udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT)
+               port = 1;
+
+       req.bmRequestType = UT_WRITE_CLASS_OTHER;
+       req.bRequest = UR_RESET_TT;
+       USETW(req.wValue, 0);
+       req.wIndex[0] = port;
+       req.wIndex[1] = 0;
+       USETW(req.wLength, 0);
+       return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ *     usbd_req_clear_tt_buffer
+ *
+ * For single TT HUBs the port should be 1.
+ *
+ * Returns:
+ *    0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_clear_tt_buffer(struct usb_device *udev, struct mtx *mtx,
+    uint8_t port, uint8_t addr, uint8_t type, uint8_t endpoint)
+{
+       struct usb_device_request req;
+       uint16_t wValue;
+
+       /* For single TT HUBs the port should be 1 */
+
+       if (udev->ddesc.bDeviceClass == UDCLASS_HUB &&
+           udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT)
+               port = 1;
+
+       wValue = (endpoint & 0xF) | ((addr & 0x7F) << 4) |
+           ((endpoint & 0x80) << 8) | ((type & 3) << 12);
+
+       req.bmRequestType = UT_WRITE_CLASS_OTHER;
+       req.bRequest = UR_CLEAR_TT_BUFFER;
+       USETW(req.wValue, wValue);
+       req.wIndex[0] = port;
+       req.wIndex[1] = 0;
+       USETW(req.wLength, 0);
+       return (usbd_do_request(udev, mtx, &req, 0));
+}

Modified: head/sys/dev/usb/usb_request.h
==============================================================================
--- head/sys/dev/usb/usb_request.h      Mon Jun  6 21:41:10 2011        
(r222785)
+++ head/sys/dev/usb/usb_request.h      Mon Jun  6 21:45:09 2011        
(r222786)
@@ -85,5 +85,9 @@ usb_error_t usbd_req_set_hub_u2_timeout(
                    struct mtx *mtx, uint8_t port, uint8_t timeout);
 usb_error_t usbd_req_set_hub_depth(struct usb_device *udev,
                    struct mtx *mtx, uint16_t depth);
+usb_error_t usbd_req_reset_tt(struct usb_device *udev, struct mtx *mtx,
+                   uint8_t port);
+usb_error_t usbd_req_clear_tt_buffer(struct usb_device *udev, struct mtx *mtx,
+                   uint8_t port, uint8_t addr, uint8_t type, uint8_t endpoint);
 
 #endif                                 /* _USB_REQUEST_H_ */

Modified: head/sys/dev/usb/usbdi.h
==============================================================================
--- head/sys/dev/usb/usbdi.h    Mon Jun  6 21:41:10 2011        (r222785)
+++ head/sys/dev/usb/usbdi.h    Mon Jun  6 21:45:09 2011        (r222786)
@@ -542,6 +542,7 @@ void        usbd_m_copy_in(struct usb_page_cach
            struct mbuf *m, usb_size_t src_offset, usb_frlength_t src_len);
 void   usbd_frame_zero(struct usb_page_cache *cache, usb_frlength_t offset,
            usb_frlength_t len);
+void   usbd_start_re_enumerate(struct usb_device *udev);
 
 int    usb_fifo_attach(struct usb_device *udev, void *priv_sc,
            struct mtx *priv_mtx, struct usb_fifo_methods *pm,
_______________________________________________
svn-src-head@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to