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
Please merge. I'll send a small hub patch too, to make khubd do its part.
- Dave
(*) http://marc.theaimsgroup.com/?l=linux-kernel&m=106606272103414&w=2 reported against 2.6.0-test7.
--- 1.58/drivers/usb/host/ohci-hcd.c Mon Mar 29 08:17:38 2004
+++ edited/drivers/usb/host/ohci-hcd.c Tue May 11 11:09:19 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
--- 1.18/drivers/usb/host/ohci-mem.c Wed Feb 11 03:42:39 2004
+++ edited/drivers/usb/host/ohci-mem.c Tue May 11 10:17:58 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;
--- 1.52/drivers/usb/host/ohci-q.c Mon Mar 29 08:17:38 2004
+++ edited/drivers/usb/host/ohci-q.c Tue May 11 10:19:12 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;
--- 1.19/drivers/usb/host/ohci.h Wed Feb 11 03:42:39 2004
+++ edited/drivers/usb/host/ohci.h Tue May 11 10:17:51 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);
+}
/*-------------------------------------------------------------------------*/
