From 44f4c3ed60fb21e1d2dd98304390ac121e6c7c6d Mon Sep 17 00:00:00 2001 From: Greg KH Date: Mon, 19 Sep 2011 16:05:11 -0700 Subject: USB: xhci: Set change bit when warm reset change is set. Sometimes, when a USB 3.0 device is disconnected, the Intel Panther Point xHCI host controller will report a link state change with the state set to "SS.Inactive". This causes the xHCI host controller to issue a warm port reset, which doesn't finish before the USB core times out while waiting for it to complete. When the warm port reset does complete, and the xHC gives back a port status change event, the xHCI driver kicks khubd. However, it fails to set the bit indicating there is a change event for that port because the logic in xhci-hub.c doesn't check for the warm port reset bit. After that, the warm port status change bit is never cleared by the USB core, and the xHC stops reporting port status change bits. (The xHCI spec says it shouldn't report more port events until all change bits are cleared.) This means any port changes when a new device is connected will never be reported, and the port will seem "dead" until the xHCI driver is unloaded and reloaded, or the computer is rebooted. Fix this by making the xHCI driver set the port change bit when a warm port reset change bit is set. A better solution would be to make the USB core handle warm port reset in differently, merging the current code with the standard port reset code that does an incremental backoff on the timeout, and tries to complete the port reset two more times before giving up. That more complicated fix will be merged next window, and this fix will be backported to stable. This should be backported to kernels as old as 3.0, since that was the first kernel with commit a11496ebf375 ("xHCI: warm reset support"). Signed-off-by: Sarah Sharp Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Linus Torvalds --- drivers/usb/host/xhci-hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 1e96d1f1fe6b..723f8231193d 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -761,7 +761,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) memset(buf, 0, retval); status = 0; - mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC; + mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC; spin_lock_irqsave(&xhci->lock, flags); /* For each port, did anything change? If so, set that bit in buf. */ -- cgit v1.2.1 From c2d7b49f42f50d7fc5cbfd195b785a128723fdf4 Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Mon, 19 Sep 2011 16:05:12 -0700 Subject: USB: xHCI: prevent infinite loop when processing MSE event When a xHC host is unable to handle isochronous transfer in the interval, it reports a Missed Service Error event and skips some tds. Currently xhci driver handles MSE event in the following ways: 1. When encounter a MSE event, set ep->skip flag, update event ring dequeue pointer and return. 2. When encounter the next event on this ep, the driver will run the do-while loop, fetch td from ep's td_list to find the td corresponding to this event. All tds missed are marked as short transfer(-EXDEV). The do-while loop will end in two ways: 1. If the td pointed by the event trb is found; 2. If the ep ring's td_list is empty. However, if a buggy HW reports some unpredicted event (for example, an overrun event following a MSE event while the ep ring is actually not empty), the driver will never find the td, and it will loop until the td_list is empty. Unfortunately, the spinlock is dropped when give back a urb in the do-while loop. During the spinlock released period, the class driver may still submit urbs and add tds to the td_list. This may cause disaster, since the td_list will never be empty and the loop never ends, and the system hangs. To fix this, count the number of TDs on the ep ring before skipping TDs, and quit the loop when skipped that number of tds. This guarantees the do-while loop will end after certain number of cycles, and driver will not be trapped in an infinite loop. Signed-off-by: Andiry Xu Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman Signed-off-by: Linus Torvalds --- drivers/usb/host/xhci-ring.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 54139a2f06ce..952e2ded61af 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1934,8 +1934,10 @@ static int handle_tx_event(struct xhci_hcd *xhci, int status = -EINPROGRESS; struct urb_priv *urb_priv; struct xhci_ep_ctx *ep_ctx; + struct list_head *tmp; u32 trb_comp_code; int ret = 0; + int td_num = 0; slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); xdev = xhci->devs[slot_id]; @@ -1957,6 +1959,12 @@ static int handle_tx_event(struct xhci_hcd *xhci, return -ENODEV; } + /* Count current td numbers if ep->skip is set */ + if (ep->skip) { + list_for_each(tmp, &ep_ring->td_list) + td_num++; + } + event_dma = le64_to_cpu(event->buffer); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); /* Look for common error cases */ @@ -2068,7 +2076,18 @@ static int handle_tx_event(struct xhci_hcd *xhci, goto cleanup; } + /* We've skipped all the TDs on the ep ring when ep->skip set */ + if (ep->skip && td_num == 0) { + ep->skip = false; + xhci_dbg(xhci, "All tds on the ep_ring skipped. " + "Clear skip flag.\n"); + ret = 0; + goto cleanup; + } + td = list_entry(ep_ring->td_list.next, struct xhci_td, td_list); + if (ep->skip) + td_num--; /* Is this a TRB in the currently executing TD? */ event_seg = trb_in_td(ep_ring->deq_seg, ep_ring->dequeue, -- cgit v1.2.1