usb: core: add support for USB_REQ_SET_ISOCH_DELAY
authorFelipe Balbi <felipe.balbi@linux.intel.com>
Thu, 14 Dec 2017 07:50:39 +0000 (09:50 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 15 Dec 2017 19:45:43 +0000 (20:45 +0100)
USB SS and SSP hubs provide wHubDelay values on their hub descriptor
which we should inform the USB Device about.

The USB Specification 3.0 explains, on section 9.4.11, how to
calculate the value and how to issue the request. Note that a
USB_REQ_SET_ISOCH_DELAY is valid on all device states (Default,
Address, Configured), we just *chose* to issue it from Address state
right after successfully fetching the USB Device Descriptor.

Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/hub.c
drivers/usb/core/message.c
drivers/usb/core/usb.h
include/linux/usb.h

index 916bcd539afac164dc955a590864764715430c0d..09bd3c3ad8b14128f26365b7c53503b9a7c9e444 100644 (file)
@@ -38,6 +38,9 @@
 #define USB_VENDOR_GENESYS_LOGIC               0x05e3
 #define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND       0x01
 
+#define USB_TP_TRANSMISSION_DELAY      40      /* ns */
+#define USB_TP_TRANSMISSION_DELAY_MAX  65535   /* ns */
+
 /* Protect struct usb_device->state and ->children members
  * Note: Both are also protected by ->dev.sem, except that ->state can
  * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */
@@ -1350,6 +1353,20 @@ static int hub_configure(struct usb_hub *hub,
                goto fail;
        }
 
+       /*
+        * Accumulate wHubDelay + 40ns for every hub in the tree of devices.
+        * The resulting value will be used for SetIsochDelay() request.
+        */
+       if (hub_is_superspeed(hdev) || hub_is_superspeedplus(hdev)) {
+               u32 delay = __le16_to_cpu(hub->descriptor->u.ss.wHubDelay);
+
+               if (hdev->parent)
+                       delay += hdev->parent->hub_delay;
+
+               delay += USB_TP_TRANSMISSION_DELAY;
+               hdev->hub_delay = min_t(u32, delay, USB_TP_TRANSMISSION_DELAY_MAX);
+       }
+
        maxchild = hub->descriptor->bNbrPorts;
        dev_info(hub_dev, "%d port%s detected\n", maxchild,
                        (maxchild == 1) ? "" : "s");
@@ -4597,7 +4614,20 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
                        if (retval >= 0)
                                retval = -EMSGSIZE;
                } else {
+                       u32 delay;
+
                        retval = 0;
+
+                       delay = udev->parent->hub_delay;
+                       udev->hub_delay = min_t(u32, delay,
+                                               USB_TP_TRANSMISSION_DELAY_MAX);
+                       retval = usb_set_isoch_delay(udev);
+                       if (retval) {
+                               dev_dbg(&udev->dev,
+                                       "Failed set isoch delay, error %d\n",
+                                       retval);
+                               retval = 0;
+                       }
                        break;
                }
        }
index dac4adeec21392487fd01cc364055a54b6efd1c2..c64cf6c4a83dde975fa90188f2b5f08cec91d688 100644 (file)
@@ -916,6 +916,30 @@ int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
        return ret;
 }
 
+/*
+ * usb_set_isoch_delay - informs the device of the packet transmit delay
+ * @dev: the device whose delay is to be informed
+ * Context: !in_interrupt()
+ *
+ * Since this is an optional request, we don't bother if it fails.
+ */
+int usb_set_isoch_delay(struct usb_device *dev)
+{
+       /* skip hub devices */
+       if (dev->descriptor.bDeviceClass == USB_CLASS_HUB)
+               return 0;
+
+       /* skip non-SS/non-SSP devices */
+       if (dev->speed < USB_SPEED_SUPER)
+               return 0;
+
+       return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+                       USB_REQ_SET_ISOCH_DELAY,
+                       USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+                       cpu_to_le16(dev->hub_delay), 0, NULL, 0,
+                       USB_CTRL_SET_TIMEOUT);
+}
+
 /**
  * usb_get_status - issues a GET_STATUS call
  * @dev: the device whose status is being checked
index 2bee08d084ae51ea3a84452329389b830b23168f..149cc74809712faef1d9f8b4c17e4e91126ebb28 100644 (file)
@@ -40,6 +40,7 @@ extern int usb_remove_device(struct usb_device *udev);
 
 extern int usb_get_device_descriptor(struct usb_device *dev,
                unsigned int size);
+extern int usb_set_isoch_delay(struct usb_device *dev);
 extern int usb_get_bos_descriptor(struct usb_device *dev);
 extern void usb_release_bos_descriptor(struct usb_device *dev);
 extern char *usb_cache_string(struct usb_device *udev, int index);
index fe665a0d5bceee70b0a320ef89311bd479b8ba0f..0173597e59aa55c6e6841c452ce772a4ed9cd1ed 100644 (file)
@@ -609,6 +609,10 @@ struct usb3_lpm_parameters {
  *     to keep track of the number of functions that require USB 3.0 Link Power
  *     Management to be disabled for this usb_device.  This count should only
  *     be manipulated by those functions, with the bandwidth_mutex is held.
+ * @hub_delay: cached value consisting of:
+ *             parent->hub_delay + wHubDelay + tTPTransmissionDelay (40ns)
+ *
+ *     Will be used as wValue for SetIsochDelay requests.
  *
  * Notes:
  * Usbcore drivers should not set usbdev->state directly.  Instead use
@@ -689,6 +693,8 @@ struct usb_device {
        struct usb3_lpm_parameters u1_params;
        struct usb3_lpm_parameters u2_params;
        unsigned lpm_disable_count;
+
+       u16 hub_delay;
 };
 #define        to_usb_device(d) container_of(d, struct usb_device, dev)