ohci-hcd patch for testing

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Dennis and Matteo:

I promised to send both of you a patch changing the way ohci-hcd
handles hardware bugs.  Well, it's finally ready for testing.  There's
only a limited amount I can do on my own machine, so now it's up to you
guys.

The patch was made against an early -rc version of 3.15, but it will
apply okay to 3.14 and maybe even earlier kernels.

Alan Stern



Index: usb-3.15/drivers/usb/host/ohci.h
===================================================================
--- usb-3.15.orig/drivers/usb/host/ohci.h
+++ usb-3.15/drivers/usb/host/ohci.h
@@ -47,6 +47,7 @@ struct ed {
 	struct ed		*ed_next;	/* on schedule or rm_list */
 	struct ed		*ed_prev;	/* for non-interrupt EDs */
 	struct list_head	td_list;	/* "shadow list" of our TDs */
+	struct list_head	in_use_list;
 
 	/* create --> IDLE --> OPER --> ... --> IDLE --> destroy
 	 * usually:  OPER --> UNLINK --> (IDLE | OPER) --> ...
@@ -66,6 +67,13 @@ struct ed {
 
 	/* HC may see EDs on rm_list until next frame (frame_no == tick) */
 	u16			tick;
+
+	/* Detect TDs not added to the done list */
+	unsigned		takeback_wdh_cnt;
+	struct td		*pending_td;
+#define	OKAY_TO_TAKEBACK(ohci, ed)			\
+		((int) (ohci->wdh_cnt - ed->takeback_wdh_cnt) >= 0)
+
 } __attribute__ ((aligned(16)));
 
 #define ED_MASK	((u32)~0x0f)		/* strip hw status in low addr bits */
@@ -380,7 +388,9 @@ struct ohci_hcd {
 	struct dma_pool		*td_cache;
 	struct dma_pool		*ed_cache;
 	struct td		*td_hash [TD_HASH_SIZE];
+	struct td		*dl_start, *dl_end;	/* the done list */
 	struct list_head	pending;
+	struct list_head	eds_in_use;	/* all EDs with at least 1 TD */
 
 	/*
 	 * driver state
@@ -392,6 +402,8 @@ struct ohci_hcd {
 	unsigned long		next_statechange;	/* suspend/resume */
 	u32			fminterval;		/* saved register */
 	unsigned		autostop:1;	/* rh auto stopping/stopped */
+	unsigned		working:1;
+	unsigned		restart_work:1;
 
 	unsigned long		flags;		/* for HC bugs */
 #define	OHCI_QUIRK_AMD756	0x01			/* erratum #4 */
@@ -407,13 +419,12 @@ struct ohci_hcd {
 #define	OHCI_QUIRK_AMD_PREFETCH	0x400			/* pre-fetch for ISO transfer */
 	// there are also chip quirks/bugs in init logic
 
-	struct work_struct	nec_work;	/* Worker for NEC quirk */
+	unsigned		prev_frame_no;
+	unsigned		wdh_cnt, prev_wdh_cnt;
+	u32			prev_donehead;
+	struct timer_list	io_watchdog;
 
-	/* Needed for ZF Micro quirk */
-	struct timer_list	unlink_watchdog;
-	unsigned		eds_scheduled;
-	struct ed		*ed_to_check;
-	unsigned		zf_delay;
+	struct work_struct	nec_work;	/* Worker for NEC quirk */
 
 	struct dentry		*debug_dir;
 	struct dentry		*debug_async;
Index: usb-3.15/drivers/usb/host/ohci-hcd.c
===================================================================
--- usb-3.15.orig/drivers/usb/host/ohci-hcd.c
+++ usb-3.15/drivers/usb/host/ohci-hcd.c
@@ -72,12 +72,14 @@
 static const char	hcd_name [] = "ohci_hcd";
 
 #define	STATECHANGE_DELAY	msecs_to_jiffies(300)
+#define	IO_WATCHDOG_DELAY	msecs_to_jiffies(250)
 
 #include "ohci.h"
 #include "pci-quirks.h"
 
-static void ohci_dump (struct ohci_hcd *ohci, int verbose);
-static void ohci_stop (struct usb_hcd *hcd);
+static void ohci_dump(struct ohci_hcd *ohci);
+static void ohci_stop(struct usb_hcd *hcd);
+static void io_watchdog_func(unsigned long _ohci);
 
 #include "ohci-hub.c"
 #include "ohci-dbg.c"
@@ -202,6 +204,16 @@ static int ohci_urb_enqueue (
 			usb_hcd_unlink_urb_from_ep(hcd, urb);
 			goto fail;
 		}
+
+		/* Start up the IO watchdog timer, if it's not running */
+		if (!timer_pending(&ohci->io_watchdog) &&
+				list_empty(&ohci->eds_in_use)) {
+			ohci->prev_frame_no = ohci_frame_no(ohci);
+			mod_timer(&ohci->io_watchdog,
+					jiffies + IO_WATCHDOG_DELAY);
+		}
+		list_add(&ed->in_use_list, &ohci->eds_in_use);
+
 		if (ed->type == PIPE_ISOCHRONOUS) {
 			u16	frame = ohci_frame_no(ohci);
 
@@ -277,30 +289,24 @@ static int ohci_urb_dequeue(struct usb_h
 	struct ohci_hcd		*ohci = hcd_to_ohci (hcd);
 	unsigned long		flags;
 	int			rc;
+	urb_priv_t		*urb_priv;
 
 	spin_lock_irqsave (&ohci->lock, flags);
 	rc = usb_hcd_check_unlink_urb(hcd, urb, status);
-	if (rc) {
-		;	/* Do nothing */
-	} else if (ohci->rh_state == OHCI_RH_RUNNING) {
-		urb_priv_t  *urb_priv;
+	if (rc == 0) {
 
 		/* Unless an IRQ completed the unlink while it was being
 		 * handed to us, flag it for unlink and giveback, and force
 		 * some upcoming INTR_SF to call finish_unlinks()
 		 */
 		urb_priv = urb->hcpriv;
-		if (urb_priv) {
-			if (urb_priv->ed->state == ED_OPER)
-				start_ed_unlink (ohci, urb_priv->ed);
+		if (urb_priv->ed->state == ED_OPER)
+			start_ed_unlink(ohci, urb_priv->ed);
+
+		if (ohci->rh_state != OHCI_RH_RUNNING) {
+			/* With HC dead, we can clean up right away */
+			ohci_work(ohci);
 		}
-	} else {
-		/*
-		 * with HC dead, we won't respect hc queue pointers
-		 * any more ... just clean up every urb's memory.
-		 */
-		if (urb->hcpriv)
-			finish_urb(ohci, urb, status);
 	}
 	spin_unlock_irqrestore (&ohci->lock, flags);
 	return rc;
@@ -332,9 +338,7 @@ rescan:
 	if (ohci->rh_state != OHCI_RH_RUNNING) {
 sanitize:
 		ed->state = ED_IDLE;
-		if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
-			ohci->eds_scheduled--;
-		finish_unlinks (ohci, 0);
+		ohci_work(ohci);
 	}
 
 	switch (ed->state) {
@@ -342,11 +346,6 @@ sanitize:
 		/* major IRQ delivery trouble loses INTR_SF too... */
 		if (limit-- == 0) {
 			ohci_warn(ohci, "ED unlink timeout\n");
-			if (quirk_zfmicro(ohci)) {
-				ohci_warn(ohci, "Attempting ZF TD recovery\n");
-				ohci->ed_to_check = ed;
-				ohci->zf_delay = 2;
-			}
 			goto sanitize;
 		}
 		spin_unlock_irqrestore (&ohci->lock, flags);
@@ -406,93 +405,7 @@ ohci_shutdown (struct usb_hcd *hcd)
 	udelay(10);
 
 	ohci_writel(ohci, ohci->fminterval, &ohci->regs->fminterval);
-}
-
-static int check_ed(struct ohci_hcd *ohci, struct ed *ed)
-{
-	return (hc32_to_cpu(ohci, ed->hwINFO) & ED_IN) != 0
-		&& (hc32_to_cpu(ohci, ed->hwHeadP) & TD_MASK)
-			== (hc32_to_cpu(ohci, ed->hwTailP) & TD_MASK)
-		&& !list_empty(&ed->td_list);
-}
-
-/* ZF Micro watchdog timer callback. The ZF Micro chipset sometimes completes
- * an interrupt TD but neglects to add it to the donelist.  On systems with
- * this chipset, we need to periodically check the state of the queues to look
- * for such "lost" TDs.
- */
-static void unlink_watchdog_func(unsigned long _ohci)
-{
-	unsigned long	flags;
-	unsigned	max;
-	unsigned	seen_count = 0;
-	unsigned	i;
-	struct ed	**seen = NULL;
-	struct ohci_hcd	*ohci = (struct ohci_hcd *) _ohci;
-
-	spin_lock_irqsave(&ohci->lock, flags);
-	max = ohci->eds_scheduled;
-	if (!max)
-		goto done;
-
-	if (ohci->ed_to_check)
-		goto out;
-
-	seen = kcalloc(max, sizeof *seen, GFP_ATOMIC);
-	if (!seen)
-		goto out;
-
-	for (i = 0; i < NUM_INTS; i++) {
-		struct ed	*ed = ohci->periodic[i];
-
-		while (ed) {
-			unsigned	temp;
-
-			/* scan this branch of the periodic schedule tree */
-			for (temp = 0; temp < seen_count; temp++) {
-				if (seen[temp] == ed) {
-					/* we've checked it and what's after */
-					ed = NULL;
-					break;
-				}
-			}
-			if (!ed)
-				break;
-			seen[seen_count++] = ed;
-			if (!check_ed(ohci, ed)) {
-				ed = ed->ed_next;
-				continue;
-			}
-
-			/* HC's TD list is empty, but HCD sees at least one
-			 * TD that's not been sent through the donelist.
-			 */
-			ohci->ed_to_check = ed;
-			ohci->zf_delay = 2;
-
-			/* The HC may wait until the next frame to report the
-			 * TD as done through the donelist and INTR_WDH.  (We
-			 * just *assume* it's not a multi-TD interrupt URB;
-			 * those could defer the IRQ more than one frame, using
-			 * DI...)  Check again after the next INTR_SF.
-			 */
-			ohci_writel(ohci, OHCI_INTR_SF,
-					&ohci->regs->intrstatus);
-			ohci_writel(ohci, OHCI_INTR_SF,
-					&ohci->regs->intrenable);
-
-			/* flush those writes */
-			(void) ohci_readl(ohci, &ohci->regs->control);
-
-			goto out;
-		}
-	}
-out:
-	kfree(seen);
-	if (ohci->eds_scheduled)
-		mod_timer(&ohci->unlink_watchdog, round_jiffies(jiffies + HZ));
-done:
-	spin_unlock_irqrestore(&ohci->lock, flags);
+	ohci->rh_state = OHCI_RH_HALTED;
 }
 
 /*-------------------------------------------------------------------------*
@@ -558,6 +471,10 @@ static int ohci_init (struct ohci_hcd *o
 	if (ohci->hcca)
 		return 0;
 
+	setup_timer(&ohci->io_watchdog, io_watchdog_func,
+			(unsigned long) ohci);
+	set_timer_slack(&ohci->io_watchdog, msecs_to_jiffies(20));
+
 	ohci->hcca = dma_alloc_coherent (hcd->self.controller,
 			sizeof *ohci->hcca, &ohci->hcca_dma, 0);
 	if (!ohci->hcca)
@@ -735,16 +652,7 @@ retry:
 	// POTPGT delay is bits 24-31, in 2 ms units.
 	mdelay ((val >> 23) & 0x1fe);
 
-	if (quirk_zfmicro(ohci)) {
-		/* Create timer to watch for bad queue state on ZF Micro */
-		setup_timer(&ohci->unlink_watchdog, unlink_watchdog_func,
-				(unsigned long) ohci);
-
-		ohci->eds_scheduled = 0;
-		ohci->ed_to_check = NULL;
-	}
-
-	ohci_dump (ohci, 1);
+	ohci_dump(ohci);
 
 	return 0;
 }
@@ -777,6 +685,142 @@ static int ohci_start(struct usb_hcd *hc
 
 /*-------------------------------------------------------------------------*/
 
+/*
+ * Some OHCI controllers are known to lose track of completed TDs.  They
+ * don't add the TDs to the hardware done list, which means we never see
+ * them as being completed.
+ *
+ * This watchdog routine checks for such problems.  Without some way to
+ * tell when those TDs have completed, we would never take their EDs off
+ * the unlink list.  As a result, URBs could never be dequeued and
+ * endpoints could never be released.
+ */
+static void io_watchdog_func(unsigned long _ohci)
+{
+	struct ohci_hcd	*ohci = (struct ohci_hcd *) _ohci;
+	bool		takeback_all_pending = false;
+	u32		status;
+	u32		head;
+	struct ed	*ed;
+	struct td	*td, *td_start, *td_next;
+	unsigned	frame_no;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&ohci->lock, flags);
+
+	/*
+	 * One way to lose track of completed TDs is if the controller
+	 * never writes back the done list head.  If it hasn't been
+	 * written back since the last time this function ran and if it
+	 * was non-empty at that time, something is badly wrong with the
+	 * hardware.
+	 */
+	status = ohci_readl(ohci, &ohci->regs->intrstatus);
+	if (!(status & OHCI_INTR_WDH) && ohci->wdh_cnt == ohci->prev_wdh_cnt) {
+		if (ohci->prev_donehead) {
+			ohci_err(ohci, "HcDoneHead not written back; disabled\n");
+ died:
+			usb_hc_died(ohci_to_hcd(ohci));
+			ohci_dump(ohci);
+			ohci_shutdown(ohci_to_hcd(ohci));
+			goto done;
+		} else {
+			/* No write back because the done list was empty */
+			takeback_all_pending = true;
+		}
+	}
+
+	/* Check every ED which might have pending TDs */
+	list_for_each_entry(ed, &ohci->eds_in_use, in_use_list) {
+		if (ed->pending_td) {
+			if (takeback_all_pending ||
+					OKAY_TO_TAKEBACK(ohci, ed)) {
+				unsigned tmp = hc32_to_cpu(ohci, ed->hwINFO);
+
+				ohci_dbg(ohci, "takeback pending TD for dev %d ep 0x%x\n",
+						0x007f & tmp,
+						(0x000f & (tmp >> 7)) +
+							((tmp & ED_IN) >> 5));
+				add_to_done_list(ohci, ed->pending_td);
+			}
+		}
+
+		/* Starting from the latest pending TD, */
+		td = ed->pending_td;
+
+		/* or the last TD on the done list, */
+		if (!td) {
+			list_for_each_entry(td_next, &ed->td_list, td_list) {
+				if (!td_next->next_dl_td)
+					break;
+				td = td_next;
+			}
+		}
+
+		/* find the last TD processed by the controller. */
+		head = hc32_to_cpu(ohci, ACCESS_ONCE(ed->hwHeadP)) & TD_MASK;
+		td_start = td;
+		td_next = list_prepare_entry(td, &ed->td_list, td_list);
+		list_for_each_entry_continue(td_next, &ed->td_list, td_list) {
+			if (head == (u32) td_next->td_dma)
+				break;
+			td = td_next;	/* head pointer has passed this TD */
+		}
+		if (td != td_start) {
+			/*
+			 * In case a WDH cycle is in progress, we will wait
+			 * for the next two cycles to complete before assuming
+			 * this TD will never get on the done list.
+			 */
+			ed->takeback_wdh_cnt = ohci->wdh_cnt + 2;
+			ed->pending_td = td;
+		}
+	}
+
+	ohci_work(ohci);
+
+	if (ohci->rh_state == OHCI_RH_RUNNING) {
+
+		/*
+		 * Sometimes a controller just stops working.  We can tell
+		 * by checking that the frame counter has advanced since
+		 * the last time we ran.
+		 *
+		 * But be careful: Some controllers violate the spec by
+		 * stopping their frame counter when no ports are active.
+		 */
+		frame_no = ohci_frame_no(ohci);
+		if (frame_no == ohci->prev_frame_no) {
+			int		active_cnt = 0;
+			int		i;
+			unsigned	tmp;
+
+			for (i = 0; i < ohci->num_ports; ++i) {
+				tmp = roothub_portstatus(ohci, i);
+				/* Enabled and not suspended? */
+				if ((tmp & RH_PS_PES) && !(tmp & RH_PS_PSS))
+					++active_cnt;
+			}
+
+			if (active_cnt > 0) {
+				ohci_err(ohci, "frame counter not updating; disabled\n");
+				goto died;
+			}
+		}
+		if (!list_empty(&ohci->eds_in_use)) {
+			ohci->prev_frame_no = frame_no;
+			ohci->prev_wdh_cnt = ohci->wdh_cnt;
+			ohci->prev_donehead = ohci_readl(ohci,
+					&ohci->regs->donehead);
+			mod_timer(&ohci->io_watchdog,
+					jiffies + IO_WATCHDOG_DELAY);
+		}
+	}
+
+ done:
+	spin_unlock_irqrestore(&ohci->lock, flags);
+}
+
 /* an interrupt happens */
 
 static irqreturn_t ohci_irq (struct usb_hcd *hcd)
@@ -825,7 +869,7 @@ static irqreturn_t ohci_irq (struct usb_
 			usb_hc_died(hcd);
 		}
 
-		ohci_dump (ohci, 1);
+		ohci_dump(ohci);
 		ohci_usb_reset (ohci);
 	}
 
@@ -863,58 +907,30 @@ static irqreturn_t ohci_irq (struct usb_
 			usb_hcd_resume_root_hub(hcd);
 	}
 
-	if (ints & OHCI_INTR_WDH) {
-		spin_lock (&ohci->lock);
-		dl_done_list (ohci);
-		spin_unlock (&ohci->lock);
-	}
-
-	if (quirk_zfmicro(ohci) && (ints & OHCI_INTR_SF)) {
-		spin_lock(&ohci->lock);
-		if (ohci->ed_to_check) {
-			struct ed *ed = ohci->ed_to_check;
-
-			if (check_ed(ohci, ed)) {
-				/* HC thinks the TD list is empty; HCD knows
-				 * at least one TD is outstanding
-				 */
-				if (--ohci->zf_delay == 0) {
-					struct td *td = list_entry(
-						ed->td_list.next,
-						struct td, td_list);
-					ohci_warn(ohci,
-						  "Reclaiming orphan TD %p\n",
-						  td);
-					takeback_td(ohci, td);
-					ohci->ed_to_check = NULL;
-				}
-			} else
-				ohci->ed_to_check = NULL;
-		}
-		spin_unlock(&ohci->lock);
-	}
+	spin_lock(&ohci->lock);
+	if (ints & OHCI_INTR_WDH)
+		update_done_list(ohci);
 
 	/* could track INTR_SO to reduce available PCI/... bandwidth */
 
 	/* handle any pending URB/ED unlinks, leaving INTR_SF enabled
 	 * when there's still unlinking to be done (next frame).
 	 */
-	spin_lock (&ohci->lock);
-	if (ohci->ed_rm_list)
-		finish_unlinks (ohci, ohci_frame_no(ohci));
-	if ((ints & OHCI_INTR_SF) != 0
-			&& !ohci->ed_rm_list
-			&& !ohci->ed_to_check
+	ohci_work(ohci);
+	if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list
 			&& ohci->rh_state == OHCI_RH_RUNNING)
 		ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable);
-	spin_unlock (&ohci->lock);
 
 	if (ohci->rh_state == OHCI_RH_RUNNING) {
 		ohci_writel (ohci, ints, &regs->intrstatus);
+		if (ints & OHCI_INTR_WDH)
+			++ohci->wdh_cnt;
+
 		ohci_writel (ohci, OHCI_INTR_MIE, &regs->intrenable);
 		// flush those writes
 		(void) ohci_readl (ohci, &ohci->regs->control);
 	}
+	spin_unlock(&ohci->lock);
 
 	return IRQ_HANDLED;
 }
@@ -925,18 +941,17 @@ static void ohci_stop (struct usb_hcd *h
 {
 	struct ohci_hcd		*ohci = hcd_to_ohci (hcd);
 
-	ohci_dump (ohci, 1);
+	ohci_dump(ohci);
 
 	if (quirk_nec(ohci))
 		flush_work(&ohci->nec_work);
+	del_timer_sync(&ohci->io_watchdog);
 
 	ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
 	ohci_usb_reset(ohci);
 	free_irq(hcd->irq, hcd);
 	hcd->irq = 0;
 
-	if (quirk_zfmicro(ohci))
-		del_timer(&ohci->unlink_watchdog);
 	if (quirk_amdiso(ohci))
 		usb_amd_dev_put();
 
@@ -993,7 +1008,7 @@ int ohci_restart(struct ohci_hcd *ohci)
 		if (!urb->unlinked)
 			urb->unlinked = -ESHUTDOWN;
 	}
-	finish_unlinks (ohci, 0);
+	ohci_work(ohci);
 	spin_unlock_irq(&ohci->lock);
 
 	/* paranoia, in case that didn't work: */
Index: usb-3.15/drivers/usb/host/ohci-q.c
===================================================================
--- usb-3.15.orig/drivers/usb/host/ohci-q.c
+++ usb-3.15/drivers/usb/host/ohci-q.c
@@ -187,10 +187,6 @@ static int ed_schedule (struct ohci_hcd
 	ed->ed_prev = NULL;
 	ed->ed_next = NULL;
 	ed->hwNextED = 0;
-	if (quirk_zfmicro(ohci)
-			&& (ed->type == PIPE_INTERRUPT)
-			&& !(ohci->eds_scheduled++))
-		mod_timer(&ohci->unlink_watchdog, round_jiffies(jiffies + HZ));
 	wmb ();
 
 	/* we care about rm_list when setting CLE/BLE in case the HC was at
@@ -869,13 +865,46 @@ static void ed_halted(struct ohci_hcd *o
 	}
 }
 
-/* replies to the request have to be on a FIFO basis so
- * we unreverse the hc-reversed done-list
- */
-static struct td *dl_reverse_done_list (struct ohci_hcd *ohci)
+/* Add a TD to the done list */
+static void add_to_done_list(struct ohci_hcd *ohci, struct td *td)
+{
+	struct td	*td2, *td_prev;
+	struct ed	*ed;
+
+	if (td->next_dl_td)
+		return;		/* Already on the list */
+
+	/* Add all the TDs going back until we reach one that's on the list */
+	ed = td->ed;
+	td2 = td_prev = td;
+	list_for_each_entry_continue_reverse(td2, &ed->td_list, td_list) {
+		if (td2->next_dl_td)
+			break;
+		td2->next_dl_td = td_prev;
+		td_prev = td2;
+	}
+
+	if (ohci->dl_end)
+		ohci->dl_end->next_dl_td = td_prev;
+	else
+		ohci->dl_start = td_prev;
+
+	/*
+	 * Make td->next_dl_td point to td itself, to mark the fact
+	 * that td is on the done list.
+	 */
+	ohci->dl_end = td->next_dl_td = td;
+
+	/* Did we just add the latest pending TD? */
+	td2 = ed->pending_td;
+	if (td2 && td2->next_dl_td)
+		ed->pending_td = NULL;
+}
+
+/* Get the entries on the hardware done list and put them on our list */
+static void update_done_list(struct ohci_hcd *ohci)
 {
 	u32		td_dma;
-	struct td	*td_rev = NULL;
 	struct td	*td = NULL;
 
 	td_dma = hc32_to_cpup (ohci, &ohci->hcca->done_head);
@@ -883,7 +912,7 @@ static struct td *dl_reverse_done_list (
 	wmb();
 
 	/* get TD from hc's singly linked list, and
-	 * prepend to ours.  ed->td_list changes later.
+	 * add to ours.  ed->td_list changes later.
 	 */
 	while (td_dma) {
 		int		cc;
@@ -905,19 +934,17 @@ static struct td *dl_reverse_done_list (
 				&& (td->ed->hwHeadP & cpu_to_hc32 (ohci, ED_H)))
 			ed_halted(ohci, td, cc);
 
-		td->next_dl_td = td_rev;
-		td_rev = td;
 		td_dma = hc32_to_cpup (ohci, &td->hwNextTD);
+		add_to_done_list(ohci, td);
 	}
-	return td_rev;
 }
 
 /*-------------------------------------------------------------------------*/
 
 /* there are some urbs/eds to unlink; called in_irq(), with HCD locked */
-static void
-finish_unlinks (struct ohci_hcd *ohci, u16 tick)
+static void finish_unlinks(struct ohci_hcd *ohci)
 {
+	unsigned	tick = ohci_frame_no(ohci);
 	struct ed	*ed, **last;
 
 rescan_all:
@@ -929,30 +956,27 @@ rescan_all:
 		/* only take off EDs that the HC isn't using, accounting for
 		 * frame counter wraps and EDs with partially retired TDs
 		 */
-		if (likely(ohci->rh_state == OHCI_RH_RUNNING)) {
-			if (tick_before (tick, ed->tick)) {
+		if (likely(ohci->rh_state == OHCI_RH_RUNNING) &&
+				tick_before(tick, ed->tick)) {
 skip_ed:
-				last = &ed->ed_next;
-				continue;
-			}
+			last = &ed->ed_next;
+			continue;
+		}
+		if (!list_empty(&ed->td_list)) {
+			struct td	*td;
+			u32		head;
 
-			if (!list_empty (&ed->td_list)) {
-				struct td	*td;
-				u32		head;
-
-				td = list_entry (ed->td_list.next, struct td,
-							td_list);
-				head = hc32_to_cpu (ohci, ed->hwHeadP) &
-								TD_MASK;
-
-				/* INTR_WDH may need to clean up first */
-				if (td->td_dma != head) {
-					if (ed == ohci->ed_to_check)
-						ohci->ed_to_check = NULL;
-					else
-						goto skip_ed;
-				}
-			}
+			td = list_first_entry(&ed->td_list, struct td, td_list);
+
+			/* INTR_WDH may need to clean up first */
+			head = hc32_to_cpu(ohci, ed->hwHeadP) & TD_MASK;
+			if (td->td_dma != head &&
+					ohci->rh_state == OHCI_RH_RUNNING)
+				goto skip_ed;
+
+			/* Don't mess up anything already on the done list */
+			if (td->next_dl_td)
+				goto skip_ed;
 		}
 
 		/* reentrancy:  if we drop the schedule lock, someone might
@@ -1020,18 +1044,16 @@ rescan_this:
 
 		/* ED's now officially unlinked, hc doesn't see */
 		ed->state = ED_IDLE;
-		if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
-			ohci->eds_scheduled--;
 		ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
 		ed->hwNextED = 0;
 		wmb ();
 		ed->hwINFO &= ~cpu_to_hc32 (ohci, ED_SKIP | ED_DEQUEUE);
 
 		/* but if there's work queued, reschedule */
-		if (!list_empty (&ed->td_list)) {
-			if (ohci->rh_state == OHCI_RH_RUNNING)
-				ed_schedule (ohci, ed);
-		}
+		if (list_empty(&ed->td_list))
+			list_del(&ed->in_use_list);
+		else if (ohci->rh_state == OHCI_RH_RUNNING)
+			ed_schedule(ohci, ed);
 
 		if (modified)
 			goto rescan_all;
@@ -1082,12 +1104,7 @@ rescan_this:
 
 /*-------------------------------------------------------------------------*/
 
-/*
- * Used to take back a TD from the host controller. This would normally be
- * called from within dl_done_list, however it may be called directly if the
- * HC no longer sees the TD and it has not appeared on the donelist (after
- * two frames).  This bug has been observed on ZF Micro systems.
- */
+/* Take back a TD from the host controller */
 static void takeback_td(struct ohci_hcd *ohci, struct td *td)
 {
 	struct urb	*urb = td->urb;
@@ -1134,37 +1151,43 @@ static void takeback_td(struct ohci_hcd
  *
  * This is the main path for handing urbs back to drivers.  The only other
  * normal path is finish_unlinks(), which unlinks URBs using ed_rm_list,
- * instead of scanning the (re-reversed) donelist as this does.  There's
- * an abnormal path too, handling a quirk in some Compaq silicon:  URBs
- * with TDs that appear to be orphaned are directly reclaimed.
+ * instead of scanning the (re-reversed) donelist as this does.
  */
-static void
-dl_done_list (struct ohci_hcd *ohci)
+static void process_done_list(struct ohci_hcd *ohci)
 {
-	struct td	*td = dl_reverse_done_list (ohci);
+	struct td	*td;
 
-	while (td) {
-		struct td	*td_next = td->next_dl_td;
-		struct ed	*ed = td->ed;
-
-		/*
-		 * Some OHCI controllers (NVIDIA for sure, maybe others)
-		 * occasionally forget to add TDs to the done queue.  Since
-		 * TDs for a given endpoint are always processed in order,
-		 * if we find a TD on the donelist then all of its
-		 * predecessors must be finished as well.
-		 */
-		for (;;) {
-			struct td	*td2;
-
-			td2 = list_first_entry(&ed->td_list, struct td,
-					td_list);
-			if (td2 == td)
-				break;
-			takeback_td(ohci, td2);
-		}
+	while (ohci->dl_start) {
+		td = ohci->dl_start;
+		if (td == ohci->dl_end)
+			ohci->dl_start = ohci->dl_end = NULL;
+		else
+			ohci->dl_start = td->next_dl_td;
 
 		takeback_td(ohci, td);
-		td = td_next;
 	}
 }
+
+/*
+ * TD takeback and URB giveback must be single-threaded.
+ * This routine takes care of it all.
+ */
+static void ohci_work(struct ohci_hcd *ohci)
+{
+	if (ohci->working) {
+		ohci->restart_work = 1;
+		return;
+	}
+	ohci->working = 1;
+
+ restart:
+	process_done_list(ohci);
+	if (ohci->ed_rm_list)
+		finish_unlinks(ohci);
+
+	if (ohci->restart_work) {
+		ohci->restart_work = 0;
+		goto restart;
+	}
+	ohci->working = 0;
+}
Index: usb-3.15/drivers/usb/host/ohci-hub.c
===================================================================
--- usb-3.15.orig/drivers/usb/host/ohci-hub.c
+++ usb-3.15/drivers/usb/host/ohci-hub.c
@@ -39,8 +39,8 @@
 #define OHCI_SCHED_ENABLES \
 	(OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
 
-static void dl_done_list (struct ohci_hcd *);
-static void finish_unlinks (struct ohci_hcd *, u16);
+static void update_done_list(struct ohci_hcd *);
+static void ohci_work(struct ohci_hcd *);
 
 #ifdef	CONFIG_PM
 static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop)
@@ -87,8 +87,8 @@ __acquires(ohci->lock)
 		msleep (8);
 		spin_lock_irq (&ohci->lock);
 	}
-	dl_done_list (ohci);
-	finish_unlinks (ohci, ohci_frame_no(ohci));
+	update_done_list(ohci);
+	ohci_work(ohci);
 
 	/* maybe resume can wake root hub */
 	if (ohci_to_hcd(ohci)->self.root_hub->do_remote_wakeup || autostop) {
@@ -291,6 +291,9 @@ static int ohci_bus_suspend (struct usb_
 	else
 		rc = ohci_rh_suspend (ohci, 0);
 	spin_unlock_irq (&ohci->lock);
+
+	if (rc == 0)
+		del_timer_sync(&ohci->io_watchdog);
 	return rc;
 }
 
Index: usb-3.15/drivers/usb/host/ohci-dbg.c
===================================================================
--- usb-3.15.orig/drivers/usb/host/ohci-dbg.c
+++ usb-3.15/drivers/usb/host/ohci-dbg.c
@@ -236,7 +236,7 @@ ohci_dump_roothub (
 	}
 }
 
-static void ohci_dump (struct ohci_hcd *controller, int verbose)
+static void ohci_dump(struct ohci_hcd *controller)
 {
 	ohci_dbg (controller, "OHCI controller state\n");
 
@@ -468,6 +468,7 @@ static ssize_t fill_async_buffer(struct
 	unsigned long		flags;
 
 	ohci = buf->ohci;
+	buf->count = PAGE_SIZE;
 
 	/* display control and bulk lists together, for simplicity */
 	spin_lock_irqsave (&ohci->lock, flags);
Index: usb-3.15/drivers/usb/host/ohci-mem.c
===================================================================
--- usb-3.15.orig/drivers/usb/host/ohci-mem.c
+++ usb-3.15/drivers/usb/host/ohci-mem.c
@@ -28,6 +28,7 @@ static void ohci_hcd_init (struct ohci_h
 	ohci->next_statechange = jiffies;
 	spin_lock_init (&ohci->lock);
 	INIT_LIST_HEAD (&ohci->pending);
+	INIT_LIST_HEAD(&ohci->eds_in_use);
 }
 
 /*-------------------------------------------------------------------------*/

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux