Skip to content

feat(dcd/dwc2): Add Periodic Transfer Interrupt feature - #3859

Merged
HiFiPhile merged 1 commit into
hathach:masterfrom
espressif:feat/dwc2_dcd_pdi
Sep 10, 2026
Merged

HiFiPhile merged 1 commit into
hathach:masterfrom
espressif:feat/dwc2_dcd_pdi

Conversation

@tore-espressif

Copy link
Copy Markdown
Contributor

DWC2 DCD: Periodic Transfer Interrupt feature

Enable DCTL.IgnrFrmNum in Buffer DMA so ISOC endpoints can be armed without even/odd frame matching.
This is required to schedule ISOC OUT on alternating 7/8 ms intervals (Bluetooth HCI 7.5 ms) while keeping bInterval = 1.

Gated by CFG_TUD_DWC2_PTI_ENABLE (default off, DMA only). When on, mask IncompISOCIN and report dropped ISOC OUT (PktDrpSts) as a failed transfer.

Enable DCTL.IgnrFrmNum in Buffer DMA so ISOC endpoints can be armed
without even/odd frame matching. This is required to schedule ISOC OUT
on alternating 7/8 ms intervals (Bluetooth HCI 7.5 ms) while keeping
bInterval = 1.
Gated by CFG_TUD_DWC2_PTI_ENABLE (default off, DMA only). When on,
mask IncompISOCIN and report dropped ISOC OUT (PktDrpSts) as a failed
transfer.
@tore-espressif

Copy link
Copy Markdown
Contributor Author

@github-actions

Copy link
Copy Markdown

Hardware-in-the-loop (HIL) Test Report

hfp.json

✅ 56 passed · ❌ 0 failed · ⚪ 0 skipped · blank not run

Board usbtest cdc_msc_throughput audio_test_freertos cdc_dual_ports cdc_msc cdc_msc_freertos dfu dfu_runtime hid_boot_interface hid_generic_inout midi_test msc_dual_lun mtp printer_to_cdc duration
lpcxpresso43s67 ✅ 30/30 ✅ C 12/9.8M M 32.8/31M 138s
stm32l412nucleo ✅ 30/30 ✅ C 499/511k M 511/511k 140s
stm32f746disco ✅ 30/30 ✅ C 13.6/12.9M M 20.5/30.5M 120s
stm32f746disco-DMA ✅ 30/30 ✅ C 15/13.5M M 26.1/33.6M 86s

tinyusb-esp.json

✅ 0 passed · ❌ 24 failed · ⚪ 0 skipped · blank not run

Board usbtest msc_file_explorer_freertos audio_test_freertos cdc_msc_freertos device_info hid_composite_freertos duration
espressif_p4_function_ev 3s
espressif_p4_function_ev-DMA 3s
espressif_s3_devkitm 3s
espressif_s3_devkitm-DMA 3s

tinyusb.json

✅ 298 passed · ❌ 83 failed · ⚪ 17 skipped · blank not run

Board usbtest cdc_msc_throughput msc_file_explorer msc_file_explorer_freertos audio_test_freertos cdc_dual_ports cdc_msc cdc_msc_freertos cdc_msc_hid device_info dfu dfu_runtime hid_boot_interface hid_generic_inout host_info_to_device_cdc midi_test msc_dual_lun mtp printer_to_cdc duration
raspberry_pi_pico_w 5s
raspberry_pi_pico2 5s
frdm_k64f 8s
raspberry_pi_pico 10s
nanoch32v203-fsdev 8s
nanoch32v203-usbfs 8s
adafruit_fruit_jam 10s
stm32g0b1nucleo ✅ 30/30 ✅ C 515/495k M 551/531k 148s
ek_tm4c123gxl ✅ 30/30 ✅ C 506/511k M 514/511k 149s
lpcxpresso55s28 ✅ 30/30 ✅ C 8.6/8.2M M 31.2/31.4M 154s
metro_m4_express ✅ 30/30 ✅ C 508/511k M 510/511k 183s
lpcxpresso11u37 ✅ 30/30 ✅ C 507/273k M 510/511k 185s
ch32v103r_r1_1v0 ✅ 30/30 ✅ C 506/508k M 550/510k 190s
stm32u083nucleo ✅ 30/30 ✅ C 514/474k M 523/513k 188s
ra4m1_ek ✅ 30/30 ✅ C 504/511k M 511/511k 264s
stm32f407disco ✅ 30/30 ✅ C 508/576k M 550/600k 264s
nrf54lm20dk ✅ C 1.9/4.1M M 6.4/8M 116s
feather_nrf52840_express ✅ 30/30 ✅ C 454/513k M 532/516k 286s
stm32l476disco ✅ 30/30 ✅ C 514/511k M 515/511k 292s
max32666fthr ❌ 29/30 ✅ C 7/14.2M M 12.9/19M 304s
stm32f072disco ✅ 30/30 ✅ C 468/299k M 510/484k 310s
mimxrt1064_evk ✅ 30/30 ✅ C 23.6/16.5M M 38.1/33.7M 1365 KB/s 1322 KB/s 215s
ch32v307v_r1_1v0-usbhs ✅ 30/30 ✅ C 7/8.1M M 23.8/14.2M 257s
ch32v307v_r1_1v0-usbfs ✅ 30/30 ✅ C 520/538k M 520/512k 109s
stm32h743nucleo ✅ 30/30 ✅ C 601/585k M 581/596k 131s
stm32h743nucleo-DMA ✅ 30/30 ✅ C 507/511k M 512/517k 268s
ch582m_evt ✅ 30/30 ✅ C 231/220k M 480/481k 256s
stm32f723disco ✅ 30/30 ✅ C 507/511k M 511/518k 13797 KB/s 3942 KB/s 264s
stm32f723disco-DMA ✅ 30/30 ✅ C 510/511k M 513/512k 14169 KB/s 3942 KB/s 158s

@github-actions

Copy link
Copy Markdown

MemBrowse Memory Report

Top 1 targets by memory change (%) (out of 2512 targets) View Project Dashboard →

target .text .rodata .data .bss total % diff
raspberrypi_zero/mtp 62,612 → 62,728 (+116) 6,239 → 6,127 (-112) 68,851 → 68,855 (+4) +0.0%

@HiFiPhile

Copy link
Copy Markdown
Collaborator

Hi @tore-espressif , didn't know HCI does this weird stuff, I got some review:
P1 — Incorrect PTI OUT transfer sizing.
edpt_schedule_packets() programs XferSize = total_bytes. DWC2 Programming Guide §9.3.5, PDF pp. 274–278, requires PTI OUT XferSize to be packet_count × MPS, with each packet rounded to a DWORD boundary. For Bluetooth altsetting 6 with MPS 63, one packet therefore occupies 64 DMA bytes, not 63. The implementation needs separate logical-length and padded-DMA-span accounting, plus guaranteed padded buffer capacity. See DWC2 Programming Guide.

P1 — Packet-drop detection is incomplete.
handle_epout_dma() checks only PktDrpSts. Programming Guide p. 275 warns that consecutive drops can produce XferComplete without PktDrpSts; remaining PktCnt/XferSize must also be inspected and the transfer reprogrammed. The current code can report that case as success.

@tore-espressif

Copy link
Copy Markdown
Contributor Author

@HiFiPhile Thank you for the review!

Ad point 1: If I understand it correctly, this problem is not PTI specific, it is valid also for mode currently implemented in DCD?

Refactoring the way how transfer sizes are calculated seems too risky to me... I'd suggest users to always use MPS = 64 to workaround this problem.

In case this is not acceptable to you, feel free to close this PR

@HiFiPhile

HiFiPhile commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

If I understand it correctly, this problem is not PTI specific, it is valid also for mode currently implemented in DCD?

You are right I've overlooked it.

@tore-espressif

I read the HCI transport spec and I think we don't need PTI. The 7.5 ms value is the cadence of non-empty mSBC HCI packets, not the USB endpoint polling interval:

  • For ISO OUT just arm the transfer every 1ms, rearm if no data received.
  • For ISO IN, queue payload when available and a ZLP otherwise.

From Codex:
Linux keeps the USB endpoint on a 1 ms schedule:

Thus Linux maintains 1 ms USB slots while placing non-empty HCI packets every 7 or 8 slots.

@gm-jiang

gm-jiang commented Sep 9, 2026

Copy link
Copy Markdown

Thus Linux maintains 1 ms USB slots while placing non-empty HCI packets every 7 or 8 slots.

@HiFiPhile Yes, for linux platform, linux usb host core stack schedule the ISO packet every 1ms, like this sequence

Frame 0: 63 bytes
Frame 1: 0 bytes
Frame 2: 0 bytes
Frame 3: 0 bytes
Frame 4: 0 bytes
Frame 5: 0 bytes
Frame 6: 0 bytes
Frame 7: 63 bytes
Frame 8: 0 bytes
Frame 9: 0 bytes
Frame 10: 0 bytes
Frame 11: 0 bytes
Frame 12: 0 bytes
Frame 13 0 bytes
Frame 14 0 bytes
Frame 15: 63 bytes
Frame 16: 0 bytes
Frame 17: 0 bytes
Frame 18: 0 bytes
Frame 19: 0 bytes
Frame 20: 0 bytes
Frame 21: 0 bytes
Frame 22: 63 bytes
Frame 23: 0 bytes
Frame 24: 0 bytes
Frame 25: 0 bytes
Frame 26: 0 bytes
Frame 27: 0 bytes
Frame 28: 0 bytes
Frame 29: 0 bytes
Frame 30: 63 bytes

But for windows platform, windows usb host usb stack schedule like this

Frame 0 : 63 bytes
+7
Frame 7 : 63 bytes
+8
Frame 15: 63 bytes
+7
Frame 22: 63 bytes
+8
Frame 30: 63 bytes
+7
Frame 37: 63 bytes
+8
Frame 45: 63 bytes
+7

Using current tinyusb stack, we always lose one fixed ISO frame packet. This is because the data does not alternate changes as odd/even frames, but instead of odd/even/even/odd/odd/even/even/odd/odd/.../

@HiFiPhile

Copy link
Copy Markdown
Collaborator

But for windows platform, windows usb host usb stack schedule like this
Frame 0 : 63 bytes
+7
Frame 7 : 63 bytes
+8

Interesting to see how Windows abuse EHCI

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The new DMA interrupt paths lack PTI-enabled test coverage.

Pull request overview

Adds opt-in DWC2 periodic transfer interrupt support for DMA-based isochronous transfers.

Changes:

  • Adds CFG_TUD_DWC2_PTI_ENABLE.
  • Configures DCTL.IgnrFrmNum and thresholding.
  • Handles incomplete IN and dropped OUT transfers.
File summaries
File Description
src/tusb_option.h Defines the PTI configuration option.
src/portable/synopsys/dwc2/dwc2_type.h Defines the ignore-frame-number register bit.
src/portable/synopsys/dwc2/dcd_dwc2.c Implements PTI setup and interrupt handling; PTI-enabled DMA coverage remains absent.
Review details

Suppressed comments (1)

src/portable/synopsys/dwc2/dcd_dwc2.c:1080

  • The opt-in path is not compiled or exercised by any existing test configuration: the DWC2 DMA HIL variants only define CFG_TUD_DWC2_DMA_ENABLE, even though they already run the isochronous audio_test_freertos example (for example, test/hil/tinyusb.json:90-99 and :422-429). Please add a PTI-enabled DMA variant or equivalent targeted coverage so the DCTL setup, incomplete-IN masking, and dropped-OUT failure path cannot regress unnoticed.
#if CFG_TUD_DWC2_PTI_ENABLE
      // PTI: XferCompl can mean a dropped ISOC OUT packet (CRC/RxFIFO), not a good payload
      if (doepint_bm.iso_packet_drop) {
        const dwc2_depctl_t depctl = {.value = epout->doepctl};
        if (depctl.type == DEPCTL_EPTYPE_ISOCHRONOUS) {
          dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_FAILED, true);
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@HiFiPhile
HiFiPhile merged commit ad54f72 into hathach:master Sep 10, 2026
319 of 321 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants