xHCI/USB: Make xHCI driver have a BOS descriptor.
authorSarah Sharp <sarah.a.sharp@linux.intel.com>
Thu, 6 Oct 2011 18:54:23 +0000 (11:54 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 18 Oct 2011 20:42:06 +0000 (13:42 -0700)
To add USB 3.0 link power management (LPM), we need to know what the U1
and U2 exit latencies are for the xHCI host controller.  External USB 3.0
hubs report these values through the SuperSpeed Capabilities descriptor in
the BOS descriptor.  Make the USB 3.0 roothub for the xHCI host behave
like an external hub and return the BOS descriptors.

The U1 and U2 exit latencies will vary across each host controller, so we
need to dynamically fill those values in by reading the exit latencies out
of the xHC registers.  Make the roothub code in the USB core handle
hub_control() returning the length of the data copied.

Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/hcd.c
drivers/usb/host/xhci-hub.c

index 100d4b002642e0c6e272c3f465d465701a7aeec5..b3b7d062906db53030de3a0a4379273e079884db 100644 (file)
@@ -442,7 +442,11 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
        struct usb_ctrlrequest *cmd;
        u16             typeReq, wValue, wIndex, wLength;
        u8              *ubuf = urb->transfer_buffer;
-       u8              tbuf [sizeof (struct usb_hub_descriptor)]
+       /*
+        * tbuf should be as big as the BOS descriptor and
+        * the USB hub descriptor.
+        */
+       u8              tbuf[USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE]
                __attribute__((aligned(4)));
        const u8        *bufp = tbuf;
        unsigned        len = 0;
@@ -562,6 +566,8 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
                        else /* unsupported IDs --> "protocol stall" */
                                goto error;
                        break;
+               case USB_DT_BOS << 8:
+                       goto nongeneric;
                default:
                        goto error;
                }
@@ -596,6 +602,7 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
        /* CLASS REQUESTS (and errors) */
 
        default:
+nongeneric:
                /* non-generic request */
                switch (typeReq) {
                case GetHubStatus:
@@ -605,6 +612,9 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
                case GetHubDescriptor:
                        len = sizeof (struct usb_hub_descriptor);
                        break;
+               case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+                       /* len is returned by hub_control */
+                       break;
                }
                status = hcd->driver->hub_control (hcd,
                        typeReq, wValue, wIndex,
@@ -615,7 +625,7 @@ error:
                status = -EPIPE;
        }
 
-       if (status) {
+       if (status < 0) {
                len = 0;
                if (status != -EPIPE) {
                        dev_dbg (hcd->self.controller,
@@ -624,6 +634,10 @@ error:
                                typeReq, wValue, wIndex,
                                wLength, status);
                }
+       } else if (status > 0) {
+               /* hub_control may return the length of data copied. */
+               len = status;
+               status = 0;
        }
        if (len) {
                if (urb->transfer_buffer_length < len)
index 9f844d45c667516f57c5dcf7a17b5b1034c11435..2f74eddbe3c1adde3a8b20115dad6a4510432dd8 100644 (file)
 #define        PORT_RWC_BITS   (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \
                         PORT_RC | PORT_PLC | PORT_PE)
 
+/* usb 1.1 root hub device descriptor */
+static u8 usb_bos_descriptor [] = {
+       USB_DT_BOS_SIZE,                /*  __u8 bLength, 5 bytes */
+       USB_DT_BOS,                     /*  __u8 bDescriptorType */
+       0x0F, 0x00,                     /*  __le16 wTotalLength, 15 bytes */
+       0x1,                            /*  __u8 bNumDeviceCaps */
+       /* First device capability */
+       USB_DT_USB_SS_CAP_SIZE,         /*  __u8 bLength, 10 bytes */
+       USB_DT_DEVICE_CAPABILITY,       /* Device Capability */
+       USB_SS_CAP_TYPE,                /* bDevCapabilityType, SUPERSPEED_USB */
+       0x00,                           /* bmAttributes, LTM off by default */
+       USB_5GBPS_OPERATION, 0x00,      /* wSpeedsSupported, 5Gbps only */
+       0x03,                           /* bFunctionalitySupport,
+                                          USB 3.0 speed only */
+       0x00,                           /* bU1DevExitLat, set later. */
+       0x00, 0x00                      /* __le16 bU2DevExitLat, set later. */
+};
+
+
 static void xhci_common_hub_descriptor(struct xhci_hcd *xhci,
                struct usb_hub_descriptor *desc, int ports)
 {
@@ -455,6 +474,21 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
                xhci_hub_descriptor(hcd, xhci,
                                (struct usb_hub_descriptor *) buf);
                break;
+       case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+               if ((wValue & 0xff00) != (USB_DT_BOS << 8))
+                       goto error;
+
+               if (hcd->speed != HCD_USB3)
+                       goto error;
+
+               memcpy(buf, &usb_bos_descriptor,
+                               USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE);
+               temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params3);
+               buf[12] = HCS_U1_LATENCY(temp);
+               put_unaligned_le16(HCS_U2_LATENCY(temp), &buf[13]);
+
+               spin_unlock_irqrestore(&xhci->lock, flags);
+               return USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE;
        case GetPortStatus:
                if (!wIndex || wIndex > max_ports)
                        goto error;