From d58b4bcc6df8046cf9c3c59f9ff84d2cd86b93eb Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:21:54 -0400 Subject: USB: EHCI: introduce high-res timer This patch (as1572) begins the conversion of ehci-hcd over to using high-resolution timers rather than old-fashioned low-resolution kernel timers. This reduces overhead caused by timer roundoff on systems where HZ is smaller than 1000. Also, the new timer framework introduced here is much more logical and easily extended than the ad-hoc approach ehci-hcd currently uses for timers. An hrtimer structure is added to ehci_hcd, along with a bitflag array and an array of ktime_t values, to keep track of which timing events are pending and what their expiration times are. Only the infrastructure for the timing operations is added in this patch. Later patches will add routines for handling each of the various timing events the driver needs. In some cases the new hrtimer handlers will replace the existing handlers for ehci-hcd's kernel timers; as this happens the old timers will be removed. In other cases the new timing events will replace busy-wait loops. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-timer.c | 106 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 drivers/usb/host/ehci-timer.c (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c new file mode 100644 index 000000000000..f6279e2883a8 --- /dev/null +++ b/drivers/usb/host/ehci-timer.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012 by Alan Stern + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/* This file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI timer support... Now using hrtimers. + * + * Lots of different events are triggered from ehci->hrtimer. Whenever + * the timer routine runs, it checks each possible event; events that are + * currently enabled and whose expiration time has passed get handled. + * The set of enabled events is stored as a collection of bitflags in + * ehci->enabled_hrtimer_events, and they are numbered in order of + * increasing delay values (ranging between 1 ms and 100 ms). + * + * Rather than implementing a sorted list or tree of all pending events, + * we keep track only of the lowest-numbered pending event, in + * ehci->next_hrtimer_event. Whenever ehci->hrtimer gets restarted, its + * expiration time is set to the timeout value for this event. + * + * As a result, events might not get handled right away; the actual delay + * could be anywhere up to twice the requested delay. This doesn't + * matter, because none of the events are especially time-critical. The + * ones that matter most all have a delay of 1 ms, so they will be + * handled after 2 ms at most, which is okay. In addition to this, we + * allow for an expiration range of 1 ms. + */ + +/* + * Delay lengths for the hrtimer event types. + * Keep this list sorted by delay length, in the same order as + * the event types indexed by enum ehci_hrtimer_event in ehci.h. + */ +static unsigned event_delays_ns[] = { +}; + +/* Enable a pending hrtimer event */ +static void ehci_enable_event(struct ehci_hcd *ehci, unsigned event, + bool resched) +{ + ktime_t *timeout = &ehci->hr_timeouts[event]; + + if (resched) + *timeout = ktime_add(ktime_get(), + ktime_set(0, event_delays_ns[event])); + ehci->enabled_hrtimer_events |= (1 << event); + + /* Track only the lowest-numbered pending event */ + if (event < ehci->next_hrtimer_event) { + ehci->next_hrtimer_event = event; + hrtimer_start_range_ns(&ehci->hrtimer, *timeout, + NSEC_PER_MSEC, HRTIMER_MODE_ABS); + } +} + + +/* + * Handler functions for the hrtimer event types. + * Keep this array in the same order as the event types indexed by + * enum ehci_hrtimer_event in ehci.h. + */ +static void (*event_handlers[])(struct ehci_hcd *) = { +}; + +static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) +{ + struct ehci_hcd *ehci = container_of(t, struct ehci_hcd, hrtimer); + ktime_t now; + unsigned long events; + unsigned long flags; + unsigned e; + + spin_lock_irqsave(&ehci->lock, flags); + + events = ehci->enabled_hrtimer_events; + ehci->enabled_hrtimer_events = 0; + ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT; + + /* + * Check each pending event. If its time has expired, handle + * the event; otherwise re-enable it. + */ + now = ktime_get(); + for_each_set_bit(e, &events, EHCI_HRTIMER_NUM_EVENTS) { + if (now.tv64 >= ehci->hr_timeouts[e].tv64) + event_handlers[e](ehci); + else + ehci_enable_event(ehci, e, false); + } + + spin_unlock_irqrestore(&ehci->lock, flags); + return HRTIMER_NORESTART; +} -- cgit v1.2.3 From 3ca9aebac2ebb8f56d2d097636b8c568320a9f87 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:22:05 -0400 Subject: USB: EHCI: use hrtimer for the periodic schedule This patch (as1573) adds hrtimer support for managing ehci-hcd's periodic schedule. There are two issues to deal with. First, the schedule's state (on or off) must not be changed until the hardware status has caught up with the current command. This is handled by an hrtimer event that polls at 1-ms intervals to see when the Periodic Schedule Status (PSS) flag matches the Periodic Schedule Enable (PSE) value. Second, the schedule should not be turned off as soon as it becomes empty. Turning the schedule on and off takes time, so we want to wait until the schedule has been empty for a suitable period before turning it off. This is handled by an hrtimer event that gets set to expire 10 ms after the periodic schedule becomes empty. The existing code polls (for up to 1125 us and with interrupts disabled!) to check the status, and doesn't implement a delay before turning off the schedule. Furthermore, if the polling fails then the driver decides that the controller has died. This has caused problems for several people; some controllers can take 10 ms or more to turn off their periodic schedules. This patch fixes these issues. It also makes the "broken_periodic" workaround unnecessary; there is no longer any danger of turning off the periodic schedule after it has been on for less than 1 ms. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 2 +- drivers/usb/host/ehci-pci.c | 4 --- drivers/usb/host/ehci-sched.c | 69 ++++++++----------------------------- drivers/usb/host/ehci-timer.c | 80 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/ehci.h | 7 ++-- 5 files changed, 101 insertions(+), 61 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index f8fed163a23a..7e00ca095cea 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -546,7 +546,7 @@ static void ehci_work (struct ehci_hcd *ehci) */ if (ehci->rh_state == EHCI_RH_RUNNING && (ehci->async->qh_next.ptr != NULL || - ehci->periodic_sched != 0)) + ehci->periodic_count != 0)) timer_action (ehci, TIMER_IO_WATCHDOG); } diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 21e5f963f331..2cb7d370c4ef 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -104,10 +104,6 @@ static int ehci_pci_setup(struct usb_hcd *hcd) break; case PCI_VENDOR_ID_INTEL: ehci->fs_i_thresh = 1; - if (pdev->device == 0x27cc) { - ehci->broken_periodic = 1; - ehci_info(ehci, "using broken periodic workaround\n"); - } if (pdev->device == PCI_DEVICE_ID_INTEL_CE4100_USB) hcd->has_tt = 1; break; diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 3429b8a33c58..f5c15880c65a 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -481,67 +481,26 @@ static int tt_no_collision ( static int enable_periodic (struct ehci_hcd *ehci) { - int status; - - if (ehci->periodic_sched++) + if (ehci->periodic_count++) return 0; - /* did clearing PSE did take effect yet? - * takes effect only at frame boundaries... - */ - status = handshake_on_error_set_halt(ehci, &ehci->regs->status, - STS_PSS, 0, 9 * 125); - if (status) { - usb_hc_died(ehci_to_hcd(ehci)); - return status; - } - - ehci->command |= CMD_PSE; - ehci_writel(ehci, ehci->command, &ehci->regs->command); - /* posted write ... PSS happens later */ + /* Stop waiting to turn off the periodic schedule */ + ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_DISABLE_PERIODIC); - /* make sure ehci_work scans these */ - ehci->next_uframe = ehci_read_frame_index(ehci) - % (ehci->periodic_size << 3); - if (unlikely(ehci->broken_periodic)) - ehci->last_periodic_enable = ktime_get_real(); + /* Don't start the schedule until PSS is 0 */ + ehci_poll_PSS(ehci); return 0; } static int disable_periodic (struct ehci_hcd *ehci) { - int status; - - if (--ehci->periodic_sched) + if (--ehci->periodic_count) return 0; - if (unlikely(ehci->broken_periodic)) { - /* delay experimentally determined */ - ktime_t safe = ktime_add_us(ehci->last_periodic_enable, 1000); - ktime_t now = ktime_get_real(); - s64 delay = ktime_us_delta(safe, now); - - if (unlikely(delay > 0)) - udelay(delay); - } - - /* did setting PSE not take effect yet? - * takes effect only at frame boundaries... - */ - status = handshake_on_error_set_halt(ehci, &ehci->regs->status, - STS_PSS, STS_PSS, 9 * 125); - if (status) { - usb_hc_died(ehci_to_hcd(ehci)); - return status; - } - - ehci->command &= ~CMD_PSE; - ehci_writel(ehci, ehci->command, &ehci->regs->command); - /* posted write ... */ - - free_cached_lists(ehci); + ehci->next_uframe = -1; /* the periodic schedule is empty */ - ehci->next_uframe = -1; + /* Don't turn off the schedule until PSS is 1 */ + ehci_poll_PSS(ehci); return 0; } @@ -650,8 +609,7 @@ static int qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh) qh->qh_state = QH_STATE_UNLINK; qh->qh_next.ptr = NULL; - /* maybe turn off periodic schedule */ - return disable_periodic(ehci); + return 0; } static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh) @@ -706,6 +664,9 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh) ehci_err(ehci, "can't reschedule qh %p, err %d\n", qh, rc); } + + /* maybe turn off periodic schedule */ + disable_periodic(ehci); } /*-------------------------------------------------------------------------*/ @@ -2447,7 +2408,7 @@ restart: /* assume completion callbacks modify the queue */ if (unlikely (modified)) { - if (likely(ehci->periodic_sched > 0)) + if (likely(ehci->periodic_count > 0)) goto restart; /* short-circuit this scan */ now_uframe = clock; @@ -2476,7 +2437,7 @@ restart: unsigned now; if (ehci->rh_state < EHCI_RH_RUNNING - || ehci->periodic_sched == 0) + || ehci->periodic_count == 0) break; ehci->next_uframe = now_uframe; now = ehci_read_frame_index(ehci) & (mod - 1); diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index f6279e2883a8..ecd3296157c6 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -16,6 +16,28 @@ /*-------------------------------------------------------------------------*/ +/* Set a bit in the USBCMD register */ +static void ehci_set_command_bit(struct ehci_hcd *ehci, u32 bit) +{ + ehci->command |= bit; + ehci_writel(ehci, ehci->command, &ehci->regs->command); + + /* unblock posted write */ + ehci_readl(ehci, &ehci->regs->command); +} + +/* Clear a bit in the USBCMD register */ +static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) +{ + ehci->command &= ~bit; + ehci_writel(ehci, ehci->command, &ehci->regs->command); + + /* unblock posted write */ + ehci_readl(ehci, &ehci->regs->command); +} + +/*-------------------------------------------------------------------------*/ + /* * EHCI timer support... Now using hrtimers. * @@ -45,6 +67,8 @@ * the event types indexed by enum ehci_hrtimer_event in ehci.h. */ static unsigned event_delays_ns[] = { + 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ + 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ }; /* Enable a pending hrtimer event */ @@ -67,12 +91,68 @@ static void ehci_enable_event(struct ehci_hcd *ehci, unsigned event, } +/* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ +static void ehci_poll_PSS(struct ehci_hcd *ehci) +{ + unsigned actual, want; + + /* Don't do anything if the controller isn't running (e.g., died) */ + if (ehci->rh_state != EHCI_RH_RUNNING) + return; + + want = (ehci->command & CMD_PSE) ? STS_PSS : 0; + actual = ehci_readl(ehci, &ehci->regs->status) & STS_PSS; + + if (want != actual) { + + /* Poll again later, but give up after about 20 ms */ + if (ehci->PSS_poll_count++ < 20) { + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_PSS, true); + return; + } + ehci_warn(ehci, "Waited too long for the periodic schedule status, giving up\n"); + } + ehci->PSS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + free_cached_lists(ehci); + if (ehci->periodic_count > 0) { + + /* make sure ehci_work scans these */ + ehci->next_uframe = ehci_read_frame_index(ehci) + & ((ehci->periodic_size << 3) - 1); + ehci_set_command_bit(ehci, CMD_PSE); + } + + } else { /* Running */ + if (ehci->periodic_count == 0) { + + /* Turn off the schedule after a while */ + ehci_enable_event(ehci, EHCI_HRTIMER_DISABLE_PERIODIC, + true); + } + } +} + +/* Turn off the periodic schedule after a brief delay */ +static void ehci_disable_PSE(struct ehci_hcd *ehci) +{ + ehci_clear_command_bit(ehci, CMD_PSE); + + /* Poll to see when it actually stops */ + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_PSS, true); +} + + /* * Handler functions for the hrtimer event types. * Keep this array in the same order as the event types indexed by * enum ehci_hrtimer_event in ehci.h. */ static void (*event_handlers[])(struct ehci_hcd *) = { + ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ + ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ }; static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 070be83028d5..da2e0ab23850 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -79,6 +79,8 @@ enum ehci_rh_state { * ehci-timer.c) in parallel with this list. */ enum ehci_hrtimer_event { + EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ + EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_NUM_EVENTS /* Must come last */ }; #define EHCI_HRTIMER_NO_EVENT 99 @@ -90,6 +92,8 @@ struct ehci_hcd { /* one per controller */ ktime_t hr_timeouts[EHCI_HRTIMER_NUM_EVENTS]; struct hrtimer hrtimer; + int PSS_poll_count; + /* glue to PCI and HCD framework */ struct ehci_caps __iomem *caps; struct ehci_regs __iomem *regs; @@ -116,7 +120,7 @@ struct ehci_hcd { /* one per controller */ union ehci_shadow *pshadow; /* mirror hw periodic table */ int next_uframe; /* scan periodic, start here */ - unsigned periodic_sched; /* periodic activity count */ + unsigned periodic_count; /* periodic activity count */ unsigned uframe_periodic_max; /* max periodic time per uframe */ @@ -165,7 +169,6 @@ struct ehci_hcd { /* one per controller */ unsigned big_endian_capbase:1; unsigned has_amcc_usb23:1; unsigned need_io_watchdog:1; - unsigned broken_periodic:1; unsigned amd_pll_fix:1; unsigned fs_i_thresh:1; /* Intel iso scheduling */ unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/ -- cgit v1.2.3 From 314466101c6ae14f6f5db8a86eda1509ba2c02a8 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:22:21 -0400 Subject: USB: EHCI: use hrtimer for async schedule This patch (as1576) adds hrtimer support for managing ehci-hcd's async schedule. Just as with the earlier change to the periodic schedule management, two new hrtimer events take care of everything. One event polls at 1-ms intervals to see when the Asynchronous Schedule Status (ASS) flag matches the Asynchronous Schedule Enable (ASE) value; the schedule's state must not be changed until it does. The other event delays for 15 ms after the async schedule becomes empty before turning it off. The new events replace a busy-wait poll and a kernel timer usage. They also replace the rather illogical method currently used for indicating the async schedule should be turned off: attempting to unlink the dedicated QH at the head of the async list. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 13 ++------- drivers/usb/host/ehci-q.c | 68 +++++++++++++++++++------------------------ drivers/usb/host/ehci-timer.c | 49 +++++++++++++++++++++++++++++++ drivers/usb/host/ehci.h | 5 +++- 4 files changed, 86 insertions(+), 49 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index fd7ae16f77be..21d6fbc0a327 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -95,7 +95,6 @@ static const char hcd_name [] = "ehci_hcd"; #define EHCI_IAA_MSECS 10 /* arbitrary */ #define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ -#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ #define EHCI_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1) /* 5-ms async qh unlink delay */ @@ -137,7 +136,7 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) * SHRINK were pending, OFF would never be requested. */ if (timer_pending(&ehci->watchdog) - && ((BIT(TIMER_ASYNC_SHRINK) | BIT(TIMER_ASYNC_OFF)) + && (BIT(TIMER_ASYNC_SHRINK) & ehci->actions)) return; @@ -150,9 +149,6 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) return; t = EHCI_IO_JIFFIES; break; - case TIMER_ASYNC_OFF: - t = EHCI_ASYNC_JIFFIES; - break; /* case TIMER_ASYNC_SHRINK: */ default: t = EHCI_SHRINK_JIFFIES; @@ -376,10 +372,6 @@ static void ehci_watchdog(unsigned long param) spin_lock_irqsave(&ehci->lock, flags); - /* stop async processing after it's idled a bit */ - if (test_bit (TIMER_ASYNC_OFF, &ehci->actions)) - start_unlink_async (ehci, ehci->async); - /* ehci could run by timer, without IRQs ... */ ehci_work (ehci); @@ -470,7 +462,8 @@ static void ehci_work (struct ehci_hcd *ehci) if (ehci->scanning) return; ehci->scanning = 1; - scan_async (ehci); + if (ehci->async_count) + scan_async(ehci); if (ehci->next_uframe != -1) scan_periodic (ehci); ehci->scanning = 0; diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 285d5a0f3f70..d68764ef4476 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -964,6 +964,30 @@ done: /*-------------------------------------------------------------------------*/ +static void enable_async(struct ehci_hcd *ehci) +{ + if (ehci->async_count++) + return; + + /* Stop waiting to turn off the async schedule */ + ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_DISABLE_ASYNC); + + /* Don't start the schedule until ASS is 0 */ + ehci_poll_ASS(ehci); +} + +static void disable_async(struct ehci_hcd *ehci) +{ + if (--ehci->async_count) + return; + + /* The async schedule and async_unlink list are supposed to be empty */ + WARN_ON(ehci->async->qh_next.qh || ehci->async_unlink); + + /* Don't turn off the schedule until ASS is 1 */ + ehci_poll_ASS(ehci); +} + /* move qh (and its qtds) onto async queue; maybe enable queue. */ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) @@ -977,24 +1001,11 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) WARN_ON(qh->qh_state != QH_STATE_IDLE); - /* (re)start the async schedule? */ - head = ehci->async; - timer_action_done (ehci, TIMER_ASYNC_OFF); - if (!head->qh_next.qh) { - if (!(ehci->command & CMD_ASE)) { - /* in case a clear of CMD_ASE didn't take yet */ - (void)handshake(ehci, &ehci->regs->status, - STS_ASS, 0, 150); - ehci->command |= CMD_ASE; - ehci_writel(ehci, ehci->command, &ehci->regs->command); - /* posted write need not be known to HC yet ... */ - } - } - /* clear halt and/or toggle; and maybe recover from silicon quirk */ qh_refresh(ehci, qh); /* splice right after start */ + head = ehci->async; qh->qh_next = head->qh_next; qh->hw->hw_next = head->hw->hw_next; wmb (); @@ -1005,6 +1016,8 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) qh->xacterrs = 0; qh->qh_state = QH_STATE_LINKED; /* qtd completions reported later by interrupt */ + + enable_async(ehci); } /*-------------------------------------------------------------------------*/ @@ -1173,16 +1186,10 @@ static void end_unlink_async (struct ehci_hcd *ehci) qh_completions (ehci, qh); - if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) { + if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) qh_link_async (ehci, qh); - } else { - /* it's not free to turn the async schedule on/off; leave it - * active but idle for a while once it empties. - */ - if (ehci->rh_state == EHCI_RH_RUNNING - && ehci->async->qh_next.qh == NULL) - timer_action (ehci, TIMER_ASYNC_OFF); - } + + disable_async(ehci); if (next) { ehci->async_unlink = NULL; @@ -1210,21 +1217,6 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) BUG (); #endif - /* stop async schedule right now? */ - if (unlikely (qh == ehci->async)) { - /* can't get here without STS_ASS set */ - if (ehci->rh_state != EHCI_RH_HALTED - && !ehci->async_unlink) { - /* ... and CMD_IAAD clear */ - ehci->command &= ~CMD_ASE; - ehci_writel(ehci, ehci->command, &ehci->regs->command); - wmb (); - // handshake later, if we need to - timer_action_done (ehci, TIMER_ASYNC_OFF); - } - return; - } - qh->qh_state = QH_STATE_UNLINK; ehci->async_unlink = qh; if (!qh->unlink_next) diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index ecd3296157c6..1e907dd3bb1b 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -67,8 +67,10 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) * the event types indexed by enum ehci_hrtimer_event in ehci.h. */ static unsigned event_delays_ns[] = { + 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ + 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; /* Enable a pending hrtimer event */ @@ -91,6 +93,51 @@ static void ehci_enable_event(struct ehci_hcd *ehci, unsigned event, } +/* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */ +static void ehci_poll_ASS(struct ehci_hcd *ehci) +{ + unsigned actual, want; + + /* Don't enable anything if the controller isn't running (e.g., died) */ + if (ehci->rh_state != EHCI_RH_RUNNING) + return; + + want = (ehci->command & CMD_ASE) ? STS_ASS : 0; + actual = ehci_readl(ehci, &ehci->regs->status) & STS_ASS; + + if (want != actual) { + + /* Poll again later, but give up after about 20 ms */ + if (ehci->ASS_poll_count++ < 20) { + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_ASS, true); + return; + } + ehci_warn(ehci, "Waited too long for the async schedule status, giving up\n"); + } + ehci->ASS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (ehci->async_count > 0) + ehci_set_command_bit(ehci, CMD_ASE); + + } else { /* Running */ + if (ehci->async_count == 0) { + + /* Turn off the schedule after a while */ + ehci_enable_event(ehci, EHCI_HRTIMER_DISABLE_ASYNC, + true); + } + } +} + +/* Turn off the async schedule after a brief delay */ +static void ehci_disable_ASE(struct ehci_hcd *ehci) +{ + ehci_clear_command_bit(ehci, CMD_ASE); +} + + /* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ static void ehci_poll_PSS(struct ehci_hcd *ehci) { @@ -151,8 +198,10 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) * enum ehci_hrtimer_event in ehci.h. */ static void (*event_handlers[])(struct ehci_hcd *) = { + ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ + ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index da2e0ab23850..bf06bbb77ba4 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -79,8 +79,10 @@ enum ehci_rh_state { * ehci-timer.c) in parallel with this list. */ enum ehci_hrtimer_event { + EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */ EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ + EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ EHCI_HRTIMER_NUM_EVENTS /* Must come last */ }; #define EHCI_HRTIMER_NO_EVENT 99 @@ -93,6 +95,7 @@ struct ehci_hcd { /* one per controller */ struct hrtimer hrtimer; int PSS_poll_count; + int ASS_poll_count; /* glue to PCI and HCD framework */ struct ehci_caps __iomem *caps; @@ -110,6 +113,7 @@ struct ehci_hcd { /* one per controller */ struct ehci_qh *async_unlink_last; struct ehci_qh *qh_scan_next; unsigned scanning : 1; + unsigned async_count; /* async activity count */ /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ @@ -229,7 +233,6 @@ static inline void iaa_watchdog_done(struct ehci_hcd *ehci) enum ehci_timer_action { TIMER_IO_WATCHDOG, TIMER_ASYNC_SHRINK, - TIMER_ASYNC_OFF, }; static inline void -- cgit v1.2.3 From df2022553dd8d34d49e16c19d851ea619438f0ef Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:22:26 -0400 Subject: USB: EHCI: use hrtimer for interrupt QH unlink This patch (as1577) adds hrtimer support for unlinking interrupt QHs in ehci-hcd. The current code relies on a fixed delay of either 2 or 55 us, which is not always adequate and in any case is totally bogus. Thanks to internal caching, the EHCI hardware may continue to access an interrupt QH for more than a millisecond after it has been unlinked. In fact, the EHCI spec doesn't say how long to wait before using an unlinked interrupt QH. The patch sets the delay to 9 microframes minimum, which ought to be adequate. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 6 ++-- drivers/usb/host/ehci-hub.c | 1 + drivers/usb/host/ehci-sched.c | 71 ++++++++++++++++++++++++++++--------------- drivers/usb/host/ehci-timer.c | 34 +++++++++++++++++++++ drivers/usb/host/ehci.h | 10 +++++- 5 files changed, 94 insertions(+), 28 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 21d6fbc0a327..edcfd2c4295e 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -309,6 +309,8 @@ static void ehci_quiesce (struct ehci_hcd *ehci) static void end_unlink_async(struct ehci_hcd *ehci); static void ehci_work(struct ehci_hcd *ehci); +static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); +static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); #include "ehci-timer.c" #include "ehci-hub.c" @@ -1034,7 +1036,7 @@ static int ehci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) switch (qh->qh_state) { case QH_STATE_LINKED: case QH_STATE_COMPLETING: - intr_deschedule (ehci, qh); + start_unlink_intr(ehci, qh); break; case QH_STATE_IDLE: qh_completions (ehci, qh); @@ -1164,7 +1166,7 @@ ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) if (eptype == USB_ENDPOINT_XFER_BULK) unlink_async(ehci, qh); else - intr_deschedule(ehci, qh); + start_unlink_intr(ehci, qh); } } spin_unlock_irqrestore(&ehci->lock, flags); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 25329e4b844f..8aa740dc510d 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -302,6 +302,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (ehci->async_unlink) end_unlink_async(ehci); + ehci_handle_intr_unlinks(ehci); /* allow remote wakeup */ mask = INTR_MASK; diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 69b1861e4325..eec8446f8ded 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -578,12 +578,20 @@ static void qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh) unsigned i; unsigned period; - // FIXME: - // IF this isn't high speed - // and this qh is active in the current uframe - // (and overlay token SplitXstate is false?) - // THEN - // qh->hw_info1 |= cpu_to_hc32(1 << 7 /* "ignore" */); + /* + * If qh is for a low/full-speed device, simply unlinking it + * could interfere with an ongoing split transaction. To unlink + * it safely would require setting the QH_INACTIVATE bit and + * waiting at least one frame, as described in EHCI 4.12.2.5. + * + * We won't bother with any of this. Instead, we assume that the + * only reason for unlinking an interrupt QH while the current URB + * is still active is to dequeue all the URBs (flush the whole + * endpoint queue). + * + * If rebalancing the periodic schedule is ever implemented, this + * approach will no longer be valid. + */ /* high bandwidth, or otherwise part of every microframe */ if ((period = qh->period) == 0) @@ -608,12 +616,8 @@ static void qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh) qh->qh_next.ptr = NULL; } -static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh) +static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) { - unsigned wait; - struct ehci_qh_hw *hw = qh->hw; - int rc; - /* If the QH isn't linked then there's nothing we can do * unless we were called during a giveback, in which case * qh_completions() has to deal with it. @@ -626,28 +630,45 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh) qh_unlink_periodic (ehci, qh); - /* simple/paranoid: always delay, expecting the HC needs to read - * qh->hw_next or finish a writeback after SPLIT/CSPLIT ... and - * expect khubd to clean up after any CSPLITs we won't issue. - * active high speed queues may need bigger delays... + /* Make sure the unlinks are visible before starting the timer */ + wmb(); + + /* + * The EHCI spec doesn't say how long it takes the controller to + * stop accessing an unlinked interrupt QH. The timer delay is + * 9 uframes; presumably that will be long enough. */ - if (list_empty (&qh->qtd_list) - || (cpu_to_hc32(ehci, QH_CMASK) - & hw->hw_info2) != 0) - wait = 2; + qh->unlink_cycle = ehci->intr_unlink_cycle; + + /* New entries go at the end of the intr_unlink list */ + if (ehci->intr_unlink) + ehci->intr_unlink_last->unlink_next = qh; else - wait = 55; /* worst case: 3 * 1024 */ + ehci->intr_unlink = qh; + ehci->intr_unlink_last = qh; + + if (ehci->intr_unlinking) + ; /* Avoid recursive calls */ + else if (ehci->rh_state < EHCI_RH_RUNNING) + ehci_handle_intr_unlinks(ehci); + else if (ehci->intr_unlink == qh) { + ehci_enable_event(ehci, EHCI_HRTIMER_UNLINK_INTR, true); + ++ehci->intr_unlink_cycle; + } +} + +static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + struct ehci_qh_hw *hw = qh->hw; + int rc; - udelay (wait); qh->qh_state = QH_STATE_IDLE; hw->hw_next = EHCI_LIST_END(ehci); - wmb (); qh_completions(ehci, qh); /* reschedule QH iff another request is queued */ - if (!list_empty(&qh->qtd_list) && - ehci->rh_state == EHCI_RH_RUNNING) { + if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) { rc = qh_schedule(ehci, qh); /* An error here likely indicates handshake failure @@ -2302,7 +2323,7 @@ restart: temp.qh->stamp = ehci->periodic_stamp; if (unlikely(list_empty(&temp.qh->qtd_list) || temp.qh->needs_rescan)) - intr_deschedule(ehci, temp.qh); + start_unlink_intr(ehci, temp.qh); } break; case Q_TYPE_FSTN: diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 1e907dd3bb1b..bd8b591771b0 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -69,6 +69,7 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) static unsigned event_delays_ns[] = { 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ + 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; @@ -192,6 +193,38 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) } +/* Handle unlinked interrupt QHs once they are gone from the hardware */ +static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) +{ + bool stopped = (ehci->rh_state < EHCI_RH_RUNNING); + + /* + * Process all the QHs on the intr_unlink list that were added + * before the current unlink cycle began. The list is in + * temporal order, so stop when we reach the first entry in the + * current cycle. But if the root hub isn't running then + * process all the QHs on the list. + */ + ehci->intr_unlinking = true; + while (ehci->intr_unlink) { + struct ehci_qh *qh = ehci->intr_unlink; + + if (!stopped && qh->unlink_cycle == ehci->intr_unlink_cycle) + break; + ehci->intr_unlink = qh->unlink_next; + qh->unlink_next = NULL; + end_unlink_intr(ehci, qh); + } + + /* Handle remaining entries later */ + if (ehci->intr_unlink) { + ehci_enable_event(ehci, EHCI_HRTIMER_UNLINK_INTR, true); + ++ehci->intr_unlink_cycle; + } + ehci->intr_unlinking = false; +} + + /* * Handler functions for the hrtimer event types. * Keep this array in the same order as the event types indexed by @@ -200,6 +233,7 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) static void (*event_handlers[])(struct ehci_hcd *) = { ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ + ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index bf06bbb77ba4..f36f1f85d7fd 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -81,6 +81,7 @@ enum ehci_rh_state { enum ehci_hrtimer_event { EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */ EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ + EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ EHCI_HRTIMER_NUM_EVENTS /* Must come last */ @@ -106,13 +107,16 @@ struct ehci_hcd { /* one per controller */ spinlock_t lock; enum ehci_rh_state rh_state; + /* general schedule support */ + unsigned scanning:1; + bool intr_unlinking:1; + /* async schedule support */ struct ehci_qh *async; struct ehci_qh *dummy; /* For AMD quirk use */ struct ehci_qh *async_unlink; struct ehci_qh *async_unlink_last; struct ehci_qh *qh_scan_next; - unsigned scanning : 1; unsigned async_count; /* async activity count */ /* periodic schedule support */ @@ -123,6 +127,9 @@ struct ehci_hcd { /* one per controller */ unsigned i_thresh; /* uframes HC might cache */ union ehci_shadow *pshadow; /* mirror hw periodic table */ + struct ehci_qh *intr_unlink; + struct ehci_qh *intr_unlink_last; + unsigned intr_unlink_cycle; int next_uframe; /* scan periodic, start here */ unsigned periodic_count; /* periodic activity count */ unsigned uframe_periodic_max; /* max periodic time per uframe */ @@ -385,6 +392,7 @@ struct ehci_qh { struct ehci_qh *unlink_next; /* next on unlink list */ unsigned long unlink_time; + unsigned unlink_cycle; unsigned stamp; u8 needs_rescan; /* Dequeue during giveback */ -- cgit v1.2.3 From bf6387bcd16975ba8952b094f262a359d74e1c8a Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:22:31 -0400 Subject: USB: EHCI: use hrtimer for controller death This patch (as1578) adds an hrtimer event to handle the death of an EHCI controller. When a controller dies, it doesn't necessarily stop running right away. The new event polls at 1-ms intervals to see when all activity has safely stopped. This replaces a busy-wait polling loop in the current code. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 20 ++++++++++---------- drivers/usb/host/ehci-timer.c | 26 ++++++++++++++++++++++++++ drivers/usb/host/ehci.h | 2 ++ 3 files changed, 38 insertions(+), 10 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index edcfd2c4295e..1676c66b8530 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -888,20 +888,20 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) /* PCI errors [4.15.2.4] */ if (unlikely ((status & STS_FATAL) != 0)) { ehci_err(ehci, "fatal error\n"); - ehci->rh_state = EHCI_RH_STOPPING; dbg_cmd(ehci, "fatal", cmd); dbg_status(ehci, "fatal", status); - ehci_halt(ehci); dead: - ehci->enabled_hrtimer_events = 0; - hrtimer_try_to_cancel(&ehci->hrtimer); - ehci_reset(ehci); - ehci_writel(ehci, 0, &ehci->regs->configured_flag); usb_hc_died(hcd); - /* generic layer kills/unlinks all urbs, then - * uses ehci_stop to clean up the rest - */ - bh = 1; + + /* Don't let the controller do anything more */ + ehci->rh_state = EHCI_RH_STOPPING; + ehci->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE); + ehci_writel(ehci, ehci->command, &ehci->regs->command); + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + ehci_handle_controller_death(ehci); + + /* Handle completions when the controller stops */ + bh = 0; } if (bh) diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index bd8b591771b0..0c5326a8883c 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -69,6 +69,7 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) static unsigned event_delays_ns[] = { 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ + 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ @@ -193,6 +194,30 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) } +/* Poll the STS_HALT status bit; see when a dead controller stops */ +static void ehci_handle_controller_death(struct ehci_hcd *ehci) +{ + if (!(ehci_readl(ehci, &ehci->regs->status) & STS_HALT)) { + + /* Give up after a few milliseconds */ + if (ehci->died_poll_count++ < 5) { + /* Try again later */ + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_DEAD, true); + return; + } + ehci_warn(ehci, "Waited too long for the controller to stop, giving up\n"); + } + + /* Clean up the mess */ + ehci->rh_state = EHCI_RH_HALTED; + ehci_writel(ehci, 0, &ehci->regs->configured_flag); + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + ehci_work(ehci); + + /* Not in process context, so don't try to reset the controller */ +} + + /* Handle unlinked interrupt QHs once they are gone from the hardware */ static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) { @@ -233,6 +258,7 @@ static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) static void (*event_handlers[])(struct ehci_hcd *) = { ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ + ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index f36f1f85d7fd..6874d89b0b64 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -81,6 +81,7 @@ enum ehci_rh_state { enum ehci_hrtimer_event { EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */ EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ + EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ @@ -97,6 +98,7 @@ struct ehci_hcd { /* one per controller */ int PSS_poll_count; int ASS_poll_count; + int died_poll_count; /* glue to PCI and HCD framework */ struct ehci_caps __iomem *caps; -- cgit v1.2.3 From 55934eb3b9fa52eb53b9d7342267fc73c38206aa Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:22:35 -0400 Subject: USB: EHCI: use hrtimer for (s)iTD deallocation This patch (as1579) adds an hrtimer event to handle deallocation of iTDs and siTDs in ehci-hcd. Because of the frame-oriented approach used by the EHCI periodic schedule, the hardware can continue to access the Transfer Descriptor for isochronous (or split-isochronous) transactions for up to a millisecond after the transaction completes. The iTD (or siTD) must not be reused before then. The strategy currently used involves putting completed iTDs on a list of cached entries and every so often returning them to the endpoint's free list. The new strategy reduces overhead by putting completed iTDs back on the free list immediately, although they are not reused until it is safe to do so. When the isochronous endpoint stops (its queue becomes empty), the iTDs on its free list get moved to a global list, from which they will be deallocated after a minimum of 2 ms. This delay is what the new hrtimer event is for. Overall this may not be a tremendous improvement over the current code, but to me it seems a lot more clear and logical. In addition, it removes the need for each iTD to keep a reference to the ehci_iso_stream it belongs to, since the iTD never needs to be moved back to the stream's free list from the global list. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 1 + drivers/usb/host/ehci-hub.c | 1 + drivers/usb/host/ehci-mem.c | 1 - drivers/usb/host/ehci-sched.c | 137 ++++++++++++------------------------------ drivers/usb/host/ehci-timer.c | 50 +++++++++++++-- drivers/usb/host/ehci.h | 5 +- 6 files changed, 90 insertions(+), 105 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 1676c66b8530..98b945840c9e 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -509,6 +509,7 @@ static void ehci_stop (struct usb_hcd *hcd) spin_lock_irq (&ehci->lock); if (ehci->async) ehci_work (ehci); + end_free_itds(ehci); spin_unlock_irq (&ehci->lock); ehci_mem_cleanup (ehci); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 8aa740dc510d..6ad806bbe468 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -303,6 +303,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (ehci->async_unlink) end_unlink_async(ehci); ehci_handle_intr_unlinks(ehci); + end_free_itds(ehci); /* allow remote wakeup */ mask = INTR_MASK; diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index 93132d8ad360..ef2c3a1eca4b 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -118,7 +118,6 @@ fail: static void ehci_mem_cleanup (struct ehci_hcd *ehci) { - free_cached_lists(ehci); if (ehci->async) qh_destroy(ehci, ehci->async); ehci->async = NULL; diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index eec8446f8ded..1e4f13c11b6a 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -1045,31 +1045,6 @@ iso_stream_put(struct ehci_hcd *ehci, struct ehci_iso_stream *stream) if (stream->refcount == 1) { // BUG_ON (!list_empty(&stream->td_list)); - while (!list_empty (&stream->free_list)) { - struct list_head *entry; - - entry = stream->free_list.next; - list_del (entry); - - /* knows about ITD vs SITD */ - if (stream->highspeed) { - struct ehci_itd *itd; - - itd = list_entry (entry, struct ehci_itd, - itd_list); - dma_pool_free (ehci->itd_pool, itd, - itd->itd_dma); - } else { - struct ehci_sitd *sitd; - - sitd = list_entry (entry, struct ehci_sitd, - sitd_list); - dma_pool_free (ehci->sitd_pool, sitd, - sitd->sitd_dma); - } - } - - stream->bEndpointAddress &= 0x0f; if (stream->ep) stream->ep->hcpriv = NULL; @@ -1230,17 +1205,19 @@ itd_urb_transaction ( spin_lock_irqsave (&ehci->lock, flags); for (i = 0; i < num_itds; i++) { - /* free_list.next might be cache-hot ... but maybe - * the HC caches it too. avoid that issue for now. + /* + * Use iTDs from the free list, but not iTDs that may + * still be in use by the hardware. */ - - /* prefer previously-allocated itds */ - if (likely (!list_empty(&stream->free_list))) { - itd = list_entry (stream->free_list.prev, + if (likely(!list_empty(&stream->free_list))) { + itd = list_first_entry(&stream->free_list, struct ehci_itd, itd_list); + if (itd->frame == ehci->clock_frame) + goto alloc_itd; list_del (&itd->itd_list); itd_dma = itd->itd_dma; } else { + alloc_itd: spin_unlock_irqrestore (&ehci->lock, flags); itd = dma_pool_alloc (ehci->itd_pool, mem_flags, &itd_dma); @@ -1762,24 +1739,18 @@ itd_complete ( done: itd->urb = NULL; - if (ehci->clock_frame != itd->frame || itd->index[7] != -1) { - /* OK to recycle this ITD now. */ - itd->stream = NULL; - list_move(&itd->itd_list, &stream->free_list); - iso_stream_put(ehci, stream); - } else { - /* HW might remember this ITD, so we can't recycle it yet. - * Move it to a safe place until a new frame starts. - */ - list_move(&itd->itd_list, &ehci->cached_itd_list); - if (stream->refcount == 2) { - /* If iso_stream_put() were called here, stream - * would be freed. Instead, just prevent reuse. - */ - stream->ep->hcpriv = NULL; - stream->ep = NULL; - } + + /* Add to the end of the free list for later reuse */ + list_move_tail(&itd->itd_list, &stream->free_list); + + /* Recycle the iTDs when the pipeline is empty (ep no longer in use) */ + if (list_empty(&stream->td_list)) { + list_splice_tail_init(&stream->free_list, + &ehci->cached_itd_list); + start_free_itds(ehci); } + + iso_stream_put(ehci, stream); return retval; } @@ -1930,17 +1901,19 @@ sitd_urb_transaction ( * means we never need two sitds for full speed packets. */ - /* free_list.next might be cache-hot ... but maybe - * the HC caches it too. avoid that issue for now. + /* + * Use siTDs from the free list, but not siTDs that may + * still be in use by the hardware. */ - - /* prefer previously-allocated sitds */ - if (!list_empty(&stream->free_list)) { - sitd = list_entry (stream->free_list.prev, + if (likely(!list_empty(&stream->free_list))) { + sitd = list_first_entry(&stream->free_list, struct ehci_sitd, sitd_list); + if (sitd->frame == ehci->clock_frame) + goto alloc_sitd; list_del (&sitd->sitd_list); sitd_dma = sitd->sitd_dma; } else { + alloc_sitd: spin_unlock_irqrestore (&ehci->lock, flags); sitd = dma_pool_alloc (ehci->sitd_pool, mem_flags, &sitd_dma); @@ -2157,24 +2130,18 @@ sitd_complete ( done: sitd->urb = NULL; - if (ehci->clock_frame != sitd->frame) { - /* OK to recycle this SITD now. */ - sitd->stream = NULL; - list_move(&sitd->sitd_list, &stream->free_list); - iso_stream_put(ehci, stream); - } else { - /* HW might remember this SITD, so we can't recycle it yet. - * Move it to a safe place until a new frame starts. - */ - list_move(&sitd->sitd_list, &ehci->cached_sitd_list); - if (stream->refcount == 2) { - /* If iso_stream_put() were called here, stream - * would be freed. Instead, just prevent reuse. - */ - stream->ep->hcpriv = NULL; - stream->ep = NULL; - } + + /* Add to the end of the free list for later reuse */ + list_move_tail(&sitd->sitd_list, &stream->free_list); + + /* Recycle the siTDs when the pipeline is empty (ep no longer in use) */ + if (list_empty(&stream->td_list)) { + list_splice_tail_init(&stream->free_list, + &ehci->cached_sitd_list); + start_free_itds(ehci); } + + iso_stream_put(ehci, stream); return retval; } @@ -2239,28 +2206,6 @@ done: /*-------------------------------------------------------------------------*/ -static void free_cached_lists(struct ehci_hcd *ehci) -{ - struct ehci_itd *itd, *n; - struct ehci_sitd *sitd, *sn; - - list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) { - struct ehci_iso_stream *stream = itd->stream; - itd->stream = NULL; - list_move(&itd->itd_list, &stream->free_list); - iso_stream_put(ehci, stream); - } - - list_for_each_entry_safe(sitd, sn, &ehci->cached_sitd_list, sitd_list) { - struct ehci_iso_stream *stream = sitd->stream; - sitd->stream = NULL; - list_move(&sitd->sitd_list, &stream->free_list); - iso_stream_put(ehci, stream); - } -} - -/*-------------------------------------------------------------------------*/ - static void scan_periodic (struct ehci_hcd *ehci) { @@ -2282,10 +2227,7 @@ scan_periodic (struct ehci_hcd *ehci) clock = now_uframe + mod - 1; clock_frame = -1; } - if (ehci->clock_frame != clock_frame) { - free_cached_lists(ehci); - ehci->clock_frame = clock_frame; - } + ehci->clock_frame = clock_frame; clock &= mod - 1; clock_frame = clock >> 3; ++ehci->periodic_stamp; @@ -2463,7 +2405,6 @@ restart: clock = now; clock_frame = clock >> 3; if (ehci->clock_frame != clock_frame) { - free_cached_lists(ehci); ehci->clock_frame = clock_frame; ++ehci->periodic_stamp; } diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 0c5326a8883c..8feb60ff4228 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -71,6 +71,7 @@ static unsigned event_delays_ns[] = { 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ + 2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; @@ -165,7 +166,6 @@ static void ehci_poll_PSS(struct ehci_hcd *ehci) /* The status is up-to-date; restart or stop the schedule as needed */ if (want == 0) { /* Stopped */ - free_cached_lists(ehci); if (ehci->periodic_count > 0) { /* make sure ehci_work scans these */ @@ -188,9 +188,6 @@ static void ehci_poll_PSS(struct ehci_hcd *ehci) static void ehci_disable_PSE(struct ehci_hcd *ehci) { ehci_clear_command_bit(ehci, CMD_PSE); - - /* Poll to see when it actually stops */ - ehci_enable_event(ehci, EHCI_HRTIMER_POLL_PSS, true); } @@ -250,6 +247,50 @@ static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) } +/* Start another free-iTDs/siTDs cycle */ +static void start_free_itds(struct ehci_hcd *ehci) +{ + if (!(ehci->enabled_hrtimer_events & BIT(EHCI_HRTIMER_FREE_ITDS))) { + ehci->last_itd_to_free = list_entry( + ehci->cached_itd_list.prev, + struct ehci_itd, itd_list); + ehci->last_sitd_to_free = list_entry( + ehci->cached_sitd_list.prev, + struct ehci_sitd, sitd_list); + ehci_enable_event(ehci, EHCI_HRTIMER_FREE_ITDS, true); + } +} + +/* Wait for controller to stop using old iTDs and siTDs */ +static void end_free_itds(struct ehci_hcd *ehci) +{ + struct ehci_itd *itd, *n; + struct ehci_sitd *sitd, *sn; + + if (ehci->rh_state < EHCI_RH_RUNNING) { + ehci->last_itd_to_free = NULL; + ehci->last_sitd_to_free = NULL; + } + + list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) { + list_del(&itd->itd_list); + dma_pool_free(ehci->itd_pool, itd, itd->itd_dma); + if (itd == ehci->last_itd_to_free) + break; + } + list_for_each_entry_safe(sitd, sn, &ehci->cached_sitd_list, sitd_list) { + list_del(&sitd->sitd_list); + dma_pool_free(ehci->sitd_pool, sitd, sitd->sitd_dma); + if (sitd == ehci->last_sitd_to_free) + break; + } + + if (!list_empty(&ehci->cached_itd_list) || + !list_empty(&ehci->cached_sitd_list)) + start_free_itds(ehci); +} + + /* * Handler functions for the hrtimer event types. * Keep this array in the same order as the event types indexed by @@ -260,6 +301,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = { ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ + end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 6874d89b0b64..bcfbb175e2b4 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -83,6 +83,7 @@ enum ehci_hrtimer_event { EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ + EHCI_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ EHCI_HRTIMER_NUM_EVENTS /* Must come last */ @@ -139,7 +140,9 @@ struct ehci_hcd { /* one per controller */ /* list of itds & sitds completed while clock_frame was still active */ struct list_head cached_itd_list; + struct ehci_itd *last_itd_to_free; struct list_head cached_sitd_list; + struct ehci_sitd *last_sitd_to_free; unsigned clock_frame; /* per root hub port */ @@ -250,8 +253,6 @@ timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action) clear_bit (action, &ehci->actions); } -static void free_cached_lists(struct ehci_hcd *ehci); - /*-------------------------------------------------------------------------*/ #include -- cgit v1.2.3 From 9d9387475af261949f61a5ec465e1f762d7be08a Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:22:44 -0400 Subject: 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: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 66 +++++++++---------------------------------- drivers/usb/host/ehci-hub.c | 1 - drivers/usb/host/ehci-q.c | 4 +-- drivers/usb/host/ehci-timer.c | 45 +++++++++++++++++++++++++++++ drivers/usb/host/ehci.h | 16 +---------- 5 files changed, 61 insertions(+), 71 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index fb1966cf5649..f74ba277c22d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -93,7 +93,6 @@ static const char hcd_name [] = "ehci_hcd"; */ #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_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1) /* 5-ms async qh unlink delay */ @@ -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) { struct ehci_hcd *ehci = (struct ehci_hcd *) param; @@ -418,7 +372,6 @@ static void ehci_shutdown(struct usb_hcd *hcd) struct ehci_hcd *ehci = hcd_to_ehci(hcd); del_timer_sync(&ehci->watchdog); - del_timer_sync(&ehci->iaa_watchdog); spin_lock_irq(&ehci->lock); ehci->rh_state = EHCI_RH_STOPPING; @@ -491,7 +444,6 @@ static void ehci_stop (struct usb_hcd *hcd) /* no more interrupts ... */ del_timer_sync (&ehci->watchdog); - del_timer_sync(&ehci->iaa_watchdog); spin_lock_irq(&ehci->lock); ehci->enabled_hrtimer_events = 0; @@ -547,10 +499,6 @@ static int ehci_init(struct usb_hcd *hcd) ehci->watchdog.function = ehci_watchdog; 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); ehci->hrtimer.function = ehci_hrtimer_func; ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT; @@ -830,6 +778,20 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) /* complete the unlinking of some qh [4.15.2.3] */ 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 */ if (cmd & CMD_IAAD) ehci_dbg(ehci, "IAA with IAAD still set?\n"); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 6ad806bbe468..3097872b1e74 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -209,7 +209,6 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (time_before (jiffies, ehci->next_statechange)) msleep(5); del_timer_sync(&ehci->watchdog); - del_timer_sync(&ehci->iaa_watchdog); spin_lock_irq (&ehci->lock); diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index d68764ef4476..7b35933d91a6 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1173,8 +1173,6 @@ static void end_unlink_async (struct ehci_hcd *ehci) struct ehci_qh *qh = ehci->async_unlink; struct ehci_qh *next; - iaa_watchdog_done(ehci); - // qh->hw_next = cpu_to_hc32(qh->qh_dma); qh->qh_state = QH_STATE_IDLE; qh->qh_next.qh = NULL; @@ -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); (void)ehci_readl(ehci, &ehci->regs->command); - iaa_watchdog_start(ehci); + ehci_enable_event(ehci, EHCI_HRTIMER_IAA_WATCHDOG, true); } /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 8feb60ff4228..aab042064226 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -72,6 +72,7 @@ static unsigned event_delays_ns[] = { 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ 2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */ + 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IAA_WATCHDOG */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; @@ -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. * Keep this array in the same order as the event types indexed by @@ -302,6 +346,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = { ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */ + ehci_iaa_watchdog, /* EHCI_HRTIMER_IAA_WATCHDOG */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 755e30b0000b..d9414aae610f 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -84,6 +84,7 @@ enum ehci_hrtimer_event { EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ 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_ASYNC, /* Wait to disable async sched */ EHCI_HRTIMER_NUM_EVENTS /* Must come last */ @@ -168,7 +169,6 @@ struct ehci_hcd { /* one per controller */ struct dma_pool *itd_pool; /* itd per iso urb */ struct dma_pool *sitd_pool; /* sitd per split iso urb */ - struct timer_list iaa_watchdog; struct timer_list watchdog; unsigned long actions; unsigned periodic_stamp; @@ -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); } - -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 { TIMER_IO_WATCHDOG, TIMER_ASYNC_SHRINK, -- cgit v1.2.3 From 3c273a056bf46167f0a1309c2ba72282a17d2541 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:22:49 -0400 Subject: USB: EHCI: unlink multiple async QHs together This patch (as1582) changes ehci-hcd's strategy for unlinking async QHs. Currently the driver never unlinks more than one QH at a time. This can be inefficient and cause unnecessary delays, since a QH cannot be reused while it is waiting to be unlinked. The new strategy unlinks all the waiting QHs at once. In practice the improvement won't be very big, because it's somewhat uncommon to have two or more QHs waiting to be unlinked at any time. But it does happen, and in any case, doing things this way makes more sense IMO. The change requires the async unlinking code to be refactored slightly. Now in addition to the routines for starting and ending an unlink, there are new routines for unlinking a single QH and starting an IAA cycle. This approach is needed because there are two separate paths for unlinking async QHs: When a transfer error occurs or an URB is cancelled, the QH must be unlinked right away; When a QH has been idle sufficiently long, it is unlinked to avoid consuming DMA bandwidth uselessly. In the first case we want the unlink to proceed as quickly as possible, whereas in the second case we can afford to batch several QHs together and unlink them all at once. Hence the division of labor. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 35 ++-------- drivers/usb/host/ehci-hub.c | 3 +- drivers/usb/host/ehci-q.c | 149 +++++++++++++++++++++++++----------------- drivers/usb/host/ehci-timer.c | 3 +- drivers/usb/host/ehci.h | 2 + 5 files changed, 98 insertions(+), 94 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index f74ba277c22d..86e8ee169c67 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -795,7 +795,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) /* guard against (alleged) silicon errata */ if (cmd & CMD_IAAD) ehci_dbg(ehci, "IAA with IAAD still set?\n"); - if (ehci->async_unlink) { + if (ehci->async_iaa) { COUNT(ehci->stats.iaa); end_unlink_async(ehci); } else @@ -926,33 +926,6 @@ static int ehci_urb_enqueue ( } } -static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) -{ - /* failfast */ - if (ehci->rh_state < EHCI_RH_RUNNING && ehci->async_unlink) - end_unlink_async(ehci); - - /* If the QH isn't linked then there's nothing we can do - * unless we were called during a giveback, in which case - * qh_completions() has to deal with it. - */ - if (qh->qh_state != QH_STATE_LINKED) { - if (qh->qh_state == QH_STATE_COMPLETING) - qh->needs_rescan = 1; - return; - } - - /* defer till later if busy */ - if (ehci->async_unlink) { - qh->qh_state = QH_STATE_UNLINK_WAIT; - ehci->async_unlink_last->unlink_next = qh; - ehci->async_unlink_last = qh; - - /* start IAA cycle */ - } else - start_unlink_async (ehci, qh); -} - /* remove from hardware lists * completions normally happen asynchronously */ @@ -979,7 +952,7 @@ static int ehci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) switch (qh->qh_state) { case QH_STATE_LINKED: case QH_STATE_COMPLETING: - unlink_async(ehci, qh); + start_unlink_async(ehci, qh); break; case QH_STATE_UNLINK: case QH_STATE_UNLINK_WAIT: @@ -1070,7 +1043,7 @@ rescan: * may already be unlinked. */ if (tmp) - unlink_async(ehci, qh); + start_unlink_async(ehci, qh); /* FALL THROUGH */ case QH_STATE_UNLINK: /* wait for hw to finish? */ case QH_STATE_UNLINK_WAIT: @@ -1133,7 +1106,7 @@ ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) * re-linking will call qh_refresh(). */ if (eptype == USB_ENDPOINT_XFER_BULK) - unlink_async(ehci, qh); + start_unlink_async(ehci, qh); else start_unlink_intr(ehci, qh); } diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 3097872b1e74..a3822700e496 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -299,8 +299,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ehci_halt (ehci); ehci->rh_state = EHCI_RH_SUSPENDED; - if (ehci->async_unlink) - end_unlink_async(ehci); + end_unlink_async(ehci); ehci_handle_intr_unlinks(ehci); end_free_itds(ehci); diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 7b35933d91a6..181832921c53 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -293,9 +293,6 @@ __acquires(ehci->lock) spin_lock (&ehci->lock); } -static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh); -static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh); - static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh); /* @@ -1166,82 +1163,114 @@ submit_async ( /*-------------------------------------------------------------------------*/ -/* the async qh for the qtds being unlinked are now gone from the HC */ - -static void end_unlink_async (struct ehci_hcd *ehci) +static void single_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh) { - struct ehci_qh *qh = ehci->async_unlink; - struct ehci_qh *next; + struct ehci_qh *prev; - // qh->hw_next = cpu_to_hc32(qh->qh_dma); - qh->qh_state = QH_STATE_IDLE; - qh->qh_next.qh = NULL; + /* Add to the end of the list of QHs waiting for the next IAAD */ + qh->qh_state = QH_STATE_UNLINK; + if (ehci->async_unlink) + ehci->async_unlink_last->unlink_next = qh; + else + ehci->async_unlink = qh; + ehci->async_unlink_last = qh; + + /* Unlink it from the schedule */ + prev = ehci->async; + while (prev->qh_next.qh != qh) + prev = prev->qh_next.qh; + + prev->hw->hw_next = qh->hw->hw_next; + prev->qh_next = qh->qh_next; + if (ehci->qh_scan_next == qh) + ehci->qh_scan_next = qh->qh_next.qh; +} - /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ - next = qh->unlink_next; - ehci->async_unlink = next; - qh->unlink_next = NULL; +static void start_iaa_cycle(struct ehci_hcd *ehci, bool nested) +{ + /* + * Do nothing if an IAA cycle is already running or + * if one will be started shortly. + */ + if (ehci->async_iaa || ehci->async_unlinking) + return; - qh_completions (ehci, qh); + /* Do all the waiting QHs at once */ + ehci->async_iaa = ehci->async_unlink; + ehci->async_unlink = NULL; - if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) - qh_link_async (ehci, qh); + /* If the controller isn't running, we don't have to wait for it */ + if (unlikely(ehci->rh_state < EHCI_RH_RUNNING)) { + if (!nested) /* Avoid recursion */ + end_unlink_async(ehci); - disable_async(ehci); + /* Otherwise start a new IAA cycle */ + } else { + /* Make sure the unlinks are all visible to the hardware */ + wmb(); - if (next) { - ehci->async_unlink = NULL; - start_unlink_async (ehci, next); + ehci_writel(ehci, ehci->command | CMD_IAAD, + &ehci->regs->command); + ehci_readl(ehci, &ehci->regs->command); + ehci_enable_event(ehci, EHCI_HRTIMER_IAA_WATCHDOG, true); } +} + +/* the async qh for the qtds being unlinked are now gone from the HC */ + +static void end_unlink_async(struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; if (ehci->has_synopsys_hc_bug) ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next); + + /* Process the idle QHs */ + restart: + ehci->async_unlinking = true; + while (ehci->async_iaa) { + qh = ehci->async_iaa; + ehci->async_iaa = qh->unlink_next; + qh->unlink_next = NULL; + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + + qh_completions(ehci, qh); + if (!list_empty(&qh->qtd_list) && + ehci->rh_state == EHCI_RH_RUNNING) + qh_link_async(ehci, qh); + disable_async(ehci); + } + ehci->async_unlinking = false; + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (ehci->async_unlink) { + start_iaa_cycle(ehci, true); + if (unlikely(ehci->rh_state < EHCI_RH_RUNNING)) + goto restart; + } } /* makes sure the async qh will become idle */ /* caller must own ehci->lock */ -static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) +static void start_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh) { - struct ehci_qh *prev; - -#ifdef DEBUG - assert_spin_locked(&ehci->lock); - if (ehci->async_unlink - || (qh->qh_state != QH_STATE_LINKED - && qh->qh_state != QH_STATE_UNLINK_WAIT) - ) - BUG (); -#endif - - qh->qh_state = QH_STATE_UNLINK; - ehci->async_unlink = qh; - if (!qh->unlink_next) - ehci->async_unlink_last = qh; - - prev = ehci->async; - while (prev->qh_next.qh != qh) - prev = prev->qh_next.qh; - - prev->hw->hw_next = qh->hw->hw_next; - prev->qh_next = qh->qh_next; - if (ehci->qh_scan_next == qh) - ehci->qh_scan_next = qh->qh_next.qh; - wmb (); - - /* If the controller isn't running, we don't have to wait for it */ - if (unlikely(ehci->rh_state < EHCI_RH_RUNNING)) { - /* if (unlikely (qh->unlink_next != 0)) - * this will recurse, probably not much - */ - end_unlink_async (ehci); + /* + * If the QH isn't linked then there's nothing we can do + * unless we were called during a giveback, in which case + * qh_completions() has to deal with it. + */ + if (qh->qh_state != QH_STATE_LINKED) { + if (qh->qh_state == QH_STATE_COMPLETING) + qh->needs_rescan = 1; return; } - ehci_writel(ehci, ehci->command | CMD_IAAD, &ehci->regs->command); - (void)ehci_readl(ehci, &ehci->regs->command); - ehci_enable_event(ehci, EHCI_HRTIMER_IAA_WATCHDOG, true); + single_unlink_async(ehci, qh); + start_iaa_cycle(ehci, false); } /*-------------------------------------------------------------------------*/ @@ -1269,11 +1298,11 @@ static void scan_async (struct ehci_hcd *ehci) * drops the lock. That's why ehci->qh_scan_next * always holds the next qh to scan; if the next qh * gets unlinked then ehci->qh_scan_next is adjusted - * in start_unlink_async(). + * in single_unlink_async(). */ temp = qh_completions(ehci, qh); if (qh->needs_rescan) - unlink_async(ehci, qh); + start_unlink_async(ehci, qh); qh->unlink_time = jiffies + EHCI_SHRINK_JIFFIES; if (temp != 0) goto rescan; diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index aab042064226..8ca5f152f5bd 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -211,6 +211,7 @@ static void ehci_handle_controller_death(struct ehci_hcd *ehci) ehci_writel(ehci, 0, &ehci->regs->configured_flag); ehci_writel(ehci, 0, &ehci->regs->intr_enable); ehci_work(ehci); + end_unlink_async(ehci); /* Not in process context, so don't try to reset the controller */ } @@ -304,7 +305,7 @@ static void ehci_iaa_watchdog(struct ehci_hcd *ehci) * (a) SMP races against real IAA firing and retriggering, and * (b) clean HC shutdown, when IAA watchdog was pending. */ - if (ehci->async_unlink) { + if (ehci->async_iaa) { u32 cmd, status; /* If we get here, IAA is *REALLY* late. It's barely diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index d9414aae610f..1a782775881b 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -114,12 +114,14 @@ struct ehci_hcd { /* one per controller */ /* general schedule support */ unsigned scanning:1; bool intr_unlinking:1; + bool async_unlinking:1; /* async schedule support */ struct ehci_qh *async; struct ehci_qh *dummy; /* For AMD quirk use */ struct ehci_qh *async_unlink; struct ehci_qh *async_unlink_last; + struct ehci_qh *async_iaa; struct ehci_qh *qh_scan_next; unsigned async_count; /* async activity count */ -- cgit v1.2.3 From 32830f207691176234b4c4dd17f0d7ab6d87d94b Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:22:53 -0400 Subject: USB: EHCI: use hrtimer for unlinking empty async QHs This patch (as1583) changes ehci-hcd to use an hrtimer event for unlinking empty (unused) async QHs instead of using a kernel timer. The check for empty QHs is moved to a new routine, where it doesn't require going through an entire scan of both the async and periodic schedules. And it can unlink multiple QHs at once, unlike the current code. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 16 +-------- drivers/usb/host/ehci-hub.c | 1 + drivers/usb/host/ehci-q.c | 78 +++++++++++++++++++++++++++++-------------- drivers/usb/host/ehci-timer.c | 2 ++ drivers/usb/host/ehci.h | 4 +-- 5 files changed, 59 insertions(+), 42 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 86e8ee169c67..74ffd20edff8 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -94,8 +94,6 @@ static const char hcd_name [] = "ehci_hcd"; #define EHCI_TUNE_FLS 1 /* (medium) 512-frame schedule */ #define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ -#define EHCI_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1) - /* 5-ms async qh unlink delay */ /* Initial IRQ latency: faster than hw default */ static int log2_irq_thresh = 0; // 0 to 6 @@ -130,15 +128,6 @@ MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us"); static void timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) { - /* Don't override timeouts which shrink or (later) disable - * the async ring; just the I/O watchdog. Note that if a - * SHRINK were pending, OFF would never be requested. - */ - if (timer_pending(&ehci->watchdog) - && (BIT(TIMER_ASYNC_SHRINK) - & ehci->actions)) - return; - if (!test_and_set_bit(action, &ehci->actions)) { unsigned long t; @@ -148,10 +137,6 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) return; t = EHCI_IO_JIFFIES; break; - /* case TIMER_ASYNC_SHRINK: */ - default: - t = EHCI_SHRINK_JIFFIES; - break; } mod_timer(&ehci->watchdog, t + jiffies); } @@ -307,6 +292,7 @@ static void ehci_quiesce (struct ehci_hcd *ehci) /*-------------------------------------------------------------------------*/ static void end_unlink_async(struct ehci_hcd *ehci); +static void unlink_empty_async(struct ehci_hcd *ehci); static void ehci_work(struct ehci_hcd *ehci); static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index a3822700e496..5d84562e2716 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -300,6 +300,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ehci->rh_state = EHCI_RH_SUSPENDED; end_unlink_async(ehci); + unlink_empty_async(ehci); ehci_handle_intr_unlinks(ehci); end_free_itds(ehci); diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 181832921c53..bae931767825 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1205,7 +1205,7 @@ static void start_iaa_cycle(struct ehci_hcd *ehci, bool nested) end_unlink_async(ehci); /* Otherwise start a new IAA cycle */ - } else { + } else if (likely(ehci->rh_state == EHCI_RH_RUNNING)) { /* Make sure the unlinks are all visible to the hardware */ wmb(); @@ -1253,6 +1253,39 @@ static void end_unlink_async(struct ehci_hcd *ehci) } } +static void unlink_empty_async(struct ehci_hcd *ehci) +{ + struct ehci_qh *qh, *next; + bool stopped = (ehci->rh_state < EHCI_RH_RUNNING); + bool check_unlinks_later = false; + + /* Unlink all the async QHs that have been empty for a timer cycle */ + next = ehci->async->qh_next.qh; + while (next) { + qh = next; + next = qh->qh_next.qh; + + if (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED) { + if (!stopped && qh->unlink_cycle == + ehci->async_unlink_cycle) + check_unlinks_later = true; + else + single_unlink_async(ehci, qh); + } + } + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (ehci->async_unlink) + start_iaa_cycle(ehci, false); + + /* QHs that haven't been empty for long enough will be handled later */ + if (check_unlinks_later) { + ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true); + ++ehci->async_unlink_cycle; + } +} + /* makes sure the async qh will become idle */ /* caller must own ehci->lock */ @@ -1277,12 +1310,8 @@ static void start_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh) static void scan_async (struct ehci_hcd *ehci) { - bool stopped; struct ehci_qh *qh; - enum ehci_timer_action action = TIMER_IO_WATCHDOG; - - timer_action_done (ehci, TIMER_ASYNC_SHRINK); - stopped = (ehci->rh_state < EHCI_RH_RUNNING); + bool check_unlinks_later = false; ehci->qh_scan_next = ehci->async->qh_next.qh; while (ehci->qh_scan_next) { @@ -1301,28 +1330,27 @@ static void scan_async (struct ehci_hcd *ehci) * in single_unlink_async(). */ temp = qh_completions(ehci, qh); - if (qh->needs_rescan) + if (qh->needs_rescan) { start_unlink_async(ehci, qh); - qh->unlink_time = jiffies + EHCI_SHRINK_JIFFIES; - if (temp != 0) + } else if (list_empty(&qh->qtd_list) + && qh->qh_state == QH_STATE_LINKED) { + qh->unlink_cycle = ehci->async_unlink_cycle; + check_unlinks_later = true; + } else if (temp != 0) goto rescan; } + } - /* unlink idle entries, reducing DMA usage as well - * as HCD schedule-scanning costs. delay for any qh - * we just scanned, there's a not-unusual case that it - * doesn't stay idle for long. - * (plus, avoids some kind of re-activation race.) - */ - if (list_empty(&qh->qtd_list) - && qh->qh_state == QH_STATE_LINKED) { - if (!ehci->async_unlink && (stopped || - time_after_eq(jiffies, qh->unlink_time))) - start_unlink_async(ehci, qh); - else - action = TIMER_ASYNC_SHRINK; - } + /* + * Unlink empty entries, reducing DMA usage as well + * as HCD schedule-scanning costs. Delay for any qh + * we just scanned, there's a not-unusual case that it + * doesn't stay idle for long. + */ + if (check_unlinks_later && ehci->rh_state == EHCI_RH_RUNNING && + !(ehci->enabled_hrtimer_events & + BIT(EHCI_HRTIMER_ASYNC_UNLINKS))) { + ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true); + ++ehci->async_unlink_cycle; } - if (action == TIMER_ASYNC_SHRINK) - timer_action (ehci, TIMER_ASYNC_SHRINK); } diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 8ca5f152f5bd..a823290b5139 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -72,6 +72,7 @@ static unsigned event_delays_ns[] = { 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ 2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */ + 6 * NSEC_PER_MSEC, /* EHCI_HRTIMER_ASYNC_UNLINKS */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IAA_WATCHDOG */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ @@ -347,6 +348,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = { ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */ + unlink_empty_async, /* EHCI_HRTIMER_ASYNC_UNLINKS */ ehci_iaa_watchdog, /* EHCI_HRTIMER_IAA_WATCHDOG */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 1a782775881b..303c36cc99c9 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -84,6 +84,7 @@ enum ehci_hrtimer_event { EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ EHCI_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ + EHCI_HRTIMER_ASYNC_UNLINKS, /* Unlink empty async QHs */ EHCI_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ @@ -123,6 +124,7 @@ struct ehci_hcd { /* one per controller */ struct ehci_qh *async_unlink_last; struct ehci_qh *async_iaa; struct ehci_qh *qh_scan_next; + unsigned async_unlink_cycle; unsigned async_count; /* async activity count */ /* periodic schedule support */ @@ -232,7 +234,6 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) enum ehci_timer_action { TIMER_IO_WATCHDOG, - TIMER_ASYNC_SHRINK, }; static inline void @@ -382,7 +383,6 @@ struct ehci_qh { struct ehci_qtd *dummy; struct ehci_qh *unlink_next; /* next on unlink list */ - unsigned long unlink_time; unsigned unlink_cycle; unsigned stamp; -- cgit v1.2.3 From 569b394f53f0abd177cc665c9b4ace89e3f4c7fb Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:23:00 -0400 Subject: USB: EHCI: always scan each interrupt QH This patch (as1585) fixes a bug in ehci-hcd's scheme for scanning interrupt QHs. Currently a single routine takes care of scanning everything on the periodic schedule. Whenever an interrupt occurs, it scans all isochronous and interrupt URBs scheduled for frames that have elapsed since the last scan. This has two disadvantages. The first is relatively minor: An interrupt QH is likely to end up getting scanned multiple times, particularly if the last scan was not fairly recent. (The current code avoids this by maintaining a periodic_stamp in each interrupt QH.) The second is more serious. The periodic schedule wraps around. If the last scan occurred during frame N, and the next scan occurs when the schedule has gone through an entire cycle and is back at frame N, the scanning code won't look at any frames other than N. Consequently it won't see any QHs that completed during frame N-1 or earlier. The patch replaces the entire frame-based approach for scanning interrupt QHs with a new routine using a list-based approach, the same as for async QHs. This has a slight disadvantage, because it means that all interrupt QHs have to be scanned every time. But it is more robust than the current approach. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 7 +++- drivers/usb/host/ehci-q.c | 3 +- drivers/usb/host/ehci-sched.c | 96 ++++++++++++++++++++++++++----------------- drivers/usb/host/ehci-timer.c | 7 +--- drivers/usb/host/ehci.h | 8 ++-- 5 files changed, 71 insertions(+), 50 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index f6cf1d178107..c13dad8a8503 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -410,8 +410,10 @@ static void ehci_work (struct ehci_hcd *ehci) ehci->need_rescan = false; if (ehci->async_count) scan_async(ehci); - if (ehci->next_uframe != -1) - scan_periodic (ehci); + if (ehci->intr_count > 0) + scan_intr(ehci); + if (ehci->isoc_count > 0) + scan_isoc(ehci); if (ehci->need_rescan) goto rescan; ehci->scanning = false; @@ -509,6 +511,7 @@ static int ehci_init(struct usb_hcd *hcd) * periodic_size can shrink by USBCMD update if hcc_params allows. */ ehci->periodic_size = DEFAULT_I_TDPS; + INIT_LIST_HEAD(&ehci->intr_qh_list); INIT_LIST_HEAD(&ehci->cached_itd_list); INIT_LIST_HEAD(&ehci->cached_sitd_list); diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index bae931767825..c9c7f7b3b7db 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -322,7 +322,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) * * It's a bug for qh->qh_state to be anything other than * QH_STATE_IDLE, unless our caller is scan_async() or - * scan_periodic(). + * scan_intr(). */ state = qh->qh_state; qh->qh_state = QH_STATE_COMPLETING; @@ -832,7 +832,6 @@ qh_make ( is_input, 0, hb_mult(maxp) * max_packet(maxp))); qh->start = NO_FRAME; - qh->stamp = ehci->periodic_stamp; if (urb->dev->speed == USB_SPEED_HIGH) { qh->c_usecs = 0; diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 11b2f21d7ac1..263b542985c0 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -569,7 +569,10 @@ static void qh_link_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh) ? ((qh->usecs + qh->c_usecs) / qh->period) : (qh->usecs * 8); + list_add(&qh->intr_node, &ehci->intr_qh_list); + /* maybe enable periodic schedule processing */ + ++ehci->intr_count; enable_periodic(ehci); } @@ -614,6 +617,11 @@ static void qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh) /* qh->qh_next still "live" to HC */ qh->qh_state = QH_STATE_UNLINK; qh->qh_next.ptr = NULL; + + if (ehci->qh_scan_next == qh) + ehci->qh_scan_next = list_entry(qh->intr_node.next, + struct ehci_qh, intr_node); + list_del(&qh->intr_node); } static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) @@ -683,6 +691,7 @@ static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) } /* maybe turn off periodic schedule */ + --ehci->intr_count; disable_periodic(ehci); } @@ -920,6 +929,35 @@ done_not_linked: return status; } +static void scan_intr(struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + + list_for_each_entry_safe(qh, ehci->qh_scan_next, &ehci->intr_qh_list, + intr_node) { + rescan: + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list)) { + int temp; + + /* + * Unlinks could happen here; completion reporting + * drops the lock. That's why ehci->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then ehci->qh_scan_next is adjusted + * in qh_unlink_periodic(). + */ + temp = qh_completions(ehci, qh); + if (unlikely(qh->needs_rescan || + (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED))) + start_unlink_intr(ehci, qh); + else if (temp != 0) + goto rescan; + } + } +} + /*-------------------------------------------------------------------------*/ /* ehci_iso_stream ops work with both ITD and SITD */ @@ -1450,6 +1488,10 @@ iso_stream_schedule ( urb->start_frame = stream->next_uframe; if (!stream->highspeed) urb->start_frame >>= 3; + + /* Make sure scan_isoc() sees these */ + if (ehci->isoc_count == 0) + ehci->next_uframe = now; return 0; fail: @@ -1608,6 +1650,7 @@ static void itd_link_urb( urb->hcpriv = NULL; timer_action (ehci, TIMER_IO_WATCHDOG); + ++ehci->isoc_count; enable_periodic(ehci); } @@ -1688,9 +1731,11 @@ itd_complete ( ehci_urb_done(ehci, urb, 0); retval = true; urb = NULL; + + --ehci->isoc_count; disable_periodic(ehci); - ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--; + ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--; if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) { if (ehci->amd_pll_fix == 1) usb_amd_quirk_pll_enable(); @@ -2008,6 +2053,7 @@ static void sitd_link_urb( urb->hcpriv = NULL; timer_action (ehci, TIMER_IO_WATCHDOG); + ++ehci->isoc_count; enable_periodic(ehci); } @@ -2074,9 +2120,11 @@ sitd_complete ( ehci_urb_done(ehci, urb, 0); retval = true; urb = NULL; + + --ehci->isoc_count; disable_periodic(ehci); - ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--; + ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--; if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) { if (ehci->amd_pll_fix == 1) usb_amd_quirk_pll_enable(); @@ -2165,8 +2213,7 @@ static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb, /*-------------------------------------------------------------------------*/ -static void -scan_periodic (struct ehci_hcd *ehci) +static void scan_isoc(struct ehci_hcd *ehci) { unsigned now_uframe, frame, clock, clock_frame, mod; unsigned modified; @@ -2189,7 +2236,6 @@ scan_periodic (struct ehci_hcd *ehci) ehci->clock_frame = clock_frame; clock &= mod - 1; clock_frame = clock >> 3; - ++ehci->periodic_stamp; for (;;) { union ehci_shadow q, *q_p; @@ -2208,36 +2254,10 @@ restart: while (q.ptr != NULL) { unsigned uf; - union ehci_shadow temp; int live; live = (ehci->rh_state >= EHCI_RH_RUNNING); switch (hc32_to_cpu(ehci, type)) { - case Q_TYPE_QH: - /* handle any completions */ - temp.qh = q.qh; - type = Q_NEXT_TYPE(ehci, q.qh->hw->hw_next); - q = q.qh->qh_next; - if (temp.qh->stamp != ehci->periodic_stamp) { - modified = qh_completions(ehci, temp.qh); - if (!modified) - temp.qh->stamp = ehci->periodic_stamp; - if (unlikely(list_empty(&temp.qh->qtd_list) || - temp.qh->needs_rescan)) - start_unlink_intr(ehci, temp.qh); - } - break; - case Q_TYPE_FSTN: - /* for "save place" FSTNs, look at QH entries - * in the previous frame for completions. - */ - if (q.fstn->hw_prev != EHCI_LIST_END(ehci)) { - ehci_dbg(ehci, - "ignoring completions from FSTNs\n"); - } - type = Q_NEXT_TYPE(ehci, q.fstn->hw_next); - q = q.fstn->fstn_next; - break; case Q_TYPE_ITD: /* If this ITD is still active, leave it for * later processing ... check the next entry. @@ -2319,12 +2339,17 @@ restart: ehci_dbg(ehci, "corrupt type %d frame %d shadow %p\n", type, frame, q.ptr); // BUG (); + /* FALL THROUGH */ + case Q_TYPE_QH: + case Q_TYPE_FSTN: + /* End of the iTDs and siTDs */ q.ptr = NULL; + break; } /* assume completion callbacks modify the queue */ if (unlikely (modified)) { - if (likely(ehci->periodic_count > 0)) + if (likely(ehci->isoc_count > 0)) goto restart; /* short-circuit this scan */ now_uframe = clock; @@ -2353,7 +2378,7 @@ restart: unsigned now; if (ehci->rh_state < EHCI_RH_RUNNING - || ehci->periodic_count == 0) + || ehci->isoc_count == 0) break; ehci->next_uframe = now_uframe; now = ehci_read_frame_index(ehci) & (mod - 1); @@ -2363,10 +2388,7 @@ restart: /* rescan the rest of this frame, then ... */ clock = now; clock_frame = clock >> 3; - if (ehci->clock_frame != clock_frame) { - ehci->clock_frame = clock_frame; - ++ehci->periodic_stamp; - } + ehci->clock_frame = clock_frame; } else { now_uframe++; now_uframe &= mod - 1; diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index a823290b5139..0e28bae78d18 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -168,13 +168,8 @@ static void ehci_poll_PSS(struct ehci_hcd *ehci) /* The status is up-to-date; restart or stop the schedule as needed */ if (want == 0) { /* Stopped */ - if (ehci->periodic_count > 0) { - - /* make sure ehci_work scans these */ - ehci->next_uframe = ehci_read_frame_index(ehci) - & ((ehci->periodic_size << 3) - 1); + if (ehci->periodic_count > 0) ehci_set_command_bit(ehci, CMD_PSE); - } } else { /* Running */ if (ehci->periodic_count == 0) { diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index c462d52ac575..08637183aad0 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -117,6 +117,7 @@ struct ehci_hcd { /* one per controller */ bool need_rescan:1; bool intr_unlinking:1; bool async_unlinking:1; + struct ehci_qh *qh_scan_next; /* async schedule support */ struct ehci_qh *async; @@ -124,7 +125,6 @@ struct ehci_hcd { /* one per controller */ struct ehci_qh *async_unlink; struct ehci_qh *async_unlink_last; struct ehci_qh *async_iaa; - struct ehci_qh *qh_scan_next; unsigned async_unlink_cycle; unsigned async_count; /* async activity count */ @@ -133,6 +133,7 @@ struct ehci_hcd { /* one per controller */ unsigned periodic_size; __hc32 *periodic; /* hw periodic table */ dma_addr_t periodic_dma; + struct list_head intr_qh_list; unsigned i_thresh; /* uframes HC might cache */ union ehci_shadow *pshadow; /* mirror hw periodic table */ @@ -140,6 +141,8 @@ struct ehci_hcd { /* one per controller */ struct ehci_qh *intr_unlink_last; unsigned intr_unlink_cycle; int next_uframe; /* scan periodic, start here */ + unsigned intr_count; /* intr activity count */ + unsigned isoc_count; /* isoc activity count */ unsigned periodic_count; /* periodic activity count */ unsigned uframe_periodic_max; /* max periodic time per uframe */ @@ -176,7 +179,6 @@ struct ehci_hcd { /* one per controller */ struct timer_list watchdog; unsigned long actions; - unsigned periodic_stamp; unsigned random_frame; unsigned long next_statechange; ktime_t last_periodic_enable; @@ -381,11 +383,11 @@ struct ehci_qh { dma_addr_t qh_dma; /* address of qh */ union ehci_shadow qh_next; /* ptr to qh; or periodic */ struct list_head qtd_list; /* sw qtd list */ + struct list_head intr_node; /* list of intr QHs */ struct ehci_qtd *dummy; struct ehci_qh *unlink_next; /* next on unlink list */ unsigned unlink_cycle; - unsigned stamp; u8 needs_rescan; /* Dequeue during giveback */ u8 qh_state; -- cgit v1.2.3 From 18aafe64d75d0e27dae206cacf4171e4e485d285 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 11 Jul 2012 11:23:04 -0400 Subject: USB: EHCI: use hrtimer for the I/O watchdog This patch (as1586) replaces the kernel timer used by ehci-hcd as an I/O watchdog with an hrtimer event. Unlike in the current code, the watchdog event is now always enabled whenever any isochronous URBs are active. This will prevent bugs caused by the periodic schedule wrapping around with no completion interrupts; the watchdog handler is guaranteed to scan the isochronous transfers at least once during each iteration of the schedule. The extra overhead will be negligible: one timer interrupt every 100 ms. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 47 +------------------------------------------ drivers/usb/host/ehci-hub.c | 5 ----- drivers/usb/host/ehci-q.c | 1 + drivers/usb/host/ehci-sched.c | 3 +-- drivers/usb/host/ehci-timer.c | 21 +++++++++++++++++++ drivers/usb/host/ehci.h | 13 +----------- 6 files changed, 25 insertions(+), 65 deletions(-) (limited to 'drivers/usb/host/ehci-timer.c') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index c13dad8a8503..9f26080889f5 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -93,8 +93,6 @@ static const char hcd_name [] = "ehci_hcd"; */ #define EHCI_TUNE_FLS 1 /* (medium) 512-frame schedule */ -#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ - /* Initial IRQ latency: faster than hw default */ static int log2_irq_thresh = 0; // 0 to 6 module_param (log2_irq_thresh, int, S_IRUGO); @@ -125,25 +123,6 @@ MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us"); /*-------------------------------------------------------------------------*/ -static void -timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) -{ - if (!test_and_set_bit(action, &ehci->actions)) { - unsigned long t; - - switch (action) { - case TIMER_IO_WATCHDOG: - if (!ehci->need_io_watchdog) - return; - t = EHCI_IO_JIFFIES; - break; - } - mod_timer(&ehci->watchdog, t + jiffies); - } -} - -/*-------------------------------------------------------------------------*/ - /* * handshake - spin reading hc until handshake completes or fails * @ptr: address of hc register to be read @@ -307,19 +286,6 @@ static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); /*-------------------------------------------------------------------------*/ -static void ehci_watchdog(unsigned long param) -{ - struct ehci_hcd *ehci = (struct ehci_hcd *) param; - unsigned long flags; - - spin_lock_irqsave(&ehci->lock, flags); - - /* ehci could run by timer, without IRQs ... */ - ehci_work (ehci); - - spin_unlock_irqrestore (&ehci->lock, flags); -} - /* On some systems, leaving remote wakeup enabled prevents system shutdown. * The firmware seems to think that powering off is a wakeup event! * This routine turns off remote wakeup and everything else, on all ports. @@ -357,8 +323,6 @@ static void ehci_shutdown(struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); - del_timer_sync(&ehci->watchdog); - spin_lock_irq(&ehci->lock); ehci->rh_state = EHCI_RH_STOPPING; ehci_silence_controller(ehci); @@ -394,8 +358,6 @@ static void ehci_port_power (struct ehci_hcd *ehci, int is_on) */ static void ehci_work (struct ehci_hcd *ehci) { - timer_action_done (ehci, TIMER_IO_WATCHDOG); - /* another CPU may drop ehci->lock during a schedule scan while * it reports urb completions. this flag guards against bogus * attempts at re-entrant schedule scanning. @@ -422,10 +384,7 @@ static void ehci_work (struct ehci_hcd *ehci) * misplace IRQs, and should let us run completely without IRQs. * such lossage has been observed on both VT6202 and VT8235. */ - if (ehci->rh_state == EHCI_RH_RUNNING && - (ehci->async->qh_next.ptr != NULL || - ehci->periodic_count != 0)) - timer_action (ehci, TIMER_IO_WATCHDOG); + turn_on_io_watchdog(ehci); } /* @@ -438,7 +397,6 @@ static void ehci_stop (struct usb_hcd *hcd) ehci_dbg (ehci, "stop\n"); /* no more interrupts ... */ - del_timer_sync (&ehci->watchdog); spin_lock_irq(&ehci->lock); ehci->enabled_hrtimer_events = 0; @@ -490,9 +448,6 @@ static int ehci_init(struct usb_hcd *hcd) * keep io watchdog by default, those good HCDs could turn off it later */ ehci->need_io_watchdog = 1; - init_timer(&ehci->watchdog); - ehci->watchdog.function = ehci_watchdog; - ehci->watchdog.data = (unsigned long) ehci; hrtimer_init(&ehci->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); ehci->hrtimer.function = ehci_hrtimer_func; diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 5d84562e2716..05490d387fd2 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -208,7 +208,6 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (time_before (jiffies, ehci->next_statechange)) msleep(5); - del_timer_sync(&ehci->watchdog); spin_lock_irq (&ehci->lock); @@ -316,10 +315,6 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT; spin_unlock_irq (&ehci->lock); - /* ehci_work() may have re-enabled the watchdog timer, which we do not - * want, and so we must delete any pending watchdog timer events. - */ - del_timer_sync(&ehci->watchdog); hrtimer_cancel(&ehci->hrtimer); return 0; } diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index c9c7f7b3b7db..9bc39ca460c8 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -970,6 +970,7 @@ static void enable_async(struct ehci_hcd *ehci) /* Don't start the schedule until ASS is 0 */ ehci_poll_ASS(ehci); + turn_on_io_watchdog(ehci); } static void disable_async(struct ehci_hcd *ehci) diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 263b542985c0..26ce8fef0e5b 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -489,6 +489,7 @@ static void enable_periodic(struct ehci_hcd *ehci) /* Don't start the schedule until PSS is 0 */ ehci_poll_PSS(ehci); + turn_on_io_watchdog(ehci); } static void disable_periodic(struct ehci_hcd *ehci) @@ -1649,7 +1650,6 @@ static void itd_link_urb( iso_sched_free (stream, iso_sched); urb->hcpriv = NULL; - timer_action (ehci, TIMER_IO_WATCHDOG); ++ehci->isoc_count; enable_periodic(ehci); } @@ -2052,7 +2052,6 @@ static void sitd_link_urb( iso_sched_free (stream, sched); urb->hcpriv = NULL; - timer_action (ehci, TIMER_IO_WATCHDOG); ++ehci->isoc_count; enable_periodic(ehci); } diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 0e28bae78d18..eb896a2c8f2e 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -76,6 +76,7 @@ static unsigned event_delays_ns[] = { 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IAA_WATCHDOG */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ + 100 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IO_WATCHDOG */ }; /* Enable a pending hrtimer event */ @@ -332,6 +333,25 @@ static void ehci_iaa_watchdog(struct ehci_hcd *ehci) } +/* Enable the I/O watchdog, if appropriate */ +static void turn_on_io_watchdog(struct ehci_hcd *ehci) +{ + /* Not needed if the controller isn't running or it's already enabled */ + if (ehci->rh_state != EHCI_RH_RUNNING || + (ehci->enabled_hrtimer_events & + BIT(EHCI_HRTIMER_IO_WATCHDOG))) + return; + + /* + * Isochronous transfers always need the watchdog. + * For other sorts we use it only if the flag is set. + */ + if (ehci->isoc_count > 0 || (ehci->need_io_watchdog && + ehci->async_count + ehci->intr_count > 0)) + ehci_enable_event(ehci, EHCI_HRTIMER_IO_WATCHDOG, true); +} + + /* * Handler functions for the hrtimer event types. * Keep this array in the same order as the event types indexed by @@ -347,6 +367,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = { ehci_iaa_watchdog, /* EHCI_HRTIMER_IAA_WATCHDOG */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ + ehci_work, /* EHCI_HRTIMER_IO_WATCHDOG */ }; static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 08637183aad0..254f414bd0bd 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -88,6 +88,7 @@ enum ehci_hrtimer_event { EHCI_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ + EHCI_HRTIMER_IO_WATCHDOG, /* Check for missing IRQs */ EHCI_HRTIMER_NUM_EVENTS /* Must come last */ }; #define EHCI_HRTIMER_NO_EVENT 99 @@ -177,8 +178,6 @@ struct ehci_hcd { /* one per controller */ struct dma_pool *itd_pool; /* itd per iso urb */ struct dma_pool *sitd_pool; /* sitd per split iso urb */ - struct timer_list watchdog; - unsigned long actions; unsigned random_frame; unsigned long next_statechange; ktime_t last_periodic_enable; @@ -235,16 +234,6 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) return container_of ((void *) ehci, struct usb_hcd, hcd_priv); } -enum ehci_timer_action { - TIMER_IO_WATCHDOG, -}; - -static inline void -timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action) -{ - clear_bit (action, &ehci->actions); -} - /*-------------------------------------------------------------------------*/ #include -- cgit v1.2.3