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)

Abierto
#153 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
52/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
c, cpp
Área
embedded-iot

Línea de trabajo

Empieza en system/libsam/source/uotghs_device.c leyendo UDD_Send, UDD_WaitIN, UDD_WaitOUT y UDD_WaitForINOrOUT; después, sigue sus llamadores a través de USBCore.cpp y CDC.cpp. Reproduce el bloqueo nativo de CDC con el sketch y la configuración del host proporcionados; se considerará terminado cuando la ausencia de un flag USB ya no provoque un bucle infinito y las rutas de envío/espera afectadas informen del resultado de la transferencia o la aborten de forma coherente.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
HTML
Estrellas
91
Forks
111
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de arduino/ArduinoCore-sam

Todos los issues de arduino/ArduinoCore-sam

Issues similares

Más issues de Embedded & IoT

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.