From: Pierre Le Magourou <plemagourou@xxxxxxxxxxxxxxxxxxxx> We can't wait for END_OF_TRANSFER event in dwc3_gadget_ep_dequeue() because it can be called in an interruption context. TRBs are now cleared after the END_OF_TRANSFER completion, in the interrupt handler. Signed-off-by: Pierre Le Magourou <plemagourou@xxxxxxxxxxxxxxxxxxxx> --- drivers/usb/dwc3/core.h | 14 ++++++ drivers/usb/dwc3/gadget.c | 122 +++++++++++++++++++++++++++------------------- 2 files changed, 85 insertions(+), 51 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 7a217a36c36b..5e2070183e3a 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -627,6 +627,7 @@ struct dwc3_event_buffer { * @wait_end_transfer: wait_queue_head_t for waiting on End Transfer complete * @lock: spinlock for endpoint request queue traversal * @regs: pointer to first endpoint register + * @pending_trb: pointer to pending TRB structure * @trb_pool: array of transaction buffers * @trb_pool_dma: dma address of @trb_pool * @trb_enqueue: enqueue 'pointer' into TRB array @@ -654,6 +655,7 @@ struct dwc3_ep { void __iomem *regs; struct dwc3_trb *trb_pool; + struct dwc3_pending_trb *pending_trb; dma_addr_t trb_pool_dma; struct dwc3 *dwc; @@ -777,6 +779,18 @@ struct dwc3_trb { u32 ctrl; } __packed; +/** + * struct dwc3_pending_trb + * @dirty_trb: pointer to TRB waiting for END_TRANSFER completion + * @extra_trb: true if extra TRB was set to align transfer + * @num_pending_sgs: number of pending SGs + */ +struct dwc3_pending_trb { + struct dwc3_trb *dirty_trb; + bool extra_trb; + unsigned int num_pending_sgs; +}; + /** * struct dwc3_hwparams - copy of HWPARAMS registers * @hwparams0: GHWPARAMS0 diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 69bf137aab37..dd1f2b74723b 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1341,6 +1341,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, struct dwc3_ep *dep = to_dwc3_ep(ep); struct dwc3 *dwc = dep->dwc; + struct dwc3_pending_trb *p_trb; unsigned long flags; int ret = 0; @@ -1367,63 +1368,29 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, * If request was already started, this means we had to * stop the transfer. With that we also need to ignore * all TRBs used by the request, however TRBs can only - * be modified after completion of END_TRANSFER - * command. So what we do here is that we wait for - * END_TRANSFER completion and only after that, we jump - * over TRBs by clearing HWO and incrementing dequeue - * pointer. + * be modified after completion of END_TRANSFER command. * - * Note that we have 2 possible types of transfers here: - * - * i) Linear buffer request - * ii) SG-list based request - * - * SG-list based requests will have r->num_pending_sgs - * set to a valid number (> 0). Linear requests, - * normally use a single TRB. - * - * For each of these two cases, if r->unaligned flag is - * set, one extra TRB has been used to align transfer - * size to wMaxPacketSize. - * - * All of these cases need to be taken into - * consideration so we don't mess up our TRB ring - * pointers. + * Don't wait for END_TRANSFER completion here because + * dwc3_gadget_ep_dequeue() can be called from within + * the controller's interrupt handler. Save the TRB + * pointer, the number of pending sgs, and the + * extra_trb flag, so that TRBs HWO can be cleared and + * dequeue pointer incremented when the END_TRANSFER + * completion is received. */ - wait_event_lock_irq(dep->wait_end_transfer, - !(dep->flags & DWC3_EP_END_TRANSFER_PENDING), - dwc->lock); if (!r->trb) goto out0; - if (r->num_pending_sgs) { - struct dwc3_trb *trb; - int i = 0; - - for (i = 0; i < r->num_pending_sgs; i++) { - trb = r->trb + i; - trb->ctrl &= ~DWC3_TRB_CTRL_HWO; - dwc3_ep_inc_deq(dep); - } - - if (r->unaligned || r->zero) { - trb = r->trb + r->num_pending_sgs + 1; - trb->ctrl &= ~DWC3_TRB_CTRL_HWO; - dwc3_ep_inc_deq(dep); - } - } else { - struct dwc3_trb *trb = r->trb; - - trb->ctrl &= ~DWC3_TRB_CTRL_HWO; - dwc3_ep_inc_deq(dep); - - if (r->unaligned || r->zero) { - trb = r->trb + 1; - trb->ctrl &= ~DWC3_TRB_CTRL_HWO; - dwc3_ep_inc_deq(dep); - } - } + p_trb = kzalloc(sizeof(*p_trb), GFP_KERNEL|GFP_ATOMIC); + p_trb->dirty_trb = r->trb; + p_trb->num_pending_sgs = r->num_pending_sgs; + + if (r->unaligned || r->zero) + p_trb->extra_trb = true; + + dep->pending_trb = p_trb; + goto out1; } dev_err(dwc->dev, "request %pK was not queued to %s\n", @@ -2430,6 +2397,56 @@ static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep, __dwc3_gadget_start_isoc(dep); } +/* Clear HWO and increment dequeue pointer. + * + * 2 types of transfers are possible: + * + * i) Linear buffer request + * ii) SG-list based request + * + * SG-list based requests have p_trb->num_pending_sgs set to a valid number + * (> 0). Linear requests, normally use a single TRB. + * + * For each of these two cases, if p_trb->extra_trb flag is set, one + * extra TRB has been used to align transfer size to wMaxPacketSize. + * + * All of these cases need to be taken into consideration so TRB ring pointers + * are not messed up. + */ +static void dwc3_clear_pending_trbs(struct dwc3_ep *dep) +{ + struct dwc3_pending_trb *p_trb = dep->pending_trb; + struct dwc3_trb *trb; + + if (p_trb->num_pending_sgs) { + int i; + + for (i = 0; i < p_trb->num_pending_sgs; i++) { + trb = p_trb->dirty_trb + i; + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + dwc3_ep_inc_deq(dep); + } + + if (p_trb->extra_trb) { + trb = p_trb->dirty_trb + p_trb->num_pending_sgs + 1; + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + dwc3_ep_inc_deq(dep); + } + } else { + trb = p_trb->dirty_trb; + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + dwc3_ep_inc_deq(dep); + + if (p_trb->extra_trb) { + trb = p_trb->dirty_trb + 1; + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + dwc3_ep_inc_deq(dep); + } + } + kfree(p_trb); + dep->pending_trb = NULL; +} + static void dwc3_endpoint_interrupt(struct dwc3 *dwc, const struct dwc3_event_depevt *event) { @@ -2466,6 +2483,9 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, if (cmd == DWC3_DEPCMD_ENDTRANSFER) { dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING; wake_up(&dep->wait_end_transfer); + + if (dep->pending_trb) + dwc3_clear_pending_trbs(dep); } break; case DWC3_DEPEVT_STREAMEVT: -- 2.16.2 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html