Skip to content

dwc2: preserve EP0 status completion before SETUP#3634

Draft
Alex-Schaefer wants to merge 2 commits into
hathach:masterfrom
HKM-Messtechnik:fix/Dwc2Stm32U5
Draft

dwc2: preserve EP0 status completion before SETUP#3634
Alex-Schaefer wants to merge 2 commits into
hathach:masterfrom
HKM-Messtechnik:fix/Dwc2Stm32U5

Conversation

@Alex-Schaefer
Copy link
Copy Markdown

@Alex-Schaefer Alex-Schaefer commented May 9, 2026

Observed Problem

  • DWC2 can report EP0 OUT xfer_complete and setup_phase_done together.
  • TinyUSB previously processed SETUP first and returned, dropping the co-latched EP0 OUT status completion.
  • That lost the previous control-read OUT status ZLP ACK.
  • Observed DFU symptom: next DNLOAD SETUP arrived while DFU was still in DFU_DNLOAD_SYNC, causing LIBUSB_ERROR_PIPE.
  • A/B testing confirmed root cause: unpatched failed, patched passed, re-patched passed.

Implemented Fix

  • Queue EP0 OUT zero-length dcd_event_xfer_complete() before dcd_event_setup_received() when both are co-reported.
  • Added ep0_out_zlp_armed latch so the synthetic completion only fires if EP0 OUT ZLP was actually armed.
  • Latch is cleared on bus reset, synthetic completion, normal EP0 OUT completion, and superseding SETUP.

Remaining Upstream-Polish Question

  • dcd_edpt_xfer_fifo() does not update ep0_out_zlp_armed.
  • This does not affect our DFU/control path because EP0 control transfers go through dcd_edpt_xfer().

Current status

Hardware validated on STM32U5G9J custom PCB with USB CDC, USB DFU device and HS USB host for flash drives. Should be in accordance to ST RM0456 for STM32U5, section 73 USB on-the-go high-speed (OTG_HS).

No further tests/validation currently done.

More tests with STM32U5G9J-DK2 evalboard and reproducable examples are planned, but I don't have time for it now.

On STM32 DWC2, SETUP phase done and EP0 OUT transfer complete can be
reported together. Processing SETUP first can overwrite control state
before the previous zero-length OUT status stage is acknowledged, which
causes DFU DNLOAD/GETSTATUS traffic to lose the status ACK and stall.

Queue the EP0 OUT zero-length transfer completion before queuing the
SETUP event when the endpoint has no pending OUT data and total_len is
zero. This keeps TinyUSB control-transfer ordering intact for the
combined interrupt case.
Copilot AI review requested due to automatic review settings May 9, 2026 07:14
@Alex-Schaefer Alex-Schaefer marked this pull request as draft May 9, 2026 07:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adjusts the DWC2 (STM32) device controller driver to preserve TinyUSB control-transfer event ordering when DWC2 reports EP0 OUT transfer completion and SETUP phase done in the same interrupt, avoiding loss of the EP0 OUT status-stage ACK (notably impacting DFU DNLOAD/GETSTATUS sequences).

Changes:

  • Detect the combined setup_phase_done + xfer_complete interrupt case on EP0 OUT with zero total length.
  • Enqueue an EP0 OUT zero-length xfer_complete event before enqueuing the SETUP event (in both slave and DMA paths).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) {
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
const bool ep0_status_complete_before_setup = (epnum == 0) && doepint_bm.setup_phase_done &&
doepint_bm.xfer_complete &&
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
const bool ep0_status_complete_before_setup = (epnum == 0) && doepint_bm.setup_phase_done &&
doepint_bm.xfer_complete &&
Track when an EP0 OUT zero-length transfer is actually armed and require that state before synthesizing a status-stage completion ahead of a co-reported SETUP event.

This preserves the validated status-before-SETUP ordering fix while avoiding stale zero-length state from producing spurious EP0 OUT completions.
@github-actions
Copy link
Copy Markdown

MemBrowse Memory Report

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

target .text .rodata .data .bss total % diff
raspberrypi_cm4/cdc_msc 57,116 → 57,500 (+384) 744 → 4,456 (+3,712) 57,860 → 61,956 (+4,096) +7.1%
raspberrypi_zero/audio_4_channel_mic 57,040 → 57,420 (+380) 1,767 → 5,483 (+3,716) 58,807 → 62,903 (+4,096) +7.0%
raspberrypi_zero/dynamic_configuration 61,212 → 61,592 (+380) 1,147 → 4,863 (+3,716) 62,359 → 66,455 (+4,096) +6.6%
xmc4500_relax/dfu_runtime 12,048 → 12,176 (+128) 24,148 → 24,404 (+256) +1.1%
xmc4500_relax/hid_multiple_interface 13,712 → 13,840 (+128) 27,476 → 27,732 (+256) +0.9%
xmc4500_relax/hid_boot_interface 13,732 → 13,860 (+128) 27,516 → 27,772 (+256) +0.9%
sipeed_longan_nano/audio_test 16,412 → 16,604 (+192) 1,612 → 1,616 (+4) 21,294 → 21,490 (+196) +0.9%
xmc4500_relax/cdc_dual_ports 14,908 → 15,036 (+128) 29,868 → 30,124 (+256) +0.9%
xmc4500_relax/printer_to_cdc 14,968 → 15,096 (+128) 29,984 → 30,240 (+256) +0.9%
xmc4500_relax/audio_test 15,060 → 15,188 (+128) 30,192 → 30,448 (+256) +0.8%

@HiFiPhile
Copy link
Copy Markdown
Collaborator

Thank you, with the test method in #3643 DFU has no more error. Are you still working on it ?

@Alex-Schaefer
Copy link
Copy Markdown
Author

Nice to hear!

I have it currently paused, need to finish a MVP product first, then I can test in more detail and look further into it, might take a week or two.

But I can briefly try with the PR branch you linked on Monday if our functionality on STM32U5 with DFU still works and report back.

@Alex-Schaefer
Copy link
Copy Markdown
Author

@HiFiPhile I now had time to take a look:

Using the branch from #3643, DFU works. But, the fix is not in this PR, I think the actual fix is in previous #3640 with DWC2 changes, using commit 1f662779c44dd913f2c26f9bddc4f37ee70f16dc my DFU functionality also works.

@HiFiPhile
Copy link
Copy Markdown
Collaborator

Hum #3640 only applies to ISO transfer.

Actually I can see the difference with/without your patch with the stress test posted in #3643

image

@Alex-Schaefer
Copy link
Copy Markdown
Author

Well, I now realised my dfu-util LIBUSB_ERROR_PIPE error is not reproducable reliable.

I tried with different USB hubs and cables, but currently even the old commit 4c7fd70e5, which has previously not worked for my DFU application, works just fine. I guess since this issue is timing related it could make sense that it's not always reproducable.

Since I don't have a USB sniffer to actually look into the protocol details, I can only test functionality. I'll see if I can find out what needs to change to trigger the DFU failure again. Maybe with delaying the USB interrupt handling within my application I could force the error again.

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.

3 participants