<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;"># This is a BitKeeper generated patch for the following project:
# Project Name: Linux kernel tree
# This patch format is intended for GNU patch command version 2.5 or higher.
# This patch includes the following deltas:
#	           ChangeSet	1.600.1.4 -&gt; 1.600.1.5
#	drivers/usb/host/ehci-q.c	1.22    -&gt; 1.23   
#	drivers/usb/host/ehci-sched.c	1.17    -&gt; 1.18   
#	drivers/usb/host/ehci-hcd.c	1.23    -&gt; 1.24   
#	drivers/usb/host/ehci.h	1.7     -&gt; 1.8    
#
# The following is the BitKeeper ChangeSet Log
# --------------------------------------------
# 02/09/04	david-b@pacbell.net	1.600.1.5
# [PATCH] ehci locking
# 
# I've been chasing problems on a KT333 based system, with
# the 8253 southbridge and EHCI 1.0 (!), and this fixes at
# least some of them:
# 
#    - locking updates:
#       * a few routines weren't protected right
#       * less irqsave thrashing for schedule lock
# 
#    - adds a watchdog timer that should fire when the
#      STS_IAA interrupt seems to be missing.
# 
#    - gives ports back to companion UHCI/OHCI on rmmod
# 
#    - re-enables faulted QH only after all its completion
#      callbacks have done their work
# 
#    - removes an oops I've seen when usb-storage unlinks
#      stuff.  (it seemed confused about error handling, but
#      that's not a reason to oops.)
# 
#    - minor cleanup:  deadcode rm, etc
# 
# Right now the watchdog just barks, and that mechanism might
# go away (or into the shared hcd code).  Sometimes the issue
# it reports seems to clear up by itself, but sometimes not...
# --------------------------------------------
#
diff -Nru a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
--- a/drivers/usb/host/ehci-hcd.c	Thu Sep  5 08:51:35 2002
+++ b/drivers/usb/host/ehci-hcd.c	Thu Sep  5 08:51:35 2002
@@ -87,7 +87,7 @@
  * 2001-June	Works with usb-storage and NEC EHCI on 2.4
  */
 
-#define DRIVER_VERSION "2002-Aug-06"
+#define DRIVER_VERSION "2002-Aug-28"
 #define DRIVER_AUTHOR "David Brownell"
 #define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver"
 
@@ -104,6 +104,8 @@
 #define	EHCI_TUNE_MULT_HS	1	/* 1-3 transactions/uframe; 4.10.3 */
 #define	EHCI_TUNE_MULT_TT	1
 
+#define EHCI_WATCHDOG_JIFFIES	(HZ/10)		/* arbitrary; ~100 msec */
+
 /* Initial IRQ latency:  lower than default */
 static int log2_irq_thresh = 0;		// 0 to 6
 MODULE_PARM (log2_irq_thresh, "i");
@@ -232,6 +234,23 @@
 
 static void ehci_tasklet (unsigned long param);
 
+static void ehci_watchdog (unsigned long param)
+{
+	struct ehci_hcd		*ehci = (struct ehci_hcd *) param;
+	unsigned long		flags;
+
+	/* guard against lost IAA, which wedges everything */
+	spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
+	if (ehci-&gt;reclaim) {
+		err ("%s watchdog, reclaim qh %p%s", ehci-&gt;hcd.self.bus_name,
+			ehci-&gt;reclaim, ehci-&gt;reclaim_ready ? " ready" : "");
+//		ehci-&gt;reclaim_ready = 1;
+		tasklet_schedule (&amp;ehci-&gt;tasklet);
+	}
+	spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
+
+}
+
 /* EHCI 0.96 (and later) section 5.1 says how to kick BIOS/SMM/...
  * off the controller (maybe it can boot from highspeed USB disks).
  */
@@ -372,6 +391,10 @@
 	ehci-&gt;tasklet.func = ehci_tasklet;
 	ehci-&gt;tasklet.data = (unsigned long) ehci;
 
+	init_timer (&amp;ehci-&gt;watchdog);
+	ehci-&gt;watchdog.function = ehci_watchdog;
+	ehci-&gt;watchdog.data = (unsigned long) ehci;
+
 	/* wire up the root hub */
 	hcd-&gt;self.root_hub = udev = usb_alloc_dev (NULL, &amp;hcd-&gt;self);
 	if (!udev) {
@@ -394,7 +417,7 @@
         /* PCI Serial Bus Release Number is at 0x60 offset */
 	pci_read_config_byte (hcd-&gt;pdev, 0x60, &amp;tempbyte);
 	temp = readw (&amp;ehci-&gt;caps-&gt;hci_version);
-	info ("USB %x.%x support enabled, EHCI rev %x.%2x",
+	info ("USB %x.%x support enabled, EHCI rev %x.%02x",
 	      ((tempbyte &amp; 0xf0)&gt;&gt;4),
 	      (tempbyte &amp; 0x0f),
 	       temp &gt;&gt; 8,
@@ -433,8 +456,15 @@
 	/* no more interrupts ... */
 	if (hcd-&gt;state == USB_STATE_RUNNING)
 		ehci_ready (ehci);
+	if (in_interrupt ())		/* should not happen!! */
+		err ("stopped %s!", RUN_CONTEXT);
+	else
+		del_timer_sync (&amp;ehci-&gt;watchdog);
 	ehci_reset (ehci);
 
+	/* let companion controllers work when we aren't */
+	writel (0, &amp;ehci-&gt;regs-&gt;configured_flag);
+
 	remove_debug_files (ehci);
 
 	/* root hub is shut down separately (first, when possible) */
@@ -546,12 +576,17 @@
 static void ehci_tasklet (unsigned long param)
 {
 	struct ehci_hcd		*ehci = (struct ehci_hcd *) param;
+	unsigned long		flags;
+
+	spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
 
 	if (ehci-&gt;reclaim_ready)
-		end_unlink_async (ehci);
-	scan_async (ehci);
+		flags = end_unlink_async (ehci, flags);
+	flags = scan_async (ehci, flags);
 	if (ehci-&gt;next_uframe != -1)
-		scan_periodic (ehci);
+		flags = scan_periodic (ehci, flags);
+
+	spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 }
 
 /*-------------------------------------------------------------------------*/
@@ -681,7 +716,13 @@
 	default:
 		spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
 		if (ehci-&gt;reclaim) {
-			dbg ("dq: reclaim busy, %s", RUN_CONTEXT);
+			dbg ("dq %p: reclaim = %p, %s",
+				qh, ehci-&gt;reclaim, RUN_CONTEXT);
+			if (qh == ehci-&gt;reclaim) {
+				/* unlinking qh for another queued urb? */
+				spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
+				return 0;
+			}
 			if (in_interrupt ()) {
 				spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 				return -EAGAIN;
@@ -702,19 +743,19 @@
 		break;
 
 	case PIPE_INTERRUPT:
+		spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
 		if (qh-&gt;qh_state == QH_STATE_LINKED) {
 			/* messy, can spin or block a microframe ... */
-			intr_deschedule (ehci, qh, 1);
+			flags = intr_deschedule (ehci, qh, 1, flags);
 			/* qh_state == IDLE */
 		}
-		qh_completions (ehci, qh);
+		flags = qh_completions (ehci, qh, flags);
 
 		/* reschedule QH iff another request is queued */
 		if (!list_empty (&amp;qh-&gt;qtd_list)
 				&amp;&amp; HCD_IS_RUNNING (ehci-&gt;hcd.state)) {
 			int status;
 
-			spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
 			status = qh_schedule (ehci, qh);
 			spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 
@@ -726,7 +767,7 @@
 			}
 			return status;
 		}
-
+		spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 		break;
 
 	case PIPE_ISOCHRONOUS:
@@ -805,8 +846,7 @@
 				start_unlink_async (ehci, qh);
 			while (qh-&gt;qh_state != QH_STATE_IDLE
 					&amp;&amp; ehci-&gt;hcd.state != USB_STATE_HALT) {
-				spin_unlock_irqrestore (&amp;ehci-&gt;lock,
-					flags);
+				spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 				wait_ms (1);
 				spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
 			}
diff -Nru a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
--- a/drivers/usb/host/ehci-q.c	Thu Sep  5 08:51:35 2002
+++ b/drivers/usb/host/ehci-q.c	Thu Sep  5 08:51:35 2002
@@ -161,9 +161,10 @@
 
 /* urb-&gt;lock ignored from here on (hcd is done with urb) */
 
-static void ehci_urb_done (
+static unsigned long ehci_urb_done (
 	struct ehci_hcd		*ehci,
-	struct urb		*urb
+	struct urb		*urb,
+	unsigned long		flags
 ) {
 #ifdef	INTR_AUTOMAGIC
 	struct urb		*resubmit = 0;
@@ -199,6 +200,8 @@
 			urb-&gt;status = 0;
 	}
 
+	/* complete() can reenter this HCD */
+	spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 	usb_hcd_giveback_urb (&amp;ehci-&gt;hcd, urb);
 
 #ifdef	INTR_AUTOMAGIC
@@ -219,27 +222,25 @@
 		usb_put_urb (resubmit);
 	}
 #endif
+
+	spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
+	return flags;
 }
 
 
 /*
- * Process completed qtds for a qh, issuing completions if needed.
- * Frees qtds, unmaps buf, returns URB to driver.
- * Races up to qh-&gt;hw_current; returns number of urb completions.
+ * Process and free completed qtds for a qh, returning URBs to drivers.
+ * Chases up to qh-&gt;hw_current, returns irqsave flags (maybe modified).
  */
-static void
-qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
+static unsigned long
+qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, unsigned long flags)
 {
 	struct ehci_qtd		*qtd, *last;
 	struct list_head	*next, *qtd_list = &amp;qh-&gt;qtd_list;
 	int			unlink = 0, halted = 0;
-	unsigned long		flags;
 
-	spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
-	if (unlikely (list_empty (qtd_list))) {
-		spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
-		return;
-	}
+	if (unlikely (list_empty (qtd_list)))
+		return flags;
 
 	/* scan QTDs till end of list, or we reach an active one */
 	for (qtd = list_entry (qtd_list-&gt;next, struct ehci_qtd, qtd_list),
@@ -252,12 +253,8 @@
 
 		/* clean up any state from previous QTD ...*/
 		if (last) {
-			if (likely (last-&gt;urb != urb)) {
-				/* complete() can reenter this HCD */
-				spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
-				ehci_urb_done (ehci, last-&gt;urb);
-				spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
-			}
+			if (likely (last-&gt;urb != urb))
+				flags = ehci_urb_done (ehci, last-&gt;urb, flags);
 
 			/* qh overlays can have HC's old cached copies of
 			 * next qtd ptrs, if an URB was queued afterwards.
@@ -283,6 +280,9 @@
 			|| (ehci-&gt;hcd.state == USB_STATE_HALT)
 			|| (qh-&gt;qh_state == QH_STATE_IDLE);
 
+		// FIXME Remove the automagic unlink mode.
+		// Drivers can now clean up safely; its' their job.
+
 		/* fault: unlink the rest, since this qtd saw an error? */
 		if (unlikely ((token &amp; QTD_STS_HALT) != 0)) {
 			unlink = 1;
@@ -341,18 +341,19 @@
 #endif
 	}
 
-	/* patch up list head? */
+	/* last urb's completion might still need calling */
+	if (likely (last != 0)) {
+		flags = ehci_urb_done (ehci, last-&gt;urb, flags);
+		ehci_qtd_free (ehci, last);
+	}
+
+	/* reactivate queue after error and driver's cleanup */
 	if (unlikely (halted &amp;&amp; !list_empty (qtd_list))) {
 		qh_update (qh, list_entry (qtd_list-&gt;next,
 				struct ehci_qtd, qtd_list));
 	}
-	spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 
-	/* last urb's completion might still need calling */
-	if (likely (last != 0)) {
-		ehci_urb_done (ehci, last-&gt;urb);
-		ehci_qtd_free (ehci, last);
-	}
+	return flags;
 }
 
 /*-------------------------------------------------------------------------*/
@@ -367,31 +368,12 @@
 	struct list_head	*qtd_list
 ) {
 	struct list_head	*entry, *temp;
-	int			unmapped = 0;
 
 	list_for_each_safe (entry, temp, qtd_list) {
 		struct ehci_qtd	*qtd;
 
 		qtd = list_entry (entry, struct ehci_qtd, qtd_list);
 		list_del (&amp;qtd-&gt;qtd_list);
-		if (unmapped != 2) {
-			int	direction;
-			size_t	size;
-
-			/* for ctrl unmap twice: SETUP and DATA;
-			 * else (bulk, intr) just once: DATA
-			 */
-			if (!unmapped++ &amp;&amp; usb_pipecontrol (urb-&gt;pipe)) {
-				direction = PCI_DMA_TODEVICE;
-				size = sizeof (struct usb_ctrlrequest);
-			} else {
-				direction = usb_pipein (urb-&gt;pipe)
-					? PCI_DMA_FROMDEVICE
-					: PCI_DMA_TODEVICE;
-				size = qtd-&gt;urb-&gt;transfer_buffer_length;
-				unmapped++;
-			}
-		}
 		ehci_qtd_free (ehci, qtd);
 	}
 }
@@ -886,25 +868,28 @@
 /* the async qh for the qtds being reclaimed are now unlinked from the HC */
 /* caller must not own ehci-&gt;lock */
 
-static void end_unlink_async (struct ehci_hcd *ehci)
+static unsigned long
+end_unlink_async (struct ehci_hcd *ehci, unsigned long flags)
 {
 	struct ehci_qh		*qh = ehci-&gt;reclaim;
 
+	del_timer (&amp;ehci-&gt;watchdog);
+
 	qh-&gt;qh_state = QH_STATE_IDLE;
 	qh-&gt;qh_next.qh = 0;
 	qh_put (ehci, qh);			// refcount from reclaim 
 	ehci-&gt;reclaim = 0;
 	ehci-&gt;reclaim_ready = 0;
 
-	qh_completions (ehci, qh);
+	flags = qh_completions (ehci, qh, flags);
 
-	// unlink any urb should now unlink all following urbs, so that
-	// relinking only happens for urbs before the unlinked ones.
 	if (!list_empty (&amp;qh-&gt;qtd_list)
 			&amp;&amp; HCD_IS_RUNNING (ehci-&gt;hcd.state))
 		qh_link_async (ehci, qh);
 	else
 		qh_put (ehci, qh);		// refcount from async list
+
+	return flags;
 }
 
 
@@ -975,16 +960,17 @@
 	cmd |= CMD_IAAD;
 	writel (cmd, &amp;ehci-&gt;regs-&gt;command);
 	/* posted write need not be known to HC yet ... */
+
+	mod_timer (&amp;ehci-&gt;watchdog, jiffies + EHCI_WATCHDOG_JIFFIES);
 }
 
 /*-------------------------------------------------------------------------*/
 
-static void scan_async (struct ehci_hcd *ehci)
+static unsigned long
+scan_async (struct ehci_hcd *ehci, unsigned long flags)
 {
 	struct ehci_qh		*qh;
-	unsigned long		flags;
 
-	spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
 rescan:
 	qh = ehci-&gt;async;
 	if (likely (qh != 0)) {
@@ -993,12 +979,9 @@
 			if (!list_empty (&amp;qh-&gt;qtd_list)) {
 				// dbg_qh ("scan_async", ehci, qh);
 				qh = qh_get (qh);
-				spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 
 				/* concurrent unlink could happen here */
-				qh_completions (ehci, qh);
-
-				spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
+				flags = qh_completions (ehci, qh, flags);
 				qh_put (ehci, qh);
 			}
 
@@ -1020,6 +1003,5 @@
 				goto rescan;
 		} while (qh != ehci-&gt;async);
 	}
-
-	spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
+	return flags;
 }
diff -Nru a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
--- a/drivers/usb/host/ehci-sched.c	Thu Sep  5 08:51:35 2002
+++ b/drivers/usb/host/ehci-sched.c	Thu Sep  5 08:51:35 2002
@@ -222,17 +222,15 @@
 
 // FIXME microframe periods not yet handled
 
-static void intr_deschedule (
+static unsigned long intr_deschedule (
 	struct ehci_hcd	*ehci,
 	struct ehci_qh	*qh,
-	int		wait
+	int		wait,
+	unsigned long	flags
 ) {
-	unsigned long	flags;
 	int		status;
 	unsigned	frame = qh-&gt;start;
 
-	spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
-
 	do {
 		periodic_unlink (ehci, frame, qh);
 		qh_put (ehci, qh);
@@ -251,8 +249,6 @@
 		vdbg ("periodic schedule still enabled");
 	}
 
-	spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
-
 	/*
 	 * If the hc may be looking at this qh, then delay a uframe
 	 * (yeech!) to be sure it's done.
@@ -260,8 +256,10 @@
 	 */
 	if (((ehci_get_frame (&amp;ehci-&gt;hcd) - frame) % qh-&gt;period) == 0) {
 		if (wait) {
+			spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
 			udelay (125);
 			qh-&gt;hw_next = EHCI_LIST_END;
+			spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
 		} else {
 			/* we may not be IDLE yet, but if the qh is empty
 			 * the race is very short.  then if qh also isn't
@@ -281,6 +279,7 @@
 	vdbg ("descheduled qh %p, per = %d frame = %d count = %d, urbs = %d",
 		qh, qh-&gt;period, frame,
 		atomic_read (&amp;qh-&gt;refcount), ehci-&gt;periodic_sched);
+	return flags;
 }
 
 static int check_period (
@@ -513,12 +512,10 @@
 	}
 	
 	/* handle any completions */
-	spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
-	qh_completions (ehci, qh);
-	spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
+	flags = qh_completions (ehci, qh, flags);
 
 	if (unlikely (list_empty (&amp;qh-&gt;qtd_list)))
-		intr_deschedule (ehci, qh, 0);
+		flags = intr_deschedule (ehci, qh, 0, flags);
 
 	return flags;
 }
@@ -1091,13 +1088,12 @@
 
 /*-------------------------------------------------------------------------*/
 
-static void scan_periodic (struct ehci_hcd *ehci)
+static unsigned long
+scan_periodic (struct ehci_hcd *ehci, unsigned long flags)
 {
 	unsigned	frame, clock, now_uframe, mod;
-	unsigned long	flags;
 
 	mod = ehci-&gt;periodic_size &lt;&lt; 3;
-	spin_lock_irqsave (&amp;ehci-&gt;lock, flags);
 
 	/*
 	 * When running, scan from last scan point up to "now"
@@ -1237,5 +1233,5 @@
 		} else
 			frame = (frame + 1) % ehci-&gt;periodic_size;
 	} 
-	spin_unlock_irqrestore (&amp;ehci-&gt;lock, flags);
+	return flags;
 }
diff -Nru a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
--- a/drivers/usb/host/ehci.h	Thu Sep  5 08:51:35 2002
+++ b/drivers/usb/host/ehci.h	Thu Sep  5 08:51:35 2002
@@ -69,6 +69,8 @@
 	struct pci_pool		*qtd_pool;	/* one or more per qh */
 	struct pci_pool		*itd_pool;	/* itd per iso urb */
 	struct pci_pool		*sitd_pool;	/* sitd per split iso urb */
+
+	struct timer_list	watchdog;
 };
 
 /* unwrap an HCD pointer to get an EHCI_HCD pointer */ 
</pre></body></html>