diff options
Diffstat (limited to 'drivers/usb/host')
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 62 | ||||
-rw-r--r-- | drivers/usb/host/ehci-q.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/isp116x-hcd.c | 15 | ||||
-rw-r--r-- | drivers/usb/host/isp116x.h | 1 |
4 files changed, 53 insertions, 27 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index b8ad55aff842..46ee7f4c0912 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -281,23 +281,44 @@ static void ehci_iaa_watchdog(unsigned long param) { struct ehci_hcd *ehci = (struct ehci_hcd *) param; unsigned long flags; - u32 status, cmd; spin_lock_irqsave (&ehci->lock, flags); - WARN_ON(!ehci->reclaim); - status = ehci_readl(ehci, &ehci->regs->status); - cmd = ehci_readl(ehci, &ehci->regs->command); - ehci_dbg(ehci, "IAA watchdog: status %x cmd %x\n", status, cmd); - - /* lost IAA irqs wedge things badly; seen first with a vt8235 */ - if (ehci->reclaim) { - if (status & STS_IAA) { - ehci_vdbg (ehci, "lost IAA\n"); + /* Lost IAA irqs wedge things badly; seen first with a vt8235. + * So we need this watchdog, but must protect it against both + * (a) SMP races against real IAA firing and retriggering, and + * (b) clean HC shutdown, when IAA watchdog was pending. + */ + if (ehci->reclaim + && !timer_pending(&ehci->iaa_watchdog) + && HC_IS_RUNNING(ehci_to_hcd(ehci)->state)) { + u32 cmd, status; + + /* If we get here, IAA is *REALLY* late. It's barely + * conceivable that the system is so busy that CMD_IAAD + * is still legitimately set, so let's be sure it's + * clear before we read STS_IAA. (The HC should clear + * CMD_IAAD when it sets STS_IAA.) + */ + cmd = ehci_readl(ehci, &ehci->regs->command); + if (cmd & CMD_IAAD) + ehci_writel(ehci, cmd & ~CMD_IAAD, + &ehci->regs->command); + + /* If IAA is set here it either legitimately triggered + * before we cleared IAAD above (but _way_ late, so we'll + * still count it as lost) ... or a silicon erratum: + * - VIA seems to set IAA without triggering the IRQ; + * - IAAD potentially cleared without setting IAA. + */ + status = ehci_readl(ehci, &ehci->regs->status); + if ((status & STS_IAA) || !(cmd & CMD_IAAD)) { COUNT (ehci->stats.lost_iaa); ehci_writel(ehci, STS_IAA, &ehci->regs->status); } - ehci_writel(ehci, cmd & ~CMD_IAAD, &ehci->regs->command); + + ehci_vdbg(ehci, "IAA watchdog: status %x cmd %x\n", + status, cmd); end_unlink_async(ehci); } @@ -631,7 +652,7 @@ static int ehci_run (struct usb_hcd *hcd) static irqreturn_t ehci_irq (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); - u32 status, pcd_status = 0; + u32 status, pcd_status = 0, cmd; int bh; spin_lock (&ehci->lock); @@ -652,7 +673,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) /* clear (just) interrupts */ ehci_writel(ehci, status, &ehci->regs->status); - ehci_readl(ehci, &ehci->regs->command); /* unblock posted write */ + cmd = ehci_readl(ehci, &ehci->regs->command); bh = 0; #ifdef EHCI_VERBOSE_DEBUG @@ -673,8 +694,17 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) /* complete the unlinking of some qh [4.15.2.3] */ if (status & STS_IAA) { - COUNT (ehci->stats.reclaim); - end_unlink_async(ehci); + /* guard against (alleged) silicon errata */ + if (cmd & CMD_IAAD) { + ehci_writel(ehci, cmd & ~CMD_IAAD, + &ehci->regs->command); + ehci_dbg(ehci, "IAA with IAAD still set?\n"); + } + if (ehci->reclaim) { + COUNT(ehci->stats.reclaim); + end_unlink_async(ehci); + } else + ehci_dbg(ehci, "IAA with nothing to reclaim?\n"); } /* remote wakeup [4.3.1] */ @@ -781,7 +811,7 @@ static int ehci_urb_enqueue ( static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) { /* failfast */ - if (!HC_IS_RUNNING(ehci_to_hcd(ehci)->state)) + if (!HC_IS_RUNNING(ehci_to_hcd(ehci)->state) && ehci->reclaim) end_unlink_async(ehci); /* if it's not linked then there's nothing to do */ diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 776a97f33914..2e49de820b14 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -319,10 +319,10 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) if (likely (last->urb != urb)) { ehci_urb_done(ehci, last->urb, last_status); count++; + last_status = -EINPROGRESS; } ehci_qtd_free (ehci, last); last = NULL; - last_status = -EINPROGRESS; } /* ignore urbs submitted during completions we reported */ diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 0130fd8571e4..d7071c855758 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -911,8 +911,7 @@ static int isp116x_hub_status_data(struct usb_hcd *hcd, char *buf) buf[0] = 0; for (i = 0; i < ports; i++) { - u32 status = isp116x->rhport[i] = - isp116x_read_reg32(isp116x, i ? HCRHPORT2 : HCRHPORT1); + u32 status = isp116x_read_reg32(isp116x, i ? HCRHPORT2 : HCRHPORT1); if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC | RH_PS_OCIC | RH_PS_PRSC)) { @@ -1031,7 +1030,9 @@ static int isp116x_hub_control(struct usb_hcd *hcd, DBG("GetPortStatus\n"); if (!wIndex || wIndex > ports) goto error; - tmp = isp116x->rhport[--wIndex]; + spin_lock_irqsave(&isp116x->lock, flags); + tmp = isp116x_read_reg32(isp116x, (--wIndex) ? HCRHPORT2 : HCRHPORT1); + spin_unlock_irqrestore(&isp116x->lock, flags); *(__le32 *) buf = cpu_to_le32(tmp); DBG("GetPortStatus: port[%d] %08x\n", wIndex + 1, tmp); break; @@ -1080,8 +1081,6 @@ static int isp116x_hub_control(struct usb_hcd *hcd, spin_lock_irqsave(&isp116x->lock, flags); isp116x_write_reg32(isp116x, wIndex ? HCRHPORT2 : HCRHPORT1, tmp); - isp116x->rhport[wIndex] = - isp116x_read_reg32(isp116x, wIndex ? HCRHPORT2 : HCRHPORT1); spin_unlock_irqrestore(&isp116x->lock, flags); break; case SetPortFeature: @@ -1095,24 +1094,22 @@ static int isp116x_hub_control(struct usb_hcd *hcd, spin_lock_irqsave(&isp116x->lock, flags); isp116x_write_reg32(isp116x, wIndex ? HCRHPORT2 : HCRHPORT1, RH_PS_PSS); + spin_unlock_irqrestore(&isp116x->lock, flags); break; case USB_PORT_FEAT_POWER: DBG("USB_PORT_FEAT_POWER\n"); spin_lock_irqsave(&isp116x->lock, flags); isp116x_write_reg32(isp116x, wIndex ? HCRHPORT2 : HCRHPORT1, RH_PS_PPS); + spin_unlock_irqrestore(&isp116x->lock, flags); break; case USB_PORT_FEAT_RESET: DBG("USB_PORT_FEAT_RESET\n"); root_port_reset(isp116x, wIndex); - spin_lock_irqsave(&isp116x->lock, flags); break; default: goto error; } - isp116x->rhport[wIndex] = - isp116x_read_reg32(isp116x, wIndex ? HCRHPORT2 : HCRHPORT1); - spin_unlock_irqrestore(&isp116x->lock, flags); break; default: diff --git a/drivers/usb/host/isp116x.h b/drivers/usb/host/isp116x.h index b91e2edd9c5c..595b90a99848 100644 --- a/drivers/usb/host/isp116x.h +++ b/drivers/usb/host/isp116x.h @@ -270,7 +270,6 @@ struct isp116x { u32 rhdesca; u32 rhdescb; u32 rhstatus; - u32 rhport[2]; /* async schedule: control, bulk */ struct list_head async; |