diff options
Diffstat (limited to 'drivers/usb/core')
-rw-r--r-- | drivers/usb/core/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/core/devices.c | 13 | ||||
-rw-r--r-- | drivers/usb/core/devio.c | 3 | ||||
-rw-r--r-- | drivers/usb/core/generic.c | 2 | ||||
-rw-r--r-- | drivers/usb/core/hcd.c | 5 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 616 | ||||
-rw-r--r-- | drivers/usb/core/hub.h | 122 | ||||
-rw-r--r-- | drivers/usb/core/message.c | 2 | ||||
-rw-r--r-- | drivers/usb/core/port.c | 202 | ||||
-rw-r--r-- | drivers/usb/core/sysfs.c | 31 | ||||
-rw-r--r-- | drivers/usb/core/usb.h | 12 |
11 files changed, 721 insertions, 288 deletions
diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index 26059b93dbf..5e847ad2f58 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -7,6 +7,7 @@ ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG usbcore-y := usb.o hub.o hcd.o urb.o message.o driver.o usbcore-y += config.o file.o buffer.o sysfs.o endpoint.o usbcore-y += devio.o notify.o generic.o quirks.o devices.o +usbcore-y += port.o usbcore-$(CONFIG_PCI) += hcd-pci.o usbcore-$(CONFIG_ACPI) += usb-acpi.o 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/devio.c b/drivers/usb/core/devio.c index b78fbe222b7..4a863fdbdcc 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -40,6 +40,7 @@ #include <linux/signal.h> #include <linux/poll.h> #include <linux/module.h> +#include <linux/string.h> #include <linux/usb.h> #include <linux/usbdevice_fs.h> #include <linux/usb/hcd.h> /* for usbcore internals */ @@ -1077,7 +1078,7 @@ static int proc_getdriver(struct dev_state *ps, void __user *arg) if (!intf || !intf->dev.driver) ret = -ENODATA; else { - strncpy(gd.driver, intf->dev.driver->name, + strlcpy(gd.driver, intf->dev.driver->name, sizeof(gd.driver)); ret = (copy_to_user(arg, &gd, sizeof(gd)) ? -EFAULT : 0); } 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 8e64adf8e4d..99b34a30354 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -620,6 +620,10 @@ nongeneric: status = hcd->driver->hub_control (hcd, typeReq, wValue, wIndex, tbuf, wLength); + + if (typeReq == GetHubDescriptor) + usb_hub_adjust_deviceremovable(hcd->self.root_hub, + (struct usb_hub_descriptor *)tbuf); break; error: /* "protocol stall" on error */ @@ -2550,7 +2554,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 cbf7168e3ce..1775ad471ed 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -26,11 +26,12 @@ #include <linux/mutex.h> #include <linux/freezer.h> #include <linux/random.h> +#include <linux/pm_qos.h> #include <asm/uaccess.h> #include <asm/byteorder.h> -#include "usb.h" +#include "hub.h" /* if we are in debug mode, always announce new devices */ #ifdef DEBUG @@ -42,62 +43,6 @@ #define USB_VENDOR_GENESYS_LOGIC 0x05e3 #define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01 -struct usb_port { - struct usb_device *child; - struct device dev; - struct dev_state *port_owner; - enum usb_port_connect_type connect_type; -}; - -struct usb_hub { - struct device *intfdev; /* the "interface" device */ - struct usb_device *hdev; - struct kref kref; - struct urb *urb; /* for interrupt polling pipe */ - - /* buffer for urb ... with extra space in case of babble */ - char (*buffer)[8]; - union { - struct usb_hub_status hub; - struct usb_port_status port; - } *status; /* buffer for status reports */ - struct mutex status_mutex; /* for the status buffer */ - - int error; /* last reported error */ - int nerrors; /* track consecutive errors */ - - struct list_head event_list; /* hubs w/data or errs ready */ - unsigned long event_bits[1]; /* status change bitmask */ - unsigned long change_bits[1]; /* ports with logical connect - status change */ - unsigned long busy_bits[1]; /* ports being reset or - resumed */ - unsigned long removed_bits[1]; /* ports with a "removed" - device present */ - unsigned long wakeup_bits[1]; /* ports that have signaled - remote wakeup */ -#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ -#error event_bits[] is too short! -#endif - - struct usb_hub_descriptor *descriptor; /* class descriptor */ - struct usb_tt tt; /* Transaction Translator */ - - unsigned mA_per_port; /* current for each child */ - - unsigned limited_power:1; - unsigned quiescing:1; - unsigned disconnected:1; - - unsigned quirk_check_port_auto_suspend:1; - - unsigned has_indicators:1; - u8 indicator[USB_MAXCHILDREN]; - struct delayed_work leds; - struct delayed_work init_work; - struct usb_port **ports; -}; - static inline int hub_is_superspeed(struct usb_device *hdev) { return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS); @@ -164,13 +109,10 @@ MODULE_PARM_DESC(use_both_schemes, DECLARE_RWSEM(ehci_cf_port_reset_rwsem); EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem); -#define HUB_DEBOUNCE_TIMEOUT 1500 +#define HUB_DEBOUNCE_TIMEOUT 2000 #define HUB_DEBOUNCE_STEP 25 #define HUB_DEBOUNCE_STABLE 100 -#define to_usb_port(_dev) \ - container_of(_dev, struct usb_port, dev) - static int usb_reset_and_verify_device(struct usb_device *udev); static inline char *portspeed(struct usb_hub *hub, int portstatus) @@ -186,7 +128,7 @@ static inline char *portspeed(struct usb_hub *hub, int portstatus) } /* Note that hdev or one of its children must be locked! */ -static struct usb_hub *hdev_to_hub(struct usb_device *hdev) +struct usb_hub *usb_hub_to_struct_hub(struct usb_device *hdev) { if (!hdev || !hdev->actconfig || !hdev->maxchild) return NULL; @@ -360,7 +302,7 @@ static void usb_set_lpm_parameters(struct usb_device *udev) if (!udev->lpm_capable || udev->speed != USB_SPEED_SUPER) return; - hub = hdev_to_hub(udev->parent); + hub = usb_hub_to_struct_hub(udev->parent); /* It doesn't take time to transition the roothub into U0, since it * doesn't have an upstream link. */ @@ -452,7 +394,7 @@ static int clear_hub_feature(struct usb_device *hdev, int feature) /* * USB 2.0 spec Section 11.24.2.2 */ -static int clear_port_feature(struct usb_device *hdev, int port1, int feature) +int usb_clear_port_feature(struct usb_device *hdev, int port1, int feature) { return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1, @@ -645,7 +587,7 @@ static void kick_khubd(struct usb_hub *hub) void usb_kick_khubd(struct usb_device *hdev) { - struct usb_hub *hub = hdev_to_hub(hdev); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); if (hub) kick_khubd(hub); @@ -667,7 +609,7 @@ void usb_wakeup_notification(struct usb_device *hdev, if (!hdev) return; - hub = hdev_to_hub(hdev); + hub = usb_hub_to_struct_hub(hdev); if (hub) { set_bit(portnum, hub->wakeup_bits); kick_khubd(hub); @@ -774,6 +716,32 @@ static void hub_tt_work(struct work_struct *work) } /** + * usb_hub_set_port_power - control hub port's power state + * @hdev: target hub + * @port1: port index + * @set: expected status + * + * call this function to control port's power via setting or + * clearing the port's PORT_POWER feature. + */ +int usb_hub_set_port_power(struct usb_device *hdev, int port1, + bool set) +{ + int ret; + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_port *port_dev = hub->ports[port1 - 1]; + + if (set) + ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); + else + ret = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER); + + if (!ret) + port_dev->power_is_on = set; + return ret; +} + +/** * usb_hub_clear_tt_buffer - clear control/bulk TT state in high speed hub * @urb: an URB associated with the failed or incomplete split transaction * @@ -849,7 +817,11 @@ static unsigned hub_power_on(struct usb_hub *hub, bool do_delay) dev_dbg(hub->intfdev, "trying to enable port power on " "non-switchable hub\n"); for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++) - set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); + if (hub->ports[port1 - 1]->power_is_on) + set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); + else + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_POWER); /* Wait at least 100 msec for power to become stable */ delay = max(pgood_delay, (unsigned) 100); @@ -943,7 +915,7 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state) if (hub_is_superspeed(hub->hdev)) ret = hub_usb3_port_disable(hub, port1); else - ret = clear_port_feature(hdev, port1, + ret = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE); } if (ret) @@ -992,7 +964,7 @@ int usb_remove_device(struct usb_device *udev) if (!udev->parent) /* Can't remove a root hub */ return -EINVAL; - hub = hdev_to_hub(udev->parent); + hub = usb_hub_to_struct_hub(udev->parent); intf = to_usb_interface(hub->intfdev); usb_autopm_get_interface(intf); @@ -1124,7 +1096,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) * Do not disable USB3 protocol ports. */ if (!hub_is_superspeed(hdev)) { - clear_port_feature(hdev, port1, + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE); portstatus &= ~USB_PORT_STAT_ENABLE; } else { @@ -1136,18 +1108,18 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) /* Clear status-change flags; we'll debounce later */ if (portchange & USB_PORT_STAT_C_CONNECTION) { need_debounce_delay = true; - clear_port_feature(hub->hdev, port1, + usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_CONNECTION); } if (portchange & USB_PORT_STAT_C_ENABLE) { need_debounce_delay = true; - clear_port_feature(hub->hdev, port1, + usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_ENABLE); } if ((portchange & USB_PORT_STAT_C_BH_RESET) && hub_is_superspeed(hub->hdev)) { need_debounce_delay = true; - clear_port_feature(hub->hdev, port1, + usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_BH_PORT_RESET); } /* We can forget about a "removed" device when there's a @@ -1181,10 +1153,16 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) set_bit(port1, hub->change_bits); } else if (udev->persist_enabled) { + struct usb_port *port_dev = hub->ports[port1 - 1]; + #ifdef CONFIG_PM udev->reset_resume = 1; #endif - set_bit(port1, hub->change_bits); + /* Don't set the change_bits when the device + * was powered off. + */ + if (port_dev->power_is_on) + set_bit(port1, hub->change_bits); } else { /* The power session is gone; tell khubd */ @@ -1294,52 +1272,6 @@ static int hub_post_reset(struct usb_interface *intf) return 0; } -static void usb_port_device_release(struct device *dev) -{ - struct usb_port *port_dev = to_usb_port(dev); - - kfree(port_dev); -} - -static void usb_hub_remove_port_device(struct usb_hub *hub, - int port1) -{ - device_unregister(&hub->ports[port1 - 1]->dev); -} - -struct device_type usb_port_device_type = { - .name = "usb_port", - .release = usb_port_device_release, -}; - -static int usb_hub_create_port_device(struct usb_hub *hub, - int port1) -{ - struct usb_port *port_dev = NULL; - int retval; - - port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); - if (!port_dev) { - retval = -ENOMEM; - goto exit; - } - - hub->ports[port1 - 1] = port_dev; - port_dev->dev.parent = hub->intfdev; - port_dev->dev.type = &usb_port_device_type; - dev_set_name(&port_dev->dev, "port%d", port1); - - retval = device_register(&port_dev->dev); - if (retval) - goto error_register; - return 0; - -error_register: - put_device(&port_dev->dev); -exit: - return retval; -} - static int hub_configure(struct usb_hub *hub, struct usb_endpoint_descriptor *endpoint) { @@ -1351,6 +1283,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 +1331,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 +1457,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); @@ -1605,6 +1550,8 @@ static int hub_configure(struct usb_hub *hub, dev_err(hub->intfdev, "couldn't create port%d device.\n", i + 1); + usb_hub_adjust_deviceremovable(hdev, hub->descriptor); + hub_activate(hub, HUB_INIT); return 0; @@ -1659,6 +1606,7 @@ static void hub_disconnect(struct usb_interface *intf) kfree(hub->status); kfree(hub->buffer); + pm_suspend_ignore_children(&intf->dev, false); kref_put(&hub->kref, hub_release); } @@ -1761,6 +1709,7 @@ descriptor_error: usb_set_intfdata (intf, hub); intf->needs_remote_wakeup = 1; + pm_suspend_ignore_children(&intf->dev, true); if (hdev->speed == USB_SPEED_HIGH) highspeed_hubs++; @@ -1779,7 +1728,7 @@ static int hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data) { struct usb_device *hdev = interface_to_usbdev (intf); - struct usb_hub *hub = hdev_to_hub(hdev); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); /* assert ifno == 0 (part of hub spec) */ switch (code) { @@ -1825,7 +1774,7 @@ static int find_port_owner(struct usb_device *hdev, unsigned port1, /* This assumes that devices not managed by the hub driver * will always have maxchild equal to 0. */ - *ppowner = &(hdev_to_hub(hdev)->ports[port1 - 1]->port_owner); + *ppowner = &(usb_hub_to_struct_hub(hdev)->ports[port1 - 1]->port_owner); return 0; } @@ -1862,7 +1811,7 @@ int usb_hub_release_port(struct usb_device *hdev, unsigned port1, void usb_hub_release_all_ports(struct usb_device *hdev, struct dev_state *owner) { - struct usb_hub *hub = hdev_to_hub(hdev); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); int n; for (n = 0; n < hdev->maxchild; n++) { @@ -1879,13 +1828,13 @@ bool usb_device_is_owned(struct usb_device *udev) if (udev->state == USB_STATE_NOTATTACHED || !udev->parent) return false; - hub = hdev_to_hub(udev->parent); + hub = usb_hub_to_struct_hub(udev->parent); return !!hub->ports[udev->portnum - 1]->port_owner; } static void recursively_mark_NOTATTACHED(struct usb_device *udev) { - struct usb_hub *hub = hdev_to_hub(udev); + struct usb_hub *hub = usb_hub_to_struct_hub(udev); int i; for (i = 0; i < udev->maxchild; ++i) { @@ -2054,7 +2003,7 @@ static void hub_free_dev(struct usb_device *udev) void usb_disconnect(struct usb_device **pdev) { struct usb_device *udev = *pdev; - struct usb_hub *hub = hdev_to_hub(udev); + struct usb_hub *hub = usb_hub_to_struct_hub(udev); int i; /* mark the device as inactive, so any further urb submissions for @@ -2081,6 +2030,19 @@ void usb_disconnect(struct usb_device **pdev) usb_disable_device(udev, 0); usb_hcd_synchronize_unlinks(udev); + if (udev->parent) { + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + struct usb_port *port_dev = hub->ports[udev->portnum - 1]; + + sysfs_remove_link(&udev->dev.kobj, "port"); + sysfs_remove_link(&port_dev->dev.kobj, "device"); + + if (!port_dev->did_runtime_put) + pm_runtime_put(&port_dev->dev); + else + port_dev->did_runtime_put = false; + } + usb_remove_ep_devs(&udev->ep0); usb_unlock_device(udev); @@ -2267,7 +2229,7 @@ static void set_usb_port_removable(struct usb_device *udev) if (!hdev) return; - hub = hdev_to_hub(udev->parent); + hub = usb_hub_to_struct_hub(udev->parent); wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics); @@ -2373,6 +2335,26 @@ int usb_new_device(struct usb_device *udev) goto fail; } + /* Create link files between child device and usb port device. */ + if (udev->parent) { + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + struct usb_port *port_dev = hub->ports[udev->portnum - 1]; + + err = sysfs_create_link(&udev->dev.kobj, + &port_dev->dev.kobj, "port"); + if (err) + goto fail; + + err = sysfs_create_link(&port_dev->dev.kobj, + &udev->dev.kobj, "device"); + if (err) { + sysfs_remove_link(&udev->dev.kobj, "port"); + goto fail; + } + + pm_runtime_get_sync(&port_dev->dev); + } + (void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev); usb_mark_last_busy(udev); pm_runtime_put_sync_autosuspend(&udev->dev); @@ -2535,77 +2517,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 +2529,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. */ @@ -2638,16 +2586,17 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1, /* FALL THROUGH */ case -ENOTCONN: case -ENODEV: - clear_port_feature(hub->hdev, + usb_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_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_BH_PORT_RESET); - clear_port_feature(hub->hdev, port1, + usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_PORT_LINK_STATE); + usb_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 +2609,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 +2653,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 +2693,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; @@ -2783,10 +2767,10 @@ static int check_port_resume_type(struct usb_device *udev, /* Late port handoff can set status-change bits */ if (portchange & USB_PORT_STAT_C_CONNECTION) - clear_port_feature(hub->hdev, port1, + usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_CONNECTION); if (portchange & USB_PORT_STAT_C_ENABLE) - clear_port_feature(hub->hdev, port1, + usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_ENABLE); } @@ -2904,7 +2888,9 @@ static int usb_disable_function_remotewakeup(struct usb_device *udev) */ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) { - struct usb_hub *hub = hdev_to_hub(udev->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + struct usb_port *port_dev = hub->ports[udev->portnum - 1]; + enum pm_qos_flags_status pm_qos_stat; int port1 = udev->portnum; int status; @@ -2962,9 +2948,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); @@ -3006,6 +2990,21 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) udev->port_is_suspended = 1; msleep(10); } + + /* + * Check whether current status meets the requirement of + * usb port power off mechanism + */ + pm_qos_stat = dev_pm_qos_flags(&port_dev->dev, + PM_QOS_FLAG_NO_POWER_OFF); + if (!udev->do_remote_wakeup + && pm_qos_stat != PM_QOS_FLAGS_ALL + && udev->persist_enabled + && !status) { + pm_runtime_put_sync(&port_dev->dev); + port_dev->did_runtime_put = true; + } + usb_mark_last_busy(hub->hdev); return status; } @@ -3141,11 +3140,22 @@ static int finish_port_resume(struct usb_device *udev) */ int usb_port_resume(struct usb_device *udev, pm_message_t msg) { - struct usb_hub *hub = hdev_to_hub(udev->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + struct usb_port *port_dev = hub->ports[udev->portnum - 1]; int port1 = udev->portnum; int status; u16 portchange, portstatus; + if (port_dev->did_runtime_put) { + status = pm_runtime_get_sync(&port_dev->dev); + port_dev->did_runtime_put = false; + if (status < 0) { + dev_dbg(&udev->dev, "can't resume usb port, status %d\n", + status); + return status; + } + } + /* Skip the initial Clear-Suspend step for a remote wakeup */ status = hub_port_status(hub, port1, &portstatus, &portchange); if (status == 0 && !port_is_suspended(hub, portstatus)) @@ -3157,11 +3167,9 @@ 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, + status = usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND); if (status) { dev_dbg(hub->intfdev, "can't resume port %d, status %d\n", @@ -3187,11 +3195,11 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) udev->port_is_suspended = 0; if (hub_is_superspeed(hub->hdev)) { if (portchange & USB_PORT_STAT_C_LINK_STATE) - clear_port_feature(hub->hdev, port1, + usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_PORT_LINK_STATE); } else { if (portchange & USB_PORT_STAT_C_SUSPEND) - clear_port_feature(hub->hdev, port1, + usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_SUSPEND); } } @@ -3247,7 +3255,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) int usb_port_resume(struct usb_device *udev, pm_message_t msg) { - struct usb_hub *hub = hdev_to_hub(udev->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); int port1 = udev->portnum; int status; u16 portchange, portstatus; @@ -3826,7 +3834,7 @@ EXPORT_SYMBOL_GPL(usb_enable_ltm); * every 25ms for transient disconnects. When the port status has been * unchanged for 100ms it returns the port status. */ -static int hub_port_debounce(struct usb_hub *hub, int port1) +int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected) { int ret; int total_time, stable_time = 0; @@ -3840,7 +3848,9 @@ static int hub_port_debounce(struct usb_hub *hub, int |