Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3585243d authored by Gilad Broner's avatar Gilad Broner
Browse files

usb: dwc3: bulk out endpoint optimization



For bulk transfers, the current implementation uses TRB allocation
"on demand". That is, it recieves an XferNotReady event is the trigger
to prepare an array of TRBs to use for the upcoming transfer, with the
last TRB set with the LST bit which will trigger an XferComplete event.
A transfer, in many cases, consists of small packets. As such, for each
of the TRBs set up an XferComplete event is received.
This results in thousands of interrupts a second and a high cpu usage.
The optimization sets the CSP (continue on short packet) bit for all the
TRBs set up for the transfer to avoid those notifications, and relies on
the XferComplete event of the last TRB to denote the end of the transfer,
reducing the number of interrupt by orders of magnitude to several dozens.
To avoid potential starvation when a small number of packets is
transfered a timer was added to notify on completed TRBs that are pending.

CRs-fixed: 654261
Change-Id: I337965b677280b0b663423cab06d483e1d17a7a5
Signed-off-by: default avatarGilad Broner <gbroner@codeaurora.org>
parent f9b51e25
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@
#include <linux/dma-mapping.h>
#include <linux/mm.h>
#include <linux/debugfs.h>
#include <linux/hrtimer.h>

#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -509,6 +510,7 @@ struct dwc3_ep_events {
 * @dbg_ep_events: different events counter for endpoint
 * @dbg_ep_events_diff: differential events counter for endpoint
 * @dbg_ep_events_ts: timestamp for previous event counters
 * @xfer_timer: timer to manage transfer complete event timeout
 */
struct dwc3_ep {
	struct usb_ep		endpoint;
@@ -548,6 +550,7 @@ struct dwc3_ep {
	struct dwc3_ep_events	dbg_ep_events;
	struct dwc3_ep_events	dbg_ep_events_diff;
	struct timespec		dbg_ep_events_ts;
	struct hrtimer		xfer_timer;
};

enum dwc3_phy {
+86 −2
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@
#include <linux/list.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
#include <linux/module.h>

#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -58,6 +59,11 @@
#include "debug.h"
#include "io.h"

#define BULK_EP_XFER_TIMEOUT	(8)	/* milliseconds */

static int bulk_ep_xfer_timeout_ms = BULK_EP_XFER_TIMEOUT;
module_param(bulk_ep_xfer_timeout_ms, int, S_IRUGO | S_IWUSR);

static void dwc3_gadget_usb2_phy_suspend(struct dwc3 *dwc, int suspend);
static void dwc3_gadget_usb3_phy_suspend(struct dwc3 *dwc, int suspend);
static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc);
@@ -69,6 +75,8 @@ struct dwc3_usb_gadget {
	struct dwc3 *dwc;
};

static void dwc3_endpoint_transfer_complete(struct dwc3 *, struct dwc3_ep *,
	const struct dwc3_event_depevt *, int);
/**
 * dwc3_gadget_set_test_mode - Enables USB2 Test Modes
 * @dwc: pointer to our context structure
@@ -904,6 +912,17 @@ update_trb:
		break;

	case USB_ENDPOINT_XFER_BULK:
		trb->ctrl = DWC3_TRBCTL_NORMAL;
		/*
		 * bulk endpoint optimization: setting the CSP bit (continue on
		 * short packet) will prevent the core from generating an
		 * XferComplete event for each TRB with a short packet
		 * (except for the last TRB).
		 */
		if (!last)
			trb->ctrl |= DWC3_TRB_CTRL_CSP;
		break;

	case USB_ENDPOINT_XFER_INT:
		trb->ctrl = DWC3_TRBCTL_NORMAL;
		break;
@@ -1186,6 +1205,12 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param,
		dep->resource_index = dwc3_gadget_ep_get_transfer_index(dwc,
				dep->number);
		WARN_ON_ONCE(!dep->resource_index);

		if (usb_endpoint_xfer_bulk(dep->endpoint.desc)) {
			hrtimer_start(&dep->xfer_timer, ktime_set(0,
				bulk_ep_xfer_timeout_ms * NSEC_PER_MSEC),
				HRTIMER_MODE_REL);
		}
	}

	return 0;
@@ -2035,6 +2060,40 @@ static const struct usb_gadget_ops dwc3_gadget_ops = {

/* -------------------------------------------------------------------------- */

static enum hrtimer_restart dwc3_gadget_ep_timer(struct hrtimer *hrtimer)
{
	struct dwc3_ep *dep = container_of(hrtimer, struct dwc3_ep, xfer_timer);
	struct dwc3_event_depevt event;
	struct dwc3 *dwc;

	if (!dep) {
		pr_err("%s: NULL endpoint!\n", __func__);
		goto out;
	}
	if (!(dep->flags & DWC3_EP_ENABLED)) {
		pr_debug("%s: disabled endpoint!\n", __func__);
		goto out;
	}
	if (!dep->endpoint.desc) {
		pr_err("%s: NULL endpoint desc!\n", __func__);
		goto out;
	}
	if (!dep->dwc) {
		pr_err("%s: NULL dwc3 ptr!\n", __func__);
		goto out;
	}
	dwc = dep->dwc;

	event.status = 0;

	spin_lock(&dwc->lock);
	dwc3_endpoint_transfer_complete(dep->dwc, dep, &event, 1);
	spin_unlock(&dwc->lock);

out:
	return HRTIMER_NORESTART;
}

static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,
		u8 num, u32 direction)
{
@@ -2083,6 +2142,10 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,

		INIT_LIST_HEAD(&dep->request_list);
		INIT_LIST_HEAD(&dep->req_queued);

		hrtimer_init(&dep->xfer_timer, CLOCK_MONOTONIC,
			HRTIMER_MODE_REL);
		dep->xfer_timer.function = dwc3_gadget_ep_timer;
	}

	return 0;
@@ -2191,7 +2254,8 @@ static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep,
			dep->flags &= ~DWC3_EP_MISSED_ISOC;
		}
	} else {
		if (count && (event->status & DEPEVT_STATUS_SHORT))
		if (count && (event->status & DEPEVT_STATUS_SHORT) &&
			!(trb->ctrl & DWC3_TRB_CTRL_CSP))
			s_pkt = 1;
	}

@@ -2227,9 +2291,19 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep,
	do {
		req = next_request(&dep->req_queued);
		if (!req) {
			if (event->status)
				WARN_ON_ONCE(1);
			return 1;
		}

		/*
		 * For bulk endpoints, HWO bit may be set when the transfer
		 * complete timeout expires.
		 */
		if (usb_endpoint_xfer_bulk(dep->endpoint.desc) &&
			(req->trb->ctrl & DWC3_TRB_CTRL_HWO))
			return 0;

		i = 0;
		do {
			slot = req->start_slot + i;
@@ -2305,6 +2379,16 @@ static void dwc3_endpoint_transfer_complete(struct dwc3 *dwc,
	if (clean_busy)
		dep->flags &= ~DWC3_EP_BUSY;

	if (usb_endpoint_xfer_bulk(dep->endpoint.desc)) {
		/* Cancel transfer complete timer when hitting the last TRB */
		if (!(event->status & DEPEVT_STATUS_LST))
			hrtimer_start(&dep->xfer_timer, ktime_set(0,
				bulk_ep_xfer_timeout_ms * NSEC_PER_MSEC),
				HRTIMER_MODE_REL);
		else
			hrtimer_cancel(&dep->xfer_timer);
	}

	/*
	 * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround.
	 * See dwc3_gadget_linksts_change_interrupt() for 1st half.