# 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 -> 1.600.1.5
#	drivers/usb/host/ehci-q.c	1.22    -> 1.23   
#	drivers/usb/host/ehci-sched.c	1.17    -> 1.18   
#	drivers/usb/host/ehci-hcd.c	1.23    -> 1.24   
#	drivers/usb/host/ehci.h	1.7     -> 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 (&ehci->lock, flags);
+	if (ehci->reclaim) {
+		err ("%s watchdog, reclaim qh %p%s", ehci->hcd.self.bus_name,
+			ehci->reclaim, ehci->reclaim_ready ? " ready" : "");
+//		ehci->reclaim_ready = 1;
+		tasklet_schedule (&ehci->tasklet);
+	}
+	spin_unlock_irqrestore (&ehci->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->tasklet.func = ehci_tasklet;
 	ehci->tasklet.data = (unsigned long) ehci;
 
+	init_timer (&ehci->watchdog);
+	ehci->watchdog.function = ehci_watchdog;
+	ehci->watchdog.data = (unsigned long) ehci;
+
 	/* wire up the root hub */
 	hcd->self.root_hub = udev = usb_alloc_dev (NULL, &hcd->self);
 	if (!udev) {
@@ -394,7 +417,7 @@
         /* PCI Serial Bus Release Number is at 0x60 offset */
 	pci_read_config_byte (hcd->pdev, 0x60, &tempbyte);
 	temp = readw (&ehci->caps->hci_version);
-	info ("USB %x.%x support enabled, EHCI rev %x.%2x",
+	info ("USB %x.%x support enabled, EHCI rev %x.%02x",
 	      ((tempbyte & 0xf0)>>4),
 	      (tempbyte & 0x0f),
 	       temp >> 8,
@@ -433,8 +456,15 @@
 	/* no more interrupts ... */
 	if (hcd->state == USB_STATE_RUNNING)
 		ehci_ready (ehci);
+	if (in_interrupt ())		/* should not happen!! */
+		err ("stopped %s!", RUN_CONTEXT);
+	else
+		del_timer_sync (&ehci->watchdog);
 	ehci_reset (ehci);
 
+	/* let companion controllers work when we aren't */
+	writel (0, &ehci->regs->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 (&ehci->lock, flags);
 
 	if (ehci->reclaim_ready)
-		end_unlink_async (ehci);
-	scan_async (ehci);
+		flags = end_unlink_async (ehci, flags);
+	flags = scan_async (ehci, flags);
 	if (ehci->next_uframe != -1)
-		scan_periodic (ehci);
+		flags = scan_periodic (ehci, flags);
+
+	spin_unlock_irqrestore (&ehci->lock, flags);
 }
 
 /*-------------------------------------------------------------------------*/
@@ -681,7 +716,13 @@
 	default:
 		spin_lock_irqsave (&ehci->lock, flags);
 		if (ehci->reclaim) {
-			dbg ("dq: reclaim busy, %s", RUN_CONTEXT);
+			dbg ("dq %p: reclaim = %p, %s",
+				qh, ehci->reclaim, RUN_CONTEXT);
+			if (qh == ehci->reclaim) {
+				/* unlinking qh for another queued urb? */
+				spin_unlock_irqrestore (&ehci->lock, flags);
+				return 0;
+			}
 			if (in_interrupt ()) {
 				spin_unlock_irqrestore (&ehci->lock, flags);
 				return -EAGAIN;
@@ -702,19 +743,19 @@
 		break;
 
 	case PIPE_INTERRUPT:
+		spin_lock_irqsave (&ehci->lock, flags);
 		if (qh->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 (&qh->qtd_list)
 				&& HCD_IS_RUNNING (ehci->hcd.state)) {
 			int status;
 
-			spin_lock_irqsave (&ehci->lock, flags);
 			status = qh_schedule (ehci, qh);
 			spin_unlock_irqrestore (&ehci->lock, flags);
 
@@ -726,7 +767,7 @@
 			}
 			return status;
 		}
-
+		spin_unlock_irqrestore (&ehci->lock, flags);
 		break;
 
 	case PIPE_ISOCHRONOUS:
@@ -805,8 +846,7 @@
 				start_unlink_async (ehci, qh);
 			while (qh->qh_state != QH_STATE_IDLE
 					&& ehci->hcd.state != USB_STATE_HALT) {
-				spin_unlock_irqrestore (&ehci->lock,
-					flags);
+				spin_unlock_irqrestore (&ehci->lock, flags);
 				wait_ms (1);
 				spin_lock_irqsave (&ehci->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->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->status = 0;
 	}
 
+	/* complete() can reenter this HCD */
+	spin_unlock_irqrestore (&ehci->lock, flags);
 	usb_hcd_giveback_urb (&ehci->hcd, urb);
 
 #ifdef	INTR_AUTOMAGIC
@@ -219,27 +222,25 @@
 		usb_put_urb (resubmit);
 	}
 #endif
+
+	spin_lock_irqsave (&ehci->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->hw_current; returns number of urb completions.
+ * Process and free completed qtds for a qh, returning URBs to drivers.
+ * Chases up to qh->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 = &qh->qtd_list;
 	int			unlink = 0, halted = 0;
-	unsigned long		flags;
 
-	spin_lock_irqsave (&ehci->lock, flags);
-	if (unlikely (list_empty (qtd_list))) {
-		spin_unlock_irqrestore (&ehci->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->next, struct ehci_qtd, qtd_list),
@@ -252,12 +253,8 @@
 
 		/* clean up any state from previous QTD ...*/
 		if (last) {
-			if (likely (last->urb != urb)) {
-				/* complete() can reenter this HCD */
-				spin_unlock_irqrestore (&ehci->lock, flags);
-				ehci_urb_done (ehci, last->urb);
-				spin_lock_irqsave (&ehci->lock, flags);
-			}
+			if (likely (last->urb != urb))
+				flags = ehci_urb_done (ehci, last->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->hcd.state == USB_STATE_HALT)
 			|| (qh->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 & 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->urb, flags);
+		ehci_qtd_free (ehci, last);
+	}
+
+	/* reactivate queue after error and driver's cleanup */
 	if (unlikely (halted && !list_empty (qtd_list))) {
 		qh_update (qh, list_entry (qtd_list->next,
 				struct ehci_qtd, qtd_list));
 	}
-	spin_unlock_irqrestore (&ehci->lock, flags);
 
-	/* last urb's completion might still need calling */
-	if (likely (last != 0)) {
-		ehci_urb_done (ehci, last->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 (&qtd->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++ && usb_pipecontrol (urb->pipe)) {
-				direction = PCI_DMA_TODEVICE;
-				size = sizeof (struct usb_ctrlrequest);
-			} else {
-				direction = usb_pipein (urb->pipe)
-					? PCI_DMA_FROMDEVICE
-					: PCI_DMA_TODEVICE;
-				size = qtd->urb->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->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->reclaim;
 
+	del_timer (&ehci->watchdog);
+
 	qh->qh_state = QH_STATE_IDLE;
 	qh->qh_next.qh = 0;
 	qh_put (ehci, qh);			// refcount from reclaim 
 	ehci->reclaim = 0;
 	ehci->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 (&qh->qtd_list)
 			&& HCD_IS_RUNNING (ehci->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, &ehci->regs->command);
 	/* posted write need not be known to HC yet ... */
+
+	mod_timer (&ehci->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 (&ehci->lock, flags);
 rescan:
 	qh = ehci->async;
 	if (likely (qh != 0)) {
@@ -993,12 +979,9 @@
 			if (!list_empty (&qh->qtd_list)) {
 				// dbg_qh ("scan_async", ehci, qh);
 				qh = qh_get (qh);
-				spin_unlock_irqrestore (&ehci->lock, flags);
 
 				/* concurrent unlink could happen here */
-				qh_completions (ehci, qh);
-
-				spin_lock_irqsave (&ehci->lock, flags);
+				flags = qh_completions (ehci, qh, flags);
 				qh_put (ehci, qh);
 			}
 
@@ -1020,6 +1003,5 @@
 				goto rescan;
 		} while (qh != ehci->async);
 	}
-
-	spin_unlock_irqrestore (&ehci->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->start;
 
-	spin_lock_irqsave (&ehci->lock, flags);
-
 	do {
 		periodic_unlink (ehci, frame, qh);
 		qh_put (ehci, qh);
@@ -251,8 +249,6 @@
 		vdbg ("periodic schedule still enabled");
 	}
 
-	spin_unlock_irqrestore (&ehci->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 (&ehci->hcd) - frame) % qh->period) == 0) {
 		if (wait) {
+			spin_unlock_irqrestore (&ehci->lock, flags);
 			udelay (125);
 			qh->hw_next = EHCI_LIST_END;
+			spin_lock_irqsave (&ehci->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->period, frame,
 		atomic_read (&qh->refcount), ehci->periodic_sched);
+	return flags;
 }
 
 static int check_period (
@@ -513,12 +512,10 @@
 	}
 	
 	/* handle any completions */
-	spin_unlock_irqrestore (&ehci->lock, flags);
-	qh_completions (ehci, qh);
-	spin_lock_irqsave (&ehci->lock, flags);
+	flags = qh_completions (ehci, qh, flags);
 
 	if (unlikely (list_empty (&qh->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->periodic_size << 3;
-	spin_lock_irqsave (&ehci->lock, flags);
 
 	/*
 	 * When running, scan from last scan point up to "now"
@@ -1237,5 +1233,5 @@
 		} else
 			frame = (frame + 1) % ehci->periodic_size;
 	} 
-	spin_unlock_irqrestore (&ehci->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 */ 
