[U-Boot] [PATCH 0/6] 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(-)

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.
Signed-off-by: Stefan Brüns stefan.bruens@rwth-aachen.de --- drivers/usb/host/dwc2.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 5ef6deb..d317104 100644 --- a/drivers/usb/host/dwc2.c +++ b/drivers/usb/host/dwc2.c @@ -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);

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)
Signed-off-by: Stefan Brüns stefan.bruens@rwth-aachen.de --- drivers/usb/host/dwc2.c | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-)
diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index d317104..ad097cb 100644 --- a/drivers/usb/host/dwc2.c +++ b/drivers/usb/host/dwc2.c @@ -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 = ®s->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 = ®s->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;

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.
Signed-off-by: Stefan Brüns stefan.bruens@rwth-aachen.de --- drivers/usb/host/dwc2.c | 112 +++++++++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 48 deletions(-)
diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index ad097cb..0e710d9 100644 --- a/drivers/usb/host/dwc2.c +++ b/drivers/usb/host/dwc2.c @@ -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 = ®s->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);

The split register setting is used for both SSPLIT and CSPLIT transactions, the bit for CSPLIT has to be set seperately.
Signed-off-by: Stefan Brüns stefan.bruens@rwth-aachen.de --- drivers/usb/host/dwc2.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 0e710d9..7fbbc4b 100644 --- a/drivers/usb/host/dwc2.c +++ b/drivers/usb/host/dwc2.c @@ -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 */

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.
Signed-off-by: Stefan Brüns stefan.bruens@rwth-aachen.de --- drivers/usb/host/dwc2.c | 81 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 15 deletions(-)
diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 7fbbc4b..413845c 100644 --- a/drivers/usb/host/dwc2.c +++ b/drivers/usb/host/dwc2.c @@ -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(®s->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;

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.
Signed-off-by: Stefan Brüns stefan.bruens@rwth-aachen.de --- drivers/usb/host/dwc2.c | 28 +++++++++++++++++++++++----- drivers/usb/host/dwc2.h | 1 + 2 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 413845c..291e4a5 100644 --- a/drivers/usb/host/dwc2.c +++ b/drivers/usb/host/dwc2.c @@ -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 = ®s->hc_regs[DWC2_HC_CHANNEL]; + struct dwc2_host_regs *host_regs = ®s->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; } diff --git a/drivers/usb/host/dwc2.h b/drivers/usb/host/dwc2.h index f69372e..594757b 100644 --- a/drivers/usb/host/dwc2.h +++ b/drivers/usb/host/dwc2.h @@ -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)

On Sunday, January 17, 2016 at 04:09:50 AM, Stefan Brüns wrote:
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).
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

On 01/16/2016 08:09 PM, Stefan Brüns wrote:
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!

On Wednesday, January 27, 2016 at 04:52:03 AM, Stephen Warren wrote:
On 01/16/2016 08:09 PM, Stefan Brüns wrote:
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!
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
participants (3)
-
Marek Vasut
-
Stefan Brüns
-
Stephen Warren