From bb1936429202df59e2adc86fe09dfa3e8e0f9240 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 12 Mar 2013 15:06:55 -0700 Subject: [PATCH] 3.4-stable patches added patches: usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch usb-rip-out-recursive-call-on-warm-port-reset.patch --- queue-3.4/series | 3 + ...ehci-port-sempahore-for-usb-3.0-hubs.patch | 66 +++++ ...actoring-by-adding-extra-udev-checks.patch | 75 +++++ ...ut-recursive-call-on-warm-port-reset.patch | 280 ++++++++++++++++++ 4 files changed, 424 insertions(+) create mode 100644 queue-3.4/usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch create mode 100644 queue-3.4/usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch create mode 100644 queue-3.4/usb-rip-out-recursive-call-on-warm-port-reset.patch diff --git a/queue-3.4/series b/queue-3.4/series index a564edc944c..9743dc8666b 100644 --- a/queue-3.4/series +++ b/queue-3.4/series @@ -35,3 +35,6 @@ fix-memory-leak-in-cpufreq-stats.patch ftrace-update-the-kconfig-for-dynamic_ftrace.patch decnet-fix-disappearing-sysctl-entries.patch dmi_scan-fix-missing-check-for-_dmi_-signature-in-smbios_present.patch +usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch +usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch +usb-rip-out-recursive-call-on-warm-port-reset.patch diff --git a/queue-3.4/usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch b/queue-3.4/usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch new file mode 100644 index 00000000000..42b931a591b --- /dev/null +++ b/queue-3.4/usb-don-t-use-ehci-port-sempahore-for-usb-3.0-hubs.patch @@ -0,0 +1,66 @@ +From sarah.a.sharp@linux.intel.com Tue Mar 12 15:00:01 2013 +From: Sarah Sharp +Date: Thu, 7 Mar 2013 16:24:19 -0800 +Subject: USB: Don't use EHCI port sempahore for USB 3.0 hubs. +To: Greg KH +Cc: stable@vger.kernel.org +Message-ID: <20130308002419.GA13701@xanatos> + +From: Sarah Sharp + +[This is upstream commit 0fe51aa5eee51db7c7ecd201d42a977ad79c58b6. +It needs to be backported to kernels as old as 3.2, because it fixes the +buggy commit 9dbcaec830cd97f44a0b91b315844e0d7144746b "USB: Handle warm +reset failure on empty port."] + +The EHCI host controller needs to prevent EHCI initialization when the +UHCI or OHCI companion controller is in the middle of a port reset. It +uses ehci_cf_port_reset_rwsem to do this. USB 3.0 hubs can't be under +an EHCI host controller, so it makes no sense to down the semaphore for +USB 3.0 hubs. It also makes the warm port reset code more complex. + +Don't down ehci_cf_port_reset_rwsem for USB 3.0 hubs. + +Signed-off-by: Sarah Sharp +Acked-by: Alan Stern +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/usb/core/hub.c | 15 +++++++-------- + 1 file changed, 7 insertions(+), 8 deletions(-) + +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -2330,17 +2330,16 @@ static int hub_port_reset(struct usb_hub + { + int i, status; + +- 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); + } + + /* Reset the port */ +@@ -2378,7 +2377,7 @@ static int hub_port_reset(struct usb_hub + port1); + + done: +- if (!warm) ++ if (!hub_is_superspeed(hub->hdev)) + up_read(&ehci_cf_port_reset_rwsem); + + return status; diff --git a/queue-3.4/usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch b/queue-3.4/usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch new file mode 100644 index 00000000000..2ee836b6625 --- /dev/null +++ b/queue-3.4/usb-prepare-for-refactoring-by-adding-extra-udev-checks.patch @@ -0,0 +1,75 @@ +From sarah.a.sharp@linux.intel.com Tue Mar 12 15:00:24 2013 +From: Sarah Sharp +Date: Thu, 7 Mar 2013 16:24:22 -0800 +Subject: USB: Prepare for refactoring by adding extra udev checks. +To: Greg KH +Cc: stable@vger.kernel.org +Message-ID: <20130308002422.GA13722@xanatos> + +From: Sarah Sharp + +[This is upstream commit 2d4fa940f99663c82ba55b2244638833b388e4e2. +It needs to be backported to kernels as old as 3.2, because it fixes the +buggy commit 9dbcaec830cd97f44a0b91b315844e0d7144746b "USB: Handle warm +reset failure on empty port."] + +The next patch will refactor the hub port code to rip out the recursive +call to hub_port_reset on a failed hot reset. In preparation for that, +make sure all code paths can deal with being called with a NULL udev. +The usb_device will not be valid if warm reset was issued because a port +transitioned to the Inactive or Compliance Mode on a device connect. + +This patch should have no effect on current behavior. + +Signed-off-by: Sarah Sharp +Acked-by: Alan Stern +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/usb/core/hub.c | 21 +++++++++++++-------- + 1 file changed, 13 insertions(+), 8 deletions(-) + +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -2253,6 +2253,9 @@ static int hub_port_wait_reset(struct us + 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)) +@@ -2296,13 +2299,15 @@ static void hub_port_finish_reset(struct + struct usb_hcd *hcd; + /* TRSTRCY = 10 ms; plus some extra */ + msleep(10 + 40); +- 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); ++ 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); ++ } + } + /* FALL THROUGH */ + case -ENOTCONN: +@@ -2316,7 +2321,7 @@ static void hub_port_finish_reset(struct + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_PORT_LINK_STATE); + } +- if (!warm) ++ if (!warm && udev) + usb_set_device_state(udev, *status + ? USB_STATE_NOTATTACHED + : USB_STATE_DEFAULT); diff --git a/queue-3.4/usb-rip-out-recursive-call-on-warm-port-reset.patch b/queue-3.4/usb-rip-out-recursive-call-on-warm-port-reset.patch new file mode 100644 index 00000000000..06bee7bc46f --- /dev/null +++ b/queue-3.4/usb-rip-out-recursive-call-on-warm-port-reset.patch @@ -0,0 +1,280 @@ +From sarah.a.sharp@linux.intel.com Tue Mar 12 15:02:01 2013 +From: Sarah Sharp +Date: Thu, 7 Mar 2013 16:24:24 -0800 +Subject: USB: Rip out recursive call on warm port reset. +To: Greg KH +Cc: stable@vger.kernel.org +Message-ID: <20130308002424.GA13738@xanatos> + +From: Sarah Sharp + +[This is upstream commit 24a6078754f28528bc91e7e7b3e6ae86bd936d8. +It needs to be backported to kernels as old as 3.2, because it fixes the +buggy commit 9dbcaec830cd97f44a0b91b315844e0d7144746b "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 +Acked-by: Alan Stern +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/usb/core/hub.c | 148 ++++++++++++++++++++++--------------------------- + 1 file changed, 67 insertions(+), 81 deletions(-) + +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -2207,73 +2207,35 @@ static int hub_port_wait_reset(struct us + 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 (!hub_is_superspeed(hub->hdev) && ++ (portchange & USB_PORT_STAT_C_CONNECTION)) ++ return -ENOTCONN; + +- 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 ((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; + } + +@@ -2291,23 +2253,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: +@@ -2320,8 +2280,10 @@ static void hub_port_finish_reset(struct + 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); +@@ -2334,6 +2296,7 @@ static int hub_port_reset(struct usb_hub + struct usb_device *udev, unsigned int delay, bool warm) + { + int i, status; ++ u16 portchange, portstatus; + + if (!hub_is_superspeed(hub->hdev)) { + if (warm) { +@@ -2365,10 +2328,33 @@ static int hub_port_reset(struct usb_hub + 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, -- 2.47.3