usb: dwc3: gadget: Look ahead when setting IOC

Previously if we run out of TRBs for the last SG entry that requires
an extra TRB, we set interrupt-on-completion (IOC) bit to an already
prepared TRB (i.e. HWO=1). This logic is not clean, and it's not a
typical way to prepare TRB. Also, this prevents showing IOC setup in
tracepoint when __dwc3_prepare_one_trb() is executed. Instead, let's
look ahead when preparing TRB to know whether to set the IOC bit before
the last SG entry. This requires adding a new parameter "must_interrupt"
to dwc3_prepare_one_trb().

Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Signed-off-by: Felipe Balbi <balbi@kernel.org>
This commit is contained in:
Thinh Nguyen 2020-09-30 17:44:19 -07:00 committed by Felipe Balbi
parent d72ecc08de
commit f9cc581bad

View file

@ -947,7 +947,7 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
dma_addr_t dma, unsigned int length, unsigned int chain,
unsigned int node, unsigned int stream_id,
unsigned int short_not_ok, unsigned int no_interrupt,
unsigned int is_last)
unsigned int is_last, bool must_interrupt)
{
struct dwc3 *dwc = dep->dwc;
struct usb_gadget *gadget = dwc->gadget;
@ -1034,7 +1034,7 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI;
}
if ((!no_interrupt && !chain) ||
if ((!no_interrupt && !chain) || must_interrupt ||
(dwc3_calc_trbs_left(dep) == 1))
trb->ctrl |= DWC3_TRB_CTRL_IOC;
@ -1061,10 +1061,12 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
* @chain: should this TRB be chained to the next?
* @node: only for isochronous endpoints. First TRB needs different type.
* @use_bounce_buffer: set to use bounce buffer
* @must_interrupt: set to interrupt on TRB completion
*/
static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
struct dwc3_request *req, unsigned int trb_length,
unsigned int chain, unsigned int node, bool use_bounce_buffer)
unsigned int chain, unsigned int node, bool use_bounce_buffer,
bool must_interrupt)
{
struct dwc3_trb *trb;
dma_addr_t dma;
@ -1091,7 +1093,21 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
req->num_trbs++;
__dwc3_prepare_one_trb(dep, trb, dma, trb_length, chain, node,
stream_id, short_not_ok, no_interrupt, is_last);
stream_id, short_not_ok, no_interrupt, is_last,
must_interrupt);
}
static bool dwc3_needs_extra_trb(struct dwc3_ep *dep, struct dwc3_request *req)
{
unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc);
unsigned int rem = req->request.length % maxp;
if ((req->request.length && req->request.zero && !rem &&
!usb_endpoint_xfer_isoc(dep->endpoint.desc)) ||
(!req->direction && rem))
return true;
return false;
}
/**
@ -1111,9 +1127,7 @@ static int dwc3_prepare_last_sg(struct dwc3_ep *dep,
unsigned int rem = req->request.length % maxp;
unsigned int num_trbs = 1;
if ((req->request.length && req->request.zero && !rem &&
!usb_endpoint_xfer_isoc(dep->endpoint.desc)) ||
(!req->direction && rem))
if (dwc3_needs_extra_trb(dep, req))
num_trbs++;
if (dwc3_calc_trbs_left(dep) < num_trbs)
@ -1124,13 +1138,13 @@ static int dwc3_prepare_last_sg(struct dwc3_ep *dep,
/* Prepare a normal TRB */
if (req->direction || req->request.length)
dwc3_prepare_one_trb(dep, req, entry_length,
req->needs_extra_trb, node, false);
req->needs_extra_trb, node, false, false);
/* Prepare extra TRBs for ZLP and MPS OUT transfer alignment */
if ((!req->direction && !req->request.length) || req->needs_extra_trb)
dwc3_prepare_one_trb(dep, req,
req->direction ? 0 : maxp - rem,
false, 1, true);
false, 1, true, false);
return num_trbs;
}
@ -1145,6 +1159,7 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
unsigned int remaining = req->request.num_mapped_sgs
- req->num_queued_sgs;
unsigned int num_trbs = req->num_trbs;
bool needs_extra_trb = dwc3_needs_extra_trb(dep, req);
/*
* If we resume preparing the request, then get the remaining length of
@ -1155,6 +1170,7 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
for_each_sg(sg, s, remaining, i) {
unsigned int trb_length;
bool must_interrupt = false;
bool last_sg = false;
trb_length = min_t(unsigned int, length, sg_dma_len(s));
@ -1176,9 +1192,20 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
if (last_sg) {
if (!dwc3_prepare_last_sg(dep, req, trb_length, i))
goto out;
break;
} else {
dwc3_prepare_one_trb(dep, req, trb_length, 1, i, false);
/*
* Look ahead to check if we have enough TRBs for the
* last SG entry. If not, set interrupt on this TRB to
* resume preparing the last SG entry when more TRBs are
* free.
*/
if (needs_extra_trb && dwc3_calc_trbs_left(dep) <= 2 &&
sg_dma_len(sg_next(s)) >= length)
must_interrupt = true;
dwc3_prepare_one_trb(dep, req, trb_length, 1, i, false,
must_interrupt);
}
/*
@ -1203,31 +1230,10 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
break;
}
if (!dwc3_calc_trbs_left(dep))
if (!dwc3_calc_trbs_left(dep) || must_interrupt)
break;
}
return req->num_trbs - num_trbs;
out:
/*
* If we run out of TRBs for MPS alignment setup, then set IOC on the
* previous TRB to get notified for TRB completion to resume when more
* TRBs are available.
*
* Note: normally we shouldn't update the TRB after the HWO bit is set.
* However, the controller doesn't update its internal cache to handle
* the newly prepared TRBs until UPDATE_TRANSFER or START_TRANSFER
* command is executed. At this point, it doesn't happen yet, so we
* should be fine modifying it here.
*/
if (i) {
struct dwc3_trb *trb;
trb = dwc3_ep_prev_trb(dep, dep->trb_enqueue);
trb->ctrl |= DWC3_TRB_CTRL_IOC;
}
return req->num_trbs - num_trbs;
}