Skip to content

fix ISO OUT endpoint failure on alt-setting MPS change#3575

Merged
HiFiPhile merged 7 commits into
hathach:masterfrom
gab-k:dwc2-iso-out-reset
Mar 30, 2026
Merged

fix ISO OUT endpoint failure on alt-setting MPS change#3575
HiFiPhile merged 7 commits into
hathach:masterfrom
gab-k:dwc2-iso-out-reset

Conversation

@gab-k
Copy link
Copy Markdown
Contributor

@gab-k gab-k commented Mar 28, 2026

Summary

When an ISO OUT endpoint is reused across alternate settings with a different wMaxPacketSize (e.g. UAC2 speaker switching sample rates), the DWC2 dcd_edpt_iso_activate() path leaves stale hardware state behind. After the alt-setting switch, the first packets fault and no data reaches the application.

The existing code called edpt_disable() before re-activation, but its full GONAK/handshake sequence does not clear residual doeptsiz/doepdma values from the previous configuration on all DWC2 variants.

Fix

Add edpt_iso_out_reset() a register reset function that clears endpoint HW state and transfer bookkeeping before reactivation. Only triggered when all three conditions are met:

  • Direction is OUT
  • Endpoint was previously active
  • New MPS differs from current MPS

All other paths (IN endpoints, same MPS, first activation) should be unchanged.

I kept it separate from edpt_disable() intentionally as its a pre-activation state reset, not a full endpoint teardown.

Context

Discovered while bringing up the nRF54LM20 DK (#3574) with the uac2_headset example. Also tested on a NUCLEO-H7S3L8, havent seen any regression issues, alt-setting switches work correctly on both boards. Testing was not extensive though, just basic functional verification. I dont have access to other DWC2 platforms (ESP32-S2/S3, etc.).

Also ran the ceedling unit tests, all passed.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 28, 2026

TinyUSB Average Code Size Metrics

File .text .rodata .data .bss size %
cdc_host.c 6617 487 15 1498 8327 5.0%
ehci.c 2763 0 0 6043 7597 4.6%
ncm_device.c 1538 28 718 5843 7395 4.4%
usbh.c 4652 55 99 961 5734 3.4%
hcd_dwc2.c 4994 33 1 513 5540 3.3%
midi_host.c 1341 7 7 3635 4979 3.0%
video_device.c 4443 5 1235 479 4914 2.9%
audio_device.c 2897 0 1260 1627 4518 2.7%
dcd_dwc2.c 4210 25 0 265 4500 2.7%
ohci.c 1940 0 0 2414 4353 2.6%
dcd_ch32_usbfs.c 1473 0 0 2444 3917 2.3%
ecm_rndis_device.c 1037 0 1 2858 3896 2.3%
hcd_stm32_fsdev.c 3287 0 1 420 3708 2.2%
usbd.c 3224 57 88 275 3564 2.1%
dcd_ft9xx.c 3276 0 0 172 3448 2.1%
dcd_khci.c 1953 0 0 1290 3243 1.9%
hcd_musb.c 3073 0 0 157 3230 1.9%
dcd_ci_fs.c 1925 0 0 1290 3215 1.9%
dcd_da146xx.c 3067 0 0 144 3211 1.9%
dcd_nrf5x.c 2918 0 0 292 3210 1.9%
hcd_rusb2.c 2923 0 0 245 3168 1.9%
dcd_rusb2.c 2919 0 0 156 3075 1.8%
msc_device.c 2525 108 2286 547 3071 1.8%
hcd_ch32_usbfs.c 2484 0 0 498 2982 1.8%
hcd_khci.c 2442 0 0 449 2891 1.7%
dcd_stm32_fsdev.c 2558 0 0 291 2849 1.7%
dcd_mm32f327x_otg.c 1478 0 0 1290 2768 1.7%
dcd_musb.c 2445 0 0 160 2605 1.6%
usbtmc_device.c 2196 24 68 316 2544 1.5%
hcd_samd.c 2220 0 0 324 2544 1.5%
dcd_ci_hs.c 1759 0 0 1344 2538 1.5%
dcd_eptri.c 2271 0 0 259 2530 1.5%
hid_host.c 1240 0 0 1251 2491 1.5%
mtp_device.c 1696 22 735 588 2292 1.4%
dcd_rp2040.c 836 20 604 655 2115 1.3%
msc_host.c 1587 0 0 394 1982 1.2%
dcd_msp430x5xx.c 1798 0 0 176 1974 1.2%
cdc_device.c 1252 16 1106 684 1935 1.2%
dcd_ch32_usbhs.c 1469 0 0 448 1917 1.1%
hcd_rp2040.c 976 73 416 384 1849 1.1%
dcd_lpc17_40.c 1474 0 0 648 1798 1.1%
midi_device.c 1151 0 1007 623 1772 1.1%
dcd_nuc505.c 0 0 1531 157 1688 1.0%
dcd_lpc_ip3511.c 1463 0 0 264 1683 1.0%
hub.c 1384 8 8 30 1418 0.8%
printer_device.c 830 0 706 566 1394 0.8%
dcd_samg.c 1320 0 0 72 1392 0.8%
dcd_samd.c 1034 0 0 266 1300 0.8%
dcd_nuc121.c 1168 0 0 101 1269 0.8%
hid_device.c 1125 44 997 119 1244 0.7%
vendor_device.c 641 0 534 565 1204 0.7%
dcd_nuc120.c 1094 0 0 78 1172 0.7%
dfu_device.c 777 28 712 140 916 0.5%
rp2040_usb.c 120 75 669 4 868 0.5%
typec_stm32.c 820 8 2 12 842 0.5%
tusb_fifo.c 841 0 480 0 836 0.5%
dwc2_common.c 602 30 0 0 618 0.4%
usbd_control.c 538 0 484 79 616 0.4%
usbc.c 420 2 20 166 608 0.4%
hcd_pio_usb.c 262 0 240 0 502 0.3%
tusb.c 451 0 383 3 453 0.3%
hcd_ci_hs.c 184 0 0 0 184 0.1%
fsdev_common.c 180 0 0 0 180 0.1%
rusb2_common.c 160 0 16 0 176 0.1%
dfu_rt_device.c 157 0 134 0 157 0.1%
TOTAL 117898 1155 16563 46972 166909 100.0%
Input files
  • cmake-build/cmake-build-adafruit_clue/metrics.json
  • cmake-build/cmake-build-apard32690/metrics.json
  • cmake-build/cmake-build-at32f403a_weact_blackpill/metrics.json
  • cmake-build/cmake-build-at_start_f402/metrics.json
  • cmake-build/cmake-build-at_start_f413/metrics.json
  • cmake-build/cmake-build-at_start_f415/metrics.json
  • cmake-build/cmake-build-at_start_f423/metrics.json
  • cmake-build/cmake-build-at_start_f425/metrics.json
  • cmake-build/cmake-build-at_start_f435/metrics.json
  • cmake-build/cmake-build-at_start_f455/metrics.json
  • cmake-build/cmake-build-b_g474e_dpow1/metrics.json
  • cmake-build/cmake-build-b_u585i_iot2a/metrics.json
  • cmake-build/cmake-build-ch32f205r-r0/metrics.json
  • cmake-build/cmake-build-ch32v103r_r1_1v0/metrics.json
  • cmake-build/cmake-build-ch32v203c_r0_1v0/metrics.json
  • cmake-build/cmake-build-ch32v307v_r1_1v0/metrics.json
  • cmake-build/cmake-build-cynthion_d11/metrics.json
  • cmake-build/cmake-build-da14695_dk_usb/metrics.json
  • cmake-build/cmake-build-double_m33_express/metrics.json
  • cmake-build/cmake-build-ea4088_quickstart/metrics.json
  • cmake-build/cmake-build-ea4357/metrics.json
  • cmake-build/cmake-build-ek_tm4c123gxl/metrics.json
  • cmake-build/cmake-build-feather_stm32f405/metrics.json
  • cmake-build/cmake-build-fomu/metrics.json
  • cmake-build/cmake-build-frdm_k32l2a4s/metrics.json
  • cmake-build/cmake-build-frdm_k64f/metrics.json
  • cmake-build/cmake-build-frdm_kl25z/metrics.json
  • cmake-build/cmake-build-frdm_mcxa153/metrics.json
  • cmake-build/cmake-build-frdm_rw612/metrics.json
  • cmake-build/cmake-build-hpm6750evk2/metrics.json
  • cmake-build/cmake-build-lpcxpresso11u37/metrics.json
  • cmake-build/cmake-build-lpcxpresso1347/metrics.json
  • cmake-build/cmake-build-lpcxpresso1549/metrics.json
  • cmake-build/cmake-build-lpcxpresso1769/metrics.json
  • cmake-build/cmake-build-lpcxpresso18s37/metrics.json
  • cmake-build/cmake-build-lpcxpresso51u68/metrics.json
  • cmake-build/cmake-build-lpcxpresso54114/metrics.json
  • cmake-build/cmake-build-metro_m0_express/metrics.json
  • cmake-build/cmake-build-metro_m4_express/metrics.json
  • cmake-build/cmake-build-metro_m7_1011/metrics.json
  • cmake-build/cmake-build-mm32f327x_mb39/metrics.json
  • cmake-build/cmake-build-mm900evxb/metrics.json
  • cmake-build/cmake-build-msp_exp430f5529lp/metrics.json
  • cmake-build/cmake-build-msp_exp432e401y/metrics.json
  • cmake-build/cmake-build-nutiny_nuc126v/metrics.json
  • cmake-build/cmake-build-nutiny_sdk_nuc120/metrics.json
  • cmake-build/cmake-build-nutiny_sdk_nuc121/metrics.json
  • cmake-build/cmake-build-nutiny_sdk_nuc505/metrics.json
  • cmake-build/cmake-build-portenta_c33/metrics.json
  • cmake-build/cmake-build-raspberry_pi_pico/metrics.json
  • cmake-build/cmake-build-raspberrypi_cm4/metrics.json
  • cmake-build/cmake-build-raspberrypi_zero/metrics.json
  • cmake-build/cmake-build-samg55_xplained/metrics.json
  • cmake-build/cmake-build-sipeed_longan_nano/metrics.json
  • cmake-build/cmake-build-stlinkv3mini/metrics.json
  • cmake-build/cmake-build-stm32c071nucleo/metrics.json
  • cmake-build/cmake-build-stm32f070rbnucleo/metrics.json
  • cmake-build/cmake-build-stm32f103_bluepill/metrics.json
  • cmake-build/cmake-build-stm32f207nucleo/metrics.json
  • cmake-build/cmake-build-stm32f303disco/metrics.json
  • cmake-build/cmake-build-stm32g0b1nucleo/metrics.json
  • cmake-build/cmake-build-stm32h503nucleo/metrics.json
  • cmake-build/cmake-build-stm32h743eval/metrics.json
  • cmake-build/cmake-build-stm32h7s3nucleo/metrics.json
  • cmake-build/cmake-build-stm32l052dap52/metrics.json
  • cmake-build/cmake-build-stm32l412nucleo/metrics.json
  • cmake-build/cmake-build-stm32n6570dk/metrics.json
  • cmake-build/cmake-build-stm32u083cdk/metrics.json
  • cmake-build/cmake-build-stm32wb55nucleo/metrics.json
  • cmake-build/cmake-build-stm32wba_nucleo/metrics.json
  • cmake-build/cmake-build-xmc4500_relax/metrics.json

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 28, 2026

MemBrowse Memory Report

MemBrowse Memory Report

Top 10 targets by memory change (%) (out of 2047 targets) View Project Dashboard →

target .text .rodata .data .bss total % diff
xmc4500_relax/dfu_runtime 12,244 → 12,308 (+64) 12,252 → 12,316 (+64) +0.5%
xmc4500_relax/hid_generic_inout 13,160 → 13,224 (+64) 13,168 → 13,232 (+64) +0.5%
sipeed_longan_nano/dfu_runtime 12,820 → 12,884 (+64) 14,034 → 14,098 (+64) +0.5%
xmc4500_relax/hid_composite 14,124 → 14,188 (+64) 14,132 → 14,196 (+64) +0.5%
xmc4500_relax/msc_dual_lun 15,112 → 15,176 (+64) 15,120 → 15,184 (+64) +0.4%
sipeed_longan_nano/hid_generic_inout 14,104 → 14,168 (+64) 15,186 → 15,250 (+64) +0.4%
xmc4500_relax/printer_to_cdc 15,228 → 15,292 (+64) 15,236 → 15,300 (+64) +0.4%
xmc4500_relax/audio_test 15,320 → 15,384 (+64) 15,328 → 15,392 (+64) +0.4%
xmc4500_relax/webusb_serial 15,588 → 15,652 (+64) 15,596 → 15,660 (+64) +0.4%
xmc4500_relax/usbtmc 16,000 → 16,064 (+64) 16,008 → 16,072 (+64) +0.4%

Comment thread src/portable/synopsys/dwc2/dcd_dwc2.c Outdated
// Full register write (not |=) to clear all bits including active/enabled state
epout->doepctl = DOEPCTL_SNAK;
epout->doepint = 0xFFFFFFFFu;
epout->doeptsiz = 0;
Copy link
Copy Markdown
Collaborator

@HiFiPhile HiFiPhile Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the fix but what I think the only thing needed is adding SNAK to edpt_disable:

Image

doeptsiz, doepdma and xfer state will be set on next transfer.

Copy link
Copy Markdown
Collaborator

@HiFiPhile HiFiPhile Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also could you dump the DWC2 config with dwc2_info.py ? It'a new issue specific to nRF54LM20.

@gab-k
Copy link
Copy Markdown
Contributor Author

gab-k commented Mar 28, 2026

I investigated the LM20 DWC2 config and also reduced the fix further.

For the DWC2 config dump, I read the registers on hardware and added a dedicated nRF54LM20 column to dwc2_info.py / dwc2_info.md.

These differ from the existing generic nRF54 entry, so LM20 seems to need its own column.

I tested the DOEPCTL.SNAK addition in edpt_disable() by itself on hardware, but that was not sufficient for this case.

All the other writes I had in the earlier version (DOEPINT, DOEPTSIZ, DOEPDMA, transfer bookkeeping, etc.) turned out not to be needed.

The extra helper function is not strictly required anymore at this point; the same logic could be inlined. I kept it for now because I think it makes the special-case intent a bit clearer, but that part is mostly a matter of preference and I can inline it if you prefer.

Edit: Actually i just noticed that i mixed up the this PR and #3574 a little bit maybe should have modified dwc2_info in that PR, hope that isnt too much of an issue.

@HiFiPhile
Copy link
Copy Markdown
Collaborator

Thank you, it looks like something is changed in GID 5.00b.

But I don't understand why settings epout->doepctl = DOEPCTL_SNAK; will fix the issue, since doepctl should be reset in edpt_disable (those who left will be overwritten in edpt_activate). Unless there are some additional flag needs to be cleared but it's not listed in https://docs.nordicsemi.com/bundle/ps_nrf54LM20A/page/usbhs.html#register.DOEPCTL1

Could you check doepctl value before/after edpt_activate call w/wo your fix ?

Disabling an endpoint without following $7.5.1 procedure is not a good idea.

@gab-k
Copy link
Copy Markdown
Contributor Author

gab-k commented Mar 28, 2026

I investigated further, it seems the issue is not that edpt_activate() fails to rewrite DOEPCTL. The problem is that edpt_disable() does not clear DOEPCTL.USBAEP. The edpt_activate() function sets USBAEP again via depctl.active = 1, while edpt_disable() only disables the endpoint (DOEPCTL_EPDIS) and leaves USBAEP set.

You are right, its probably better to not skip the normal disable flow. I changed the sequence to:

edpt_disable() -> clear DOEPCTL.USBAEP -> edpt_activate()

This only applies for ISO OUT when the MPS changes.

The actual reason why the DOEPCTL = DOEPCTL_SNAK write worked fine was that it also cleared USBAEP.

For completeness, here are the DOEPCTL values before and after edpt_activate() for the current fix and without the fix:

DOEPCTL before edpt_activate() after edpt_activate()
with fix 0x0005001C 0x00058038
without fix 0x0005801C 0x00058038

The only difference is bit 15 being cleared before re-activation.

I considered putting this into the generic OUT path in edpt_disable(), since that function currently leaves DOEPCTL.USBAEP set. However, I’m not really confident enough to say that changing edpt_disable() globally is safe, the issue seems to be specific to ISO OUT re-activation with changed MPS on this DWC2 revision.

@HiFiPhile
Copy link
Copy Markdown
Collaborator

I think it's safe to clear ActEP in edpt_disable, linux also clear it in EP disable, although the databook doesn't mention it much.

Is it work for you :

diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 8685ec6dc..e32dedd1b 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -345,6 +345,11 @@ static void edpt_disable(uint8_t rhport, uint8_t ep_addr, bool stall) {
       dwc2->dctl |= DCTL_CGONAK;
     }
   }
+
+  // Clear ActEP
+  if (!stall && epnum != 0) {
+    dep->ctl &= ~EPCTL_USBAEP;
+  }
 }
 
 // Since this function returns void, it is not possible to return a boolean success message

@gab-k
Copy link
Copy Markdown
Contributor Author

gab-k commented Mar 30, 2026

Alright, sounds good to me! This probably is the most elegant solution anyway I was just afraid that it might break something somewhere else.

@HiFiPhile HiFiPhile merged commit 5b3468e into hathach:master Mar 30, 2026
290 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants