Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

UDD_Send() (uotghs_device.c) has an unbounded busy-wait on TXINI — permanent MCU hang if host doesn't set it (native USB CDC line-state related)

Open
#153 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
52/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
c, cpp
Domain
embedded-iot

Research direction

Start in system/libsam/source/uotghs_device.c by reading UDD_Send, UDD_WaitIN, UDD_WaitOUT, and UDD_WaitForINOrOUT, then trace their callers through USBCore.cpp and CDC.cpp. Reproduce the native CDC hang with the provided sketch and host setup; done means a missing USB flag no longer spins forever and the affected send/wait paths report or abort the transfer consistently.

Written by the indexing model from the issue text.

Description

Type: Bug USB: CDC serial

Summary

UDD_Send() in system/libsam/source/uotghs_device.c contains an unbounded busy-wait loop with no timeout, waiting for the TXINI (Transmit IN complete) flag on the target endpoint. If the host does not set this flag (e.g. around a USB line-state change such as DTR/RTS toggling on the native CDC port), the MCU hangs permanently in this loop, with interrupts still enabled and no way to recover except a full reset/power cycle.

uint32_t UDD_Send(uint32_t ep, const void* data, uint32_t len)
{
    const uint8_t *ptr_src = data;
    uint8_t *ptr_dest = (uint8_t *) &udd_get_endpoint_fifo_access8(ep);
    uint32_t i;

    while( UOTGHS_DEVEPTISR_TXINI != (UOTGHS->UOTGHS_DEVEPTISR[ep] & UOTGHS_DEVEPTISR_TXINI )) {}
    ...

(https://github.com/arduino/ArduinoCore-sam/blob/master/system/libsam/source/uotghs_device.c)

This is called from USBD_Send() in USBCore.cpp, which is the underlying function used by every SerialUSB.write()/println() call (via Serial_::write in CDC.cpp), as well as HID reports.

Related but similarly unbounded waits exist elsewhere in the same file (UDD_WaitIN, UDD_WaitOUT, UDD_WaitForINOrOUT) — all are plain while(...); with no timeout or bail-out condition.

How I found this

I have an Arduino Due (genuine, Rev3) that reliably hangs during extended native-USB operation on specific Windows 11 hosts (newer Intel xHCI controllers — Tiger Lake/Alder Lake generation). Full investigation history, including USB traffic capture (Wireshark/USBPcap) and elimination of third-party software as a cause, is documented here: https://github.com/arduino/ArduinoCore-sam/issues/71#issuecomment-5382181174

Using an ST-Link V2 + OpenOCD + GDB attached via the DEBUG (SWD) header, I was able to halt the MCU while it was hung (native USB unresponsive, but the rest of loop() — an independent LED blink — still running, i.e. not a full lockup: DHCSR.S_LOCKUP was 0, core was in normal Thread mode with interrupts enabled).

Register/symbol dump at the hang:

pc = 0x00081b06   -> inside UDD_Send+18
lr = 0x00080e99   -> called from USBD_Send() at USBCore.cpp:204
xpsr = 0x01000000 -> Thread mode, not inside an ISR
primask/faultmask = 0 -> interrupts not disabled

info line *0x00080e99 resolved cleanly to USBCore.cpp:204 inside USBD_Send, confirming the call chain Serial_::write → USBD_Send → UDD_Send, stuck spinning on the TXINI wait with no way out.

Reliable(-ish) repro

A minimal sketch (no HID/Joystick needed, plain CDC is enough):

bool state;
uint32_t Tim;
uint32_t TimLog;

void setup() {
  pinMode(13, OUTPUT);
  delay(1000);
  SerialUSB.println(1);
}

void loop() {
  if (millis() - Tim >= 500) {
    Tim = millis();
    digitalWrite(13, state);
    state = !state;
  }
  if (millis() - TimLog >= 5000) {
    TimLog = millis();
    SerialUSB.println(millis());
  }
}
  • Left running for ~15-25 minutes on an affected Windows 11 host: hangs organically.
  • Faster/more reliable trigger: from the host, open the native CDC COM port via .NET System.IO.Ports.SerialPort without setting DtrEnable/RtsEnable to true before .Open() — this toggles the CDC line state in a way that reliably triggers the hang within seconds in my testing.
  • Does not reproduce on Linux or in Windows Safe Mode on the same physical hardware — consistent with the theory that it's a host-timing-dependent race on whether TXINI gets set before the firmware's next attempt to send.

Suggested direction for a fix

At minimum, every unbounded while(...) wait in uotghs_device.c (UDD_Send, UDD_WaitIN, UDD_WaitOUT, UDD_WaitForINOrOUT) should have a bounded timeout (e.g. based on millis() or a cycle counter) and return an error/abort the transfer instead of spinning forever when the expected hardware flag never arrives. I understand this changes the function signatures/call sites (currently void/unconditional uint32_t return used as byte count, not status) — happy to discuss/help test a patch if a maintainer can advise on the preferred approach (e.g. optional timeout parameter, or a global "USB write timeout" behavior similar to how other Serial implementations use setTimeout()).

Environment

  • Board: Arduino Due (genuine, Rev3, native USB port)
  • Core: Arduino SAM Boards (32-bit ARM Cortex-M3), package version installed via Boards Manager — 1.6.12
  • Debug setup: ST-Link V2 (clone) via the onboard 4-pin SWD DEBUG header, OpenOCD 0.12.0, arm-none-eabi-gdb 4.8.3 (bundled with Arduino IDE's SAM toolchain)
  • Host: Windows 11 (build 26100), Intel USB 3.10/3.20 xHCI controllers (PCI\VEN_8086&DEV_51ED, PCI\VEN_8086&DEV_461E)

Happy to provide more register dumps, the .elf/.map files, or the full Wireshark capture if useful.

Dominant language
HTML
Stars
91
Forks
112
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from arduino/ArduinoCore-sam

All issues in arduino/ArduinoCore-sam

Similar issues

More Embedded & IoT issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.