By Todd Brandt on Dec, 2016
The USB resume code in the kernel currently uses a set of hard coded delay values that are defined in the USB 2.0 spec. Specifically these have the most effect on resume time:
- tdrsmdn: resume signal time (20ms - infinity) usb 2.0 spec 7.1.7.7
- trsmrcy: resume recovery time (10ms) usb 2.0 spec 7.1.7.7
- trstrcy: reset recovery time (0ms - infinity) usb 2.0 spec 7.1.7.5
- tdrstr: root port reset time (50ms) usb2.0 spec 7.1.7.5
Unfortunately some of these values have been padded considerably in order to accomodate non-compliant devices. The kernel code currently defines these values as such:
- tdrsmdn: resume signal time = 40ms
- trstrcy: reset recovery time = 50ms
These values were padded for USB compliance testing and so that even non-compliant devices could still connect successfully. Basically they sacrificed performance for broader compatibility with poorly made USB devices. Here is the reasoning given in the kernel code comments:
For TDRSMDN (resume signal time) which has a minimum of 20ms, they set it to 40ms:
/*
* USB Resume Timer: Every Host controller driver should drive the resume
* signalling on the bus for the amount of time defined by this macro.
*
* That way we will have a 'stable' behavior among all HCDs supported by Linux.
*
* Note that the USB Specification states we should drive resume for *at least*
* 20 ms, but it doesn't give an upper bound. This creates two possible
* situations which we want to avoid:
*
* (a) sometimes an msleep(20) might expire slightly before 20 ms, which causes
* us to fail USB Electrical Tests, thus failing Certification
*
* (b) Some (many) devices actually need more than 20 ms of resume signalling,
* and while we can argue that's against the USB Specification, we don't have
* control over which devices a certification laboratory will be using for
* certification. If CertLab uses a device which was tested against Windows and
* that happens to have relaxed resume signalling rules, we might fall into
* situations where we fail interoperability and electrical tests.
*
* In order to avoid both conditions, we're using a 40 ms resume timeout, which
* should cope with both LPJ calibration errors and devices not following every
* detail of the USB Specification.
*/
#define USB_RESUME_TIMEOUT 40 /* ms */
For TRSTRCY (reset recovery time) which has a minimum of 0ms, they set it to 50ms:
/* TRSTRCY = 10 ms; plus some extra */
msleep(10 + 40);
Thus, a simple way of improving USB performance is to go through and set these values to their spec minimums. The following patches do this by creating a struct which contains these three values, and allowing a kernel parameter select between the current defaults and the spec minimums:
- 0000-cover-letter.patch
- 0001-USB-add-switch-to-turn-off-padding-of-resume-time-de.patch
- 0002-USB-usb-timing-value-debug.patch
To test, apply them and add the following module param to the kernel command line:
usbcore.timing_minimum=1
In my own testing I have yet to discover a device that fails to resume properly with the values set at their minimums; and the benefits are really impressive. On systems with several USB devices attached you can get several hundred milliseconds of savings. Take the following test case as an example. I used the patch on an ivybridge laptop running ubuntu 16.04. I attached a USB hub with a webcam, 4TB USB drive, a cellphone, and a wireless keyboard adapter. The machine also has an internal USB webcam, touchscreen, and bluetooth reciever. That's seven USB devices which get resumed. With the patch the total resume time is reduced by 250ms with no loss of functionality.
This is the timeline with the patch disabled, using the default delay values:
This is the timeline with the patch enabled, using the minimum delay values:
Note that the USB subsystem was the major factor in the first resume time, but in the second it switched to the i915. If you ignore the i915 and phy driver resume time, you can see that USB resume has improved by nearly 250ms.