ChangeSet 1.1587.3.50, 2004/05/11 15:56:50-07:00, david-b@pacbell.net

[PATCH] USB: OHCI resume/reset stops deadlocking in PM code

System-wide PM resume now happily deadlocks if one of the
resuming devices tries to remove devices which vanished
during the suspend(*).  IMO that's unreasonable both
because devices can/do vanish, and because 2.4 didn't
deadlock in those cases; but no patch to fix that has been
merged.  The result is that ever since merging the "new" PM
code, some OHCI-based systems deadlock on resume.

So this patch handles the "lost power during resume" case
differently:  it doesn't disconnect the root hub (or its
children) directly.  Instead, it does part of that work
immediately, and defers the rest to khubd:

  - add a "pending" list for live urbs, and use it after reset
    to abort pending URBs (and reclaim "live" EDs/TDs)
  - immediately mark all devices NOTATTACHED, so any operations
    on the devices before khubd handles the disconnects, including
    resume() callbacks, will fail
  - kick root hub so it can do the cleanup

It also handles "fminterval" init/reinit a bit better, mostly
to work better in some remote wakeup scenarios addressed in
later patches:

   - save any initial value the boot firmware provided
   - use it during initialization (and eventually, remote wakeup)

Other changes:

  - use better jiffies calculation for scheduled delays
  - the allocator does more of the one-time initialization
  - initialize hcd.can_wakeup according to boot firmware
  - move some inlines to the header
  - minor cleanups


(*) http://marc.theaimsgroup.com/?l=linux-kernel&m=106606272103414&w=2
     reported against 2.6.0-test7.


 drivers/usb/host/ohci-hcd.c |  119 ++++++++++++++++++++++++++++++++------------
 drivers/usb/host/ohci-mem.c |    2 
 drivers/usb/host/ohci-q.c   |    4 +
 drivers/usb/host/ohci.h     |   33 +++++++++++-
 4 files changed, 125 insertions(+), 33 deletions(-)


diff -Nru a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
--- a/drivers/usb/host/ohci-hcd.c	Fri May 14 15:28:42 2004
+++ b/drivers/usb/host/ohci-hcd.c	Fri May 14 15:28:42 2004
@@ -118,19 +118,12 @@
 /* For initializing controller (mask in an HCFS mode too) */
 #define	OHCI_CONTROL_INIT 	OHCI_CTRL_CBSR
 
-#define OHCI_UNLINK_TIMEOUT	 (HZ / 10)
-
 /*-------------------------------------------------------------------------*/
 
 static const char	hcd_name [] = "ohci_hcd";
 
 #include "ohci.h"
 
-static inline void disable (struct ohci_hcd *ohci)
-{
-	ohci->hcd.state = USB_STATE_HALT;
-}
-
 #include "ohci-hub.c"
 #include "ohci-dbg.c"
 #include "ohci-mem.c"
@@ -206,8 +199,7 @@
 	if (!urb_priv)
 		return -ENOMEM;
 	memset (urb_priv, 0, sizeof (urb_priv_t) + size * sizeof (struct td *));
-	
-	/* fill the private part of the URB */
+	INIT_LIST_HEAD (&urb_priv->pending);
 	urb_priv->length = size;
 	urb_priv->ed = ed;	
 
@@ -397,6 +389,16 @@
 {
 	u32 temp;
 
+	/* boot firmware should have set this up (5.1.1.3.1) */
+	if (!ohci->fminterval) {
+		temp = readl (&ohci->regs->fminterval);
+		if (temp & 0x3fff0000)
+			ohci->fminterval = temp;
+		else
+			ohci->fminterval = DEFAULT_FMINTERVAL;
+		/* also: power/overcurrent flags in roothub.a */
+	}
+
 	/* SMM owns the HC?  not for long!
 	 * On PA-RISC, PDC can leave IR set incorrectly; ignore it there.
 	 */
@@ -413,7 +415,7 @@
 		writel (OHCI_INTR_OC, &ohci->regs->intrenable);
 		writel (OHCI_OCR, &ohci->regs->cmdstatus);
 		while (readl (&ohci->regs->control) & OHCI_CTRL_IR) {
-			wait_ms (10);
+			msec_delay (10);
 			if (--temp == 0) {
 				ohci_err (ohci, "USB HC TakeOver failed!\n");
 				return -1;
@@ -430,9 +432,12 @@
 
   	/* Reset USB (needed by some controllers); RemoteWakeupConnected
 	 * saved if boot firmware (BIOS/SMM/...) told us it's connected
+	 * (for OHCI integrated on mainboard, it normally is)
 	 */
 	ohci->hc_control = readl (&ohci->regs->control);
 	ohci->hc_control &= OHCI_CTRL_RWC;	/* hcfs 0 = RESET */
+	if (ohci->hc_control)
+		ohci->hcd.can_wakeup = 1;
 	writel (ohci->hc_control, &ohci->regs->control);
 	if (power_switching) {
 		unsigned ports = roothub_a (ohci) & RH_A_NDP; 
@@ -444,7 +449,7 @@
 	}
 	// flush those pci writes
 	(void) readl (&ohci->regs->control);
-	wait_ms (50);
+	msec_delay (50);
 
 	/* HC Reset requires max 10 us delay */
 	writel (OHCI_HCR,  &ohci->regs->cmdstatus);
@@ -473,9 +478,6 @@
 
 /*-------------------------------------------------------------------------*/
 
-#define	FI		0x2edf		/* 12000 bits per frame (-1) */
-#define LSTHRESH	0x628		/* lowspeed bit threshold */
-
 /* Start an OHCI controller, set the BUS operational
  * enable interrupts 
  * connect the virtual root hub
@@ -486,7 +488,6 @@
   	struct usb_device	*udev;
   	struct usb_bus		*bus;
 
-	spin_lock_init (&ohci->lock);
 	disable (ohci);
 
 	/* Tell the controller where the control and bulk lists are
@@ -497,12 +498,7 @@
 	/* a reset clears this */
 	writel ((u32) ohci->hcca_dma, &ohci->regs->hcca);
 
-	/* force default fmInterval (we won't adjust it); init thresholds
-	 * for last FS and LS packets, reserve 90% for periodic.
-	 */
-	writel ((((6 * (FI - 210)) / 7) << 16) | FI, &ohci->regs->fminterval);
-	writel (((9 * FI) / 10) & 0x3fff, &ohci->regs->periodicstart);
-	writel (LSTHRESH, &ohci->regs->lsthresh);
+	periodic_reinit (ohci);
 
 	/* some OHCI implementations are finicky about how they init.
 	 * bogus values here mean not even enumeration could work.
@@ -551,9 +547,14 @@
 
 	// POTPGT delay is bits 24-31, in 2 ms units.
 	mdelay ((roothub_a (ohci) >> 23) & 0x1fe);
+	bus = hcd_to_bus (&ohci->hcd);
+
+	if (bus->root_hub) {
+		ohci->hcd.state = USB_STATE_RUNNING;
+		return 0;
+	}
  
 	/* connect the virtual root hub */
-	bus = hcd_to_bus (&ohci->hcd);
 	bus->root_hub = udev = usb_alloc_dev (NULL, bus, 0);
 	ohci->hcd.state = USB_STATE_RUNNING;
 	if (!udev) {
@@ -670,22 +671,68 @@
 
 /*-------------------------------------------------------------------------*/
 
-// FIXME:  this restart logic should be generic,
-// and handle full hcd state cleanup
-
-/* controller died; cleanup debris, then restart */
 /* must not be called from interrupt context */
 
 #ifdef CONFIG_PM
+
+static void mark_children_gone (struct usb_device *dev)
+{
+	unsigned i;
+
+	for (i = 0; i < dev->maxchild; i++) {
+		if (dev->children [i] == 0)
+			continue;
+		dev->children [i]->state = USB_STATE_NOTATTACHED;
+		mark_children_gone (dev->children [i]);
+	}
+}
+
 static int hc_restart (struct ohci_hcd *ohci)
 {
 	int temp;
 	int i;
+	struct urb_priv *priv;
 
+	/* mark any devices gone, so they do nothing till khubd disconnects.
+	 * recycle any "live" eds/tds (and urbs) right away.
+	 * later, khubd disconnect processing will recycle the other state,
+	 * (either as disconnect/reconnect, or maybe someday as a reset).
+	 */ 
+	spin_lock_irq(&ohci->lock);
 	disable (ohci);
-	if (hcd_to_bus (&ohci->hcd)->root_hub)
-		usb_disconnect (&hcd_to_bus (&ohci->hcd)->root_hub);
-	
+	mark_children_gone (ohci->hcd.self.root_hub);
+	if (!list_empty (&ohci->pending))
+		ohci_dbg(ohci, "abort schedule...\n");
+	list_for_each_entry (priv, &ohci->pending, pending) {
+		struct urb	*urb = priv->td[0]->urb;
+		struct ed	*ed = priv->ed;
+
+		switch (ed->state) {
+		case ED_OPER:
+			ed->state = ED_UNLINK;
+			ed->hwINFO |= ED_DEQUEUE;
+			ed_deschedule (ohci, ed);
+
+			ed->ed_next = ohci->ed_rm_list;
+			ed->ed_prev = 0;
+			ohci->ed_rm_list = ed;
+			/* FALLTHROUGH */
+		case ED_UNLINK:
+			break;
+		default:
+			ohci_dbg(ohci, "bogus ed %p state %d\n",
+					ed, ed->state);
+		}
+
+		spin_lock (&urb->lock);
+		urb->status = -ESHUTDOWN;
+		spin_unlock (&urb->lock);
+	}
+	finish_unlinks (ohci, 0, 0);
+	spin_unlock_irq(&ohci->lock);
+
+	/* paranoia, in case that didn't work: */
+
 	/* empty the interrupt branches */
 	for (i = 0; i < NUM_INTS; i++) ohci->load [i] = 0;
 	for (i = 0; i < NUM_INTS; i++) ohci->hcca->int_table [i] = 0;
@@ -700,8 +747,20 @@
 	if ((temp = hc_reset (ohci)) < 0 || (temp = hc_start (ohci)) < 0) {
 		ohci_err (ohci, "can't restart, %d\n", temp);
 		return temp;
-	} else
+	} else {
+		/* here we "know" root ports should always stay powered,
+		 * and that if we try to turn them back on the root hub
+		 * will respond to CSC processing.
+		 */
+		i = roothub_a (ohci) & RH_A_NDP;
+		while (i--)
+			writel (RH_PS_PSS,
+				&ohci->regs->roothub.portstatus [temp]);
+		ohci->hcd.self.root_hub->dev.power.power_state = 0;
+		ohci->hcd.state = USB_STATE_RUNNING;
 		ohci_dbg (ohci, "restart complete\n");
+		ohci_dump (ohci, 1);
+	}
 	return 0;
 }
 #endif
diff -Nru a/drivers/usb/host/ohci-mem.c b/drivers/usb/host/ohci-mem.c
--- a/drivers/usb/host/ohci-mem.c	Fri May 14 15:28:42 2004
+++ b/drivers/usb/host/ohci-mem.c	Fri May 14 15:28:42 2004
@@ -31,6 +31,8 @@
 	if (ohci != 0) {
 		memset (ohci, 0, sizeof (struct ohci_hcd));
 		ohci->hcd.product_desc = "OHCI Host Controller";
+		spin_lock_init (&ohci->lock);
+		INIT_LIST_HEAD (&ohci->pending);
 		return &ohci->hcd;
 	}
 	return 0;
diff -Nru a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
--- a/drivers/usb/host/ohci-q.c	Fri May 14 15:28:42 2004
+++ b/drivers/usb/host/ohci-q.c	Fri May 14 15:28:42 2004
@@ -22,6 +22,7 @@
 		}
 	}
 
+	list_del (&urb_priv->pending);
 	kfree (urb_priv);
 }
 
@@ -419,7 +420,7 @@
 	}
 
 	/* NOTE: only ep0 currently needs this "re"init logic, during
-	 * enumeration (after set_address, or if ep0 maxpacket >8).
+	 * enumeration (after set_address).
 	 */
   	if (ed->state == ED_IDLE) {
 		u32	info;
@@ -593,6 +594,7 @@
 	}
 
 	urb_priv->td_cnt = 0;
+	list_add (&urb_priv->pending, &ohci->pending);
 
 	if (data_len)
 		data = urb->transfer_dma;
diff -Nru a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
--- a/drivers/usb/host/ohci.h	Fri May 14 15:28:42 2004
+++ b/drivers/usb/host/ohci.h	Fri May 14 15:28:42 2004
@@ -318,8 +318,9 @@
 /* hcd-private per-urb state */
 typedef struct urb_priv {
 	struct ed		*ed;
-	__u16			length;		// # tds in this request
-	__u16			td_cnt;		// tds already serviced
+	u16			length;		// # tds in this request
+	u16			td_cnt;		// tds already serviced
+	struct list_head	pending;
 	struct td		*td [0];	// all TDs in this request
 
 } urb_priv_t;
@@ -364,12 +365,14 @@
 	struct dma_pool		*td_cache;
 	struct dma_pool		*ed_cache;
 	struct td		*td_hash [TD_HASH_SIZE];
+	struct list_head	pending;
 
 	/*
 	 * driver state
 	 */
 	int			load [NUM_INTS];
 	u32 			hc_control;	/* copy of hc control reg */
+	u32			fminterval;		/* saved register */
 
 	unsigned long		flags;		/* for HC bugs */
 #define	OHCI_QUIRK_AMD756	0x01			/* erratum #4 */
@@ -383,6 +386,32 @@
 };
 
 #define hcd_to_ohci(hcd_ptr) container_of(hcd_ptr, struct ohci_hcd, hcd)
+
+/*-------------------------------------------------------------------------*/
+
+static inline void disable (struct ohci_hcd *ohci)
+{
+	ohci->hcd.state = USB_STATE_HALT;
+}
+
+#define	MSEC_TO_JIFFIES(msec) ((HZ * (msec) + 999) / 1000)
+
+static inline void msec_delay(int msec)
+{
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout(MSEC_TO_JIFFIES(msec));
+}
+
+#define	FI			0x2edf		/* 12000 bits per frame (-1) */
+#define	DEFAULT_FMINTERVAL 	((((6 * (FI - 210)) / 7) << 16) | FI)
+#define	LSTHRESH		0x628		/* lowspeed bit threshold */
+
+static inline void periodic_reinit (struct ohci_hcd *ohci)
+{
+	writel (ohci->fminterval, &ohci->regs->fminterval);
+	writel (((9 * FI) / 10) & 0x3fff, &ohci->regs->periodicstart);
+	writel (LSTHRESH, &ohci->regs->lsthresh);
+}
 
 /*-------------------------------------------------------------------------*/
 
