From 56a7bbd7412ae8dedca14877bb7aa45199de730c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Sun, 17 Jan 2016 04:09:51 +0100 Subject: usb: dwc2: Fix out-of-bounds access, fix chunk size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- drivers/usb/host/dwc2.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 5ef6debd9a..d317104028 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); -- cgit v1.2.1 From 03460cdc3d73e06596e5925fa4a19d7e1da68b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Sun, 17 Jan 2016 04:09:52 +0100 Subject: usb: dwc2: Simplify wait_for_chhltd(), remove ignore_ack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- drivers/usb/host/dwc2.c | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index d317104028..ad097cbda4 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; -- cgit v1.2.1 From daed305941eb7ff30039af8142c2392bd5c3ad91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Sun, 17 Jan 2016 04:09:53 +0100 Subject: usb: dwc2: split transfer core from outer loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- drivers/usb/host/dwc2.c | 112 +++++++++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 48 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index ad097cbda4..0e710d9d8a 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); -- cgit v1.2.1 From 890f0ee4bb454712c302bd9d235091cd3fe70931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Sun, 17 Jan 2016 04:09:54 +0100 Subject: usb: dwc2: add helper function for setting SPLIT HC registers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- drivers/usb/host/dwc2.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 0e710d9d8a..7fbbc4bc7f 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 */ -- cgit v1.2.1 From b54e44705258c0443310ed12b8982707d7992fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Sun, 17 Jan 2016 04:09:55 +0100 Subject: usb: dwc2: Implement SPLIT transaction support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- drivers/usb/host/dwc2.c | 81 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 15 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 7fbbc4bc7f..413845cdc2 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; -- cgit v1.2.1 From d2ff51b372a6800fcf1394fc2ca9821d86b15335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Sun, 17 Jan 2016 04:09:56 +0100 Subject: usb: dwc2: Add SPLIT INTERRUPT transaction support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- drivers/usb/host/dwc2.c | 28 +++++++++++++++++++++++----- drivers/usb/host/dwc2.h | 1 + 2 files changed, 24 insertions(+), 5 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 413845cdc2..291e4a5f57 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 f69372e6b4..594757b609 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) -- cgit v1.2.1 From 25612f23b5ba1f7d0fb96dbf576b98dc122da24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Sat, 23 Jan 2016 01:42:25 +0100 Subject: usb: dwc2: Do not mix data toggle for IN and OUT endpoints, check bounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USB protocol allows for 16 IN and 16 OUT endpoints (USB 2.0 Spec, 8.3.2.2 Endpoint Field). A function may have an EP 1 for both IN and OUT, so these two should be kept separate. As EPs are either BULK or INTERRUPT (or ISO), it is fine to have one array per direction for all transfer types (also see e236519b7365ef75c5da6a5623f0b03d9c00cfae). USB device address is 7 bits, so a bus may have more than 16 devices. Check the device number, as the DWC2 driver only supports BULK/ISO for the first 16 devices. Signed-off-by: Stefan Brüns --- drivers/usb/host/dwc2.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index 291e4a5f57..b272c57112 100644 --- a/drivers/usb/host/dwc2.c +++ b/drivers/usb/host/dwc2.c @@ -34,7 +34,8 @@ struct dwc2_priv { uint8_t *aligned_buffer; uint8_t *status_buffer; #endif - int bulk_data_toggle[MAX_DEVICE][MAX_ENDPOINT]; + u8 in_data_toggle[MAX_DEVICE][MAX_ENDPOINT]; + u8 out_data_toggle[MAX_DEVICE][MAX_ENDPOINT]; struct dwc2_core_regs *regs; int root_hub_devnum; }; @@ -739,7 +740,7 @@ static int dwc_otg_submit_rh_msg(struct dwc2_priv *priv, struct usb_device *dev, return stat; } -int wait_for_chhltd(struct dwc2_hc_regs *hc_regs, uint32_t *sub, int *toggle) +int wait_for_chhltd(struct dwc2_hc_regs *hc_regs, uint32_t *sub, u8 *toggle) { int ret; uint32_t hcint, hctsiz; @@ -775,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, + u8 *pid, int in, void *buffer, int num_packets, int xfer_len, int *actual_len, int odd_frame) { int ret = 0; @@ -829,7 +830,7 @@ static int transfer_chunk(struct dwc2_hc_regs *hc_regs, void *aligned_buffer, } int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev, - unsigned long pipe, int *pid, int in, void *buffer, int len) + unsigned long pipe, u8 *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]; @@ -960,14 +961,19 @@ int _submit_bulk_msg(struct dwc2_priv *priv, struct usb_device *dev, { int devnum = usb_pipedevice(pipe); int ep = usb_pipeendpoint(pipe); + u8* pid; - if (devnum == priv->root_hub_devnum) { + if ((devnum >= MAX_DEVICE) || (devnum == priv->root_hub_devnum)) { dev->status = 0; return -EINVAL; } - return chunk_msg(priv, dev, pipe, &priv->bulk_data_toggle[devnum][ep], - usb_pipein(pipe), buffer, len); + if (usb_pipein(pipe)) + pid = &priv->in_data_toggle[devnum][ep]; + else + pid = &priv->out_data_toggle[devnum][ep]; + + return chunk_msg(priv, dev, pipe, pid, usb_pipein(pipe), buffer, len); } static int _submit_control_msg(struct dwc2_priv *priv, struct usb_device *dev, @@ -975,7 +981,8 @@ static int _submit_control_msg(struct dwc2_priv *priv, struct usb_device *dev, struct devrequest *setup) { int devnum = usb_pipedevice(pipe); - int pid, ret, act_len; + int ret, act_len; + u8 pid; /* For CONTROL endpoint pid should start with DATA1 */ int status_direction; @@ -1075,8 +1082,10 @@ static int dwc2_init_common(struct dwc2_priv *priv) DWC2_HPRT0_PRTRST); for (i = 0; i < MAX_DEVICE; i++) { - for (j = 0; j < MAX_ENDPOINT; j++) - priv->bulk_data_toggle[i][j] = DWC2_HC_PID_DATA0; + for (j = 0; j < MAX_ENDPOINT; j++) { + priv->in_data_toggle[i][j] = DWC2_HC_PID_DATA0; + priv->out_data_toggle[i][j] = DWC2_HC_PID_DATA0; + } } return 0; -- cgit v1.2.1 From 4feefdcfe916113ac6e1837ea22857f25fe1771c Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 25 Jan 2016 15:00:36 +0900 Subject: usb: add clock support for generic EHCI This driver is designed in a generic manner, so clocks should be handled genericly as well. Signed-off-by: Masahiro Yamada --- drivers/usb/host/ehci-generic.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-generic.c b/drivers/usb/host/ehci-generic.c index 1292caae4d..84114c5df9 100644 --- a/drivers/usb/host/ehci-generic.c +++ b/drivers/usb/host/ehci-generic.c @@ -5,6 +5,7 @@ */ #include +#include #include #include "ehci.h" @@ -21,6 +22,19 @@ static int ehci_usb_probe(struct udevice *dev) { struct ehci_hccr *hccr = (struct ehci_hccr *)dev_get_addr(dev); struct ehci_hcor *hcor; + int i; + + for (i = 0; ; i++) { + struct udevice *clk_dev; + int clk_id; + + clk_id = clk_get_by_index(dev, i, &clk_dev); + if (clk_id < 0) + break; + if (clk_enable(clk_dev, clk_id)) + printf("failed to enable clock (dev=%s, id=%d)\n", + clk_dev->name, clk_id); + } hcor = (struct ehci_hcor *)((uintptr_t)hccr + HC_LENGTH(ehci_readl(&hccr->cr_capbase))); -- cgit v1.2.1