Re: [PATCH] USB: EHCI: go back to using the system clock for QH unlinks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Jul 6, 2011 at 12:34 AM, Alan Stern <stern@xxxxxxxxxxxxxxxxxxx> wrote:
> This patch (as1477) fixes a problem affecting a few types of EHCI
> controller.  Contrary to what one might expect, these controllers
> automatically stop their internal frame counter when no ports are
> enabled.  Since ehci-hcd currently relies on the frame counter for
> determining when it should unlink QHs from the async schedule, those
> controllers run into trouble: The frame counter stops and the QHs
> never get unlinked.
>
> Some systems have also experienced other problems traced back to
> commit b963801164618e25fbdc0cd452ce49c3628b46c8 (USB: ehci-hcd unlink
> speedups), which made the original switch from using the system clock
> to using the frame counter.  It never became clear what the reason was
> for these problems, but evidently it is related to use of the frame
> counter.
>
> To fix all these problems, this patch more or less reverts that commit
> and goes back to using the system clock.  But this can't be done
> cleanly because other changes have since been made to the scan_async()
> subroutine.  One of these changes involved the tricky logic that tries
> to avoid rescanning QHs that have already been seen when the scanning
> loop is restarted, which happens whenever an URB is given back.
> Switching back to clock-based unlinks would make this logic even more
> complicated.
>
> Therefore the new code doesn't rescan the entire async list whenever a
> giveback occurs.  Instead it rescans only the current QH and continues
> on from there.  This requires the use of a separate pointer to keep
> track of the next QH to scan, since the current QH may be unlinked
> while the scanning is in progress.  That new pointer must be global,
> so that it can be adjusted forward whenever the _next_ QH gets
> unlinked.  (uhci-hcd uses this same trick.)
>
> Simplification of the scanning loop removes a level of indentation,
> which accounts for the size of the patch.  The amount of code changed
> is relatively small, and it isn't exactly a reversion of the
> b963801164 commit.
>
> This fixes Bugzilla #32432.
>
> Signed-off-by: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>
> CC: <stable@xxxxxxxxxx>
>
> ---
>
>  drivers/usb/host/ehci-hcd.c |    8 +---
>  drivers/usb/host/ehci-q.c   |   86 +++++++++++++++++++++-----------------------
>  drivers/usb/host/ehci.h     |    3 +
>  3 files changed, 47 insertions(+), 50 deletions(-)
>
> Index: usb-3.0/drivers/usb/host/ehci.h
> ===================================================================
> --- usb-3.0.orig/drivers/usb/host/ehci.h
> +++ usb-3.0/drivers/usb/host/ehci.h
> @@ -75,6 +75,7 @@ struct ehci_hcd {                     /* one per controlle
>        struct ehci_qh          *async;
>        struct ehci_qh          *dummy;         /* For AMD quirk use */
>        struct ehci_qh          *reclaim;
> +       struct ehci_qh          *qh_scan_next;
>        unsigned                scanning : 1;
>
>        /* periodic schedule support */
> @@ -117,7 +118,6 @@ struct ehci_hcd {                   /* one per controlle
>        struct timer_list       iaa_watchdog;
>        struct timer_list       watchdog;
>        unsigned long           actions;
> -       unsigned                stamp;
>        unsigned                periodic_stamp;
>        unsigned                random_frame;
>        unsigned long           next_statechange;
> @@ -343,6 +343,7 @@ struct ehci_qh {
>        struct ehci_qh          *reclaim;       /* next to reclaim */
>
>        struct ehci_hcd         *ehci;
> +       unsigned long           unlink_time;
>
>        /*
>         * Do NOT use atomic operations for QH refcounting. On some CPUs
> Index: usb-3.0/drivers/usb/host/ehci-hcd.c
> ===================================================================
> --- usb-3.0.orig/drivers/usb/host/ehci-hcd.c
> +++ usb-3.0/drivers/usb/host/ehci-hcd.c
> @@ -94,7 +94,8 @@ static const char     hcd_name [] = "ehci_hc
>  #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_FRAMES     5               /* async qh unlink delay */
> +#define EHCI_SHRINK_JIFFIES    (DIV_ROUND_UP(HZ, 200) + 1)
> +                                               /* 200-ms async qh unlink delay */
>
>  /* Initial IRQ latency:  faster than hw default */
>  static int log2_irq_thresh = 0;                // 0 to 6
> @@ -152,10 +153,7 @@ timer_action(struct ehci_hcd *ehci, enum
>                        break;
>                /* case TIMER_ASYNC_SHRINK: */
>                default:
> -                       /* add a jiffie since we synch against the
> -                        * 8 KHz uframe counter.
> -                        */
> -                       t = DIV_ROUND_UP(EHCI_SHRINK_FRAMES * HZ, 1000) + 1;
> +                       t = EHCI_SHRINK_JIFFIES;
>                        break;
>                }
>                mod_timer(&ehci->watchdog, t + jiffies);
> Index: usb-3.0/drivers/usb/host/ehci-q.c
> ===================================================================
> --- usb-3.0.orig/drivers/usb/host/ehci-q.c
> +++ usb-3.0/drivers/usb/host/ehci-q.c
> @@ -1231,6 +1231,8 @@ static void start_unlink_async (struct e
>
>        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 */
> @@ -1256,53 +1258,49 @@ static void scan_async (struct ehci_hcd
>        struct ehci_qh          *qh;
>        enum ehci_timer_action  action = TIMER_IO_WATCHDOG;
>
> -       ehci->stamp = ehci_readl(ehci, &ehci->regs->frame_index);

Interesting, this may fix a possible performance bug[1] too.

I guess that if the HC hw doesn't obey the rule of Interrupt Threshold
Control, then two sequential scan_async may be called in same uframe, the
qh_completions for the 2nd URB will be missed and io watchdog will be
triggered, so degrade performance a lot.

[1], https://bugs.launchpad.net/bugs/624510


>        timer_action_done (ehci, TIMER_ASYNC_SHRINK);
> -rescan:
>        stopped = !HC_IS_RUNNING(ehci_to_hcd(ehci)->state);
> -       qh = ehci->async->qh_next.qh;
> -       if (likely (qh != NULL)) {
> -               do {
> -                       /* clean any finished work for this qh */
> -                       if (!list_empty(&qh->qtd_list) && (stopped ||
> -                                       qh->stamp != ehci->stamp)) {
> -                               int temp;
> -
> -                               /* unlinks could happen here; completion
> -                                * reporting drops the lock.  rescan using
> -                                * the latest schedule, but don't rescan
> -                                * qhs we already finished (no looping)
> -                                * unless the controller is stopped.
> -                                */
> -                               qh = qh_get (qh);
> -                               qh->stamp = ehci->stamp;
> -                               temp = qh_completions (ehci, qh);
> -                               if (qh->needs_rescan)
> -                                       unlink_async(ehci, qh);
> -                               qh_put (qh);
> -                               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->reclaim && (stopped ||
> -                                       ((ehci->stamp - qh->stamp) & 0x1fff)
> -                                               >= EHCI_SHRINK_FRAMES * 8))
> -                                       start_unlink_async(ehci, qh);
> -                               else
> -                                       action = TIMER_ASYNC_SHRINK;
> -                       }
>
> -                       qh = qh->qh_next.qh;
> -               } while (qh);
> +       ehci->qh_scan_next = ehci->async->qh_next.qh;
> +       while (ehci->qh_scan_next) {
> +               qh = ehci->qh_scan_next;
> +               ehci->qh_scan_next = qh->qh_next.qh;
> + 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 start_unlink_async().
> +                        */
> +                       qh = qh_get(qh);
> +                       temp = qh_completions(ehci, qh);
> +                       if (qh->needs_rescan)
> +                               unlink_async(ehci, qh);
> +                       qh->unlink_time = jiffies + EHCI_SHRINK_JIFFIES;
> +                       qh_put(qh);
> +                       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->reclaim && (stopped ||
> +                                       time_after_eq(jiffies, qh->unlink_time)))
> +                               start_unlink_async(ehci, qh);
> +                       else
> +                               action = TIMER_ASYNC_SHRINK;
> +               }
>        }
>        if (action == TIMER_ASYNC_SHRINK)
>                timer_action (ehci, TIMER_ASYNC_SHRINK);
>
> --
> 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


Thanks,
--
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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux