aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2012-11-01 14:20:44 -0400
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2013-01-03 17:10:33 -0500
commita24a6078754f28528bc91e7e7b3e6ae86bd936d8 (patch)
tree5bf00ba50ebfee6a299eaff3eee92bfe9c8519c1 /drivers/usb/core
parent2d4fa940f99663c82ba55b2244638833b388e4e2 (diff)
USB: Rip out recursive call on warm port reset.
When a hot reset fails on a USB 3.0 port, the current port reset code recursively calls hub_port_reset inside hub_port_wait_reset. This isn't ideal, since we should avoid recursive calls in the kernel, and it also doesn't allow us to issue multiple warm resets on reset failures. Rip out the recursive call. Instead, add code to hub_port_reset to issue a warm reset if the hot reset fails, and try multiple warm resets before giving up on the port. In hub_port_wait_reset, remove the recursive call and re-indent. The code is basically the same, except: 1. It bails out early if the port has transitioned to Inactive or Compliance Mode after the reset completed. 2. It doesn't consider a connect status change to be a failed reset. If multiple warm resets needed to be issued, the connect status may have changed, so we need to ignore that and look at the port link state instead. hub_port_reset will now do that. 3. It unconditionally sets udev->speed on all types of successful resets. The old recursive code would set the port speed when the second hub_port_reset returned. The old code did not handle connected devices needing a warm reset well. There were only two situations that the old code handled correctly: an empty port needing a warm reset, and a hot reset that migrated to a warm reset. When an empty port needed a warm reset, hub_port_reset was called with the warm variable set. The code in hub_port_finish_reset would skip telling the USB core and the xHC host that the device was reset, because otherwise that would result in a NULL pointer dereference. When a USB 3.0 device reset migrated to a warm reset, the recursive call made the call stack look like this: hub_port_reset(warm = false) hub_wait_port_reset(warm = false) hub_port_reset(warm = true) hub_wait_port_reset(warm = true) hub_port_finish_reset(warm = true) (return up the call stack to the first wait) hub_port_finish_reset(warm = false) The old code didn't want to notify the USB core or the xHC host of device reset twice, so it only did it in the second call to hub_port_finish_reset, when warm was set to false. This was necessary because before patch two ("USB: Ignore xHCI Reset Device status."), the USB core would pay attention to the xHC Reset Device command error status, and the second call would always fail. Now that we no longer have the recursive call, and warm can change from false to true in hub_port_reset, we need to have hub_port_finish_reset unconditionally notify the USB core and the xHC of the device reset. In hub_port_finish_reset, unconditionally clear the connect status change (CSC) bit for USB 3.0 hubs when the port reset is done. If we had to issue multiple warm resets for a device, that bit may have been set if the device went into SS.Inactive and then was successfully warm reset. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Acked-by: Alan Stern <stern@rowland.harvard.edu>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/hub.c150
1 files changed, 68 insertions, 82 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index c3368f978c44..d1fceb219d97 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2538,73 +2538,35 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
2538 if ((portstatus & USB_PORT_STAT_RESET)) 2538 if ((portstatus & USB_PORT_STAT_RESET))
2539 goto delay; 2539 goto delay;
2540 2540
2541 /* 2541 if (hub_port_warm_reset_required(hub, portstatus))
2542 * Some buggy devices require a warm reset to be issued even 2542 return -ENOTCONN;
2543 * when the port appears not to be connected. 2543
2544 /* Device went away? */
2545 if (!(portstatus & USB_PORT_STAT_CONNECTION))
2546 return -ENOTCONN;
2547
2548 /* bomb out completely if the connection bounced. A USB 3.0
2549 * connection may bounce if multiple warm resets were issued,
2550 * but the device may have successfully re-connected. Ignore it.
2544 */ 2551 */
2545 if (!warm) { 2552 if (!hub_is_superspeed(hub->hdev) &&
2546 /* 2553 (portchange & USB_PORT_STAT_C_CONNECTION))
2547 * Some buggy devices can cause an NEC host controller 2554 return -ENOTCONN;
2548 * to transition to the "Error" state after a hot port 2555
2549 * reset. This will show up as the port state in 2556 if ((portstatus & USB_PORT_STAT_ENABLE)) {
2550 * "Inactive", and the port may also report a 2557 if (!udev)
2551 * disconnect. Forcing a warm port reset seems to make
2552 * the device work.
2553 *
2554 * See https://bugzilla.kernel.org/show_bug.cgi?id=41752
2555 */
2556 if (hub_port_warm_reset_required(hub, portstatus)) {
2557 int ret;
2558
2559 if ((portchange & USB_PORT_STAT_C_CONNECTION))
2560 clear_port_feature(hub->hdev, port1,
2561 USB_PORT_FEAT_C_CONNECTION);
2562 if (portchange & USB_PORT_STAT_C_LINK_STATE)
2563 clear_port_feature(hub->hdev, port1,
2564 USB_PORT_FEAT_C_PORT_LINK_STATE);
2565 if (portchange & USB_PORT_STAT_C_RESET)
2566 clear_port_feature(hub->hdev, port1,
2567 USB_PORT_FEAT_C_RESET);
2568 dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
2569 port1);
2570 ret = hub_port_reset(hub, port1,
2571 udev, HUB_BH_RESET_TIME,
2572 true);
2573 if ((portchange & USB_PORT_STAT_C_CONNECTION))
2574 clear_port_feature(hub->hdev, port1,
2575 USB_PORT_FEAT_C_CONNECTION);
2576 return ret;
2577 }
2578 /* Device went away? */
2579 if (!(portstatus & USB_PORT_STAT_CONNECTION))
2580 return -ENOTCONN;
2581
2582 /* bomb out completely if the connection bounced */
2583 if ((portchange & USB_PORT_STAT_C_CONNECTION))
2584 return -ENOTCONN;
2585
2586 if ((portstatus & USB_PORT_STAT_ENABLE)) {
2587 if (!udev)
2588 return 0;
2589
2590 if (hub_is_wusb(hub))
2591 udev->speed = USB_SPEED_WIRELESS;
2592 else if (hub_is_superspeed(hub->hdev))
2593 udev->speed = USB_SPEED_SUPER;
2594 else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
2595 udev->speed = USB_SPEED_HIGH;
2596 else if (portstatus & USB_PORT_STAT_LOW_SPEED)
2597 udev->speed = USB_SPEED_LOW;
2598 else
2599 udev->speed = USB_SPEED_FULL;
2600 return 0; 2558 return 0;
2601 }
2602 } else {
2603 if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
2604 hub_port_warm_reset_required(hub,
2605 portstatus))
2606 return -ENOTCONN;
2607 2559
2560 if (hub_is_wusb(hub))
2561 udev->speed = USB_SPEED_WIRELESS;
2562 else if (hub_is_superspeed(hub->hdev))
2563 udev->speed = USB_SPEED_SUPER;
2564 else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
2565 udev->speed = USB_SPEED_HIGH;
2566 else if (portstatus & USB_PORT_STAT_LOW_SPEED)
2567 udev->speed = USB_SPEED_LOW;
2568 else
2569 udev->speed = USB_SPEED_FULL;
2608 return 0; 2570 return 0;
2609 } 2571 }
2610 2572
@@ -2622,23 +2584,21 @@ delay:
2622} 2584}
2623 2585
2624static void hub_port_finish_reset(struct usb_hub *hub, int port1, 2586static void hub_port_finish_reset(struct usb_hub *hub, int port1,
2625 struct usb_device *udev, int *status, bool warm) 2587 struct usb_device *udev, int *status)
2626{ 2588{
2627 switch (*status) { 2589 switch (*status) {
2628 case 0: 2590 case 0:
2629 if (!warm) { 2591 /* TRSTRCY = 10 ms; plus some extra */
2630 struct usb_hcd *hcd; 2592 msleep(10 + 40);
2631 /* TRSTRCY = 10 ms; plus some extra */ 2593 if (udev) {
2632 msleep(10 + 40); 2594 struct usb_hcd *hcd = bus_to_hcd(udev->bus);
2633 if (udev) { 2595
2634 update_devnum(udev, 0); 2596 update_devnum(udev, 0);
2635 hcd = bus_to_hcd(udev->bus); 2597 /* The xHC may think the device is already reset,
2636 /* The xHC may think the device is already 2598 * so ignore the status.
2637 * reset, so ignore the status. 2599 */
2638 */ 2600 if (hcd->driver->reset_device)
2639 if (hcd->driver->reset_device) 2601 hcd->driver->reset_device(hcd, udev);
2640 hcd->driver->reset_device(hcd, udev);
2641 }
2642 } 2602 }
2643 /* FALL THROUGH */ 2603 /* FALL THROUGH */
2644 case -ENOTCONN: 2604 case -ENOTCONN:
@@ -2651,8 +2611,10 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
2651 USB_PORT_FEAT_C_BH_PORT_RESET); 2611 USB_PORT_FEAT_C_BH_PORT_RESET);
2652 clear_port_feature(hub->hdev, port1, 2612 clear_port_feature(hub->hdev, port1,
2653 USB_PORT_FEAT_C_PORT_LINK_STATE); 2613 USB_PORT_FEAT_C_PORT_LINK_STATE);
2614 clear_port_feature(hub->hdev, port1,
2615 USB_PORT_FEAT_C_CONNECTION);
2654 } 2616 }
2655 if (!warm && udev) 2617 if (udev)
2656 usb_set_device_state(udev, *status 2618 usb_set_device_state(udev, *status
2657 ? USB_STATE_NOTATTACHED 2619 ? USB_STATE_NOTATTACHED
2658 : USB_STATE_DEFAULT); 2620 : USB_STATE_DEFAULT);
@@ -2665,6 +2627,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
2665 struct usb_device *udev, unsigned int delay, bool warm) 2627 struct usb_device *udev, unsigned int delay, bool warm)
2666{ 2628{
2667 int i, status; 2629 int i, status;
2630 u16 portchange, portstatus;
2668 2631
2669 if (!hub_is_superspeed(hub->hdev)) { 2632 if (!hub_is_superspeed(hub->hdev)) {
2670 if (warm) { 2633 if (warm) {
@@ -2696,10 +2659,33 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
2696 status); 2659 status);
2697 } 2660 }
2698 2661
2699 /* return on disconnect or reset */ 2662 /* Check for disconnect or reset */
2700 if (status == 0 || status == -ENOTCONN || status == -ENODEV) { 2663 if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
2701 hub_port_finish_reset(hub, port1, udev, &status, warm); 2664 hub_port_finish_reset(hub, port1, udev, &status);
2702 goto done; 2665
2666 if (!hub_is_superspeed(hub->hdev))
2667 goto done;
2668
2669 /*
2670 * If a USB 3.0 device migrates from reset to an error
2671 * state, re-issue the warm reset.
2672 */
2673 if (hub_port_status(hub, port1,
2674 &portstatus, &portchange) < 0)
2675 goto done;
2676
2677 if (!hub_port_warm_reset_required(hub, portstatus))
2678 goto done;
2679
2680 /*
2681 * If the port is in SS.Inactive or Compliance Mode, the
2682 * hot or warm reset failed. Try another warm reset.
2683 */
2684 if (!warm) {
2685 dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
2686 port1);
2687 warm = true;
2688 }
2703 } 2689 }
2704 2690
2705 dev_dbg (hub->intfdev, 2691 dev_dbg (hub->intfdev,