From patchwork Mon Jun 24 09:42:04 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Lei X-Patchwork-Id: 2769471 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 3F2CB9F245 for ; Mon, 24 Jun 2013 09:46:07 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 65F1820163 for ; Mon, 24 Jun 2013 09:46:05 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 63FCC2015A for ; Mon, 24 Jun 2013 09:46:03 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Ur3JP-0004KG-DF; Mon, 24 Jun 2013 09:43:52 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Ur3J0-0003Sb-HN; Mon, 24 Jun 2013 09:43:26 +0000 Received: from mail-pb0-x22e.google.com ([2607:f8b0:400e:c01::22e]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Ur3Ik-0003Pf-Mp for linux-arm-kernel@lists.infradead.org; Mon, 24 Jun 2013 09:43:13 +0000 Received: by mail-pb0-f46.google.com with SMTP id rq2so10710663pbb.33 for ; Mon, 24 Jun 2013 02:42:49 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; bh=CQpxtoGbO/vCSKRtTVJIGT2w8gOo+6EyqyxxZg9Fszc=; b=aEowcVG7SEZFqqLvkWDE5IcK6TLUmcgSwDurqnRAzFL+UI++oHyD0LgEPf3uncKfe2 Eho3oQwusNansrdOHAq/IAqyd5nXSedPOQ3dTJA6rA3rMP0KkZvunw2lHsgD73CZtobn TJHBY14BzNkPWI8o+JAmOBKEaWGllX+ajXvtc9V37IsEsyijicVvawHlDd4SiQB2Jicm fWM+H7WMaWgJHmvB/+ujpWWhoHnaCAfAgr5UjqPU3ZIqYiPMYpgBwyjEeSDLwfd+fRAE hycZMRXmmIeNOMnVB+MEuhX+FEfj32QdZ8XRy4wJv4Ni7VMuttVFOiU+6QUytXq4woSf BT3w== X-Received: by 10.68.41.106 with SMTP id e10mr23322662pbl.136.1372066969035; Mon, 24 Jun 2013 02:42:49 -0700 (PDT) Received: from localhost ([183.37.208.198]) by mx.google.com with ESMTPSA id pq5sm17428000pbc.7.2013.06.24.02.42.43 for (version=TLSv1.2 cipher=RC4-SHA bits=128/128); Mon, 24 Jun 2013 02:42:48 -0700 (PDT) From: Ming Lei To: Greg Kroah-Hartman Subject: [PATCH v2 3/4] USB: EHCI: improve interrupt qh unlink Date: Mon, 24 Jun 2013 17:42:04 +0800 Message-Id: <1372066925-23579-4-git-send-email-ming.lei@canonical.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1372066925-23579-1-git-send-email-ming.lei@canonical.com> References: <1372066925-23579-1-git-send-email-ming.lei@canonical.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130624_054310_975606_F5992F76 X-CRM114-Status: GOOD ( 23.58 ) X-Spam-Score: -1.9 (-) Cc: Oliver Neukum , Ming Lei , linux-usb@vger.kernel.org, Steven Rostedt , Alan Stern , Thomas Gleixner , linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-5.3 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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. Cc: Alan Stern Signed-off-by: Ming Lei --- 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 | 62 ++++++++++++++++++++++++++++++++++++++--- drivers/usb/host/ehci-timer.c | 48 ++++++++++++++++++++++++++++++- drivers/usb/host/ehci.h | 3 ++ 6 files changed, 111 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 7abf1ce..35f1372 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -487,6 +487,7 @@ static int ehci_init(struct usb_hcd *hcd) ehci->periodic_size = DEFAULT_I_TDPS; INIT_LIST_HEAD(&ehci->async_unlink); INIT_LIST_HEAD(&ehci->async_idle); + INIT_LIST_HEAD(&ehci->intr_unlink_wait); INIT_LIST_HEAD(&ehci->intr_unlink); INIT_LIST_HEAD(&ehci->intr_qh_list); INIT_LIST_HEAD(&ehci->cached_itd_list); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 2b70277..6037f84 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -345,6 +345,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) end_unlink_async(ehci); unlink_empty_async_suspended(ehci); + ehci_handle_start_intr_unlinks(ehci); ehci_handle_intr_unlinks(ehci); end_free_itds(ehci); diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index ef2c3a1..52a7773 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -93,6 +93,7 @@ static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, gfp_t flags) qh->qh_dma = dma; // INIT_LIST_HEAD (&qh->qh_list); INIT_LIST_HEAD (&qh->qtd_list); + INIT_LIST_HEAD(&qh->unlink_node); /* dummy td enables safe urb queuing */ qh->dummy = ehci_qtd_alloc (ehci, flags); 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 */ +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); +} + +static void start_do_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + /* if the qh is waitting for unlink, cancel it now */ + 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); +} + static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) { struct ehci_qh_hw *hw = qh->hw; @@ -881,6 +932,9 @@ static int intr_submit ( goto done; } + /* put back qh from unlink wait list */ + cancel_unlink_wait_intr(ehci, qh); + /* 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); @@ -926,7 +980,7 @@ static void scan_intr(struct ehci_hcd *ehci) temp = qh_completions(ehci, qh); if (unlikely(temp || (list_empty(&qh->qtd_list) && qh->qh_state == QH_STATE_LINKED))) - start_unlink_intr(ehci, qh); + start_unlink_intr_wait(ehci, qh); } } } diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 11e5b32..9fef5d4 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 */ + 5 * NSEC_PER_MSEC, /* EHCI_HRTIMER_START_UNLINK_INTR */ 6 * NSEC_PER_MSEC, /* EHCI_HRTIMER_ASYNC_UNLINKS */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IAA_WATCHDOG */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ @@ -98,6 +99,19 @@ static void ehci_enable_event(struct ehci_hcd *ehci, unsigned event, } } +/* Warning: don't call this function from hrtimer handler context */ +static void ehci_disable_event(struct ehci_hcd *ehci, unsigned event) +{ + if (!hcd_giveback_urb_in_bh(ehci_to_hcd(ehci))) + return; + + ehci->enabled_hrtimer_events &= ~(1 << event); + if (!ehci->enabled_hrtimer_events) { + ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT; + hrtimer_cancel(&ehci->hrtimer); + } +} + /* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */ static void ehci_poll_ASS(struct ehci_hcd *ehci) @@ -215,6 +229,37 @@ static void ehci_handle_controller_death(struct ehci_hcd *ehci) /* Not in process context, so don't try to reset the controller */ } +/* start to unlink interrupt QHs */ +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); + if (!stopped && + (qh->unlink_cycle == + ehci->intr_unlink_wait_cycle)) + break; + list_del_init(&qh->unlink_node); + start_unlink_intr(ehci, qh); + } + + /* Handle remaining entries later */ + if (!list_empty(&ehci->intr_unlink_wait)) { + ehci_enable_event(ehci, EHCI_HRTIMER_START_UNLINK_INTR, true); + ++ehci->intr_unlink_wait_cycle; + } +} /* Handle unlinked interrupt QHs once they are gone from the hardware */ static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) @@ -236,7 +281,7 @@ static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) unlink_node); if (!stopped && qh->unlink_cycle == ehci->intr_unlink_cycle) break; - list_del(&qh->unlink_node); + list_del_init(&qh->unlink_node); end_unlink_intr(ehci, qh); } @@ -363,6 +408,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_handle_start_intr_unlinks, /* EHCI_HRTIMER_START_UNLINK_INTR */ unlink_empty_async, /* EHCI_HRTIMER_ASYNC_UNLINKS */ ehci_iaa_watchdog, /* EHCI_HRTIMER_IAA_WATCHDOG */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 64f9a08..28dc8d2 100644 --- 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 */ 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 */