Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 67795
b: refs/heads/master
c: 89a0fd1
h: refs/heads/master
i:
  67793: a1d27ef
  67791: 647d3fd
v: v3
  • Loading branch information
Mike Nuss authored and Greg Kroah-Hartman committed Oct 12, 2007
1 parent 3ab71b7 commit 942039d
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 76 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: e8fa0ce65c58dbb60be279c4e33534650dcacc31
refs/heads/master: 89a0fd18a96eb1f8732714b575073f8a8d69c009
166 changes: 139 additions & 27 deletions trunk/drivers/usb/host/ohci-hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ static void ohci_dump (struct ohci_hcd *ohci, int verbose);
static int ohci_init (struct ohci_hcd *ohci);
static void ohci_stop (struct usb_hcd *hcd);
static int ohci_restart (struct ohci_hcd *ohci);
static void ohci_quirk_nec_worker (struct work_struct *work);

#include "ohci-hub.c"
#include "ohci-dbg.c"
Expand Down Expand Up @@ -314,14 +313,21 @@ ohci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
if (!HC_IS_RUNNING (hcd->state)) {
sanitize:
ed->state = ED_IDLE;
if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
ohci->eds_scheduled--;
finish_unlinks (ohci, 0);
}

switch (ed->state) {
case ED_UNLINK: /* wait for hw to finish? */
/* major IRQ delivery trouble loses INTR_SF too... */
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;
}
spin_unlock_irqrestore (&ohci->lock, flags);
Expand Down Expand Up @@ -379,6 +385,93 @@ ohci_shutdown (struct usb_hcd *hcd)
(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
*-------------------------------------------------------------------------*/
Expand Down Expand Up @@ -616,6 +709,15 @@ static int ohci_run (struct ohci_hcd *ohci)
mdelay ((temp >> 23) & 0x1fe);
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);

return 0;
Expand All @@ -629,10 +731,11 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
struct ohci_regs __iomem *regs = ohci->regs;
int ints;
int ints;

/* 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)
&& ! (hc32_to_cpup (ohci, &ohci->hcca->done_head)
& 0x01)) {
Expand All @@ -651,7 +754,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)

if (ints & OHCI_INTR_UE) {
// 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
* in Apple's PowerBooks. Adapted from Darwin code.
*/
Expand Down Expand Up @@ -713,6 +816,31 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
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 */

/* handle any pending URB/ED unlinks, leaving INTR_SF enabled
Expand All @@ -721,7 +849,9 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
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
if ((ints & OHCI_INTR_SF) != 0
&& !ohci->ed_rm_list
&& !ohci->ed_to_check
&& HC_IS_RUNNING(hcd->state))
ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable);
spin_unlock (&ohci->lock);
Expand Down Expand Up @@ -751,6 +881,9 @@ static void ohci_stop (struct usb_hcd *hcd)
free_irq(hcd->irq, hcd);
hcd->irq = -1;

if (quirk_zfmicro(ohci))
del_timer(&ohci->unlink_watchdog);

remove_debug_files (ohci);
ohci_mem_cleanup (ohci);
if (ohci->hcca) {
Expand Down Expand Up @@ -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

MODULE_AUTHOR (DRIVER_AUTHOR);
Expand Down
1 change: 0 additions & 1 deletion trunk/drivers/usb/host/ohci-mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ static void ohci_hcd_init (struct ohci_hcd *ohci)
ohci->next_statechange = jiffies;
spin_lock_init (&ohci->lock);
INIT_LIST_HEAD (&ohci->pending);
INIT_WORK (&ohci->nec_work, ohci_quirk_nec_worker);
}

/*-------------------------------------------------------------------------*/
Expand Down
22 changes: 21 additions & 1 deletion trunk/drivers/usb/host/ohci-pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ static int ohci_quirk_zfmicro(struct usb_hcd *hcd)
struct ohci_hcd *ohci = hcd_to_ohci (hcd);

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;
}
Expand Down Expand Up @@ -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.
*/

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)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);

ohci->flags |= OHCI_QUIRK_NEC;
INIT_WORK(&ohci->nec_work, ohci_quirk_nec_worker);
ohci_dbg (ohci, "enabled NEC chipset lost interrupt quirk\n");

return 0;
Expand Down
Loading

0 comments on commit 942039d

Please sign in to comment.