summaryrefslogtreecommitdiff
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c153
1 files changed, 105 insertions, 48 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 21ac9b464696..416af6d76374 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -18,6 +18,7 @@
#include <linux/sched/mm.h>
#include <linux/list.h>
#include <linux/slab.h>
+#include <linux/string_choices.h>
#include <linux/kcov.h>
#include <linux/ioctl.h>
#include <linux/usb.h>
@@ -1384,7 +1385,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
}
/* Stop hub_wq and related activity */
- del_timer_sync(&hub->irq_urb_retry);
+ timer_delete_sync(&hub->irq_urb_retry);
usb_kill_urb(hub->urb);
if (hub->has_indicators)
cancel_delayed_work_sync(&hub->leds);
@@ -1496,7 +1497,7 @@ static int hub_configure(struct usb_hub *hub,
maxchild = hub->descriptor->bNbrPorts;
dev_info(hub_dev, "%d port%s detected\n", maxchild,
- (maxchild == 1) ? "" : "s");
+ str_plural(maxchild));
hub->ports = kcalloc(maxchild, sizeof(struct usb_port *), GFP_KERNEL);
if (!hub->ports) {
@@ -1848,6 +1849,17 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
hdev = interface_to_usbdev(intf);
/*
+ * The USB 2.0 spec prohibits hubs from having more than one
+ * configuration or interface, and we rely on this prohibition.
+ * Refuse to accept a device that violates it.
+ */
+ if (hdev->descriptor.bNumConfigurations > 1 ||
+ hdev->actconfig->desc.bNumInterfaces > 1) {
+ dev_err(&intf->dev, "Invalid hub with more than one config or interface\n");
+ return -EINVAL;
+ }
+
+ /*
* Set default autosuspend delay as 0 to speedup bus suspend,
* based on the below considerations:
*
@@ -4139,16 +4151,16 @@ static int usb_set_device_initiated_lpm(struct usb_device *udev,
break;
default:
dev_warn(&udev->dev, "%s: Can't %s non-U1 or U2 state.\n",
- __func__, enable ? "enable" : "disable");
+ __func__, str_enable_disable(enable));
return -EINVAL;
}
if (udev->state != USB_STATE_CONFIGURED) {
dev_dbg(&udev->dev, "%s: Can't %s %s state "
"for unconfigured device.\n",
- __func__, enable ? "enable" : "disable",
+ __func__, str_enable_disable(enable),
usb3_lpm_names[state]);
- return 0;
+ return -EINVAL;
}
if (enable) {
@@ -4172,8 +4184,7 @@ static int usb_set_device_initiated_lpm(struct usb_device *udev,
}
if (ret < 0) {
dev_warn(&udev->dev, "%s of device-initiated %s failed.\n",
- enable ? "Enable" : "Disable",
- usb3_lpm_names[state]);
+ str_enable_disable(enable), usb3_lpm_names[state]);
return -EBUSY;
}
return 0;
@@ -4223,9 +4234,9 @@ static int usb_set_lpm_timeout(struct usb_device *udev,
}
/*
- * Don't allow device intiated U1/U2 if the system exit latency + one bus
- * interval is greater than the minimum service interval of any active
- * periodic endpoint. See USB 3.2 section 9.4.9
+ * Don't allow device intiated U1/U2 if device isn't in the configured state,
+ * or the system exit latency + one bus interval is greater than the minimum
+ * service interval of any active periodic endpoint. See USB 3.2 section 9.4.9
*/
static bool usb_device_may_initiate_lpm(struct usb_device *udev,
enum usb3_link_state state)
@@ -4233,7 +4244,7 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev,
unsigned int sel; /* us */
int i, j;
- if (!udev->lpm_devinit_allow)
+ if (!udev->lpm_devinit_allow || !udev->actconfig)
return false;
if (state == USB3_LPM_U1)
@@ -4281,7 +4292,7 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev,
* driver know about it. If that call fails, it should be harmless, and just
* take up more slightly more bus bandwidth for unnecessary U1/U2 exit latency.
*/
-static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
+static int usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
enum usb3_link_state state)
{
int timeout;
@@ -4290,7 +4301,7 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
/* Skip if the device BOS descriptor couldn't be read */
if (!udev->bos)
- return;
+ return -EINVAL;
u1_mel = udev->bos->ss_cap->bU1devExitLat;
u2_mel = udev->bos->ss_cap->bU2DevExitLat;
@@ -4301,7 +4312,7 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
*/
if ((state == USB3_LPM_U1 && u1_mel == 0) ||
(state == USB3_LPM_U2 && u2_mel == 0))
- return;
+ return -EINVAL;
/* We allow the host controller to set the U1/U2 timeout internally
* first, so that it can change its schedule to account for the
@@ -4312,13 +4323,13 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
/* xHCI host controller doesn't want to enable this LPM state. */
if (timeout == 0)
- return;
+ return -EINVAL;
if (timeout < 0) {
dev_warn(&udev->dev, "Could not enable %s link state, "
"xHCI error %i.\n", usb3_lpm_names[state],
timeout);
- return;
+ return timeout;
}
if (usb_set_lpm_timeout(udev, state, timeout)) {
@@ -4327,29 +4338,15 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
* host know that this link state won't be enabled.
*/
hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state);
- return;
- }
-
- /* Only a configured device will accept the Set Feature
- * U1/U2_ENABLE
- */
- if (udev->actconfig &&
- usb_device_may_initiate_lpm(udev, state)) {
- if (usb_set_device_initiated_lpm(udev, state, true)) {
- /*
- * Request to enable device initiated U1/U2 failed,
- * better to turn off lpm in this case.
- */
- usb_set_lpm_timeout(udev, state, 0);
- hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state);
- return;
- }
+ return -EBUSY;
}
if (state == USB3_LPM_U1)
udev->usb3_lpm_u1_enabled = 1;
else if (state == USB3_LPM_U2)
udev->usb3_lpm_u2_enabled = 1;
+
+ return 0;
}
/*
* Disable the hub-initiated U1/U2 idle timeouts, and disable device-initiated
@@ -4382,8 +4379,6 @@ static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
if (usb_set_lpm_timeout(udev, state, 0))
return -EBUSY;
- usb_set_device_initiated_lpm(udev, state, false);
-
if (hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state))
dev_warn(&udev->dev, "Could not disable xHCI %s timeout, "
"bus schedule bandwidth may be impacted.\n",
@@ -4413,6 +4408,7 @@ static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
int usb_disable_lpm(struct usb_device *udev)
{
struct usb_hcd *hcd;
+ int err;
if (!udev || !udev->parent ||
udev->speed < USB_SPEED_SUPER ||
@@ -4430,14 +4426,19 @@ int usb_disable_lpm(struct usb_device *udev)
/* If LPM is enabled, attempt to disable it. */
if (usb_disable_link_state(hcd, udev, USB3_LPM_U1))
- goto enable_lpm;
+ goto disable_failed;
if (usb_disable_link_state(hcd, udev, USB3_LPM_U2))
- goto enable_lpm;
+ goto disable_failed;
+
+ err = usb_set_device_initiated_lpm(udev, USB3_LPM_U1, false);
+ if (!err)
+ usb_set_device_initiated_lpm(udev, USB3_LPM_U2, false);
return 0;
-enable_lpm:
- usb_enable_lpm(udev);
+disable_failed:
+ udev->lpm_disable_count--;
+
return -EBUSY;
}
EXPORT_SYMBOL_GPL(usb_disable_lpm);
@@ -4498,10 +4499,24 @@ void usb_enable_lpm(struct usb_device *udev)
port_dev = hub->ports[udev->portnum - 1];
if (port_dev->usb3_lpm_u1_permit)
- usb_enable_link_state(hcd, udev, USB3_LPM_U1);
+ if (usb_enable_link_state(hcd, udev, USB3_LPM_U1))
+ return;
if (port_dev->usb3_lpm_u2_permit)
- usb_enable_link_state(hcd, udev, USB3_LPM_U2);
+ if (usb_enable_link_state(hcd, udev, USB3_LPM_U2))
+ return;
+
+ /*
+ * Enable device initiated U1/U2 with a SetFeature(U1/U2_ENABLE) request
+ * if system exit latency is short enough and device is configured
+ */
+ if (usb_device_may_initiate_lpm(udev, USB3_LPM_U1)) {
+ if (usb_set_device_initiated_lpm(udev, USB3_LPM_U1, true))
+ return;
+
+ if (usb_device_may_initiate_lpm(udev, USB3_LPM_U2))
+ usb_set_device_initiated_lpm(udev, USB3_LPM_U2, true);
+ }
}
EXPORT_SYMBOL_GPL(usb_enable_lpm);
@@ -4697,9 +4712,6 @@ void usb_ep0_reinit(struct usb_device *udev)
}
EXPORT_SYMBOL_GPL(usb_ep0_reinit);
-#define usb_sndaddr0pipe() (PIPE_CONTROL << 30)
-#define usb_rcvaddr0pipe() ((PIPE_CONTROL << 30) | USB_DIR_IN)
-
static int hub_set_address(struct usb_device *udev, int devnum)
{
int retval;
@@ -4723,7 +4735,7 @@ static int hub_set_address(struct usb_device *udev, int devnum)
if (hcd->driver->address_device)
retval = hcd->driver->address_device(hcd, udev, timeout_ms);
else
- retval = usb_control_msg(udev, usb_sndaddr0pipe(),
+ retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_SET_ADDRESS, 0, devnum, 0,
NULL, 0, timeout_ms);
if (retval == 0) {
@@ -4804,7 +4816,7 @@ static int get_bMaxPacketSize0(struct usb_device *udev,
for (i = 0; i < GET_MAXPACKET0_TRIES; ++i) {
/* Start with invalid values in case the transfer fails */
buf->bDescriptorType = buf->bMaxPacketSize0 = 0;
- rc = usb_control_msg(udev, usb_rcvaddr0pipe(),
+ rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
USB_DT_DEVICE << 8, 0,
buf, size,
@@ -6056,6 +6068,36 @@ void usb_hub_cleanup(void)
} /* usb_hub_cleanup() */
/**
+ * hub_hc_release_resources - clear resources used by host controller
+ * @udev: pointer to device being released
+ *
+ * Context: task context, might sleep
+ *
+ * Function releases the host controller resources in correct order before
+ * making any operation on resuming usb device. The host controller resources
+ * allocated for devices in tree should be released starting from the last
+ * usb device in tree toward the root hub. This function is used only during
+ * resuming device when usb device require reinitialization – that is, when
+ * flag udev->reset_resume is set.
+ *
+ * This call is synchronous, and may not be used in an interrupt context.
+ */
+static void hub_hc_release_resources(struct usb_device *udev)
+{
+ struct usb_hub *hub = usb_hub_to_struct_hub(udev);
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+ int i;
+
+ /* Release up resources for all children before this device */
+ for (i = 0; i < udev->maxchild; i++)
+ if (hub->ports[i]->child)
+ hub_hc_release_resources(hub->ports[i]->child);
+
+ if (hcd->driver->reset_device)
+ hcd->driver->reset_device(hcd, udev);
+}
+
+/**
* usb_reset_and_verify_device - perform a USB port reset to reinitialize a device
* @udev: device to reset (not in SUSPENDED or NOTATTACHED state)
*
@@ -6095,6 +6137,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
struct usb_hub *parent_hub;
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
struct usb_device_descriptor descriptor;
+ struct usb_interface *intf;
struct usb_host_bos *bos;
int i, j, ret = 0;
int port1 = udev->portnum;
@@ -6119,6 +6162,9 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
bos = udev->bos;
udev->bos = NULL;
+ if (udev->reset_resume)
+ hub_hc_release_resources(udev);
+
mutex_lock(hcd->address0_mutex);
for (i = 0; i < PORT_INIT_TRIES; ++i) {
@@ -6149,6 +6195,18 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
if (!udev->actconfig)
goto done;
+ /*
+ * Some devices can't handle setting default altsetting 0 with a
+ * Set-Interface request. Disable host-side endpoints of those
+ * interfaces here. Enable and reset them back after host has set
+ * its internal endpoint structures during usb_hcd_alloc_bandwith()
+ */
+ for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
+ intf = udev->actconfig->interface[i];
+ if (intf->cur_altsetting->desc.bAlternateSetting == 0)
+ usb_disable_interface(udev, intf, true);
+ }
+
mutex_lock(hcd->bandwidth_mutex);
ret = usb_hcd_alloc_bandwidth(udev, udev->actconfig, NULL, NULL);
if (ret < 0) {
@@ -6180,12 +6238,11 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
*/
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
struct usb_host_config *config = udev->actconfig;
- struct usb_interface *intf = config->interface[i];
struct usb_interface_descriptor *desc;
+ intf = config->interface[i];
desc = &intf->cur_altsetting->desc;
if (desc->bAlternateSetting == 0) {
- usb_disable_interface(udev, intf, true);
usb_enable_interface(udev, intf, true);
ret = 0;
} else {