Skip to contentSkip to conversation

usb: dwc2: Add support for SPLIT transactions

PATCH New Stefan Brüns 3 participants 10 messages Jan 17, 2016 – Jan 27, 2016 USB

lore
Share & tools
lore
#0 lore raw

cover usb: dwc2: Add support for SPLIT transactions

The first patch fixes an out-of-bounds access, and makes the calculation of maximum transfer size more straightforward. It also makes overriding the maximum transfer size easier for split transactions

2nd and 3rd patch cleanup and restructure the current code in preparation for the split support.

Patch 4 and 5 add the actual support for CONTROL and BULK transactions.

Patch 6 adds support for INTERUPT splits.

The patch series uses the same logic as the earlier patch series, but is otherwise a rewrite.

With the patch series, all my LS/FS devices enumerate behind the RPi 1 onboard hub and an external hub.

Interrupt transfers have been tested with a Logitech K400 and a noname wired keyboard. Both work if "stdin" includes usbkbd prior to usb start, and u-boot has been configured with CONFIG_USB_KEYBOARD=y and CONFIG_SYS_USB_EVENT_POLL=y (but only one at a time).

Stefan Br?ns (6):
usb: dwc2: Fix out-of-bounds access, fix chunk size
usb: dwc2: Simplify wait_for_chhltd(), remove ignore_ack
usb: dwc2: split transfer core from outer loop
usb: dwc2: add helper function for setting SPLIT HC registers
usb: dwc2: Implement SPLIT transaction support
usb: dwc2: Add SPLIT INTERRUPT transaction support

 drivers/usb/host/dwc2.c | 280 ++++++++++++++++++++++++++++++++----------------
 drivers/usb/host/dwc2.h |   1 +
 2 files changed, 187 insertions(+), 94 deletions(-)
Signature · 1 line
2.1.4
#1 lore raw

PATCH 1/6 usb: dwc2: Fix out-of-bounds access, fix chunk size

Fix two errors in transfer len calculation, move loop invariant code out of loop.

If xfer_len is equal to CONFIG_DWC2_MAX_TRANSFER_SIZE (or slightly smaller), the xfer_len will be to large, e.g.:
xfer_len = MAX_TRANSFER_SIZE = 65535
max packet size = 512

    => num_packets = 128
    => IN xfer_len = 65536

For OUT transactions larger than (65536 - mps) bytes, the xfer_len determination is quite awkward, it is only correct due to:
- max_packet_size for control/bulk/interrupt is required to be power-of-two.
- (CONFIG_DWC2_MAX_TRANSFER_SIZE + 1) % max-packet-size is zero for all allowed (2^3 ... 2^9) packet sizes

As the max xfer len is loop invariant, it can be moved out of the loop.

---
 drivers/usb/host/dwc2.c | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)
drivers/usb/host/dwc2.c +15 -15
index 5ef6deb..d317104 100644
@@ -786,34 +786,34 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	uint32_t xfer_len;
 	uint32_t num_packets;
 	int stop_transfer = 0;
+	uint32_t max_xfer_len;
 
 	debug("%s: msg: pipe %lx pid %d in %d len %d\n", __func__, pipe, *pid,
 	      in, len);
 
+	max_xfer_len = CONFIG_DWC2_MAX_PACKET_COUNT * max;
+	if (max_xfer_len > CONFIG_DWC2_MAX_TRANSFER_SIZE)
+		max_xfer_len = CONFIG_DWC2_MAX_TRANSFER_SIZE;
+	if (max_xfer_len > DWC2_DATA_BUF_SIZE)
+		max_xfer_len = DWC2_DATA_BUF_SIZE;
+
+	/* Make sure that max_xfer_len is a multiple of max packet size. */
+	num_packets = max_xfer_len / max;
+	max_xfer_len = num_packets * max;
+
 	do {
 		/* Initialize channel */
 		dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, dev, devnum, ep, in,
 				eptype, max);
 
 		xfer_len = len - done;
-		if (xfer_len > CONFIG_DWC2_MAX_TRANSFER_SIZE)
-			xfer_len = CONFIG_DWC2_MAX_TRANSFER_SIZE - max + 1;
-		if (xfer_len > DWC2_DATA_BUF_SIZE)
-			xfer_len = DWC2_DATA_BUF_SIZE - max + 1;
 
-		/* Make sure that xfer_len is a multiple of max packet size. */
-		if (xfer_len > 0) {
+		if (xfer_len > max_xfer_len)
+			xfer_len = max_xfer_len;
+		else if (xfer_len > max)
 			num_packets = (xfer_len + max - 1) / max;
-			if (num_packets > CONFIG_DWC2_MAX_PACKET_COUNT) {
-				num_packets = CONFIG_DWC2_MAX_PACKET_COUNT;
-				xfer_len = num_packets * max;
-			}
-		} else {
+		else
 			num_packets = 1;
-		}
-
-		if (in)
-			xfer_len = num_packets * max;
 
 		debug("%s: chunk: pid %d xfer_len %u pkts %u\n", __func__,
 		      *pid, xfer_len, num_packets);
Signature · 1 line
2.1.4
#2 lore raw

PATCH 2/6 usb: dwc2: Simplify wait_for_chhltd(), remove ignore_ack

In reply to Stefan Brüns

A transfer is completed if the XFERCOMP flag is set, irrespective of the ACK flag. BULK OUT transfers to some HS devices complete without having the ACK flag set, which signal the devices has responded with an NYET to the transfer (PING protocol).
The new behaviour matches the Linux kernel minus any PING protocol.

Also see 5966defabdcc (usb: dwc2: fix bulk transfers)

---
 drivers/usb/host/dwc2.c | 40 +++++++++++++++++-----------------------
 1 file changed, 17 insertions(+), 23 deletions(-)
drivers/usb/host/dwc2.c +17 -23
index d317104..ad097cb 100644
@@ -729,10 +729,8 @@ static int dwc_otg_submit_rh_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	return stat;
 }
 
-int wait_for_chhltd(struct dwc2_core_regs *regs, uint32_t *sub, int *toggle,
-		    bool ignore_ack)
+int wait_for_chhltd(struct dwc2_core_regs *regs, uint32_t *sub, int *toggle)
 {
-	uint32_t hcint_comp_hlt_ack = DWC2_HCINT_XFERCOMP | DWC2_HCINT_CHHLTD;
 	struct dwc2_hc_regs *hc_regs = &regs->hc_regs[DWC2_HC_CHANNEL];
 	int ret;
 	uint32_t hcint, hctsiz;
@@ -742,25 +740,22 @@ int wait_for_chhltd(struct dwc2_core_regs *regs, uint32_t *sub, int *toggle,
 		return ret;
 
 	hcint = readl(&hc_regs->hcint);
-	if (hcint & (DWC2_HCINT_NAK | DWC2_HCINT_FRMOVRUN))
-		return -EAGAIN;
-	if (ignore_ack)
-		hcint &= ~DWC2_HCINT_ACK;
-	else
-		hcint_comp_hlt_ack |= DWC2_HCINT_ACK;
-	if (hcint != hcint_comp_hlt_ack) {
-		debug("%s: Error (HCINT=%08x)\n", __func__, hcint);
-		return -EINVAL;
-	}
-
 	hctsiz = readl(&hc_regs->hctsiz);
 	*sub = (hctsiz & DWC2_HCTSIZ_XFERSIZE_MASK) >>
 		DWC2_HCTSIZ_XFERSIZE_OFFSET;
 	*toggle = (hctsiz & DWC2_HCTSIZ_PID_MASK) >> DWC2_HCTSIZ_PID_OFFSET;
 
-	debug("%s: sub=%u toggle=%d\n", __func__, *sub, *toggle);
+	debug("%s: HCINT=%08x sub=%u toggle=%d\n", __func__, hcint, *sub,
+	      *toggle);
 
-	return 0;
+	if (hcint & DWC2_HCINT_XFERCOMP)
+		return 0;
+
+	if (hcint & (DWC2_HCINT_NAK | DWC2_HCINT_FRMOVRUN))
+		return -EAGAIN;
+
+	debug("%s: Error (HCINT=%08x)\n", __func__, hcint);
+	return -EINVAL;
 }
 
 static int dwc2_eptype[] = {
@@ -771,8 +766,7 @@ static int dwc2_eptype[] = {
 };
 
 int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
-	      unsigned long pipe, int *pid, int in, void *buffer, int len,
-	      bool ignore_ack)
+	      unsigned long pipe, int *pid, int in, void *buffer, int len)
 {
 	struct dwc2_core_regs *regs = priv->regs;
 	struct dwc2_hc_regs *hc_regs = &regs->hc_regs[DWC2_HC_CHANNEL];
@@ -841,7 +835,7 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 				(1 << DWC2_HCCHAR_MULTICNT_OFFSET) |
 				DWC2_HCCHAR_CHEN);
 
-		ret = wait_for_chhltd(regs, &sub, pid, ignore_ack);
+		ret = wait_for_chhltd(regs, &sub, pid);
 		if (ret)
 			break;
 
@@ -883,7 +877,7 @@ int _submit_bulk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	}
 
 	return chunk_msg(priv, dev, pipe, &priv->bulk_data_toggle[devnum][ep],
-			 usb_pipein(pipe), buffer, len, true);
+			 usb_pipein(pipe), buffer, len);
 }
 
 static int _submit_control_msg(struct dwc2_priv *priv, struct usb_device *dev,
@@ -903,14 +897,14 @@ static int _submit_control_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	}
 
 	pid = DWC2_HC_PID_SETUP;
-	ret = chunk_msg(priv, dev, pipe, &pid, 0, setup, 8, true);
+	ret = chunk_msg(priv, dev, pipe, &pid, 0, setup, 8);
 	if (ret)
 		return ret;
 
 	if (buffer) {
 		pid = DWC2_HC_PID_DATA1;
 		ret = chunk_msg(priv, dev, pipe, &pid, usb_pipein(pipe), buffer,
-				len, false);
+				len);
 		if (ret)
 			return ret;
 		act_len = dev->act_len;
@@ -926,7 +920,7 @@ static int _submit_control_msg(struct dwc2_priv *priv, struct usb_device *dev,
 
 	pid = DWC2_HC_PID_DATA1;
 	ret = chunk_msg(priv, dev, pipe, &pid, status_direction,
-			priv->status_buffer, 0, false);
+			priv->status_buffer, 0);
 	if (ret)
 		return ret;
 
Signature · 1 line
2.1.4
#3 lore raw

PATCH 3/6 usb: dwc2: split transfer core from outer loop

In reply to Stefan Brüns

Split the movement of data between CPU and Host Controller from the status handling and tracking of transfer progress.
This will also simplify adding of SPLIT transaction support.

---
 drivers/usb/host/dwc2.c | 112 +++++++++++++++++++++++++++---------------------
 1 file changed, 64 insertions(+), 48 deletions(-)
drivers/usb/host/dwc2.c +64 -48
index ad097cb..0e710d9 100644
@@ -426,9 +426,6 @@ static void dwc_otg_hc_init(struct dwc2_core_regs *regs, uint8_t hc_num,
 	if (dev->speed == USB_SPEED_LOW)
 		hcchar |= DWC2_HCCHAR_LSPDDEV;
 
-	/* Clear old interrupt conditions for this host channel. */
-	writel(0x3fff, &hc_regs->hcint);
-
 	/*
 	 * Program the HCCHARn register with the endpoint characteristics
 	 * for the current transfer.
@@ -729,9 +726,8 @@ static int dwc_otg_submit_rh_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	return stat;
 }
 
-int wait_for_chhltd(struct dwc2_core_regs *regs, uint32_t *sub, int *toggle)
+int wait_for_chhltd(struct dwc2_hc_regs *hc_regs, uint32_t *sub, int *toggle)
 {
-	struct dwc2_hc_regs *hc_regs = &regs->hc_regs[DWC2_HC_CHANNEL];
 	int ret;
 	uint32_t hcint, hctsiz;
 
@@ -765,6 +761,58 @@ static int dwc2_eptype[] = {
 	DWC2_HCCHAR_EPTYPE_BULK,
 };
 
+static int transfer_chunk(struct dwc2_hc_regs *hc_regs, void *aligned_buffer,
+			  int *pid, int in, void *buffer, int num_packets,
+			  int xfer_len, int *actual_len)
+{
+	int ret = 0;
+	uint32_t sub;
+
+	debug("%s: chunk: pid %d xfer_len %u pkts %u\n", __func__,
+	      *pid, xfer_len, num_packets);
+
+	writel((xfer_len << DWC2_HCTSIZ_XFERSIZE_OFFSET) |
+	       (num_packets << DWC2_HCTSIZ_PKTCNT_OFFSET) |
+	       (*pid << DWC2_HCTSIZ_PID_OFFSET),
+	       &hc_regs->hctsiz);
+
+	if (!in && xfer_len) {
+		memcpy(aligned_buffer, buffer, xfer_len);
+
+		flush_dcache_range((unsigned long)aligned_buffer,
+				   (unsigned long)aligned_buffer +
+				   roundup(xfer_len, ARCH_DMA_MINALIGN));
+	}
+
+	writel(phys_to_bus((unsigned long)aligned_buffer), &hc_regs->hcdma);
+
+	/* Clear old interrupt conditions for this host channel. */
+	writel(0x3fff, &hc_regs->hcint);
+
+	/* Set host channel enable after all other setup is complete. */
+	clrsetbits_le32(&hc_regs->hcchar, DWC2_HCCHAR_MULTICNT_MASK |
+			DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS,
+			(1 << DWC2_HCCHAR_MULTICNT_OFFSET) |
+			DWC2_HCCHAR_CHEN);
+
+	ret = wait_for_chhltd(hc_regs, &sub, pid);
+	if (ret < 0)
+		return ret;
+
+	if (in) {
+		xfer_len -= sub;
+
+		invalidate_dcache_range((unsigned long)aligned_buffer,
+					(unsigned long)aligned_buffer +
+					roundup(xfer_len, ARCH_DMA_MINALIGN));
+
+		memcpy(buffer, aligned_buffer, xfer_len);
+	}
+	*actual_len = xfer_len;
+
+	return ret;
+}
+
 int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	      unsigned long pipe, int *pid, int in, void *buffer, int len)
 {
@@ -776,7 +824,6 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	int eptype = dwc2_eptype[usb_pipetype(pipe)];
 	int done = 0;
 	int ret = 0;
-	uint32_t sub;
 	uint32_t xfer_len;
 	uint32_t num_packets;
 	int stop_transfer = 0;
@@ -795,11 +842,12 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	num_packets = max_xfer_len / max;
 	max_xfer_len = num_packets * max;
 
-	do {
-		/* Initialize channel */
-		dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, dev, devnum, ep, in,
-				eptype, max);
+	/* Initialize channel */
+	dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, dev, devnum, ep, in,
+			eptype, max);
 
+	do {
+		int actual_len = 0;
 		xfer_len = len - done;
 
 		if (xfer_len > max_xfer_len)
@@ -809,49 +857,17 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 		else
 			num_packets = 1;
 
-		debug("%s: chunk: pid %d xfer_len %u pkts %u\n", __func__,
-		      *pid, xfer_len, num_packets);
+		ret = transfer_chunk(hc_regs, priv->aligned_buffer, pid,
+				     in, (char *)buffer + done, num_packets,
+				     xfer_len, &actual_len);
 
-		writel((xfer_len << DWC2_HCTSIZ_XFERSIZE_OFFSET) |
-		       (num_packets << DWC2_HCTSIZ_PKTCNT_OFFSET) |
-		       (*pid << DWC2_HCTSIZ_PID_OFFSET),
-		       &hc_regs->hctsiz);
-
-		if (!in && xfer_len) {
-			memcpy(priv->aligned_buffer, (char *)buffer + done,
-			       xfer_len);
-
-			flush_dcache_range((unsigned long)priv->aligned_buffer,
-				(unsigned long)((void *)priv->aligned_buffer +
-				roundup(xfer_len, ARCH_DMA_MINALIGN)));
-		}
-
-		writel(phys_to_bus((unsigned long)priv->aligned_buffer),
-		       &hc_regs->hcdma);
-
-		/* Set host channel enable after all other setup is complete. */
-		clrsetbits_le32(&hc_regs->hcchar, DWC2_HCCHAR_MULTICNT_MASK |
-				DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS,
-				(1 << DWC2_HCCHAR_MULTICNT_OFFSET) |
-				DWC2_HCCHAR_CHEN);
-
-		ret = wait_for_chhltd(regs, &sub, pid);
 		if (ret)
 			break;
 
-		if (in) {
-			xfer_len -= sub;
-
-			invalidate_dcache_range((unsigned long)priv->aligned_buffer,
-				(unsigned long)((void *)priv->aligned_buffer +
-				roundup(xfer_len, ARCH_DMA_MINALIGN)));
-
-			memcpy(buffer + done, priv->aligned_buffer, xfer_len);
-			if (sub)
-				stop_transfer = 1;
-		}
+		if (actual_len < xfer_len)
+			stop_transfer = 1;
 
-		done += xfer_len;
+		done += actual_len;
 
 	} while ((done < len) && !stop_transfer);
 
Signature · 1 line
2.1.4
#4 lore raw

PATCH 4/6 usb: dwc2: add helper function for setting SPLIT HC registers

In reply to Stefan Brüns

The split register setting is used for both SSPLIT and CSPLIT transactions, the bit for CSPLIT has to be set seperately.

---
 drivers/usb/host/dwc2.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)
drivers/usb/host/dwc2.c +14 -1
index 0e710d9..7fbbc4b 100644
@@ -432,10 +432,23 @@ static void dwc_otg_hc_init(struct dwc2_core_regs *regs, uint8_t hc_num,
 	 */
 	writel(hcchar, &hc_regs->hcchar);
 
-	/* Program the HCSPLIT register for SPLITs */
+	/* Program the HCSPLIT register, default to no SPLIT */
 	writel(0, &hc_regs->hcsplt);
 }
 
+static void dwc_otg_hc_init_split(struct dwc2_hc_regs *hc_regs,
+				  uint8_t hub_devnum, uint8_t hub_port)
+{
+	uint32_t hcsplt = 0;
+
+	hcsplt = DWC2_HCSPLT_SPLTENA;
+	hcsplt |= hub_devnum << DWC2_HCSPLT_HUBADDR_OFFSET;
+	hcsplt |= hub_port << DWC2_HCSPLT_PRTADDR_OFFSET;
+
+	/* Program the HCSPLIT register for SPLITs */
+	writel(hcsplt, &hc_regs->hcsplt);
+}
+
 /*
  * DWC2 to USB API interface
  */
Signature · 1 line
2.1.4
#5 lore raw

PATCH 5/6 usb: dwc2: Implement SPLIT transaction support

In reply to Stefan Brüns

In contrast to non-SPLIT transfers each transaction has to be submitted as an individual chunk.
The transaction state machine proceeds from SSPLIT to CSPLIT if the ACK flag is set. CSPLIT has to be repeated while NYET is set.

---
 drivers/usb/host/dwc2.c | 81 ++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 66 insertions(+), 15 deletions(-)
drivers/usb/host/dwc2.c +66 -15
index 7fbbc4b..413845c 100644
@@ -837,6 +837,8 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	int eptype = dwc2_eptype[usb_pipetype(pipe)];
 	int done = 0;
 	int ret = 0;
+	int do_split = 0;
+	int complete_split = 0;
 	uint32_t xfer_len;
 	uint32_t num_packets;
 	int stop_transfer = 0;
@@ -859,8 +861,26 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, dev, devnum, ep, in,
 			eptype, max);
 
+	/* Check if the target is a FS/LS device behind a HS hub */
+	if (dev->speed != USB_SPEED_HIGH) {
+		uint8_t hub_addr;
+		uint8_t hub_port;
+		uint32_t hprt0 = readl(&regs->hprt0);
+		if ((hprt0 & DWC2_HPRT0_PRTSPD_MASK) ==
+		     DWC2_HPRT0_PRTSPD_HIGH) {
+			usb_find_usb2_hub_address_port(dev, &hub_addr,
+						       &hub_port);
+			dwc_otg_hc_init_split(hc_regs, hub_addr, hub_port);
+
+			do_split = 1;
+			num_packets = 1;
+			max_xfer_len = max;
+		}
+	}
+
 	do {
 		int actual_len = 0;
+		uint32_t hcint;
 		xfer_len = len - done;
 
 		if (xfer_len > max_xfer_len)
@@ -870,10 +890,29 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 		else
 			num_packets = 1;
 
+		if (complete_split)
+			setbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT);
+		else if (do_split)
+			clrbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT);
+
 		ret = transfer_chunk(hc_regs, priv->aligned_buffer, pid,
 				     in, (char *)buffer + done, num_packets,
 				     xfer_len, &actual_len);
 
+		hcint = readl(&hc_regs->hcint);
+		if (complete_split) {
+			stop_transfer = 0;
+			if (hcint & DWC2_HCINT_NYET)
+				ret = 0;
+			else
+				complete_split = 0;
+		} else if (do_split) {
+			if (hcint & DWC2_HCINT_ACK) {
+				ret = 0;
+				complete_split = 1;
+			}
+		}
+
 		if (ret)
 			break;
 
@@ -882,7 +921,11 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 
 		done += actual_len;
 
-	} while ((done < len) && !stop_transfer);
+	/* Transactions are done when when either all data is transferred or
+	 * there is a short transfer. In case of a SPLIT make sure the CSPLIT
+	 * is executed.
+	 */
+	} while (((done < len) && !stop_transfer) || complete_split);
 
 	writel(0, &hc_regs->hcintmsk);
 	writel(0xFFFFFFFF, &hc_regs->hcint);
@@ -925,31 +968,39 @@ static int _submit_control_msg(struct dwc2_priv *priv, struct usb_device *dev,
 					     setup);
 	}
 
+	/* SETUP stage */
 	pid = DWC2_HC_PID_SETUP;
-	ret = chunk_msg(priv, dev, pipe, &pid, 0, setup, 8);
+	do {
+		ret = chunk_msg(priv, dev, pipe, &pid, 0, setup, 8);
+	} while (ret == -EAGAIN);
 	if (ret)
 		return ret;
 
+	/* DATA stage */
+	act_len = 0;
 	if (buffer) {
 		pid = DWC2_HC_PID_DATA1;
-		ret = chunk_msg(priv, dev, pipe, &pid, usb_pipein(pipe), buffer,
-				len);
+		do {
+			ret = chunk_msg(priv, dev, pipe, &pid, usb_pipein(pipe),
+					buffer, len);
+			act_len += dev->act_len;
+			buffer += dev->act_len;
+			len -= dev->act_len;
+		} while (ret == -EAGAIN);
 		if (ret)
 			return ret;
-		act_len = dev->act_len;
-	} /* End of DATA stage */
-	else
-		act_len = 0;
-
-	/* STATUS stage */
-	if ((len == 0) || usb_pipeout(pipe))
+		status_direction = usb_pipeout(pipe);
+	} else {
+		/* No-data CONTROL always ends with an IN transaction */
 		status_direction = 1;
-	else
-		status_direction = 0;
+	}
 
+	/* STATUS stage */
 	pid = DWC2_HC_PID_DATA1;
-	ret = chunk_msg(priv, dev, pipe, &pid, status_direction,
-			priv->status_buffer, 0);
+	do {
+		ret = chunk_msg(priv, dev, pipe, &pid, status_direction,
+				priv->status_buffer, 0);
+	} while (ret == -EAGAIN);
 	if (ret)
 		return ret;
 
Signature · 1 line
2.1.4
#6 lore raw

PATCH 6/6 usb: dwc2: Add SPLIT INTERRUPT transaction support

In reply to Stefan Brüns

CSPLITs for INTERRUPT transactions have to be scheduled in each microframe following the SSPLIT. INTERRUPT transfers are executed in the next even/ odd microframe depending on the HCCHAR_ODDFRM flag.

As there are no handshakes for INTERRUPT SSPLITs the SSPLIT may have failed (transport error) without the error being detected by the host driver. If the last CSPLIT is not received within 4 microframes after the SSPLIT there was a transaction error and the complete transaction has to be restarted.

---
 drivers/usb/host/dwc2.c | 28 +++++++++++++++++++++++-----
 drivers/usb/host/dwc2.h |  1 +
 2 files changed, 24 insertions(+), 5 deletions(-)
drivers/usb/host/dwc2.c +23 -5
index 413845c..291e4a5 100644
@@ -776,7 +776,7 @@ static int dwc2_eptype[] = {
 
 static int transfer_chunk(struct dwc2_hc_regs *hc_regs, void *aligned_buffer,
 			  int *pid, int in, void *buffer, int num_packets,
-			  int xfer_len, int *actual_len)
+			  int xfer_len, int *actual_len, int odd_frame)
 {
 	int ret = 0;
 	uint32_t sub;
@@ -804,8 +804,10 @@ static int transfer_chunk(struct dwc2_hc_regs *hc_regs, void *aligned_buffer,
 
 	/* Set host channel enable after all other setup is complete. */
 	clrsetbits_le32(&hc_regs->hcchar, DWC2_HCCHAR_MULTICNT_MASK |
-			DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS,
+			DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS |
+			DWC2_HCCHAR_ODDFRM,
 			(1 << DWC2_HCCHAR_MULTICNT_OFFSET) |
+			(odd_frame << DWC2_HCCHAR_ODDFRM_OFFSET) |
 			DWC2_HCCHAR_CHEN);
 
 	ret = wait_for_chhltd(hc_regs, &sub, pid);
@@ -831,6 +833,7 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 {
 	struct dwc2_core_regs *regs = priv->regs;
 	struct dwc2_hc_regs *hc_regs = &regs->hc_regs[DWC2_HC_CHANNEL];
+	struct dwc2_host_regs *host_regs = &regs->host_regs;
 	int devnum = usb_pipedevice(pipe);
 	int ep = usb_pipeendpoint(pipe);
 	int max = usb_maxpacket(dev, pipe);
@@ -843,6 +846,7 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	uint32_t num_packets;
 	int stop_transfer = 0;
 	uint32_t max_xfer_len;
+	int ssplit_frame_num = 0;
 
 	debug("%s: msg: pipe %lx pid %d in %d len %d\n", __func__, pipe, *pid,
 	      in, len);
@@ -881,6 +885,7 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 	do {
 		int actual_len = 0;
 		uint32_t hcint;
+		int odd_frame = 0;
 		xfer_len = len - done;
 
 		if (xfer_len > max_xfer_len)
@@ -895,19 +900,32 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
 		else if (do_split)
 			clrbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT);
 
+		if (eptype == DWC2_HCCHAR_EPTYPE_INTR) {
+			int uframe_num = readl(&host_regs->hfnum);
+			if (!(uframe_num & 0x1))
+				odd_frame = 1;
+		}
+
 		ret = transfer_chunk(hc_regs, priv->aligned_buffer, pid,
 				     in, (char *)buffer + done, num_packets,
-				     xfer_len, &actual_len);
+				     xfer_len, &actual_len, odd_frame);
 
 		hcint = readl(&hc_regs->hcint);
 		if (complete_split) {
 			stop_transfer = 0;
-			if (hcint & DWC2_HCINT_NYET)
+			if (hcint & DWC2_HCINT_NYET) {
 				ret = 0;
-			else
+				int frame_num = DWC2_HFNUM_MAX_FRNUM &
+						readl(&host_regs->hfnum);
+				if (((frame_num - ssplit_frame_num) &
+				    DWC2_HFNUM_MAX_FRNUM) > 4)
+					ret = -EAGAIN;
+			} else
 				complete_split = 0;
 		} else if (do_split) {
 			if (hcint & DWC2_HCINT_ACK) {
+				ssplit_frame_num = DWC2_HFNUM_MAX_FRNUM &
+						   readl(&host_regs->hfnum);
 				ret = 0;
 				complete_split = 1;
 			}
drivers/usb/host/dwc2.h +1 -0
index f69372e..594757b 100644
@@ -500,6 +500,7 @@ struct dwc2_core_regs {
 #define DWC2_HFNUM_FRNUM_OFFSET				0
 #define DWC2_HFNUM_FRREM_MASK				(0xFFFF << 16)
 #define DWC2_HFNUM_FRREM_OFFSET				16
+#define DWC2_HFNUM_MAX_FRNUM				0x3FFF
 #define DWC2_HPTXSTS_PTXFSPCAVAIL_MASK			(0xFFFF << 0)
 #define DWC2_HPTXSTS_PTXFSPCAVAIL_OFFSET		0
 #define DWC2_HPTXSTS_PTXQSPCAVAIL_MASK			(0xFF << 16)
Signature · 1 line
2.1.4
#7 lore raw

cover usb: dwc2: Add support for SPLIT transactions

In reply to Stefan Brüns
Quoted 14 lines · Stefan Br?ns

The first patch fixes an out-of-bounds access, and makes the calculation of maximum transfer size more straightforward. It also makes overriding the maximum transfer size easier for split transactions

2nd and 3rd patch cleanup and restructure the current code in preparation for the split support.

Patch 4 and 5 add the actual support for CONTROL and BULK transactions.

Patch 6 adds support for INTERUPT splits.

The patch series uses the same logic as the earlier patch series, but is otherwise a rewrite.

Quoted 7 lines

With the patch series, all my LS/FS devices enumerate behind the RPi 1 onboard hub and an external hub.

Interrupt transfers have been tested with a Logitech K400 and a noname wired keyboard. Both work if "stdin" includes usbkbd prior to usb start, and u-boot has been configured with CONFIG_USB_KEYBOARD=y and CONFIG_SYS_USB_EVENT_POLL=y (but only one at a time).

The series is fine with me and I was testing previous version on socfpga. I'd like to apply this, unless there is some opposition.

Best regards,
Marek Vasut

#8 lore raw

cover usb: dwc2: Add support for SPLIT transactions

In reply to Stefan Brüns
Quoted 3 lines · Stefan Br?ns

The first patch fixes an out-of-bounds access, and makes the calculation of maximum transfer size more straightforward. It also makes overriding the maximum transfer size easier for split transactions

Sorry I haven't been keeping up on the U-Boot dwc2 patches. Is this series still pending, or was it replaced with something else? It doesn't apply cleanly on top of u-boot/master.

The good news is that with u-boot/master (b72ae192e39f "Merge branch 'master' of git://git.denx.de/u-boot-video") both USB keyboards I have work perfectly on both a model A and a model B, including keyboard repeat. That's great work; thanks very much!

#9 lore raw

cover usb: dwc2: Add support for SPLIT transactions

Quoted 5 lines · Stephen Warren
Quoted 3 lines · Stefan Br?ns

The first patch fixes an out-of-bounds access, and makes the calculation of maximum transfer size more straightforward. It also makes overriding the maximum transfer size easier for split transactions

Quoted 8 lines

Sorry I haven't been keeping up on the U-Boot dwc2 patches. Is this series still pending, or was it replaced with something else? It doesn't apply cleanly on top of u-boot/master.

The good news is that with u-boot/master (b72ae192e39f "Merge branch 'master' of git://git.denx.de/u-boot-video") both USB keyboards I have work perfectly on both a model A and a model B, including keyboard repeat. That's great work; thanks very much!

V2 is applied on top of u-boot/master. Your keyboard patch is also applied:

http://git.denx.de/?p=u-boot/u-boot-usb.git;a=summary

Thank you!

Best regards,
Marek Vasut