Commit 102ee001 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge tag 'for-usb-next-2013-01-03' of...

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
parents 962426e0 026630d0
...@@ -2535,77 +2535,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, ...@@ -2535,77 +2535,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
return ret; return ret;
/* The port state is unknown until the reset completes. */ /* The port state is unknown until the reset completes. */
if ((portstatus & USB_PORT_STAT_RESET)) if (!(portstatus & USB_PORT_STAT_RESET))
goto delay; break;
/*
* 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;
}
delay:
/* switch to the long delay after two short delay failures */ /* switch to the long delay after two short delay failures */
if (delay_time >= 2 * HUB_SHORT_RESET_TIME) if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
delay = HUB_LONG_RESET_TIME; delay = HUB_LONG_RESET_TIME;
...@@ -2615,20 +2547,54 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, ...@@ -2615,20 +2547,54 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
port1, warm ? "warm " : "", 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, 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) { switch (*status) {
case 0: case 0:
if (!warm) { /* TRSTRCY = 10 ms; plus some extra */
struct usb_hcd *hcd; msleep(10 + 40);
/* TRSTRCY = 10 ms; plus some extra */ if (udev) {
msleep(10 + 40); struct usb_hcd *hcd = bus_to_hcd(udev->bus);
update_devnum(udev, 0); update_devnum(udev, 0);
hcd = bus_to_hcd(udev->bus);
/* The xHC may think the device is already reset, /* The xHC may think the device is already reset,
* so ignore the status. * so ignore the status.
*/ */
...@@ -2640,14 +2606,15 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1, ...@@ -2640,14 +2606,15 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
case -ENODEV: case -ENODEV:
clear_port_feature(hub->hdev, clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_C_RESET); port1, USB_PORT_FEAT_C_RESET);
/* FIXME need disconnect() for NOTATTACHED device */
if (hub_is_superspeed(hub->hdev)) { if (hub_is_superspeed(hub->hdev)) {
clear_port_feature(hub->hdev, port1, clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_BH_PORT_RESET); USB_PORT_FEAT_C_BH_PORT_RESET);
clear_port_feature(hub->hdev, port1, clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_PORT_LINK_STATE); 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_set_device_state(udev, *status
? USB_STATE_NOTATTACHED ? USB_STATE_NOTATTACHED
: USB_STATE_DEFAULT); : USB_STATE_DEFAULT);
...@@ -2660,18 +2627,30 @@ static int hub_port_reset(struct usb_hub *hub, int port1, ...@@ -2660,18 +2627,30 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay, bool warm) struct usb_device *udev, unsigned int delay, bool warm)
{ {
int i, status; int i, status;
u16 portchange, portstatus;
if (!warm) { if (!hub_is_superspeed(hub->hdev)) {
/* Block EHCI CF initialization during the port reset. if (warm) {
* Some companion controllers don't like it when they mix.
*/
down_read(&ehci_cf_port_reset_rwsem);
} else {
if (!hub_is_superspeed(hub->hdev)) {
dev_err(hub->intfdev, "only USB3 hub support " dev_err(hub->intfdev, "only USB3 hub support "
"warm reset\n"); "warm reset\n");
return -EINVAL; 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 */ /* Reset the port */
...@@ -2692,10 +2671,33 @@ static int hub_port_reset(struct usb_hub *hub, int port1, ...@@ -2692,10 +2671,33 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
status); status);
} }
/* return on disconnect or reset */ /* Check for disconnect or reset */
if (status == 0 || status == -ENOTCONN || status == -ENODEV) { if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
hub_port_finish_reset(hub, port1, udev, &status, warm); hub_port_finish_reset(hub, port1, udev, &status);
goto done;
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, dev_dbg (hub->intfdev,
...@@ -2709,7 +2711,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1, ...@@ -2709,7 +2711,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
port1); port1);
done: done:
if (!warm) if (!hub_is_superspeed(hub->hdev))
up_read(&ehci_cf_port_reset_rwsem); up_read(&ehci_cf_port_reset_rwsem);
return status; return status;
...@@ -2945,9 +2947,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) ...@@ -2945,9 +2947,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
/* see 7.1.7.6 */ /* see 7.1.7.6 */
if (hub_is_superspeed(hub->hdev)) if (hub_is_superspeed(hub->hdev))
status = set_port_feature(hub->hdev, status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U3);
port1 | (USB_SS_PORT_LS_U3 << 3),
USB_PORT_FEAT_LINK_STATE);
else else
status = set_port_feature(hub->hdev, port1, status = set_port_feature(hub->hdev, port1,
USB_PORT_FEAT_SUSPEND); USB_PORT_FEAT_SUSPEND);
...@@ -3117,9 +3117,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) ...@@ -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 */ /* see 7.1.7.7; affects power usage, but not budgeting */
if (hub_is_superspeed(hub->hdev)) if (hub_is_superspeed(hub->hdev))
status = set_port_feature(hub->hdev, status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0);
port1 | (USB_SS_PORT_LS_U0 << 3),
USB_PORT_FEAT_LINK_STATE);
else else
status = clear_port_feature(hub->hdev, status = clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_SUSPEND); port1, USB_PORT_FEAT_SUSPEND);
...@@ -4700,12 +4698,21 @@ static void hub_events(void) ...@@ -4700,12 +4698,21 @@ static void hub_events(void)
*/ */
if (hub_port_warm_reset_required(hub, portstatus)) { if (hub_port_warm_reset_required(hub, portstatus)) {
int status; int status;
struct usb_device *udev =
hub->ports[i - 1]->child;
dev_dbg(hub_dev, "warm reset port %d\n", i); dev_dbg(hub_dev, "warm reset port %d\n", i);
status = hub_port_reset(hub, i, NULL, if (!udev) {
HUB_BH_RESET_TIME, true); status = hub_port_reset(hub, i,
if (status < 0) NULL, HUB_BH_RESET_TIME,
hub_port_disable(hub, i, 1); 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; connect_change = 0;
} }
......
...@@ -2706,13 +2706,11 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) ...@@ -2706,13 +2706,11 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
{ {
struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct xhci_hcd *xhci = hcd_to_xhci(hcd);
u32 status; u32 status;
union xhci_trb *trb;
u64 temp_64; u64 temp_64;
union xhci_trb *event_ring_deq; union xhci_trb *event_ring_deq;
dma_addr_t deq; dma_addr_t deq;
spin_lock(&xhci->lock); spin_lock(&xhci->lock);
trb = xhci->event_ring->dequeue;
/* Check if the xHC generated the interrupt, or the irq is shared */ /* Check if the xHC generated the interrupt, or the irq is shared */
status = xhci_readl(xhci, &xhci->op_regs->status); status = xhci_readl(xhci, &xhci->op_regs->status);
if (status == 0xffffffff) if (status == 0xffffffff)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment