Commit 9d938747 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: EHCI: use hrtimer for the IAA watchdog

This patch (as1581) replaces the iaa_watchdog kernel timer used by
ehci-hcd with an hrtimer event, in keeping with the general conversion
to high-res timers.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 8c5bf7be
...@@ -93,7 +93,6 @@ static const char hcd_name [] = "ehci_hcd"; ...@@ -93,7 +93,6 @@ static const char hcd_name [] = "ehci_hcd";
*/ */
#define EHCI_TUNE_FLS 1 /* (medium) 512-frame schedule */ #define EHCI_TUNE_FLS 1 /* (medium) 512-frame schedule */
#define EHCI_IAA_MSECS 10 /* arbitrary */
#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ #define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */
#define EHCI_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1) #define EHCI_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1)
/* 5-ms async qh unlink delay */ /* 5-ms async qh unlink delay */
...@@ -322,51 +321,6 @@ static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); ...@@ -322,51 +321,6 @@ static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh);
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static void ehci_iaa_watchdog(unsigned long param)
{
struct ehci_hcd *ehci = (struct ehci_hcd *) param;
unsigned long flags;
spin_lock_irqsave (&ehci->lock, flags);
/* 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->async_unlink
&& !timer_pending(&ehci->iaa_watchdog)
&& ehci->rh_state == EHCI_RH_RUNNING) {
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 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_vdbg(ehci, "IAA watchdog: status %x cmd %x\n",
status, cmd);
end_unlink_async(ehci);
}
spin_unlock_irqrestore(&ehci->lock, flags);
}
static void ehci_watchdog(unsigned long param) static void ehci_watchdog(unsigned long param)
{ {
struct ehci_hcd *ehci = (struct ehci_hcd *) param; struct ehci_hcd *ehci = (struct ehci_hcd *) param;
...@@ -418,7 +372,6 @@ static void ehci_shutdown(struct usb_hcd *hcd) ...@@ -418,7 +372,6 @@ static void ehci_shutdown(struct usb_hcd *hcd)
struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct ehci_hcd *ehci = hcd_to_ehci(hcd);
del_timer_sync(&ehci->watchdog); del_timer_sync(&ehci->watchdog);
del_timer_sync(&ehci->iaa_watchdog);
spin_lock_irq(&ehci->lock); spin_lock_irq(&ehci->lock);
ehci->rh_state = EHCI_RH_STOPPING; ehci->rh_state = EHCI_RH_STOPPING;
...@@ -491,7 +444,6 @@ static void ehci_stop (struct usb_hcd *hcd) ...@@ -491,7 +444,6 @@ static void ehci_stop (struct usb_hcd *hcd)
/* no more interrupts ... */ /* no more interrupts ... */
del_timer_sync (&ehci->watchdog); del_timer_sync (&ehci->watchdog);
del_timer_sync(&ehci->iaa_watchdog);
spin_lock_irq(&ehci->lock); spin_lock_irq(&ehci->lock);
ehci->enabled_hrtimer_events = 0; ehci->enabled_hrtimer_events = 0;
...@@ -547,10 +499,6 @@ static int ehci_init(struct usb_hcd *hcd) ...@@ -547,10 +499,6 @@ static int ehci_init(struct usb_hcd *hcd)
ehci->watchdog.function = ehci_watchdog; ehci->watchdog.function = ehci_watchdog;
ehci->watchdog.data = (unsigned long) ehci; ehci->watchdog.data = (unsigned long) ehci;
init_timer(&ehci->iaa_watchdog);
ehci->iaa_watchdog.function = ehci_iaa_watchdog;
ehci->iaa_watchdog.data = (unsigned long) ehci;
hrtimer_init(&ehci->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); hrtimer_init(&ehci->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
ehci->hrtimer.function = ehci_hrtimer_func; ehci->hrtimer.function = ehci_hrtimer_func;
ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT; ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT;
...@@ -830,6 +778,20 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) ...@@ -830,6 +778,20 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
/* complete the unlinking of some qh [4.15.2.3] */ /* complete the unlinking of some qh [4.15.2.3] */
if (status & STS_IAA) { if (status & STS_IAA) {
/* Turn off the IAA watchdog */
ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_IAA_WATCHDOG);
/*
* Mild optimization: Allow another IAAD to reset the
* hrtimer, if one occurs before the next expiration.
* In theory we could always cancel the hrtimer, but
* tests show that about half the time it will be reset
* for some other event anyway.
*/
if (ehci->next_hrtimer_event == EHCI_HRTIMER_IAA_WATCHDOG)
++ehci->next_hrtimer_event;
/* guard against (alleged) silicon errata */ /* guard against (alleged) silicon errata */
if (cmd & CMD_IAAD) if (cmd & CMD_IAAD)
ehci_dbg(ehci, "IAA with IAAD still set?\n"); ehci_dbg(ehci, "IAA with IAAD still set?\n");
......
...@@ -209,7 +209,6 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ...@@ -209,7 +209,6 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
if (time_before (jiffies, ehci->next_statechange)) if (time_before (jiffies, ehci->next_statechange))
msleep(5); msleep(5);
del_timer_sync(&ehci->watchdog); del_timer_sync(&ehci->watchdog);
del_timer_sync(&ehci->iaa_watchdog);
spin_lock_irq (&ehci->lock); spin_lock_irq (&ehci->lock);
......
...@@ -1173,8 +1173,6 @@ static void end_unlink_async (struct ehci_hcd *ehci) ...@@ -1173,8 +1173,6 @@ static void end_unlink_async (struct ehci_hcd *ehci)
struct ehci_qh *qh = ehci->async_unlink; struct ehci_qh *qh = ehci->async_unlink;
struct ehci_qh *next; struct ehci_qh *next;
iaa_watchdog_done(ehci);
// qh->hw_next = cpu_to_hc32(qh->qh_dma); // qh->hw_next = cpu_to_hc32(qh->qh_dma);
qh->qh_state = QH_STATE_IDLE; qh->qh_state = QH_STATE_IDLE;
qh->qh_next.qh = NULL; qh->qh_next.qh = NULL;
...@@ -1243,7 +1241,7 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -1243,7 +1241,7 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
ehci_writel(ehci, ehci->command | CMD_IAAD, &ehci->regs->command); ehci_writel(ehci, ehci->command | CMD_IAAD, &ehci->regs->command);
(void)ehci_readl(ehci, &ehci->regs->command); (void)ehci_readl(ehci, &ehci->regs->command);
iaa_watchdog_start(ehci); ehci_enable_event(ehci, EHCI_HRTIMER_IAA_WATCHDOG, true);
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -72,6 +72,7 @@ static unsigned event_delays_ns[] = { ...@@ -72,6 +72,7 @@ static unsigned event_delays_ns[] = {
1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */
1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */
2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */ 2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */
10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IAA_WATCHDOG */
10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */
15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */
}; };
...@@ -291,6 +292,49 @@ static void end_free_itds(struct ehci_hcd *ehci) ...@@ -291,6 +292,49 @@ static void end_free_itds(struct ehci_hcd *ehci)
} }
/* Handle lost (or very late) IAA interrupts */
static void ehci_iaa_watchdog(struct ehci_hcd *ehci)
{
if (ehci->rh_state != EHCI_RH_RUNNING)
return;
/*
* 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->async_unlink) {
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 IAA is set here it either legitimately triggered
* after the watchdog timer expired (_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_vdbg(ehci, "IAA watchdog: status %x cmd %x\n",
status, cmd);
end_unlink_async(ehci);
}
}
/* /*
* Handler functions for the hrtimer event types. * Handler functions for the hrtimer event types.
* Keep this array in the same order as the event types indexed by * Keep this array in the same order as the event types indexed by
...@@ -302,6 +346,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = { ...@@ -302,6 +346,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = {
ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */
ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */
end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */ end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */
ehci_iaa_watchdog, /* EHCI_HRTIMER_IAA_WATCHDOG */
ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */
ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */
}; };
......
...@@ -84,6 +84,7 @@ enum ehci_hrtimer_event { ...@@ -84,6 +84,7 @@ enum ehci_hrtimer_event {
EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */
EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */
EHCI_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ EHCI_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */
EHCI_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */
EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */
EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */
EHCI_HRTIMER_NUM_EVENTS /* Must come last */ EHCI_HRTIMER_NUM_EVENTS /* Must come last */
...@@ -168,7 +169,6 @@ struct ehci_hcd { /* one per controller */ ...@@ -168,7 +169,6 @@ struct ehci_hcd { /* one per controller */
struct dma_pool *itd_pool; /* itd per iso urb */ struct dma_pool *itd_pool; /* itd per iso urb */
struct dma_pool *sitd_pool; /* sitd per split iso urb */ struct dma_pool *sitd_pool; /* sitd per split iso urb */
struct timer_list iaa_watchdog;
struct timer_list watchdog; struct timer_list watchdog;
unsigned long actions; unsigned long actions;
unsigned periodic_stamp; unsigned periodic_stamp;
...@@ -228,20 +228,6 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) ...@@ -228,20 +228,6 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci)
return container_of ((void *) ehci, struct usb_hcd, hcd_priv); return container_of ((void *) ehci, struct usb_hcd, hcd_priv);
} }
static inline void
iaa_watchdog_start(struct ehci_hcd *ehci)
{
WARN_ON(timer_pending(&ehci->iaa_watchdog));
mod_timer(&ehci->iaa_watchdog,
jiffies + msecs_to_jiffies(EHCI_IAA_MSECS));
}
static inline void iaa_watchdog_done(struct ehci_hcd *ehci)
{
del_timer(&ehci->iaa_watchdog);
}
enum ehci_timer_action { enum ehci_timer_action {
TIMER_IO_WATCHDOG, TIMER_IO_WATCHDOG,
TIMER_ASYNC_SHRINK, TIMER_ASYNC_SHRINK,
......
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