summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-01-07 10:14:23 -0800
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-01-07 10:14:23 -0800
commit102ee001912f67a7701f26a56ef2bcf84fc78028 (patch)
treeef1f70c744a734a745c4c8fbd67294bdbbc7df9a
parent962426e0e2312c5134db13f39d4389a2fa6a13d0 (diff)
parent026630d09ba7b6cd44a43b92ad256c28a9e3ddc2 (diff)
Merge tag 'for-usb-next-2013-01-03' of git://git.kernel.org/pub/scm/linux/kernel/git/sarah/xhci into usb-next
Sarah writes: usb-next: Further warm reset improvements Hi Greg, Here's some patches for 3.9. They further improve the warm reset error handling, but they're too big to go into stable. There's also a patch to remove an unused variable in the xHCI driver. As I mentioned, you'll need to merge usb-linus into usb-next before applying these patches. Sarah Sharp
-rw-r--r--drivers/usb/core/hub.c207
-rw-r--r--drivers/usb/host/xhci-ring.c2
2 files changed, 107 insertions, 102 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 9641e9c1dec..ae10862fb04 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2535,77 +2535,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
return ret;
/* The port state is unknown until the reset completes. */
- 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 (!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 (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;
- }
- } else {
- if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
- hub_port_warm_reset_required(hub,
- portstatus))
- return -ENOTCONN;
-
- return 0;
- }
+ if (!(portstatus & USB_PORT_STAT_RESET))
+ break;
-delay:
/* switch to the long delay after two short delay failures */
if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
delay = HUB_LONG_RESET_TIME;
@@ -2615,20 +2547,54 @@ delay:
port1, warm ? "warm " : "", delay);
}
- return -EBUSY;
+ if ((portstatus & USB_PORT_STAT_RESET))
+ return -EBUSY;
+
+ 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 (!hub_is_superspeed(hub->hdev) &&
+ (portchange & USB_PORT_STAT_C_CONNECTION))
+ return -ENOTCONN;
+
+ if (!(portstatus & USB_PORT_STAT_ENABLE))
+ return -EBUSY;
+
+ 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;
+ return 0;
}
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);
+ /* TRSTRCY = 10 ms; plus some extra */
+ msleep(10 + 40);
+ if (udev) {
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+
update_devnum(udev, 0);
- hcd = bus_to_hcd(udev->bus);
/* The xHC may think the device is already reset,
* so ignore the status.
*/
@@ -2640,14 +2606,15 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
case -ENODEV:
clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_C_RESET);
- /* FIXME need disconnect() for NOTATTACHED device */
if (hub_is_superspeed(hub->hdev)) {
clear_port_feature(hub->hdev, 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)
+ if (udev)
usb_set_device_state(udev, *status
? USB_STATE_NOTATTACHED
: USB_STATE_DEFAULT);
@@ -2660,18 +2627,30 @@ 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 (!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);
+ } else if (!warm) {
+ /*
+ * If the caller hasn't explicitly requested a warm reset,
+ * double check and see if one is needed.
+ */
+ status = hub_port_status(hub, port1,
+ &portstatus, &portchange);
+ if (status < 0)
+ goto done;
+
+ if (hub_port_warm_reset_required(hub, portstatus))
+ warm = true;
}
/* Reset the port */
@@ -2692,10 +2671,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,
@@ -2709,7 +2711,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
port1);
done:
- if (!warm)
+ if (!hub_is_superspeed(hub->hdev))
up_read(&ehci_cf_port_reset_rwsem);
return status;
@@ -2945,9 +2947,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
/* see 7.1.7.6 */
if (hub_is_superspeed(hub->hdev))
- status = set_port_feature(hub->hdev,
- port1 | (USB_SS_PORT_LS_U3 << 3),
- USB_PORT_FEAT_LINK_STATE);
+ status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U3);
else
status = set_port_feature(hub->hdev, port1,
USB_PORT_FEAT_SUSPEND);
@@ -3117,9 +3117,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
/* see 7.1.7.7; affects power usage, but not budgeting */
if (hub_is_superspeed(hub->hdev))
- status = set_port_feature(hub->hdev,
- port1 | (USB_SS_PORT_LS_U0 << 3),
- USB_PORT_FEAT_LINK_STATE);
+ status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0);
else
status = clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_SUSPEND);
@@ -4700,12 +4698,21 @@ static void hub_events(void)
*/
if (hub_port_warm_reset_required(hub, portstatus)) {
int status;
+ struct usb_device *udev =
+ hub->ports[i - 1]->child;
dev_dbg(hub_dev, "warm reset port %d\n", i);
- status = hub_port_reset(hub, i, NULL,
- HUB_BH_RESET_TIME, true);
- if (status < 0)
- hub_port_disable(hub, i, 1);
+ if (!udev) {
+ status = hub_port_reset(hub, i,
+ NULL, HUB_BH_RESET_TIME,
+ true);
+ if (status < 0)
+ hub_port_disable(hub, i, 1);
+ } else {
+ usb_lock_device(udev);
+ status = usb_reset_device(udev);
+ usb_unlock_device(udev);
+ }
connect_change = 0;
}
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 59fb5c677db..f69720983fc 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -2706,13 +2706,11 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
u32 status;
- union xhci_trb *trb;
u64 temp_64;
union xhci_trb *event_ring_deq;
dma_addr_t deq;
spin_lock(&xhci->lock);
- trb = xhci->event_ring->dequeue;
/* Check if the xHC generated the interrupt, or the irq is shared */
status = xhci_readl(xhci, &xhci->op_regs->status);
if (status == 0xffffffff)