Commit 89a0fd18 authored by Mike Nuss's avatar Mike Nuss Committed by Greg Kroah-Hartman

USB: OHCI handles more ZFMicro quirks

The ZF Micro OHCI controller exhibits unexpected behavior that seems to be
related to high load.  Under certain conditions, the controller will
complete a TD, remove it from the endpoint's queue, and fail to add it to
the donelist. This causes the endpoint to appear to stop responding. Worse,
if the device is removed while in that state, OHCI will hang while waiting
for the orphaned TD to complete.  The situation is not recoverable without
rebooting.

This fix enhances the scope of the existing OHCI_QUIRK_ZFMICRO flag:

 1. A watchdog routine periodically scans the OHCI structures to check
    for orphaned TDs. In these cases the TD is taken back from the
    controller and completed normally.

 2. If a device is removed while the endpoint is hung but before the
    watchdog catches the situation, any outstanding TDs are taken back
    from the controller in the 'sanitize' phase.

The ohci-hcd driver used to print "INTR_SF lossage" in this situation;
this changes it to the universally accurate "ED unlink timeout".  Other
instances of this message presumably have different root causes.

Both this Compaq quirk and a NEC quirk are now properly compiled out for
non-PCI builds of this driver.
Signed-off-by: default avatarMike Nuss <mike@terascala.com>
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent e8fa0ce6
...@@ -81,7 +81,6 @@ static void ohci_dump (struct ohci_hcd *ohci, int verbose); ...@@ -81,7 +81,6 @@ static void ohci_dump (struct ohci_hcd *ohci, int verbose);
static int ohci_init (struct ohci_hcd *ohci); static int ohci_init (struct ohci_hcd *ohci);
static void ohci_stop (struct usb_hcd *hcd); static void ohci_stop (struct usb_hcd *hcd);
static int ohci_restart (struct ohci_hcd *ohci); static int ohci_restart (struct ohci_hcd *ohci);
static void ohci_quirk_nec_worker (struct work_struct *work);
#include "ohci-hub.c" #include "ohci-hub.c"
#include "ohci-dbg.c" #include "ohci-dbg.c"
...@@ -314,6 +313,8 @@ ohci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) ...@@ -314,6 +313,8 @@ ohci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
if (!HC_IS_RUNNING (hcd->state)) { if (!HC_IS_RUNNING (hcd->state)) {
sanitize: sanitize:
ed->state = ED_IDLE; ed->state = ED_IDLE;
if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
ohci->eds_scheduled--;
finish_unlinks (ohci, 0); finish_unlinks (ohci, 0);
} }
...@@ -321,7 +322,12 @@ ohci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) ...@@ -321,7 +322,12 @@ ohci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
case ED_UNLINK: /* wait for hw to finish? */ case ED_UNLINK: /* wait for hw to finish? */
/* major IRQ delivery trouble loses INTR_SF too... */ /* major IRQ delivery trouble loses INTR_SF too... */
if (limit-- == 0) { if (limit-- == 0) {
ohci_warn (ohci, "IRQ INTR_SF lossage\n"); 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; goto sanitize;
} }
spin_unlock_irqrestore (&ohci->lock, flags); spin_unlock_irqrestore (&ohci->lock, flags);
...@@ -379,6 +385,93 @@ ohci_shutdown (struct usb_hcd *hcd) ...@@ -379,6 +385,93 @@ ohci_shutdown (struct usb_hcd *hcd)
(void) ohci_readl (ohci, &ohci->regs->control); (void) ohci_readl (ohci, &ohci->regs->control);
} }
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)
{
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_relative(HZ));
done:
spin_unlock_irqrestore(&ohci->lock, flags);
}
/*-------------------------------------------------------------------------* /*-------------------------------------------------------------------------*
* HC functions * HC functions
*-------------------------------------------------------------------------*/ *-------------------------------------------------------------------------*/
...@@ -616,6 +709,15 @@ static int ohci_run (struct ohci_hcd *ohci) ...@@ -616,6 +709,15 @@ static int ohci_run (struct ohci_hcd *ohci)
mdelay ((temp >> 23) & 0x1fe); mdelay ((temp >> 23) & 0x1fe);
hcd->state = HC_STATE_RUNNING; hcd->state = HC_STATE_RUNNING;
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, 1);
return 0; return 0;
...@@ -632,7 +734,8 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) ...@@ -632,7 +734,8 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
int ints; int ints;
/* we can eliminate a (slow) ohci_readl() /* we can eliminate a (slow) ohci_readl()
if _only_ WDH caused this irq */ * if _only_ WDH caused this irq
*/
if ((ohci->hcca->done_head != 0) if ((ohci->hcca->done_head != 0)
&& ! (hc32_to_cpup (ohci, &ohci->hcca->done_head) && ! (hc32_to_cpup (ohci, &ohci->hcca->done_head)
& 0x01)) { & 0x01)) {
...@@ -651,7 +754,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) ...@@ -651,7 +754,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
if (ints & OHCI_INTR_UE) { if (ints & OHCI_INTR_UE) {
// e.g. due to PCI Master/Target Abort // e.g. due to PCI Master/Target Abort
if (ohci->flags & OHCI_QUIRK_NEC) { if (quirk_nec(ohci)) {
/* Workaround for a silicon bug in some NEC chips used /* Workaround for a silicon bug in some NEC chips used
* in Apple's PowerBooks. Adapted from Darwin code. * in Apple's PowerBooks. Adapted from Darwin code.
*/ */
...@@ -713,6 +816,31 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) ...@@ -713,6 +816,31 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
ohci_writel (ohci, OHCI_INTR_WDH, &regs->intrenable); ohci_writel (ohci, OHCI_INTR_WDH, &regs->intrenable);
} }
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);
}
/* could track INTR_SO to reduce available PCI/... bandwidth */ /* could track INTR_SO to reduce available PCI/... bandwidth */
/* handle any pending URB/ED unlinks, leaving INTR_SF enabled /* handle any pending URB/ED unlinks, leaving INTR_SF enabled
...@@ -721,7 +849,9 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) ...@@ -721,7 +849,9 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
spin_lock (&ohci->lock); spin_lock (&ohci->lock);
if (ohci->ed_rm_list) if (ohci->ed_rm_list)
finish_unlinks (ohci, ohci_frame_no(ohci)); finish_unlinks (ohci, ohci_frame_no(ohci));
if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list if ((ints & OHCI_INTR_SF) != 0
&& !ohci->ed_rm_list
&& !ohci->ed_to_check
&& HC_IS_RUNNING(hcd->state)) && HC_IS_RUNNING(hcd->state))
ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable); ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable);
spin_unlock (&ohci->lock); spin_unlock (&ohci->lock);
...@@ -751,6 +881,9 @@ static void ohci_stop (struct usb_hcd *hcd) ...@@ -751,6 +881,9 @@ static void ohci_stop (struct usb_hcd *hcd)
free_irq(hcd->irq, hcd); free_irq(hcd->irq, hcd);
hcd->irq = -1; hcd->irq = -1;
if (quirk_zfmicro(ohci))
del_timer(&ohci->unlink_watchdog);
remove_debug_files (ohci); remove_debug_files (ohci);
ohci_mem_cleanup (ohci); ohci_mem_cleanup (ohci);
if (ohci->hcca) { if (ohci->hcca) {
...@@ -828,27 +961,6 @@ static int ohci_restart (struct ohci_hcd *ohci) ...@@ -828,27 +961,6 @@ static int ohci_restart (struct ohci_hcd *ohci)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* NEC workaround */
static void ohci_quirk_nec_worker(struct work_struct *work)
{
struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work);
int status;
status = ohci_init(ohci);
if (status != 0) {
ohci_err(ohci, "Restarting NEC controller failed "
"in ohci_init, %d\n", status);
return;
}
status = ohci_restart(ohci);
if (status != 0)
ohci_err(ohci, "Restarting NEC controller failed "
"in ohci_restart, %d\n", status);
}
/*-------------------------------------------------------------------------*/
#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC #define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC
MODULE_AUTHOR (DRIVER_AUTHOR); MODULE_AUTHOR (DRIVER_AUTHOR);
......
...@@ -28,7 +28,6 @@ static void ohci_hcd_init (struct ohci_hcd *ohci) ...@@ -28,7 +28,6 @@ static void ohci_hcd_init (struct ohci_hcd *ohci)
ohci->next_statechange = jiffies; ohci->next_statechange = jiffies;
spin_lock_init (&ohci->lock); spin_lock_init (&ohci->lock);
INIT_LIST_HEAD (&ohci->pending); INIT_LIST_HEAD (&ohci->pending);
INIT_WORK (&ohci->nec_work, ohci_quirk_nec_worker);
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -84,7 +84,7 @@ static int ohci_quirk_zfmicro(struct usb_hcd *hcd) ...@@ -84,7 +84,7 @@ static int ohci_quirk_zfmicro(struct usb_hcd *hcd)
struct ohci_hcd *ohci = hcd_to_ohci (hcd); struct ohci_hcd *ohci = hcd_to_ohci (hcd);
ohci->flags |= OHCI_QUIRK_ZFMICRO; ohci->flags |= OHCI_QUIRK_ZFMICRO;
ohci_dbg (ohci, "enabled Compaq ZFMicro chipset quirk\n"); ohci_dbg(ohci, "enabled Compaq ZFMicro chipset quirks\n");
return 0; return 0;
} }
...@@ -113,11 +113,31 @@ static int ohci_quirk_toshiba_scc(struct usb_hcd *hcd) ...@@ -113,11 +113,31 @@ static int ohci_quirk_toshiba_scc(struct usb_hcd *hcd)
/* Check for NEC chip and apply quirk for allegedly lost interrupts. /* Check for NEC chip and apply quirk for allegedly lost interrupts.
*/ */
static void ohci_quirk_nec_worker(struct work_struct *work)
{
struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work);
int status;
status = ohci_init(ohci);
if (status != 0) {
ohci_err(ohci, "Restarting NEC controller failed in %s, %d\n",
"ohci_init", status);
return;
}
status = ohci_restart(ohci);
if (status != 0)
ohci_err(ohci, "Restarting NEC controller failed in %s, %d\n",
"ohci_restart", status);
}
static int ohci_quirk_nec(struct usb_hcd *hcd) static int ohci_quirk_nec(struct usb_hcd *hcd)
{ {
struct ohci_hcd *ohci = hcd_to_ohci (hcd); struct ohci_hcd *ohci = hcd_to_ohci (hcd);
ohci->flags |= OHCI_QUIRK_NEC; ohci->flags |= OHCI_QUIRK_NEC;
INIT_WORK(&ohci->nec_work, ohci_quirk_nec_worker);
ohci_dbg (ohci, "enabled NEC chipset lost interrupt quirk\n"); ohci_dbg (ohci, "enabled NEC chipset lost interrupt quirk\n");
return 0; return 0;
......
...@@ -179,6 +179,10 @@ static int ed_schedule (struct ohci_hcd *ohci, struct ed *ed) ...@@ -179,6 +179,10 @@ static int ed_schedule (struct ohci_hcd *ohci, struct ed *ed)
ed->ed_prev = NULL; ed->ed_prev = NULL;
ed->ed_next = NULL; ed->ed_next = NULL;
ed->hwNextED = 0; ed->hwNextED = 0;
if (quirk_zfmicro(ohci)
&& (ed->type == PIPE_INTERRUPT)
&& !(ohci->eds_scheduled++))
mod_timer(&ohci->unlink_watchdog, round_jiffies_relative(HZ));
wmb (); wmb ();
/* we care about rm_list when setting CLE/BLE in case the HC was at /* we care about rm_list when setting CLE/BLE in case the HC was at
...@@ -940,10 +944,14 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick) ...@@ -940,10 +944,14 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
TD_MASK; TD_MASK;
/* INTR_WDH may need to clean up first */ /* INTR_WDH may need to clean up first */
if (td->td_dma != head) if (td->td_dma != head) {
if (ed == ohci->ed_to_check)
ohci->ed_to_check = NULL;
else
goto skip_ed; goto skip_ed;
} }
} }
}
/* reentrancy: if we drop the schedule lock, someone might /* reentrancy: if we drop the schedule lock, someone might
* have modified this list. normally it's just prepending * have modified this list. normally it's just prepending
...@@ -998,6 +1006,8 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick) ...@@ -998,6 +1006,8 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
/* ED's now officially unlinked, hc doesn't see */ /* ED's now officially unlinked, hc doesn't see */
ed->state = ED_IDLE; ed->state = ED_IDLE;
if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
ohci->eds_scheduled--;
ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H); ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
ed->hwNextED = 0; ed->hwNextED = 0;
wmb (); wmb ();
...@@ -1021,7 +1031,7 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick) ...@@ -1021,7 +1031,7 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
if (ohci->ed_controltail) { if (ohci->ed_controltail) {
command |= OHCI_CLF; command |= OHCI_CLF;
if (ohci->flags & OHCI_QUIRK_ZFMICRO) if (quirk_zfmicro(ohci))
mdelay(1); mdelay(1);
if (!(ohci->hc_control & OHCI_CTRL_CLE)) { if (!(ohci->hc_control & OHCI_CTRL_CLE)) {
control |= OHCI_CTRL_CLE; control |= OHCI_CTRL_CLE;
...@@ -1031,7 +1041,7 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick) ...@@ -1031,7 +1041,7 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
} }
if (ohci->ed_bulktail) { if (ohci->ed_bulktail) {
command |= OHCI_BLF; command |= OHCI_BLF;
if (ohci->flags & OHCI_QUIRK_ZFMICRO) if (quirk_zfmicro(ohci))
mdelay(1); mdelay(1);
if (!(ohci->hc_control & OHCI_CTRL_BLE)) { if (!(ohci->hc_control & OHCI_CTRL_BLE)) {
control |= OHCI_CTRL_BLE; control |= OHCI_CTRL_BLE;
...@@ -1043,13 +1053,13 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick) ...@@ -1043,13 +1053,13 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
/* CLE/BLE to enable, CLF/BLF to (maybe) kickstart */ /* CLE/BLE to enable, CLF/BLF to (maybe) kickstart */
if (control) { if (control) {
ohci->hc_control |= control; ohci->hc_control |= control;
if (ohci->flags & OHCI_QUIRK_ZFMICRO) if (quirk_zfmicro(ohci))
mdelay(1); mdelay(1);
ohci_writel (ohci, ohci->hc_control, ohci_writel (ohci, ohci->hc_control,
&ohci->regs->control); &ohci->regs->control);
} }
if (command) { if (command) {
if (ohci->flags & OHCI_QUIRK_ZFMICRO) if (quirk_zfmicro(ohci))
mdelay(1); mdelay(1);
ohci_writel (ohci, command, &ohci->regs->cmdstatus); ohci_writel (ohci, command, &ohci->regs->cmdstatus);
} }
...@@ -1061,57 +1071,68 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick) ...@@ -1061,57 +1071,68 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* /*
* Process normal completions (error or success) and clean the schedules. * 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
* This is the main path for handing urbs back to drivers. The only other * HC no longer sees the TD and it has not appeared on the donelist (after
* path is finish_unlinks(), which unlinks URBs using ed_rm_list, instead of * two frames). This bug has been observed on ZF Micro systems.
* scanning the (re-reversed) donelist as this does.
*/ */
static void static void takeback_td(struct ohci_hcd *ohci, struct td *td)
dl_done_list (struct ohci_hcd *ohci)
{ {
struct td *td = dl_reverse_done_list (ohci);
while (td) {
struct td *td_next = td->next_dl_td;
struct urb *urb = td->urb; struct urb *urb = td->urb;
urb_priv_t *urb_priv = urb->hcpriv; urb_priv_t *urb_priv = urb->hcpriv;
struct ed *ed = td->ed; struct ed *ed = td->ed;
/* update URB's length and status from TD */ /* update URB's length and status from TD */
td_done (ohci, urb, td); td_done(ohci, urb, td);
urb_priv->td_cnt++; urb_priv->td_cnt++;
/* If all this urb's TDs are done, call complete() */ /* If all this urb's TDs are done, call complete() */
if (urb_priv->td_cnt == urb_priv->length) if (urb_priv->td_cnt == urb_priv->length)
finish_urb (ohci, urb); finish_urb(ohci, urb);
/* clean schedule: unlink EDs that are no longer busy */ /* clean schedule: unlink EDs that are no longer busy */
if (list_empty (&ed->td_list)) { if (list_empty(&ed->td_list)) {
if (ed->state == ED_OPER) if (ed->state == ED_OPER)
start_ed_unlink (ohci, ed); start_ed_unlink(ohci, ed);
/* ... reenabling halted EDs only after fault cleanup */ /* ... reenabling halted EDs only after fault cleanup */
} else if ((ed->hwINFO & cpu_to_hc32 (ohci, } else if ((ed->hwINFO & cpu_to_hc32(ohci, ED_SKIP | ED_DEQUEUE))
ED_SKIP | ED_DEQUEUE)) == cpu_to_hc32(ohci, ED_SKIP)) {
== cpu_to_hc32 (ohci, ED_SKIP)) { td = list_entry(ed->td_list.next, struct td, td_list);
td = list_entry (ed->td_list.next, struct td, td_list); if (!(td->hwINFO & cpu_to_hc32(ohci, TD_DONE))) {
if (!(td->hwINFO & cpu_to_hc32 (ohci, TD_DONE))) { ed->hwINFO &= ~cpu_to_hc32(ohci, ED_SKIP);
ed->hwINFO &= ~cpu_to_hc32 (ohci, ED_SKIP);
/* ... hc may need waking-up */ /* ... hc may need waking-up */
switch (ed->type) { switch (ed->type) {
case PIPE_CONTROL: case PIPE_CONTROL:
ohci_writel (ohci, OHCI_CLF, ohci_writel(ohci, OHCI_CLF,
&ohci->regs->cmdstatus); &ohci->regs->cmdstatus);
break; break;
case PIPE_BULK: case PIPE_BULK:
ohci_writel (ohci, OHCI_BLF, ohci_writel(ohci, OHCI_BLF,
&ohci->regs->cmdstatus); &ohci->regs->cmdstatus);
break; break;
} }
} }
} }
}
/*
* Process normal completions (error or success) and clean the schedules.
*
* 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.
*/
static void
dl_done_list (struct ohci_hcd *ohci)
{
struct td *td = dl_reverse_done_list (ohci);
while (td) {
struct td *td_next = td->next_dl_td;
takeback_td(ohci, td);
td = td_next; td = td_next;
} }
} }
...@@ -401,8 +401,34 @@ struct ohci_hcd { ...@@ -401,8 +401,34 @@ struct ohci_hcd {
// there are also chip quirks/bugs in init logic // there are also chip quirks/bugs in init logic
struct work_struct nec_work; /* Worker for NEC quirk */ struct work_struct nec_work; /* Worker for NEC quirk */
/* Needed for ZF Micro quirk */
struct timer_list unlink_watchdog;
unsigned eds_scheduled;
struct ed *ed_to_check;
unsigned zf_delay;
}; };
#ifdef CONFIG_PCI
static inline int quirk_nec(struct ohci_hcd *ohci)
{
return ohci->flags & OHCI_QUIRK_NEC;
}
static inline int quirk_zfmicro(struct ohci_hcd *ohci)
{
return ohci->flags & OHCI_QUIRK_ZFMICRO;
}
#else
static inline int quirk_nec(struct ohci_hcd *ohci)
{
return 0;
}
static inline int quirk_zfmicro(struct ohci_hcd *ohci)
{
return 0;
}
#endif
/* convert between an hcd pointer and the corresponding ohci_hcd */ /* convert between an hcd pointer and the corresponding ohci_hcd */
static inline struct ohci_hcd *hcd_to_ohci (struct usb_hcd *hcd) static inline struct ohci_hcd *hcd_to_ohci (struct usb_hcd *hcd)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment