diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r-- | drivers/usb/core/hub.c | 82 |
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); |