[PATCH] usb: dwc3: gadget: Properly handle miss isoc event

From: William Wu
Date: Wed Jul 12 2023 - 22:58:13 EST


If miss isoc event happens, the current code just set
the req status to -EXDEV and giveback the req to the usb
gadget driver, and then stop the active transfer with the
cmd DWC3_DEPCMD_ENDTRANSFER and wait for a XferNotReady
event to restart a transfer again. However, for isoc
ep in transfer, it cause to lost the isoc data of the
req.

This patch moves the miss isoc req to pending_list in
order to restart transfer immediately instead of give
back the req to the usb gadget driver.

Signed-off-by: William Wu <william.wu@xxxxxxxxxxxxxx>
---
drivers/usb/dwc3/gadget.c | 47 +++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc3/gadget.h | 16 +++++++++++++
2 files changed, 63 insertions(+)

diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 5fd067151fbf..ef295746b241 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -3454,6 +3454,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 request_status;
int ret;

@@ -3475,6 +3476,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;
+ }
+
/*
* The event status only reflects the status of the TRB with IOC set.
* For the requests that don't set interrupt on completion, the driver
@@ -3564,6 +3587,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;

dwc3_gadget_ep_cleanup_completed_requests(dep, event, status);
@@ -3574,6 +3598,29 @@ static bool dwc3_gadget_endpoint_trbs_complete(struct dwc3_ep *dep,
if (!dep->endpoint.desc)
return no_started_trb;

+ /*
+ * 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 55a56cf67d73..242426b67798 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);

--
2.17.1