ChangeSet 1.879.9.1, 2003/01/06 15:47:26-08:00, david-b@pacbell.net

[PATCH] ehci, remove potential hangs

These don't affect the hang I'm hunting for, but paranoia
argues the patch is better integrated than not:

- prevent resubmit-from-completion looping in_irq if the
   transfers complete really fast.  (likely never seen, but...)

- grab ehci lock before reading irq status; should be harmless
   except in one host error cleanup-after-death


diff -Nru a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
--- a/drivers/usb/host/ehci-hcd.c	Wed Jan  8 12:03:21 2003
+++ b/drivers/usb/host/ehci-hcd.c	Wed Jan  8 12:03:21 2003
@@ -637,9 +637,13 @@
 static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs)
 {
 	struct ehci_hcd		*ehci = hcd_to_ehci (hcd);
-	u32			status = readl (&ehci->regs->status);
+	u32			status;
 	int			bh;
 
+	spin_lock (&ehci->lock);
+
+	status = readl (&ehci->regs->status);
+
 	/* e.g. cardbus physical eject */
 	if (status == ~(u32) 0) {
 		ehci_dbg (ehci, "device removed\n");
@@ -648,9 +652,7 @@
 
 	status &= INTR_MASK;
 	if (!status)			/* irq sharing? */
-		return;
-
-	spin_lock (&ehci->lock);
+		goto done;
 
 	/* clear (just) interrupts */
 	writel (status, &ehci->regs->status);
@@ -693,6 +695,7 @@
 
 	if (bh)
 		ehci_work (ehci, regs);
+done:
 	spin_unlock (&ehci->lock);
 }
 
diff -Nru a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
--- a/drivers/usb/host/ehci-q.c	Wed Jan  8 12:03:21 2003
+++ b/drivers/usb/host/ehci-q.c	Wed Jan  8 12:03:21 2003
@@ -222,7 +222,7 @@
 static unsigned
 qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs)
 {
-	struct ehci_qtd		*last = 0;
+	struct ehci_qtd		*last = 0, *end = qh->dummy;
 	struct list_head	*entry, *tmp;
 	int			stopped = 0;
 	unsigned		count = 0;
@@ -253,6 +253,10 @@
 			last = 0;
 		}
 
+		/* ignore urbs submitted during completions we reported */
+		if (qtd == end)
+			break;
+
 		/* hardware copies qtd out of qh overlay */
 		rmb ();
 		token = le32_to_cpu (qtd->hw_token);
@@ -967,25 +971,28 @@
 scan_async (struct ehci_hcd *ehci, struct pt_regs *regs)
 {
 	struct ehci_qh		*qh;
-	unsigned		count;
 
+	if (!++(ehci->stamp))
+		ehci->stamp++;
 rescan:
 	qh = ehci->async->qh_next.qh;
-	count = 0;
 	if (likely (qh != 0)) {
 		do {
 			/* clean any finished work for this qh */
-			if (!list_empty (&qh->qtd_list)) {
+			if (!list_empty (&qh->qtd_list)
+					&& qh->stamp != ehci->stamp) {
 				int temp;
 
 				/* unlinks could happen here; completion
-				 * reporting drops the lock.
+				 * reporting drops the lock.  rescan using
+				 * the latest schedule, but don't rescan
+				 * qhs we already finished (no looping).
 				 */
 				qh = qh_get (qh);
+				qh->stamp = ehci->stamp;
 				temp = qh_completions (ehci, qh, regs);
 				qh_put (ehci, qh);
 				if (temp != 0) {
-					count += temp;
 					goto rescan;
 				}
 			}
diff -Nru a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
--- a/drivers/usb/host/ehci.h	Wed Jan  8 12:03:21 2003
+++ b/drivers/usb/host/ehci.h	Wed Jan  8 12:03:21 2003
@@ -81,6 +81,7 @@
 	struct pci_pool		*sitd_pool;	/* sitd per split iso urb */
 
 	struct timer_list	watchdog;
+	unsigned		stamp;
 
 #ifdef EHCI_STATS
 	struct ehci_stats	stats;
@@ -306,6 +307,7 @@
 	struct ehci_qtd		*dummy;
 
 	atomic_t		refcount;
+	unsigned		stamp;
 
 	u8			qh_state;
 #define	QH_STATE_LINKED		1		/* HC sees this */
