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
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1.4.11 (GNU/Linux)
 
 iQIcBAABAgAGBQJQ5gVdAAoJEBMGWMLi1Gc5DZMQAKBwA07JWHONfJVfoPU/+Iz5
 rxstace/JP4HWerUbvuArkd7aEn+14JO2nU7czLZHxsjm1F7PiNp+f/rPTKxdoG5
 Qm6x5oASeGfFrihG8BVh4WDeT2SraCNOtD6miqZwkjVW73mfYSEjyPFxJYlnpXOI
 u7ZBQVqWC5ZTJGupR4CHs38utYFuBlS1brR+f4D4+4p/YardBM0aD2u/o5xdqQJV
 AHK1HAL9UttVubQkV7B7tQwB1hk8RQ/24TrATceQpehsqqRvWqAz2dzSivk1/tqb
 Vf8abYURxKWNUYYQ1L7iV6n+t5oiF3wK3dI+D8xwlkb4xheVwcu1lvXC0XmGdyrf
 2JqYs6quJk6755flM5qGtftdf3Bwdj8p4pqpcizxjeCDCCqIGpy+32cnzli5JfBy
 /w0pa2qbUzHZWNqGVlrqO3f8Bjb+Qsx2mqdvJVfhsbt2OsP+k/SYUwQ4UK/6ZDRO
 /mZxW0MEWVUd7CdGoFlGFam0vD/XWrPCDh32V5Oqt4kNKPHxXMjsrexR5XyRm7MV
 Kd2pA+L+XAbUPF2wJQXtyFXb4h6jtSZe6xvWujI3rtsqpeb5XyX/70uqOQ4y4ZYG
 HEWmieCCtuP+SfUQyR/xvi/WKRihQT+Pz00BlCmXEsuD5h5IBnK7i/CMBN85HUPj
 bb4GuYIrzKPADIP0ReLh
 =VBlC
 -----END PGP SIGNATURE-----

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
This commit is contained in:
Greg Kroah-Hartman 2013-01-07 10:14:23 -08:00
commit 102ee00191
2 changed files with 107 additions and 102 deletions

View File

@ -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;
if (!(portstatus & USB_PORT_STAT_RESET))
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 */
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;
}

View File

@ -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)