Subject: [PATCH 2 of 5] USB ehci driver update
From: Greg KH <[email-masked]>
To: [email-masked]
Cc: [email-masked]

Hi,

Here's a patch against 2.5.2-pre10 that provides the following changes
to the ehci driver:
	- Bulk/control queueing works now
	- Resolves two FIXMEs about unlinking with queued bulk or
	  control urbs
	- Removes old code to support a non-production stepping of the
	  NEC controller
	- Updates two messages to not use 2.5-only features
The patch was written by David Brownell.

thanks,

greg k-h

diff -Nru a/drivers/usb/hcd/ehci-q.c b/drivers/usb/hcd/ehci-q.c
--- a/drivers/usb/hcd/ehci-q.c	Tue Jan  8 09:29:37 2002
+++ b/drivers/usb/hcd/ehci-q.c	Tue Jan  8 09:29:37 2002
@@ -39,12 +39,6 @@
  * buffer low/full speed data so the host collects it at high speed.
  */
 
-#ifdef	EHCI_SOFT_RETRIES
-static int soft_retries = EHCI_SOFT_RETRIES;
-MODULE_PARM (soft_retries, "i");
-MODULE_PARM_DESC (soft_retries, "Number of software retries for endpoint i/o");
-#endif
-
 /*-------------------------------------------------------------------------*/
 
 /* fill a qtd, returning how much of the buffer we were able to queue up */
@@ -134,8 +128,9 @@
 			urb->status = -EPIPE;
 		else	/* unknown */
 			urb->status = -EPROTO;
-		dbg ("devpath %s ep %d-%s qtd token %x --> status %d",
-			urb->dev->devpath, usb_pipeendpoint (urb->pipe),
+		dbg ("ep %d-%s qtd token %08x --> status %d",
+			/* devpath */
+			usb_pipeendpoint (urb->pipe),
 			usb_pipein (urb->pipe) ? "in" : "out",
 			token, urb->status);
 
@@ -148,8 +143,8 @@
 					usb_pipeendpoint (pipe),
 					usb_pipeout (pipe));
 			if (urb->dev->tt && !usb_pipeint (pipe)) {
-err ("must CLEAR_TT_BUFFER, hub %s port %d%s addr %d ep %d",
-    urb->dev->tt->hub->devpath, urb->dev->ttport,
+err ("must CLEAR_TT_BUFFER, hub port %d%s addr %d ep %d",
+    urb->dev->ttport, /* devpath */
     urb->dev->tt->multi ? "" : " (all-ports TT)",
     urb->dev->devnum, usb_pipeendpoint (urb->pipe));
 				// FIXME something (khubd?) should make the hub
@@ -228,12 +223,10 @@
 	struct list_head	*qtd_list,
 	int			freeing
 ) {
-	struct ehci_qtd		*qtd = 0;
-	struct list_head	*next = 0;
-	u32			token;
+	struct ehci_qtd		*qtd, *last;
+	struct list_head	*next;
 	struct ehci_qh		*qh = 0;
-	struct urb		*urb = 0;
-	int			halted = 0;
+	int			unlink = 0, halted = 0;
 	unsigned long		flags;
 	int			retval = 0;
 
@@ -243,89 +236,116 @@
 		return retval;
 	}
 
-	for (qtd = list_entry (qtd_list->next, struct ehci_qtd, qtd_list);
+	/* scan QTDs till end of list, or we reach an active one */
+	for (qtd = list_entry (qtd_list->next, struct ehci_qtd, qtd_list),
+			    	last = 0, next = 0;
 			next != qtd_list;
-			qtd = list_entry (next, struct ehci_qtd, qtd_list)) {
-		token = le32_to_cpu (qtd->hw_token);
-		if (!qh) {
-			urb = qtd->urb;
-			qh = (struct ehci_qh *) urb->hcpriv;
+			last = qtd, qtd = list_entry (next,
+						struct ehci_qtd, qtd_list)) {
+		struct urb	*urb = qtd->urb;
+		u32		token = 0;
+
+		/* qh is non-null iff these qtds were queued to the HC */
+		qh = (struct ehci_qh *) urb->hcpriv;
+
+		/* 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);
+				if (likely (freeing != 0))
+					ehci_urb_done (ehci, last->buf_dma,
+						last->urb);
+				else
+					ehci_urb_complete (ehci, last->buf_dma,
+						last->urb);
+				spin_lock_irqsave (&ehci->lock, flags);
+				retval++;
+			}
+
+			/* qh overlays can have HC's old cached copies of
+			 * next qtd ptrs, if an URB was queued afterwards.
+			 */
+			if (qh && cpu_to_le32 (last->qtd_dma) == qh->hw_current
+					&& last->hw_next != qh->hw_qtd_next) {
+				qh->hw_alt_next = last->hw_alt_next;
+				qh->hw_qtd_next = last->hw_next;
+			}
+
+			if (likely (freeing != 0))
+				ehci_qtd_free (ehci, last);
+			last = 0;
 		}
+		next = qtd->qtd_list.next;
+
+		/* if these qtds were queued to the HC, some may be active.
+		 * else we're cleaning up after a failed URB submission.
+		 */
 		if (likely (qh != 0)) {
+			int		qh_halted;
+
+			qh_halted = __constant_cpu_to_le32 (QTD_STS_HALT)
+					& qh->hw_token;
+			token = le32_to_cpu (qtd->hw_token);
 			halted = halted
+				|| qh_halted
 				|| (ehci->hcd.state == USB_STATE_HALT)
 				|| (qh->qh_state == QH_STATE_IDLE);
 
-			if (unlikely ((token & QTD_STS_HALT) != 0)) {
-#ifdef	EHCI_SOFT_RETRIES
-				/* extra soft retries for protocol errors */
-				if (!halted
-						&& qh->retries < soft_retries
-						&& (QTD_STS_HALT|QTD_STS_XACT)
-							== (token & 0xff)
-						&& QTD_CERR (token) == 0) {
-					if (qh->retries == 0)
-						dbg ("soft retry, qh %p qtd %p",
-							qh, qtd);
-					qh->retries++;
-					token &= ~0x0ff;
-					token |= QTD_STS_ACTIVE;
-					token |= (EHCI_TUNE_CERR << 10);
-					/* qtd update not needed */
-					qh->hw_token = cpu_to_le32 (token);
-					spin_unlock_irqrestore (&ehci->lock,
-						flags);
-					return;
-
-				} else if (qh->retries >= soft_retries
-						&& soft_retries) {
-					dbg ("retried %d times, qh %p qtd %p",
-						qh->retries, qh, qtd);
-				}
-#endif	/* EHCI_SOFT_RETRIES */
-				halted = 1;
-			}
-
-			if (unlikely ((token & QTD_STS_ACTIVE) != 0)) {
-				/* stop scan if qtd is visible to the HC */
-				if (!halted) {
-					urb = 0;
-					break;
-				}
+			/* QH halts only because of fault or unlink; in both
+			 * cases, queued URBs get unlinked.  But for unlink,
+			 * URBs at the head of the queue can stay linked.
+			 */
+			if (unlikely (halted != 0)) {
 
-				/* continue cleanup if HC is halted */
+				/* unlink everything because of HC shutdown? */
 				if (ehci->hcd.state == USB_STATE_HALT) {
+					freeing = unlink = 1;
 					urb->status = -ESHUTDOWN;
-					goto scrub;
-				}
 
-				/* stall? some other urb was unlinked? */
-				if (urb->status == -EINPROGRESS) {
-dbg ("?why? qh %p, qtd %p halted, urb %p, token %8x, len %d",
-	qh, qtd, urb, token, urb->actual_length);
-spin_unlock_irqrestore (&ehci->lock, flags);
-return retval;
-	    /*
-	     * FIXME: write this code.  When one queued urb is unlinked,
-	     * unlink every succeeding urb.
-	     */
+				/* explicit unlink, starting here? */
+				} else if (qh->qh_state == QH_STATE_IDLE
+					&& (urb->status == -ECONNRESET
+						|| urb->status == -ENOENT)) {
+					freeing = unlink = 1;
+
+				/* unlink everything because of error? */
+				} else if (qh_halted
+						&& !(token & QTD_STS_HALT)) {
+					freeing = unlink = 1;
+					if (urb->status == -EINPROGRESS)
+						urb->status = -ECONNRESET;
+
+				/* unlink the rest? */
+				} else if (unlink) {
+					urb->status = -ECONNRESET;
+
+				/* QH halted to unlink urbs after this?  */
+				} else if ((token & QTD_STS_ACTIVE) != 0) {
+					qtd = 0;
 					continue;
 				}
 
-				/* else stopped for some other reason */
-			} 
-scrub:
+			/* Else QH is active, so we must not modify QTDs
+			 * that HC may be working on.  Break from loop.
+			 */
+			} else if (unlikely ((token & QTD_STS_ACTIVE) != 0)) {
+				next = qtd_list;
+				qtd = 0;
+				continue;
+			}
+
 			spin_lock (&urb->lock);
 			qtd_copy_status (urb, qtd->length, token);
 			spin_unlock (&urb->lock);
 		}
-		next = qtd->qtd_list.next;
 
 		/*
 		 * NOTE:  this won't work right with interrupt urbs that
 		 * need multiple qtds ... only the first scan of qh->qtd_list
 		 * starts at the right qtd, yet multiple scans could happen
 		 * for transfers that are scheduled across multiple uframes. 
+		 * (Such schedules are not currently allowed!)
 		 */
 		if (likely (freeing != 0))
 			list_del (&qtd->qtd_list);
@@ -347,8 +367,6 @@
 			qtd->hw_buf [0] |= cpu_to_le32 (0x0fff & qtd->buf_dma);
 		}
 
-		spin_unlock_irqrestore (&ehci->lock, flags);
-
 #if 0
 		if (urb->status == -EINPROGRESS)
 			vdbg ("  qtd %p ok, urb %p, token %8x, len %d",
@@ -364,21 +382,6 @@
 			pci_unmap_single (ehci->hcd.pdev,
 				qtd->buf_dma, sizeof (struct usb_ctrlrequest),
 				PCI_DMA_TODEVICE);
-
-		/* another queued urb? */
-		if (unlikely (qtd->urb != urb)) {
-			if (likely (freeing != 0))
-				ehci_urb_done (ehci, qtd->buf_dma, urb);
-			else
-				ehci_urb_complete (ehci, qtd->buf_dma, urb);
-			retval++;
-			urb = qtd->urb;
-		}
-
-		if (likely (freeing != 0))
-			ehci_qtd_free (ehci, qtd);
-		spin_lock_irqsave (&ehci->lock, flags);
-		qtd = list_entry (next, struct ehci_qtd, qtd_list);
 	}
 
 	/* patch up list head? */
@@ -389,11 +392,12 @@
 	spin_unlock_irqrestore (&ehci->lock, flags);
 
 	/* last urb's completion might still need calling */
-	if (likely (qtd && urb)) {
-		if (likely (freeing != 0))
-			ehci_urb_done (ehci, qtd->buf_dma, urb);
-		else
-			ehci_urb_complete (ehci, qtd->buf_dma, urb);
+	if (likely (last != 0)) {
+		if (likely (freeing != 0)) {
+			ehci_urb_done (ehci, last->buf_dma, last->urb);
+			ehci_qtd_free (ehci, last);
+		} else
+			ehci_urb_complete (ehci, last->buf_dma, last->urb);
 		retval++;
 	}
 	return retval;
@@ -749,7 +753,9 @@
 		/* is an URB is queued to this qh already? */
 		if (unlikely (!list_empty (&qh->qtd_list))) {
 			struct ehci_qtd		*last_qtd;
+			int			short_rx = 0;
 
+			/* update the last qtd's "next" pointer */
 			// dbg_qh ("non-empty qh", ehci, qh);
 			last_qtd = list_entry (qh->qtd_list.prev,
 					struct ehci_qtd, qtd_list);
@@ -760,6 +766,21 @@
 					&& (epnum & 0x10)) {
 				// only the last QTD for now
 				last_qtd->hw_alt_next = hw_next;
+				short_rx = 1;
+			}
+
+			/* Adjust any old copies in qh overlay too.
+			 * Interrupt code must cope with case of HC having it
+			 * cached, and clobbering these updates.
+			 * ... complicates getting rid of extra interrupts!
+			 */
+			if (qh->hw_current == cpu_to_le32 (last_qtd->qtd_dma)) {
+				wmb ();
+				qh->hw_qtd_next = hw_next;
+				if (short_rx)
+					qh->hw_alt_next = hw_next
+				    		| (qh->hw_alt_next & 0x1e);
+				vdbg ("queue to qh %p, patch", qh);
 			}
 
 		/* no URB queued */
@@ -822,8 +843,8 @@
 
 	qh_completions (ehci, &qh->qtd_list, 1);
 
-	// FIXME unlink any urb should unlink all following urbs,
-	// so that this will never happen
+	// 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);