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); +} /*-------------------------------------------------------------------------*/