Skip to content

[BACKPORT] fix(arch/otg): unmask WKUP so USB resume reaches the class driver - #393

Merged
dakejahl merged 1 commit into
PX4:px4_firmware_nuttx-10.3.0+from
dakejahl:backport/otg-device-unmask-wkup
Aug 25, 2026
Merged

[BACKPORT] fix(arch/otg): unmask WKUP so USB resume reaches the class driver#393
dakejahl merged 1 commit into
PX4:px4_firmware_nuttx-10.3.0+from
dakejahl:backport/otg-device-unmask-wkup

Conversation

@dakejahl

Copy link
Copy Markdown
Contributor

Summary

Backport of apache/nuttx#19936.

The DWC2-derived USB device drivers enable USBSUSP in GINTMSK but not WKUP, and each one ANDs GINTSTS with GINTMSK at the top of its ISR before dispatching. The WKUP branch and the *_resumeinterrupt() it calls are dead code: CLASS_SUSPEND is delivered, CLASS_RESUME never is.

For CDC/ACM that is fatal. cdcacm_suspend() calls uart_connected(&priv->serdev, false), after which serial.c refuses every board-side open() and write() with -ENOTCONN. The cdcacm_resume() that would clear it never runs, so the port is unusable for the rest of the boot while the device stays enumerated.

Adding WKUP to the mask makes the existing handler reachable. The status bit is already acknowledged in the same read/ack cycle as the other writable interrupts, so unmasking it cannot latch.

The upstream PR also carries a host-side test script; it is left out here.

Impact

Fixes PX4/PX4-Autopilot#28340 once the submodule is bumped.

Affects every PX4 board with CONFIG_CDCACM=y, which is 152 in-tree NuttX configs: 113 on STM32H7, 21 on STM32F7, 18 on STM32 F4. CDCACM selects SERIAL_REMOVABLE, so no board opts into this — they all inherit it.

Linux hosts trigger it by default: with power/control=auto and the usual autosuspend_delay_ms=2000, closing the tty is enough. QGroundControl holds the port open continuously and so never sees it; CLI tooling that opens, reads and closes — mavlink_shell.py, pymavlink scripts, production test benches — loses USB MAVLink on the first close and does not get it back without a reboot.

No configuration, API or wire-format change. One extra interrupt source per suspend/resume, handled by code already present.

Testing

Host: Linux 7.0.0-28-generic x86_64, xhci_hcd, usbcore.autosuspend=2.
Board: ARK FMU v6X (STM32H743), ark_fmu-v6x_default, console on a separate STLINK-V3 VCP so it survives the CDC port dying.

Only the STM32H7 path was exercised on hardware. The other five drivers carry a line-for-line copy of the same GINTMSK initialisation and the same masked-off resume handler.

Each cycle forces a verified USB runtime suspend, resumes the device by opening the port, reads for 2 s, then asks the board over its console whether the CDC port is writable again.

Before:

[1] suspended=True   read=12179   tail=0       board-side open failed: -ENOTCONN
[2] suspended=True   read=0       tail=0       board-side open failed: -ENOTCONN
[3] suspended=True   read=0       tail=0       board-side open failed: -ENOTCONN
[4] suspended=True   read=0       tail=0       board-side open failed: -ENOTCONN
[5] suspended=True   read=0       tail=0       board-side open failed: -ENOTCONN

FAIL: the link died after suspend and did not come back.

read is bytes over the whole 2 s window, tail bytes in its last second. The 12179 on the first cycle is the stale CONFIG_CDCACM_TXBUFSIZE=12000 buffer flushing on resume, not a working link.

After:

[1] suspended=True   read=40898   tail=23316   board-side open ok
[2] suspended=True   read=42441   tail=22020   board-side open ok
[3] suspended=True   read=36945   tail=20372   board-side open ok
[4] suspended=True   read=44866   tail=22897   board-side open ok
[5] suspended=True   read=37565   tail=20480   board-side open ok

PASS: the link recovered from every suspend.

On the unpatched build, one host suspend is enough to kill the port for good:

nsh> echo hi > /dev/ttyACM0
nsh: echo: open failed: Transport endpoint is not connected

Patched, the same command succeeds once the host resumes, and a 22 s suspend is followed by full recovery: mavlink status instance #2 back to tx 21946 B/s, txerr 0.0 B/s, 217 kB read by the host over the following 10 s.

Build-tested beyond STM32H7: cubepilot_cubeyellow (stm32f7), airmind_mindpx-v2 (stm32 OTGFS), matek_gnss-m9n-f4 (stm32 OTGHS).

… driver

Every DWC2-derived USB device driver enables USBSUSP in GINTMSK but not
WKUP, and every one of them ANDs GINTSTS with GINTMSK before dispatch.
The resume handler is therefore unreachable: CLASS_SUSPEND is delivered
on suspend, CLASS_RESUME never is.

For CDC/ACM that is fatal. cdcacm_suspend() calls uart_connected(false),
after which serial.c refuses every open() and write() with -ENOTCONN,
and the cdcacm_resume() that would clear it never runs. On a Linux host
with the default USB autosuspend (power/control=auto, 2000 ms) simply
closing the tty is enough to trip it, and the port stays dead for the
rest of the boot while the device remains enumerated.

Verified on STM32H7 (ARK FMU v6X): before, one host suspend leaves the
CDC/ACM port permanently -ENOTCONN; after, five forced suspend/resume
cycles all recover with the MAVLink stream intact. The remaining
drivers carry a line-for-line copy of the same initialisation.

Upstream: apache/nuttx#19936

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
@dakejahl
dakejahl merged commit c7898f5 into PX4:px4_firmware_nuttx-10.3.0+ Aug 25, 2026
dakejahl added a commit to PX4/PX4-Autopilot that referenced this pull request Aug 25, 2026
Brings in PX4/NuttX#393 (backport of apache/nuttx#19936). The DWC2-derived
USB device drivers masked off the WKUP interrupt, so CLASS_RESUME was never
delivered. cdcacm_suspend() marks the serial device disconnected and the
matching resume never runs, leaving the CDC/ACM port returning -ENOTCONN for
the rest of the boot after the first host autosuspend.

Assisted-by: Claude:claude-opus-5[1m]

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
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.

[Bug] USB MAVLink dies permanently after the host closes the port (Linux USB autosuspend + CONFIG_SERIAL_REMOVABLE)

1 participant