On Thu, Mar 07, 2013 at 04:23:39PM -0800, Sarah Sharp wrote:
> [This is upstream commit 24a6078754f28528bc91e7e7b3e6ae86bd936d8.
I believe you meant commit 'a24a6078754f28528bc91e7e7b3e6ae86bd936d8'.
Cheers,
--
Luis
> It needs to be backported to kernels as old as 3.2, because it fixes
> the buggy commit 65bdac5effd15d6af619b3b7218627ef4d84ed6a "USB: Handle
> warm reset failure on empty port."]
>
> 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 <[email protected]>
> Acked-by: Alan Stern <[email protected]>
> Cc: [email protected]
> ---
> drivers/usb/core/hub.c | 150
> ++++++++++++++++++++++--------------------------
> 1 files changed, 68 insertions(+), 82 deletions(-)
>
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 3ecd663..b3161b9 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,
> 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 (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 (!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 (!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;
> + if (!hub_is_superspeed(hub->hdev) &&
> + (portchange & USB_PORT_STAT_C_CONNECTION))
> + return -ENOTCONN;
> +
> + if ((portstatus & USB_PORT_STAT_ENABLE)) {
> + if (!udev)
> return 0;
> - }
> - } else {
> - if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
> - hub_port_warm_reset_required(hub,
> - portstatus))
> - return -ENOTCONN;
>
> + 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;
> }
>
> @@ -2622,23 +2584,21 @@ delay:
> }
>
> 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);
> - if (udev) {
> - update_devnum(udev, 0);
> - hcd = bus_to_hcd(udev->bus);
> - /* The xHC may think the device is already
> - * reset, so ignore the status.
> - */
> - if (hcd->driver->reset_device)
> - hcd->driver->reset_device(hcd, udev);
> - }
> + /* TRSTRCY = 10 ms; plus some extra */
> + msleep(10 + 40);
> + if (udev) {
> + struct usb_hcd *hcd = bus_to_hcd(udev->bus);
> +
> + update_devnum(udev, 0);
> + /* The xHC may think the device is already reset,
> + * so ignore the status.
> + */
> + if (hcd->driver->reset_device)
> + hcd->driver->reset_device(hcd, udev);
> }
> /* FALL THROUGH */
> case -ENOTCONN:
> @@ -2651,8 +2611,10 @@ static void hub_port_finish_reset(struct usb_hub *hub,
> int port1,
> USB_PORT_FEAT_C_BH_PORT_RESET);
> clear_port_feature(hub->hdev, port1,
> USB_PORT_FEAT_C_PORT_LINK_STATE);
> + clear_port_feature(hub->hdev, port1,
> + USB_PORT_FEAT_C_CONNECTION);
> }
> - if (!warm && udev)
> + if (udev)
> usb_set_device_state(udev, *status
> ? USB_STATE_NOTATTACHED
> : USB_STATE_DEFAULT);
> @@ -2665,6 +2627,7 @@ 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 (!hub_is_superspeed(hub->hdev)) {
> if (warm) {
> @@ -2696,10 +2659,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,
> --
> 1.7.9
>
> --
> To unsubscribe from this list: send the line "unsubscribe stable" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html