summaryrefslogtreecommitdiff
path: root/drivers/usb/host
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/ehci-hcd.c62
-rw-r--r--drivers/usb/host/ehci-q.c2
-rw-r--r--drivers/usb/host/isp116x-hcd.c15
-rw-r--r--drivers/usb/host/isp116x.h1
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;