[PATCH] usb: core: add support for USB_REQ_SET_ISOCH_DELAY

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxxxxxxx>
---

Tested against dwc3. Here's tracepoints showing values changing when
connecting straight to roothub port or through one USB3 hub:

irq/13-dwc3-1577  [000] d..1   497.671262: dwc3_ctrl_req: Set Isochronous Delay(Delay = 40 ns)
irq/13-dwc3-1577  [000] d..1   766.912097: dwc3_ctrl_req: Set Isochronous Delay(Delay = 108 ns)

 drivers/usb/core/hub.c     | 24 ++++++++++++++++++++++++
 drivers/usb/core/message.c | 24 ++++++++++++++++++++++++
 drivers/usb/core/usb.h     |  1 +
 include/linux/usb.h        |  6 ++++++
 4 files changed, 55 insertions(+)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index e9ce6bb0b22d..7feef558f788 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -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. */
@@ -1352,6 +1355,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");
@@ -4586,7 +4603,14 @@ 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);
+			usb_set_isoch_delay(udev);
 			break;
 		}
 	}
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 371a07d874a3..c1db751163d5 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -915,6 +915,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.
+ */
+void usb_set_isoch_delay(struct usb_device *dev)
+{
+	/* skip hub devices */
+	if (dev->descriptor.bDeviceClass == USB_CLASS_HUB)
+		return;
+
+	/* skip non-SS/non-SSP devices */
+	if (dev->speed < USB_SPEED_SUPER)
+		return;
+
+	(void) 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
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index dc6949248823..4671b171747c 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -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 void 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);
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 9c63792a8134..1e026fb975db 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -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)
 
-- 
2.14.2.642.g20fed7cad4

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux