summaryrefslogtreecommitdiffstats
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.c82
1 files changed, 36 insertions, 46 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index eaffb0248de1..d7c3d5a35946 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2896,10 +2896,12 @@ static int port_is_suspended(struct usb_hub *hub, unsigned portstatus)
*/
static int check_port_resume_type(struct usb_device *udev,
struct usb_hub *hub, int port1,
- int status, unsigned portchange, unsigned portstatus)
+ int status, u16 portchange, u16 portstatus)
{
struct usb_port *port_dev = hub->ports[port1 - 1];
+ int retries = 3;
+ retry:
/* Is a warm reset needed to recover the connection? */
if (status == 0 && udev->reset_resume
&& hub_port_warm_reset_required(hub, port1, portstatus)) {
@@ -2907,10 +2909,17 @@ static int check_port_resume_type(struct usb_device *udev,
}
/* Is the device still present? */
else if (status || port_is_suspended(hub, portstatus) ||
- !port_is_power_on(hub, portstatus) ||
- !(portstatus & USB_PORT_STAT_CONNECTION)) {
+ !port_is_power_on(hub, portstatus)) {
if (status >= 0)
status = -ENODEV;
+ } else if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
+ if (retries--) {
+ usleep_range(200, 300);
+ status = hub_port_status(hub, port1, &portstatus,
+ &portchange);
+ goto retry;
+ }
+ status = -ENODEV;
}
/* Can't do a normal resume if the port isn't enabled,
@@ -4643,9 +4652,13 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
test_bit(port1, hub->removed_bits)) {
- /* maybe switch power back on (e.g. root hub was reset) */
+ /*
+ * maybe switch power back on (e.g. root hub was reset)
+ * but only if the port isn't owned by someone else.
+ */
if (hub_is_port_power_switchable(hub)
- && !port_is_power_on(hub, portstatus))
+ && !port_is_power_on(hub, portstatus)
+ && !port_dev->port_owner)
set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
if (portstatus & USB_PORT_STAT_ENABLE)
@@ -4870,7 +4883,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
static void port_event(struct usb_hub *hub, int port1)
__must_hold(&port_dev->status_lock)
{
- int connect_change, reset_device = 0;
+ int connect_change;
struct usb_port *port_dev = hub->ports[port1 - 1];
struct usb_device *udev = port_dev->child;
struct usb_device *hdev = hub->hdev;
@@ -4958,30 +4971,14 @@ static void port_event(struct usb_hub *hub, int port1)
if (hub_port_reset(hub, port1, NULL,
HUB_BH_RESET_TIME, true) < 0)
hub_port_disable(hub, port1, 1);
- } else
- reset_device = 1;
- }
-
- /*
- * On disconnect USB3 protocol ports transit from U0 to
- * SS.Inactive to Rx.Detect. If this happens a warm-
- * reset is not needed, but a (re)connect may happen
- * before hub_wq runs and sees the disconnect, and the
- * device may be an unknown state.
- *
- * If the port went through SS.Inactive without hub_wq
- * seeing it the C_LINK_STATE change flag will be set,
- * and we reset the dev to put it in a known state.
- */
- if (reset_device || (udev && hub_is_superspeed(hub->hdev)
- && (portchange & USB_PORT_STAT_C_LINK_STATE)
- && (portstatus & USB_PORT_STAT_CONNECTION))) {
- usb_unlock_port(port_dev);
- usb_lock_device(udev);
- usb_reset_device(udev);
- usb_unlock_device(udev);
- usb_lock_port(port_dev);
- connect_change = 0;
+ } else {
+ usb_unlock_port(port_dev);
+ usb_lock_device(udev);
+ usb_reset_device(udev);
+ usb_unlock_device(udev);
+ usb_lock_port(port_dev);
+ connect_change = 0;
+ }
}
if (connect_change)
@@ -5577,26 +5574,19 @@ EXPORT_SYMBOL_GPL(usb_reset_device);
* possible; depending on how the driver attached to each interface
* handles ->pre_reset(), the second reset might happen or not.
*
- * - If a driver is unbound and it had a pending reset, the reset will
- * be cancelled.
- *
- * - This function can be called during .probe() or .disconnect()
- * times. On return from .disconnect(), any pending resets will be
- * cancelled.
- *
- * There is no no need to lock/unlock the @reset_ws as schedule_work()
- * does its own.
+ * - If the reset is delayed so long that the interface is unbound from
+ * its driver, the reset will be skipped.
*
- * NOTE: We don't do any reference count tracking because it is not
- * needed. The lifecycle of the work_struct is tied to the
- * usb_interface. Before destroying the interface we cancel the
- * work_struct, so the fact that work_struct is queued and or
- * running means the interface (and thus, the device) exist and
- * are referenced.
+ * - This function can be called during .probe(). It can also be called
+ * during .disconnect(), but doing so is pointless because the reset
+ * will not occur. If you really want to reset the device during
+ * .disconnect(), call usb_reset_device() directly -- but watch out
+ * for nested unbinding issues!
*/
void usb_queue_reset_device(struct usb_interface *iface)
{
- schedule_work(&iface->reset_ws);
+ if (schedule_work(&iface->reset_ws))
+ usb_get_intf(iface);
}
EXPORT_SYMBOL_GPL(usb_queue_reset_device);