Skip to content

Commit cfd54fa

Browse files
matnymangregkh
authored andcommitted
usb: Fix out of sync data toggle if a configured device is reconfigured
Userspace drivers that use a SetConfiguration() request to "lightweight" reset an already configured usb device might cause data toggles to get out of sync between the device and host, and the device becomes unusable. The xHCI host requires endpoints to be dropped and added back to reset the toggle. If USB core notices the new configuration is the same as the current active configuration it will avoid these extra steps by calling usb_reset_configuration() instead of usb_set_configuration(). A SetConfiguration() request will reset the device side data toggles. Make sure usb_reset_configuration() function also drops and adds back the endpoints to ensure data toggles are in sync. To avoid code duplication split the current usb_disable_device() function and reuse the endpoint specific part. Cc: stable <[email protected]> Tested-by: Martin Thierer <[email protected]> Signed-off-by: Mathias Nyman <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 6b6c71e commit cfd54fa

File tree

1 file changed

+42
-49
lines changed

1 file changed

+42
-49
lines changed

drivers/usb/core/message.c

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,34 @@ void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf,
12051205
}
12061206
}
12071207

1208+
/*
1209+
* usb_disable_device_endpoints -- Disable all endpoints for a device
1210+
* @dev: the device whose endpoints are being disabled
1211+
* @skip_ep0: 0 to disable endpoint 0, 1 to skip it.
1212+
*/
1213+
static void usb_disable_device_endpoints(struct usb_device *dev, int skip_ep0)
1214+
{
1215+
struct usb_hcd *hcd = bus_to_hcd(dev->bus);
1216+
int i;
1217+
1218+
if (hcd->driver->check_bandwidth) {
1219+
/* First pass: Cancel URBs, leave endpoint pointers intact. */
1220+
for (i = skip_ep0; i < 16; ++i) {
1221+
usb_disable_endpoint(dev, i, false);
1222+
usb_disable_endpoint(dev, i + USB_DIR_IN, false);
1223+
}
1224+
/* Remove endpoints from the host controller internal state */
1225+
mutex_lock(hcd->bandwidth_mutex);
1226+
usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL);
1227+
mutex_unlock(hcd->bandwidth_mutex);
1228+
}
1229+
/* Second pass: remove endpoint pointers */
1230+
for (i = skip_ep0; i < 16; ++i) {
1231+
usb_disable_endpoint(dev, i, true);
1232+
usb_disable_endpoint(dev, i + USB_DIR_IN, true);
1233+
}
1234+
}
1235+
12081236
/**
12091237
* usb_disable_device - Disable all the endpoints for a USB device
12101238
* @dev: the device whose endpoints are being disabled
@@ -1218,7 +1246,6 @@ void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf,
12181246
void usb_disable_device(struct usb_device *dev, int skip_ep0)
12191247
{
12201248
int i;
1221-
struct usb_hcd *hcd = bus_to_hcd(dev->bus);
12221249

12231250
/* getting rid of interfaces will disconnect
12241251
* any drivers bound to them (a key side effect)
@@ -1264,22 +1291,8 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
12641291

12651292
dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__,
12661293
skip_ep0 ? "non-ep0" : "all");
1267-
if (hcd->driver->check_bandwidth) {
1268-
/* First pass: Cancel URBs, leave endpoint pointers intact. */
1269-
for (i = skip_ep0; i < 16; ++i) {
1270-
usb_disable_endpoint(dev, i, false);
1271-
usb_disable_endpoint(dev, i + USB_DIR_IN, false);
1272-
}
1273-
/* Remove endpoints from the host controller internal state */
1274-
mutex_lock(hcd->bandwidth_mutex);
1275-
usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL);
1276-
mutex_unlock(hcd->bandwidth_mutex);
1277-
/* Second pass: remove endpoint pointers */
1278-
}
1279-
for (i = skip_ep0; i < 16; ++i) {
1280-
usb_disable_endpoint(dev, i, true);
1281-
usb_disable_endpoint(dev, i + USB_DIR_IN, true);
1282-
}
1294+
1295+
usb_disable_device_endpoints(dev, skip_ep0);
12831296
}
12841297

12851298
/**
@@ -1522,6 +1535,9 @@ EXPORT_SYMBOL_GPL(usb_set_interface);
15221535
* The caller must own the device lock.
15231536
*
15241537
* Return: Zero on success, else a negative error code.
1538+
*
1539+
* If this routine fails the device will probably be in an unusable state
1540+
* with endpoints disabled, and interfaces only partially enabled.
15251541
*/
15261542
int usb_reset_configuration(struct usb_device *dev)
15271543
{
@@ -1537,10 +1553,7 @@ int usb_reset_configuration(struct usb_device *dev)
15371553
* calls during probe() are fine
15381554
*/
15391555

1540-
for (i = 1; i < 16; ++i) {
1541-
usb_disable_endpoint(dev, i, true);
1542-
usb_disable_endpoint(dev, i + USB_DIR_IN, true);
1543-
}
1556+
usb_disable_device_endpoints(dev, 1); /* skip ep0*/
15441557

15451558
config = dev->actconfig;
15461559
retval = 0;
@@ -1553,34 +1566,10 @@ int usb_reset_configuration(struct usb_device *dev)
15531566
mutex_unlock(hcd->bandwidth_mutex);
15541567
return -ENOMEM;
15551568
}
1556-
/* Make sure we have enough bandwidth for each alternate setting 0 */
1557-
for (i = 0; i < config->desc.bNumInterfaces; i++) {
1558-
struct usb_interface *intf = config->interface[i];
1559-
struct usb_host_interface *alt;
15601569

1561-
alt = usb_altnum_to_altsetting(intf, 0);
1562-
if (!alt)
1563-
alt = &intf->altsetting[0];
1564-
if (alt != intf->cur_altsetting)
1565-
retval = usb_hcd_alloc_bandwidth(dev, NULL,
1566-
intf->cur_altsetting, alt);
1567-
if (retval < 0)
1568-
break;
1569-
}
1570-
/* If not, reinstate the old alternate settings */
1570+
/* xHCI adds all endpoints in usb_hcd_alloc_bandwidth */
1571+
retval = usb_hcd_alloc_bandwidth(dev, config, NULL, NULL);
15711572
if (retval < 0) {
1572-
reset_old_alts:
1573-
for (i--; i >= 0; i--) {
1574-
struct usb_interface *intf = config->interface[i];
1575-
struct usb_host_interface *alt;
1576-
1577-
alt = usb_altnum_to_altsetting(intf, 0);
1578-
if (!alt)
1579-
alt = &intf->altsetting[0];
1580-
if (alt != intf->cur_altsetting)
1581-
usb_hcd_alloc_bandwidth(dev, NULL,
1582-
alt, intf->cur_altsetting);
1583-
}
15841573
usb_enable_lpm(dev);
15851574
mutex_unlock(hcd->bandwidth_mutex);
15861575
return retval;
@@ -1589,8 +1578,12 @@ int usb_reset_configuration(struct usb_device *dev)
15891578
USB_REQ_SET_CONFIGURATION, 0,
15901579
config->desc.bConfigurationValue, 0,
15911580
NULL, 0, USB_CTRL_SET_TIMEOUT);
1592-
if (retval < 0)
1593-
goto reset_old_alts;
1581+
if (retval < 0) {
1582+
usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL);
1583+
usb_enable_lpm(dev);
1584+
mutex_unlock(hcd->bandwidth_mutex);
1585+
return retval;
1586+
}
15941587
mutex_unlock(hcd->bandwidth_mutex);
15951588

15961589
/* re-init hc/hcd interface/endpoint state */

0 commit comments

Comments
 (0)