diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index b19b67d8d399..539034cc0e5f 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3249,6 +3249,7 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep, const struct dwc3_event_depevt *event, struct dwc3_request *req, int status) { + struct dwc3 *dwc = dep->dwc; int ret; if (req->request.num_mapped_sgs) @@ -3269,6 +3270,28 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep, req->needs_extra_trb = false; } + /* + * If MISS ISOC happens, we need to move the req from started_list + * to cancelled_list, then unmap the req and clear the HWO of trb. + * Later in the dwc3_gadget_endpoint_trbs_complete(), it will move + * the req from the cancelled_list to the pending_list, and restart + * the req for isoc transfer. + */ + if (status == -EXDEV && usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + req->remaining = 0; + req->needs_extra_trb = false; + dwc3_gadget_move_cancelled_request(req, DWC3_REQUEST_STATUS_DEQUEUED); + if (req->trb) { + usb_gadget_unmap_request_by_dev(dwc->sysdev, + &req->request, + req->direction); + req->trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + req->trb = NULL; + } + ret = 0; + goto out; + } + dwc3_gadget_giveback(dep, req, status); out: @@ -3324,6 +3347,7 @@ static bool dwc3_gadget_endpoint_trbs_complete(struct dwc3_ep *dep, const struct dwc3_event_depevt *event, int status) { struct dwc3 *dwc = dep->dwc; + struct dwc3_request *req, *tmp; bool no_started_trb = true; if (!dep->endpoint.desc) @@ -3334,6 +3358,29 @@ static bool dwc3_gadget_endpoint_trbs_complete(struct dwc3_ep *dep, if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) goto out; + /* + * If MISS ISOC happens, we need to do the following three steps + * to restart the reqs in the cancelled_list and pending_list + * in order. + * Step1. Move all the reqs from pending_list to the tail of + * cancelled_list. + * Step2. Move all the reqs from cancelled_list to the tail + * of pending_list. + * Step3. Stop and restart an isoc transfer. + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && status == -EXDEV && + !list_empty(&dep->cancelled_list) && + !list_empty(&dep->pending_list)) { + list_for_each_entry_safe(req, tmp, &dep->pending_list, list) + dwc3_gadget_move_cancelled_request(req, DWC3_REQUEST_STATUS_DEQUEUED); + } + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && status == -EXDEV && + !list_empty(&dep->cancelled_list)) { + list_for_each_entry_safe(req, tmp, &dep->cancelled_list, list) + dwc3_gadget_move_queued_request(req); + } + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && list_empty(&dep->started_list) && (list_empty(&dep->pending_list) || status == -EXDEV)) diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index 77df4b6d6c13..8129beb07466 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -104,6 +104,22 @@ static inline void dwc3_gadget_move_cancelled_request(struct dwc3_request *req, list_move_tail(&req->list, &dep->cancelled_list); } +/** + * dwc3_gadget_move_queued_request - move @req to the pending_list + * @req: the request to be moved + * + * Caller should take care of locking. This function will move @req from its + * current list to the endpoint's pending_list. + * + */ +static inline void dwc3_gadget_move_queued_request(struct dwc3_request *req) +{ + struct dwc3_ep *dep = req->dep; + + req->status = DWC3_REQUEST_STATUS_QUEUED; + list_move_tail(&req->list, &dep->pending_list); +} + void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, int status);