|
@@ -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);
|
|
@@ -773,6 +715,32 @@ static void hub_tt_work(struct work_struct *work)
|
|
|
spin_unlock_irqrestore (&hub->tt.lock, flags);
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 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 port1)
|
|
|
|
|
|
if (!(portchange & USB_PORT_STAT_C_CONNECTION) &&
|
|
|
(portstatus & USB_PORT_STAT_CONNECTION) == connection) {
|
|
|
- stable_time += HUB_DEBOUNCE_STEP;
|
|
|
+ if (!must_be_connected ||
|
|
|
+ (connection == USB_PORT_STAT_CONNECTION))
|
|
|
+ stable_time += HUB_DEBOUNCE_STEP;
|
|
|
if (stable_time >= HUB_DEBOUNCE_STABLE)
|
|
|
break;
|
|
|
} else {
|
|
@@ -3849,7 +3859,7 @@ static int hub_port_debounce(struct usb_hub *hub, int port1)
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
@@ -4246,16 +4256,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)
|
|
@@ -4290,6 +4307,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",
|
|
@@ -4353,7 +4371,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
|
|
|
|
|
if (portchange & (USB_PORT_STAT_C_CONNECTION |
|
|
|
USB_PORT_STAT_C_ENABLE)) {
|
|
|
- status = hub_port_debounce(hub, port1);
|
|
|
+ status = hub_port_debounce_be_stable(hub, port1);
|
|
|
if (status < 0) {
|
|
|
if (printk_ratelimit())
|
|
|
dev_err(hub_dev, "connect-debounce failed, "
|
|
@@ -4379,6 +4397,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++) {
|
|
|
|
|
@@ -4426,7 +4448,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,
|
|
@@ -4528,7 +4550,7 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
|
|
|
if (!hub_is_superspeed(hdev)) {
|
|
|
if (!(portchange & USB_PORT_STAT_C_SUSPEND))
|
|
|
return 0;
|
|
|
- clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND);
|
|
|
+ usb_clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND);
|
|
|
} else {
|
|
|
if (!udev || udev->state != USB_STATE_SUSPENDED ||
|
|
|
(portstatus & USB_PORT_STAT_LINK_STATE) !=
|
|
@@ -4656,7 +4678,7 @@ static void hub_events(void)
|
|
|
continue;
|
|
|
|
|
|
if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
|
|
- clear_port_feature(hdev, i,
|
|
|
+ usb_clear_port_feature(hdev, i,
|
|
|
USB_PORT_FEAT_C_CONNECTION);
|
|
|
connect_change = 1;
|
|
|
}
|
|
@@ -4667,7 +4689,7 @@ static void hub_events(void)
|
|
|
"port %d enable change, "
|
|
|
"status %08x\n",
|
|
|
i, portstatus);
|
|
|
- clear_port_feature(hdev, i,
|
|
|
+ usb_clear_port_feature(hdev, i,
|
|
|
USB_PORT_FEAT_C_ENABLE);
|
|
|
|
|
|
/*
|
|
@@ -4698,7 +4720,7 @@ static void hub_events(void)
|
|
|
|
|
|
dev_dbg(hub_dev, "over-current change on port "
|
|
|
"%d\n", i);
|
|
|
- clear_port_feature(hdev, i,
|
|
|
+ usb_clear_port_feature(hdev, i,
|
|
|
USB_PORT_FEAT_C_OVER_CURRENT);
|
|
|
msleep(100); /* Cool down */
|
|
|
hub_power_on(hub, true);
|
|
@@ -4712,7 +4734,7 @@ static void hub_events(void)
|
|
|
dev_dbg (hub_dev,
|
|
|
"reset change on port %d\n",
|
|
|
i);
|
|
|
- clear_port_feature(hdev, i,
|
|
|
+ usb_clear_port_feature(hdev, i,
|
|
|
USB_PORT_FEAT_C_RESET);
|
|
|
}
|
|
|
if ((portchange & USB_PORT_STAT_C_BH_RESET) &&
|
|
@@ -4720,18 +4742,18 @@ static void hub_events(void)
|
|
|
dev_dbg(hub_dev,
|
|
|
"warm reset change on port %d\n",
|
|
|
i);
|
|
|
- clear_port_feature(hdev, i,
|
|
|
+ usb_clear_port_feature(hdev, i,
|
|
|
USB_PORT_FEAT_C_BH_PORT_RESET);
|
|
|
}
|
|
|
if (portchange & USB_PORT_STAT_C_LINK_STATE) {
|
|
|
- clear_port_feature(hub->hdev, i,
|
|
|
+ usb_clear_port_feature(hub->hdev, i,
|
|
|
USB_PORT_FEAT_C_PORT_LINK_STATE);
|
|
|
}
|
|
|
if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
|
|
|
dev_warn(hub_dev,
|
|
|
"config error on port %d\n",
|
|
|
i);
|
|
|
- clear_port_feature(hub->hdev, i,
|
|
|
+ usb_clear_port_feature(hub->hdev, i,
|
|
|
USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
|
|
|
}
|
|
|
|
|
@@ -4740,12 +4762,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;
|
|
|
}
|
|
|
|
|
@@ -5006,7 +5037,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
|
|
|
dev_dbg(&udev->dev, "%s for root hub!\n", __func__);
|
|
|
return -EISDIR;
|
|
|
}
|
|
|
- parent_hub = hdev_to_hub(parent_hdev);
|
|
|
+ parent_hub = usb_hub_to_struct_hub(parent_hdev);
|
|
|
|
|
|
/* Disable LPM and LTM while we reset the device and reinstall the alt
|
|
|
* settings. Device-initiated LPM settings, and system exit latency
|
|
@@ -5262,7 +5293,7 @@ EXPORT_SYMBOL_GPL(usb_queue_reset_device);
|
|
|
struct usb_device *usb_hub_find_child(struct usb_device *hdev,
|
|
|
int port1)
|
|
|
{
|
|
|
- struct usb_hub *hub = hdev_to_hub(hdev);
|
|
|
+ struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
|
|
|
|
|
if (port1 < 1 || port1 > hdev->maxchild)
|
|
|
return NULL;
|
|
@@ -5279,7 +5310,7 @@ EXPORT_SYMBOL_GPL(usb_hub_find_child);
|
|
|
void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1,
|
|
|
enum usb_port_connect_type type)
|
|
|
{
|
|
|
- struct usb_hub *hub = hdev_to_hub(hdev);
|
|
|
+ struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
|
|
|
|
|
hub->ports[port1 - 1]->connect_type = type;
|
|
|
}
|
|
@@ -5295,11 +5326,52 @@ void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1,
|
|
|
enum usb_port_connect_type
|
|
|
usb_get_hub_port_connect_type(struct usb_device *hdev, int port1)
|
|
|
{
|
|
|
- struct usb_hub *hub = hdev_to_hub(hdev);
|
|
|
+ struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
|
|
|
|
|
return hub->ports[port1 - 1]->connect_type;
|
|
|
}
|
|
|
|
|
|
+void usb_hub_adjust_deviceremovable(struct usb_device *hdev,
|
|
|
+ struct usb_hub_descriptor *desc)
|
|
|
+{
|
|
|
+ enum usb_port_connect_type connect_type;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (!hub_is_superspeed(hdev)) {
|
|
|
+ for (i = 1; i <= hdev->maxchild; i++) {
|
|
|
+ connect_type = usb_get_hub_port_connect_type(hdev, i);
|
|
|
+
|
|
|
+ if (connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) {
|
|
|
+ u8 mask = 1 << (i%8);
|
|
|
+
|
|
|
+ if (!(desc->u.hs.DeviceRemovable[i/8] & mask)) {
|
|
|
+ dev_dbg(&hdev->dev, "usb port%d's DeviceRemovable is changed to 1 according to platform information.\n",
|
|
|
+ i);
|
|
|
+ desc->u.hs.DeviceRemovable[i/8] |= mask;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ u16 port_removable = le16_to_cpu(desc->u.ss.DeviceRemovable);
|
|
|
+
|
|
|
+ for (i = 1; i <= hdev->maxchild; i++) {
|
|
|
+ connect_type = usb_get_hub_port_connect_type(hdev, i);
|
|
|
+
|
|
|
+ if (connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) {
|
|
|
+ u16 mask = 1 << i;
|
|
|
+
|
|
|
+ if (!(port_removable & mask)) {
|
|
|
+ dev_dbg(&hdev->dev, "usb port%d's DeviceRemovable is changed to 1 according to platform information.\n",
|
|
|
+ i);
|
|
|
+ port_removable |= mask;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ desc->u.ss.DeviceRemovable = cpu_to_le16(port_removable);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
#ifdef CONFIG_ACPI
|
|
|
/**
|
|
|
* usb_get_hub_port_acpi_handle - Get the usb port's acpi handle
|
|
@@ -5312,7 +5384,7 @@ usb_get_hub_port_connect_type(struct usb_device *hdev, int port1)
|
|
|
acpi_handle usb_get_hub_port_acpi_handle(struct usb_device *hdev,
|
|
|
int port1)
|
|
|
{
|
|
|
- struct usb_hub *hub = hdev_to_hub(hdev);
|
|
|
+ struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
|
|
|
|
|
return DEVICE_ACPI_HANDLE(&hub->ports[port1 - 1]->dev);
|
|
|
}
|