diff options
author | David Brownell <david-b@pacbell.net> | 2008-02-02 05:42:52 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-04-25 00:16:34 -0400 |
commit | e01e7fe3886715f083313da409c5850472455d06 (patch) | |
tree | 1e7804fe4f5fe563335ee270218062d406128f42 /drivers/usb/host/ohci-hub.c | |
parent | 9776afc8b3dc487557f3f576002520f59be334e6 (diff) |
USB: ohci: port reset paranoia timeout
This limits how long the OHCI port reset loop waits for the hardware
to do its job, if the controller either (a) dies, or (b) can't finish
the reset. Such limits are always a good idea.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/ohci-hub.c')
-rw-r--r-- | drivers/usb/host/ohci-hub.c | 24 |
1 files changed, 21 insertions, 3 deletions
diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 48e4b11f4d3e..c638e6b33c43 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c | |||
@@ -564,14 +564,18 @@ static inline int root_port_reset (struct ohci_hcd *ohci, unsigned port) | |||
564 | u32 temp; | 564 | u32 temp; |
565 | u16 now = ohci_readl(ohci, &ohci->regs->fmnumber); | 565 | u16 now = ohci_readl(ohci, &ohci->regs->fmnumber); |
566 | u16 reset_done = now + PORT_RESET_MSEC; | 566 | u16 reset_done = now + PORT_RESET_MSEC; |
567 | int limit_1 = DIV_ROUND_UP(PORT_RESET_MSEC, PORT_RESET_HW_MSEC); | ||
567 | 568 | ||
568 | /* build a "continuous enough" reset signal, with up to | 569 | /* build a "continuous enough" reset signal, with up to |
569 | * 3msec gap between pulses. scheduler HZ==100 must work; | 570 | * 3msec gap between pulses. scheduler HZ==100 must work; |
570 | * this might need to be deadline-scheduled. | 571 | * this might need to be deadline-scheduled. |
571 | */ | 572 | */ |
572 | do { | 573 | do { |
574 | int limit_2; | ||
575 | |||
573 | /* spin until any current reset finishes */ | 576 | /* spin until any current reset finishes */ |
574 | for (;;) { | 577 | limit_2 = PORT_RESET_HW_MSEC * 2; |
578 | while (--limit_2 >= 0) { | ||
575 | temp = ohci_readl (ohci, portstat); | 579 | temp = ohci_readl (ohci, portstat); |
576 | /* handle e.g. CardBus eject */ | 580 | /* handle e.g. CardBus eject */ |
577 | if (temp == ~(u32)0) | 581 | if (temp == ~(u32)0) |
@@ -581,6 +585,17 @@ static inline int root_port_reset (struct ohci_hcd *ohci, unsigned port) | |||
581 | udelay (500); | 585 | udelay (500); |
582 | } | 586 | } |
583 | 587 | ||
588 | /* timeout (a hardware error) has been observed when | ||
589 | * EHCI sets CF while this driver is resetting a port; | ||
590 | * presumably other disconnect paths might do it too. | ||
591 | */ | ||
592 | if (limit_2 < 0) { | ||
593 | ohci_dbg(ohci, | ||
594 | "port[%d] reset timeout, stat %08x\n", | ||
595 | port, temp); | ||
596 | break; | ||
597 | } | ||
598 | |||
584 | if (!(temp & RH_PS_CCS)) | 599 | if (!(temp & RH_PS_CCS)) |
585 | break; | 600 | break; |
586 | if (temp & RH_PS_PRSC) | 601 | if (temp & RH_PS_PRSC) |
@@ -590,8 +605,11 @@ static inline int root_port_reset (struct ohci_hcd *ohci, unsigned port) | |||
590 | ohci_writel (ohci, RH_PS_PRS, portstat); | 605 | ohci_writel (ohci, RH_PS_PRS, portstat); |
591 | msleep(PORT_RESET_HW_MSEC); | 606 | msleep(PORT_RESET_HW_MSEC); |
592 | now = ohci_readl(ohci, &ohci->regs->fmnumber); | 607 | now = ohci_readl(ohci, &ohci->regs->fmnumber); |
593 | } while (tick_before(now, reset_done)); | 608 | } while (tick_before(now, reset_done) && --limit_1 >= 0); |
594 | /* caller synchronizes using PRSC */ | 609 | |
610 | /* caller synchronizes using PRSC ... and handles PRS | ||
611 | * still being set when this returns. | ||
612 | */ | ||
595 | 613 | ||
596 | return 0; | 614 | return 0; |
597 | } | 615 | } |