Skip to contentSkip to conversation

usb: dwc3: gadget: Handle streams

PATCH In review Thinh Nguyen 2 participants 13 messages May 6, 2020 – May 14, 2020

Subsystems (2)
lore
Share & tools
lore

Thinh Nguyen

author maintainer
#0 lore raw

v2 cover usb: dwc3: gadget: Handle streams

Overview of stream transfer requirement:
* A transfer will have a set of TRBs of the same stream ID.
* A transfer is started with a stream ID in START_TRANSFER command.
* A new stream will only start when the previous completes.

Overview of stream events:
* A "prime" from host indicates that its endpoints are active (buffers prepared and ready to receive/transmit data). The controller automatically initiates stream if it sees this.
* A "NoStream" rejection event indicates that the host isn't ready. Host will put the endpoint back to idle state. Device may need to reinitiate the stream to start transfer again.
* A Stream Found event means host accepted device initiated stream. Nothing needs to be done from driver.

To initiate a stream, the driver will issue START_TRANSFER command with a stream ID. To reinitiate the stream, the driver must issue END_TRANSFER and restart the transfer with START_TRANSFER command with the same stream ID.

This implementation handles device-initated streams (e.g. UASP driver). It also handles some hosts' quirky behavior where they only prime each endpoint once.

Prerequisite:
This series requires DWC_usb32 patch series
https://patchwork.kernel.org/project/linux-usb/list/?series=269641

[PATCH 1/2] usb: dwc3: Add support for DWC_usb32 IP [PATCH 2/2] usb: dwc3: Get MDWIDTH for DWC_usb32

Changes in v2:
- Update cover letter
- Split handling of stream and of transfer completion into smaller patches
- Reword usb_request->is_last to strictly use for streams
- Enforce transfer completion handling to only for stream capable endpoints

Thinh Nguyen (11):
usb: gadget: Introduce usb_request->is_last
usb: gadget: f_tcm: Inform last stream request
usb: dwc3: gadget: Continue to process pending requests
usb: dwc3: gadget: Check for in-progress END_TRANSFER
usb: dwc3: gadget: Refactor TRB completion handler
usb: dwc3: gadget: Enable XferComplete event
usb: dwc3: gadget: Handle XferComplete for streams
usb: dwc3: gadget: Wait for transfer completion
usb: dwc3: gadget: Don't prepare beyond a transfer
usb: dwc3: gadget: Handle stream transfers
usb: dwc3: gadget: Use SET_EP_PRIME for NoStream

 drivers/usb/dwc3/core.h             |  12 ++
 drivers/usb/dwc3/debug.h            |   2 +
 drivers/usb/dwc3/gadget.c           | 225 +++++++++++++++++++++++++++++++-----
 drivers/usb/gadget/function/f_tcm.c |   3 +
 include/linux/usb/gadget.h          |   3 +
 5 files changed, 215 insertions(+), 30 deletions(-)
Signature · 1 line
2.11.0
author maintainer
#1 lore raw

PATCH v2 1/11 usb: gadget: Introduce usb_request->is_last

To take advantage of DWC3 internal TRB prefetch and cache for performance, inform the controller the last request with stream_id before switching to a different stream transfer. This allows the controller to maintain its transfer burst within the stream ID.

Introduce the usb-request is_last field to help inform the DWC3 controller of this.

---
 include/linux/usb/gadget.h | 3 +++
 1 file changed, 3 insertions(+)
include/linux/usb/gadget.h +3 -0
index e959c09a97c9..281eabae7f33 100644
@@ -42,6 +42,8 @@ struct usb_ep;
  * @num_mapped_sgs: number of SG entries mapped to DMA (internal)
  * @length: Length of that data
  * @stream_id: The stream id, when USB3.0 bulk streams are being used
+ * @is_last: Indicates if this is the last request of a stream_id before
+ *	switching to a different stream (required for DWC3 controllers).
  * @no_interrupt: If true, hints that no completion irq is needed.
  *	Helpful sometimes with deep request queues that are handled
  *	directly by DMA controllers.
@@ -104,6 +106,7 @@ struct usb_request {
 	unsigned		num_mapped_sgs;
 
 	unsigned		stream_id:16;
+	unsigned		is_last:1;
 	unsigned		no_interrupt:1;
 	unsigned		zero:1;
 	unsigned		short_not_ok:1;
Signature · 1 line
2.11.0
author maintainer
#2 lore raw

PATCH v2 2/11 usb: gadget: f_tcm: Inform last stream request

In reply to Thinh Nguyen

Set the request->is_last to each stream request to indicate that the request is the last stream request of a transfer. The DWC3 controller needs to know this info to properly switch streams. The current implementation of f_tcm uses a single request per transfer, so every stream request is the last of its stream.

---
 drivers/usb/gadget/function/f_tcm.c | 3 +++
 1 file changed, 3 insertions(+)
drivers/usb/gadget/function/f_tcm.c +3 -0
index 36504931b2d1..2979cbe4d95f 100644
@@ -531,6 +531,7 @@ static int uasp_prepare_r_request(struct usbg_cmd *cmd)
 		stream->req_in->sg = se_cmd->t_data_sg;
 	}
 
+	stream->req_in->is_last = 1;
 	stream->req_in->complete = uasp_status_data_cmpl;
 	stream->req_in->length = se_cmd->data_length;
 	stream->req_in->context = cmd;
@@ -554,6 +555,7 @@ static void uasp_prepare_status(struct usbg_cmd *cmd)
 	 */
 	iu->len = cpu_to_be16(se_cmd->scsi_sense_length);
 	iu->status = se_cmd->scsi_status;
+	stream->req_status->is_last = 1;
 	stream->req_status->context = cmd;
 	stream->req_status->length = se_cmd->scsi_sense_length + 16;
 	stream->req_status->buf = iu;
@@ -991,6 +993,7 @@ static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req)
 		req->sg = se_cmd->t_data_sg;
 	}
 
+	req->is_last = 1;
 	req->complete = usbg_data_write_cmpl;
 	req->length = se_cmd->data_length;
 	req->context = cmd;
Signature · 1 line
2.11.0
author maintainer
#3 lore raw

PATCH v2 3/11 usb: dwc3: gadget: Continue to process pending requests

In reply to Thinh Nguyen

If there are still pending requests because no TRB was available, prepare more when started requests are completed.

Introduce dwc3_gadget_ep_should_continue() to check for incomplete and pending requests to resume updating new TRBs to the controller's TRB cache.

---
 drivers/usb/dwc3/gadget.c | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)
drivers/usb/dwc3/gadget.c +21 -3
index 4ca3e197bee4..865e6fbb7360 100644
@@ -2605,10 +2605,8 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep,
 
 	req->request.actual = req->request.length - req->remaining;
 
-	if (!dwc3_gadget_ep_request_completed(req)) {
-		__dwc3_gadget_kick_transfer(dep);
+	if (!dwc3_gadget_ep_request_completed(req))
 		goto out;
-	}
 
 	dwc3_gadget_giveback(dep, req, status);
 
@@ -2632,6 +2630,24 @@ static void dwc3_gadget_ep_cleanup_completed_requests(struct dwc3_ep *dep,
 	}
 }
 
+static bool dwc3_gadget_ep_should_continue(struct dwc3_ep *dep)
+{
+	struct dwc3_request	*req;
+
+	if (!list_empty(&dep->pending_list))
+		return true;
+
+	/*
+	 * We only need to check the first entry of the started list. We can
+	 * assume the completed requests are removed from the started list.
+	 */
+	req = next_request(&dep->started_list);
+	if (!req)
+		return false;
+
+	return !dwc3_gadget_ep_request_completed(req);
+}
+
 static void dwc3_gadget_endpoint_frame_from_event(struct dwc3_ep *dep,
 		const struct dwc3_event_depevt *event)
 {
@@ -2661,6 +2677,8 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
 
 	if (stop)
 		dwc3_stop_active_transfer(dep, true, true);
+	else if (dwc3_gadget_ep_should_continue(dep))
+		__dwc3_gadget_kick_transfer(dep);
 
 	/*
 	 * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround.
Signature · 1 line
2.11.0
author maintainer
#4 lore raw

PATCH v2 4/11 usb: dwc3: gadget: Check for in-progress END_TRANSFER

In reply to Thinh Nguyen

While handling TRBs completion, if a END_TRANSFER command isn't completed, don't kick new transfer or issue END_TRANSFER command.

---
 drivers/usb/dwc3/gadget.c | 4 ++++
 1 file changed, 4 insertions(+)
drivers/usb/dwc3/gadget.c +4 -0
index 865e6fbb7360..3bb6f847a865 100644
@@ -2675,11 +2675,15 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
 
 	dwc3_gadget_ep_cleanup_completed_requests(dep, event, status);
 
+	if (dep->flags & DWC3_EP_END_TRANSFER_PENDING)
+		goto out;
+
 	if (stop)
 		dwc3_stop_active_transfer(dep, true, true);
 	else if (dwc3_gadget_ep_should_continue(dep))
 		__dwc3_gadget_kick_transfer(dep);
 
+out:
 	/*
 	 * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround.
 	 * See dwc3_gadget_linksts_change_interrupt() for 1st half.
Signature · 1 line
2.11.0
author maintainer
#5 lore raw

PATCH v2 5/11 usb: dwc3: gadget: Refactor TRB completion handler

In reply to Thinh Nguyen

To prepare for handling of XferComplete event, let's refactor and split up dwc3_gadget_endpoint_transfer_in_progress() to handle TRBs completion for different events. The handling of TRBs completion will be the same, but the status of XferComplete event is different than XferInProgress. No functional change in this commit.

---
 drivers/usb/dwc3/gadget.c | 45 ++++++++++++++++++++++++++-------------------
 1 file changed, 26 insertions(+), 19 deletions(-)
drivers/usb/dwc3/gadget.c +26 -19
index 3bb6f847a865..5f98b424f20b 100644
@@ -2654,34 +2654,22 @@ static void dwc3_gadget_endpoint_frame_from_event(struct dwc3_ep *dep,
 	dep->frame_number = event->parameters;
 }
 
-static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
-		const struct dwc3_event_depevt *event)
+static bool dwc3_gadget_endpoint_trbs_complete(struct dwc3_ep *dep,
+		const struct dwc3_event_depevt *event, int status)
 {
 	struct dwc3		*dwc = dep->dwc;
-	unsigned		status = 0;
-	bool			stop = false;
-
-	dwc3_gadget_endpoint_frame_from_event(dep, event);
-
-	if (event->status & DEPEVT_STATUS_BUSERR)
-		status = -ECONNRESET;
-
-	if (event->status & DEPEVT_STATUS_MISSED_ISOC) {
-		status = -EXDEV;
-
-		if (list_empty(&dep->started_list))
-			stop = true;
-	}
+	bool			no_started_trb = true;
 
 	dwc3_gadget_ep_cleanup_completed_requests(dep, event, status);
 
 	if (dep->flags & DWC3_EP_END_TRANSFER_PENDING)
 		goto out;
 
-	if (stop)
+	if (status == -EXDEV && list_empty(&dep->started_list))
 		dwc3_stop_active_transfer(dep, true, true);
 	else if (dwc3_gadget_ep_should_continue(dep))
-		__dwc3_gadget_kick_transfer(dep);
+		if (__dwc3_gadget_kick_transfer(dep) == 0)
+			no_started_trb = false;
 
 out:
 	/*
@@ -2699,7 +2687,7 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
 				continue;
 
 			if (!list_empty(&dep->started_list))
-				return;
+				return no_started_trb;
 		}
 
 		reg = dwc3_readl(dwc->regs, DWC3_DCTL);
@@ -2708,6 +2696,25 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
 
 		dwc->u1u2 = 0;
 	}
+
+	return no_started_trb;
+}
+
+static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
+		const struct dwc3_event_depevt *event)
+{
+	int status = 0;
+
+	if (usb_endpoint_xfer_isoc(dep->endpoint.desc))
+		dwc3_gadget_endpoint_frame_from_event(dep, event);
+
+	if (event->status & DEPEVT_STATUS_BUSERR)
+		status = -ECONNRESET;
+
+	if (event->status & DEPEVT_STATUS_MISSED_ISOC)
+		status = -EXDEV;
+
+	dwc3_gadget_endpoint_trbs_complete(dep, event, status);
 }
 
 static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep,
Signature · 1 line
2.11.0
author maintainer
#6 lore raw

PATCH v2 6/11 usb: dwc3: gadget: Enable XferComplete event

In reply to Thinh Nguyen

To switch from one stream to another, this requires the driver to start a new transfer with a specific stream ID. For a transfer to complete, the driver needs to indicate the last TRB of a transfer, and it needs to enable XferComplete event to handle completed TRBs of a transfer. Let's enable this event only for stream capable endpoints.

---
 drivers/usb/dwc3/gadget.c | 1 +
 1 file changed, 1 insertion(+)
drivers/usb/dwc3/gadget.c +1 -0
index 5f98b424f20b..81aa7de4cb17 100644
@@ -579,6 +579,7 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action)
 
 	if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) {
 		params.param1 |= DWC3_DEPCFG_STREAM_CAPABLE
+			| DWC3_DEPCFG_XFER_COMPLETE_EN
 			| DWC3_DEPCFG_STREAM_EVENT_EN;
 		dep->stream_capable = true;
 	}
Signature · 1 line
2.11.0
author maintainer
#7 lore raw

PATCH v2 7/11 usb: dwc3: gadget: Handle XferComplete for streams

In reply to Thinh Nguyen

In DWC3, to prepare TRBs for streams, all the TRBs of a transfer will use the same stream ID. To start a new stream, the driver needs to wait for the current transfer to complete or ended (by END_TRANFER command). As a result, inform the controller of the last TRB of a transfer so that it knows when a transfer completes and start a new transfer of a new stream.

Even though the transfer completion handling can be applied for other non-isoc endpoints, only do it for streams due to its requirement. It's better to keep the controller's TRB cache full than waiting for transfer completion and starting a new transfer.

---
 drivers/usb/dwc3/gadget.c | 34 ++++++++++++++++++++++++++++------
 1 file changed, 28 insertions(+), 6 deletions(-)
drivers/usb/dwc3/gadget.c +28 -6
index 81aa7de4cb17..052f6dc52a51 100644
@@ -919,7 +919,8 @@ static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep)
 
 static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
 		dma_addr_t dma, unsigned length, unsigned chain, unsigned node,
-		unsigned stream_id, unsigned short_not_ok, unsigned no_interrupt)
+		unsigned stream_id, unsigned short_not_ok,
+		unsigned no_interrupt, unsigned is_last)
 {
 	struct dwc3		*dwc = dep->dwc;
 	struct usb_gadget	*gadget = &dwc->gadget;
@@ -1012,6 +1013,8 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
 
 	if (chain)
 		trb->ctrl |= DWC3_TRB_CTRL_CHN;
+	else if (dep->stream_capable && is_last)
+		trb->ctrl |= DWC3_TRB_CTRL_LST;
 
 	if (usb_endpoint_xfer_bulk(dep->endpoint.desc) && dep->stream_capable)
 		trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(stream_id);
@@ -1039,6 +1042,7 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
 	unsigned		stream_id = req->request.stream_id;
 	unsigned		short_not_ok = req->request.short_not_ok;
 	unsigned		no_interrupt = req->request.no_interrupt;
+	unsigned		is_last = req->request.is_last;
 
 	if (req->request.num_sgs > 0) {
 		length = sg_dma_len(req->start_sg);
@@ -1059,7 +1063,7 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
 	req->num_trbs++;
 
 	__dwc3_prepare_one_trb(dep, trb, dma, length, chain, node,
-			stream_id, short_not_ok, no_interrupt);
+			stream_id, short_not_ok, no_interrupt, is_last);
 }
 
 static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep,
@@ -1104,7 +1108,8 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep,
 					maxp - rem, false, 1,
 					req->request.stream_id,
 					req->request.short_not_ok,
-					req->request.no_interrupt);
+					req->request.no_interrupt,
+					req->request.is_last);
 		} else {
 			dwc3_prepare_one_trb(dep, req, chain, i);
 		}
@@ -1148,7 +1153,8 @@ static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep,
 		__dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, maxp - rem,
 				false, 1, req->request.stream_id,
 				req->request.short_not_ok,
-				req->request.no_interrupt);
+				req->request.no_interrupt,
+				req->request.is_last);
 	} else if (req->request.zero && req->request.length &&
 		   (IS_ALIGNED(req->request.length, maxp))) {
 		struct dwc3	*dwc = dep->dwc;
@@ -1165,7 +1171,8 @@ static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep,
 		__dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, 0,
 				false, 1, req->request.stream_id,
 				req->request.short_not_ok,
-				req->request.no_interrupt);
+				req->request.no_interrupt,
+				req->request.is_last);
 	} else {
 		dwc3_prepare_one_trb(dep, req, false, 0);
 	}
@@ -2718,6 +2725,19 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
 	dwc3_gadget_endpoint_trbs_complete(dep, event, status);
 }
 
+static void dwc3_gadget_endpoint_transfer_complete(struct dwc3_ep *dep,
+		const struct dwc3_event_depevt *event)
+{
+	int status = 0;
+
+	dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
+
+	if (event->status & DEPEVT_STATUS_BUSERR)
+		status = -ECONNRESET;
+
+	dwc3_gadget_endpoint_trbs_complete(dep, event, status);
+}
+
 static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep,
 		const struct dwc3_event_depevt *event)
 {
@@ -2781,8 +2801,10 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
 			dep->flags &= ~DWC3_EP_DELAY_START;
 		}
 		break;
-	case DWC3_DEPEVT_STREAMEVT:
 	case DWC3_DEPEVT_XFERCOMPLETE:
+		dwc3_gadget_endpoint_transfer_complete(dep, event);
+		break;
+	case DWC3_DEPEVT_STREAMEVT:
 	case DWC3_DEPEVT_RXTXFIFOEVT:
 		break;
 	}
Signature · 1 line
2.11.0
author maintainer
#8 lore raw

PATCH v2 8/11 usb: dwc3: gadget: Wait for transfer completion

In reply to Thinh Nguyen

If a transfer is in-progress, any new request should not kick off another transfer. The driver needs to wait for the current transfer to complete before starting off the next transfer. Introduce a new flag DWC3_EP_WAIT_TRANSFER_COMPLETE for this.

---
 drivers/usb/dwc3/core.h   | 1 +
 drivers/usb/dwc3/gadget.c | 9 ++++++++-
 2 files changed, 9 insertions(+), 1 deletion(-)
drivers/usb/dwc3/core.h +1 -0
index 7204a838ec06..b11183a715a7 100644
@@ -701,6 +701,7 @@ struct dwc3_ep {
 #define DWC3_EP_END_TRANSFER_PENDING BIT(4)
 #define DWC3_EP_PENDING_REQUEST	BIT(5)
 #define DWC3_EP_DELAY_START	BIT(6)
+#define DWC3_EP_WAIT_TRANSFER_COMPLETE	BIT(7)
 
 	/* This last one is specific to EP0 */
 #define DWC3_EP0_DIR_IN		BIT(31)
drivers/usb/dwc3/gadget.c +8 -1
index 052f6dc52a51..97c6a5785725 100644
@@ -1292,6 +1292,9 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep)
 		return ret;
 	}
 
+	if (dep->stream_capable && req->request.is_last)
+		dep->flags |= DWC3_EP_WAIT_TRANSFER_COMPLETE;
+
 	return 0;
 }
 
@@ -1498,6 +1501,9 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
 	list_add_tail(&req->list, &dep->pending_list);
 	req->status = DWC3_REQUEST_STATUS_QUEUED;
 
+	if (dep->flags & DWC3_EP_WAIT_TRANSFER_COMPLETE)
+		return 0;
+
 	/* Start the transfer only after the END_TRANSFER is completed */
 	if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) {
 		dep->flags |= DWC3_EP_DELAY_START;
@@ -2735,7 +2741,8 @@ static void dwc3_gadget_endpoint_transfer_complete(struct dwc3_ep *dep,
 	if (event->status & DEPEVT_STATUS_BUSERR)
 		status = -ECONNRESET;
 
-	dwc3_gadget_endpoint_trbs_complete(dep, event, status);
+	if (dwc3_gadget_endpoint_trbs_complete(dep, event, status))
+		dep->flags &= ~DWC3_EP_WAIT_TRANSFER_COMPLETE;
 }
 
 static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep,
Signature · 1 line
2.11.0
author maintainer
#9 lore raw

PATCH v2 9/11 usb: dwc3: gadget: Don't prepare beyond a transfer

In reply to Thinh Nguyen

Don't prepare TRBs beyond a transfer. In DWC_usb32, its transfer burst capability may try to read and use TRBs beyond the active transfer. For other controllers, they don't process the next transfer TRBs until the current transfer is completed. Explicitly prevent preparing TRBs ahead for all controllers.

---
 drivers/usb/dwc3/gadget.c | 8 ++++++++
 1 file changed, 8 insertions(+)
drivers/usb/dwc3/gadget.c +8 -0
index 97c6a5785725..07824b670440 100644
@@ -1231,6 +1231,14 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep)
 
 		if (!dwc3_calc_trbs_left(dep))
 			return;
+
+		/*
+		 * Don't prepare beyond a transfer. In DWC_usb32, its transfer
+		 * burst capability may try to read and use TRBs beyond the
+		 * active transfer instead of stopping.
+		 */
+		if (dep->stream_capable && req->request.is_last)
+			return;
 	}
 }
 
Signature · 1 line
2.11.0
author maintainer
#10 lore raw

PATCH v2 10/11 usb: dwc3: gadget: Handle stream transfers

In reply to Thinh Nguyen

Overview of stream transfer requirement:
* A transfer will have a set of TRBs of the same stream ID.
* A transfer is started with a stream ID in START_TRANSFER command.
* A new stream will only start when the previous completes.

Overview of stream events:
* A "prime" from host indicates that its endpoints are active (buffers prepared and ready to receive/transmit data). The controller automatically initiates stream if it sees this.
* A "NoStream" rejection event indicates that the host isn't ready. Host will put the endpoint back to idle state. Device may need to reinitiate the stream to start transfer again.
* A Stream Found event means host accepted device initiated stream. Nothing needs to be done from driver.

To initiate a stream, the driver will issue START_TRANSFER command with a stream ID. To reinitiate the stream, the driver must issue END_TRANSFER and restart the transfer with START_TRANSFER command with the same stream ID.

This implementation handles device-initated streams (e.g. UASP driver). It also handles some hosts' quirky behavior where they only prime each endpoint once.

---
 drivers/usb/dwc3/core.h   |  8 ++++
 drivers/usb/dwc3/debug.h  |  2 +
 drivers/usb/dwc3/gadget.c | 97 +++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 104 insertions(+), 3 deletions(-)
drivers/usb/dwc3/core.h +8 -0
index b11183a715a7..4def088329c7 100644
@@ -495,6 +495,7 @@
 #define DWC3_DGCMD_SELECTED_FIFO_FLUSH	0x09
 #define DWC3_DGCMD_ALL_FIFO_FLUSH	0x0a
 #define DWC3_DGCMD_SET_ENDPOINT_NRDY	0x0c
+#define DWC3_DGCMD_SET_ENDPOINT_PRIME	0x0d
 #define DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK	0x10
 
 #define DWC3_DGCMD_STATUS(n)		(((n) >> 12) & 0x0F)
@@ -702,6 +703,9 @@ struct dwc3_ep {
 #define DWC3_EP_PENDING_REQUEST	BIT(5)
 #define DWC3_EP_DELAY_START	BIT(6)
 #define DWC3_EP_WAIT_TRANSFER_COMPLETE	BIT(7)
+#define DWC3_EP_IGNORE_NEXT_NOSTREAM	BIT(8)
+#define DWC3_EP_FORCE_RESTART_STREAM	BIT(9)
+#define DWC3_EP_FIRST_STREAM_PRIMED	BIT(10)
 
 	/* This last one is specific to EP0 */
 #define DWC3_EP0_DIR_IN		BIT(31)
@@ -1301,6 +1305,10 @@ struct dwc3_event_depevt {
 #define DEPEVT_STREAMEVT_FOUND		1
 #define DEPEVT_STREAMEVT_NOTFOUND	2
 
+/* Stream event parameter */
+#define DEPEVT_STREAM_PRIME		0xfffe
+#define DEPEVT_STREAM_NOSTREAM		0x0
+
 /* Control-only Status */
 #define DEPEVT_STATUS_CONTROL_DATA	1
 #define DEPEVT_STATUS_CONTROL_STATUS	2
drivers/usb/dwc3/debug.h +2 -0
index 0f95656c9622..d8f600e0e88f 100644
@@ -68,6 +68,8 @@ dwc3_gadget_generic_cmd_string(u8 cmd)
 		return "All FIFO Flush";
 	case DWC3_DGCMD_SET_ENDPOINT_NRDY:
 		return "Set Endpoint NRDY";
+	case DWC3_DGCMD_SET_ENDPOINT_PRIME:
+		return "Set Endpoint Prime";
 	case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK:
 		return "Run SoC Bus Loopback Test";
 	default:
drivers/usb/dwc3/gadget.c +94 -3

139 lines not on this page · raw message

Signature · 1 line
2.11.0
author maintainer
#11 lore raw

PATCH v2 11/11 usb: dwc3: gadget: Use SET_EP_PRIME for NoStream

In reply to Thinh Nguyen

DWC_usb32 v1.00a and later can use SET_EP_PRIME command to reinitiate a stream. Use the command to handle NoStream rejection instead of ending and restarting the endpoint.

---
 drivers/usb/dwc3/core.h   |  3 +++
 drivers/usb/dwc3/gadget.c | 13 ++++++++++---
 2 files changed, 13 insertions(+), 3 deletions(-)
drivers/usb/dwc3/core.h +3 -0
index 4def088329c7..013f42a2b5dc 100644
@@ -1161,6 +1161,9 @@ struct dwc3 {
 #define DWC31_REVISION_180A	0x3138302a
 #define DWC31_REVISION_190A	0x3139302a
 
+#define DWC32_REVISION_ANY	0x0
+#define DWC32_REVISION_100A	0x3130302a
+
 	u32			version_type;
 
 #define DWC31_VERSIONTYPE_ANY		0x0
drivers/usb/dwc3/gadget.c +10 -3
index 0380f76151a1..fea4fde1b5e3 100644
@@ -2844,9 +2844,16 @@ static void dwc3_gadget_endpoint_stream_event(struct dwc3_ep *dep,
 		 * hosts, force to reinitate the stream until the host is ready
 		 * instead of waiting for the host to prime the endpoint.
 		 */
-		dep->flags |= DWC3_EP_DELAY_START;
-		dwc3_stop_active_transfer(dep, true, true);
-		return;
+		if (DWC3_VER_IS_WITHIN(DWC32, 100A, ANY)) {
+			unsigned int cmd = DWC3_DGCMD_SET_ENDPOINT_PRIME;
+
+			dwc3_send_gadget_generic_command(dwc, cmd, dep->number);
+		} else {
+			dep->flags |= DWC3_EP_DELAY_START;
+			dwc3_stop_active_transfer(dep, true, true);
+			return;
+		}
+		break;
 	}
 
 out:
Signature · 1 line
2.11.0
#12 lore raw
In reply to Thinh Nguyen

Hi,

Quoted 38 lines · Thinh Nguyen

Overview of stream transfer requirement:
* A transfer will have a set of TRBs of the same stream ID.
* A transfer is started with a stream ID in START_TRANSFER command.
* A new stream will only start when the previous completes.

Overview of stream events:
* A "prime" from host indicates that its endpoints are active (buffers prepared and ready to receive/transmit data). The controller automatically initiates stream if it sees this.
* A "NoStream" rejection event indicates that the host isn't ready. Host will put the endpoint back to idle state. Device may need to reinitiate the stream to start transfer again.
* A Stream Found event means host accepted device initiated stream. Nothing needs to be done from driver.

To initiate a stream, the driver will issue START_TRANSFER command with a stream ID. To reinitiate the stream, the driver must issue END_TRANSFER and restart the transfer with START_TRANSFER command with the same stream ID.

This implementation handles device-initated streams (e.g. UASP driver). It also handles some hosts' quirky behavior where they only prime each endpoint once.

Prerequisite:
This series requires DWC_usb32 patch series
https://patchwork.kernel.org/project/linux-usb/list/?series=269641

[PATCH 1/2] usb: dwc3: Add support for DWC_usb32 IP [PATCH 2/2] usb: dwc3: Get MDWIDTH for DWC_usb32

Changes in v2:
- Update cover letter
- Split handling of stream and of transfer completion into smaller patches
- Reword usb_request->is_last to strictly use for streams
- Enforce transfer completion handling to only for stream capable endpoints

Quoted 12 lines

Thinh Nguyen (11):
usb: gadget: Introduce usb_request->is_last
usb: gadget: f_tcm: Inform last stream request
usb: dwc3: gadget: Continue to process pending requests
usb: dwc3: gadget: Check for in-progress END_TRANSFER
usb: dwc3: gadget: Refactor TRB completion handler
usb: dwc3: gadget: Enable XferComplete event
usb: dwc3: gadget: Handle XferComplete for streams
usb: dwc3: gadget: Wait for transfer completion
usb: dwc3: gadget: Don't prepare beyond a transfer
usb: dwc3: gadget: Handle stream transfers
usb: dwc3: gadget: Use SET_EP_PRIME for NoStream

This seris is now in testing/next. Can you check that everything looks fine for you? I had to manually apply one patch.

Signature · 1 line
balbi