Skip to content

Commit 1e6c549

Browse files
committed
[nrf fromtree] drivers: udc_dwc2: Fix incomplete iso handling race
Incomplete iso IN/OUT is just informative and its occurrence does not prevent the endpoint from actually transmitting/receiving data. Such "late" isochronous transfers, which are perfectly fine according to USB specification, were observed on Windows host with nRF54H20 running explicit feedback sample operating at High-Speed. The incorrect handling manifested itself with "ISO RX buffer too small" error message. The faulty scenario was: * incompISOIN handler does not find any matching endpoint * incompISOOUT handler disables endpoint, discards buffer and sets rearm flag * next DWC2 interrupt handler iteration after reading GINTSTS * XferCompl interrupt on iso IN endpoint * XferCompl interrupt on iso OUT endpoint - transfer was actually happening to the buffer discarded in incompISOOUT handler - XferCompl handler modified the next buffer * GOUTNakEff interrupt, iso OUT endpoint EPDIS bit is set * EPDisbld interrupt, rearm flag set - the buffer modified by XferCompl is used and fails because it is not large enough Modify the sequence so it accounts for host actions and the above faulty scenario no longer causes any problems. Signed-off-by: Tomasz Moń <[email protected]> (cherry picked from commit 8d1f7b3bef5fcbb63b8dab99f85320f02835fe63)
1 parent 163e855 commit 1e6c549

File tree

1 file changed

+74
-24
lines changed

1 file changed

+74
-24
lines changed

drivers/usb/udc/udc_dwc2.c

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ struct udc_dwc2_data {
141141
unsigned int enumdone : 1;
142142
unsigned int enumspd : 2;
143143
unsigned int pending_dout_feed : 1;
144+
unsigned int ignore_ep0_nakeff : 1;
144145
enum dwc2_suspend_type suspend_type;
145146
/* Number of endpoints including control endpoint */
146147
uint8_t numdeveps;
@@ -485,7 +486,6 @@ static int dwc2_tx_fifo_write(const struct device *dev,
485486
mem_addr_t dieptsiz_reg = (mem_addr_t)&base->in_ep[ep_idx].dieptsiz;
486487
/* TODO: use dwc2_get_dxepctl_reg() */
487488
mem_addr_t diepctl_reg = (mem_addr_t)&base->in_ep[ep_idx].diepctl;
488-
mem_addr_t diepint_reg = (mem_addr_t)&base->in_ep[ep_idx].diepint;
489489

490490
uint32_t diepctl;
491491
uint32_t max_xfersize, max_pktcnt, pktcnt;
@@ -617,9 +617,6 @@ static int dwc2_tx_fifo_write(const struct device *dev,
617617
}
618618
sys_write32(diepctl, diepctl_reg);
619619

620-
/* Clear IN Endpoint NAK Effective interrupt in case it was set */
621-
sys_write32(USB_DWC2_DIEPINT_INEPNAKEFF, diepint_reg);
622-
623620
if (dwc2_in_completer_mode(dev)) {
624621
const uint8_t *src = buf->data;
625622

@@ -811,7 +808,20 @@ static void dwc2_handle_xfer_next(const struct device *dev,
811808
if (USB_EP_DIR_IS_OUT(cfg->addr)) {
812809
dwc2_prep_rx(dev, buf, cfg);
813810
} else {
814-
int err = dwc2_tx_fifo_write(dev, cfg, buf);
811+
int err;
812+
813+
if (cfg->addr == USB_CONTROL_EP_IN &&
814+
udc_ctrl_stage_is_status_in(dev)) {
815+
/* It was observed that EPENA results in INEPNAKEFF
816+
* interrupt which leads to endpoint disable. It is not
817+
* clear how to prevent this without violating sequence
818+
* described in Programming Guide, so just set a flag
819+
* for interrupt handler to ignore it.
820+
*/
821+
priv->ignore_ep0_nakeff = 1;
822+
}
823+
824+
err = dwc2_tx_fifo_write(dev, cfg, buf);
815825

816826
if (cfg->addr == USB_CONTROL_EP_IN) {
817827
/* Feed a buffer for the next setup packet after arming
@@ -862,6 +872,7 @@ static int dwc2_handle_evt_setup(const struct device *dev)
862872
* transfer beforehand. In Buffer DMA the SETUP can be copied to any EP0
863873
* OUT buffer. If there is any buffer queued, it is obsolete now.
864874
*/
875+
priv->ignore_ep0_nakeff = 0;
865876
udc_dwc2_ep_disable(dev, cfg_in, false, true);
866877
atomic_and(&priv->xfer_finished, ~(BIT(0) | BIT(16)));
867878

@@ -2658,7 +2669,21 @@ static inline void dwc2_handle_iepint(const struct device *dev)
26582669
}
26592670

26602671
if (status & USB_DWC2_DIEPINT_INEPNAKEFF) {
2661-
sys_set_bits(diepctl_reg, USB_DWC2_DEPCTL_EPDIS);
2672+
uint32_t diepctl = sys_read32(diepctl_reg);
2673+
2674+
if (!(diepctl & USB_DWC2_DEPCTL_NAKSTS)) {
2675+
/* Ignore stale NAK effective interrupt */
2676+
} else if (n == 0 && priv->ignore_ep0_nakeff) {
2677+
/* Status stage enabled endpoint. NAK will be
2678+
* cleared in STSPHSERCVD interrupt.
2679+
*/
2680+
} else if (diepctl & USB_DWC2_DEPCTL_EPENA) {
2681+
diepctl &= ~USB_DWC2_DEPCTL_EPENA;
2682+
diepctl |= USB_DWC2_DEPCTL_EPDIS;
2683+
sys_write32(diepctl, diepctl_reg);
2684+
} else if (priv->iso_in_rearm & (BIT(n))) {
2685+
priv->iso_in_rearm &= ~BIT(n);
2686+
}
26622687
}
26632688

26642689
if (status & USB_DWC2_DIEPINT_EPDISBLD) {
@@ -2676,6 +2701,13 @@ static inline void dwc2_handle_iepint(const struct device *dev)
26762701
if ((usb_dwc2_get_depctl_eptype(diepctl) == USB_DWC2_DEPCTL_EPTYPE_ISO) &&
26772702
(priv->iso_in_rearm & BIT(n))) {
26782703
struct udc_ep_config *cfg = udc_get_ep_cfg(dev, n | USB_EP_DIR_IN);
2704+
struct net_buf *buf;
2705+
2706+
/* Data is no longer relevant, discard it */
2707+
buf = udc_buf_get(cfg);
2708+
if (buf) {
2709+
udc_submit_ep_event(dev, buf, 0);
2710+
}
26792711

26802712
/* Try to queue next packet before SOF */
26812713
dwc2_handle_xfer_next(dev, cfg);
@@ -2837,9 +2869,17 @@ static inline void dwc2_handle_oepint(const struct device *dev)
28372869
if ((usb_dwc2_get_depctl_eptype(doepctl) == USB_DWC2_DEPCTL_EPTYPE_ISO) &&
28382870
(priv->iso_out_rearm & BIT(n))) {
28392871
struct udc_ep_config *cfg;
2872+
struct net_buf *buf;
28402873

2841-
/* Try to queue next packet before SOF */
28422874
cfg = udc_get_ep_cfg(dev, n | USB_EP_DIR_OUT);
2875+
2876+
/* Discard disabled transfer buffer */
2877+
buf = udc_buf_get(cfg);
2878+
if (buf) {
2879+
udc_submit_ep_event(dev, buf, 0);
2880+
}
2881+
2882+
/* Try to queue next packet before SOF */
28432883
dwc2_handle_xfer_next(dev, cfg);
28442884

28452885
priv->iso_out_rearm &= ~BIT(n);
@@ -2883,21 +2923,14 @@ static void dwc2_handle_incompisoin(const struct device *dev)
28832923
/* Check if endpoint didn't receive ISO IN data */
28842924
if ((diepctl & mask) == val) {
28852925
struct udc_ep_config *cfg;
2886-
struct net_buf *buf;
28872926

28882927
cfg = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN);
28892928
__ASSERT_NO_MSG(cfg && cfg->stat.enabled &&
28902929
dwc2_ep_is_iso(cfg));
28912930

28922931
udc_dwc2_ep_disable(dev, cfg, false, false);
28932932

2894-
buf = udc_buf_get(cfg);
2895-
if (buf) {
2896-
/* Data is no longer relevant */
2897-
udc_submit_ep_event(dev, buf, 0);
2898-
2899-
rearm |= BIT(i);
2900-
}
2933+
rearm |= BIT(i);
29012934
}
29022935

29032936
eps &= ~BIT(i);
@@ -2940,20 +2973,14 @@ static void dwc2_handle_incompisoout(const struct device *dev)
29402973
/* Check if endpoint didn't receive ISO OUT data */
29412974
if ((doepctl & mask) == val) {
29422975
struct udc_ep_config *cfg;
2943-
struct net_buf *buf;
29442976

29452977
cfg = udc_get_ep_cfg(dev, i);
29462978
__ASSERT_NO_MSG(cfg && cfg->stat.enabled &&
29472979
dwc2_ep_is_iso(cfg));
29482980

29492981
udc_dwc2_ep_disable(dev, cfg, false, false);
29502982

2951-
buf = udc_buf_get(cfg);
2952-
if (buf) {
2953-
udc_submit_ep_event(dev, buf, 0);
2954-
2955-
rearm |= BIT(i);
2956-
}
2983+
rearm |= BIT(i);
29572984
}
29582985

29592986
eps &= ~BIT(i);
@@ -2984,8 +3011,31 @@ static void dwc2_handle_goutnakeff(const struct device *dev)
29843011
doepctl_reg = (mem_addr_t)&base->out_ep[ep_idx].doepctl;
29853012
doepctl = sys_read32(doepctl_reg);
29863013

2987-
/* The application cannot disable control OUT endpoint 0. */
2988-
if (ep_idx != 0) {
3014+
if (!(doepctl & USB_DWC2_DEPCTL_EPENA)) {
3015+
/* While endpoint was enabled when application requested
3016+
* to disable it, there is a race window between setting
3017+
* DCTL SGOUTNAK bit and it becoming effective. During
3018+
* the window, it is possible for host to actually send
3019+
* OUT DATA packet ending the transfer which disables
3020+
* the endpoint.
3021+
*
3022+
* If we arrived here due to INCOMPISOOUT, clearing
3023+
* the rearm flag is enough.
3024+
*/
3025+
if (priv->iso_out_rearm & BIT(ep_idx)) {
3026+
priv->iso_out_rearm &= ~BIT(ep_idx);
3027+
}
3028+
3029+
/* If application requested STALL then set it here, but
3030+
* otherwise there's nothing to do.
3031+
*/
3032+
if (!(priv->ep_out_stall & BIT(ep_idx))) {
3033+
continue;
3034+
}
3035+
} else if (ep_idx != 0) {
3036+
/* Only set EPDIS if EPENA is set, but do not set it for
3037+
* endpoint 0 which cannot be disabled by application.
3038+
*/
29893039
doepctl |= USB_DWC2_DEPCTL_EPDIS;
29903040
}
29913041

0 commit comments

Comments
 (0)