diff options
Diffstat (limited to 'drivers/usb/core')
-rw-r--r-- | drivers/usb/core/devices.c | 13 | ||||
-rw-r--r-- | drivers/usb/core/generic.c | 2 | ||||
-rw-r--r-- | drivers/usb/core/hcd.c | 1 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 266 | ||||
-rw-r--r-- | drivers/usb/core/message.c | 2 | ||||
-rw-r--r-- | drivers/usb/core/sysfs.c | 31 | ||||
-rw-r--r-- | drivers/usb/core/usb.h | 9 |
7 files changed, 192 insertions, 132 deletions
diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index cbacea933b1..e33224e2377 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -316,17 +316,23 @@ static char *usb_dump_iad_descriptor(char *start, char *end, */ static char *usb_dump_config_descriptor(char *start, char *end, const struct usb_config_descriptor *desc, - int active) + int active, int speed) { + int mul; + if (start > end) return start; + if (speed == USB_SPEED_SUPER) + mul = 8; + else + mul = 2; start += sprintf(start, format_config, /* mark active/actual/current cfg. */ active ? '*' : ' ', desc->bNumInterfaces, desc->bConfigurationValue, desc->bmAttributes, - desc->bMaxPower * 2); + desc->bMaxPower * mul); return start; } @@ -342,7 +348,8 @@ static char *usb_dump_config(int speed, char *start, char *end, if (!config) /* getting these some in 2.3.7; none in 2.3.6 */ return start + sprintf(start, "(null Cfg. desc.)\n"); - start = usb_dump_config_descriptor(start, end, &config->desc, active); + start = usb_dump_config_descriptor(start, end, &config->desc, active, + speed); for (i = 0; i < USB_MAXIADS; i++) { if (config->intf_assoc[i] == NULL) break; diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index eff2010eb63..271e761f563 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -100,7 +100,7 @@ int usb_choose_configuration(struct usb_device *udev) */ /* Rule out configs that draw too much bus current */ - if (c->desc.bMaxPower * 2 > udev->bus_mA) { + if (usb_get_max_power(udev, c) > udev->bus_mA) { insufficient_power++; continue; } diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 4225d5e7213..5f6da8b2d6a 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2506,7 +2506,6 @@ int usb_add_hcd(struct usb_hcd *hcd, } /* starting here, usbcore will pay attention to this root hub */ - rhdev->bus_mA = min(500u, hcd->power_budget); if ((retval = register_root_hub(hcd)) != 0) goto err_register_root_hub; diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 957ed2c4148..dc6ebd1d88d 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1351,6 +1351,8 @@ static int hub_configure(struct usb_hub *hub, unsigned int pipe; int maxp, ret, i; char *message = "out of memory"; + unsigned unit_load; + unsigned full_load; hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL); if (!hub->buffer) { @@ -1397,6 +1399,13 @@ static int hub_configure(struct usb_hub *hub, } wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics); + if (hub_is_superspeed(hdev)) { + unit_load = 150; + full_load = 900; + } else { + unit_load = 100; + full_load = 500; + } /* FIXME for USB 3.0, skip for now */ if ((wHubCharacteristics & HUB_CHAR_COMPOUND) && @@ -1516,40 +1525,44 @@ static int hub_configure(struct usb_hub *hub, goto fail; } le16_to_cpus(&hubstatus); + hcd = bus_to_hcd(hdev->bus); if (hdev == hdev->bus->root_hub) { - if (hdev->bus_mA == 0 || hdev->bus_mA >= 500) - hub->mA_per_port = 500; + if (hcd->power_budget > 0) + hdev->bus_mA = hcd->power_budget; + else + hdev->bus_mA = full_load * hdev->maxchild; + if (hdev->bus_mA >= full_load) + hub->mA_per_port = full_load; else { hub->mA_per_port = hdev->bus_mA; hub->limited_power = 1; } } else if ((hubstatus & (1 << USB_DEVICE_SELF_POWERED)) == 0) { + int remaining = hdev->bus_mA - + hub->descriptor->bHubContrCurrent; + dev_dbg(hub_dev, "hub controller current requirement: %dmA\n", hub->descriptor->bHubContrCurrent); hub->limited_power = 1; - if (hdev->maxchild > 0) { - int remaining = hdev->bus_mA - - hub->descriptor->bHubContrCurrent; - if (remaining < hdev->maxchild * 100) - dev_warn(hub_dev, + if (remaining < hdev->maxchild * unit_load) + dev_warn(hub_dev, "insufficient power available " "to use all downstream ports\n"); - hub->mA_per_port = 100; /* 7.2.1.1 */ - } + hub->mA_per_port = unit_load; /* 7.2.1 */ + } else { /* Self-powered external hub */ /* FIXME: What about battery-powered external hubs that * provide less current per port? */ - hub->mA_per_port = 500; + hub->mA_per_port = full_load; } - if (hub->mA_per_port < 500) + if (hub->mA_per_port < full_load) dev_dbg(hub_dev, "%umA bus power budget for each child\n", hub->mA_per_port); /* Update the HCD's internal representation of this hub before khubd * starts getting port status changes for devices under the hub. */ - hcd = bus_to_hcd(hdev->bus); if (hcd->driver->update_hub_device) { ret = hcd->driver->update_hub_device(hcd, hdev, &hub->tt, GFP_KERNEL); @@ -2535,77 +2548,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, return ret; /* The port state is unknown until the reset completes. */ - if ((portstatus & USB_PORT_STAT_RESET)) - goto delay; - - /* - * Some buggy devices require a warm reset to be issued even - * when the port appears not to be connected. - */ - if (!warm) { - /* - * Some buggy devices can cause an NEC host controller - * to transition to the "Error" state after a hot port - * reset. This will show up as the port state in - * "Inactive", and the port may also report a - * disconnect. Forcing a warm port reset seems to make - * the device work. - * - * See https://bugzilla.kernel.org/show_bug.cgi?id=41752 - */ - if (hub_port_warm_reset_required(hub, portstatus)) { - int ret; - - if ((portchange & USB_PORT_STAT_C_CONNECTION)) - clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_C_CONNECTION); - if (portchange & USB_PORT_STAT_C_LINK_STATE) - clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_C_PORT_LINK_STATE); - if (portchange & USB_PORT_STAT_C_RESET) - clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_C_RESET); - dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n", - port1); - ret = hub_port_reset(hub, port1, - udev, HUB_BH_RESET_TIME, - true); - if ((portchange & USB_PORT_STAT_C_CONNECTION)) - clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_C_CONNECTION); - return ret; - } - /* Device went away? */ - if (!(portstatus & USB_PORT_STAT_CONNECTION)) - return -ENOTCONN; - - /* bomb out completely if the connection bounced */ - if ((portchange & USB_PORT_STAT_C_CONNECTION)) - return -ENOTCONN; - - if ((portstatus & USB_PORT_STAT_ENABLE)) { - if (hub_is_wusb(hub)) - udev->speed = USB_SPEED_WIRELESS; - else if (hub_is_superspeed(hub->hdev)) - udev->speed = USB_SPEED_SUPER; - else if (portstatus & USB_PORT_STAT_HIGH_SPEED) - udev->speed = USB_SPEED_HIGH; - else if (portstatus & USB_PORT_STAT_LOW_SPEED) - udev->speed = USB_SPEED_LOW; - else - udev->speed = USB_SPEED_FULL; - return 0; - } - } else { - if (!(portstatus & USB_PORT_STAT_CONNECTION) || - hub_port_warm_reset_required(hub, - portstatus)) - return -ENOTCONN; - - return 0; - } + if (!(portstatus & USB_PORT_STAT_RESET)) + break; -delay: /* switch to the long delay after two short delay failures */ if (delay_time >= 2 * HUB_SHORT_RESET_TIME) delay = HUB_LONG_RESET_TIME; @@ -2615,20 +2560,54 @@ delay: port1, warm ? "warm " : "", delay); } - return -EBUSY; + if ((portstatus & USB_PORT_STAT_RESET)) + return -EBUSY; + + if (hub_port_warm_reset_required(hub, portstatus)) + return -ENOTCONN; + + /* Device went away? */ + if (!(portstatus & USB_PORT_STAT_CONNECTION)) + return -ENOTCONN; + + /* bomb out completely if the connection bounced. A USB 3.0 + * connection may bounce if multiple warm resets were issued, + * but the device may have successfully re-connected. Ignore it. + */ + if (!hub_is_superspeed(hub->hdev) && + (portchange & USB_PORT_STAT_C_CONNECTION)) + return -ENOTCONN; + + if (!(portstatus & USB_PORT_STAT_ENABLE)) + return -EBUSY; + + if (!udev) + return 0; + + if (hub_is_wusb(hub)) + udev->speed = USB_SPEED_WIRELESS; + else if (hub_is_superspeed(hub->hdev)) + udev->speed = USB_SPEED_SUPER; + else if (portstatus & USB_PORT_STAT_HIGH_SPEED) + udev->speed = USB_SPEED_HIGH; + else if (portstatus & USB_PORT_STAT_LOW_SPEED) + udev->speed = USB_SPEED_LOW; + else + udev->speed = USB_SPEED_FULL; + return 0; } static void hub_port_finish_reset(struct usb_hub *hub, int port1, - struct usb_device *udev, int *status, bool warm) + struct usb_device *udev, int *status) { switch (*status) { case 0: - if (!warm) { - struct usb_hcd *hcd; - /* TRSTRCY = 10 ms; plus some extra */ - msleep(10 + 40); + /* TRSTRCY = 10 ms; plus some extra */ + msleep(10 + 40); + if (udev) { + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + update_devnum(udev, 0); - hcd = bus_to_hcd(udev->bus); /* The xHC may think the device is already reset, * so ignore the status. */ @@ -2640,14 +2619,15 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1, case -ENODEV: clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_RESET); - /* FIXME need disconnect() for NOTATTACHED device */ if (hub_is_superspeed(hub->hdev)) { clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_BH_PORT_RESET); clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_PORT_LINK_STATE); + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_CONNECTION); } - if (!warm) + if (udev) usb_set_device_state(udev, *status ? USB_STATE_NOTATTACHED : USB_STATE_DEFAULT); @@ -2660,18 +2640,30 @@ static int hub_port_reset(struct usb_hub *hub, int port1, struct usb_device *udev, unsigned int delay, bool warm) { int i, status; + u16 portchange, portstatus; - if (!warm) { - /* Block EHCI CF initialization during the port reset. - * Some companion controllers don't like it when they mix. - */ - down_read(&ehci_cf_port_reset_rwsem); - } else { - if (!hub_is_superspeed(hub->hdev)) { + if (!hub_is_superspeed(hub->hdev)) { + if (warm) { dev_err(hub->intfdev, "only USB3 hub support " "warm reset\n"); return -EINVAL; } + /* Block EHCI CF initialization during the port reset. + * Some companion controllers don't like it when they mix. + */ + down_read(&ehci_cf_port_reset_rwsem); + } else if (!warm) { + /* + * If the caller hasn't explicitly requested a warm reset, + * double check and see if one is needed. + */ + status = hub_port_status(hub, port1, + &portstatus, &portchange); + if (status < 0) + goto done; + + if (hub_port_warm_reset_required(hub, portstatus)) + warm = true; } /* Reset the port */ @@ -2692,10 +2684,33 @@ static int hub_port_reset(struct usb_hub *hub, int port1, status); } - /* return on disconnect or reset */ + /* Check for disconnect or reset */ if (status == 0 || status == -ENOTCONN || status == -ENODEV) { - hub_port_finish_reset(hub, port1, udev, &status, warm); - goto done; + hub_port_finish_reset(hub, port1, udev, &status); + + if (!hub_is_superspeed(hub->hdev)) + goto done; + + /* + * If a USB 3.0 device migrates from reset to an error + * state, re-issue the warm reset. + */ + if (hub_port_status(hub, port1, + &portstatus, &portchange) < 0) + goto done; + + if (!hub_port_warm_reset_required(hub, portstatus)) + goto done; + + /* + * If the port is in SS.Inactive or Compliance Mode, the + * hot or warm reset failed. Try another warm reset. + */ + if (!warm) { + dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n", + port1); + warm = true; + } } dev_dbg (hub->intfdev, @@ -2709,7 +2724,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1, port1); done: - if (!warm) + if (!hub_is_superspeed(hub->hdev)) up_read(&ehci_cf_port_reset_rwsem); return status; @@ -2945,9 +2960,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) /* see 7.1.7.6 */ if (hub_is_superspeed(hub->hdev)) - status = set_port_feature(hub->hdev, - port1 | (USB_SS_PORT_LS_U3 << 3), - USB_PORT_FEAT_LINK_STATE); + status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U3); else status = set_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND); @@ -3123,9 +3136,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) /* see 7.1.7.7; affects power usage, but not budgeting */ if (hub_is_superspeed(hub->hdev)) - status = set_port_feature(hub->hdev, - port1 | (USB_SS_PORT_LS_U0 << 3), - USB_PORT_FEAT_LINK_STATE); + status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0); else status = clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND); @@ -4212,16 +4223,23 @@ hub_power_remaining (struct usb_hub *hub) for (port1 = 1; port1 <= hdev->maxchild; ++port1) { struct usb_device *udev = hub->ports[port1 - 1]->child; int delta; + unsigned unit_load; if (!udev) continue; + if (hub_is_superspeed(udev)) + unit_load = 150; + else + unit_load = 100; - /* Unconfigured devices may not use more than 100mA, - * or 8mA for OTG ports */ + /* + * Unconfigured devices may not use more than one unit load, + * or 8mA for OTG ports + */ if (udev->actconfig) - delta = udev->actconfig->desc.bMaxPower * 2; + delta = usb_get_max_power(udev, udev->actconfig); else if (port1 != udev->bus->otg_port || hdev->parent) - delta = 100; + delta = unit_load; else delta = 8; if (delta > hub->mA_per_port) @@ -4256,6 +4274,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, le16_to_cpu(hub->descriptor->wHubCharacteristics); struct usb_device *udev; int status, i; + unsigned unit_load; dev_dbg (hub_dev, "port %d, status %04x, change %04x, %s\n", @@ -4345,6 +4364,10 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, goto done; return; } + if (hub_is_superspeed(hub->hdev)) + unit_load = 150; + else + unit_load = 100; for (i = 0; i < SET_CONFIG_TRIES; i++) { @@ -4392,7 +4415,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, * on the parent. */ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB - && udev->bus_mA <= 100) { + && udev->bus_mA <= unit_load) { u16 devstat; status = usb_get_status(udev, USB_RECIP_DEVICE, 0, @@ -4706,12 +4729,21 @@ static void hub_events(void) */ if (hub_port_warm_reset_required(hub, portstatus)) { int status; + struct usb_device *udev = + hub->ports[i - 1]->child; dev_dbg(hub_dev, "warm reset port %d\n", i); - status = hub_port_reset(hub, i, NULL, - HUB_BH_RESET_TIME, true); - if (status < 0) - hub_port_disable(hub, i, 1); + if (!udev) { + status = hub_port_reset(hub, i, + NULL, HUB_BH_RESET_TIME, + true); + if (status < 0) + hub_port_disable(hub, i, 1); + } else { + usb_lock_device(udev); + status = usb_reset_device(udev); + usb_unlock_device(udev); + } connect_change = 0; } diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 131f73649b6..444d30e3a78 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1751,7 +1751,7 @@ free_interfaces: } } - i = dev->bus_mA - cp->desc.bMaxPower * 2; + i = dev->bus_mA - usb_get_max_power(dev, cp); if (i < 0) dev_warn(&dev->dev, "new config #%d exceeds power " "limit by %dmA\n", diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 818e4a024d0..3f81a3dc686 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -17,7 +17,7 @@ #include "usb.h" /* Active configuration fields */ -#define usb_actconfig_show(field, multiplier, format_string) \ +#define usb_actconfig_show(field, format_string) \ static ssize_t show_##field(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ @@ -28,18 +28,31 @@ static ssize_t show_##field(struct device *dev, \ actconfig = udev->actconfig; \ if (actconfig) \ return sprintf(buf, format_string, \ - actconfig->desc.field * multiplier); \ + actconfig->desc.field); \ else \ return 0; \ } \ -#define usb_actconfig_attr(field, multiplier, format_string) \ -usb_actconfig_show(field, multiplier, format_string) \ -static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); +#define usb_actconfig_attr(field, format_string) \ + usb_actconfig_show(field, format_string) \ + static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); + +usb_actconfig_attr(bNumInterfaces, "%2d\n") +usb_actconfig_attr(bmAttributes, "%2x\n") -usb_actconfig_attr(bNumInterfaces, 1, "%2d\n") -usb_actconfig_attr(bmAttributes, 1, "%2x\n") -usb_actconfig_attr(bMaxPower, 2, "%3dmA\n") +static ssize_t show_bMaxPower(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + struct usb_host_config *actconfig; + + udev = to_usb_device(dev); + actconfig = udev->actconfig; + if (!actconfig) + return 0; + return sprintf(buf, "%dmA\n", usb_get_max_power(udev, actconfig)); +} +static DEVICE_ATTR(bMaxPower, S_IRUGO, show_bMaxPower, NULL); static ssize_t show_configuration_string(struct device *dev, struct device_attribute *attr, char *buf) @@ -56,7 +69,7 @@ static ssize_t show_configuration_string(struct device *dev, static DEVICE_ATTR(configuration, S_IRUGO, show_configuration_string, NULL); /* configuration value is always present, and r/w */ -usb_actconfig_show(bConfigurationValue, 1, "%u\n"); +usb_actconfig_show(bConfigurationValue, "%u\n"); static ssize_t set_bConfigurationValue(struct device *dev, struct device_attribute *attr, diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 1c528c1bf0b..fb7d8fcb455 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -38,6 +38,15 @@ extern char *usb_cache_string(struct usb_device *udev, int index); extern int usb_set_configuration(struct usb_device *dev, int configuration); extern int usb_choose_configuration(struct usb_device *udev); +static inline unsigned usb_get_max_power(struct usb_device *udev, + struct usb_host_config *c) +{ + /* SuperSpeed power is in 8 mA units; others are in 2 mA units */ + unsigned mul = (udev->speed == USB_SPEED_SUPER ? 8 : 2); + + return c->desc.bMaxPower * mul; +} + extern void usb_kick_khubd(struct usb_device *dev); extern int usb_match_one_id_intf(struct usb_device *dev, struct usb_host_interface *intf, |