On Wed, 21 Dec 2011, Anton Alekseev wrote: > Alan, I've applied your patch to 3.1.5 and it seems that some troubles fixed. > HCI doesn't die now even with too small audio latencies (died without > this patch). > Will continue testing. Michael and Anton: Below is a revised version of the original patch (it's actually a combination of several patches that I intend to submit, assuming they work okay). The patch is based on the 3.3 kernel, but with a little hand-editing it ought to work with earlier or later versions too. I'd appreciate any testing you can do. Geoff, I removed the changes we added to accomodate the PS3. I think the driver will work okay without them, but you may see the warning message added in ehci_poll_PSS() more often than you want. If necessary we can modify that routine to avoid logging the warning on the PS3, or we could make it a one-time message. Alan Stern Index: x/drivers/usb/host/ehci-hcd.c =================================================================== --- x.orig/drivers/usb/host/ehci-hcd.c +++ x/drivers/usb/host/ehci-hcd.c @@ -30,8 +30,7 @@ #include <linux/vmalloc.h> #include <linux/errno.h> #include <linux/init.h> -#include <linux/timer.h> -#include <linux/ktime.h> +#include <linux/hrtimer.h> #include <linux/list.h> #include <linux/interrupt.h> #include <linux/usb.h> @@ -128,6 +127,7 @@ MODULE_PARM_DESC(hird, "host initiated r #include "ehci.h" #include "ehci-dbg.c" #include "pci-quirks.h" +#include "ehci-timer.c" /*-------------------------------------------------------------------------*/ @@ -227,75 +227,18 @@ static int ehci_halt (struct ehci_hcd *e if ((temp & STS_HALT) != 0) return 0; + /* + * This routine gets called during probe before ehci->command + * has been initialized, so we can't rely on its value. + */ + ehci->command &= ~CMD_RUN; temp = ehci_readl(ehci, &ehci->regs->command); - temp &= ~CMD_RUN; + temp &= ~(CMD_RUN | CMD_IAAD); ehci_writel(ehci, temp, &ehci->regs->command); return handshake (ehci, &ehci->regs->status, STS_HALT, STS_HALT, 16 * 125); } -#if defined(CONFIG_USB_SUSPEND) && defined(CONFIG_PPC_PS3) - -/* - * The EHCI controller of the Cell Super Companion Chip used in the - * PS3 will stop the root hub after all root hub ports are suspended. - * When in this condition handshake will return -ETIMEDOUT. The - * STS_HLT bit will not be set, so inspection of the frame index is - * used here to test for the condition. If the condition is found - * return success to allow the USB suspend to complete. - */ - -static int handshake_for_broken_root_hub(struct ehci_hcd *ehci, - void __iomem *ptr, u32 mask, u32 done, - int usec) -{ - unsigned int old_index; - int error; - - if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) - return -ETIMEDOUT; - - old_index = ehci_read_frame_index(ehci); - - error = handshake(ehci, ptr, mask, done, usec); - - if (error == -ETIMEDOUT && ehci_read_frame_index(ehci) == old_index) - return 0; - - return error; -} - -#else - -static int handshake_for_broken_root_hub(struct ehci_hcd *ehci, - void __iomem *ptr, u32 mask, u32 done, - int usec) -{ - return -ETIMEDOUT; -} - -#endif - -static int handshake_on_error_set_halt(struct ehci_hcd *ehci, void __iomem *ptr, - u32 mask, u32 done, int usec) -{ - int error; - - error = handshake(ehci, ptr, mask, done, usec); - if (error == -ETIMEDOUT) - error = handshake_for_broken_root_hub(ehci, ptr, mask, done, - usec); - - if (error) { - ehci_halt(ehci); - ehci->rh_state = EHCI_RH_HALTED; - ehci_err(ehci, "force halt; handshake %p %08x %08x -> %d\n", - ptr, mask, done, error); - } - - return error; -} - /* put TDI/ARC silicon into EHCI mode */ static void tdi_reset (struct ehci_hcd *ehci) { @@ -348,6 +291,7 @@ static int ehci_reset (struct ehci_hcd * if (ehci->debug) dbgp_external_startup(); + ehci->command = ehci_readl(ehci, &ehci->regs->command); return retval; } @@ -362,20 +306,17 @@ static void ehci_quiesce (struct ehci_hc #endif /* wait for any schedule enables/disables to take effect */ - temp = ehci_readl(ehci, &ehci->regs->command) << 10; - temp &= STS_ASS | STS_PSS; - if (handshake_on_error_set_halt(ehci, &ehci->regs->status, - STS_ASS | STS_PSS, temp, 16 * 125)) - return; + temp = (ehci->command << 10) & (STS_ASS | STS_PSS); + handshake(ehci, &ehci->regs->status, + STS_ASS | STS_PSS, temp, 16 * 125); /* then disable anything that's still active */ - temp = ehci_readl(ehci, &ehci->regs->command); - temp &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); - ehci_writel(ehci, temp, &ehci->regs->command); + ehci->command &= ~(CMD_ASE | CMD_PSE); + ehci_writel(ehci, ehci->command, &ehci->regs->command); /* hardware can take 16 microframes to turn off ... */ - handshake_on_error_set_halt(ehci, &ehci->regs->status, - STS_ASS | STS_PSS, 0, 16 * 125); + handshake(ehci, &ehci->regs->status, + STS_ASS | STS_PSS, 0, 16 * 125); } /*-------------------------------------------------------------------------*/ @@ -416,9 +357,6 @@ static void ehci_iaa_watchdog(unsigned l * CMD_IAAD when it sets STS_IAA.) */ cmd = ehci_readl(ehci, &ehci->regs->command); - if (cmd & CMD_IAAD) - ehci_writel(ehci, cmd & ~CMD_IAAD, - &ehci->regs->command); /* If IAA is set here it either legitimately triggered * before we cleared IAAD above (but _way_ late, so we'll @@ -499,7 +437,10 @@ static void ehci_shutdown(struct usb_hcd spin_lock_irq(&ehci->lock); ehci_silence_controller(ehci); + ehci->enabled_hrtimer_events = 0; spin_unlock_irq(&ehci->lock); + + hrtimer_cancel(&ehci->hrtimer); } static void ehci_port_power (struct ehci_hcd *ehci, int is_on) @@ -566,6 +507,7 @@ static void ehci_stop (struct usb_hcd *h del_timer_sync(&ehci->iaa_watchdog); spin_lock_irq(&ehci->lock); + ehci->enabled_hrtimer_events = 0; if (ehci->rh_state == EHCI_RH_RUNNING) ehci_quiesce (ehci); @@ -573,6 +515,7 @@ static void ehci_stop (struct usb_hcd *h ehci_reset (ehci); spin_unlock_irq(&ehci->lock); + hrtimer_cancel(&ehci->hrtimer); remove_sysfs_files(ehci); remove_debug_files (ehci); @@ -621,6 +564,10 @@ static int ehci_init(struct usb_hcd *hcd 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->enabled_hrtimer_events = EHCI_HRTIMER_NO_EVENT; + hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params); /* @@ -909,7 +856,7 @@ static irqreturn_t ehci_irq (struct usb_ pcd_status = status; /* resume root hub? */ - if (!(cmd & CMD_RUN)) + if (ehci->rh_state == EHCI_RH_SUSPENDED) usb_hcd_resume_root_hub(hcd); /* get per-port change detect bits */ @@ -952,6 +899,8 @@ static irqreturn_t ehci_irq (struct usb_ 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); Index: x/drivers/usb/host/ehci-dbg.c =================================================================== --- x.orig/drivers/usb/host/ehci-dbg.c +++ x/drivers/usb/host/ehci-dbg.c @@ -1032,10 +1032,8 @@ static ssize_t debug_lpm_write(struct fi if (strict_strtoul(buf + 5, 16, &hird)) return -EINVAL; printk(KERN_INFO "setting hird %s %lu\n", buf + 6, hird); - temp = ehci_readl(ehci, &ehci->regs->command); - temp &= ~CMD_HIRD; - temp |= hird << 24; - ehci_writel(ehci, temp, &ehci->regs->command); + ehci->command = (ehci->command & ~CMD_HIRD) | (hird << 24); + ehci_writel(ehci, ehci->command, &ehci->regs->command); } else if (strncmp(buf, "disable", 7) == 0) { if (strict_strtoul(buf + 8, 10, &port)) return -EINVAL; Index: x/drivers/usb/host/ehci-hub.c =================================================================== --- x.orig/drivers/usb/host/ehci-hub.c +++ x/drivers/usb/host/ehci-hub.c @@ -238,7 +238,6 @@ static int ehci_bus_suspend (struct usb_ /* stop schedules, clean any completed work */ if (ehci->rh_state == EHCI_RH_RUNNING) ehci_quiesce (ehci); - ehci->command = ehci_readl(ehci, &ehci->regs->command); ehci_work(ehci); /* Unlike other USB host controller types, EHCI doesn't have @@ -324,12 +323,14 @@ static int ehci_bus_suspend (struct usb_ ehci_readl(ehci, &ehci->regs->intr_enable); ehci->next_statechange = jiffies + msecs_to_jiffies(10); + ehci->enabled_hrtimer_events = 0; 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; } @@ -379,6 +380,7 @@ static int ehci_bus_resume (struct usb_h ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next); /* restore CMD_RUN, framelist size, and irq threshold */ + ehci->command |= CMD_RUN; ehci_writel(ehci, ehci->command, &ehci->regs->command); ehci->rh_state = EHCI_RH_RUNNING; Index: x/drivers/usb/host/ehci-q.c =================================================================== --- x.orig/drivers/usb/host/ehci-q.c +++ x/drivers/usb/host/ehci-q.c @@ -981,14 +981,12 @@ static void qh_link_async (struct ehci_h head = ehci->async; timer_action_done (ehci, TIMER_ASYNC_OFF); if (!head->qh_next.qh) { - u32 cmd = ehci_readl(ehci, &ehci->regs->command); - - if (!(cmd & CMD_ASE)) { + 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); - cmd |= CMD_ASE; - ehci_writel(ehci, cmd, &ehci->regs->command); + ehci->command |= CMD_ASE; + ehci_writel(ehci, ehci->command, &ehci->regs->command); /* posted write need not be known to HC yet ... */ } } @@ -1204,7 +1202,6 @@ static void end_unlink_async (struct ehc static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) { - int cmd = ehci_readl(ehci, &ehci->regs->command); struct ehci_qh *prev; #ifdef DEBUG @@ -1222,8 +1219,8 @@ static void start_unlink_async (struct e if (ehci->rh_state != EHCI_RH_HALTED && !ehci->reclaim) { /* ... and CMD_IAAD clear */ - ehci_writel(ehci, cmd & ~CMD_ASE, - &ehci->regs->command); + 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); @@ -1253,8 +1250,7 @@ static void start_unlink_async (struct e return; } - cmd |= CMD_IAAD; - ehci_writel(ehci, cmd, &ehci->regs->command); + ehci_writel(ehci, ehci->command | CMD_IAAD, &ehci->regs->command); (void)ehci_readl(ehci, &ehci->regs->command); iaa_watchdog_start(ehci); } Index: x/drivers/usb/host/ehci-sched.c =================================================================== --- x.orig/drivers/usb/host/ehci-sched.c +++ x/drivers/usb/host/ehci-sched.c @@ -481,67 +481,38 @@ static int tt_no_collision ( static int enable_periodic (struct ehci_hcd *ehci) { - u32 cmd; - int status; - if (ehci->periodic_sched++) 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; - } - - cmd = ehci_readl(ehci, &ehci->regs->command) | CMD_PSE; - ehci_writel(ehci, cmd, &ehci->regs->command); - /* posted write ... PSS happens later */ + /* If we're waiting to turn off the periodic schedule, stop waiting */ + if (ehci->enabled_hrtimer_events & BIT(EHCI_HRTIMER_DISABLE_PERIODIC)) + ehci->enabled_hrtimer_events &= + ~BIT(EHCI_HRTIMER_DISABLE_PERIODIC); + + /* Otherwise, don't start the schedule until PSS is known to be 0 */ + else + ehci_poll_PSS(ehci, NULL); /* 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(); return 0; } static int disable_periodic (struct ehci_hcd *ehci) { - u32 cmd; - int status; - if (--ehci->periodic_sched) 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; - } - - cmd = ehci_readl(ehci, &ehci->regs->command) & ~CMD_PSE; - ehci_writel(ehci, cmd, &ehci->regs->command); - /* posted write ... */ - - free_cached_lists(ehci); + /* If we're waiting to start the periodic schedule, stop waiting */ + if (ehci->enabled_hrtimer_events & BIT(EHCI_HRTIMER_POLL_PSS)) + ehci->enabled_hrtimer_events &= + ~BIT(EHCI_HRTIMER_POLL_PSS); + + /* Otherwise wait for a while before turning the schedule off */ + else + ehci_enable_event(ehci, EHCI_HRTIMER_DISABLE_PERIODIC, + &ehci->periodic_event_time, true); ehci->next_uframe = -1; return 0; Index: x/drivers/usb/host/ehci.h =================================================================== --- x.orig/drivers/usb/host/ehci.h +++ x/drivers/usb/host/ehci.h @@ -68,7 +68,24 @@ enum ehci_rh_state { EHCI_RH_RUNNING }; +/* + * Timer events ordered by delay length. + * Always update ehci_event_delays_ns[] 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_NO_EVENT /* Must come last */ +}; + struct ehci_hcd { /* one per controller */ + enum ehci_hrtimer_event next_hrtimer_event; + unsigned enabled_hrtimer_events; + struct hrtimer hrtimer; + + ktime_t periodic_event_time; + ktime_t PSS_giveup_time; + /* glue to PCI and HCD framework */ struct ehci_caps __iomem *caps; struct ehci_regs __iomem *regs; @@ -141,7 +158,6 @@ struct ehci_hcd { /* one per controlle 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*/ Index: x/drivers/usb/host/ehci-timer.c =================================================================== --- /dev/null +++ x/drivers/usb/host/ehci-timer.c @@ -0,0 +1,173 @@ +/* + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* This file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* 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. + * + * 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/2 ms. + */ + +/* + * Delay lengths for the hrtimer event types. + * Keep this sorted by delay lengths, and in the same order as + * the events types indexed by enum ehci_hrtimer_event in ehci.h. + */ +static unsigned ehci_event_delays_ns[] = { + 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ + 10 * NSEC_PER_MSEC, /* EHCI_DISABLE_PERIODIC */ +}; + +/* Enable a pending hrtimer event */ +static void ehci_enable_event(struct ehci_hcd *ehci, unsigned event, + ktime_t *timeout, bool resched) +{ + if (resched) + *timeout = ktime_add(ktime_get(), + ktime_set(0, ehci_event_delays_ns[event])); + ehci->enabled_hrtimer_events |= (1 << event); + + /* Track only the lowest-numbered event */ + if (event < ehci->next_hrtimer_event) { + ehci->next_hrtimer_event = event; + hrtimer_start_range_ns(&ehci->hrtimer, *timeout, + NSEC_PER_MSEC / 2, HRTIMER_MODE_ABS); + } +} + + +/* Poll the STS_PSS status bit */ +static void ehci_poll_PSS(struct ehci_hcd *ehci, ktime_t *now) +{ + u32 status; + + /* Don't enable anything if the controller isn't running (e.g., died) */ + if (!(ehci->command & CMD_RUN)) + return; + + /* + * When the periodic schedule stops, restart it. If it takes + * too long to stop then give up and restart it anyway, otherwise + * continue polling. + */ + status = ehci_readl(ehci, &ehci->regs->status); + if (unlikely(now && (status & STS_PSS))) { + if (now->tv64 >= ehci->PSS_giveup_time.tv64) { + ehci_warn(ehci, "Waited too long for the periodic schedule to stop, giving up\n"); + status = 0; + } + } + + if (!(status & STS_PSS)) + ehci_set_command_bit(ehci, CMD_PSE); + + else /* Poll again later */ + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_PSS, + &ehci->periodic_event_time, true); +} + +/* Disable the periodic schedule */ +static void ehci_turn_off_PSE(struct ehci_hcd *ehci) +{ + ehci_clear_command_bit(ehci, CMD_PSE); + free_cached_lists(ehci); + + /* Allow up to 20 ms for the schedule to actually stop */ + ehci->PSS_giveup_time = ktime_add(ktime_get(), + ktime_set(0, 20 * NSEC_PER_MSEC)); +} + + +static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) +{ + struct ehci_hcd *ehci = container_of(t, struct ehci_hcd, hrtimer); + ktime_t now = ktime_get(); + unsigned events; + unsigned long flags; + + 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. + */ + if (events & BIT(EHCI_HRTIMER_POLL_PSS)) { + if (now.tv64 >= ehci->periodic_event_time.tv64) + ehci_poll_PSS(ehci, &now); + else + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_PSS, + &ehci->periodic_event_time, false); + } + + if (events & BIT(EHCI_HRTIMER_DISABLE_PERIODIC)) { + if (now.tv64 >= ehci->periodic_event_time.tv64) + ehci_turn_off_PSE(ehci); + else + ehci_enable_event(ehci, EHCI_HRTIMER_DISABLE_PERIODIC, + &ehci->periodic_event_time, false); + } + + spin_unlock_irqrestore(&ehci->lock, flags); + return HRTIMER_NORESTART; +} Index: x/drivers/usb/host/ehci-pci.c =================================================================== --- x.orig/drivers/usb/host/ehci-pci.c +++ x/drivers/usb/host/ehci-pci.c @@ -131,10 +131,6 @@ static int ehci_pci_setup(struct usb_hcd case PCI_VENDOR_ID_INTEL: ehci->need_io_watchdog = 0; ehci->fs_i_thresh = 1; - if (pdev->device == 0x27cc) { - ehci->broken_periodic = 1; - ehci_info(ehci, "using broken periodic workaround\n"); - } if (pdev->device == 0x0806 || pdev->device == 0x0811 || pdev->device == 0x0829) { ehci_info(ehci, "disable lpm for langwell/penwell\n"); -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html