Commit 7bd89b40 authored by Sarah Sharp's avatar Sarah Sharp

xhci: Don't submit commands or URBs to halted hosts.

Commit fccf4e86
"USB: Free bandwidth when usb_disable_device is called" caused a bit of an
issue when the xHCI host controller driver is unloaded.  It changed the
USB core to remove all endpoints when a USB device is disabled.  When the
driver is unloaded, it will remove the SuperSpeed split root hub, which
will disable all devices under that roothub and then halt the host
controller.  When the second High Speed split roothub is removed, the USB
core will attempt to disable the endpoints, which will submit a Configure
Endpoint command to a halted host controller.

The command will eventually time out, but it makes the xHCI driver unload
take *minutes* if there are a couple of USB 1.1/2.0 devices attached.  We
must halt the host controller when the SuperSpeed roothub is removed,
because we can't allow any interrupts from things like port status
changes.

Make several different functions not submit commands or URBs to the host
controller when the host is halted, by adding a check in
xhci_check_args().  xhci_check_args() is used by these functions:

xhci.c-int xhci_urb_enqueue()
xhci.c-int xhci_drop_endpoint()
xhci.c-int xhci_add_endpoint()
xhci.c-int xhci_check_bandwidth()
xhci.c-void xhci_reset_bandwidth()
xhci.c-static int xhci_check_streams_endpoint()
xhci.c-int xhci_discover_or_reset_device()

It's also used by xhci_free_dev().  However, we have to take special
care in that case, because we want the device memory to be freed if the
host controller is halted.

This patch should be backported to the 2.6.39 and 3.0 kernel.
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Cc: stable@kernel.org
parent e371d46a
...@@ -345,7 +345,8 @@ static void xhci_event_ring_work(unsigned long arg) ...@@ -345,7 +345,8 @@ static void xhci_event_ring_work(unsigned long arg)
spin_lock_irqsave(&xhci->lock, flags); spin_lock_irqsave(&xhci->lock, flags);
temp = xhci_readl(xhci, &xhci->op_regs->status); temp = xhci_readl(xhci, &xhci->op_regs->status);
xhci_dbg(xhci, "op reg status = 0x%x\n", temp); xhci_dbg(xhci, "op reg status = 0x%x\n", temp);
if (temp == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING)) { if (temp == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING) ||
(xhci->xhc_state & XHCI_STATE_HALTED)) {
xhci_dbg(xhci, "HW died, polling stopped.\n"); xhci_dbg(xhci, "HW died, polling stopped.\n");
spin_unlock_irqrestore(&xhci->lock, flags); spin_unlock_irqrestore(&xhci->lock, flags);
return; return;
...@@ -939,8 +940,11 @@ static int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev, ...@@ -939,8 +940,11 @@ static int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev,
return 0; return 0;
} }
xhci = hcd_to_xhci(hcd);
if (xhci->xhc_state & XHCI_STATE_HALTED)
return -ENODEV;
if (check_virt_dev) { if (check_virt_dev) {
xhci = hcd_to_xhci(hcd);
if (!udev->slot_id || !xhci->devs if (!udev->slot_id || !xhci->devs
|| !xhci->devs[udev->slot_id]) { || !xhci->devs[udev->slot_id]) {
printk(KERN_DEBUG "xHCI %s called with unaddressed " printk(KERN_DEBUG "xHCI %s called with unaddressed "
...@@ -1242,7 +1246,8 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) ...@@ -1242,7 +1246,8 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
xhci_urb_free_priv(xhci, urb_priv); xhci_urb_free_priv(xhci, urb_priv);
return ret; return ret;
} }
if (xhci->xhc_state & XHCI_STATE_DYING) { if ((xhci->xhc_state & XHCI_STATE_DYING) ||
(xhci->xhc_state & XHCI_STATE_HALTED)) {
xhci_dbg(xhci, "Ep 0x%x: URB %p to be canceled on " xhci_dbg(xhci, "Ep 0x%x: URB %p to be canceled on "
"non-responsive xHCI host.\n", "non-responsive xHCI host.\n",
urb->ep->desc.bEndpointAddress, urb); urb->ep->desc.bEndpointAddress, urb);
...@@ -2665,7 +2670,10 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) ...@@ -2665,7 +2670,10 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
int i, ret; int i, ret;
ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__);
if (ret <= 0) /* If the host is halted due to driver unload, we still need to free the
* device.
*/
if (ret <= 0 && ret != -ENODEV)
return; return;
virt_dev = xhci->devs[udev->slot_id]; virt_dev = xhci->devs[udev->slot_id];
...@@ -2679,7 +2687,8 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) ...@@ -2679,7 +2687,8 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
spin_lock_irqsave(&xhci->lock, flags); spin_lock_irqsave(&xhci->lock, flags);
/* Don't disable the slot if the host controller is dead. */ /* Don't disable the slot if the host controller is dead. */
state = xhci_readl(xhci, &xhci->op_regs->status); state = xhci_readl(xhci, &xhci->op_regs->status);
if (state == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING)) { if (state == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING) ||
(xhci->xhc_state & XHCI_STATE_HALTED)) {
xhci_free_virt_device(xhci, udev->slot_id); xhci_free_virt_device(xhci, udev->slot_id);
spin_unlock_irqrestore(&xhci->lock, flags); spin_unlock_irqrestore(&xhci->lock, flags);
return; return;
......
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