[PATCH v2 3/4] USB: EHCI: improve interrupt qh unlink

Ming Lei ming.lei at canonical.com
Tue Jun 25 07:56:11 EDT 2013


On Tue, Jun 25, 2013 at 2:53 AM, Alan Stern <stern at rowland.harvard.edu> wrote:
> On Mon, 24 Jun 2013, Ming Lei wrote:
>
>> Given interrupt URB will be resubmitted from tasklet context which
>> is scheduled by ehci hardware interrupt handler, and commonly only
>> one interrupt URB is scheduled on qh, so the qh may be unlinked
>> immediately once qh_completions() returns from ehci_irq(), then
>> the intr URB to be resubmitted in complete() can only be scheduled
>> and linked to hardware until the qh unlink is completed.
>>
>> This patch improves this above situation, and the qh will wait for 5
>> milliseconds before being unlinked from hardware, if one URB is submitted
>> during the period, the qh is move out of unlink wait list and the
>> interrupt transfer can be scheduled immediately, not like before: the
>> transfer is only linked to hardware until previous unlink is completed.
>
> This description is very hard to understand.  I suggest rewriting it,
> something like this:
>
> ehci-hcd currently unlinks an interrupt QH when it becomes empty, that
> is, after its last URB completes.  This works well because in almost
> all cases, the completion handler for an interrupt URB resubmits the
> URB; therefore the QH doesn't become empty and doesn't get unlinked.
>
> When we start using tasklets for URB completion, this scheme won't work
> as well.  The resubmission won't occur until the tasklet runs, which
> will be some time after the completion is queued with the tasklet.
> During that delay, the QH will be empty and so will be unlinked
> unnecessarily.
>
> To prevent this problem, this patch adds a 5-ms time delay before empty
> interrupt QHs are unlinked.  Most often, during that time the interrupt
> URB will be resubmitted and thus we can avoid unlinking the QH.

Excellent description about the change, and I will add it in V3,
also care to add your signed-off in the patch for the change log part?

>
>> diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
>> index f80d033..5bf67e2 100644
>> --- a/drivers/usb/host/ehci-sched.c
>> +++ b/drivers/usb/host/ehci-sched.c
>> @@ -601,12 +601,24 @@ static void qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh)
>>       list_del(&qh->intr_node);
>>  }
>>
>> -static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh)
>> +/* must be called with holding ehci->lock */
>
> The comment should be:
>
> /* caller must hold ehci->lock */
>
> But in fact you can leave it out.  Almost all the code in this file
> runs with the lock held.

OK.

>
>> +static void cancel_unlink_wait_intr(struct ehci_hcd *ehci, struct ehci_qh *qh)
>>  {
>> -     /* If the QH isn't linked then there's nothing we can do. */
>> -     if (qh->qh_state != QH_STATE_LINKED)
>> +     if (qh->qh_state != QH_STATE_LINKED || list_empty(&qh->unlink_node))
>>               return;
>>
>> +     list_del_init(&qh->unlink_node);
>> +
>> +     /* avoid unnecessary CPU wakeup */
>> +     if (list_empty(&ehci->intr_unlink_wait))
>> +             ehci_disable_event(ehci, EHCI_HRTIMER_START_UNLINK_INTR);
>
> If you don't mind, can we leave out ehci_disable_event() for now and
> add it after the rest of this series is merged?  It will keeps things a
> little simpler, and then we'll be able to use ehci_disable_event() for
> the IAA watchdog timer event as well as using it here.

I am fine, so this patch will become simpler and has less change.

>
>> +}
>> +
>> +static void start_do_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh)
>> +{
>> +     /* if the qh is waitting for unlink, cancel it now */
>
> s/waitt/wait/
>
>> +     cancel_unlink_wait_intr(ehci, qh);
>> +
>>       qh_unlink_periodic (ehci, qh);
>>
>>       /* Make sure the unlinks are visible before starting the timer */
>> @@ -632,6 +644,45 @@ static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh)
>>       }
>>  }
>>
>> +/*
>> + * It is common only one intr URB is scheduled on one qh, and
>> + * given complete() is run in tasklet context, introduce a bit
>> + * delay to avoid unlink qh too early.
>> + */
>> +static void __start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh,
>> +                             bool wait)
>> +{
>> +     /* If the QH isn't linked then there's nothing we can do. */
>> +     if (qh->qh_state != QH_STATE_LINKED)
>> +             return;
>> +
>> +     if (!wait)
>> +             return start_do_unlink_intr(ehci, qh);
>> +
>> +     qh->unlink_cycle = ehci->intr_unlink_wait_cycle;
>> +
>> +     /* New entries go at the end of the intr_unlink_wait list */
>> +     list_add_tail(&qh->unlink_node, &ehci->intr_unlink_wait);
>> +
>> +     if (ehci->rh_state < EHCI_RH_RUNNING)
>> +             ehci_handle_start_intr_unlinks(ehci);
>> +     else if (ehci->intr_unlink_wait.next == &qh->unlink_node) {
>> +             ehci_enable_event(ehci, EHCI_HRTIMER_START_UNLINK_INTR, true);
>> +             ++ehci->intr_unlink_wait_cycle;
>> +     }
>> +}
>> +
>> +static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh)
>> +{
>> +     __start_unlink_intr(ehci, qh, false);
>> +}
>> +
>> +static void start_unlink_intr_wait(struct ehci_hcd *ehci,
>> +                                struct ehci_qh *qh)
>> +{
>> +     __start_unlink_intr(ehci, qh, true);
>> +}
>
> This is not what I had in mind.
>
> Instead, copy the qh->qh_state test into the beginning of
> start_unlink_intr() and move the rest of start_do_unlink_intr() there.
> Then you can rename __start_unlink_intr() to start_unlink_intr_wait()
> and get rid of the "wait" parameter.

The current code can do the check centrally, but it isn't big deal, and I
will follow your suggestion.

>
>> @@ -881,6 +932,9 @@ static int intr_submit (
>>                       goto done;
>>       }
>>
>> +     /* put back qh from unlink wait list */
>> +     cancel_unlink_wait_intr(ehci, qh);
>
> It might be better to change this line into an "else" clause for the
> "if (qh->qh_state == QH_STATE_IDLE)" statement after the BUG_ON below.

OK.

>
>> +
>>       /* then queue the urb's tds to the qh */
>>       qh = qh_append_tds(ehci, urb, qtd_list, epnum, &urb->ep->hcpriv);
>>       BUG_ON (qh == NULL);
>
>> +static void ehci_handle_start_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.
>> +      */
>> +     while (!list_empty(&ehci->intr_unlink_wait)) {
>> +             struct ehci_qh  *qh;
>> +
>> +             qh = list_first_entry(&ehci->intr_unlink_wait,
>> +                                   struct ehci_qh, unlink_node);
>
> As I mentioned before, this line should be indented by two tab stops
> beyond the preceding line.

Looks I forget this one.

>
>> +             if (!stopped &&
>> +                             (qh->unlink_cycle ==
>
> This doesn't need to be on a separate line at all.
>
>> +                                     ehci->intr_unlink_wait_cycle))
>
> And this should also be indented by two tab stops beyond the preceding
> line.

OK.

BTW, indenting two tab might cause more lines, and looks many subsystems
don't do that. But, anyway, I am fine with two tab, :-)

>
>> --- a/drivers/usb/host/ehci.h
>> +++ b/drivers/usb/host/ehci.h
>> @@ -88,6 +88,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_START_UNLINK_INTR, /* wait for new intr schedule */
>
> The comment should be:                  /* Unlink empty interrupt QHs */

OK

>
>>       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 */
>> @@ -143,6 +144,8 @@ struct ehci_hcd {                 /* one per controller */
>>       unsigned                i_thresh;       /* uframes HC might cache */
>>
>>       union ehci_shadow       *pshadow;       /* mirror hw periodic table */
>> +     struct list_head        intr_unlink_wait;
>> +     unsigned                intr_unlink_wait_cycle;
>>       struct list_head        intr_unlink;
>>       unsigned                intr_unlink_cycle;
>>       unsigned                now_frame;      /* frame from HC hardware */
>
> To avoid an alignment gap on 64-bit systems, put intr_unlink_wait_cycle
> after intr_unlink.

Good catch.

Thanks,
--
Ming Lei



More information about the linux-arm-kernel mailing list